A five-year battery life claim is not a feature you add near the end of firmware development. It is a budget you either respect from the first line of code or blow through without noticing. The arithmetic is simple: capacity divided by time gives an average current, and every firmware decision either fits inside that number or does not.

Teams miss it not because the math is hard but because average current is dominated by things that feel small — a status LED blinking once a second, a sensor left powered between reads, a radio that reconnects more often than anyone specified.

Start from the budget, not the code

Before writing firmware, do this calculation on paper. Say the product runs on a single lithium primary AA cell rated at 2,600 mAh. Derate for self-discharge, temperature, and voltage sag below your cutoff — assume roughly 2,100 mAh usable. (Which chemistry you can use, and how much derating is honest, is covered in battery pack design for a product.) The requirement is five years, or about 43,800 hours. So:

2,100 mAh ÷ 43,800 h ≈ 0.048 mA ≈ 48 µA average

That is your entire budget. Put it on the wall, because it makes every subsequent argument concrete. "Should we sample every ten seconds or every minute?" stops being a preference and becomes a subtraction problem.

Working the duty cycle math

Average current is the sum of every state's current weighted by time spent in it. Take a plausible sensor node:

ActivityCurrentDurationRateAverage contribution
Deep sleep (RTC running)3 µAcontinuous3.0 µA
Sensor read + MCU active5 mA20 msevery 60 s1.7 µA
Radio transmit + ack15 mA300 msevery 60 min1.3 µA
Status LED blink2 mA10 msevery 10 s2.0 µA
Total8.0 µA

Eight microamps against a 48 µA budget — roughly six times the headroom, which is about right for a first design, because reality will consume some of it.

Now change one requirement. Product management wants telemetry every minute instead of every hour, because a dashboard looks better with fresh data. The radio line goes from 1.3 µA to 75 µA, total average current becomes about 82 µA, and the five-year battery is now a fourteen-month battery. Nothing about the firmware got worse; one scheduling parameter changed. This is why the budget belongs in the requirements document — the person asking for minute-by-minute data needs to see that it costs four years of battery life.

Sleep current and wake sources

The device is asleep more than 99.9% of the time, so sleep current sets the floor. Getting from 3 µA to 300 µA is easy to do by accident, and it is a 100-fold error in the dominant term. The usual culprits are not the MCU core:

  • Peripherals left enabled. A UART, ADC, or timer clock running through sleep can cost tens of microamps.
  • Floating GPIO pins. An unconnected input without a defined pull can oscillate and draw current in the input buffer. Configure every pin.
  • External components. An LDO with 40 µA quiescent current, or a battery-sense divider drawing continuously — a 100 kΩ divider on 3.6 V burns 36 µA forever. Put a MOSFET in series.
  • Debug hardware. An SWD interface left enabled keeps blocks powered that should be off.

The wake path matters as much. Prefer hardware wake sources — an RTC alarm, a GPIO interrupt from a sensor's threshold output — over anything requiring the CPU to notice. Modern sensors do threshold detection, FIFO buffering, even motion classification internally, waking the MCU only when something is worth reporting. That offload beats any code optimization. MCU power states are covered in low-power firmware and microcontroller sleep modes.

The radio is the expensive part

On almost every connected low-power product the radio dominates active energy. Three firmware levers control it.

Connection interval and advertising rate

On BLE, connection interval and slave latency decide how often the radio wakes to listen even when there is nothing to say; advertising interval does the same before connection. Moving from a 30 ms to a 1-second advertising interval can cut idle radio current by an order of magnitude at the cost of slower discovery — usually a fine trade for a sensor nobody pairs with daily. These parameters are covered in BLE firmware development.

Batch, do not stream

Radio energy is dominated by turn-on, synchronization, and connection overhead, not payload bytes. Sending 24 hourly readings once a day costs far less than sending one reading 24 times. Buffer, transmit on a schedule, accept the latency — and if alarms need low latency, split the traffic.

Bound your retries

An unbounded reconnect loop is the most common way a multi-year product dies in six weeks. Use exponential backoff with a hard ceiling and add jitter. Link-layer energy profiles differ enormously too, which is part of the argument in using LoRa and LoRaWAN when long range and low power win.

Firmware habits that quietly cost years

  • Busy-waiting. Any loop polling a flag or spinning on a delay keeps the core at full current. A single 500 ms busy-wait during sensor warm-up, executed every minute, costs more than the radio. Replace delays with timers that sleep and polls with interrupts.
  • Running fast when you could run slow. For most MCUs race-to-sleep beats running slowly, because static current flows either way.
  • Clock choices. Run the RTC from a low-frequency crystal and keep the high-speed oscillator off. Watch startup times: a crystal needing 300 ms to stabilize, waking every minute, is a duty-cycle term nobody accounted for.
  • Logging and flash writes. Debug UART output left on in production is both a current draw and a wake source; flash writes are expensive and often blocking. Batch them.

Watchdog and brownout behavior

A watchdog is mandatory in a product nobody can reach to reset, and it is also a power item: one running on a low-speed oscillator costs a microamp or two, and the kick pattern must be designed so a hung task actually triggers a reset. The failure modes are in watchdog timers and firmware recovery.

Brownout is subtler. As a primary cell nears end of life its internal resistance rises, and a radio transmit can pull the rail low enough to reset the MCU. Firmware that boots, transmits, browns out, and reboots chews through remaining capacity in a loop and may corrupt flash on the way. Set the brownout threshold above your flash write minimum, size a bulk capacitor for the transmit pulse, and degrade gracefully — cut transmit power, stretch the reporting interval, then stop transmitting and hold the alarm state.

Measure. Do not trust the datasheet.

Datasheet sleep currents are measured at 25 °C, peripherals off, on a bare die. Your board has an LDO, a level shifter, a sensor, and a pull-up network. What matters is what your assembly draws, and on a first build it is routinely 5–50 times the datasheet figure.

Put a current measurement setup on the bench from week one — a source-measure unit or power analyzer that resolves nanoamps in sleep and milliamps during transmit in one capture. A multimeter cannot do both, and the useful information is in the transitions. Then make it routine: capture a full duty cycle, integrate to charge per cycle, compare against the budget at every release, and run one unit hot, since leakage and self-discharge both rise with temperature. Put it in your automated test rig so a regression fails the build — the setups in hardware-in-the-loop testing for firmware are its natural home.

Automate it because power regressions are invisible in functional testing. A refactor that leaves the ADC clock enabled breaks nothing a tester would notice — until the field units die two years early, all at once, every one of them somewhere expensive to reach.

Projects House designs low-power firmware and the hardware around it, with measured power budgets rather than estimated ones. If your product has to survive years on one cell, get in touch through the contact form.