Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

TP4056 battery charger with Raspberry Pi Pico — run on Li-ion and read VSYS

Power a Raspberry Pi Pico from a Li-ion cell with a TP4056 into VSYS through a Schottky diode, and read the battery on ADC3 (GP29) in MicroPython.

MicroPythonVerified — micropython (mpy-cross v1.29.0), 3 Nov 2026Compile-only

Community draft — report a mistake

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 PicoOnly the pins used are shown · 3.3 V logicTP4056 Li-ion charger module (with DW01A protection)1N5817GNDVSYSIN+ (not connected)IN- (not connected)B+ → 18650 / Li-ion cell +B- → 18650 / Li-ion cell −OUT+OUT-
Also needed: 1N5817 (or SS14) Schottky diode, TP4056 module with protection (DW01A, 6 pins), 18650 cell in a holder.
Every connection
Raspberry Pi PicoTP4056 Li-ion charger module (with DW01A protection)WireNotes
—B+—To 18650 / Li-ion cell + (not drawn)
—B-—To 18650 / Li-ion cell − (not drawn)
VSYSOUT+redVia 1N5817 Schottky diode — Anode on OUT+, band (cathode) towards VSYS (pin 39). Without it, USB would charge the cell through VSYS.
GNDOUT-blackGND 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

main.pyMicroPython
# 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

REPL / Thonny shellOutput
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.

↑↓ to move · Enter to open