Skip to content
Snowbird Power Systems

Documentation

Library

The Snowbird API for Arduino, C and MicroPython, one register map behind all three.

One library, every board

The Arduino library (Snowbird Power in the Library Manager, snowbird-power on PlatformIO), the C library and the MicroPython package (snowbird) are generated from the same register map, so they expose the same calls with the same semantics. The examples below are Arduino; the MicroPython names are the same in snake_case.

On begin() the library reads the capability register and learns which board it is on. A call the board cannot honour returns Err::NotOnThisBoard rather than silently doing nothing. cellVoltage_mV(2) on a Snowbird Feather 1S is the usual way to meet it.

Setup

#include <Snowbird.h>
Snowbird pmic;

pmic.begin(Wire);                 // default address 0x36
pmic.begin(Wire, 0x37);           // the address jumper moves it to 0x37

begin() returns false if no board answers. It does not change any setting.

Capabilities

Call Returns
pmic.boardId() Board::Feather1S, Core1S, Cell18650, Pack, Lite1S
pmic.cells() 1, 2, 3 or 4
pmic.hasBMS() true on every board except the Snowbird Lite 1S
pmic.hasBLE() true on every board except the Snowbird Lite 1S
pmic.hasBalance() true on the Snowbird Pack
pmic.firmwareVersion() semantic version string
pmic.registerMapVersion() 1.2 at this release

Configuration

Settings are stored on the board and survive a host reflash and a power cycle.

setChemistry(Chem)

Chem::LiIon, Chem::LiPo, Chem::LiHV, Chem::LiFePO4, Chem::LiSOCl2. Sets the charge voltage, the cutoff and the BMS thresholds together. Refused with Err::AboveCeiling if the requested chemistry's voltage is above the chemistry jumper's ceiling. The jumper rule is in Chemistries.

setCapacity_mAh(uint16_t)

The cell or pack capacity. Drives the default charge current and the coulomb counter. Required for LiFePO4 and LiSOCl2, where the gauge cannot infer it from the voltage curve.

setChargeCurrent_mA(uint16_t)

Returns the value actually applied, which is the lowest of:

Limit Value
Request what you asked for, rounded down to 10 mA
Chemistry ceiling 1 C of the configured capacity
Hardware ceiling 3 A
Input what the source offers: 0.5, 1.5 or 3 A on USB-C, or the panel's MPPT point on solar

Default without a call: 0.5 C. On a LiSOCl2 board the call returns 0 and sets Err::PrimaryCell.

set5V(bool)

Enables the 5 V rail, up to 2 A. Off by default. The EN5V pin, when pulled low, overrides this to off. is5VEnabled() reads back the combined state.

setTempLimits(TempLimits)

pmic.setTempLimits(TempLimits::charge(0, 45).discharge(-20, 60));

Narrower than the chemistry's default is accepted. Wider is refused with Err::AboveCeiling. The hardware layer enforces the ceilings whatever this says; see Safety.

setOutputVoltage(uint16_t mV, Confirm)

The 3.3 V rail can be set between 1.8 V and 3.3 V on boards whose voltage jumper permits it. Because the host usually runs from this rail, the call needs the unlock-and-confirm sequence:

pmic.unlock();
pmic.setOutputVoltage(1800, Snowbird::CONFIRM_PENDING);   // applied provisionally
// ... host confirms it is still running ...
pmic.confirm();                                            // within 5 s, or the rail reverts

Telemetry

Call Returns Notes
soc() 0 to 100 % ModelGauge on Li-ion, LiPo, LiHV; coulomb counting on LiFePO4 and LiSOCl2
voltage_mV() pack voltage
current_mA() signed; positive into the cell 1 mA resolution
timeToEmpty() minutes, or UINT16_MAX while charging
timeToFull() minutes, or UINT16_MAX while discharging
cellVoltage_mV(n) one cell, n = 1 to 4 Pack only
imbalance_mV() highest minus lowest cell Pack only
batteryTemp_C() from the thermistor NAN if the thermistor is open
boardTemp_C() the supervisor's own sensor
inputSource() Input::None, USB, Solar
inputCurrentLimit_mA() 500, 1500 or 3000 on USB
solarPower_mW() panel power at the MPPT point
harvestedToday_mWh() since local midnight, or since boot if no clock was set
chargeState() Charge::Idle, Precharge, CC, CV, Done, Held

Faults and events

faults() returns a bitmask that is set until the condition clears:

Fault f = pmic.faults();
if (f & Fault::OverVoltage) ...
// OverVoltage, UnderVoltage, OverCurrent, ShortCircuit, OverTemp, UnderTemp,
// NtcOpen, NtcShort, BmsTripped, InputOverVoltage, ReversedBattery, CellCountMismatch

Events are edge-triggered and arrive on the INT pin:

void onPower(Event e) { ... }

pmic.onEvent(Event::ChargeDone | Event::PowerLost | Event::TempHold | Event::BmsTrip, onPower);
// in loop(), or from the INT ISR:
pmic.poll();

The event set: ChargeDone, ChargeStart, PowerLost, PowerRestored, TempHold, TempCutoff, BmsTrip, SocLow (threshold set by setSocLowThreshold()), SolarDay, SolarNight. Reading events() clears the flags.

Power states

pmic.shipMode();           // opens the battery switch; draws 3 µA; wakes on USB or solar
pmic.sleep();              // outputs off, supervisor reporting; wakes on INT or I2C

shipMode() is the last thing a sketch should do before a board goes in a box. It needs the unlock sequence, because it also takes down the host.

MicroPython

from machine import I2C, Pin
from snowbird import Snowbird, Chem, Event

pmic = Snowbird(I2C(0, sda=Pin(4), scl=Pin(5)))
pmic.set_chemistry(Chem.LIFEPO4)
pmic.set_capacity_mah(3000)

def on_event(e):
    print("event", e)

pmic.on_event(Event.CHARGE_DONE | Event.POWER_LOST, on_event)

print(pmic.soc(), pmic.voltage_mv(), pmic.time_to_empty())
print(pmic.cells(), pmic.has_bms(), pmic.has_ble())

pmic.poll() dispatches events; call it from the main loop or from a Pin.irq handler on the INT pin.

Errors

Every setter returns Err::Ok or one of:

Error Meaning
Err::NotOnThisBoard The board does not have the hardware for this call
Err::AboveCeiling The value exceeds a jumper or chemistry limit
Err::PrimaryCell Charging is not possible on a LiSOCl2 board
Err::Locked The call needs unlock() first
Err::Timeout The board did not answer; check the bus

pmic.lastError() returns the most recent one and pmic.errorString(err) a short description, for serial logs.

Without the library

The board is an ordinary I2C device at 0x36. The register map documents every register, so a bare I2C master in any language can read SOC and voltage with two transactions.