コンテンツにスキップ

Power Management, Watchdog, and Sleep Modes

Power Management, Watchdog, and Sleep Modes hero image

このコンテンツはまだ日本語訳がありません。

Modified:
Published:

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 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.

Project specifications:

ParameterValue
MCUATmega328P (standalone, 8 MHz internal oscillator)
Power source2x AA batteries (3V nominal)
Sleep modePower-down (deepest)
Sleep currentLess than 1 uA (BOD disabled)
Active current~5 mA at 3V, 8 MHz
Wake sourcesPin-change interrupt (reed switch), watchdog timer
Alert outputPiezo buzzer (PB1), LED (PB0)
Watchdog heartbeatEvery 8 seconds
Battery life estimateOver 1 year on 2x AA (2500 mAh)

Parts for This Lesson

RefComponentQuantityNotes
1ATmega328P-PU (DIP-28)1Standalone chip (not on Nano)
22x AA battery holder1With leads or JST connector
3AA batteries2Alkaline or lithium
4Magnetic reed switch1Normally closed (NC) type
5Piezo buzzer (passive)1From Lesson 3
6LED (any color)1Low-current type preferred
7220 ohm resistor1LED current limiting
8100 nF ceramic capacitor1Decoupling on VCC
9Breadboard1From previous lessons
10ISP programmer (USBasp or Arduino as ISP)1For programming standalone chip

ATmega328P Sleep Modes



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.

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 ModeCurrent (typical)What Stays RunningWake Sources
Idle~3 mAAll clocks, all peripheralsAny interrupt
ADC Noise Reduction~1 mAADC, Timer2 (async), ext. interruptsADC complete, ext. int, Timer2, PCINT
Power-save~1 uATimer2 (async), ext. interruptsTimer2, ext. int, PCINT
Power-down~0.1 uAExternal interrupts onlyINT0, INT1, PCINT, TWI address match
Standby~0.2 uAMain oscillator, ext. interruptsSame as power-down (faster wake)
Extended Standby~0.2 uAMain 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

  1. Disable the ADC before sleeping:

    ADCSRA &= ~(1 << ADEN);
  2. Shut down unused peripherals via PRR:

    PRR = (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM0)
    | (1 << PRTIM1) | (1 << PRSPI) | (1 << PRUSART0)
    | (1 << PRADC);
  3. 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;
  4. Disable the analog comparator:

    ACSR |= (1 << ACD);
  5. 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.

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

WDP3WDP2WDP1WDP0Timeout
000016 ms
000132 ms
001064 ms
0011125 ms
0100250 ms
0101500 ms
01101 s
01112 s
10004 s
10018 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>
static void wdt_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).

SignalATmega328P PinConnection
Reed switchPD2 (INT0) or PB0 (PCINT0)One terminal to pin, other to GND
Piezo buzzerPB1 (OC1A)Buzzer between PB1 and GND
Status LEDPB0LED with 220R to GND

EEPROM: Persistent Storage Across Power Cycles



Lesson 1 introduced the ATmega328P’s three memory spaces: Flash (program), SRAM (runtime variables), and EEPROM (non-volatile data). Flash and SRAM lose their contents when power is removed (SRAM) or can only be written during programming (Flash). EEPROM retains data through power cycles, making it ideal for storing configuration values, calibration constants, or event counters like our door-open count.

The ATmega328P has 1024 bytes of EEPROM with a rated endurance of 100,000 write cycles per byte. Three registers control access:

RegisterPurpose
EEARH:EEARLAddress (0 to 1023)
EEDRData byte to read or write
EECRControl: EEMPE (master program enable), EEPE (program enable), EERE (read enable)

Writing a single byte requires a timed sequence: set the address and data, enable the master program bit, then enable the program bit within four clock cycles. The CPU halts for about 3.3 ms during the write. Reading is instant (one clock cycle after setting EERE).

The avr-libc library provides avr/eeprom.h with convenient functions that handle the register sequence for you:

#include <avr/eeprom.h>
/* Read a 16-bit word from EEPROM address 0 */
uint16_t count = eeprom_read_word((uint16_t *)0);
/* Write only if the value changed (saves write cycles) */
eeprom_update_word((uint16_t *)0, count + 1);

The eeprom_update_* functions are preferred over eeprom_write_* because they skip the write if the value is already correct, extending EEPROM lifespan. At 10 door events per day, 100,000 write cycles would last over 27 years.

Our door alert will store the total number of door-open events at EEPROM address 0. Each time the reed switch triggers, the firmware increments the counter. On startup, the LED blinks the stored count (modulo 10) so you can check it without UART.

Complete Firmware



#ifndef F_CPU
#define F_CPU 8000000UL /* Internal 8 MHz RC oscillator */
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#define LED_PIN PB0
#define BUZZER_PIN PB1
#define REED_PIN PD2 /* INT0 */
#define EE_DOOR_COUNT ((uint16_t *)0) /* EEPROM address for door counter */
volatile uint8_t wdt_fired = 0;
volatile uint8_t door_opened = 0;
ISR(WDT_vect)
{
wdt_fired = 1;
}
ISR(INT0_vect)
{
door_opened = 1;
}
static void delay_ms(uint16_t ms)
{
while (ms--) _delay_ms(1);
}
static void led_blink(uint8_t count, uint16_t on_ms, uint16_t off_ms)
{
DDRB |= (1 << LED_PIN);
for (uint8_t i = 0; i < count; i++) {
PORTB |= (1 << LED_PIN);
delay_ms(on_ms);
PORTB &= ~(1 << LED_PIN);
delay_ms(off_ms);
}
}
static void buzzer_beep(uint16_t duration_ms)
{
/* Use Timer1 CTC for ~1 kHz tone */
DDRB |= (1 << BUZZER_PIN);
PRR &= ~(1 << PRTIM1); /* Enable Timer1 */
TCCR1A = (1 << COM1A0); /* Toggle OC1A */
TCCR1B = (1 << WGM12) | (1 << CS11); /* CTC, prescaler 8 */
OCR1A = 499; /* counts 0-499 (500 steps): 8MHz / (2 * 8 * 500) = 1 kHz */
delay_ms(duration_ms);
TCCR1A = 0;
TCCR1B = 0;
PORTB &= ~(1 << BUZZER_PIN);
PRR |= (1 << PRTIM1); /* Disable Timer1 */
}
static void enter_deep_sleep(void)
{
/* Disable ADC */
ADCSRA &= ~(1 << ADEN);
/* Disable analog comparator */
ACSR |= (1 << ACD);
/* Power down unused peripherals */
PRR = (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM0)
| (1 << PRTIM1) | (1 << PRSPI) | (1 << PRUSART0)
| (1 << PRADC);
/* Set all output pins low to save power */
DDRB &= ~((1 << LED_PIN) | (1 << BUZZER_PIN));
PORTB &= ~((1 << LED_PIN) | (1 << BUZZER_PIN));
/* 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();
}
int main(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();
/* Read door count from EEPROM and blink it (mod 10) at startup */
uint16_t door_count = eeprom_read_word(EE_DOOR_COUNT);
if (door_count == 0xFFFF) door_count = 0; /* Fresh EEPROM reads as 0xFF */
uint8_t show = door_count % 10;
if (show > 0) led_blink(show, 150, 150);
else led_blink(3, 100, 100); /* No events yet */
while (1) {
enter_deep_sleep();
/* --- Woke up --- */
if (door_opened) {
door_opened = 0;
/* Increment and save door count */
door_count++;
eeprom_update_word(EE_DOOR_COUNT, door_count);
/* 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:

MCU = atmega328p
F_CPU = 8000000UL
CC = avr-gcc
OBJCOPY = avr-objcopy
CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -Wextra -std=c11
TARGET = door_alert
all: $(TARGET).hex
$(TARGET).elf: main.c
$(CC) $(CFLAGS) -o $@ $<
$(TARGET).hex: $(TARGET).elf
$(OBJCOPY) -O ihex -R .eeprom $< $@
avr-size --mcu=$(MCU) -C $<
flash: $(TARGET).hex
avrdude -c usbasp -p $(MCU) -U flash:w:$<
clean:
rm -f $(TARGET).elf $(TARGET).hex
.PHONY: all flash clean

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.

FuseValueMeaning
lfuse0xE2Internal 8 MHz RC, 65ms startup, no clock divide
hfuse0xD9No bootloader, SPI enabled, EEPROM preserved
efuse0xFFBOD disabled (we disable it in software too)
Terminal window
# Program fuses with USBasp
avrdude -c usbasp -p m328p \
-U lfuse:w:0xE2:m \
-U hfuse:w:0xD9:m \
-U efuse: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

ScenarioAverage CurrentBattery Life (2500 mAh AA)
Always active (8 MHz, 3V)5 mA~21 days
Sleep with 8s heartbeat (20ms active)~1.5 uA averageOver 10 years (theoretical)
Door opens 10 times/day (2s beep each)~3 uA averageOver 5 years (theoretical)
Realistic with self-discharge~3 uA average1 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



  1. 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.
  2. Replace the reed switch with a PIR motion sensor. The sensor’s digital output goes high on motion and can drive INT0 directly.
  3. Add a “reset counter” feature: if the button is held during power-up (reed switch closed), clear the EEPROM counter to zero and confirm with a long LED flash. This lets you start fresh after checking the count.
  4. 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, the watchdog timer as both a safety mechanism and a periodic wakeup source, and EEPROM for storing data that survives power cycles. The door alert project demonstrates how a battery-powered device can sleep at sub-microamp levels, wake instantly on an external event, and keep a persistent event log in non-volatile memory. 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

Loading comments...


© 2021-2026 SiliconWit®. All rights reserved.