What it does
A pulse counter clipped to a water meter in an unheated utility room, reporting the count over LoRaWAN every six hours. It is installed once and not visited again. The energy store is a single ER14505 lithium thionyl chloride bobbin cell, 2.4 Ah, the kind of cell that sits in utility meters for a decade. Those cells must never see charge current, and Snowbird's primary mode is built for exactly that: the chemistry jumper's PRIMARY position holds the charger's charge-enable inactive in hardware, the firmware refuses any charge-current write, and the BMS overvoltage threshold sits just above the cell's open-circuit voltage so any current into the cell trips the protector. Three independent blocks have to fail before the cell is charged.
The Snowbird Core 1S is the connector-less population, soldered down by its castellations onto a small carrier board that holds the cell clip, the LoRa module and the pulse input. USB-C and solar still power the load through the power path if a technician ever plugs in; the cell is simply left alone.
Parts
| Part | Role | Example |
|---|---|---|
| Snowbird Core 1S | BMS, gauge, 3.3 V rail, primary-cell mode, as a solder-down module | snowbird-core-1s |
| ER14505 cell, 2.4 Ah | Energy store, AA size, bobbin construction | Saft LS14500 or Tadiran SL-760 |
| Hybrid layer capacitor | Covers the radio burst the bobbin cell cannot | Tadiran HLC-1020 on Snowbird's VBAT footprint |
| LoRaWAN module | Uplink | Seeed Wio-E5 or a Murata CMWX1ZZABZ |
| Pulse input | The meter | Reed switch on the meter's magnet, 100 k pull-up, 100 nF across |
| Carrier board | Holds everything | Two-layer, 50 × 40 mm, castellation pads for the Core |
| Enclosure | Dust and the occasional drip | ABS box, IP54 |
Wiring
| Snowbird pin | Device pin | Note |
|---|---|---|
| 3V3 castellation | Carrier 3V3 plane | LoRa module and MCU run from Snowbird's 3.3 V rail |
| GND castellations | Carrier ground | All of them, for the return path |
| SDA, SCL castellations | MCU I2C | Snowbird at 0x36 |
| INT castellation | MCU interrupt input | Carries the TEMP_CUTOFF and BMS events; no jumper on the Core, it is a pad |
| EN5V castellation | not connected | The 5 V rail is never enabled; the internal pull-up keeps it off |
| VBAT pad, NTC pad, GND | Cell clip +, thermistor, cell clip − | The 10 k thermistor is taped to the cell |
| HLC footprint | Hybrid layer capacitor | Across VBAT, polarity marked |
| Chemistry jumper | PRIMARY | Set before the cell goes in. Charging is then disabled in hardware |
| USB-C pads | not populated | The Core ships without the connector; a technician's USB pads are on the carrier instead |
| Solar pads | not populated |
With the Core there is no Feather host and no handoff to think about: the carrier's MCU is the only load and Snowbird's rail is the only supply.
Power budget
| Load | Duty | Average current | Daily energy |
|---|---|---|---|
| MCU sleeping, counting pulses on an interrupt | 100 % | 2.1 µA | 0.00018 Wh |
| LoRaWAN uplink, 14 dBm, 1.2 s | four a day | 1.1 µA | 0.00010 Wh |
| Snowbird idle, no advertising | 100 % | 28 µA | 0.0024 Wh |
| Cell self-discharge, 1 % a year | 2.7 µA equivalent | 0.00024 Wh | |
| Total | 34 µA | 0.0030 Wh |
The ER14505 holds 2.4 Ah at 3.6 V, about 8.6 Wh. At 34 µA that is 70,000 hours, a little over eight years; derated for the cell's own capacity loss over a decade at room temperature it comes out at 7.5 to 8 years, and at 20 µA with Snowbird's supervisor in its low-report mode (one gauge update an hour, which this application can live with) the arithmetic reaches eleven years. The gauge is told the capacity at install, counts every microamp-hour out, and the time-to-empty it reports is the number the maintenance schedule uses.
Sketch
#include <Wire.h>
#include <Snowbird.h>
#include <LoRaWAN.h>
Snowbird pmic;
volatile uint32_t pulses = 0;
void onPulse() { pulses++; }
void setup() {
Wire.begin();
pmic.begin(Wire);
// Primary cell. The jumper is in PRIMARY; this call confirms it and the
// library refuses every charge-current write from here on.
pmic.setChemistry(Chem::LiSOCl2);
pmic.setCapacity_mAh(2400); // the gauge counts out from this
pmic.setReportInterval(3600); // one gauge update an hour
pmic.set5V(false);
pmic.setTempLimits(TempLimits::discharge(-40, 85));
// First power-up on a shelf-aged cell: a short depassivation load so the
// gauge does not read the voltage dip as an empty cell.
if (pmic.firstBoot()) {
pmic.depassivate(/*mA*/ 30, /*seconds*/ 20);
}
pmic.onEvent(TEMP_CUTOFF | BMS_TRIP, [](uint32_t ev) {
LoRaWAN.sendAlert(ev);
});
pinMode(PULSE_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PULSE_PIN), onPulse, FALLING);
LoRaWAN.begin(US915);
LoRaWAN.joinOTAA(APP_EUI, DEV_EUI, APP_KEY);
}
void report() {
noInterrupts();
uint32_t count = pulses;
interrupts();
Uplink u;
u.pulses = count;
u.soc = pmic.soc();
u.voltage_mV = pmic.voltage_mV();
u.timeToEmpty_h = pmic.timeToEmpty();
u.cell_C = pmic.batteryTemp_C();
LoRaWAN.send(&u, sizeof(u)); // ~1.2 s; the HLC supplies the burst
}
void loop() {
report();
deepSleep(6 * 3600); // wakes on the pulse interrupt too
}
Field notes
The first cell out of the drawer had been on a shelf for three years. On first power-up its voltage dipped to 3.1 V under a 30 mA load and recovered to 3.6 V over about fifteen seconds: passivation, the lithium chloride film that forms on a stored thionyl chloride cell. Without the depassivation step the gauge would have logged that dip as a near-empty cell on day one. With it, the gauge saw a full cell and the time-to-empty started where it should.
The hybrid layer capacitor is not optional. A bobbin cell is happy at 100 mA continuous and unhappy at the 400 mA a LoRa transmit at 14 dBm pulls for a second. The first carrier was built without the capacitor to see what would happen: the 3.3 V rail held, because Snowbird's regulator is a buck-boost and rides the cell down to 2.0 V, but the cell voltage sagged to 2.4 V during each uplink and the gauge's current reading swung. With the capacitor fitted the cell sees a 90 mA average over the burst and the voltage barely moves.
The temperature window is wide on purpose. LiSOCl2 is rated to minus fifty-five; the board's own parts limit it to minus forty, and the firmware's discharge limits are set to that. In the test room, which touched minus eight in February, nothing happened, which is the result.
Fourteen months in, the gauge reads 88 % and the time-to-empty reads 8.1 years. The counted charge out matches the budget to within 4 %, with the difference being the uplink retries during a week when the gateway was down.
Primary mode is the one setting on the board that cannot be changed from software, and that is deliberate. A technician with a USB-C cable can power the node, read the gauge and update the firmware; they cannot, from any interface, put current into the cell.