The Raspberry Pi 5 demands up to 25W (5V/5A) via USB-C Power Delivery. When your power supply sags or your cabling introduces too much resistance, the board drops below the critical 4.63V threshold. The result? The dreaded thunderbolt icon, filesystem corruption, and dropped USB peripherals.

Relying on the OS to tell you about power issues after the fact is a reactive game. In this guide, we will build a proactive raspberry pi power monitoring system using a Texas Instruments INA219 high-side current shunt monitor. This setup reads real-time voltage and current over I2C, logs the data, and triggers a safe hardware shutdown before voltage sag corrupts your SD card.

Project Difficulty: Intermediate | Time Required: 45 Minutes | Target OS: Raspberry Pi OS (Bookworm)

Raspberry Pi Power Specs & The Under-Voltage Thresholds

Before wiring any sensors, you must understand the exact power envelopes and failure thresholds for your specific board variant. The kernel does not guess when power is low; it relies on the onboard brownout detector triggering at highly specific millivolt thresholds.

Board Variant Nominal Input Max Current Draw Undervoltage Trigger Recommended PSU
Pi 3 Model B+ 5.1V 2.5A 4.63V ± 0.05V 5.1V / 2.5A Micro-USB
Pi 4 Model B 5.1V 3.0A 4.63V ± 0.05V 5.1V / 3.0A USB-C
Pi 5 (8GB) 5.0V (PD) 5.0A 4.63V ± 0.05V 5V / 5A 27W USB-C PD
Pi Zero 2 W 5.1V 1.2A 4.63V ± 0.05V 5.1V / 2.5A Micro-USB

When the voltage drops below the trigger threshold, the kernel logs a very specific error string. If you run dmesg | grep -i voltage, you will see:

Under-voltage detected! (0x00050005)

The hex code 0x00050005 indicates both the under-voltage event and the under-voltage has occurred at least once since boot. Monitoring this via software is useful, but measuring the actual rail voltage before the Pi's internal polyfuse and PCB traces drop it further is where the INA219 shines.

Hardware: Parts List & INA219 Pin Mapping

To build this monitor, we are using the standard Adafruit INA219 breakout. However, there is a critical hardware trap you must avoid when measuring the main 5V rail of a Raspberry Pi 5.

The Shunt Resistor Trap: The standard Adafruit INA219 (Product ID: 904) ships with a 0.1Ω shunt resistor. By Ohm's Law (V = IR), if your Pi 5 pulls 5A, the shunt will drop 0.5V (5A × 0.1Ω). If your PSU outputs 5.0V, the Pi will only see 4.5V. The sensor itself will cause the undervoltage error! To monitor the main 5V rail safely, you must desolder the 0.1Ω resistor and replace it with a 0.01Ω (10mΩ) shunt, or use the sensor to monitor a downstream peripheral/HAT instead of the main input rail.

Spec-Sheet & Parts List

  • Microcontroller: Raspberry Pi 5 (8GB) or Pi 4 Model B
  • Sensor: Adafruit INA219 High Side DC Current Sensor Breakout (Modified with 10mΩ shunt for main rail, or stock 100mΩ for peripheral monitoring)
  • Power Supply: Official Raspberry Pi 27W USB-C PD Power Supply
  • Wiring: 4x Female-to-Female Dupont Jumper Wires (22 AWG silicone)

I2C Pin Mapping Table

The INA219 communicates via I2C. We will use the primary hardware I2C bus on the Pi's 40-pin header.

INA219 Breakout Pin Raspberry Pi GPIO Header (Physical Pin) Function
VIN Pin 1 (3.3V DC Power) Logic Power (Do NOT connect to 5V)
GND Pin 6 (Ground) Common Ground
SDA Pin 3 (GPIO 2 / SDA.1) I2C Data Line
SCL Pin 5 (GPIO 3 / SCL.1) I2C Clock Line

Wiring Note: The INA219 Vin pin powers the I2C logic chip and must be connected to the Pi's 3.3V pin. The high-side load voltage (up to 26V) is measured via the screw terminals on the breakout board, completely isolated from the logic VCC.

Python Power Monitor & Auto-Shutdown Code

This script targets the Raspberry Pi 4 and 5 running Raspberry Pi OS (Bookworm). It uses the Adafruit CircuitPython INA219 library.

Before running, install the dependencies and enable I2C via sudo raspi-config:

sudo apt update
sudo apt install python3-pip
pip3 install --break-system-packages adafruit-circuitpython-ina219

Note: To allow the script to execute a shutdown without a password prompt, add this line to your sudoers file (sudo visudo): pi ALL=(ALL) NOPASSWD: /sbin/shutdown

import time
import board
import busio
import os
import sys
from adafruit_ina219 import ADCResolution, BusVoltageRange, INA219

# --- PIN DEFINITIONS & I2C SETUP ---
# Explicitly defining the I2C bus using default Pi SDA (GPIO2) and SCL (GPIO3)
try:
    i2c_bus = busio.I2C(board.SCL, board.SDA)
    ina219 = INA219(i2c_bus, addr=0x40)
except ValueError as e:
    print(f"Hardware Error: {e}")
    sys.exit(1)

# --- SENSOR CALIBRATION ---
# Crucial for Pi 5 main-rail monitoring: Assumes hardware shunt was swapped to 0.01 ohm (10mΩ)
# Max expected current 6A, Shunt = 0.01 ohm. Max shunt voltage = 60mV.
ina219.bus_adc_resolution = ADCResolution.ADCRES_12BIT_32S
ina219.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_32S
ina219.bus_voltage_range = BusVoltageRange.RANGE_16V

# Calibrate for 10mΩ shunt (0.01) and 6A max current
# If using stock 0.1Ω shunt for a peripheral, change to: ina219.configure(0.1, 3.2)
ina219.configure(0.01, 6.0)

# --- THRESHOLDS ---
CRITICAL_VOLTAGE = 4.65  # Volts - Triggers safe shutdown
WARNING_VOLTAGE = 4.80   # Volts - Logs warning
POLL_INTERVAL = 2.0      # Seconds

def trigger_safe_shutdown(voltage):
    print(f"[CRITICAL] Voltage at {voltage:.3f}V. Initiating safe shutdown to prevent SD corruption.")
    # Flush filesystem buffers before killing power
    os.system("sync")
    os.system("sudo shutdown -h now 'INA219 Undervoltage Protection Triggered'")

def main():
    print("Raspberry Pi Power Monitor Active. Polling every 2 seconds...")
    try:
        while True:
            bus_voltage = ina219.bus_voltage
            current = ina219.current
            shunt_voltage = ina219.shunt_voltage
            power = bus_voltage * (current / 1000.0)

            # Output telemetry
            print(f"V: {bus_voltage:6.3f} V | I: {current:7.2f} mA | P: {power:6.2f} W | Shunt: {shunt_voltage:5.3f} mV")

            # Decision Path
            if bus_voltage < CRITICAL_VOLTAGE and bus_voltage > 0.5: 
                # >0.5V check prevents false triggers if sensor disconnects and reads near-zero noise
                trigger_safe_shutdown(bus_voltage)
            elif bus_voltage < WARNING_VOLTAGE:
                print(f"[WARNING] Voltage sag detected: {bus_voltage:.3f}V. Check PSU and cabling.")
            
            time.sleep(POLL_INTERVAL)

    except KeyboardInterrupt:
        print("\nMonitor stopped by user.")
    except OSError as e:
        print(f"I2C Communication Lost: {e}. Check wiring.")

if __name__ == "__main__":
    main()

Debugging: First Three Things to Check When It Fails

If the script crashes on boot or the Pi throws OS-level errors, follow this ranked decision path. These are the exact failure modes encountered on the bench.

1. I2C Bus Not Enabled or Missing

Exact Error String: FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'

Cause: The Raspberry Pi OS disables the hardware I2C interface by default to free up GPIO pins and reduce boot time.

Fix: Run sudo raspi-config, navigate to Interface Options > I2C, and select Yes. Reboot the Pi. Verify the bus exists by running ls /dev/i2c* in the terminal.

2. INA219 Address Collision or Missing Pull-Ups

Exact Error String: ValueError: No I2C device at address: 0x40

Cause: The Pi cannot see the sensor. The INA219 defaults to address 0x40. This happens if the SDA/SCL lines are swapped, the 3.3V logic power (VIN pin) is unconnected, or you are using a clone breakout board lacking onboard I2C pull-up resistors.

Fix: Run sudo i2cdetect -y 1. If the grid is empty, check your 3.3V connection. If your clone board lacks pull-ups, solder two 4.7kΩ resistors between the SDA/SCL lines and the 3.3V VIN pin. The TI INA219 datasheet specifies open-drain I2C lines that require external pull-ups to function reliably at 400kHz.

3. Shunt Saturation & ADC Overflow

Symptom: The script runs, but current reads max out at exactly 3200.00 mA or voltage reads 0.000 V under load, followed by the OS throwing Under-voltage detected! (0x00050005).

Cause: You are using the stock 0.1Ω shunt on a Pi 5 pulling >3.2A. The INA219 shunt ADC maxes out at 320mV. (3.2A × 0.1Ω = 320mV). Furthermore, the 0.5V drop across the shunt is starving the Pi.

Fix: As noted in the hardware section, you must swap the surface-mount shunt resistor to 10mΩ (0.01Ω) and update the ina219.configure(0.01, 6.0) parameter in the Python script. Alternatively, move the sensor to monitor a downstream 12V/5V buck converter feeding a motor HAT, rather than the main Pi input rail.

Extending or Simplifying the Build

Depending on your deployment environment, you may need to scale this raspberry pi power monitoring setup up for a server rack or down for a remote IoT node.

Simplifying for Remote IoT (Pi Zero 2 W)

If you are deploying a Pi Zero 2 W in a solar-powered enclosure, the 5A/0.1Ω shunt problem disappears. The Zero 2 W rarely exceeds 800mA. You can use the stock Adafruit INA219 without modifying the shunt resistor.

  • Code Change: Revert calibration to ina219.configure(0.1, 3.2).
  • Power Source: Wire the INA219 high-side terminals between a 18650 LiFePO4 pack and a 5V boost converter to monitor total battery discharge telemetry.

Extending for Rack Clusters (MQTT & OLED)

If you are managing a cluster of five Raspberry Pi 5 nodes in a 1U rackmount chassis, polling a local terminal is inefficient.

  • Add Local Display: Wire an SSD1306 128x64 I2C OLED to the same I2C bus (address 0x3C). The INA219 sits at 0x40, so they will not collide. Use the adafruit_ssd1306 library to render a live voltage graph directly on the rack.
  • Add Network Telemetry: Import the paho-mqtt library. Publish the bus_voltage and current variables to a central Mosquitto broker topic (e.g., cluster/node01/power). This allows you to integrate the Pi's power health into Home Assistant or Grafana, triggering alerts if a specific node's power supply begins to degrade over time.

By moving power monitoring from a reactive OS-level warning to a proactive hardware-level measurement, you eliminate the guesswork from Pi stability. Whether you are running a 3D printer farm or a remote weather station, knowing your exact millivolt headroom is the difference between a reliable deployment and a corrupted filesystem.