banner left Boxtec Banner
Platzhalter BoxtecProdukteForumShopKontaktPlaygroundn/aJobs
 

Arduino Sleep

Warum mit sleep() Energie sparen ?

Bei batteriegestützten Anwendungen wie z.B. Sensor Knoten ist der Stromverbrauch über die Zeit ein wichtiges Kriterium welches letztlich darüber entscheidet wie lange die eingesetzten Batterien im Betrieb durchhalten. Aber auch bei Anwendungen mit Stromversorgung die z.B. nur alle 30s einen Messwert erfassen und weiterleiten ist es sinnvoll, sich mit dem Thema zu befassen - bei einem mehrjährigen Betrieb kann ein sinnvoller Einsatz der Stromsparmöglichkeiten des Mikrokontrollers durchaus bezifferbar Geld sparen.

Ein kleines Rechenbeispiel dazu: Ein Arduino mit einem angeschlossenen Sensor benötigt im Dauerbetrieb 60mA, d.h. bei 5V 300mW. Pro Jahr werden so wohl gegen 2.6KWh für die Anwendung zusammenkommen. Das macht je nach Gegend zwischen 0.5 und 1 Franken Energiekosten pro Jahr.

Gehen wir nun davon aus, dass die Anwendung für 59s bei einem Verbrauch von 0.5mA läuft und 1s bei 60mA, dann ergibt sich ein Durchschnittsverbrauch von 1.491mA für die Anwendung. Daraus resultiert eine Leistung von 7.5mW und ein Jahresverbrauch von ca. 0.065KWh. D.h. die gleiche Anwendung läuft mit den gleichen Energiekosten oder Batterien ca. 40mal länger! Wenn die Anwendung statt 1s pro Minute nur 0.5s läuft wird daraus schon Faktor 80 und so weiter..

Oder anders gesagt: Mit der Energie für einen Knoten kann ich 80 Knoten mit etwas schlauer Programmierung betreiben.

Energiespartechniken

Hardware optimieren

Bei der Hardware gibt es bereits einige Optimierungsmöglichkeiten bei Komponenten die von einem Schlafmodus nicht profitieren. Dazu gehören onboard Spannungsregler, Schnittstellen-Bausteine wie USB-Serial, Power LEDs etc. Wenn ein Board die Hauptzeit seines Lebens im Betrieb verbringt ist eine dauernd mit Strom versorgte FTDI Schnittstelle nicht sinnvoll, genauso wenig ein Power LED welches dauernd Strom braucht und nicht abgeschaltet werden kann. Hier empfiehlt sich der Einsatz eines möglichst rudimentären und auf den Zweck zugeschnittenen Boards, welches auf überflüssige Komponenten verzichtet.

Power Saving in der Software

Das Aufwachen

Wer vor dem Schlafengehn den Wecker nicht stellt, der verpennt ziemlich sicher. Darum sollten wir uns zuerst um die Rückkehr aus dem Schlafmodus kümmern bevor wir diesen einleiten.

Um aus dem Schlafmodus aufzuwachen gibt es verschiedene Möglichkeiten, diese listet die Tabelle 7-1 aus dem ATmega328 Datenblatt:

ATmega328 Wakeup Sources

Das Aufwachen kann also je nach “Schlaftiefe” (Sleep Mode) auf verschiedene Quellen hin passieren. Wir konzentrieren uns im folgenden auf den wirksamstem Sleep Mode Power Down und auf dessen Möglichkeiten zum Aufwecken (Interrupts 0,1 und Pinchange, TWI und Watchdog).

Aufwachen mit externem Interrupt

Damit kann z.B. auf Pegeländerungen hin aufgewacht werden, dazu muss vorher ein Interrupt definiert werden. Ein möglicher Sketch sieht so aus:

#include <avr/sleep.h>
void setup() {
  pinMode(2, INPUT);
  Serial.begin(9600);
}
 
void loop() {
  Serial.println(digitalRead(2));
  Serial.println("Leg mich mal hin..");
  delay(80);
  // Aktiviere Interrupt auf Pin2 (Interrupt 0) mit Handler isr_pin2():
  attachInterrupt(0, isr_pin2, HIGH);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_mode(); 
  // Hier gehts nach dem Schlafen weiter:
  Serial.println("Bin wieder da..");  
}
 
void isr_pin2() {
  // Deaktivieren des Interrupts, damit er nicht dauernd wieder triggert
  detachInterrupt(0);  
}
Macro sleep_mode()

Der Befehl sleep_mode() ist nur ein Macro für:

sleep_enable(); 
sleep_cpu(); 
// Hier wird nach dem Schlaf weitergefahren
sleep_disable();

Aufwachen mit internem Timer

Der ATmega328 verfügt über drei interne Timer:

  • Timer0 (8bit)
  • Timer1 (16bit)
  • Timer2 (8bit)

Das Prinzip hinter den Timern ist einfach, wann immer ein Timer überläuft und auf 0 zurücksetzt, kann dieser einen Interrupt auslösen, dieser Interrupt kann dann wiederum wie oben zum Aufwachen des Mikrokontrollers verwendet werden. Wenn die Timer aber bei jedem Takt des Mikrokontrollers inkrementiert werden, bleibt bei einem 8bit Timer und 16MHz nicht viel Zeit (ca 4ms). Dies ergibt sich aus folgender Berechnung:

1/16MHz * 2^8 = 16us

Dies lässt sich mit Clock Select noch um bis zu den Faktor 1024 erweitern, d.h. bei Timer0 und Timer2 ergibt sich eine maximale Zeit bis zum Overflow von:

1/16MHz * 2^8 * 1024 = 16.384ms

Bei Timer1 sieht dies bei maximalem Clock Select von 1024 (d.h. es wird nur jeder 1024 Takt gezählt) etwas besser aus:

1/16MHz * 2^16 * 1024 = 4.194ms

Für den Schlaf kommt also meist nur Timer/Counter1 in Frage.

Für die Einstellung der Clock Select Bits CS10, CS11 und CS12 im Register TCCR1B bestehen folgende Möglichkeiten:

Ein Beispiel dazu:

#include <avr/sleep.h>
#include <avr/power.h>
 
void setup() {
  Serial.begin(9600);
  // Abschalten der Interrupts fuer die Timerkonfiguration
  cli();
  // Reset Timer1 Control Registers
  TCCR1A = 0; 
  TCCR1B = 0; 
  /* Reset des Zaehlregisters, hier kann ggf. auch ein Offset
  mitgegeben werden um z.B. ganze Sekunden zu schlafen */
  //TCNT1=0; 
  TCNT1=194304; //(setzt den Zaehler vor fuer genau 4s)
  /* Aktivieren des Bits Timer Overflow Interrupt Enable */
  TIMSK1 = (1 << TOIE1);
  // Prescaler Bits CS10 und CS12 fuer ~4s Schlaf setzen
  TCCR1B |= (1<<CS10) | (1<<CS12);
  // Interrupts wieder aktivieren
  sei();
}
 
void loop() {
  Serial.println("Leg mich mal hin..");
  /* Etwas Pause damit die seriellen Daten rausgepumpt werden koennen.
  ist diese Zeit zu kurz, könnte der UART Interrupt im duemmsten Moment abfeuern. */
  delay(40);
  // Setzen des Schlafmodus auf SLEEP_MODE_IDLE
  set_sleep_mode(SLEEP_MODE_IDLE);
  // Aktivieren der Sleep Funktionen
  sleep_enable(); 
  /* Ausschalten der Peripherie, neben dem dass es Strom spart
  sorgt es auch dafür, dass keiner der Komponenten einen Interrupt
  abfeuert der uns ausser Plan zum Aufwachen bringt */
  power_adc_disable();
  power_spi_disable();
  power_timer0_disable();
  power_timer2_disable();
  power_twi_disable();  
  // In Schlafzustand gehen  
  sleep_cpu(); 
  /* Hier gehts nach dem Aufwachen und dem Aufruf der Interrupt Service
  Routine (ISR) weiter, Schlaffunktionen ausschalten  und alle Peripherie wieder einschalten */
  sleep_disable();
  power_all_enable();
  Serial.println("Bin wieder da..");  
}
 
ISR(TIMER1_OVF_vect)
{
  /* Nutze diese Funktion zum Setzen von Flags oder aehnlichem.
  Vermeide wenn immer moeglich blockende Funktionen und deklariere
  alle Variablen die in der ISR verwendet werden also volatile.
  Die Funktion kehrt ohne Rueckgabe zurueck sobald alle Peripherie
  wieder einsatzbereit ist. */
  return;
}
Tip: Länger als 4s mit Timer2 schlafen

Am einfachsten geht das in dem die Anweisungen zum Schlafen in eine eigene Funktion gepackt werden und diese so oft wie gewünscht aufgerufen wird:

#include <avr/sleep.h>
#include <avr/power.h>
 
void setup() {
  Serial.begin(9600);
  // Abschalten der Interrupts fuer die Timerkonfiguration
  cli();
  // Reset Timer1 Control Registers
  TCCR1A = 0; 
  TCCR1B = 0; 
  /* Reset des Zaehlregisters, hier kann ggf. auch ein Offset
  mitgegeben werden um z.B. ganze Sekunden zu schlafen */
  //TCNT1=0; 
  TCNT1=194304; //(setzt den Zaehler vor fuer genau 4s)
  /* Aktivieren des Bits Timer Overflow Interrupt Enable */
  TIMSK1 = (1 << TOIE1);
  // Prescaler Bits CS10 und CS12 fuer ~4s Schlaf setzen
  TCCR1B |= (1<<CS10) | (1<<CS12);
  // Interrupts wieder aktivieren
  sei();
}
 
void loop() {
  Serial.println("Loop Start:");
  go_sleep();
  go_sleep();
  go_sleep();
}
 
ISR(TIMER1_OVF_vect)
{
  /* Nutze diese Funktion zum Setzen von Flags oder aehnlichem.
  Vermeide wenn immer moeglich blockende Funktionen und deklariere
  alle Variablen die in der ISR verwendet werden also volatile.
  Die Funktion kehrt ohne Rueckgabe zurueck sobald alle Peripherie
  wieder einsatzbereit ist. */
  return;
}
 
void go_sleep() {
  Serial.println("Leg mich mal hin..");
  /* Etwas Pause damit die seriellen Daten rausgepumpt werden koennen.
  ist diese Zeit zu kurz, könnte der UART Interrupt im duemmsten Moment abfeuern. */
  delay(50);
  // Setzen des Schlafmodus auf SLEEP_MODE_IDLE
  set_sleep_mode(SLEEP_MODE_IDLE);
  // Aktivieren der Sleep Funktionen
  sleep_enable(); 
  /* Ausschalten der Peripherie, neben dem dass es Strom spart
  sorgt es auch dafür, dass keiner der Komponenten einen Interrupt
  abfeuert der uns ausser Plan zum Aufwachen bringt */
  power_adc_disable();
  power_spi_disable();
  power_timer0_disable();
  power_timer2_disable();
  power_twi_disable();  
  // In Schlafzustand gehen  
  sleep_cpu(); 
  /* Hier gehts nach dem Aufwachen und dem Aufruf der Interrupt Service
  Routine (ISR) weiter, Schlaffunktionen ausschalten  und alle Peripherie wieder einschalten */
  sleep_disable();
  power_all_enable();
  Serial.println("Bin wieder da..");
  delay(50);  
}

Aufwachen mit Watchdog

Eine Möglichkeit um etwas längere Timer zu nutzen ist das Aufwachen mittels bordeigenem Watchdog, dieser ist in der Lage den Mikrokontroller nach 8s wieder zu wecken.

Um den Schlafmodus mit Watchdog vorzubereiten sind folgende Schritte nötig:

  1. Zurücksetzen des Watchdog System Reset Flags (WDRF):
    MCUSR &= ~(1<<WDRF);

    Damit wird Bit 3 des MCUSR Registers auf 0 gesetzt.

  2. Vorbereiten des WDTCSR Registers zum Aktivieren des Watchdogs und Einstellen des Prescalers:
    WDTCSR |= (1<<WDCE) | (1<<WDE);

    Dies erlaubt für Taktzyklen den schreibenden Zugriff auf das WDTCSR Register.

  3. Prescaler Werte (gewünschte Verzögerung) setzen:
    WDTCSR = (1<<WDP1) | (1<<WDP2); // 1 Sekunde

    Kann auch als WDTCSR = WDTO_1S; abgekürzt werden.

  4. Einschalten des Watchdog Interrupts ohne Systemreset:
    WDTCSR |= 1<<WDIE;

    Watchdog Timer Configuration

Das folgende Beispiel zeigt wie man den Arduino standard blink-Sketch mit den Sleep Funktionen statt delay() umsetzt:

#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>
 
#define LED 13
 
void setup() {
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  // Reset Flag zuruecksetzen:
  MCUSR &= ~(1<<WDRF);
  // WDTCSR zum Schreiben vorbereiten
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  /// Setze Prescaler fuer 1s
  WDTCSR =  WDTO_1S; // kurz fuer: WDTCSR = 1<<WDP1 | 1<<WDP2;
  // Aktivieren des WD Interrupt (ohne System Reset)
  WDTCSR |= 1<<WDIE;
}
 
void loop() {
  digitalWrite(LED, (bitRead(PORTB,5) ^ 1));
  Serial.println("Leg mich mal hin..");
  delay(80);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  power_adc_disable();
  power_spi_disable();
  power_timer0_disable();
  power_timer2_disable();
  power_twi_disable();  
  sleep_cpu();
  power_all_enable();
  sleep_disable();
  Serial.println("Bin wieder da..");
}
 
ISR(WDT_vect)
{
  return;
}

ATmega Register

Für viele nützliche Funktionen der ATmega Mikrokontroller ist der direkte Zugriff auf Register des ATmega Chips nötig, die wichtigsten beteiligten Register sollen deshalb hier kurz vorgestellt werden:

  • MCUCR - MCU Control Register. Wird zum Steuern der Brownout Detection (BOD) verwendet.
  • MCUSR - MCU Status Register. Dieses Register enthält Informationen zum Auslöser des letzten MCU Resets.
  • WDTCSR - Watchdog Timer Control Register. Dieses Register steuert sämtliche Aspekte des Watchdogs.
  • PRR – Power Reduction Register. Regelt die Feineinstellung der Power Saving Möglichkeiten.
  • ADCSRA - ADC Control and Status Register A. Steuert das Verhalten des Analog Digital Converters.
  • TCCR1A - Timer/Counter0 Control Register. Steuerung von Timer0.
  • TCCR1B - Timer/Counter0 Control Register. Steuerung von Timer1.
  • TCCR1C - Timer/Counter0 Control Register. Steuerung von Timer2.

Detaillierte Informationen zu allen Registern finden sich im Datenblatt Deines ATmega Mikrokontrollers.

Referenzen

 
arduino/sleep.txt · Last modified: 2014/08/24 16:35 by boxtec
 
 

zum Seitenanfang

Letzte Aktualisierung: © boxtec internet appliances · the better security products