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. Das liegt daran, dass das Arduino Boardpaket bestimmte Funktionen standardmäßig anschaltet, die für einen energieeffizienten Schlaf explizit abgeschaltet werden müssen. Wir kommen noch dazu.
  • 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 ADC aus
    • zum Ausschalten müsst ihr den ADC vorher deaktivieren (Zeile 18)

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 28 bis 35.

Um fluktuierende Pinzustände während des Schlafes zu vermeiden, setzt ihr die Pins am besten auf OUTPUT/LOW (Zeile 6-11).

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

void setup(){
  DDRB = 0;
  DDRC = 0;
  DDRD = 0;
  PORTB = 0;
  PORTC = 0;
  PORTD = 0;

  watchdogSetup();
}

void loop(){
  delay(5000);
  ADCSRA = 0; // disable ADC
  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

//  power_usart0_disable();
//  power_spi_disable();
//  power_timer0_disable();
//  power_timer1_disable();
//  power_timer2_disable();
//  power_twi_disable();
//  power_adc_disable();
//  power_all_disable (); // short summary of the former six lines
  
  sleep_enable(); // sets the SE (sleep enable) bit
  sleep_bod_disable(); // disable brown-out detector
  
 
  sei(); // 
  sleep_cpu(); // sleep now!! // no wake up option selected
  
  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
}

 

Mit diesem Sketch konnte ich den Stromverbrauch bei 5 V Versorgungsspannung auf 6.3 µA senken. Wenn ich dazu noch den Watchdog ausgeschaltet habe, ging er hinunter bis auf 0.28 µA. Dann allerdings wacht der ATmega328 gar nicht mehr auf. Alternativ habe ich als Weckmethode einen Pin Change Interrupt ausprobiert. Damit bin ich auf einen Stromverbrauch von 0.71 µA gekommen – auch nicht schlecht.

Wie ihr seht, habe ich in diesem Sketch keine der Optionen genutzt, die das Power Reduction Register liefert. Das war nicht nötig, weil die Komponenten im entweder nicht aktiviert oder im Power-down Modus ohnehin abgeschaltet wurden. Die wesentliche zusätzliche Einsparung gegenüber dem vorherigen Sketch war das Abschalten des ADCs mittels ADCSRA = 0.

Wenn ihr nicht sicher seid, was im Wachmodus aktiviert war oder nicht, dann schaltet lieber mehr ab als zu wenig. Allerdings solltet ihr nichts abschalten, was ihr zum Wecken braucht, und ihr solltet nicht vergessen, die Komponenten auch wieder anzuschalten.

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:

49 thoughts on “Sleep Modes und Power Management

  1. Hallo,

    Ihre Seite hier hat mir sehr geholfen, einen ATmega168A in den Sleep Modus zu versetzen und auch wieder aufzuwecken. Nachdem das erreicht war, habe ich die Stromaufnahme im Sleep Modus gemessen und musste leider feststellen, dass sie weit von den Angaben im Datenblatt entfernt ist.

    Ich habe dann Schaltung und Programm auf das nötigste reduziert. Es ist nur noch der Controller und eine LED sowie ein Pull-Up Widerstand an Reset_ vorhanden. Die Schaltung wird mit 3,3V betrieben.

    Im Power Down Modus messe ich ca. 100uA (statt laut Datenblatt ca. 0,1uA) und im Idle Modus sind es gut 650uA (statt laut Datenblatt unter 500uA). Daran ändert sich nichts, wenn ich die LED oder den Pull-Up an Reset_ entferne.

    Mit einem ATmega328P sind die Messwerte ähnlich, nur ca. 15uA niedriger.

    Die Messwerte stimmen, ich habe sie mit verschiedenen Methoden überprüft und auch Vergleichsmessungen mit einer Spannungsquelle an einem MegOhm-Widerstand gemacht.

    Meine Frage ist nun, wo mein Fehler liegt. Hier der Sketch, den ich ohne Bootloader auf den Controller programmiere. Der Controller läuft mit dem 8MHz internem RC-Oszillator, in den Fuses ist BOD ist abgeschaltet und auch der Watchdog.

    //#include
    #include
    #include

    #define PIN_LED 8

    void setup()
    {
    // verhindert floating Inputs auch für analoge Pins
    for (int i=0; i<=19; i++)
    {
    pinMode(i, INPUT_PULLUP);
    }

    // blinkende LED zeigt POR an
    pinMode(PIN_LED, OUTPUT);
    for (int i=0; i<4; i++)
    {
    digitalWrite(PIN_LED, HIGH);
    delay(100);
    digitalWrite(PIN_LED, LOW);
    delay(100);
    }

    // nicht benötigte Module abschalten
    cli();
    power_adc_disable();
    power_usart0_disable();
    power_spi_disable();
    power_timer0_disable();
    power_timer1_disable();
    power_timer2_disable();
    power_twi_disable();
    //sleep_bod_disable(); // per Fuse abgeschaltet
    sei();
    }

    void loop()
    {
    // ~100uA @ 3,3V statt laut Datenblatt 0,1uA
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);

    // ~650uA @ 3,3V statt laut Datenblatt <500uA
    //set_sleep_mode(SLEEP_MODE_IDLE);
    cli();
    sleep_enable();
    sei();
    sleep_cpu(); // sleep!

    // sollte nie aufwachen und nie hier weiter machen
    sleep_disable();
    // wenn doch: LED dauerhaft ein
    digitalWrite(PIN_LED, HIGH);
    while(1);
    }

    Ich würde mich freuen, wenn ich dieses Problem lösen könnte bzw. eine Erklärung für die hohe Stromaufnahme bekommen könnte.

    Vielen Dank und Grüße
    Gerhard

    1. Hallo,
      ich kann es nicht sagen, wo der Grund für die Unterschiede liegt. Die im IDLE Mode gemessenen Werte entsprechen zumindest in der Dimension dem Datenblattwert, der Unterschied im Power Down Mode ist schon ziemlich krass. Aber wenn Sie sich meine Messwerte für den Power Down Mode bei 8 MHz anschauen, dann sehen Sie, dass sie auch meilenweit entfernt sind. Und Sie sehen, dass alleine der Wechsel von der Programmierung per Arduino („Atmega328 on a breadboard“) auf die Programmierung per Atmel Studio den Strom von 150µA auf 30µA gesenkt hat, ohne dass ich irgendetwas geändert hätte (ausser loop() gegen while(1) usw.) Ich bin dem bisher nicht weiter auf den Grund gegangen. Ich vermute aber, dass das Geheimnis irgendwo in dem Arduino Boardpaket liegt. Nun müsste man ja im Prinzip alles, was das Boardpaket „anschaltet“ auch wieder „ausschalten“ können. Wie gesagt, ich bin dem nicht weiter auf den Grund gegangen. Aber sofern Sie Atmel Studio zur Verfügung haben, könnte das einen erheblichen Schritt bringen.

      Ansonsten gibt es hier noch einige Hinweise, die vielleicht weiterhelfen:
      http://www.gammon.com.au/power

      Vielleicht schaue ich mir das Thema auch noch einmal an. Aber das wird sicherlich noch dauern.

      Viele Grüße, Wolfgang

      1. Hallo,

        vielen Dank für die freundliche und vor allem auch hilfreiche Antwort.

        Ich habe den Sketch jetzt in C umgeschrieben und mit dem Atmel Studio compiliert und programmiert. Vermutlich werden die includes hier wieder nicht angezeigt. Es sind io.h, interrupt.h, sleep.h und power.h.

        #include
        #include
        #include
        #include

        // Definitionen
        #define F_CPU 8000000UL
        #define PIN_LED 0
        #define NOP() __asm__ __volatile__ („nop\n\t“)

        // globale Variablen
        uint8_t i;
        uint16_t j;
        uint32_t k;

        // Initialisierung
        void init(void)
        {
        // alle Pins als Input mit Pull-Up
        DDRB = 0x00; //make PORT all inputs
        PORTB = 0xFF; //enable all pull-ups
        DDRC = 0x00;
        PORTC = 0xFF;
        DDRD = 0x00;
        PORTD = 0xFF;

        // LED-Pin als Output low
        DDRB |= (1<<PIN_LED);
        PORTB &= ~(1<<PIN_LED);

        // nicht benutzte Module abschalten
        PRR = (1<<PRTWI) | (1<<PRTIM2) | (1<<PRTIM1) | (1<<PRTIM0) | (1<<PRSPI) | (1<<PRUSART0) | (1<<PRADC);

        // Interrupts aktivieren
        sei();

        return;
        }

        int main(void)
        {
        init();

        for (i=0;i<4;i++) // LED blinken lassen
        {
        PORTB |= (1<<PIN_LED);
        for (k=0;k<100000UL;k++) NOP();
        PORTB &= ~(1<<PIN_LED);
        for (k=0;k<100000UL;k++) NOP();
        }

        set_sleep_mode(SLEEP_MODE_PWR_DOWN);
        cli();
        sleep_enable();
        sei();
        sleep_cpu(); // sleep!

        // sollte nie aufwachen und nie hier weiter machen
        sleep_disable();
        // wenn doch: LED dauerhaft ein
        PORTB |= (1<<PIN_LED);

        while (1);
        }

        Als das Programm dann endlich gelaufen ist, schaute ich mir mit Erstaunen die Messwerte damit an (ATmega168A mit internem 8MHz RC-Oszillator). Mit dem Multimeter messe ich gar nichts mehr. Der uCurrent zeigt 0,1uA an, das ist der typ. Wert aus dem Datenblatt. Mit Ihrer Hilfe konnte ich so die Stromaufnahme wie gewünscht absenken!

        Die Frage ist jetzt nur, wie ich den Rest des Programms ohne Arduino in C schreibe. Denn dort fehlen viele Funktionen, die das Programmieren doch sehr erleichtern, das fängt schon mit delay() an…

        Ich habe mir das List-File des Arduino angeschaut, werde daraus aber nicht schlau. Immerhin fällt auf, dass es fast 1000 Zeilen lang ist. Im Vergleich hat das List-File aus dem Atmel Studio gerade einmal knapp 200 Zeilen.

        Mal sehen, wie es weiter geht. Vielleicht lässt sich die Stromaufnahme ja auch mit der Arduino-IDE soweit absenken.

        Danke nochmal und Grüße
        Gerhard

      2. Hallo, Herr Ewald,
        ich bin wirklich sehr beeindruckt, dass Sie zu einem zwanzig Jahre alten Chip immer wieder Neuigkeiten produzieren.
        Die Spielsachen, die ich gelegentlich bastele, haben bisher alle einen Ein/Ausschalter, der aber oft nach Benutzung nicht in Stellung AUS gebracht wird, und das wars dann für die Batterien. Mit Ihrem Tip sinkt die Stromaufnahme fast auf den Wert der Selbstentladung der Batterien. Das Wiedereinschalten macht die Reset-Taste, und den Schalter kann ich mir zukünftig sparen.
        Beim Aufräumen fand ich dann aber im Schrank einen verstaubten (und völlig überteuerten) Taschenrechner-Bausatz der Firma Spikenziel von 2012:
        https://www.spikenzielabs.com/Catalog/spikenzielabs/spikenzielabs-calculator-kit
        siehe auch:
        http://arduino-praxis.ch/2013/04/21/testbericht-calculator-kit/
        Da hätte ich bloss im mitgelieferten Quellcode bei GoToSleep() nachzuschauen brauchen …
        Ich freue mich schon auf die nächsten Neuigkeiten auf Ihrer Seite!
        Klaus Koch

        1. Vielen Dank. Auch da wird der ADC ausgeschaltet:
          ADCSRA &= ~(1<<ADEN);
          Das scheint der „Witz“ bei der Sache zu sein um das letzte Stück Energiesparung zu schaffen.

          Und ja, es ist schon erstaunlich, dass sich in dieser sonst so kurzlebigen Zeit der ATmega328P immer noch großer Beliebtheit erfreut, selbst wenn modernere Mikrocontroller noch deutlich leistungsfähiger sind.

          VG, Wolfgang

    2. Hallo Gerhard,

      ich hänge mich hier mal rein, da ich diese Erfahrungen auch schon hinter mir habe. Das Problem habe ich mit der Library von rocketscream gelöst: https://www.arduinoforum.de/arduino-Thread-Stromsparendes-Schlafen-168PA-328P
      Es lohnt sich, die aktuell von Wolfgang beschriebenen tinyAVR anzuschauen, da kommt man mit dem PIT (Periodic Interrupt) problemlos auf 700nA @ 3.0V mit periodischem Wecken (bis 32s)! Auch damit habe ich schon FunkSensoren gebaut, die Jahre mit einer CR2032 laufen.

      Gruß André

      1. Hallo nochmal und vielen Dank für alle Antworten.

        Die genannte Library muss ich mir bei Gelegenheit anschauen.
        Inzwischen habe ich aber eine Lösung für die Arduino-IDE gefunden.

        Mit diesem Code hier
        ADCSRA = 0;
        power_all_disable ();

        oder alternativ (ist das gleiche):
        ADCSRA = 0;
        PRR = 0xFF;

        bekommt man eine geringe Stromaufnahme, nahe den Werten aus dem Datenblatt.

        Verstanden habe ich das leider nicht, denn der ADC sollte eigentlich ja auch über das Register PRR abgeschaltet werden. Wenn aber die Zeile mit ADCSRA = 0 fehlt, hat man ca. 100uA mehr Stromaufnahme.
        Vielleicht kennt ja jemand die Zusammenhänge und kann das kurz erläutern?

        Vielen Dank und Grüße
        Gerhard

        1. Hallo, da kann ich etwas Licht ins Dunkel bringen. Der ADC muss vor dem Shut-Down im PRR Register disabled werden. Im Datenblatt steht:

          Bit 0 – PRADC: Power Reduction ADC
          Writing a logic one to this bit shuts down the ADC. The ADC must be disabled before shut down. The analog comparator
          cannot use the ADC input MUX when the ADC is shut down.

          VG, Wolfgang

  2. Hallo an alle Stromsparer…..

    ich hab noch einen kleinen Tip – legt die Stromversorgung von SD Kartenlesern, Sensoren und allem anderen Zeug außer RTC und Hauptchip auf einen extra Stromkreis und schaltet den mit nem Mosfet im deepsleep ab – das spart nochmal ein Haufen !

    VG Jens

  3. 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);   
      }
      
  4. 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.

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

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

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

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

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

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

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

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

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

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