Battery-powered Arduino projects fail in two ways: unstable wake cycles and hidden current draw. I start by listing every component in active and sleep state, then I budget power in milliamp-hours before writing firmware.
For periodic sensing, watchdog-based wake-ups are usually enough. I keep each wake window short: read sensor, validate range, transmit, then sleep immediately. Serial logging stays disabled outside development builds because UART prints quietly destroy battery life.
Hardware choices matter as much as code. Linear regulators and LED indicators can dominate idle consumption. Replacing always-on modules with switchable rails, plus selecting low-leakage sensors, gave me a bigger gain than any firmware tweak.
In deployment, I log wake count and battery voltage every 100 cycles. That creates a degradation curve I can compare between firmware versions and weather conditions.
A minimal AVR sleep cycle
On classic AVR boards (Uno, Nano, Pro Mini) the pattern I reach for is the watchdog timer as a wake source with the deepest sleep mode, SLEEP_MODE_PWR_DOWN. The watchdog's longest interval is ~8 s, so for longer periods I count wakeups.
#include <avr/sleep.h>
#include <avr/wdt.h>
volatile uint8_t wdtTicks = 0;
ISR(WDT_vect) {
wdtTicks++; // just wake; keep the ISR trivial
}
// Configure the watchdog for an ~8 s interrupt (not reset).
void setupWatchdog8s() {
cli();
MCUSR &= ~(1 << WDRF);
WDTCSR |= (1 << WDCE) | (1 << WDE);
// WDP3 | WDP0 = ~8 s; WDIE = interrupt mode instead of system reset
WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0);
sei();
}
void sleepOneCycle() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu(); // core halts here until the WDT fires
sleep_disable(); // execution resumes on the next line
}
void loop() {
sleepOneCycle();
if (wdtTicks >= 8) { // ~8 wakeups * 8 s ≈ 64 s sensing interval
wdtTicks = 0;
// wake window: read sensor, validate, transmit, then fall back to sleep
}
}
If you prefer a library over raw registers, Rocket Scream's LowPower library wraps the same idea (LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF)), and disabling brown-out detection during sleep is one of the bigger single wins on AVR.
One measurement note: verify the result with a multimeter or current-sense setup in series with the battery — sleep current should be in the low tens of microamps, and if it isn't, a peripheral is still powered.
Sources
- Imported from the previous version of the site (gaborl.hu). Original publication around 2025-02-06.
Update history
- — Adopted from previous gaborl.hu (Hugo) site via legacy import.