Um in Projekten, die auf dem Arduino basieren, Strom zu sparen, um eine möglichst lange Batterielaufzeit zu ermöglichen wurden im ersten Teil schon einige Möglichkeiten beschrieben. In diesem Beitrag geht es um den sogenannten "Sleep-Modus". Dies bezeichnet den Zustand des Mikrocontrollers, in dem verschiedene Funktionsblöcke wie Timer, UART, AD-Wandler etc. deaktiviert sind.
Hinweis: Hier wir der Sleep-Modus (auch damit verbundene Strom-Messungen) mit einem Arduino-Board vorgenommen, welches zur Entwicklung von Prototypen vorgesehen ist. Es soll also nur das Einspar-Potential aufgezeigt werden. In Projekten, die wirklich mit jedem mA rechnen müssen, sind daher diese Development-Boards nicht geeignet und man sollte nach besser optimierten Lösungen suchen, die von sich aus schon weniger Strom verbrauchen.
Der Sleep-Modus ist vor allem bei Anwendungen sinnvoll, die zum einen im mobilen Einsatz sind, d.h. von einer Batterie oder Akku abhängen und zum Anderen, die nicht permanent die Rechenleistung des Mikrocontrollers benötigen, z.B. Erfassung von Sensordaten über eine längere Zeit in bestimmten Intervallen. Dabei werden meist relativ wenig Daten in langen Zeitabständen aufgezeichnet, gespeichert oder übertragen. Zwischendurch gibt es für den Mikrocontroller meist nichts zu tun, daher kann man ihn während dieser Zeiten schlafen legen, denn dies kann eine Menge Strom einsparen.
Nach der Initialisierung setup()
des Mikrocontrollers und evtl. angeschlossener externer
Module (LCD, SD-Karte, o.ä.) geht der Prozessor in eine Endlosschleife loop()
, in welcher
die verschiedenen Aufgaben zyklisch abgearbeitet werden. Wenn jedoch gerade nichts zu tun ist geht der
Prozessor schlafen. Ein Interrupt weckt ihn, der Interrupt wird ausgeführt und er setzt seine Arbeit in
der Hauptschleife fort. Das heißt aber auch, dass vor dem Eintritt in den Sleep-Mode die jeweiligen Interrupts
freigegeben werden müssen und auch die entsprechenden Interruptroutinen bereitgestellt werden müssen.
Sonst gibt es im wahrsten Sinne des Wortes ein böses Erwachen oder einen endlosen Dornröschenschlaf,
der nur durch ein Reset des Mikrocontrollers beendet werden kann.
Bevor man den Sleep-Modus aktiviert, sollte man möglichst alle Module abschalten, welche nicht
gebraucht werden, um wertvollen Strom zu sparen. Dazu zählen:
Der Atmel ATmega328P auf dem Arduino Uno verfügt über 6 verschiedene Typen des Sleep-Modus. Je effektiver der Schlafmodus ist, desto weniger Komponenten sind aktiv, wie die folgende Tabelle aus dem Datenblatt des ATmega328P zeigt:
Wenn keine Berechnungen erfolgen sollen, Timer, UART, ADC etc. aber aktiv bleiben sollen, ist dieser Modus geeignet. CPU und Flash stoppen, der Stromverbrauch sinkt auf ca. 0,35 mA. Aus diesem Modus kann jeder Interrupt die CPU wieder wecken. Sie ist nach 6 Takten wieder voll einsatzfähig.
Hier wird zusätzlich zum Idle Mode der Takt für die IO-Komponenten abgeschaltet. Nur noch der AD-Wandler, die externen Interrupts, das TWI und der Watchdog sind funktionsfähig. Der UART, Timer0 und Timer1 sind nicht mehr nutzbar.
Hier werden fast alle Oszillatoren mit Ausnahme von Timer2 gestoppt. Dieser Timer könnte bei Bedarf mit einem externen 32,768kHz-Quarz betrieben werden. Ist er entsprechend konfiguriert, dann bleibt er beim Einschalten des Power-save Modus aktiv.
In diesem Modus werden alle Takte des Controllers gestoppt, nur der Hauptoszillator läuft weiter. Das kostet zwar etwas Energie, aber dadurch ist die CPU nach dem Aufwachen nach nur sechs Takten wieder bereit. Weil alle Oszillatoren gestoppt sind, funktionieren nur noch die asynchronen Komponenten.
Dies ist sozusagen der Winterschlaf des Mikrocontrollers. Es werden alle Oszillatoren gestoppt (außer dem Watchdog-Takt, falls aktiviert). Nur asynchrone Komponenten funktionieren jetzt noch und können den schlafenden Controller wecken.
In diesem Beispiel wird gezeigt, wie man den Arduino über einen externen Interrupt weckt. Der Atmega168 besitzt zwei Interrupts INT0 (liegt an Pin 2) und INT1 (liegt an Pin 3), wobei hier Pin 2 als Trigger mit einem angeschlossenen Taster verwendet wird. Im Sketch muss noch eine ISR (Interrupt service routine) implementiert werden, so dass der Mikrocontroller bei einem LOW-Signal des Tasters wieder aufwacht. Es gibt für externe Interrupts auch noch drei weitere Trigger-Modi (CHANGE, RISING und FALLING), die hier nicht benutzt werden.
Der folgende Sketch verwendet für den Sleep-Mode die Library sleep.h
, die standardmäßig schon
in der Arduino-IDE vorhanden ist.
Der Sketch lässt eine LED für eine gewisse Zeit leuchten (default: 5 Sekunden). Sobald der Trigger (hier: Taster)
ausgelöst wird, wird der LED-Timer wieder zurückgesetzt und die Leuchtdauer verlängert sich. Läuft der LED-Timer
aus, so erlischt die LED und der Arduino fährt in den Power-down-Modus. Wird nun der Trigger wieder ausgelöst,
so erwacht der Mikrocontroller, der LED-Timer wird zurückgesetzt und die LED leuchtet erneut.
#include <avr/sleep.h>
#define PIN_TRIGGER 2
#define PIN_LED 8
#define LIGHT_TIME 5 // in s
#define lmillis() ((long)millis())
bool isLedLit;
byte tiltStatus = LOW, lastTiltStatus = LOW;
int ledTimer = 0;
long lastAction = 0;
void isrAwake(void)
{
detachInterrupt(0);
}
void enterSleepMode(void)
{
attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), isrAwake, LOW);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
sleep_disable();
}
void lightLED()
{
digitalWrite(PIN_LED, HIGH);
isLedLit = true;
ledTimer = LIGHT_TIME;
}
void setup()
{
pinMode(PIN_TRIGGER, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT);
pinMode(13, OUTPUT);
digitalWrite(13, LOW);
lightLED();
lastAction = lmillis();
}
void loop()
{
if (isLedLit && ledTimer <= 0) {
digitalWrite(PIN_LED, LOW);
isLedLit = false;
enterSleepMode();
}
tiltStatus = digitalRead(PIN_TRIGGER);
if (tiltStatus != lastTiltStatus && ledTimer < LIGHT_TIME) {
lastTiltStatus = tiltStatus;
lightLED();
}
if (ledTimer > 0 && lmillis() - lastAction >= 0) {
lastAction = lmillis() + 1000;
ledTimer--;
}
}
Nachdem statt der USB-Spannungsversorgung des PCs eine externe 5V-Spannungsversorgung mit einem 18650 Li-Ionen-Akku über
den Pin Vin
und GND
angelegt wurde, konnte der Unterschied in der Stromaufnahme deutlich gemessen werden:
Will man beispielsweise zu Debug-Zwecken durch Aufrufe von Serial.print()
Ausgaben auf der seriellen Konsole erzeugen, sollte man immer nach jeder Ausgabe eine kleine Verzögerung
von etwa 100ms einbauen, damit die serielle Kommunikation abgeschlossen ist, bevor der Mikrocontroller
schlafen geht!
Der Funktionalität des Watchdog-Timers wurde schon in einem früheren Beitrag besprochen.
Wir verwenden ihn hier als Trigger zum Aufwecken des Mikrocontrollers aus dem Sleep-Mode.
Der Watchdog-Timer (WDT) auf dem Arduino hat einen eigenen internen 128kHz-Oszillator, der es dem WDT ermöglicht, sogar im
niedrigsten Leistungsmodus Power-down zu arbeiten. Allerdings kann der Prozessor bei dieser Art des Aufweckens nur maximal
8 Sekunden am Stück in den Schlaf geschickt werden (Minimum sind 16ms). Durch Iteration kann dies jedoch je nach Anwendung zu
enormer Stromersparnis führen.
Der WDT bietet 3 Möglichkeiten:
WDTON | WDE | WDIE | Modus | Aktion bei Timeout |
---|---|---|---|---|
1 | 0 | 0 | gestoppt | keine |
1 | 0 | 1 | Interrupt-Modus | Interrupt |
1 | 1 | 0 | Systemreset-Modus | Reset |
1 | 1 | 1 | Interrupt- und Systemreset-Modus | Interrupt, anschließend Reset |
0 | x | x | Systemreset-Modus | Reset |
In diesem Beispiel wird alle 2 Sekunden ein WDT-Interrupt ausgelöst, der die LED umschaltet und dann den Mikrocontroller
wieder schlafen legt. Hier muss die globale Variable toggle
als volatile deklariert werden.
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#define LED_PIN 8
volatile bool toggle = true;
// Watchdog imer Interrupt Service Routine
ISR(WDT_vect)
{
toggle = true;
}
void enterSleepMode(void)
{
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
sleep_enable();
power_adc_disable(); // disable analog inputs
power_spi_disable();
power_timer0_disable();
power_timer2_disable();
power_twi_disable();
sleep_mode();
sleep_disable();
power_all_enable(); // enable everything
}
void setup()
{
pinMode(LED_PIN, OUTPUT);
// setup of the WDT
MCUSR &= ~(1 << WDRF); // remove reset flag
WDTCSR |= (1 << WDCE) | (1 << WDE); // set WDCE, access prescaler
WDTCSR = 1 << WDP0 | 1 << WDP1 | 1 << WDP2; // set prescaler bits to to 2s
WDTCSR |= 1 << WDIE; // access WDT interrupt
}
void loop()
{
if (toggle) {
toggle = false;
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
enterSleepMode();
}
}
Ähnlich dem Watchdog kann auch ein normaler Timer verwendet werden, den Sleep-Mode zu beenden.
#include <avr/sleep.h>
#include <avr/power.h>
#define LED_PIN 8
volatile bool toggle = true;
ISR(TIMER1_OVF_vect)
{
toggle = true;
}
void enterSleepMode(void)
{
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer2_disable();
power_twi_disable();
sleep_mode();
sleep_disable();
power_all_enable();
}
void setup()
{
pinMode(LED_PIN, OUTPUT);
// configure timer1
TCCR1A = 0x00;
TCNT1 = 0x0000; // reset timer
TCCR1B = 0x05; // prescaler to 1024
TIMSK1 = 0x01; // enable timer interrupt
}
void loop()
{
if (toggle) {
toggle = false;
digitalWrite(LED_PIN, ! digitalRead(LED_PIN));
enterSleepMode();
}
}
Eine leichtgewichtige Library für das Versetzen des Arduinos in den Tiefschlaf ist die
LowPower.
Der folgende Beispiel-Sketch lässt zunächst eine LED an Pin 8 für eine Sekunde aufleuchten,
dann wieder erlöschen und versetzt den Arduino für 8 Sekunden in Sleep-Mode. Danach wacht
der Arduino wieder auf und der loop()
beginnt von vorne.
#include <LowPower.h>
#define PIN_LED 8
void setup()
{
pinMode(PIN_LED, OUTPUT);
}
void loop()
{
digitalWrite(PIN_LED, HIGH);
delay(1000);
digitalWrite(PIN_LED, LOW);
LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);
}