LoRa is chosen for range and efficiency, but many nodes still miss battery targets by a large margin. The reason is usually poor budgeting assumptions and missing field validation.
1. Build a current profile per operating mode
Measure current in each mode:
- deep sleep
- sensor warm-up
- sensor sampling
- radio transmit
- radio receive window
Do not rely only on datasheet typical values. Board-level leakage and regulator losses can dominate.
2. Duty-cycle aware energy model
Compute average current from real duty cycle:
- sample interval
- payload size
- spreading factor and airtime
- retransmission rate
Long airtime configurations can multiply energy usage unexpectedly.
A worked example: does this node last a year?
Abstract advice is easy to nod along to, so here is the actual arithmetic I run on the back of an envelope before I trust any node. Take a typical node built around an STM32L0 (or similar low-power MCU) and an SX1276 radio, sending one small reading every 15 minutes on EU868. I measure the currents on the bench rather than trusting datasheet typicals, but these numbers are representative of what I usually see:
| Mode | Current | Duration per cycle |
|---|---|---|
| Deep sleep (MCU + radio in sleep, RTC running) | 12 µA | ~899.5 s |
| Wake + sensor warm-up and sampling | 8 mA | 300 ms |
| Radio TX (SF9, ~14 dBm, SX1276) | 120 mA | 185 ms |
| Radio RX window (single Class A RX1) | 11 mA | 15 ms |
The cycle period is 15 min = 900 s. The trick is to convert each mode into a charge contribution per cycle (current × time, in mAh), sum them, then divide by the period to get an average current.
Per cycle, in ampere-seconds (A·s):
- Sleep:
0.000012 A × 899.5 s = 0.01079 A·s - Sensor:
0.008 A × 0.300 s = 0.00240 A·s - TX:
0.120 A × 0.185 s = 0.02220 A·s - RX:
0.011 A × 0.015 s = 0.000165 A·s - Total:
0.03556 A·sper 900 s cycle
Average current = 0.03556 A·s ÷ 900 s = 39.5 µA.
That is the number that matters, and notice what it says: the radio transmit burst is only 185 ms but contributes more charge per cycle (0.0222 A·s) than 15 minutes of deep sleep (0.0108 A·s). Airtime, not sleep current, dominates this budget. Doubling the spreading factor to SF11 roughly triples airtime; at ~600 ms TX the average current jumps past 80 µA and the projected life halves.
For runtime, I derate the nominal battery capacity to about 80% to account for self-discharge, temperature, and the fact that you never usefully extract the last drop:
- A 3000 mAh AA-class LiSOCl2 cell, derated to ~2400 mAh usable.
- Runtime =
2400 mAh ÷ 0.0395 mA ≈ 60,700 h ≈ 6.9 years— comfortably battery-shelf-life limited rather than budget limited. - Swap in a smaller 1200 mAh cell (derated to ~960 mAh) and the same node lands near 2.8 years.
The point is not the exact figure; it is that the model tells you where the energy goes. If the field result misses this by 3x, one of the four rows above is wrong — almost always a higher-than-measured sleep current from a peripheral that never actually slept.
3. Battery chemistry and temperature behavior
Battery curves vary significantly by temperature. If deployments see winter conditions, capacity assumptions must be derated.
Also account for pulse-current limits during radio bursts.
4. Regulator and peripheral overhead
Common hidden drains:
- always-on regulator quiescent current
- sensor modules that never fully sleep
- indicator LEDs
- USB-UART bridges left powered
These are often bigger than MCU sleep current.
5. Firmware power patterns
Power-friendly firmware rules:
- batch sensor reads in one wake window
- avoid unnecessary RX listening windows
- compress payload to reduce airtime
- disable debug interfaces in release builds
Tiny code choices can add months of battery life.
The pattern that enforces all of this is a strict wake-do-sleep loop: nothing runs between cycles except the RTC. The MCU wakes on a timer, powers only what it needs, transmits once, and returns to its lowest sleep state. In pseudocode (the specifics depend on your MCU HAL, but the shape is what matters):
// Duty-cycled main loop. Everything outside the wake window must be off.
void loop() {
powerUpSensors(); // enable the sensor rail via a load switch
delay(SENSOR_WARMUP_MS); // only as long as the datasheet demands
Reading r = readAllSensors(); // batch every sensor in one wake window
powerDownSensors(); // cut the sensor rail before the radio TX
radio.wake();
radio.transmit(pack(r)); // one uplink; smallest payload that is useful
// Class A: open only the RX windows LoRaWAN requires, then stop listening.
radio.sleep();
// Deepest sleep the design allows; RTC wakes us after the interval.
// Subtract the ~0.5 s we spent awake so the cadence stays honest.
enterDeepSleep(SLEEP_INTERVAL_MS - awakeMillis());
}
The two lines that save the most current are powerDownSensors() before the radio burst and radio.sleep() after it. A sensor rail or radio left in idle instead of sleep is the classic reason a measured node draws 10x its modeled average.
6. Reliability vs power tradeoffs
More retries improve data delivery but cost power. Define acceptable loss rate and tune retransmission policy accordingly.
For non-critical telemetry, controlled loss can be preferable to rapid battery depletion.
7. Field validation plan
Lab numbers are not enough. Validate with:
- real gateway distance
- environmental temperature variation
- expected RF interference
- long-run discharge observation
Track battery voltage and event counters over weeks.
8. Maintenance and replacement policy
Set replacement thresholds and maintenance cadence from measured degradation, not optimistic calculations.
Document expected runtime bands, not a single number.
Final note
A reliable LoRa power budget is built from measurement, realistic airtime assumptions, and field data feedback. When these are in place, battery predictions become trustworthy.
Sources
- Imported from the previous version of the site (gaborl.hu). Original publication around 2024-06-19.
- Semtech, SX1276/77/78/79 Datasheet — transmit/receive/sleep current figures and airtime behavior.
- The Things Network, LoRaWAN airtime calculator (avbentem.github.io/airtime-calculator) — spreading factor vs airtime per payload.
- LoRa Alliance, LoRaWAN Regional Parameters (RP002) — EU868 duty-cycle limits and Class A RX window timing.
Update history
- — Adopted from previous gaborl.hu (Hugo) site via legacy import.