A battery-powered Arduino running at full speed drains a typical AA cell in hours. But with proper use of sleep modes, that same circuit can run for months or even years on the same battery. Arduino sleep mode low power techniques leverage the microcontroller’s hardware sleep states to cut current draw from 15–20 mA down to 0.1–5 µA — a reduction of 1,000 to 100,000 times. This guide covers every sleep mode on the ATmega328P, how to wake from them, power consumption measurements, and complete code for common low-power sensor applications.
- Why Sleep Modes Matter for Battery Life
- ATmega328P Sleep Modes Overview
- Power Consumption by Mode
- Using avr/sleep.h
- Wake-Up Sources
- WDT-Based Periodic Wake
- Interrupt-Based Wake
- LowPower Library: The Easy Way
- Additional Power Reduction Techniques
- Frequently Asked Questions
Why Sleep Modes Matter for Battery Life
Consider a typical remote temperature sensor checking readings every 60 seconds. With a 1000 mAh battery at the Arduino’s active current of 15 mA, that’s about 66 hours of runtime. The same device using Power-Down sleep between readings, drawing 0.4 µA asleep and 15 mA active for 50 ms per wake cycle, has an average current of:
Average = (15000 µA × 0.05s + 0.4 µA × 59.95s) / 60s ≈ 13.0 µA
That’s 1000 mAh / 0.013 mA ≈ 77,000 hours — about 8.8 years on the same battery. Sleep modes aren’t an optimisation; for battery-powered devices, they’re a fundamental design requirement.
The savings are available for any periodic sensor — weather stations, GPS trackers, soil moisture monitors, water leak detectors, livestock trackers, and smart home sensors all benefit dramatically from sleep modes.
ATmega328P Sleep Modes Overview
The ATmega328P supports six sleep modes, progressively shutting down more hardware subsystems:
| Mode | CPU | I/O Clk | Flash | WDT | Timer/Counter | Wake Sources |
|---|---|---|---|---|---|---|
| Idle | Stop | Run | Run | Run | Run | Any interrupt |
| ADC Noise Reduction | Stop | Stop | Stop | Run | ADC only | ADC, external INT, TWI, WDT |
| Power-Save | Stop | Stop | Stop | Run | T2 only | WDT, T2 overflow, external INT, TWI |
| Power-Down | Stop | Stop | Stop | Run | Stop | WDT, external INT, TWI |
| Standby | Stop | Stop | Run | Run | Stop | WDT, external INT, TWI |
| Extended Standby | Stop | Stop | Run | Run | T2 only | WDT, T2 overflow, external INT, TWI |
For most low-power projects, Power-Down is the right choice. It stops everything except the WDT and external interrupt logic, achieving the lowest possible current draw while still allowing periodic wake-up.
Power Consumption by Mode
Measured at 5V, 25°C on an ATmega328P (bare chip, not full Arduino board):
| Mode | Typical Current (5V) | Notes |
|---|---|---|
| Active (16 MHz) | ~12 mA | Bare chip; full Arduino Uno adds ~5 mA for regulators, LEDs |
| Idle | ~5 mA | CPU stopped, peripherals still running |
| ADC Noise Reduction | ~1.1 mA | ADC active for accurate conversion |
| Power-Save | ~1.1 µA | Timer2 running; BOD disabled |
| Power-Down | ~0.1–0.5 µA | BOD disabled; only WDT osc. running |
Important note: measurements above are for the bare ATmega328P chip. A complete Arduino Uno board draws ~4–5 mA more due to the USB-Serial chip (ATmega16U2 or CH340), onboard LED, and voltage regulator quiescent current. For true low-power deployments, use bare ATmega328P on a minimal PCB, an Arduino Pro Mini (no USB chip), or a 3.3V board with a low-quiescent regulator.
Using avr/sleep.h
The AVR libc sleep library provides a clean API for entering sleep modes:
#include <avr/sleep.h>
#include <avr/power.h>
void goToSleep() {
// Choose sleep mode
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Optional: disable ADC to save more power
power_adc_disable();
// Enable sleep, then sleep
sleep_enable();
sleep_cpu(); // CPU halts here; resumes after wake
// Code resumes here after wake-up interrupt
sleep_disable(); // Always disable sleep after waking
power_adc_enable();
}
Available sleep mode constants:
SLEEP_MODE_IDLESLEEP_MODE_ADCSLEEP_MODE_PWR_SAVESLEEP_MODE_PWR_DOWNSLEEP_MODE_STANDBYSLEEP_MODE_EXT_STANDBY
Wake-Up Sources
In Power-Down mode, the processor can only wake from:
- External Interrupt (INT0 / INT1): Level-triggered LOW level, or edge (rising/falling) depending on interrupt configuration. These are digital pins D2 (INT0) and D3 (INT1) on the Uno.
- Pin Change Interrupt (PCINT): Any change on enabled PCINT pins (all Arduino pins can be PCINT sources). Level change detection — less precise timing than INT0/INT1.
- Watchdog Timer interrupt
- TWI (I2C) address match — if the device is acting as an I2C slave
Key limitation: in Power-Down mode, the INT0/INT1 pins can only detect a LOW level (not rising or falling edge) unless the BOD (Brown-Out Detector) is active. To wake on a rising edge with minimal power, keep BOD disabled and use a LOW-level interrupt with a pull-up resistor — when a button is pressed pulling the pin low, the interrupt fires.
WDT-Based Periodic Wake
The most common pattern for sensor devices: sleep in Power-Down mode, wake every N seconds via WDT interrupt, read sensor, sleep again.
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <avr/power.h>
volatile bool wdtWake = false;
// WDT ISR — just set flag, no Serial here
ISR(WDT_vect) {
wdtWake = true;
}
void enableWDT_interrupt(uint8_t timerBits) {
cli();
WDTCSR |= (1 << WDCE) | (1 << WDE);
// Interrupt-only mode (no reset), with specified period
WDTCSR = (1 << WDIE) | timerBits;
sei();
}
void sleepNow() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
power_adc_disable();
power_spi_disable();
power_twi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_usart0_disable();
sleep_enable();
sleep_cpu();
sleep_disable();
// Re-enable needed peripherals after wake
power_all_enable();
}
void setup() {
Serial.begin(9600);
// WDT interrupt every 8 seconds (WDTO_8S bits = WDP3|WDP0)
enableWDT_interrupt((1 << WDP3) | (1 << WDP0));
}
void loop() {
if (wdtWake) {
wdtWake = false;
// Read sensor
float temp = readTemperature();
Serial.print("Temp: ");
Serial.println(temp);
Serial.flush(); // Ensure Serial transmits before sleep
// Re-arm WDT interrupt for next cycle
enableWDT_interrupt((1 << WDP3) | (1 << WDP0));
}
sleepNow();
}
For intervals longer than 8 seconds (the WDT maximum), count WDT cycles:
volatile uint8_t wdtCount = 0;
#define SLEEP_CYCLES 8 // 8 × 8s = 64 seconds between readings
ISR(WDT_vect) { wdtCount++; }
void loop() {
if (wdtCount >= SLEEP_CYCLES) {
wdtCount = 0;
doSensorReading();
}
sleepNow();
}
Interrupt-Based Wake
For event-driven applications (a PIR motion sensor, a door contact, a button), wake on external interrupt rather than WDT:
#include <avr/sleep.h>
#include <avr/interrupt.h>
#define WAKE_PIN 2 // INT0 = D2
volatile bool triggered = false;
ISR(INT0_vect) {
triggered = true;
}
void sleepUntilEvent() {
// Configure INT0 for LOW level wake
EICRA &= ~((1 << ISC01) | (1 << ISC00)); // LOW level
EIMSK |= (1 << INT0); // Enable INT0
sei();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu();
sleep_disable();
EIMSK &= ~(1 << INT0); // Disable INT0 after wake
}
void setup() {
Serial.begin(9600);
pinMode(WAKE_PIN, INPUT_PULLUP); // Pull-up: LOW when PIR triggers
}
void loop() {
sleepUntilEvent();
if (triggered) {
triggered = false;
Serial.println("Motion detected!");
// Process event...
delay(5000); // Active window: 5 seconds
Serial.flush();
}
}
LowPower Library: The Easy Way
The LowPower library by Rocket Scream wraps all the avr/sleep.h complexity into a clean one-liner API:
#include "LowPower.h"
void setup() {
Serial.begin(9600);
}
void loop() {
// Read sensors
float temp = readTemperature();
Serial.println(temp);
Serial.flush();
// Sleep for 8 seconds: Power-Down, ADC off, BOD off
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
// Execution resumes here after 8 seconds
}
Install via Arduino Library Manager: search for “Low-Power” by Rocket Scream Electronics. Available sleep periods: SLEEP_15MS, SLEEP_30MS, SLEEP_60MS, SLEEP_120MS, SLEEP_250MS, SLEEP_500MS, SLEEP_1S, SLEEP_2S, SLEEP_4S, SLEEP_8S, SLEEP_FOREVER.
For SLEEP_FOREVER (wake on external interrupt only):
void loop() {
// Attach interrupt before sleeping
attachInterrupt(digitalPinToInterrupt(2), wakeISR, LOW);
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
detachInterrupt(digitalPinToInterrupt(2));
// Handle event
processEvent();
}
Additional Power Reduction Techniques
Sleep modes alone don’t capture all available savings. Additional techniques compound the reduction:
1. Disable the Brown-Out Detector (BOD)
The BOD constantly monitors supply voltage, drawing ~20 µA. In Power-Down mode, disabling BOD via MCUCR before sleeping saves significant current. The LowPower library’s BOD_OFF parameter handles this automatically.
2. Lower the Supply Voltage
ATmega328P power draw scales roughly linearly with voltage and quadratically with clock speed. Running at 3.3V instead of 5V reduces active current by ~33%. Running at 8 MHz instead of 16 MHz halves dynamic power in active mode. The Pro Mini 3.3V/8 MHz combines both.
3. Power Peripherals via a Mosfet
Sensors and modules draw quiescent current even when not actively reading. A P-channel mosfet (like 2N7000 or SI2307) controlled by an Arduino output pin can completely cut power to a peripheral between readings:
#define SENSOR_POWER_PIN 5 // Controls mosfet gate
void readSensor() {
digitalWrite(SENSOR_POWER_PIN, HIGH); // Power on sensor
delay(100); // Allow sensor to stabilise
float reading = sensor.read();
digitalWrite(SENSOR_POWER_PIN, LOW); // Power off
return reading;
}
4. Disable Unused Arduino Peripherals
The power.h library lets you disable individual hardware peripherals:
#include <avr/power.h>
void setup() {
power_adc_disable(); // ADC: ~300 µA saved
power_spi_disable(); // SPI: ~1.5 mA at 16 MHz
power_twi_disable(); // TWI/I2C
power_timer1_disable(); // Timer1 (if not using PWM or Servo)
power_timer2_disable(); // Timer2 (if not using tone())
power_usart0_disable(); // UART (if not using Serial)
}
5. Remove Onboard LEDs
The Uno’s power LED draws ~2 mA permanently. For long-term battery deployments, physically remove this LED (D1 on the board) or cut its trace. The TX/RX LEDs are only active during serial communication.
Frequently Asked Questions
Does millis() still work after sleeping?
Partially. Timer0 (which drives millis()) is stopped during Power-Down sleep. millis() will not increment while the CPU is asleep. After waking, millis() resumes from where it left off — it does not account for time spent sleeping. If you need accurate elapsed time tracking across sleep cycles, track the expected sleep duration manually and add it to a software clock counter, or use an external RTC module like the DS3231.
Will Serial.print() work after waking from sleep?
Yes, but always call Serial.flush() before entering sleep to ensure any pending transmission completes. UART transmission in progress when you enter sleep will be interrupted. After waking, the UART peripheral is re-enabled and Serial works normally. If you disabled the USART with power_usart0_disable(), re-enable it with power_usart0_enable() after waking.
What is the minimum achievable current draw with an Arduino Pro Mini?
With the Pro Mini 3.3V version: remove the power LED (saves ~1 mA), disable BOD, enter Power-Down mode — typical measured current is 0.1–0.2 µA at 3.3V. The main residual draw is the voltage regulator’s quiescent current (~50 µA for the common MIC5205). Bypass the regulator and power the 3.3V pin directly from a LiPo or regulated supply to achieve the sub-µA chip current.
Can I use sleep modes with the Arduino Nano 33 IoT or Nano RP2040?
These boards use different microcontrollers (SAMD21 and RP2040 respectively) with their own sleep/dormant modes. The avr/sleep.h library is AVR-specific and won’t compile for these boards. Use the Arduino Low Power library (by Arduino SA) for SAMD21 boards, or the RP2040’s SDK dormant mode for the RP2040. Concepts are identical — the API differs.
How do I measure the actual sleep current of my circuit?
The current in sleep mode (sub-µA range) is too low for most standard multimeters to read accurately. Use a dedicated power profiler like the Nordic PPK2, Current Ranger by Low Power Lab, or a precision microammeter. Alternatively, measure the voltage across a 100 kΩ resistor in series with the power supply — at 0.1 µA, this gives a 10 mV drop readable by a standard multimeter set to mV.
Ready to build battery-powered Arduino projects? Browse our selection of Arduino boards optimised for low-power applications at Zbotic — including the Pro Mini, Nano Every, and Nano RP2040 Connect, plus sensors and modules suitable for long-life battery-powered deployments across India.
Add comment