EP-0247

From 52Pi Wiki
Jump to navigation Jump to search

UPS for Raspberry Pi Zero/Zero W/Zero 2 W

Descriptions

The Raspberry Pi Zero-sized power management module(AKA UPS for zero) is a compact and versatile device designed to provide efficient power supply management for Raspberry Pi Zero, Zero W, and Zero 2W devices. With its dedicated pogo pin connectors, this module ensures seamless integration and power management for the Raspberry Pi Zero series. Additionally, it is compatible with other popular single-board computers such as the NanoPi and OrangePi 5, thanks to its adaptable design.

Communication with the host device is facilitated through the I2C protocol, allowing for easy integration and data exchange. The module is equipped with three INA219 chips, enabling the monitoring of voltage and current across the USB-C charging port, battery, and the output to the Raspberry Pi, providing valuable insights into power consumption and efficiency.

Features

  • I2C Communication: Utilizes the I2C protocol for communication with the host device, ensuring compatibility and ease of integration.
  • Multiple INA219 Chips: Three INA219 chips on board for monitoring voltage and current at the USB-C port, battery, and output to the Raspberry Pi.
  • Power Monitoring: Provides real-time data on power consumption, including voltage, current, and power usage.
  • Compatibility: Designed with a Raspberry Pi Zero series-specific positioning hole but also compatible with other SBCs like NanoPi and OrangePi 5.
  • Configurable Settings: Offers various registers accessible via I2C for configuring the UPS power module to suit different application scenarios.
  • Extensive Information Access: Capable of reading WHO_AM_I, firmware version, output voltage, input voltage, battery voltage, MCU voltage, temperature, battery protection voltage, shutdown countdown, auto-start status, startup voltage settings, runtime, button status, OTA mode status, and a unique user ID serial number.

Specifications

  • Form Factor: Compact, designed to fit the size of a Raspberry Pi Zero.
  • Power Input: USB-C charging port.
  • Multifunctional Power switch: Short press: turn on UPS, Long press: Force turn off/Force turn on UPS, Press for 45s + entering to OTA Mode
  • Voltage Monitoring Points: USB-C, battery, and output to the Raspberry Pi.
  • Current Monitoring: Real-time monitoring of current flow at various points.
  • Communication Protocol: I2C.
  • Chipset: INA219 for power monitoring.
  • Compatible Devices: Raspberry Pi Zero, Zero W, Zero 2W, NanoPi, OrangePi 5, and other similar devices.
  • Control Registers: Multiple registers for accessing and configuring module settings.
  • Safety Features: Battery protection voltage and shutdown countdown settings.
  • Operational Modes: Supports auto-start on power recovery and OTA (Over-The-Air) updates.
  • Unique Identifier: Each module has a unique user ID for identification and tracking.

This power management module is an essential accessory for Raspberry Pi enthusiasts and developers looking to enhance their projects with reliable and intelligent power control solutions.

Device address and Register mapping information

  • Device address explainations
ID Device address Function
1 Ox17 Normal mode
2 Ox18 OTA mode
3 Ox40 USB-C Input Detection(INA219-1)
4 Ox44 Battery Detection(INA219-2)
5 Ox45 Output to Pi Detection(INA219-3)
Shunt_OHMS = 0.01 

Registers table

ID Register Function Valid Value
1 0x00 WHO_AM_I Identification information Default:0xa5, Fixed, Read-only
2 0x01 Firmware Version information Fixed, Read-only
3 0x02 LOW 8bit of OUTPUT voltage NOTE:This register is 16-bit wide
4 0x03 HIGH 8bit of OUTPUT voltage
5 0x04 LOW 8bit of INPUT voltage NOTE:This register is 16-bit wide
6 0x05 HIGH 8bit of INPUT voltage
7 0x06 LOW 8bit of Battery's voltage NOTE:This register is 16-bit wide
8 0x07 HIGH 8bit of Battery's voltage
9 0x08 LOW 8bit of Onboard MCU's voltage NOTE:This register is 16-bit wide
10 0x09 HIGH 8bit of Onboard MCU's voltage
11 0x0A Temperature Junction Temperature on MCU
12 0x0B LOW 8bit of Battery Protection Voltage NOTE:This register is 16-bit wide
13 0x0C HIGH 8bit of Battery Protection Voltage
14 0x0D LOW 8bit of Shutdown Countdown NOTE:This register is 16-bit wide
15 0x0E HIGH 8bit of Shutdown Countdown
16 0x0F auto start mode Default:1, valid value: 0 - disabled, 1 - enabled
17 0x10 LOW 8bit of Auto start voltage 16-bit wide, It is threshold of auto start voltage, beyond this voltage will trigger UPS auto start if auto start mode has been setting to 1.
18 0x11 HIGH 8bit of Auto start voltage
19 0x12 LOW 8bit of runtime information 32bit wide
20 0x13 9-16bit of runtime information
21 0x14 17-24bit of runtime information 32bit wide
22 0x15 HIGH 8bit of runtime information
23 0x16 5V output status Indicates whether there is a 5V output, 1 represents there is output, and 0 represents there is no output.(0=OFF,1=ON)
24 0x17 Request upgrade status If this value is "0xAA" means entering into OTA MODE
25 0x18 LOW 8bit of UUID0 Universal Uniq Identifier(UUID0):32bit wide
26 0x19 9-16bit of UUID0
27 0x1A 17-24bit of UUID0
28 0x1B HIGH 8bit of UUID0
29 0x1C LOW 8bit of UUID1 Universal Uniq Identifier(UUID1):32bit wide
30 0x1D 9-16bit of UUID1
31 0x1E 17-24bit of UUID1
32 0x1F HIGH 8bit of UUID1
33 0x20 LOW 8bit of UUID2 Universal Uniq Identifier(UUID1):32bit wide
34 0x21 9-16bit of UUID2
35 0x22 17-24bit of UUID2
36 0x23 HIGH 8bit of UUID2
37 0x24 8Bit int protect temperature beyond this temperature will stop charging

How to check if the UPS has been recognized by SBC?

  • It can be detected by using i2c-tools in most linux distributions, here we recommend you use Raspberry Pi OS, If you encounter error like: "Command not found", please execute following command to install it.
sudo apt update 
sudo apt upgrade -y 
sudo apt -y install i2c-tools
  • Make sure your have already enabled I2C function on your Raspberry Pi.
sudo raspi-config

Navigate to Interface Options -> I2C -> Enable -> YES -> Finish.

Check Device address

  • Using following command to show the device address
i2cdetect -y 1 
Ups devices address table.png


  • Explainations:

0x17: Current UPS under normal mode. When you connect the USB-C power supply, it will charge the battery and charging LED will blink at the same time.
0x40: INA219-1, it will help you to detect the voltage, current, power, shunt_voltage on USB-C port
0x44: INA219-2, it will help you to detect the voltage, current, power, shunt_voltage on Battery
0x45: INA219-3, it will help you to detect the voltage, current, power, shunt_voltage on OUTPUT to Pi (PogoPin)

How to check the register information by using command line?

How to get information of WHO_AM_I?

  • Execute following command in terminal:
i2cget -y 1 0x17 0x00 b 
UPS get whoami.png


How to get information of UPS's firmware verison?

  • Execute following command in terminal:
i2cget -y 1 0x17 0x01 b 
UPS get version.png


How to get output voltage value?

  • Execute following command in terminal:
i2cget -y 1 0x17 0x02 w 
Ups get output voltage.png


NOTE: The data in this 16-bit register is stored in little-endian order 

How to read device status by using Pyhton script

  • Please make sure you have installed following python libraries:
  • smbus
  • pi-ina219

pip3 install smbus
pip3 install pi-ina219

  • Demo code
import smbus
from datetime import datetime


# define color
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
RESET = "\033[0m"

# bus
bus_number = 1

# init i2c bus
bus = smbus.SMBus(bus_number)

# device address and register address
device_address = 0x17

# register address
whoami = 0x00
version = 0x01
output_16_vol = 0x02
input_16_vol = 0x04
batt_16_vol = 0x06
mcu_16_vol = 0x08
temperature = 0x0A
battery_protection_voltage = 0x0B
shutdown_countdown = 0x0D
auto_start_mode = 0x0F
auto_start_voltage = 0x10
runtime = 0x12  # 32bit
sw_status = 0x16
request_upgrade = 0x17  # if 0xAA means OTA mode
uuid0 = 0x18
uuid1 = 0x1C
uuid2 = 0x20
protection_temperature = 0x24


# read register data
try:
    value1 = bus.read_byte_data(device_address, whoami)

    value2 = bus.read_byte_data(device_address, version)

    value3_l = bus.read_byte_data(device_address, output_16_vol)
    value3_h = bus.read_byte_data(device_address, output_16_vol+1)
    value3 = value3_h << 8 | value3_l

    value4_l = bus.read_byte_data(device_address, input_16_vol)
    value4_h = bus.read_byte_data(device_address, input_16_vol+1)
    value4 = value4_h << 8 | value4_l

    value5_l = bus.read_byte_data(device_address, batt_16_vol)
    value5_h = bus.read_byte_data(device_address, batt_16_vol+1)
    value5 = value5_h << 8 | value5_l

    value6_l = bus.read_byte_data(device_address, mcu_16_vol)
    value6_h = bus.read_byte_data(device_address, mcu_16_vol+1)
    value6 = value6_h << 8 | value6_l

    value7 = bus.read_byte_data(device_address, temperature)

    value8_l = bus.read_byte_data(device_address, battery_protection_voltage)
    value8_h = bus.read_byte_data(device_address, battery_protection_voltage+1)
    value8 = value8_h << 8 | value8_l

    value9_l = bus.read_byte_data(device_address, shutdown_countdown)
    value9_h = bus.read_byte_data(device_address, shutdown_countdown+1)
    value9 = value9_h << 8 | value9_l

    value10 = bus.read_byte_data(device_address, auto_start_mode)

    value11_l = bus.read_byte_data(device_address, auto_start_voltage)
    value11_h = bus.read_byte_data(device_address, auto_start_voltage+1)
    value11 = value11_h << 8 | value11_l

    value12_l8 = bus.read_byte_data(device_address, runtime)
    value12_l16 = bus.read_byte_data(device_address, runtime+1)
    value12_l24 = bus.read_byte_data(device_address, runtime+2)
    value12_l32 = bus.read_byte_data(device_address, runtime+3)
    value12 = value12_l32 << 24 | value12_l24 << 16 | value12_l16 << 8 | value12_l8

    value13 = bus.read_byte_data(device_address, sw_status)

    value14 = bus.read_byte_data(device_address, request_upgrade)

    value15_l8 = bus.read_byte_data(device_address, uuid0)
    value15_l16 = bus.read_byte_data(device_address, uuid0 + 1)
    value15_l24 = bus.read_byte_data(device_address, uuid0 + 2)
    value15_l32 = bus.read_byte_data(device_address, uuid0 + 3)
    value15 = value15_l32 << 24 | value15_l24 << 16 | value15_l16 << 8 | value15_l8

    value16_l8 = bus.read_byte_data(device_address, uuid1)
    value16_l16 = bus.read_byte_data(device_address, uuid1+1)
    value16_l24 = bus.read_byte_data(device_address, uuid1+2)
    value16_l32 = bus.read_byte_data(device_address, uuid1+3)
    value16 = value16_l32 << 24 | value16_l24 << 16 | value16_l16 << 8 | value16_l8

    value17_l8 = bus.read_byte_data(device_address, uuid2)
    value17_l16 = bus.read_byte_data(device_address, uuid2+1)
    value17_l24 = bus.read_byte_data(device_address, uuid2+2)
    value17_l32 = bus.read_byte_data(device_address, uuid2+3)
    value17 = value17_l32 << 24 | value17_l24 << 16 | value17_l16 << 8 | value17_l8

    now = datetime.now()
    print(f"------------UPS INFOR---------------")
    print(f"------------------------------------")
    print("* {}".format(now))
    print("* WHO_AM_I data: {:08x}".format(value1))
    print(f"* Version: {value2}")
    print(f"* Output voltage: {value3} mV")
    print(GREEN + f"* Input voltage: {value4} mV" + RESET)
    print(RED + f"* Battery voltage: {value5} mV" + RESET)
    print(f"* Mcu voltage: {value6} mV")
    print(f"* Temperature: {value7}°C")
    print(f"* Battery_protection_voltage: {value8} mV")
    print(f"* Shutdown_countdown: {value9}")
    print(f"* Auto_start_mode: {value10}")
    print(f"* Auto_start_voltage: {value11}")
    print(f"* Runtime: {value12} seconds")
    print(f"* Sw_status: {value13}")
    print(f"* Request_upgrade: {value14}")
    print("* UUID0: {:08x}".format(value15))
    print("* UUID1: {:08x}".format(value16))
    print("* UUID2: {:08x}".format(value17))
    print(f"------------------------------------")

except IOError as e:
    print(f"I2C communication failed: {e}")

bus.close()
  • Save it to a python scirpt file ups_status.py and execute it.
python3 ups_status.py 
  • Results:
Ups status.png


How to read device status by using C Language

  • Make sure Raspberry Pi has `gcc` compiler
 gcc -v
Gcc-info.png


  • Create a C file: main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

#define I2C_BUS "/dev/i2c-1"     // i2c bus number 
#define I2C_DEVICE_ADDR 0x17

typedef struct
{
    uint8_t WHO_AM_I;                    // Fixed value: 0xA5
    uint8_t version;                     // Version number (read-only)
    uint16_t output_voltage;             // Output voltage
    uint16_t input_voltage;              // Input voltage
    uint16_t battery_voltage;            // Battery voltage
    uint16_t mcu_voltage;                // MCU voltage
    int8_t temperature;                  // Temperature
    uint16_t battery_protection_voltage; // Battery protection voltage
    uint16_t shutdown_countdown;         // Shutdown countdown
    uint8_t auto_start_mode;             // Auto start mode on power up
    uint16_t auto_start_voltage;         // Auto start battery voltage threshold (even if powered, will start only when this voltage is reached)
    uint32_t runtime;                    // Total runtime
    uint8_t sw_status;                   // 5V OUTPUT status (0=off, 1=on)
    uint8_t request_upgrade;             // Request to enter OTA mode when set to 0xAA
    uint32_t uuid0;                      // Part of unique product ID
    uint32_t uuid1;                      // Part of unique product ID
    uint32_t uuid2;                      // Part of unique product ID
} DeviceStatus;

uint8_t g_aReceiveBuffer[255];
DeviceStatus *device_status = (DeviceStatus *)g_aReceiveBuffer;

int main()
{
    int file;
    unsigned char reg = 0x00;
    unsigned char check_byte;

    if ((file = open(I2C_BUS, O_RDWR)) < 0)
    {
        perror("Failed to open the i2c bus");
        exit(1);
    }

    if (ioctl(file, I2C_SLAVE, I2C_DEVICE_ADDR) < 0)
    {
        perror("Failed to acquire bus access and/or talk to slave");
        exit(1);
    }

    if (write(file, &reg, 1) != 1)
    {
        perror("Failed to write to the i2c bus");
        close(file);
        exit(1);
    }

    if (read(file, &check_byte, 1) != 1)
    {
        perror("Failed to read from the i2c bus");
        close(file);
        exit(1);
    }

    if (check_byte == 0xA5)
    {
        if (write(file, &reg, 1) != 1)
        {
            perror("Failed to write to the i2c bus");
            close(file);
            exit(1);
        }

        // Read 100 Bytes
        if (read(file, g_aReceiveBuffer, sizeof(DeviceStatus)) != sizeof(DeviceStatus))
        {
            perror("Failed to read from the i2c bus");
        }
        else
        {
            printf("Data read successfully\n");
        }

        printf("WHO_AM_I: 0x%02X\n", device_status->WHO_AM_I);
        printf("Version: %d\n", device_status->version);
        printf("Output Voltage: %d mV\n", device_status->output_voltage);
        printf("Input Voltage: %d mV\n", device_status->input_voltage);
        printf("Battery Voltage: %d mV\n", device_status->battery_voltage);
        printf("MCU Voltage: %d mV\n", device_status->mcu_voltage);
        printf("Temperature: %d °C\n", device_status->temperature);
        printf("Battery Protection Voltage: %d mV\n", device_status->battery_protection_voltage);
        printf("Shutdown Countdown: %d s\n", device_status->shutdown_countdown);
        printf("Auto Start Mode: %d\n", device_status->auto_start_mode);
        printf("Auto Start Voltage: %d mV\n", device_status->auto_start_voltage);
        printf("Runtime: %d s\n", device_status->runtime);
        printf("5V OUTPUT Status: %d (0=off, 1=on)\n", device_status->sw_status);
        printf("Request Upgrade: 0x%02X\n", device_status->request_upgrade);
        printf("UUID0: 0x%08X\n", device_status->uuid0);
        printf("UUID1: 0x%08X\n", device_status->uuid1);
        printf("UUID2: 0x%08X\n", device_status->uuid2);
    }
    else
    {
        printf("Received byte is not 0xA5, it is 0x%02x\n", check_byte);
    }

    // close i2c bus
    close(file);

    return 0;
}
  • Save it and compile it and run:
gcc -fpack-struct=1 -O0 -o main main.c 
./main 
  • Result:
Ups-status in C.png


How to set battery protection voltage?

NOTE:

  • 1. When we set a battery protection voltage, the system will automatically shut down if the battery voltage falls below this protection level.
  • 2. If the battery voltage does not exceed the protection voltage, normal power-on will not activate the UPS, with the intention of preserving the battery.
  • 3. To force a start, press and hold the UPS switch for about 2-3 seconds, which will clear the battery voltage protection settings and force the UPS to start.
  • 4. If compiling with the C language, it is important to prevent the compiler from rearranging structures. Add the compilation argument: `gcc -fpack-struct=1 -O0 -o XXX main.c`.

Open a terminal and typing following command to set battery protection voltage. For example: Due to the little-endian sequence on the Raspberry Pi, the two register addresses for setting the protection voltage in the device register are 0x0b and 0x0c, respectively. If we want to set 3700mV (3.7V) as the protection voltage for the lithium battery, the UPS will be shut down to protect the battery when the voltage is lower than this. Therefore, we need to convert 3700 into a hexadecimal number first. At this time, the corresponding HEX value for 3700 calculated by the computer calculator is E74. However, due to the little-endian sequence here, it will become 740E. Thus, we write the corresponding register information for the following operations:

 
i2cset -y 1 0x17 0x0b 0x74 
i2cset -y 1 0x17 0x0c 0x0E 

And then, you can use the the C code provided previously to read the result of the settings.

Setting protection voltage.png