Für einige Anwendungen werden immer wieder längerperiodische Pausen benötigt, währenddessen eine elektronische Schaltung nichts tun soll, z.B. periodische Messungen, LED-Kerzen (sollen nur eine bestimmte Zeit "brennen"), Timelapse-Fotoaufnahmen, etc.
Der Watchdog-Timer (WDT) ist für die Überwachung des Mikrocontrollers entwickelt worden, kann aber auch als Langzeit-Timer verwendet werden. Vorteil bei dieser Umsetzung ist, dass der Mikrocontroller während der Ruhezeit in Sleep-Mode versetzt werden kann und die Schaltung somit Strom sparen kann (v.a. wichtig bei batteriebetriebenen Anwendungen). Beim ATmega ist der WDT für 16 Millisekunden bis 8 Sekunden ausgelegt, so dass längere Intervalle mit mehreren WDT-Zyklen umgesetzt werden müssen.
Hier wird eine möglichst einfache Schaltung verwendet, d.h. beim Arduino können wir die interne LED zu Demonstrationszwecken hernehmen (Beim ATtiny wird an einen beliebigen Pin ebenfalls eine LED mit entsprechendem Vorwiderstand geschaltet).
Der folgende Sketch lässt eine LED einige gewisse Zeit lang Leuchten und geht dann für längere Zeit in eine Ruhephase. Umgesetzt wird dies, indem der Mikrocontroller in den Sleep-Modus versetzt wird und der WDT nach seiner maximalen Überwachungszeit überprüft, ob die LED wieder erneut leuchten soll. Großer Nachteil dieser Schaltung ist, dass der WDT recht unpräzise ist und mit ca. 5%-10% Abweichung von der realen Zeit arbeitet. Somit ergeben sich besonders bei langen Ruhezeiten große Abweichungen der Schaltintervalle. Also ist diese Lösung weniger geeignet, wenn die Intervalle halbwegs genau sein sollen.
#include <avr/sleep.h>
#include <avr/wdt.h>
#define PIN_LED 13
#define LIGHT_TIME 10 // in seconds
#define PAUSE_TIME 7200 // in seconds
#define lmillis() ((long)millis())
bool isLedLit;
long nextAction;
volatile long ledTimer;
void setup()
{
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH);
isLedLit = true;
ledTimer = 0;
cli();
wdt_reset(); // reset watchdog timer
MCUSR &= ~(1 << WDRF); // remove reset flag
WDTCR = (1 << WDCE); // set WDCE, access prescaler
WDTCR = 1 << WDP0 | 1 << WDP3; // set prescaler bits to to 8s
WDTCR |= (1 << WDIE); // access WDT interrupt
sei();
}
void loop()
{
if (isLedLit) {
if (ledTimer >= LIGHT_TIME) {
ledTimer = 0;
digitalWrite(PIN_LED, LOW);
isLedLit = false;
}
} else {
if (ledTimer >= PAUSE_TIME) {
ledTimer = 0;
digitalWrite(PIN_LED, HIGH);
isLedLit = true;
}
}
enterSleepMode();
}
// Watchdog timer interrupt service routine
ISR(WDT_vect)
{
ledTimer += 8;
}
void enterSleepMode()
{
byte adcsra;
adcsra = ADCSRA; // save ADC control and status register A
ADCSRA &= ~(1 << ADEN); // disable ADC
MCUCR |= (1 << SM1) & ~(1 << SM0); // Sleep-Modus = Power Down
MCUCR |= (1 << SE); // set sleep enable
sleep_cpu(); // sleep
MCUCR &= ~(1 << SE); // reset sleep enable
ADCSRA = adcsra; // restore ADC control and status register A
}
Sogenannte RTC-ICs ("real time clock") sind recht präzise arbeitende Zeitgeber, mit denen man einen Mikrocontroller nach einer vorgegebenen Zeit aus dem Sleep-Mode wecken kann. In diesem Experiment wird der RTC PCF8563 verwendet, der einen Arduino mit seiner eigebauten Alarm-Funktion aufwecken soll.
Die Schaltung kann vom Beitrag über Schreiben eines Registers des PCF8563 übernommen werden. Die LED am Pin #7 des RTC wird hier nicht benötigt. Allerdings muss zusätzlich eine Verbindung vom Pin #3 des RTC zum Pin D2 zum Arduino gemacht werden, damit der Interrupt auslösen kann.
Die Library Rtc_Pcf8563
bietet die Möglichkeit, recht einfach den Alarm des PCF8563 zu stellen. Im Arduino kann man dann
durch einen Interrupt den Pin #3 des PCF8563 (=Interrupt-Ausgang; active LOW) überwachen und entsprechend
darauf reagieren.
Der folgende Sketch stellt den Alarm des RTC auf 30 in die Zukunft und zeigt dann in der seriellen Konsole
des Arduino eine entsprechende Meldung, wenn der Alarm auslöst.
#include <Wire.h>
#include <Rtc_Pcf8563.h>
#define PIN_RTC_INT 2
Rtc_Pcf8563 rtc;
volatile byte alarmFlag = 0;
void alarmISR()
{
alarmFlag = 1;
}
void setup()
{
Serial.begin(9600);
pinMode(PIN_RTC_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_RTC_INT), alarmISR, FALLING);
rtc.initClock();
// set current date/time of the RTC
rtc.setDate(9, 6, 10, 0, 20); // day, weekday, month, century, year
rtc.setTime(14, 15, 30); // hours, minutes, seconds
/* set an alarm for 30 secs later.
alarm pin goes low when match occurs
this triggers the interrupt routine
*/
rtc.setAlarm(16, 99, 99, 99); // minutes, hours, days, weekday (99 = no alarm)
}
void loop()
{
Serial.print(rtc.formatDate());
Serial.print(" ");
Serial.print(rtc.formatTime());
Serial.print(" - 0x");
Serial.println(rtc.getStatus2(), HEX);
if (alarmFlag == 1) {
execAlarm();
}
delay(1000);
}
void execAlarm()
{
detachInterrupt(digitalPinToInterrupt(PIN_RTC_INT));
Serial.println("ALARM!");
rtc.clearAlarm();
alarmFlag = 0;
}
Kombiniert mit dem Sleep-Mode des Arduino kann man folgenden Sketch verwenden. Hier wird zunächst die Zeit wie auch der Alarm des RTC gestellt. Danach geht der Arduino in den Sleep-Mode und überwacht den Pin #2. Sobald der Alarm des RTC ausgelöst wird, wird auf dem Interrupt eine fallende Flanke detektiert und der Arduino wird aus dem Sleep-Mode geholt. Um dies anzuzeigen wird die interne LED des Arduino für 2 Sekunden beleuchtet. Außerdem wird der Interrupt sowie der Alarm des RTC ausgeschaltet.
#include <avr/sleep.h>
#include <Wire.h>
#include <Rtc_Pcf8563.h>
#define PIN_RTC_INT 2
#define PIN_LED 13
Rtc_Pcf8563 rtc;
volatile bool isLedLit = false;
void isrAwake()
{
isLedLit = true;
}
void enterSleepMode()
{
attachInterrupt(digitalPinToInterrupt(PIN_RTC_INT), isrAwake, FALLING);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
sleep_disable();
}
void setup()
{
pinMode(PIN_RTC_INT, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT);
rtc.initClock();
// set current date/time of the RTC
rtc.setDate(9, 6, 10, 0, 20); // (2020-10-09) day, weekday, month, century, year
rtc.setTime(12, 15, 30); // (12:15:30) hours, minutes, seconds
// set an alarm for 30 secs later
rtc.setAlarm(16, 99, 99, 99); // minutes, hours, days, weekday (99 = no alarm)
}
void loop()
{
if (isLedLit) {
detachInterrupt(digitalPinToInterrupt(PIN_RTC_INT));
rtc.clearAlarm();
isLedLit = false;
digitalWrite(PIN_LED, HIGH);
delay(2000);
digitalWrite(PIN_LED, LOW);
}
enterSleepMode();
}
Natürlich kann man die Ruhephasen auch von anderen äußeren Einflüssen abhängig machen, z.B. von der Intensität
des Umgebungslichtes. Dafür bieten sich LDRs oder
Fotodioden bzw. Fototransistoren an.
Einige der folgenden Beiträge haben dies schon realisiert:
Geht es weniger um minimalen Stromverbrauch, aber mehr um präsize Zeitintervalle, so bietet sich ein internetfähiger Mikrocontroller (z.B. ESP32) an, der sich mit einem NTP-Service (Network Time Protocol) im WWW verbindet und dort die genau Atomzeit ausliest.
Eine weitere Möglichkeit ist auch, den Mikrocontroller in Sleep-Mode zu versetzen
und dann in bestimmten Intervallen wieder aus dem Tiefschlaf aufwecken zu lassen.
Dies könnte man beispielsweise mit einem NE555 als astabile Kippstufe
oder auch mit einem CD4060-Oszillator-IC realisieren.
Hier muss allerdings die Stromaufnahme des ICs geprüft werden, denn beispielsweise verbraucht der
NE555 ca. 5-10mA, kann aber in der CMOS-Version bis auf 250µA (bei 5V) heruntergedrückt werden.
Beim NE555 hängt die Präzision des Timers jedoch an den externen Komponenten (Widerstand und Kondensator),
die dann vor der Verwendung genau geprüft werden müssen, um möglichst präzise Ergebnisse zu erzielen.
Eine weitere Möglichkeit ist, einen externen, sogenannten "Long Timer, Low Frequency Oscillator" wie den LTC6995 zu verwenden. Dieser besitzt laut Hersteller folgende Merkmale:
Bisher habe ich diese Möglichkeit noch nicht ausprobiert, ein Beitrag ist aber unter LTC6995 – Long Timer verfügbar.
zurück