Table of ContentsArduino SleepWarum 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. EnergiespartechnikenHardware optimierenBei 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 SoftwareDas AufwachenWer 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: 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 InterruptDamit 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 TimerDer ATmega328 verfügt über drei interne Timer:
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 schlafenAm 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 WatchdogEine 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:
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 RegisterFü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:
Detaillierte Informationen zu allen Registern finden sich im Datenblatt Deines ATmega Mikrokontrollers. Referenzen |
|
Letzte Aktualisierung: © boxtec internet appliances · the better security products |