Arduino Nano Stromverbrauch senken: Sleep-Modi, Watchdog-Wakeup & DS3231-Alarm für monatelangen Batteriebetrieb
Du baust einen Sensor, der draußen im Garten stehen soll. Der Sketch läuft, die Schaltung ist fertig, vier AA-Zellen sind dran – und nach zwei Tagen ist Schluss. Ein Arduino Nano zieht mit einem delay(1000) in der Schleife genauso viel Strom wie beim Rechnen: rund 19 mA. Der ATmega328P selbst kann aber unter 1 µA. Dieser Artikel zeigt dir, wo die anderen 18,999 mA hingehen – und wie du sie loswirst.
Wo der Strom wirklich hingeht
Das überraschendste Ergebnis vorweg: Der Mikrocontroller ist beim Nano nicht der größte Verbraucher. Diese Messwerte stammen von einem Nano V3 mit CH340 bei 5 V über USB:
| Verbraucher | Strom | Abschaltbar? |
|---|---|---|
| ATmega328P aktiv, 16 MHz | ca. 9 mA | durch Sleep-Modus |
| Power-LED (rot) | ca. 3 mA | nur mit Lötkolben |
| USB-Seriell-Chip CH340 | ca. 5 mA | nur bei Versorgung über VIN/5V |
| Spannungsregler (Ruhestrom) | ca. 2 mA | durch Umgehen des Reglers |
| Summe im Leerlauf | ca. 19 mA | – |
| ATmega328P im Power-Down | ca. 0,15 µA | Zielwert |
💡 Software allein bringt dich von 19 mA auf etwa 10 mA – das ist ein Faktor 2. Software plus Hardware bringt dich auf 0,1 mA – das ist ein Faktor 190. Deshalb lohnt sich dieser Artikel nur, wenn du beide Hälften machst. Ein Nano mit perfektem Sleep-Code und funktionierender Power-LED ist immer noch nach einer Woche leer.
ℹ️ Die fünf Sleep-Modi des ATmega328P. Der Chip kennt IDLE, ADC Noise Reduction, Power-save, Standby und Power-down. Interessant sind praktisch nur zwei: IDLE (CPU aus, alle Timer laufen weiter, ca. 4 mA – gut für kurze Wartezeiten mit millis()) und Power-down (alles aus außer Watchdog und externen Interrupts, unter 1 µA). Die Zwischenstufen bringen im Maker-Alltag kaum Vorteile.
Das brauchst du
Für die Messung brauchst du ein Multimeter, das Mikroampere auflöst. Viele einfache Geräte zeigen im µA-Bereich nur Nullen – dann hilft ein 100-Ω-Shunt in der Versorgungsleitung und die Spannungsmessung darüber.
1Power-Down-Sleep ohne Bibliothek
Du brauchst keine externe Library. Die Register des ATmega328P sind über avr/sleep.h direkt erreichbar, und der Watchdog lässt sich in acht Stufen von 16 ms bis 8 s einstellen:
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
volatile bool wdtGeweckt = false;
// Der Watchdog loest im Interrupt-Modus diese ISR aus statt zu resetten
ISR(WDT_vect) {
wdtGeweckt = true;
}
// periode: 0=16ms 1=32ms 2=64ms 3=125ms 4=250ms 5=500ms
// 6=1s 7=2s 8=4s 9=8s
void watchdogSetzen(uint8_t periode) {
if (periode > 9) periode = 9;
uint8_t wdps = periode & 0x07;
if (periode > 7) wdps |= (1 << WDP3); // Bit 5 liegt woanders
MCUSR &= ~(1 << WDRF); // Reset-Flag loeschen
cli();
WDTCSR = (1 << WDCE) | (1 << WDE); // Aenderung freigeben
WDTCSR = (1 << WDIE) | wdps; // Interrupt statt Reset
sei();
}
void watchdogAus() {
cli();
MCUSR &= ~(1 << WDRF);
WDTCSR = (1 << WDCE) | (1 << WDE);
WDTCSR = 0x00;
sei();
}
// Ein Schlafzyklus im tiefsten Modus
void schlafeTief() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
ADCSRA &= ~(1 << ADEN); // ADC aus - spart allein rund 250 uA
power_all_disable(); // alle Peripherie-Takte abschalten
sleep_enable();
// Brown-Out-Detector fuer die Schlafphase deaktivieren.
// Diese Sequenz muss innerhalb von 4 Takten abgeschlossen sein.
MCUCR = (1 << BODS) | (1 << BODSE);
MCUCR = (1 << BODS);
sei();
sleep_cpu(); // ---- hier schlaeft der Chip ----
sleep_disable();
power_all_enable();
ADCSRA |= (1 << ADEN);
}
// Bequemer Wrapper: schlafe ungefaehr n Sekunden
void schlafeSekunden(uint16_t sekunden) {
while (sekunden >= 8) { watchdogSetzen(9); schlafeTief(); sekunden -= 8; }
while (sekunden >= 4) { watchdogSetzen(8); schlafeTief(); sekunden -= 4; }
while (sekunden >= 2) { watchdogSetzen(7); schlafeTief(); sekunden -= 2; }
while (sekunden >= 1) { watchdogSetzen(6); schlafeTief(); sekunden -= 1; }
watchdogAus();
}
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(50);
digitalWrite(LED_BUILTIN, LOW);
Serial.println(F("schlafe 30 Sekunden..."));
Serial.flush(); // WICHTIG: Puffer leeren vor dem Schlafen
schlafeSekunden(30);
}
⚠️ Serial.flush() vor jedem Schlafen – sonst gehen Zeichen verloren. Der UART sendet asynchron im Hintergrund. Legt sich der Chip schlafen, während noch Zeichen im Puffer stehen, bleiben sie dort liegen oder werden verstümmelt ausgegeben. Das ist ein Klassiker, der beim Debuggen viel Verwirrung stiftet.
⚠️ Der Watchdog-Oszillator ist ungenau. Er läuft mit rund 128 kHz aus einem internen RC-Glied und driftet mit Temperatur und Betriebsspannung um bis zu 10 %. Eine Stunde „Watchdog-Zeit" kann in der Praxis 54 oder 66 Minuten dauern. Für einen Bodenfeuchte-Logger egal, für eine Zeitschaltuhr unbrauchbar – dafür kommt gleich der DS3231.
2Aufwachen per externem Interrupt
Ein Bewegungsmelder, ein Reed-Kontakt oder ein Taster soll den Nano wecken? Dann brauchst du keinen Watchdog. Beim ATmega328P können aus dem Power-Down nur D2 und D3 als klassische Interrupts wecken – und dort nur mit LOW oder CHANGE:
#include <avr/sleep.h>
#include <avr/power.h>
#define PIR_PIN 2 // INT0 - HC-SR501 Ausgang
#define TASTER_PIN 3 // INT1 - Taster gegen GND
#define LED_PIN 8
volatile bool weckgrundPir = false;
volatile bool weckgrundTaster = false;
void isrPir() { weckgrundPir = true; }
void isrTaster() { weckgrundTaster = true; }
void schlafeBisEreignis() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
ADCSRA &= ~(1 << ADEN);
power_all_disable();
// Interrupts erst kurz vor dem Schlafen scharf schalten,
// sonst verpasst man Ereignisse in der Vorbereitungsphase.
attachInterrupt(digitalPinToInterrupt(PIR_PIN), isrPir, RISING);
attachInterrupt(digitalPinToInterrupt(TASTER_PIN), isrTaster, LOW);
sleep_enable();
MCUCR = (1 << BODS) | (1 << BODSE);
MCUCR = (1 << BODS);
sei();
sleep_cpu();
sleep_disable();
detachInterrupt(digitalPinToInterrupt(PIR_PIN));
detachInterrupt(digitalPinToInterrupt(TASTER_PIN));
power_all_enable();
ADCSRA |= (1 << ADEN);
}
void setup() {
Serial.begin(9600);
pinMode(PIR_PIN, INPUT); // PIR treibt aktiv - kein Pullup
pinMode(TASTER_PIN, INPUT_PULLUP); // Taster gegen GND
pinMode(LED_PIN, OUTPUT);
// Alle ungenutzten Pins definiert legen - offene Eingaenge
// koennen im Schlaf durch Umladen mehrere hundert uA kosten.
for (uint8_t p = 4; p <= 7; p++) pinMode(p, INPUT_PULLUP);
for (uint8_t p = 9; p <= 13; p++) pinMode(p, INPUT_PULLUP);
}
void loop() {
Serial.println(F("schlafe - warte auf Ereignis"));
Serial.flush();
schlafeBisEreignis();
if (weckgrundPir) {
weckgrundPir = false;
Serial.println(F("Bewegung erkannt!"));
digitalWrite(LED_PIN, HIGH);
delay(3000);
digitalWrite(LED_PIN, LOW);
// Warten bis der PIR-Ausgang wieder low ist,
// sonst weckt uns dasselbe Signal sofort erneut.
while (digitalRead(PIR_PIN) == HIGH) delay(50);
}
if (weckgrundTaster) {
weckgrundTaster = false;
Serial.println(F("Taster gedrueckt"));
while (digitalRead(TASTER_PIN) == LOW) delay(20); // entprellen
delay(50);
}
}
ℹ️ Warum nur LOW und CHANGE aus dem Power-Down wecken. Im tiefsten Schlafmodus stehen alle Takte still – auch der, den die Flankenerkennung braucht. RISING und FALLING funktionieren beim ATmega328P nur, solange ein Takt läuft. In der Praxis funktioniert RISING am PIR trotzdem oft, weil das Signal lang genug anliegt; verlassen solltest du dich darauf nicht. Sicher ist LOW mit anschließendem Abfragen des Pin-Zustands. Mehr zu Interrupts steht im Artikel Arduino Interrupts verstehen.
⚠️ Offene Eingänge sind teurer als der Prozessor. Ein unbeschalteter Pin schwebt irgendwo zwischen High und Low. Die Eingangsstufe schaltet dann ständig um und zieht bei jedem Umladen Strom – in Summe leicht 200 bis 500 µA. Setz jeden ungenutzten Pin auf INPUT_PULLUP. Das kostet nichts und ist oft der Unterschied zwischen 0,1 mA und 0,6 mA Schlafstrom.
3Der DS3231 als präziser Wecker
Wenn dein Gerät genau um 6:00 Uhr messen soll, ist der Watchdog nutzlos. Der DS3231 hat einen temperaturkompensierten Quarz (±2 ppm, also etwa eine Minute pro Jahr) und zwei Alarme, die einen Open-Drain-Ausgang auf Masse ziehen:
| DS3231 | Arduino Nano | Hinweis |
|---|---|---|
| VCC | 5 V (oder 3,3 V) | ca. 1 µA im Batteriebetrieb |
| GND | GND | – |
| SDA | A4 | Pullups sind auf dem Modul |
| SCL | A5 | – |
| SQW / INT | D2 | zusätzlich 10 kΩ Pullup nach VCC |
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/power.h>
#define DS3231_ADDR 0x68
#define RTC_INT_PIN 2
volatile bool alarmAusgeloest = false;
void isrAlarm() { alarmAusgeloest = true; }
uint8_t zuBcd(uint8_t v) { return ((v / 10) << 4) | (v % 10); }
uint8_t ausBcd(uint8_t v) { return ((v >> 4) * 10) + (v & 0x0F); }
void regSchreiben(uint8_t reg, uint8_t wert) {
Wire.beginTransmission(DS3231_ADDR);
Wire.write(reg);
Wire.write(wert);
Wire.endTransmission();
}
uint8_t regLesen(uint8_t reg) {
Wire.beginTransmission(DS3231_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDR, (uint8_t)1);
return Wire.read();
}
// Alarm 1 auf "jede Minute bei Sekunde xx" stellen.
// Maskenbits A1M1..A1M4 bestimmen, worauf verglichen wird.
void alarmJedeMinute(uint8_t sekunde) {
regSchreiben(0x07, zuBcd(sekunde)); // A1 Sekunden, A1M1 = 0
regSchreiben(0x08, 0x80); // A1 Minuten, A1M2 = 1
regSchreiben(0x09, 0x80); // A1 Stunden, A1M3 = 1
regSchreiben(0x0A, 0x80); // A1 Tag, A1M4 = 1
uint8_t ctrl = regLesen(0x0E);
ctrl |= 0b00000101; // INTCN = 1, A1IE = 1
ctrl &= ~0b01000000; // Konvertierung nicht erzwingen
regSchreiben(0x0E, ctrl);
regSchreiben(0x0F, regLesen(0x0F) & ~0x01); // A1F loeschen
}
// Alarm 1 auf eine feste Uhrzeit taeglich
void alarmTaeglich(uint8_t stunde, uint8_t minute) {
regSchreiben(0x07, zuBcd(0)); // Sekunde 0, A1M1 = 0
regSchreiben(0x08, zuBcd(minute)); // A1M2 = 0
regSchreiben(0x09, zuBcd(stunde)); // A1M3 = 0
regSchreiben(0x0A, 0x80); // A1M4 = 1 -> Tag egal
regSchreiben(0x0E, regLesen(0x0E) | 0b00000101);
regSchreiben(0x0F, regLesen(0x0F) & ~0x01);
}
void alarmQuittieren() {
regSchreiben(0x0F, regLesen(0x0F) & ~0x01); // A1F zuruecksetzen
}
void uhrzeitLesen(uint8_t &h, uint8_t &m, uint8_t &s) {
Wire.beginTransmission(DS3231_ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDR, (uint8_t)3);
s = ausBcd(Wire.read());
m = ausBcd(Wire.read());
h = ausBcd(Wire.read() & 0x3F);
}
void schlafeBisAlarm() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
ADCSRA &= ~(1 << ADEN);
power_all_disable();
attachInterrupt(digitalPinToInterrupt(RTC_INT_PIN), isrAlarm, LOW);
sleep_enable();
MCUCR = (1 << BODS) | (1 << BODSE);
MCUCR = (1 << BODS);
sei();
sleep_cpu();
sleep_disable();
detachInterrupt(digitalPinToInterrupt(RTC_INT_PIN));
power_all_enable();
ADCSRA |= (1 << ADEN);
}
void setup() {
Serial.begin(9600);
Wire.begin();
pinMode(RTC_INT_PIN, INPUT_PULLUP);
alarmTaeglich(6, 0); // jeden Tag um 06:00 Uhr wecken
Serial.println(F("Alarm auf 06:00 gestellt"));
}
void loop() {
Serial.flush();
schlafeBisAlarm();
if (alarmAusgeloest) {
alarmAusgeloest = false;
alarmQuittieren();
uint8_t h, m, s;
uhrzeitLesen(h, m, s);
Serial.print(F("Geweckt um "));
Serial.print(h); Serial.print(':');
if (m < 10) Serial.print('0');
Serial.print(m); Serial.print(':');
if (s < 10) Serial.print('0');
Serial.println(s);
// ... hier misst und sendet dein Projekt ...
}
}
💡 Das Alarmflag muss quittiert werden. Solange A1F gesetzt ist, hält der DS3231 seinen INT-Ausgang auf Masse – und der Nano wacht sofort wieder auf, weil der Low-Interrupt weiterhin anliegt. Das Ergebnis ist eine Endlosschleife, die genauso viel Strom zieht wie gar kein Sleep. alarmQuittieren() gehört als Erstes nach dem Aufwachen. Grundlagen zum Modul findest du im Artikel DS3231 RTC mit Arduino.
⚠️ Die Ladeschaltung des DS3231-Moduls kann gefährlich werden. Viele günstige Module haben eine Ladeschaltung für LIR2032-Akkuzellen. Steckt dort eine nicht wiederaufladbare CR2032 drin, wird sie geladen – mit dem Risiko, dass sie ausläuft oder aufbläht. Entweder du nutzt eine LIR2032, oder du entfernst die Diode neben dem Batteriehalter.
4Hardware-Maßnahmen: die anderen 10 mA
Der beste Sleep-Code nützt nichts, solange die Power-LED leuchtet. Diese Eingriffe bringen den größten Sprung:
| Maßnahme | Ersparnis | Aufwand | Nachteil |
|---|---|---|---|
| Power-LED-Widerstand auslöten | ca. 3 mA | ruhige Hand, SMD | keine Betriebsanzeige mehr |
| Direkt an 5V-Pin einspeisen | ca. 2 mA | keiner | kein Verpolungsschutz |
| Auf 3,3 V und 8 MHz umstellen | ca. 50 % im Aktivbetrieb | Bootloader tauschen | USB-Upload braucht Anpassung |
| Sensoren über GPIO schalten | je nach Sensor 1–20 mA | eine Zeile Code | Anlaufzeit einplanen |
| Ungenutzte Pins auf Pullup | bis 0,5 mA | Schleife im Setup | keiner |
// Sensor ueber einen GPIO versorgen statt fest an VCC.
// Ein kapazitiver Bodenfeuchte-Sensor zieht dauerhaft rund 5 mA -
// das ist mehr als der schlafende Nano um den Faktor 30000.
#define SENSOR_VCC 7 // versorgt den Sensor
#define SENSOR_AOUT A0
uint16_t feuchteMessen() {
pinMode(SENSOR_VCC, OUTPUT);
digitalWrite(SENSOR_VCC, HIGH);
delay(150); // Einschwingzeit des Oszillators
uint32_t summe = 0;
for (uint8_t i = 0; i < 16; i++) {
summe += analogRead(SENSOR_AOUT);
delay(3);
}
digitalWrite(SENSOR_VCC, LOW);
pinMode(SENSOR_VCC, INPUT); // Pin hochohmig -> kein Reststrom
return (uint16_t)(summe / 16);
}
// Versorgungsspannung ohne externe Bauteile messen.
// Der ATmega vergleicht die interne 1,1-V-Referenz gegen Vcc.
uint16_t vccMillivolt() {
ADMUX = (1 << REFS0) | 0x0E; // Vcc als Referenz, 1V1 als Eingang
delay(3); // Referenz einschwingen lassen
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC)) { }
uint16_t roh = ADC;
if (roh == 0) return 0;
// 1125300 = 1,1 V * 1023 * 1000; Wert bei Bedarf kalibrieren
return (uint16_t)(1125300UL / roh);
}
// Batteriewaechter: unter 3,4 V lohnt sich Messen nicht mehr
bool batterieOk() {
uint16_t mv = vccMillivolt();
return (mv > 3400);
}
ℹ️ vccMillivolt() ersetzt einen kompletten Spannungsteiler. Der Trick funktioniert, weil der ADC die interne Bandgap-Referenz gegen die Versorgung misst – kein Pin, keine Widerstände, kein Ruhestrom. Die Genauigkeit liegt bei etwa ±10 %, weil die 1,1-V-Referenz je nach Chip zwischen 1,0 und 1,2 V schwankt. Einmal gegen ein Multimeter kalibriert (die Konstante 1125300 anpassen), kommst du auf unter 1 %.
5Alles zusammen: Bodenfeuchte-Logger mit Monatslaufzeit
Das komplette Beispielprojekt – wacht stündlich auf, misst, protokolliert und schläft weiter:
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <EEPROM.h>
#define SENSOR_VCC 7
#define SENSOR_AOUT A0
#define PUMPE_PIN 8
#define LED_PIN LED_BUILTIN
const uint16_t MESSINTERVALL_S = 3600; // eine Stunde
const uint16_t TROCKEN_ROH = 620; // an der Luft gemessen
const uint16_t NASS_ROH = 280; // im Wasserglas gemessen
const uint8_t SCHWELLE_PROZENT = 35;
uint16_t eeAdresse = 0;
volatile bool wdtGeweckt = false;
ISR(WDT_vect) { wdtGeweckt = true; }
// (watchdogSetzen, watchdogAus, schlafeTief, schlafeSekunden
// wie in Schritt 1 - hier der Kuerze halber ausgelassen)
void watchdogSetzen(uint8_t periode);
void watchdogAus();
void schlafeTief();
void schlafeSekunden(uint16_t sekunden);
uint16_t rohMessen() {
pinMode(SENSOR_VCC, OUTPUT);
digitalWrite(SENSOR_VCC, HIGH);
delay(150);
uint32_t summe = 0;
for (uint8_t i = 0; i < 16; i++) { summe += analogRead(SENSOR_AOUT); delay(3); }
digitalWrite(SENSOR_VCC, LOW);
pinMode(SENSOR_VCC, INPUT);
return (uint16_t)(summe / 16);
}
uint8_t rohZuProzent(uint16_t roh) {
if (roh >= TROCKEN_ROH) return 0;
if (roh <= NASS_ROH) return 100;
return (uint8_t)(((uint32_t)(TROCKEN_ROH - roh) * 100UL)
/ (TROCKEN_ROH - NASS_ROH));
}
void wertSpeichern(uint8_t prozent) {
EEPROM.update(eeAdresse, prozent);
eeAdresse++;
if (eeAdresse >= 1000) eeAdresse = 0; // Ringpuffer, ca. 41 Tage
}
void giessen(uint16_t sekunden) {
pinMode(PUMPE_PIN, OUTPUT);
digitalWrite(PUMPE_PIN, HIGH);
delay((uint32_t)sekunden * 1000UL);
digitalWrite(PUMPE_PIN, LOW);
pinMode(PUMPE_PIN, INPUT);
}
void setup() {
// Alle Pins definiert legen, bevor irgendetwas anderes passiert
for (uint8_t p = 2; p <= 13; p++) pinMode(p, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Serial.begin(9600);
Serial.println(F("Bodenfeuchte-Logger gestartet"));
Serial.flush();
}
void loop() {
uint16_t roh = rohMessen();
uint8_t p = rohZuProzent(roh);
Serial.print(F("roh=")); Serial.print(roh);
Serial.print(F(" ")); Serial.print(p);
Serial.println(F(" %"));
Serial.flush();
wertSpeichern(p);
if (p < SCHWELLE_PROZENT) {
Serial.println(F("zu trocken - giesse 8 s"));
Serial.flush();
giessen(8);
schlafeSekunden(600); // 10 min ziehen lassen
return; // dann direkt neu messen
}
// Kurzes Lebenszeichen - 5 ms LED kosten praktisch nichts
digitalWrite(LED_PIN, HIGH);
delay(5);
digitalWrite(LED_PIN, LOW);
schlafeSekunden(MESSINTERVALL_S);
}
Die Laufzeitrechnung für diesen Aufbau mit drei AA-Zellen (rund 2500 mAh):
| Phase | Strom | Dauer je Stunde | Ladung je Stunde |
|---|---|---|---|
| Power-Down | 0,12 mA | 3599,5 s | 0,120 mAh |
| Messen (Sensor an) | 14 mA | 0,25 s | 0,001 mAh |
| Auswerten + EEPROM | 9 mA | 0,25 s | 0,001 mAh |
| Summe je Stunde | – | – | 0,122 mAh |
💡 2500 mAh geteilt durch 2,93 mAh pro Tag ergibt rund 850 Tage – mehr als zwei Jahre. Der begrenzende Faktor ist nicht mehr die Schaltung, sondern die Selbstentladung der Zellen. Und man sieht sofort: Der Schlafstrom von 0,12 mA macht 98 % des Verbrauchs aus. Jede weitere Optimierung muss dort ansetzen, nicht beim Messen.
Troubleshooting
⚠️ Der Nano wacht nie wieder auf: Du hast watchdogSetzen() vergessen oder WDIE nicht gesetzt – dann startet der Watchdog einen Reset statt eines Interrupts, oder es passiert gar nichts. Prüfe, ob ISR(WDT_vect) im Sketch steht; ohne diese Funktion führt ein Watchdog-Interrupt zum Absturz.
⚠️ Der gemessene Schlafstrom liegt bei 5 mA statt 0,1 mA: Du misst noch mit angestecktem USB. Der CH340 wird über USB versorgt und schläft nicht mit. Trenn das Kabel und speise über den 5V- oder VIN-Pin ein.
⚠️ Nach dem Aufwachen sind die ersten Analogwerte Unsinn: Der ADC braucht nach dem Wiedereinschalten einige Wandlungen, bis er eingeschwungen ist. Verwirf die ersten drei analogRead()-Ergebnisse.
⚠️ Der Sketch lässt sich nicht mehr hochladen: Bei sehr kurzen Watchdog-Perioden oder Schlafen direkt im setup() erwischt der Bootloader kein Zeitfenster. Halte Reset gedrückt, starte den Upload und lass genau in dem Moment los, in dem die IDE „Hochladen" meldet. Vorbeugen: immer ein delay(3000) am Anfang von setup().
⚠️ Der DS3231 weckt sofort wieder: Das Alarmflag A1F ist noch gesetzt. Es muss nach jedem Aufwachen explizit gelöscht werden – der Chip macht das nicht von selbst.
⚠️ Zeitmessung mit millis() stimmt nicht mehr: Im Power-Down steht Timer0 still. millis() zählt die Schlafzeit nicht mit. Wenn du echte Zeitstempel brauchst, führt kein Weg an einer RTC vorbei.
⚠️ Die Batterie ist trotzdem nach zwei Wochen leer: Miss den Ruhestrom des gesamten Aufbaus, nicht nur des Nano. Ein Spannungsregler-Modul, ein Pegelwandler oder ein Display mit Hintergrundbeleuchtung ziehen oft mehr als alles andere zusammen.
Praxistipps
- Erst messen, dann optimieren. Ohne einen Messwert vor und nach jedem Eingriff optimierst du blind. Ein 10-Ω-Shunt und ein Multimeter im Millivoltbereich reichen völlig.
- Zwei AA-Zellen plus MT3608 sind nicht immer besser. Der Step-Up-Wandler hat selbst einen Ruhestrom von einigen hundert Mikroampere. Bei einem Gerät, das 99 % der Zeit schläft, frisst er mehr als er bringt. Drei AA-Zellen direkt an 5V und der Regler bleibt außen vor.
- EEPROM statt SD-Karte für wenige Werte. Ein SD-Modul zieht im Leerlauf 0,2 bis 1 mA – mehr als der ganze schlafende Nano. Für ein paar hundert Messwerte reicht das interne EEPROM. Details dazu im Artikel Arduino EEPROM & ESP32 Preferences.
- Alkaline statt Lithium-Ionen bei langen Laufzeiten. Ein 18650 mit TP4056 ist bequem, verliert aber 2 bis 5 % pro Monat durch Selbstentladung. AA-Alkalizellen aus einem Batteriehalter halten Jahre.
- Winter einplanen. Bei minus 10 Grad liefert eine Alkalizelle nur noch etwa die Hälfte ihrer Nennkapazität. Rechne bei Außenprojekten grundsätzlich mit dem Faktor 0,5.
- Lebenszeichen kurz halten. Eine LED aus dem LED-Sortiment, die 5 ms pro Stunde blinkt, kostet 0,00003 mAh. Dieselbe LED mit 500 ms kostet das Hundertfache – und ist auch nicht besser sichtbar.
- Watchdog für grobe Intervalle, RTC für Uhrzeiten. Beides zu kombinieren macht selten Sinn. Entscheide dich pro Projekt für eine Variante.
🚀 Bauteile für dein Batterieprojekt
- Arduino Nano V3 (USB-C) – klein, sparsam nach der Behandlung
- DS3231 RTC-Modul – präziser Weckalarm auf die Sekunde
- AA-Batteriehalter Set (5 Stück) – 1 bis 4 Zellen
- TP4056 Lademodul (3er Set) – für 18650-Akkubetrieb
- MT3608 Step-Up-Modul – wenn zwei Zellen reichen sollen
- Kapazitiver Bodenfeuchte-Sensor – der Logger aus Schritt 5
- HC-SR501 PIR-Bewegungsmelder – Wakeup per Interrupt
- 600er Widerstands-Sortiment – Pullups und Shunts
- Breadboard 400 Kontakte und Dupont-Jumperkabel
Fazit
Ein Arduino Nano, der zwei Jahre an drei AA-Zellen läuft, ist kein Kunststück – er ist das Ergebnis von vier Entscheidungen: Power-Down statt delay(), alle ungenutzten Pins auf Pullup, Sensoren über einen GPIO versorgen und die Power-LED opfern. Zusammen bringen sie den Verbrauch von 19 mA auf etwa 0,12 mA – Faktor 150.
Der Rest ist Feinschliff. Ob du mit dem Watchdog aufwachst oder mit einem DS3231, hängt allein davon ab, ob dein Projekt „ungefähr stündlich" oder „punkt sechs Uhr" braucht. Und wenn du am Ende ausrechnest, dass die Selbstentladung der Batterie zum begrenzenden Faktor geworden ist, hast du alles richtig gemacht.