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

Arduino Nano V3 (USB-C, CH340) – das Testobjekt
DS3231 RTC-Modul – der präziseste Weckdienst, den du bekommen kannst
AA-Batteriehalter Set – für 2 bis 4 Zellen, je nach Versorgungskonzept
TP4056 Lademodul – wenn es ein wiederaufladbarer 18650 sein soll
MT3608 Step-Up-Modul – hebt zwei AA-Zellen auf 5 V an
Kapazitiver Bodenfeuchte-Sensor – das Beispielprojekt am Ende
HC-SR501 PIR-Bewegungsmelder – weckt den Nano per Interrupt
600er Widerstands-Sortiment – Pullups und Spannungsteiler

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

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.

Zurück zum Blog