EP-0247
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
- 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
How to get information of UPS's firmware verison?
- Execute following command in terminal:
i2cget -y 1 0x17 0x01 b
How to get output voltage value?
- Execute following command in terminal:
i2cget -y 1 0x17 0x02 w
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:
How to read device status by using C Language
- Make sure Raspberry Pi has `gcc` compiler
gcc -v
- 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, ®, 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, ®, 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:
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.