Selecting AVR Sleep Modes for Battery-Powered Arduino Projects
Choose the deepest AVR sleep mode that supports your wake-up source. Compare Idle through Power-down, implement watchdog wake-up, and validate microamp-level current.
08 Sept 2025, 07:39 UTC

The Battery Problem: Sleep Mode Is the Decision, Not the Code
Battery-powered Arduino projects often fail to meet their runtime targets because the microcontroller stays in an active mode while waiting for sensor input or a timer. The useful takeaway is that the deepest sleep mode compatible with your wake-up source will dominate your power budget; everything else is secondary.
Decision and Constraints
You are choosing a sleep mode for an AVR-based board (ATmega328P on Uno/Nano, ATmega2560 on Mega). The decision is: select the deepest sleep mode that still supports your required wake-up source while preserving RAM. Three constraints bound that choice:
- Wake-up sources: external interrupts, pin-change interrupts, watchdog timer, TWI address match, or Timer2 in asynchronous mode.
- Power target: multi-month battery life usually requires chip sleep current below 1 µA, which rules out Idle and ADC Noise Reduction.
- Code complexity: deeper modes require explicit peripheral shutdown and re-initialization on wake-up.
Supported Sleep Modes Compared
AVR microcontrollers provide six sleep modes. The table below summarizes typical behavior at 5 V; exact values vary with voltage and temperature, so treat these as planning figures and verify with the ATmega328P datasheet.
| Mode | Typical Current @5V | Wake-up Sources | RAM Preserved | Active Peripherals |
|---|---|---|---|---|
| Idle | ~15 mA | Any interrupt | Yes | Timer0, UART, SPI, I2C, ADC |
| ADC Noise Reduction | ~6 mA | ADC, WDT, external interrupt | Yes | ADC only |
| Power-down | ~0.1 µA | External interrupt, TWI address match, WDT, pin-change | Yes | None |
| Power-save | ~0.75 µA | External interrupt, TWI, Timer2 async, WDT, pin-change | Yes | Timer2 async |
| Standby | ~0.1 µA | External interrupt, WDT, pin-change | Yes | Oscillator running |
| Extended Standby | ~0.75 µA | External interrupt, Timer2 async, WDT, pin-change | Yes | Oscillator + Timer2 async |
Trade-offs: Depth Versus Flexibility
Power-down gives the lowest current but accepts only external interrupts, pin-change interrupts, watchdog timer, or TWI address match as wake-up sources. It is the right choice when an external event (button press, sensor threshold) or a periodic watchdog timeout is sufficient.
Power-save costs roughly 0.75 µA more but keeps Timer2 running in asynchronous mode. That allows periodic wake-ups without external hardware, which is useful when you need a regular sampling interval and do not want to chain watchdog interrupts.
Standby and Extended Standby keep the main oscillator running, reducing wake-up latency to about six clock cycles. They still draw around 0.1 µA to 0.75 µA, so they are rarely worth the extra current unless your application demands immediate response.
Idle and ADC Noise Reduction leave too many peripherals active. At 15 mA and 6 mA respectively, they cannot support multi-month battery life and are better suited to short naps between bursts of computation.
Concrete Implementation: Power-Down with Watchdog Wake-Up
The following sketch puts an ATmega328P into Power-down and wakes every 8 seconds using the watchdog timer. Run it in the Arduino IDE, compile, and upload to your target board. No special permissions are required beyond normal USB access.
#include <avr/sleep.h>
#include <avr/wdt.h>
void setup() {
// Reduce board-level current before sleeping
ADCSRA &= ~(1 << ADEN); // Disable ADC
// Configure watchdog for 8 s interrupt (not reset)
WDTCSR = (1 << WDCE) | (1 << WDE); // Unlock WDTCSR
WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0); // 8 s, interrupt mode
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
void loop() {
sleep_enable();
sleep_cpu(); // Enter sleep; execution resumes after WDT ISR
sleep_disable();
// Re-enable peripherals here if needed
}
ISR(WDT_vect) {
// Empty handler required; optional LED toggle for validation
}
Expected checks: after upload, the board should consume microamps rather than milliamps when measured between Vcc and GND with no loads attached. If you toggle an LED inside ISR(WDT_vect), you should see a pulse every 8 seconds on an oscilloscope or logic analyzer.
Risks: the watchdog can be configured to reset the chip instead of interrupt. The unlock sequence (WDCE/WDE) must occur within four clock cycles or the change is ignored. On a full Uno or Nano, the onboard USB-serial converter and linear regulator typically draw 10–15 mA, masking the chip-level savings.
Validating the Result
Measure supply current with a multimeter in µA range or a dedicated current profiler. On a bare ATmega328P at 3.3 V, Power-down should read approximately 0.1–0.5 µA. On a complete Uno board, expect the regulator and USB-serial chip to dominate. To approach the datasheet figure, use a bare-bones board or a Pro Mini with the regulator removed or bypassed.
Compare your measurement against the ATmega328P datasheet's typical current consumption tables for your target voltage and temperature. If the reading is orders of magnitude higher, check for pull-up resistors, LEDs, or an enabled ADC.
Limitations That Change the Decision
- Brown-out detection (BOD): consumes roughly 20 µA in sleep. Disable it in software using the BODS/BODSE sequence, or adjust the BODLEVEL fuse, but only after confirming your battery voltage range.
- Watchdog ceiling: the maximum WDT interval is 8 s. Longer periods require chaining multiple wake-ups or moving to Timer2 in Power-save with an external 32.768 kHz crystal.
- Pin-change interrupts: require PCICR and PCMSK register setup. Pins on the same port share one interrupt vector, so you must poll the port to identify the source.
- Peripheral restoration: ADC, UART, and I2C do not auto-restore after wake-up. Re-enable them explicitly before use.
- Fuse settings: CKDIV8 and BODLEVEL affect startup time and sleep current. Verify against the datasheet for your target voltage and clock frequency.
If You Are Not on AVR
ESP32 and ESP8266 Arduino cores expose a different API, such as ESP.deepSleep(), with microamp-level deep sleep and RTC timer or GPIO wake-up. The register-level details differ significantly from AVR, so consult the Espressif technical reference and the Arduino-ESP32 examples rather than applying AVR sleep code directly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.