A microcontroller that runs continuously from a battery will drain it in hours. But the ATmega328P can drop its current consumption from roughly 12 mA down to under 1 microamp in power-down sleep mode. The difference between a project that lasts a day and one that lasts a year comes down to how well you manage sleep, wake sources, and peripheral shutdown. In this final lesson you will explore all six sleep modes, configure the watchdog timer as both a safety net and a periodic wakeup source, and build a battery-powered door open alert. The device sleeps almost all the time, wakes when a magnetic reed switch opens, beeps a piezo buzzer, and goes right back to sleep. #PowerManagement #SleepModes #LowPower
What We Are Building
Battery-Powered Door Open Alert
A standalone device powered by two AA batteries (3V) that monitors a door using a magnetic reed switch. When the door opens (magnet moves away, reed switch opens), a pin-change interrupt wakes the MCU from power-down sleep. The firmware sounds a piezo buzzer for 2 seconds, blinks an LED 3 times, then returns to deep sleep. A watchdog timer provides a periodic 8-second wakeup to blink the LED once as a “still alive” heartbeat. In power-down mode with BOD disabled, the entire circuit draws under 1 microamp.
The ATmega328P has six sleep modes, each disabling progressively more hardware to save power. The deeper modes save more current but limit which events can wake the CPU.
Sleep mode hierarchy (shallow to deep):
+----------------+ ~3 mA Any interrupt wakes
| Idle |
+-------+--------+
|
+-------+--------+ ~1 mA ADC, ext int, Timer2
| ADC Noise Red. |
+-------+--------+
|
+-------+--------+ ~1 uA Timer2, ext int
| Power-save |
+-------+--------+
|
+-------+--------+ ~0.1 uA INT0/1, PCINT, TWI
| Power-down | (used in this lesson)
+-------+--------+
|
+-------+--------+ ~0.2 uA Same + faster wake
| Standby |
+-------+--------+
|
+-------+--------+ ~0.2 uA + Timer2 async
| Ext. Standby |
+----------------+
The deeper the sleep, the fewer wake sources are available and the longer it takes to resume execution. Power-down mode disables everything except the external interrupt logic and the watchdog timer, achieving the lowest possible current draw.
Sleep Mode
Current (typical)
What Stays Running
Wake Sources
Idle
~3 mA
All clocks, all peripherals
Any interrupt
ADC Noise Reduction
~1 mA
ADC, Timer2 (async), ext. interrupts
ADC complete, ext. int, Timer2, PCINT
Power-save
~1 uA
Timer2 (async), ext. interrupts
Timer2, ext. int, PCINT
Power-down
~0.1 uA
External interrupts only
INT0, INT1, PCINT, TWI address match
Standby
~0.2 uA
Main oscillator, ext. interrupts
Same as power-down (faster wake)
Extended Standby
~0.2 uA
Main osc, Timer2 (async)
Timer2 + power-down sources
Entering Sleep Mode
#include<avr/sleep.h>
/* Select sleep mode */
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
/* Enable sleep, enter sleep, disable sleep after waking */
sleep_enable();
sei(); /* Interrupts must be enabled to wake */
sleep_cpu(); /* CPU halts here until interrupt */
sleep_disable();
Reducing Power Consumption
Getting to sub-microamp sleep current requires more than just calling sleep_mode(). You need to disable every peripheral that draws current in the background. The PRR (Power Reduction Register) lets you shut down individual peripherals. The ADC has its own enable bit that should be cleared before sleeping. BOD (Brown-Out Detection) can be disabled during sleep to save another 15 to 20 microamps.
Power Reduction Checklist
Disable the ADC before sleeping:
ADCSRA &=~(1<< ADEN);
Shut down unused peripherals via PRR:
PRR = (1<< PRTWI) | (1<< PRTIM2) | (1<< PRTIM0)
| (1<< PRTIM1) | (1<< PRSPI) | (1<< PRUSART0)
| (1<< PRADC);
Set all unused pins as inputs with pull-ups enabled (prevents floating pins from drawing current):
DDRB =0x00; PORTB =0xFF;
DDRC =0x00; PORTC =0xFF;
DDRD =0x00; PORTD =0xFF;
Disable the analog comparator:
ACSR |= (1<< ACD);
Disable BOD during sleep (must be done in a timed sequence):
MCUCR |= (1<< BODS) | (1<< BODSE);
MCUCR = (MCUCR &~(1<< BODSE)) | (1<< BODS);
sleep_cpu(); /* Must enter sleep within 3 clock cycles */
Watchdog Timer
The door alert firmware spends nearly all its time asleep, waking only on two events. The watchdog provides a periodic heartbeat, while INT0 signals an immediate alarm condition.
Door alert firmware flow:
+-------------+
| Power on |
+------+------+
|
v
+------+------+
| Init GPIO, |
| WDT, INT0 |
+------+------+
|
v
+------+------+<-----+<-----+
| Enter | | |
| Power-down | | |
| sleep | | |
+------+------+ | |
| | |
[wake source?] | |
/ \ | |
v v | |
[WDT] [INT0] | |
| (door) | |
v v | |
Blink Beep 2s | |
LED once Blink 3x | |
| | | |
+-----------+-------+ |
|
The watchdog timer (WDT) has two uses: as a safety mechanism that resets the chip if the firmware hangs, and as a periodic wakeup source during sleep. It runs from a separate 128 kHz internal oscillator that operates independently of the main clock. The WDT can be configured with timeout periods from 16 ms to 8 seconds.
Watchdog Timeout Periods
WDP3
WDP2
WDP1
WDP0
Timeout
0
0
0
0
16 ms
0
0
0
1
32 ms
0
0
1
0
64 ms
0
0
1
1
125 ms
0
1
0
0
250 ms
0
1
0
1
500 ms
0
1
1
0
1 s
0
1
1
1
2 s
1
0
0
0
4 s
1
0
0
1
8 s
Configuring WDT for Interrupt (Not Reset)
By default, the watchdog resets the chip on timeout. For a periodic wakeup, you configure it to fire an interrupt instead. Setting WDIE (Watchdog Interrupt Enable) in WDTCSR makes the WDT generate an interrupt on timeout. The interrupt clears WDIE automatically, so you must re-enable it before each sleep cycle.
#include<avr/wdt.h>
staticvoidwdt_setup_interrupt_8s(void)
{
cli();
wdt_reset();
/* Timed sequence to change WDT configuration */
WDTCSR |= (1<< WDCE) | (1<< WDE);
/* Interrupt mode, 8s timeout (WDP3=1, WDP0=1) */
WDTCSR = (1<< WDIE) | (1<< WDP3) | (1<< WDP0);
sei();
}
ISR(WDT_vect)
{
/* Watchdog fired. WDIE is auto-cleared. */
/* Re-enable for next cycle in main loop */
}
Reed Switch Wiring
The standalone ATmega328P circuit runs from two AA batteries with minimal external components. No crystal is needed because the internal 8 MHz oscillator provides the clock.
Standalone ATmega328P circuit:
2x AA (3V)
+ -
| |
+-----+--+---------+
| VCC GND |
| |
| ATmega328P-PU |
| |
| PB0---[220R]---[LED]---GND
| PB1---[Piezo buzzer]---GND
| PD2---[Reed switch]---GND
| |
| VCC---[100nF]---GND (decoupling)
| |
+-------------------+
Programming: connect USBasp to
MOSI, MISO, SCK, RESET, VCC, GND
A magnetic reed switch is a glass tube containing two ferromagnetic contacts that close in the presence of a magnet. The normally-closed (NC) type keeps the contacts closed when the magnet is near (door closed) and opens them when the magnet moves away (door opens). Wire one terminal to the MCU pin and the other to ground. Enable the internal pull-up on the MCU pin. When the door is closed, the pin reads low (shorted to ground). When the door opens, the pin goes high (pulled up).
/* Re-enable watchdog interrupt (auto-cleared after each trigger) */
WDTCSR |= (1<< WDIE);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
/* Disable BOD during sleep (timed sequence) */
MCUCR |= (1<< BODS) | (1<< BODSE);
MCUCR = (MCUCR &~(1<< BODSE)) | (1<< BODS);
sei();
sleep_cpu(); /* Sleep here until interrupt */
sleep_disable();
}
intmain(void)
{
/* Disable watchdog if set by bootloader */
MCUSR &=~(1<< WDRF);
wdt_disable();
/* Reed switch on PD2 (INT0): input with pull-up */
DDRD &=~(1<< REED_PIN);
PORTD |= (1<< REED_PIN);
/* INT0: rising edge (pin goes high when door opens) */
EICRA = (1<< ISC01) | (1<< ISC00);
EIMSK = (1<< INT0);
/* Watchdog: interrupt mode, 8s timeout */
cli();
wdt_reset();
WDTCSR |= (1<< WDCE) | (1<< WDE);
WDTCSR = (1<< WDIE) | (1<< WDP3) | (1<< WDP0);
sei();
/* Startup indication */
led_blink(3, 100, 100);
while (1) {
enter_deep_sleep();
/* --- Woke up --- */
if (door_opened) {
door_opened =0;
/* Sound alarm */
buzzer_beep(2000);
led_blink(3, 200, 200);
/* Wait for door to close (debounce) */
_delay_ms(500);
}
if (wdt_fired) {
wdt_fired =0;
/* Heartbeat blink */
DDRB |= (1<< LED_PIN);
PORTB |= (1<< LED_PIN);
_delay_ms(20);
PORTB &=~(1<< LED_PIN);
DDRB &=~(1<< LED_PIN);
}
}
}
Makefile for This Lesson
This project runs at 8 MHz on the internal oscillator, not 16 MHz like the Nano. Copy your Lesson 1 Makefile and change the clock and programmer settings:
Note the differences from earlier lessons: F_CPU is 8000000UL (not 16000000UL), the programmer is usbasp (not arduino), and there is no -P port or -b baud flag since USBasp uses direct SPI, not serial.
Fuse Settings for Standalone Operation
When running the ATmega328P standalone on batteries, you need fuse settings for the internal 8 MHz oscillator (no external crystal needed). This simplifies the circuit and saves the power that the crystal oscillator draws.
Fuse
Value
Meaning
lfuse
0xE2
Internal 8 MHz RC, 65ms startup, no clock divide
hfuse
0xD9
No bootloader, SPI enabled, EEPROM preserved
efuse
0xFF
BOD disabled (we disable it in software too)
Terminal window
# Program fuses with USBasp
avrdude-cusbasp-pm328p\
-Ulfuse:w:0xE2:m\
-Uhfuse:w:0xD9:m\
-Uefuse:w:0xFF:m
Measuring Power Consumption
To verify your power savings, measure the current with a multimeter in series with the battery. In power-down sleep, expect readings below 1 microamp (you may need a microamp-range meter). In active mode at 3V and 8 MHz, expect around 4 to 5 mA. The ratio between these two values determines your battery life: if the device sleeps 99.99% of the time, the average current is dominated by the sleep current.
Battery Life Estimation
Scenario
Average Current
Battery Life (2500 mAh AA)
Always active (8 MHz, 3V)
5 mA
~21 days
Sleep with 8s heartbeat (20ms active)
~1.5 uA average
Over 10 years (theoretical)
Door opens 10 times/day (2s beep each)
~3 uA average
Over 5 years (theoretical)
Realistic with self-discharge
~3 uA average
1 to 2 years
The limiting factor in most battery-powered projects is the battery’s self-discharge rate (about 5% per year for alkaline), not the circuit’s current draw.
Exercises
Add a “low battery” warning: wake every 30 minutes, read the battery voltage via the internal bandgap reference trick (measure 1.1V bandgap against VCC), and if VCC drops below 2.4V, blink the LED rapidly.
Replace the reed switch with a PIR motion sensor. The sensor’s digital output goes high on motion and can drive INT0 directly.
Implement a door-open counter stored in EEPROM. Each time the door opens, increment the count. On a special button press (hold during power-up), read and transmit the count over UART.
Measure and compare the sleep current with and without BOD disabled, with and without the analog comparator disabled, and with floating pins vs. pulled-up pins. Document the contribution of each source.
Summary
You now understand every power management feature of the ATmega328P: six sleep modes from Idle to Power-down, the Power Reduction Register, analog comparator disable, BOD disable during sleep, and the watchdog timer as both a safety mechanism and a periodic wakeup source. The door alert project demonstrates how a battery-powered device can sleep at sub-microamp levels and wake instantly on an external event. These techniques are essential for any embedded system that runs on batteries, and the principles apply directly to every other microcontroller family you will encounter.
Comments