Sleep Modes und Power Management

Über den Beitrag

Im letzten Beitrag hatte ich gezeigt, wie man den ATmega328P standalone, also außerhalb seiner Arduino UNO Peripherie betreibt. Das spart Kosten, Platz und Strom. Betreibt ihr ein Projekt mit so einem „nackten“ Atmega328P, ist ein Durchschnittsakku von sagen wir 2000 mAh trotzdem nach wenigen Tagen leer, selbst wenn ihr nichts anderes dran hängen habt. Um den Strombedarf zu senken, haben die AVR Microcontroller verschiedene Sleep Modes. Wenn ihr zum Beispiel eine Wetterstation baut, in der ein Microcontroller auf der Sensorseite nur einmal pro Minute eine Messung durchführt, kann man ihn den Rest der Zeit getrost in den Schlaf schicken. 

Stromverbrauch einiger Boards und MCUs (Bare Minimum Sketch / 5V)
Tabelle 1: Stromverbrauch einiger Boards und MCUs (Bare Minimum Sketch / 5V)

Ich konzentriere mich in diesem Beitrag auf den ATmega328P, da er denjenigen, die mit dem Arduino UNO arbeiten, am geläufigsten ist. Für viele Projekte ist er jedoch überdimensioniert. Deswegen gehe ich zum Schluss des Beitrages noch einmal kurz auf die Sleep Modes der weniger stromhungrigen ATtinys 85/45/25 am Beispiel des ATtiny85 ein. Auch die ATtinys 85/45/25 sind über die Arduino IDE programmierbar. Das hatte ich hier beschrieben. 

Inhaltsangabe

Vorab noch….

Hochladen der Sketche

Ich werde in dem Beitrag nicht oder nur sehr wenig darauf eingehen, wie die Sketche auf ATmega328P bzw. den ATtiny85 kommen. Das habe ich ausführlich in den oben angegebenen Beiträgen beschrieben. Es würde den Rahmen sprengen, das hier noch einmal im Detail zu wiederholen. 

Pinout des ATmega328P

Zur Orientierung noch das Pinout Schema des ATmega328P:

Pinout des ATmega328P

Die Sleep Modes des ATmega328P

Sleep Modes behandele ich am Beispiel des ATmega328P

Der Atmega328P kennt sechs verschiedene Sleep Modes. Je nach Modus werden mehr oder weniger seiner Funktionen schlafen geschickt. Entsprechend unterschiedlich ist auch der verbleibende Stromverbrauch.

Wenn ihr den ATmega328P schlafen schickt, müsst ihr ihn natürlich auch wieder wecken können. Jeder Modus hat dabei eine eigene Auswahl möglicher „Weckrufe“. Darüber hinaus ist zu beachten, dass der ATmega328P je nach „Schlaftiefe“ unterschiedlich lange zum Aufwachen braucht. 

Die folgende Tabelle gibt einen Überblick, in welchen Sleep Modes welche Funktionen noch aktiviert sind und wie man den ATmega328P wieder wecken kann. Die gute Nachricht ist, dass die Tabelle für eine ganze Serie von ATmegas gültig ist (siehe Tabellenunterschrift). 

Tabelle 2: Sleep Modes des ATmega328P (und 48A/48PA/88A/88PA/168A/168PA/328)

Hier eine Kurzbeschreibung der Sleep Modes. Mehr Informationen gibt es im Datenblatt.

  • Idle: der Leichtschlaf. Unter Umständen müsst ihr weitere Komponenten per PRR (kommt später) direkt abschalten, damit der ATmega328P nicht ungewollt aufwacht.
  • ADC Noise Reduction: Auch in diesem Modus bleibt vieles angeschaltet. Der ADC Noise Reduction Mode wird, wie der Name schon andeutet, auch verwendet, um bei Analog Digital-Wandlungen Rauschen zu reduzieren. Dadurch kann eine höhere Auflösung erreicht werden. 
  • Power-down: der stromsparendste Tiefschlaf. Lediglich externe Interrupts, TWI (Two Wire Interface -> I2C) oder der Watchdog Interrupt können den ATmega328P aufwecken. 
  • Power-save: ist ähnlich wie der Power-down Modus, allerdings ist der Timer2 noch wach und könnte über einen externen Taktgeber betrieben werden.
  • Stand-by: hier läuft noch der Systemoszillator. Diesen Modus wählt man typischerweise, wenn schnelles Aufwachen nötig ist. Lediglich sechs Zyklen werden benötigt. Man muss dazu wissen, dass ein externer Quarzoszillator Einschwingzeiten im Millisekundenbereich hat. 
  • Extended Standby: Gegenüber dem Stand-by Modus ist hier der Timer2 aktiv.

Die Sleep Modes aktivieren

Sleep Modes über das SMCR steuern

Die Sleep Modes werden durch entsprechende Einträge in das Sleep Mode Control Register SMCR gesteuert. Die Bits SM0…SM2 legen den Modus fest, das SE Bit (sleep enable) startet den Schlafmodus. 

Das Sleep Mode Control Register SMCR
Einstellung der Sleep Modes über das SMCR
Tabelle 3: Einstellung der Sleep Modes über das SMCR

Sleep Modes mit den sleep.h Funktionen

Die Kurzschreibweise

Nicht jeder mag es, Register mit Anweisungen wie SMCR |= (1<<SM0); zu beschreiben. Wenn ihr die Headerdatei sleep.h einbindet, dann könnt ihr besser lesbare Funktionen verwenden. Wie das geht, zeige ich im folgenden Sketch.

Zum Aufwecken verwende ich den Watchdog Timer, den ich auf 8 Sekunden eingestellt habe. Details zum Watchdog Timer findet ihr hier. Andere Weckmethoden bespreche ich später.

Der Sketch macht Folgendes:

  • zunächst werden wdt.h (für den Watchdog Timer) und sleep.h eingebunden
  • PD7 wird auf OUTPUT gesetzt 
  • der Watchdog wird eingerichtet
  • Die LED an PD7 geht 3.5 Sekunden lang an
    • das dient lediglich als „Ich-bin-wach-Zeichen“
    • 3.5 Sekunden habe ich gewählt, damit mein träges Multimeter den Stromverbrauch vernünftig messen konnte
  • nach weiteren 3.5 Sekunden wird der Watchdog Timer zurückgesetzt
  • der Sleep Mode wird mit set_sleep_mode(...) ausgewählt, in diesem Fall Power-down
  • sleep_mode() startet den Schlafmodus
  • nach 8 Sekunden weckt der Watchdog; die ISR (Interrupt Service Routine) ist leer, sie muss aber trotzdem in den Sketch aufgenommen werden
  • Der Sketch nimmt seine Arbeit direkt hinter sleep_mode() wieder auf
#include <avr/wdt.h>
#include <avr/sleep.h>

void setup(){
  DDRD = (1<<PD7); // equals (roughly) pinMode(7, OUTPUT);
  watchdogSetup();
}

void loop(){
  PORTD |= (1<<PD7);  // equals (roughly) digitalWrite(7, HIGH);
  delay(3500);
  PORTD &= ~(1<<PD7); // equals (roughly) digitalWrite(7, LOW);
  delay(3500);
  wdt_reset();

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // choose power down mode
//  set_sleep_mode(SLEEP_MODE_PWR_SAVE); // choose power save mode
//  set_sleep_mode(SLEEP_MODE_STANDBY); // choose external standby power mode
//  set_sleep_mode(SLEEP_MODE_EXT_STANDBY); // choose external standby power mode
//  set_sleep_mode(SLEEP_MODE_IDLE); // did not work like this!
//  set_sleep_mode(SLEEP_MODE_ADC); // choose ADC noise reduction mode
//  sleep_bod_disable();  // optional brown-out detection switch off  

  sleep_mode(); // sleep now!
}

void watchdogSetup(void){
  cli();
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3) | (1<<WDP0);  // 8s / interrupt, no system reset
  sei();
}

ISR(WDT_vect){//put in additional code here
}

 

Für andere Sleep Modes entkommentiert einfach die entsprechenden Zeilen. Probiert auch mal den Idle Modus. Ihr werdet sehen, dass er nicht funktioniert und die LED mit einer Frequenz von 3.5 s leuchtet. Idle ist sozusagen ein leichter Schlaf und aus einem solchen wacht man eben auch leicht auf. Wir kommen noch dazu wie ihr das verhindert. 

Und nun vertauscht die Zeilen 10 und 12. Die LED leuchtet nun auch während der ATmega328P schläft, denn alle Registerinhalte bleiben während der Schlafphase erhalten, einschließlich der Portregister.  

Die ausführlichere Schreibweise

Die Funktion sleep_mode(); entspricht der Befehlsfolge:

sleep_enable(); 
sleep_cpu(); 
sleep_disable();

Das ist übrigens in sleep.h so definiert. Ihr findet die Datei unter: „Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr\sleep.h“. Schadet nicht, da mal reinzuschauen. 

Es ist sicherer, wenn ihr die ausführlichere Befehlsfolge verwendet und vor sleep_enable() alle Interrupts durch cli() abschaltet. Vor sleep_cpu() schaltet ihr die Interrupts über sei() wieder ein. Ohne das zwischenzeitliche Abschalten kann es zur „Kollision“ von Interrupts mit unvorhersehbaren Folgen kommen. Das spielt vor allem dann eine Rolle, wenn zwischen sleep_enable() und sleep_cpu() weitere Anweisungen eingefügt werden und genau das werden wir bald tun. 

Aber zunächst einmal sieht der Sketch jetzt so aus:

#include <avr/wdt.h>
#include <avr/sleep.h>

void setup(){
  DDRD = (1<<PD7); // equals (roughly) pinMode(7, OUTPUT);
  watchdogSetup();
}

void loop(){
  PORTD |= (1<<PD7);  // equals (roughly) digitalWrite(7, HIGH);
  delay(3500);
  PORTD &= ~(1<<PD7); // equals (roughly) digitalWrite(7, LOW);
  delay(3500);
  wdt_reset();

    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // choose power down mode
//  set_sleep_mode(SLEEP_MODE_PWR_SAVE); // choose power save mode
//  set_sleep_mode(SLEEP_MODE_STANDBY); // choose external standby power mode
//  set_sleep_mode(SLEEP_MODE_EXT_STANDBY); // choose external standby power mode
//  set_sleep_mode(SLEEP_MODE_IDLE); // did not work!
//  set_sleep_mode(SLEEP_MODE_ADC); // choose ADC noise reduction mode

  cli(); // deactivate interrupts
  sleep_enable(); // sets the SE (sleep enable) bit
  sei(); // 
  sleep_cpu(); // sleep now!!
  sleep_disable(); // deletes the SE bit
}

void watchdogSetup(void){
  cli();
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3) | (1<<WDP0);  // 8s / interrupt, no system reset
  sei();
}

ISR(WDT_vect){//put in additional code here
}

 

Liebhaber der Registerschreibweise können Zeile 23 bis 27 ersetzen durch:

SMCR |= (1<<SM1); // power down sleep mode
cli();
SMCR |= (1<<SE); // sleep_enable();
sei();
asm("SLEEP"); // sleep_cpu();
SMCR &= ~(1<<SE); // sleep_disable();

Ich finde das hat in seiner Kürze auch was. 

Was bringen die Sleep Modes – einige Messungen

Ich habe den letzten Sketch auf dem Arduino UNO, dem standalone ATmega328P und dem ATtiny85 bei verschiedenen Taktfrequenzen laufen lassen und den Stromverbrauch in der Schlafphase gemessen. Zum Teil habe ich den Sketch über die Arduino IDE hochgeladen (wie im letzten Beitrag beschrieben), zum Teil über Atmel Studio.

Diese Werte gelten nur für die von mir gewählten Bedingungen! Durch weitere Maßnahmen (z.B. ADC ausschalten) lässt sich noch mehr herausholen.

Stromverbrauch in verschiedenen Sleep Modi
Tabelle 4: Stromverbrauch in versch. Sleep Modi für den UNO, ATmega328P und ATtiny85

Teilweise waren die Ergebnisse wie erwartet, zum Teil auch nicht:

  • Das Arduino UNO Board schluckt ordentlich Strom, da viel an Peripherie unterhalten werden muss (z.B. die Power LED).
  • Der Idle-Mode verbraucht im standalone Betrieb am meisten Strom (ich komme noch dazu, wie man ihn zum Laufen bekommt).
  • Am wenigsten Strom verbraucht der Power-down Sleep Mode
  • Es macht für den Power-down Modus einen erstaunlich großen Unterschied, ob der Sketch per Arduino IDE oder per Atmel Studio hochgeladen wird. Woran das genau liegt, kann ich nicht sagen. Die Schaltungen waren absolut identisch (nach Trennung von den Programmern). 
  • Im Power-down Modus gibt es keinen Unterschied bei unterschiedlichen Taktfrequenzen – der Systemoszillator ist ja auch aus.
  • Die Verwendung eines ATtiny85 bringt noch einmal eine Riesenersparnis.

Weitere Energiesparpotentiale

Energiesparen mit dem Power Reduction Register

Je nach Sleep Mode und gewünschter Weckmethode könnt ihr den Stromverbrauch noch weiter senken, indem ihr einige Komponenten manuell abschaltet. Gesteuert wird das über das Power Reduction Register PRR:

Das Power Reduction Register PRR

Gesetzte Bits schalten die entsprechende Komponente ab:

  • PRTWI: schaltet das Two Wire Interface (I2C) ab. Nach dem Aufwecken muss es neu initialisiert werden.
  • PRTIM2: schaltet den Timer2 im synchronen Modus ab.
  • PRTIM0/PRTIM1:  diese Bits sind für das Abschalten der Timer0 und Timer1 zuständig. 
    • Die Timer0/1/2 setzen ihre Arbeit nach dem Aufwecken fort, ohne dass weitere Maßnahmen getroffen werden müssen
  • PRSPI: schaltet die SPI Schnittstelle aus. SPI muss nach dem Aufwecken neu initialisiert werden.
  • PRUSART0: schaltet die serielle Schnittstelle aus, also RX/TX (Universal Synchronous and Asynchronous serial Receiver and Transmitter). Die USART Schnittstelle muss nach dem Aufwachen neu intialisiert werden. 
  • PRADC: schaltet den AD-Wandler aus

Ihr müsst keine Binäroperationen anwenden, um die Bits eurer Wahl zu setzen (obwohl das ja nicht wirklich schwer ist). Dafür gibt es Funktionen, die in der Headerdatei power.h definiert sind. Im folgenden Sketch findet ihr die (selbsterklärenden) Funktionen in Zeile 26 bis 32.

#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>

void setup(){
  DDRD = (1<<PD7); // equals (roughly) pinMode(7, OUTPUT);
  watchdogSetup();
}

void loop(){
  PORTD |= (1<<PD7);  // equals (roughly) digitalWrite(7, LOW);
  delay(3500);
  PORTD &= ~(1<<PD7); // equals (roughly) digitalWrite(7, LOW);
  delay(3500);
  wdt_reset();

//  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // choose power down mode
//  set_sleep_mode(SLEEP_MODE_PWR_SAVE); // choose power save mode
//  set_sleep_mode(SLEEP_MODE_STANDBY); // choose external standby power mode
//  set_sleep_mode(SLEEP_MODE_EXT_STANDBY); // choose external standby power mode
  set_sleep_mode(SLEEP_MODE_IDLE); 
//  set_sleep_mode(SLEEP_MODE_ADC); // choose ADC noise reduction mode

  cli();
  sleep_enable();
  power_adc_disable(); 
  power_usart0_disable();
  power_spi_disable(); 
  power_timer0_disable();
  power_timer1_disable(); 
  power_timer2_disable(); 
  power_twi_disable();
  //sleep_bod_disable(); // disable brown-out detector
  sei();
  sleep_cpu();
  sleep_disable();
  power_all_enable();

}

void watchdogSetup(void){
  cli();
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3) | (1<<WDP0);  // 8s / interrupt, no system reset
  sei();
}

ISR(WDT_vect){//put in additional code here
}

 

Wenn ihr den Sketch ausprobiert, werdet ihr sehen, dass jetzt auch der Idle Sleep Mode funktioniert. Alle Störenfriede, die den leichten Schlaf unterbrechen könnten, sind abgeschaltet. 

Ihr müsst natürlich aufpassen, dass ihr keine Komponenten ausschaltet, die ihr zum Aufwecken benutzen wollt! 

Ausschalten des Brown-Out Detektors (BOD)

Was ist ein BOD?

Bei einem plötzlichen, kompletten Stromausfall spricht man von einem Black-Out. Diesen Ausdruck kennt wohl jeder. Von einem Brown-Out hingegen spricht man, wenn die Versorgungsspannung des Microcontrollers unter ein Mindestlevel rutscht. Typischerweise passiert das, wenn der Akku, der euer Projekt betreibt, sich entleert. Ein Microcontroller, der zu wenig Spannung bekommt, macht völlig unvorhersehbare Dinge. Der Microcontroller selbst nimmt dabei nicht unbedingt Schaden. Aber wenn die Dinge, die er steuert „austicken“, kann das unter Umständen kritisch sein. Deshalb haben die meisten Microcontroller einen Brown-Out Detektor (BOD). Dieser vergleicht die Betriebsspannung mit einem festen Trigger-Level. Bei Unterschreitung dieses Levels wird ein Reset ausgelöst, um das System zu schützen. Bei manchen Anwendungen mag ein Brown-Out jedoch unproblematisch sein. 

Wie schalte ich den BOD aus?

Bei den picoPower Varianten der AVR Microntroller kann der Brown-Out Detektor ausgeschaltet werden. Ihr erkennt die picoPower Modelle am „P“ im Namen, z.B. beim ATmega328P. Die Steuerung erfolgt über das MCU Control Register MCUCR:

Das Microcontroller Control Register MCUCR

Relevant sind dabei die folgenden Bits:

  • BODS: BOD Sleep
  • BODSE: BOD Sleep Enable

Beide Bits müssen in einer bestimmten Abfolge gesetzt werden, um den BOD auszuschalten. Einfacher ist es mit der in sleep.h definierten Funktion sleep_bod_disable(). Im letzten Sketch (idle_mode_enable.ino) hatte ich die Funktion schon in Zeile 33 eingefügt, aber noch nicht erklärt und auskommentiert. 

Alternativ könnt ihr, sofern ihr Programme wie Atmel Studio benutzt, den BOD auch über die zuständigen Fuse Bits (BODLEVEL) steuern. Es stehen die Einstellungen 4.3 V, 2.7 V, 1.8 V oder „disabled“ zur Verfügung. 

BOD Einstellung über die Fuses
BOD Einstellung über die Fuses

Nach meiner Messung bringt das Ausschalten des BODs im Sleep Mode Power-down weitere 8 µA Stromersparnis. Klingt nicht viel, aber in einem Jahr sind das immerhin ungefähr 70 mAh.

Senken der Taktfrequenz

Im Dauer-Wachbetrieb bringt das Senken der Taktfrequenz eine erhebliche Einsparung (siehe Tabelle 1). Ihr habt aber auch gesehen, dass der Stromverbrauch des Microcontrollers zumindest im Power-down Modus frequenzunabhängig ist. Wenn ihr den Microcontroller schlafen schickt und nur zwischendurch kurz aufweckt um ein paar Dinge zu, dann kann es günstiger sein, eine hohe Taktfrequenz zu wählen. Der Grund dafür ist, dass die Dinge bei hoher Taktfrequenz schneller erledigt sind. Die Schnelligkeit überkompensiert den höheren Stromverbrauch. Verwendet ihr delays, sieht die Sache allerdings schon wieder anders aus. Zu dem Thema Energieverbrauch vs. Taktfrequenz gibt es auch eine interessante Diskussion auf mikrocontroller.net

Senken der Betriebsspannung

Ein Microcontroller wirkt wie ein Kondensator bzw. wie viele kleine Kondensatoren und deren Ladung hängt von der Betriebsspannung ab. Man spricht von parasitären Kapazitäten. Hinzu kommen Leckströme, z.B. von Leiterbahn zu Leiterbahn.

Im Power-down Modus konnte ich den Stromverbrauch des ATmega328P von 0.15 mA (über Arduino IDE programmiert) auf immerhin 0.12 mA senken. Weitere Informationen und Vergleichswerte findet ihr hier.

Andere Weckmethoden

In den bisherigen Beispielen war der Watchdog unser Wecker für den ATmega328P. Ich möchte nun noch auf zwei andere Methoden eingehen. 

Aufwecken per externem Interrupt

Mit Arduino Funktionen

Im folgenden Sketch wird der ATmega328P per externem Interrupt geweckt. Dazu habe ich einen Taster an INT0 (PD2, Pin4 = Arduino Pin 2) gehängt, der beim Betätigen ein HIGH Signal erzeugt. Mit jedem Tasterdruck wacht der ATmega328P auf, die LED leuchtet für 1 Sekunde, dann legt sich der ATmega328P wieder hin. 

Mit den Arduino Funktionen lässt sich der Interrupt einfach einrichten. Es ist ratsam, den Interrupt nach dem Aufwachen zu deaktivieren, damit er z.B. beim Tasterprellen nicht mehrfach ausgelöst wird. Ich habe hier übrigens wieder die kurze sleep_mode() Funktion verwendet, um das Augenmerk auf das Wesentliche zu lenken. 

#include <avr/sleep.h>

void setup(){
  DDRD = (1<<PD7); 
}

void loop(){
  PORTD |= (1<<PD7); 
  delay(1000);
  PORTD &= ~(1<<PD7); 
  attachInterrupt(digitalPinToInterrupt(2), intRoutine, RISING);
  
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // choose power down mode
  sleep_mode(); // sleep now!
}

void intRoutine(){ 
  detachInterrupt(2); // external interrupt disable (INT0)
}

 

Programmierung in C

Ihr könnt die zuständigen Register auch direkt ansprechen, was nicht weiter schwer ist. Im EICRA Register (External Interrupt Control Register A) stellt ihr die Bedingungen ein, unter denen der Interrupt ausgelöst wird.

Das External Interrupt Control Register A EICRA
Einstellung des Interrupts an INTO

Im EIMSK Register (External Interrupt Mask Register) wird der Interrupt aktiviert:

Das External Interrupt Mask Register EIMSK

Und so sieht der Sketch dann aus: 

#include <avr/sleep.h>

void setup(){
  DDRD = (1<<PD7); 
  EICRA |= (1<<ISC01)|(1<<ISC00); // interrupt on rising edge of INT0
}

void loop(){
  PORTD |= (1<<PD7); 
  delay(1000);
  PORTD &= ~(1<<PD7); 
  EIMSK |= (1<<INT0); // external interrupt enable on INT0
  
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // choose power down mode
  sleep_mode(); // sleep now!
}

// INT0 interrupt service routine
ISR(INT0_vect){ 
  EIMSK &= ~(1<<INT0); // external interrupt disable (INT0)
}

 

Aufwecken mit Timer2

Wenn ihr den ATmega328P über einen Timer wecken wollt, dann geht das mit dem Timer2 in allen Sleep Modes, außer im Power-down Modus. Wer meine Beiträge über die 8-Bit Timer0 und Timer2 und den 16-Bit Timer1 gelesen hat, der weiß, dass der Timer2 in relativ kurzer Zeit überläuft, selbst bei Verwendung des maximalen Prescalers. Auf die Details der Timerprogrammierung gehe ich hier nicht noch einmal ein. 

Im folgenden Beispielsketch ist der Timer2 mit dem maximalen Prescaler eingestellt. Bei einer Taktfrequenz von 16 MHz werden pro Sekunde 16 Mio/1024/256 = ~61 Interrupts ausgelöst. Um wieder auf ca. 8 Sekunden Ruhephase zu kommen, habe ich einen Counter eingefügt, der bei jedem Aufwachen inkrementiert wird. Erst beim 500sten Aufwachen leuchtet die LED. Das entspricht 500 / 61 = ~8.2 Sekunden Pause. Man sollte meinen, dass das häufige Aufwachen zu erheblichem Mehrverbrauch an Strom führt. Erstaunlicherweise habe ich mit dieser Methode in der Schlafphase sogar etwas weniger Stromverbrauch festgestellt als mit der Watchdog Dauerschlafmethode (2.92 mA gegenüber 2.97 mA).

#include <avr/sleep.h>
#include <avr/power.h>
int counter = 0;

void setup(){
  DDRD = (1<<PD7); 
  TCCR2A = 0x00; // Wave Form Generation Mode 0: Normal Mode, OC2A disconnected
  TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); // prescaler = 1024
  TIMSK2 = (1<<TOIE2);
}

void loop(){
  if(counter==500){
    TIMSK2 &= ~(1<<TOIE2);
    PORTD |= (1<<PD7); 
    delay(1000);
    PORTD &= ~(1<<PD7); 
    counter = 0;
  }
  TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed
  TCNT2 = 0;
  set_sleep_mode(SLEEP_MODE_PWR_SAVE); // choose power down mode
  //power_timer2_disable(); // if you uncomment the MCU will sleep forever 
  sleep_mode(); // sleep now!
  counter++;
}

// TIMER2 interrupt service routine
ISR(TIMER2_OVF_vect){ 
}

 

Und dann könnt ihr ja nochmal die Zeile 23 entkommentieren. Der ATmega328P verfällt daraufhin in einen Dornröschenschlaf – nur ohne Prinz, der ihn wach küsst. Erst ein Reset weckt ihn wieder. 

Sleep Modes auf andere AVR MCUs anwenden

Wie schon eingangs erwähnt ist der ATmega328P nicht unbedingt die erste Wahl für ein stromsparendes Projekt. Mit dem hier erworbenen Wissen sollte es aber kein Problem sein, auch andere AVRs in den Schlaf zu schicken. Schaut ins Datenblatt und sucht nach Sleep Mode. So sieht beispielsweise die Sleep Mode Tabelle für den ATtiny85 / 45 / 25 aus:

Sleep Modes ATtiny85, ATtiny45, ATtiny25

Es stehen drei Sleep Modes zur Verfügung, die über die entsprechenden Funktionen aus sleep.h aktiviert werden können. Wecken über den Timer ist nicht möglich. Wenn ihr die Register direkt ansprecht, müsst ihr allerdings darauf achten, dass diese z.T. unterschiedliche Bezeichnungen haben oder dass z.B. der Watchdog andere Prescaler hat. 

Danksagungen

Diesmal habe ich eine ganze Menge Einzelbilder von Pixabay verwendet. Im einzelnen Danke ich den fleißigen Fotografen:

39 thoughts on “Sleep Modes und Power Management

  1. Kann man den SLEEP_MODE_ADC auf dem Arduino Nano nutzen um Analogwerte besser/genauer/rauschfreier einzulesen? Wie funktioniert das in einem Sketch?
    Würde das eine Verbesserung bringen?

    1. Hallo, ja, das kann man. Allerdings geht das nicht mit analogRead(). Stattdessen muss man auf der Registerebene bleiben. So kann ein Sketch aussehen:

      #include <avr/sleep.h>    // Sleep Modes
      EMPTY_INTERRUPT(ADC_vect);  // when conversion completed, take an interrupt 
      
      int adcConversion(byte adcPin){
          ADCSRA = (1<<ADEN) | (1<<ADIF);  // enable ADC, clear interrupts
          ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);   // prescaler: 128
          ADMUX = (1<<REFS0) | (adcPin);  // Referrence: AVcc   
          
          noInterrupts ();
          set_sleep_mode(SLEEP_MODE_ADC);    // sleep during sample
          sleep_enable();  
          ADCSRA |= (1<<ADSC) | (1<<ADIE); // conversion
          interrupts ();
          sleep_cpu ();     
          sleep_disable ();
          //while(bit_is_set(ADCSRA, ADSC)){} // in case another interrupt woke up the MCU
          return ADC;
      }  
        
      void setup (){
          Serial.begin (115200);
      } 
        
      void loop(){
          int adcValue = adcConversion(0); // 0 for A0, 1 for A1, etc.
          Serial.println(adcValue);  
          delay(1000);   
      }
      
  2. Hallo Wolfgang,

    danke für deinen Tollen Beitrag. Ich versuche gerade einen Arduino pro mini 5V davon zu überzeugen, dass er genau das tun soll, was ich ihm Sage. Ich verwende folgenden Sketch: wake_up_on_timer2_interrupt.ino

    Das Problem ist, dass er nach 12 Stunden, also nach 2635200 mal aufwachen, was ich in Zeile 13 eintrage, nicht aufwacht und meine Pumpe, welches überein Realis an Pin 7 anliegt, aktiviert.

    Die Schaltung funktioniert bei Counter=500, Wasser kommt, aber leider sehe ich nach 12 Stunden kein Wasser.

    Was macht ich falsch? Ist die Zahl zu groß?

    Mit besten Grüßen und Danke für deine tolle Seite.

    1. Hi Sven,
      genau, counter ist als int definiert, d.h. bei 2^15 – 1 ist Schluss. Wenn du counter als unsigned long definierst sollte es gehen.
      VG, Wolfgang

      1. Hallo Wolfgang,
        Ich muss gestehen, dass meine Programmierkenntnisse eigentlich nicht vorhanden sind. Ich kann den Code lesen und verstehen und Zahlen ändern aber eben nicht selbst schreiben.
        könntest du mir zeigen, wie ich das am besten mache? Das wäre mir eine sehr große Hilfe.

        1. Hi Sven,
          Zeile 3 ersetzt du durch:
          unsigned long int counter = 0;
          und Zeile 13 durch:
          if (counter >= 2635200){

          Der genaue Wert wäre das Ergebnis von:
          16000000 / 1024 / 256 * 12 * 60 *60

          Zu beachten ist, dass der Oszillator eine gewisse Abweichung hat. Bei 12 h kann das ein paar Minuten ausmachen.
          VG, Wolfgang

      2. Hallo Wolgang,

        ich habe mich nun intensivst in die Materie eingelesen.

        Meine eventuelle Lösung:
        Variante 1:

        unsigned long counter;

        int counter = 0;
        void setup(){

        Variante 2:

        Oder ist die folgende Variante besser:
        void loop(){
        if(unsigned long counter==2635200){

        Variante 3:

        Oder sollte ich es über eine Variable für den Counter machen:

        unsigned long time;

        viod setup()
        uint32_t time = 2635200 //Zeiteinstellung bis zudem der Counter zählt

        void loop(){
        if(unsigned long counter==time){

        Wobei nun ja das Problem ist, dass der Counter hochzählt und dann über 2^15 – 1 läuft. Variante 1 wäre das einfachste.

        1. Leider kann man Kommentare nicht editieren. Ich habe nun eine weitere Variante kompiliert, aber nicht getestet:

          unsigned long int counter = 0;
          unsigned long int countermax = 2635200;

          void setup(){
          DDRD = (1<<PD7);
          TCCR2A = 0x00; // Wave Form Generation Mode 0: Normal Mode, OC2A disconnected
          TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); // prescaler = 1024
          TIMSK2 = (1<<TOIE2);
          }
          void loop(){
          if(counter==countermax){
          ……

          Geht das?

          Eine Erweiterung, dass man die Zahl des Intervalls nicht mehr ausrechnen muss, sondern über eine Eingabe errechnen lässt:
          unsigned long int counter = 0;
          int stunde = 5;
          int minute = 10;
          int sekunde = 0;
          unsigned long int countermax =( (stunde * 60.0 + minute) * 60.0 + sekunde) * 61.0;

          void setup(){

          void loop(){
          if(counter==countermax){
          ……

          Ich hoffe auch das ist korrekt.

          1. Das sieht gut aus, aber ich muss noch etwas verbessern. In der Berechnung von countermax verwendest du Fließkommazahlen wie 60.0. countermax ist aber eine Ganzzahl. Wahrscheinlich funktioniert es trotzdem weil der Compiler aus der 60.0 eine 60 macht. Fließkommazahlen sind float oder double. Man sollte sie aber nur verwenden, wenn unbedingt notwendig, da sie nicht exakt sind.
            VG, Wolfgang

        2. Am besten lernt man durch Ausprobieren und Fehler machen. Du kannst ja kleinere Werte nehmen, bei den etwas passieren soll, damit du nicht 12 h warten musst.

          Variante 1 wird einen Fehler geben, da du counter 2x definierst, einmal als int und einmal als unsigned long. Lass das int bei int counter = 0 weg.

          Variante 2 geht nicht, weil du counter bei jedem loop Schleifendurchlauf neu definierst. Was ginge ist:
          void loop(){
          static unsigned long counter = 0:
          if (counter == 2635200) {
          usw.
          Dann musst du aber die erste Definition von counter vor dem setup weglassen. Der Zusatz static bewirkt, dass counter nicht bei jedem Durchlauf neu definiert und auf Null gesetzt wird.

          Zu Variante 3: Variablen, die du in setup definierst sind nur in setup bekannt.

  3. Hi!
    Danke für deinen tollen Beitrag hier – hat mir sehr weitergeholfen bei meinem momentanen batteriebetriebenen AVR-Projekt! Ich habe dann auch länger herumexperimentiert und kam mit dem 328P nie unter ca. 23mA. Viel zu viel für längerfristigen Batteriebetrieb.
    Hab dann schon gedacht muss ich doch mal mit Atmel Studio beginnen, da du erwähnt hast dass du mit Arduino hier auch nicht weiter gekommen bist, aber durch einen anderen Artikel habe ich dann noch andere Dinge ausprobiert und habe festgestellt, dass das Abschalten des ADC wohl mit power_adc_disable() nicht korrekt funktioniert.
    Wenn ich den ADC direkt über das Register ADCSRA = 0; deaktiviere sinkt der Strombedarf im Power Down Mode sofort auf ~15µA.
    Wieder aktivieren (für bandgap Methode zur Messung der Batteriespannung) geht dann so:
    ADCSRA = 0x80; // -> enable ADC, set MSB of ADCSRA register to 1

    Außerdem sollte man noch den Brown-Out detector deaktivieren – wie das geht (ich vertraue den high level Funktionen aus der power.h jetzt nicht mehr) steht auch dort.

    Hier der Link: https://www.gammon.com.au/power

    Konnte jedenfalls mit diesen gesammelten Infos aus einem modifizierten Pro Mini nun einen batteriebetriebenen Funk-Impulssender für den an der Grundstücksgrenze befindlichen Gaszähler bauen, der wohl mit 3xAAA recht lange laufen wird 🙂

    Vielen Dank und LG,
    Flo

  4. Hallo Wolfgang,
    jedesmal wenn ich ein neues Projekt starte… Wolfgang war bereits da 🙂 Das ist gut, denn deine Anleitungen sind wirklich gut.
    Ich habe ein paar weitergehende Fragen (genau zwei) die hoffentlich im Rahmen bleiben. Ich versuche es mal:

    Am ATTiny85 möchte ich in Power Down gehen und nur mit einen RISING an PCINT2/PB2 aufwecken. Der PIN PB2 ist als INPUT definiert und der ATTiny bekommt im Setup folgende Aufträge:
    cli();
    GIFR |= (1 << PCIF); // clear any outstanding interrupts
    GIMSK |= (1 << PCIE); //Pinchange Interrupt enable
    PCMSK |= (1 << PIN_BUTTON); //Pinchange Interrupt an PB2
    MCUCR |= (1 << SM1)|(0 << SM0 )|(1 << ISC00)|(1 << ISC01);//Sleep als Power down (und eigentlich nur rising edge löst Interrupt aus – macht aber nicht was es soll)
    sei();

    Frage 1 und Problem: Jeder Change löst einen Intterupt aus, nicht nur Rising. Ich habe überlegt ob ich besser fahre wenn ich mit attachInterrupt(0,Funktionsname,RISING) arbeite… dann sehe ich nicht was passiert, aber vielleicht klappt es ja… nein eigentlich möchte ich schon lieber wissen wo der Denkfehler ist.;-)

    Frage 2:
    Man sieht oft "ADCSRA = 0;" in verschiedenen Beiträgen – teilweise wird ADCSRA zwischengespeichert und wieder mirt den Ausgangswerten gefüllt wenn es ans aufwachen geht. Sowie ich verstehe geht es hier um den Analog-Digital-Wandler? Kann der nicht immer schlafen wenn ich keine analogen Messerte brauche, dann würde er dauerhaft keinen Strom ziehen und ich könnte auch im Wachzustand energie sparen? Oder bin ich da komplett auf dem Holzweg?

    Vielen herzlichen Dank schon alleine fürs Lesen, ich hoffe Du kannst mir auf die Sprünge helfen.

    Grüße,
    Leif

    1. Hallo Leif,

      ich denke du musst das INT0 Bit setzen und nicht das PCIE Bit, sofern du das ganze „flankensensitiv“ gestalten willst. So interpretiere ich jedenfalls das Datenblatt.

      Bit 6 – INT0: External Interrupt Request 0 Enable
      When the INT0 bit is set (one) and the I-bit in the status register (SREG) is set (one), the external pin interrupt is enabled.
      The interrupt sense control0 bits 1/0 (ISC01 and ISC00) in the external interrupt control register A (EICRA) define whether
      the external interrupt is activated on rising and/or falling edge of the INT0 pin or level sensed. Activity on the pin will cause an
      interrupt request even if INT0 is configured as an output. The corresponding interrupt of external interrupt request 0 is
      executed from the INT0 interrupt vector.
      • Bit 5 – PCIE1: Pin Change Interrupt Enable 1
      When the PCIE1 bit is set (one) and the I-bit in the status register (SREG) is set (one), pin change interrupt 1 is enabled. Any
      change on any enabled PCINT11..8 pin will cause an interrupt. The corresponding interrupt of pin change interrupt request is
      executed from the PCI1 interrupt vector. PCINT11..8 pins are enabled individually by the PCMSK1 register

      Und was den ADC angeht, gebe ich die Recht. Eigentlich müssen man ihn dauerhaft abschalten können. Datenblätter sind keine schöne Lektüre, aber ab und zu lohnt ein Blick.
      • Bit 7 – ACD: Analog Comparator Disable
      When this bit is written logical one, the power to the analog comparator is switched off. This bit can be set at any time to turn
      off the analog comparator. This will reduce power consumption in active and idle mode. When changing the ACD bit, the
      analog comparator interrupt must be disabled by clearing the ACIE bit in ACSR. Otherwise an interrupt can occur when the
      bit is changed

      ADC ist das Bit 7 von ADCSRA, sollte ich vielleicht erwähnen. Demnach sollte das auch im aktiven Modus Strom sparen.

      Und nur eine Kleinigkeit: (0< dann setzt du genau diese Bits und alle anderen sind 0.
      oder um SM0 gezielt zu löschen:
      MCUCR &= ~(1<

      1. Danke Wolfgang!
        Ja, du hast vollkommen recht. Ich lese Beiträge in Foren, kopiere Code und lese gerade soviel quer durchs Datenblatt, dass ich das kopierte als plausibel anerkennen kann ohne es richtig zu verstehen. Ich gebe zu, es gibt sogar eine gewisse Angst vor Datenblättern weil zuviel Lesen glatt eine Katatonie erzeugt, oder ich heulend zu Bett will – Bildlich gesprochen:-). Manchmal ist aber lesen besser als Googlen: GIMSK Bit 6 mit dem schönen Namen INT0 ist mein Freund. In den meisten gegoogleten Beiträgen ist hingegen Pinchange-Interrupt das Thema – wäre aber gar nicht mein Thema gewesen. Danke fürs Kopf-Zurechtrücken.
        Zum ACD und zur Umsetzung der „Kleinigkeit“ in einem Satz – ich pfusche zum ersten Mal mit Registern und verstehe die Bitmanipulation bisher auch nur durchs kopieren ohne wirklich kognitiv zu verstehen was ich da tue:
        „ACSR |= (1 << ACD) & ~(1 << ACIE ); " ist richtig
        und
        "ACSR |= (1 << ACD) | (0 << ACIE );"
        ist falsch?

        Grüße und Danke!

        1. Hi Leif,

          die zweite Anweisung würde nur das ACD Bit setzen, aber nicht ACIE löschen. (0<<x) geht nicht! Die erste Anweisung ist fast richtig. Schritt für Schritt:
          ACD ist 7 und ACIE ist 3.
          (1<<7) & ~(1<<3) = 0b10000000 &~0b00001000 = 0b10000000 & 0b11110111 = 0b10000000
          Daraus wird dann: ACSR |= 0b10000000;, sprich: ACSR = ACSR | 0b10000000;
          Das hast du ACIE also nicht gelöscht. Du muss Setzen und Löschen trennen:
          ACSR |= (1<<ACD);
          ACSR &= ~(1<<ACIE);
          Denn der letzte Ausdruck bedeutet:
          ACSR &= 0b11110111;
          ACSR = ACSR & 0b111101111:
          …und das ist, was du willst.
          VG, Wolfgang

          1. Danke nochmals. Zwar habe ich ein paar Tutorials zu Bit Operationen angesehen, die Idee x |= (1<<y) & ~(1<<z); jedoch mal wieder nur kopiert und zumindest solange darüber nachgedacht bis es mir plausibel “erschien”. (https://elektro.turanis.de/html/prj271/index.html)
            Ich war am Ende der Meinung das die Operationen nach der Reihe von links nach rechts erfolgen, also x = (x | (1<<y) ) & ~(1<<x);
            Wie so oft ist Mein Glaube!=Wissen und ich sehe ein, dass ich Vermutungen besser prüfen sollte. Zwei Zeilen reduzieren auf jeden Fall die Fehlerquellen, aber irgendwie kommt man sich smart vor wenn man nur eine Zeile braucht… Eitelkeit.
            In diesem Zusammenhang … sehe ich manchmal die Schreibweise:
            x |= _BV(y);
            Oder auch:
            x |= bite (y);
            Um ehrlich zu sein…. Ich bin ratlos was das bedeutet.
            Es macht mich kirre, dass man in drei verschiedenen Tutorials genauso viele Varianten ein der Bitmanipulation liest. Ich glaube ich werde mich hinsetzen müssen und jede ausprobieren, was mich -konstruktivistischekonfusion- davon abhalten wird das Projekt zu schnell beenden;-)

            1. Das beste ist: einfach ausprobieren, z.B. so:
              void setup(){
              Serial.begin(9600);
              byte reg = 0b0001000;
              Serial.println(reg, BIN);
              reg |= (1<<2);
              Serial.println(reg, BIN);
              reg |= (0<<2);
              Serial.println(reg, BIN);
              reg = 0b10001111;
              Serial.println(reg, BIN);
              reg |= (1<<4) &~(<<2);
              Serial.println(reg, BIN);
              reg &= ~(1<<2);
              Serial.println(reg, BIN);
              }
              und dann schauen was passiert. Und diese _BV-Schreibweise ist einfach nur eine andere Schreibweise:

              #define _BV(x) (1<<x)

              _BV(x) ist also dasselbe wie (1<<x).

              1. Wolfgang Du ist ein Fuchs !!!! Du hast wieder mal völlig recht:
                byte testwert = 0b1000;

                void setup() {
                // put your setup code here, to run once:
                Serial.begin(9600);

                }

                void loop() {
                delay(100);
                // put your main code here, to run repeatedly:
                Serial.print(„testwert initial= „);
                Serial.println(testwert, BIN);

                testwert |= (1<<2);
                Serial.print("testwert |= (1<<2); Ergibt: ");
                Serial.println(testwert, BIN);

                testwert &= ~(1<<2);
                Serial.print("testwert &= ~(1<<2); Ergibt: ");
                Serial.println(testwert, BIN);

                testwert |= (1<<2) | (1<<1);
                Serial.print("testwert |= (1<<2) | (1<<1); Ergibt: ");
                Serial.println(testwert, BIN);

                testwert |= (1<<0) & ~(1<<1);
                Serial.print("testwert |= (1<<0) & ~(1<<1); Ergibt: ");
                Serial.println(testwert, BIN);

                testwert = testwert | (1<<0) & ~(1<<1);
                Serial.print("testwert = testwert | (1<<0) & ~(1<<1); Ergibt: ");
                Serial.println(testwert, BIN);

                testwert = (testwert | (1<<0)) & ~(1<<1);
                Serial.print("testwert = (testwert | (1<<0)) & ~(1< testwert initial= 1000
                08:57:13.455 -> testwert |= (1< testwert &= ~(1< testwert |= (1<<2) | (1< testwert |= (1<<0) & ~(1< testwert = testwert | (1<<0) & ~(1< testwert = (testwert | (1<<0)) & ~(1<<1); Ergibt: 1101

                Du weißt es, aber vielleich hilft es den anderen Lesern. Falls nicht gewünscht kannst Du meine Antwort aber natürlich gern rausnehmen.

                Vielen Dank. Ich habe jetzt genug Stoff um das Projekt ordentlich weiterzubringen.

  5. Moin,

    ich habe bisher immer den Standby Sleep Modus (da Akkubetrieb) verwendet.
    Jetzt möchte ich gerne einen Timer, der Sekunden, Minuten und Stunden auf während des Sleep Modus zählt. Bisher habe ich immer den Timer1 verwendet und in der ISR jede Sekunde hochgezählt. Jeoch gehe ich davon aus, das der Timer1 im Standby nicht läuft. Wie könnte man das am besten lösen.
    Lg, Simon

    1. Hi Simon, der Timer2 würde funktionieren, so wie ich es oben beschrieben habe. Der Atmega328P wacht dann zwar sehr hochfrequent auf, aber über eine Zählvariable kannst du steuern, dass er z.B. erst beim 100.000sten Aufwachen im Programm fortfahren soll, ansonsten wird er wieder schlafen geschickt. Klingt, als ob das ergetisch nischts bringen würde, tut es aber. Außer dem Timer2 wäre noch der WatchDogTimer geeignet. Auch wieder unter Zuhilfenahme eines Zählers.
      Ansonsten kann ich noch den LT6995 Timer als externen „Wecker“ empfehlen:
      https://wolles-elektronikkiste.de/ltc6995-long-timer-low-frequency-oscillator
      Der kann Wecksignale bis zu einer Periode von 9.5 Stunden geben. Allerdings ist eine sekundengenaue Einstellung schwierig.
      Das mit dem Schlafen bzw. Wecken kann der ansonsten sehr stromhungrige ESP32 besser:
      https://wolles-elektronikkiste.de/esp32-mit-arduino-code-programmieren#strom
      VG, Wolfgang

  6. Hallo Wolfgang
    Es stimmt, du hast für power.h keine Angaben gemacht. Habe einfach angenommen, wenn bei sleep.h Einsparungen möglich sind muss sich doch bei power.h und den genannten Anwendung doch auch was tun. Bei meinen Messungen konnte ich leider nur ca, 1-2 mA nach vollziehen. Bei sleep stimmen deine Angaben so +/- mit meinen Messungen überein. Leider finde ich im Netz zu power.h und Strom kaum Angaben.
    achim

    1. Hallo Achim, der Sleep Mode ist nicht die Summe aus allen power.h – disable Funktionen. So weit ich das sehe, gibt es z.B. keine Funktion, um die CPU Clock auszuschalten. Ich würde es vielleicht so ausdrücken, dass die power.h Funktionen es ermöglichen, ein Finetuning des Sleep Modes vorzunehmen.

  7. Hallo Wolfgang
    Versuche gerade für einen Attiny 841 deine Stromsparangaben mit power.h nach zuvollziehen. Die Einsparung mit sleep funktioniert ohne Probleme . Leider sind die Messungen bei power.h sehr klein. Es werden ca. nur 1 – 2 mA eingespart wenn alles ausgeschaltet ist. Wie kommt es zu diesem doch grossen unterschied?
    achim

    1. Hallo Achim,
      meinst du mit „Messungen bei power.h“ die Funktionen power_xxxxx_disable()? Dazu habe ich gar keine Angaben hinsichtlich der Einsparung gemacht. Wie ich im Beitrag schrieb, beziehen sich die Stromangaben auf den Sketch „sleep_modes_wd_wakeup_with_int_disable.ino“, der keine Funktionen aus power.h verwendet – oder reden (will sagen schreiben 😉 ) wir gerade aneinander vorbei?
      VG, Wolfgang

  8. Hallo,
    wie mache ich das mit dem Sleep Mode wenn die Zeit, die der ATmega schalfen soll, größer als 8 Sekunden ist?
    Mache ich dann einfach eine Zählschleife im ISR, die im jeden Druchgang einen hochzählt?

    1. Hallo Felix,
      es kommt darauf an, welchen sleep mode du verwenden willst. Wenn du den Power-Down Modus verwendest, dann kann sich der Atmega nur über einen WDT selbst wecken. Das geht aus der Tabelle 2 (Wake Up Sources) hervor. Du kannst natürlich die Aufwachvorgänge zählen und schickst den Atmega bis zum Erreichen eines Limit gleich wieder schlafen. Aber während des Schlafvorganges kannst du die Frist nicht verlängern. Das ist so als würde man dir die Aufgabe geben, deinen Wecker auf 8 Stunden einzustellen, aber schon nach 7 Stunden sollst du ihn weiterstellen – ohne aufzuwachen!

      Im Power-Save Modus kann sich der Atmega durch seinen Timer2 wecken lassen. Auch hier lässt sich der Zähler nicht zurückstellen, während der Atmega schläft. Und der Timer2 läuft ziemlich schnell über. Auch hier würde ich dann einen Zähler einfügen. Ob du das im Sketch machst oder im ISR (in diesem Fall das „volatile“ bei der Definition nicht vergessen) , spielt keine Rolle. Das Aufwachen kannst du aber nicht verhindern und startest wieder dort, wo du nach sleep_mode() aufgehört hast. Ein kurzes Aufwachen fällt aber energetisch nicht sehr ins Gewicht.

      Eine Alternative wäre ein externer Timer der den Atmega über einen Interrupt von Außen weckt, z.B.:
      https://wolles-elektronikkiste.de/ltc6995-long-timer-low-frequency-oscillator
      VG, Wolfgang

      1. Hallo,
        vielen Dank.
        In meinem Projekt benötigte ich den Standby Mode.
        Hier würde es sich doch nur anbieten, die Vorgänge zu Zählen, da hier ja kein Timer2 zur Verfügung steht.

  9. Hallo Wolfgang,

    wie erklärst Du denn die großen Abweichungen zwischen Deinen Messungen und den Microchip-Spezifikationen? Ich habe hier mal ein paar Beispiele:

    ATmega328P

    – 8 MHz 5 V: 5.5 mA versus 18 mA

    – 16 MHz 5 V: 9.5 mA versus 24 mA

    – PowerDown 5V: 0.15 μA versus 30 μA oder 150 μA

    ATtiny85

    – PowerDown 5 V: 0.5 μA versus 6.8 μA

    1. Hallo Bernhard,

      in der Tat große Abweichungen, die ich auch nicht erklären kann. So gemessen. An der Messgenauigkeit kann es nicht liegen. Würde mich sehr interessieren was andere messen. Oder wenn jemand einen Fehler in dem Sketch entdeckt, mit dem ich die Werte ermittelt habe, dann bin ich um Mitteilung auch sehr dankbar.

      VG, Wolfgang

      1. Beim Powerdown-Modus hast du vergessen, den ADC auszuschalten. Außerdem ist vermutlich BOD aktiviert. Mit Nick Gammons Sketch D unter http://www.gammon.com.au/power solltest Du auch auf ca. 100 nA kommen können (im Rahmen der Messgenauigkeit). Das habe ich jedenfalls ziemlich konsistent bei meinen 328P-Systemen gemessen.

        Im aktiven Modus bei 16 MHz und 5 V sind die hohen Werte auch mir nicht erklärlich. 24 mA erscheint mir extrem hoch. Ich habe etwas niedrigere gemessen (16-18 mA), bei Nick Gammon sind es 15 mA. Drückt man das ganze noch mit PRR und lässt außerdem nur NOPs ausführen, komme ich auf 12.5 mA. Ist aber immer noch weit weg von „typisch“ 9.5 mA, wie in der Figur 29-331 im Datenblatt versprochen.

        Bei aktivem Modus bei 8 MHz und 3,3 V sind es dann allerdings 3-4 mA, wie im Datenblatt in Figur 29-334 angegeben.

        1. Herzlichen Dank für die fundierten Informationen, das ist ausgesprochen nützlich! Ich werde das nochmal experimentell nachvollziehen. Für den Moment werde ich im Beitrag vermerken, dass die von mir angegebenen Werte spezifisch für den angegebenen Sketch ermittelt wurden und es weiteres Potential gibt, z.B. durch Abschalten des ADC.

          1. In einem „guten“ uC Datenblatt werden die Stromangaben mit „min“ „typisch“ und „max“ gemacht. Auf Grund des temperaturabhängigen Leakage Stromes in Halbleitern und auch Schwankungen in Halbleiterprozessen, kann es zu Abweichungen zwischen Messungen und Datenblattwerten kommen.

    1. Hallo Wolfgang,

      vielen Dank. Das klingt sehr vielversprechend! Auf jeden Fall interessant!

      VG, Wolle

  10. Moin,
    im Abschnitt „Aufwecken mit Timer2“ steht im ersten Satz: „außer im Power-down und Power-save Modus“. Dennoch benutzt du im Sketch den „Power-save Modus“!
    LG Sascha

    1. Moin Sascha, vielen Dank. Stimmt, wie auch ein Blick in Tabelle 2 verrät. Nur im Power down Modus kann der ATmega328P nicht geweckt werden. Im Power Save Modus geht es. Wird gleich korrigiert. Nobody is perfect! LG, Wolfgang

  11. Hallo,

    zunächst mal: Deine Seite ist fantastisch, vielen Dank!

    Ich möchte dich außerdem um Tipps für ein Projekt bitten:

    Es geht um einen Wasseralarm für unter dem Spülbecken. An unserem Wasserhahn lockert sich gelegentlich eine Schraube und dann tropft Wasser nach unten in das Schrankfach der Einbauküche. In diesem Fall soll ein Alarm ausgelöst werden.

    Wassersensor soll ein Regensensor (Bezeichnung YL-83) sein. Dieser benötigt 5 V Spannung und nach meinen Messungen LOW ca. 4 mA/HIGH ca. 7,5 mA Strom. Ich möchte den Sensor regelmäßig, z.B. stündlich, mit einem MCU auslesen.

    Gegebenenfalls soll ein Piezo-Buzzer-Alarm ausgelöst werden; das Modul benötigt ebenfalls 5 V Spannung, und gut 12 mA Strom. Wobei der Stromverbrauch kaum entscheidend sein dürfte, da der Alarm ja hoffentlich die meiste Zeit aus ist – allenfalls ist der maximale OUTPUT-Strom des MCU-Pins ein Thema.

    Jetzt der Haken: Das Ganze soll batteriebetrieben sein mit möglichst geringem Batterieverbrauch, und es soll so günstig wie möglich realisiert werden. Insbesondere würde ich ungern extra einen Programmer kaufen müssen. Ich habe leider auch keine Komponentensammlung, was Kondensatoren, einen Schwingquarz, Widerstände angeht. Deshalb denke ich auch, dass es besser wäre, wenn der MCU 5 V verträgt, denn dann bräuchte ich auch keinen Logic Level Converter.

    Kannst du mir einen Rat für dieses Projekt geben?

    Vielen Dank im Voraus!

    Roman

    1. Hallo Roman, nimm doch einen ATtiny, der verbraucht wenig Strom, du brauchst keinen Schwingquarz und auch sonst ist er recht anspruchslos. Die Arduinoboards verbrauchen recht viel Strom und man bekommt das auch mit Sleep Modes schwer in den Griff. Schon allein wg. der LEDs.

  12. Sehr interessanter Beitrag. Damit hast du mir echt einiges erleichtert. Ich hatte einige Probleme mit der Programmierung…

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert