A Raspberry Pi Pico runs happily from one Li-ion cell: VSYS takes 1.8–5.5 V. A TP4056 module charges the cell from USB and protects it; its output goes to VSYS through a Schottky diode so the Pico’s own USB can never back-feed the battery.
The plain Pico can also watch its battery with no extra parts — GP29 reads VSYS/3 and GP24 says whether USB is present. The script prints the cell voltage and a rough percentage, and lights the GP25 LED when it runs low.
Wiring
| Raspberry Pi Pico | TP4056 Li-ion charger module (with DW01A protection) | Wire | Notes |
|---|---|---|---|
| — | B+ | — | To 18650 / Li-ion cell + (not drawn) |
| — | B- | — | To 18650 / Li-ion cell − (not drawn) |
| VSYS | OUT+ | red | Via 1N5817 Schottky diode — Anode on OUT+, band (cathode) towards VSYS (pin 39). Without it, USB would charge the cell through VSYS. |
| GND | OUT- | black | GND pin 38, next to VSYS. |
5 V input (4.5–5.5 V, USB) part, 3.3 V board. No logic pins. OUT+ is the raw cell voltage, 3.0–4.2 V: feed it to an ESP32 or Pico through a low-dropout 3.3 V regulator (HT7333, MCP1700) or the board’s VSYS/VIN pin, and to a 5 V board through a boost converter (MT3608). Never connect it straight to a 3.3 V pin.
Board quirks
- VSYS accepts 1.8–5.5 V and the Pico’s buck-boost regulator makes 3.3 V from it, so a single Li-ion cell (3.0–4.2 V) runs the Pico directly — never feed the cell to the 3V3 pin.
- The plain Pico measures its own supply: GP29 (
ADC(29)) reads VSYS through an on-board ÷3 divider, and GP24 is HIGH while USB is plugged in. On the Pico W both pins are used by the wireless chip. - The script sets GP23 HIGH, which switches the on-board regulator to PWM mode: slightly more current, but less ripple and steadier ADC readings.
- Cell voltage = VSYS + the diode drop (about 0.2–0.35 V depending on current), so the percentage is an estimate. Charge by plugging USB into the TP4056, not the Pico.
- Use the 6-pin TP4056 with B+/B− and OUT+/OUT− (it has DW01A over-discharge protection). The 4-pin version without protection can drain a cell until it is damaged.
Code
# Battery-powered Raspberry Pi Pico with a TP4056 charger: read the battery voltage on VSYS (MicroPython).
# Wiring: TP4056 OUT+ -> 1N5817 Schottky diode (band towards the Pico) -> VSYS (pin 39),
# OUT- -> GND (pin 38). Li-ion cell on B+ / B-. Charge through the TP4056's own USB socket.
# Plain Pico only: GP29 = VSYS / 3 (ADC3), GP24 = "USB connected", GP23 = regulator mode.
from machine import ADC, Pin
import time
DIODE_V = 0.3 # Schottky drop: cell voltage = VSYS + about 0.3 V
LOW_V = 3.4 # warn below this cell voltage (the TP4056's DW01A cuts off near 2.5 V)
vsys = ADC(29)
usb = Pin(24, Pin.IN) # HIGH while 5 V is present on VBUS
smps_pwm = Pin(23, Pin.OUT, value=1) # force PWM mode: less regulator ripple, steadier ADC readings
led = Pin(25, Pin.OUT)
def cell_volts():
raw = sum(vsys.read_u16() for _ in range(32)) / 32
return raw * 3.3 / 65535 * 3 + DIODE_V
def percent(v):
# rough Li-ion resting curve: 4.2 V = 100 %, 3.7 V ~ 50 %, 3.3 V = 0 %
pts = ((4.20, 100), (4.00, 80), (3.85, 60), (3.75, 40), (3.65, 20), (3.50, 8), (3.30, 0))
if v >= pts[0][0]:
return 100
for (v1, p1), (v2, p2) in zip(pts, pts[1:]):
if v >= v2:
return int(p2 + (p1 - p2) * (v - v2) / (v1 - v2))
return 0
while True:
v = cell_volts()
on_usb = usb.value() == 1
if on_usb:
print("USB power - VSYS comes from USB, battery reading not meaningful ({:.2f} V)".format(v))
led.on()
else:
p = percent(v)
print("Battery {:.2f} V ~{} %{}".format(v, p, " LOW - charge me" if v < LOW_V else ""))
led.value(v < LOW_V) # LED = low battery
time.sleep(5)# Battery-powered Raspberry Pi Pico with a TP4056 charger: read the battery voltage on VSYS (MicroPython).
# Wiring: TP4056 OUT+ -> 1N5817 Schottky diode (band towards the Pico) -> VSYS (pin 39),
# OUT- -> GND (pin 38). Li-ion cell on B+ / B-. Charge through the TP4056's own USB socket.
# Plain Pico only: GP29 = VSYS / 3 (ADC3), GP24 = "USB connected", GP23 = regulator mode.
from machine import ADC, Pin
import time
DIODE_V = 0.3 # Schottky drop: cell voltage = VSYS + about 0.3 V
LOW_V = 3.4 # warn below this cell voltage (the TP4056's DW01A cuts off near 2.5 V)
vsys = ADC(29)
usb = Pin(24, Pin.IN) # HIGH while 5 V is present on VBUS
smps_pwm = Pin(23, Pin.OUT, value=1) # force PWM mode: less regulator ripple, steadier ADC readings
led = Pin(25, Pin.OUT)
def cell_volts():
raw = sum(vsys.read_u16() for _ in range(32)) / 32
return raw * 3.3 / 65535 * 3 + DIODE_V
def percent(v):
# rough Li-ion resting curve: 4.2 V = 100 %, 3.7 V ~ 50 %, 3.3 V = 0 %
pts = ((4.20, 100), (4.00, 80), (3.85, 60), (3.75, 40), (3.65, 20), (3.50, 8), (3.30, 0))
if v >= pts[0][0]:
return 100
for (v1, p1), (v2, p2) in zip(pts, pts[1:]):
if v >= v2:
return int(p2 + (p1 - p2) * (v - v2) / (v1 - v2))
return 0
while True:
v = cell_volts()
on_usb = usb.value() == 1
if on_usb:
print("USB power - VSYS comes from USB, battery reading not meaningful ({:.2f} V)".format(v))
led.on()
else:
p = percent(v)
print("Battery {:.2f} V ~{} %{}".format(v, p, " LOW - charge me" if v < LOW_V else ""))
led.value(v < LOW_V) # LED = low battery
time.sleep(5)Expected serial output
Battery 4.07 V ~85 %
Battery 3.91 V ~68 %
Battery 3.38 V ~4 % LOW - charge me
USB power - VSYS comes from USB, battery reading not meaningful (4.98 V)Battery 4.07 V ~85 % Battery 3.91 V ~68 % Battery 3.38 V ~4 % LOW - charge me USB power - VSYS comes from USB, battery reading not meaningful (4.98 V)
Common errors
Battery reading is about 0.3 V low
Set DIODE_V to the drop you measure with a multimeter (cell voltage minus VSYS) at your typical load.
Cell gets warm while the Pico is on USB
The diode is missing or reversed, so USB is charging the cell through VSYS. Fit the Schottky with its band towards VSYS.
Pico turns off and won’t restart until USB is connected
The DW01A cut the cell off near 2.5 V. Plug USB into the TP4056 to reset it and recharge.