Watchdog Timer

Über den Beitrag

Nach meinen Beiträgen über die 8-Bit Timer und den 16-Bit Timer des Arduinos bzw. des ATmega 328P möchte ich das Thema nun mit dem Watchdog Timer komplettieren. Darum geht es in diesem Beitrag:

Was ist ein Watchdog Timer?

Unter bestimmten Bedingungen kann sich ein Microcontroller genau wie ein PC aufgrund unerwarteter Bedingungen oder Programmierfehlern „aufhängen“. Meistens machen sich Programmierfehler sofort bemerkbar, manchmal aber auch erst später unter speziellen Bedingungen, die man bei der Programmierung nicht berücksichtigt hat. Auch können Fehler in Peripherie, wie z.B. Kommunikationsleitungen oder Sensoren, dazu führen, dass der Sketch auf einmal in einer Programmschleife festhängt.

Einfach ausgedrückt wacht der Watchdog Timer über den Sketch, indem er regelmäßig Lebenszeichen von ihm erwartet. Bleiben diese aus, wird je nach Einstellung und Mikrocontroller, nach einer einstellbaren Zeit ein Reset durchgeführt, ein Interrupt ausgelöst oder beides. Etwas genauer ausgedrückt ist der Watchdog Timer ein automatischer Zähler, der die besagten Aktionen bei Erreichen seines Limits auslöst, wenn er nicht vorher zurückgesetzt wird.

Ein Interrupt vor dem Reset kann zum Beispiel dazu genutzt werden, um

  • sichere Bedingungen bei der gesteuerten Anlage zu schaffen
  • Parameter auf einem EEPROM zum Zwecke der Fehleranalyse zu speichern
  • den Stand des Programmes zu speichern, damit es nach dem Reset an der richtigen Stelle seine Arbeit fortsetzt (Beispiel: Steuerung einer Waschmaschine)

Der Watchdog Timer des Arduino (UNO)

Die folgende Anleitung gilt für die ganze ATmega 48 / 88 / 168 / 328 / 328P Familie. Nachzulesen ist das im Datenblatt ab Seite 60. Prinzipiell gelten die Angaben auch für die anderen Vertreter der AVR Microcontroller. Nur ist nicht jeder Time-Out bei jedem Modell verfügbar. Weitere Angaben zu den Time-Outs gibt es hier.

Einstellungen

Der Watchdog Timer des Arduino UNO bzw. des ATmega 328P wird nicht wie die anderen, zuvor besprochenen Timer über den Systemtakt gespeist, sondern über einen separaten 128 kHz Oszillator. Dadurch sind die Sketche leichter übertragbar.

Die Einstellungen des Watchdog Timers nehmt ihr im Watchdog Timer Control Register WDTCSR vor:

Das Watchdog Timer Control Register
Das Watchdog Timer Control Register
  • WDIF: Watchdog Interrupt Flag – das Bit wird bei Time-Out des  Watchdog Timers gesetzt, wenn zuvor auch WDIE gesetzt wurde
  • WDIE: Watchdog Interrupt Enable – setzt ihr dieses Bit, dann löst ein Time-Out des Watchdog Timers einen Interrupt aus
  • WDPx: Watchdog Timer Prescaler – mit diesen Bits legt ihr die Periode des Watchdog Timers entsprechend der nachfolgenden Tabelle fest
  • WDCE: Watchdog Change Enable – wollt ihr die Watchdog Timer Einstellungen ändern, dann müsst ihr zunächst dieses Bit setzen. Die Prozedur wird noch erklärt.
  • WDE: Watchdog System Reset Enable – setzt Ihr dieses Bit, dann löst ein Time-Out des Watchdog Timers ein Reset des Microcontrollers aus.
Tabelle 1: Prescaler Einstellungen des Watchdog Timers
Tabelle 1: Prescaler Einstellungen des Watchdog Timers

Einrichten des Watchdog Timers – Schritt für Schritt

Wir fangen ganz einfach an. Der folgende Sketch bleibt in einer while-Schleife hängen, da ich das Inkrementieren meines Zählers i „vergessen“ habe.

void setup(){
  Serial.begin(9600);
  Serial.println("Program with endless loop...");
  Serial.println("Sketch starts...");
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
}

void endlessLoop(){
  int i = 0;
  while(i < 5){
    //do nothing
  }
}

 

Watchdog Timer mit System Reset

Nun soll ein Watchdog eingerichtet werden, der den Sketch aus der Schleife befreit und den Microcontroller neu startet:

void setup(){
  Serial.begin(9600);
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
  asm("WDR"); // this will not be executed
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  asm("WDR"); // watchdog reset
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset
  sei();
}

void endlessLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 5){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    seconds++;
  }
}

 

Die Einrichtung des Watchdog Timers läuft nach dem folgenden Schema:

  • cli() schaltet alle Interrupts ab. Das ist notwendig, da die Einrichtung des Watchdog Timers gestört werden könnte
  • asm("WDR"); ist eine Assembler Anweisung für den Watchdog Reset; wir werden bald sehen, wie man eine weniger kryptische Funktion einsetzen kann.
  • WDTCSR |= (1<<WDCE) | (1<<WDE); leitet die Änderung der Watchdog Parameter ein. Es ist wichtig, dass „|=“ und nicht „=“ verwendet wird. Nach dieser Initialisierung muss die eigentliche Änderung innerhalb der nächsten 4 Taktzyklen erfolgen.
  • WDTCSR = (1<<WDE) | (1<<WDP3); bedeutet: Reset ist aktiviert und der Watchdog Timer ist auf vier Sekunden eingestellt (siehe Einstellungstabelle).
  • sei(); lässt Interrupts wieder zu.

Der Watchdog Reset am Ende der Loop-Schleife (Zeile 11) würde den System Reset verhindern. Diese Anweisung ist aber unerreichbar.

Und so sieht die Ausgabe am seriellen Monitor aus:

Watchdog Timer Reset
Watchdog Timer Reset

Der Watchdog funktioniert – alle vier Sekunden startet der Arduino neu. Theoretisch dürfte der Sketch „4 Sekunden“ schon gar nicht mehr ausgeben. Der Watchdog Timer ist aber weniger exakt als die anderen Timer. So gibt das Datenblatt die Time-Out Werte auch nur als „typisch“ an.

Watchdog Timer mit System Reset und Interrupt

Der folgende Sketch enthält immer noch die Endlos-Schleife, die zum Watchdog Time-Out führt. Hier führt der Time-Out aber nicht nur zum Reset, sondern vorher wird noch ein Interrupt ausgelöst. Dieser wiederum ruft die ISR (Interrupt Service Routine) auf. Dazu muss ihr „WDT_vect“ übergeben werden.

void setup(){
  Serial.begin(9600);
  Serial.println("*******************");
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
  asm("WDR"); // Watchdog Reset will not happen
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  asm("WDR");
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (1<<WDE) | (1<<WDP3); // 4s / interrupt, system reset
  sei();
}

void endlessLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 5){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    seconds++;
  }
}

ISR(WDT_vect){
  Serial.println("....but I will print this before I reset soon");
}

 

Und so sieht dann die Ausgabe aus:

Watchdog Timer Reset mit vorherigem Interrupt
Watchdog Timer Reset mit vorherigem Interrupt

Wie ihr erkennt, läuft der Sketch noch vier Sekunden weiter, bevor der Reset ausgelöst wird. Der Interrupt gewährt einen Aufschub um eine Watchdog Periode. Bei der Auswahl der Watchdog Timer Periode müsst ihr also darauf achten, dass die vor dem Reset auszuführenden Aktionen auch in eine Periode „hineinpassen“.

Vereinfachte Schreibweise mit wdt.h

Die kryptische Einrichtung des Watchdog Timers könnt ihr durch Einbinden der Bibliothek „avr/wdt.h“ etwas angenehmer gestalten:

  • Um das Abschalten der Interrupts durch cli(); braucht ihr euch nicht mehr zu kümmern.
  • Aus asm("WDR"); wird wdt_reset();.
  • Die Initialisierung durch das Setzen von WDCE und die nachfolgende Prescale Festlegung wird ersetzt durch wdt_enable(WDTO_XYZ);, wobei ihr WDTO_XYZ der Tabelle 1 entnehmt.
  • Einzig für das Aktivieren des Watchdog Interrupts hat man komischerweise keine Funktion vorgesehen. Dafür müsst ihr immer noch die Binäroperation WDTCSR = (1<<WDIE); verwenden.
  • Der Watchdog Interrupt ohne Systemreset lässt sich nicht mit den wdt.h Funktionen einrichten.
#include <avr/wdt.h>
void setup(){
  Serial.begin(9600);
  Serial.println("*******************");
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
  wdt_reset(); // Watchdog Reset will not happen
}

void watchdogSetup(void){
  wdt_reset();
  wdt_enable(WDTO_4S); // 4s / System Reset
  WDTCSR = (1<<WDIE); // interrupt
}

void endlessLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 5){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    seconds++;
  }
}

ISR(WDT_vect){
  Serial.println("....but I will print this before I reset");
}

 

Ein „OK“-Sketch

Der Vollständigkeit halber hier noch ein Sketch mit einer Schleife, die rechtzeitig beendet wird. Dazu habe ich in Zeile 30 ein i++ eingefügt und i<4 als Abbruchbedingung für die while-Schleife.

#include <avr/wdt.h>
void setup(){
  Serial.begin(9600);
  Serial.println("*******************");
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  finiteLoop();  
  Serial.println("Now I will print this...");
  wdt_reset();
}

void watchdogSetup(void){
  wdt_reset();
  wdt_enable(WDTO_4S);
  //wdt_enable(WDTO_2S);
  WDTCSR = (1<<WDIE);
}

void finiteLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 4){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    i++;
    seconds++;
  }
}

ISR(WDT_vect){
  Serial.println("....but not this - as long there's no failure");
}

 

So sieht dann die Ausgabe aus:

Wenn der Watchdog nicht beißt....
Wenn der Watchdog nicht beißt….

Spaßeshalber könnt ihr mal Zeile 18 aus- und Zeile 19 entkommentieren und schauen, wie sich die Ausgabe verändert.

Abschalten des Watchdog Timers

Wenn ihr avr/wdt.h eingebunden habt, dann könnt ihr den Watchdog Timer einfach mit wdt_disable(); abschalten. „Zu Fuß“ geht das so:

void watchdogOff(){
  cli();
  asm("WDR");
  MCUSR &= ~(1<<WDRF);
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = 0x00;
  sei();
}

 

Was euch dabei vielleicht auffällt, ist die Anweisung MCUSR &= ~(1<<WDRF);. MCUSR ist das Microcontroller Status Register und WDRF ist das Watchdog System Reset Flag Bit. Letzteres überschreibt das WDE Bit in WDTCSR und muss deswegen zwingender Maßen zuerst gelöscht werden.

Watchdog Timer „immer an“ mit dem Fuse Bit WDTON

Ihr könnt – sofern ihr ein Programm wie Atmel Studio mit zugehörigem Programmer habt – den Watchdog Timer auch dauerhaft aktivieren, indem ihr das Fuse Bit WDTON setzt. In diesem Fall ist der reine Reset Modus ohne Watchdog Interrupt aktiviert. Ihr könnt also nur den Time-Out ändern.

Fuse Bit Programmierung in Atmel Studio
Fuse Bit Programmierung in Atmel Studio

Watchdog Timer mit Interrupt und ohne Reset

Die Variante mit Watchdog Interrupt, aber ohne System Reset, lässt sich, wie schon zuvor erwähnt, nicht in Verbindung mit wdt_enable(); realisieren. Hier müsst ihr deshalb wieder die binären Operationen verwenden. Dazu folgendes Beispiel:

#include <avr/wdt.h>

void setup(){
  Serial.begin(9600);
  watchdogSetup();
}

void loop(){
  Serial.println("Here's the loop!");
  delay(1000);
}

void watchdogSetup(void){
  cli();
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (1<<WDP3);  // 4s / interrupt, no system reset
  sei();
}

ISR(WDT_vect){
  Serial.println("Greetings from the ISR!");
}

 

Watchdog Timer Interrupt ohne Reset
Watchdog Timer Interrupt ohne Reset

Diese Einstellung des Watchdog Timers ist sinnvoll um periodisch Dinge erledigen zu lassen, ohne dabei auf zeitabhängige Vorgänge in der loop-Schleife Rücksicht nehmen zu müssen. Vielleicht wartet z.B. in der loop-Schleife ein Taster auf Betätigung und über den Watchdog wird parallel regelmäßig ein Sensor ausgelesen. Dinge der Art eben, die sonst schnell komplex werden können, wenn man sie nebeneinander in der loop-Schleife unterbringen möchte.

Aufwecken mit dem Watchdog Timer

Der Watchdog Timer kann auch verwendet werden, um den Microcontroller aus dem Schlaf zu wecken. Eine mögliche Anwendung wäre zum Beispiel die Ansteuerung eines Sensors, der alle paar Sekunden eine Messung durchführen soll. Ist zwischendurch nichts zu tun, kann man den Microcontroller auch gerne in den Schlaf schicken, um (Batterie-) Strom zu sparen. Wie das prinzipiell geht, seht ihr im folgenden Sketch:

#include <avr/wdt.h>
#include <avr/sleep.h>
const int ledPin = 12;

void setup(){
  pinMode(ledPin,OUTPUT);
  watchdogSetup();
}

void loop(){
  digitalWrite(ledPin,HIGH);
  delay(500);
  digitalWrite(ledPin,LOW);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // chose power down modus
  sleep_mode(); // sleep now!
  sleep_disable(); // disable sleep after wake up
}

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
}

 

Länger als 8 Sekunden Schlaf geht mit der Methode nicht. Bei Bedarf könntet ihr aber noch einen Zähler einbauen, der dafür sorgt, dass die gewünschte Aktion erst nach dem x-ten Aufwachen ausgelöst wird. Ansonsten geht es wieder in den Schlaf.

Watchdog am Arduino Pro Mini und Arduino Nano

Die Problematik mit dem Reset

Verschiedene Arduino Pro Mini Boards

Der folgende Sketch funktioniert am Arduino Uno ohne Probleme. Alle vier Sekunden löst der Watchdog Timer einen Reset aus. Probiert ihr denselben Sketch auf dem Arduino Pro Mini oder Arduino Nano, dann stellt ihr fest, dass der Sketch nur einmal durchläuft und dann hängt.

#include <avr/wdt.h>
const int ledPin = 12;

void setup(){
  pinMode(ledPin,OUTPUT);
  watchdogSetup();
}

void loop(){
  digitalWrite(ledPin,HIGH);
  delay(1000);
  digitalWrite(ledPin,LOW);
  delay(500);
  endlessLoop(); 
  wdt_reset(); // will not happen 
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset
  sei();
}

void endlessLoop(){
  int i = 0;
  while(i < 5){
    delay(1000);
  }
}

 

Ein Reset durch den Watchdog Timer ist auf dem Arduino Pro Mini und dem Arduino Nano nicht möglich. Das Problem ist dabei der Bootloader. Wie man das Problem durch einen Wechsel des Bootloaders löst, ist hier beschrieben. Es gibt aber auch zwei andere, simple Alternativen, die ich im Folgenden beschreiben werde. Beide beruhen darauf, dass der Watchdog mit Interrupt, aber ohne Reset eingestellt wird. Der Reset erfolgt dann „manuell“ in der ISR.

Workaround 1: Interrupt mit Hardware Reset

Ein LOW Signal am Reset Pin des Arduino Pro Mini initiiert einen Hardware Reset. Dazu könnt Ihr folgende Schaltung und folgenden Sketch verwenden:

Reset-Schaltung für den Arduino Pro Mini
Reset-Schaltung für den Arduino Pro Mini
#include <avr/wdt.h>
const int ledPin = 12;
const int resetPin = 9;

void setup(){
  pinMode(ledPin,OUTPUT);
  pinMode(resetPin,OUTPUT);
  digitalWrite(resetPin,HIGH);
  watchdogSetup();
}

void loop(){
  digitalWrite(ledPin,HIGH);
  delay(1000);
  digitalWrite(ledPin,LOW);
  delay(500);
  endlessLoop(); 
  wdt_reset(); // will not happen 
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset
  sei();
}

void hardwareReset(){
  digitalWrite(resetPin,LOW);
}

void endlessLoop(){
  int i = 0;
  while(i < 5){
    delay(1000);
  }
}

ISR(WDT_vect){
  hardwareReset();
}

 

Vertauscht ihr in dem Sketch Zeile 7 und Zeile 8, dann könnt ihr auf den 1 kOhm Widerstand zwischen Pin 9 und RESET verzichten. Ohne das Vertauschen reicht die kurze Zeit, in der Pin 9 auf OUTPUT steht und noch nicht HIGH ist aus, um ein Reset Signal zu geben.

Workaround 2: Interrupt mit Software Reset

Für den zweiten Workaround ersetzt ihr die Funktion hardwareReset() durch eine Software Resetfunktion: void (*resetFunc)() = 0. Dann ruft ihr die Funktion resetFunc(); in der ISR auf. Oder ihr schreibt lediglich direkt in die ISR: asm volatile ("jmp 0");. Dafür ist dann keine weitere Verdrahtung notwendig.

Der Nachteil an dieser Methode ist jedoch, dass kein echter Reboot stattfindet, sondern lediglich ein Programmneustart. Der Microcontroller springt auf die Adresse Null. Alle Registerinhalte, Pinstatus, usw. bleiben erhalten, wie sie sind und können so für unerwünschte Effekte sorgen. Gegebenenfalls müsst ihr also noch eine Funktion ins Setup einfügen, die für einen sicheren Ausgangszustand sorgt.

Der Watchdog am ESP8266

ESP8266 Ausführungen
ESP8266 Ausführungen

Im Vergleich zu den AVR Microcontrollern ist der Watchdog Timer des ESP8266 grundsätzlich anders konzipiert. Zunächst einmal verfügt er über einen Hardware- und einen Software Watchdog Timer. Ohne weiteres Zutun ist der Software Watchdog Timer automatisch aktiviert. Ladet den folgenden Sketch auf euren ESP8266 und schaut auf dem seriellen Monitor (115200 Baud einstellen!) was passiert.

void setup() {
  //ESP.wdtDisable();
}

void loop() { 
  while(1){}             
}

 

Watchdog Timer Software Reset - Ausgabe am seriellen Monitor
Software Reset – Ausgabe am seriellen Monitor

Wie ihr seht, startet der ESP8266 ca. alle drei Sekunden neu. Bildlich gesprochen „füttert“ der Software Watchdog den Hardware Watchdog, damit dieser nicht „beißt“. Um den Software Watchdog auszuschalten, entkommentiert die Funktion ESP.wdDisable(); und schaut was passiert:

Watchdog Timer Hardware Reset - Ausgabe am seriellen Monitor
Hardware Reset – Ausgabe am seriellen Monitor

Nun gibt es ca. alle acht Sekunden einen Reset und auf dem seriellen Monitor erscheint „wdt reset“. Das war der Hardware Watchdog. Um zu verhindern, dass der Watchdog beißt, könnt ihr ihn füttern, indem ihr in die while Schleife ein ESP.wdtFeed(); einfügt. Ihr könntet allerdings genauso gut ein delay(x ms); oder irgendeine andere Funktion einfügen.

Limitierte Einstellungen

Möglichkeiten, Hardware und Software Watchdog Timer am ESP8266 zu konfigurieren, gibt es praktisch nicht. Im Grunde könnt ihr lediglich zwischen den beiden Watchdog Varianten wählen. Habt ihr den Software Watchdog ausgeschaltet, dann könnt ihr ihn mit ESP.wdtEnable(x); wieder aktivieren. x ist dabei ein Integerwert, der zwingender Maßen übergeben werden muss. Das weckt die Hoffnung, dass man damit Time-Out Periode wählen könnte. Das ist leider nicht der Fall. Egal, ob ihr z.B. ESP.wdtEnable(0); oder ESP.wdtEnable(4000); oder ESP.wdtEnable(WDTO_4S); wählt, der Effekt ist immer derselbe.

Ein komfortableren Watchdog Timer selber kreieren

Wenn ihr einen Watchdog Timer braucht, der ähnliche Funktionen wie die AVR Watchdog Timer besitzt, dann könnt ihr ihn mithilfe der Library Ticker.h mit wenigen Programmzeilen selbst erzeugen. Hier gibt es ein Beispiel.

Danksagung

Den Wachhund im Beitragsbild habe ich von Manfred Richter auf Pixabay, die Stoppuhr stammt von OpenClipart-Vectors, auch Pixabay.

 

22 thoughts on “Watchdog Timer

  1. Hi,
    feine Sache der Wachhund.
    Gibt es „ISR(WDT_vect){}“ auch für den ESP32 um vor dem Reboot eine serielle Ausgabe zu senden?
    Danke.

  2. Hallo,

    vielen vielen Dank für auch diese richtig gute Beschreibung.
    Ich habe es inzwischen bei mir mal angefangen einzubauen, mir ist nur eine Kleinigkeit aufgefallen, weiß aber nicht wie relevant dies ist:

    watchdog_off_function
    void watchdogOff(){

    müsste nicht noch ein „void“ in die Klammer?
    also so:
    void watchdogOff(void){

    Aber vieleicht habe ich das Datenblatt aus Falsch verstanden, ich bin leider noch nicht so Fit, würde mich über eine Antwort freuen.

    Viele Grüße

    1. „void“ heißt leer. Hat man keinen Rückgabewert bei einer Funktion, dann muss man es angeben, so wie bei „void loop(){}“. Als Übergabeparameter an eine Funktion kann man es normalerweise weglassen. Es gibt Stimmen, die sagen, man sollte es grundsätzlich auch in solchen Situationen verwenden, um zu verdeutlichen, dass man wirklich nichts übergeben will. Hier:
      void watchdogSetup(void){
      hätte ich es auch weglassen können.
      Probier’s einfach aus!

  3. Vielen Dank, auch für dieses Tutorial,
    habe nur eine grobe Vorstellung, wie viel Arbeit und Aufwand das macht.

    Ich bin Anfänger und konnte meinen Nano von ca. 12mA auf ca. 4-5mA bringen.

    Nun schläft der Nano 0,125s und läuft dann durch das Programm.
    Über Zählerabfragen lasse ich ihn dann die verschiedenen Aufgaben erledigen.

    Warum?
    Es gibt Handys, die blinken, wenn ein Anruf bzw. eine SMS angekommen ist.
    Mein kleines altes Handy hat das nicht, …

    Aber diese Funktion ist auch für mich sehr brauchbar, …

    Also: die Idee.
    Das Handy irgendwo drauflegen, und der Rest geht von alleine.

    Durch das Handygewicht und einem kleinen Schalter wird der Nano eingeschaltet.
    – Abfrage eines lichtempfindlichen Widerstandes: alle 0,375sec
    – Abfrage der Akkuspannung: alle paar Minuten
    – blinken der LEDs: 0,125sec An / 0,75sec Aus

    Alles im Rhythmus von x-mal 0.125sec. (oder so)

    Sicherlich, wäre ein ATTiny 85 + SteppDown Wandler auch eine gute Lösung,
    bin aber daran gescheitert.

    Nochmal vielen Dank,
    Vim

    1. Hi Vim, vielen Dank für den Kommentar. Wenn du den Strom weiter seken möchtest, könntest du dir überlegen, die LED auf dem Nano zu entfernen. Ansonsten viel Spaß bei der Umsetzung. Hattest du noch eine Frage dazu?
      Und bzgl. StepDown Konverter und ATtiny85: kann man dir da noch helfen? Dazu wäre hilfreich zu wissen, was genau das Problem war. VG, Wolfgang

      1. Hi,
        vielen Dank für die Antwort.

        Mit dem Stromverbrauch des Nanos, nun nur noch 4-5mA => Akku wechsel alle 2- 4 Wochen
        komme ich gut zurecht.
        Der ‚halb schlafende‘ Nano ist seit Tagen am Werkeln und er läuft fein, keinen Fehlalarm‘ und auch kein falsches ‚da war nichts‘.

        Das eigentliche Problem, nun ich bin Anfänger und alles ist etwas unsicher, unklar und irgendwo tauchen immer Fragen auf, die lange brauchen, bis sie geklärt sind.
        Und vieles was Weltweit einfach so läuft, ging bei mir einfach nicht.

        Habe mit drei Nanos mit Bootloader gekauft, aber die IDE gab nur Fehlermeldungen raus.
        Wieder schlau gemacht, und über einen Mega den Bootloader auf den Nano aufgebracht.

        Mit dem Nano habe ich dann die Schaltung aufgebaut und war über den Stromverbrauch überrascht.
        (Akku wechsel häufiger als einmal pro Woche.)

        Wieder schlau gemacht, und einen Attiny85 bestellt.
        Und hier kommt jetzt alles zusammen, die Pinbezeichnungen, der Bootloader, die verschiedenen Optionen des Attiny85, der Nano als ISP-Programmer, das Steckbrett, das nicht immer sauber verbindet, die IDE, die mehrere Versuche braucht um das COM wiederzufinden, wenn der USB Stecker zwischendurch gezogen wurde. Und die Liste ist noch viel länger.

        Aber die Nano-Schaltung war ja schon fast fertig.
        Wieder schlau gemacht, und Sleep gefunden.
        Eines neues Programm für den Nano geschrieben (also das dritte Arduino Programm in meinem Leben) und nun ist alles soweit gut, mit dem Nano.

        Aber, nun habe ich einen ATtiny85 hier herumliegen und irgendwie lacht er mich an.

        Meine Vorstellung vom Endergebnis ist klarer und praktischer geworden, und ich werde noch einen Versuch mit dem ATtiny85 machen. Mit einem Akku, und USB-Lademodul, dann kann ich auch ein elegantes Gehäuse nutzen und nicht noch einmal einen 4fach Halter für 1865 Akkus.

        Nun habe ich eine Frage, rund um den ADC beim ATtiny85.
        Ich habe mir das Datenblatt angeschaut, aber nicht verstanden bzw. nichts Passendes gefunden.
        (Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf).

        Im Betrieb mit einem Li-Akku wird die VCC von 4,2V auf 3,7V fallen.
        Verändern sich der ADC-Wert bei gleicher Messspannung, durch die veränderte Versorgungsspannung oder bleiben der ADC-Wert gleich z. B. durch die interne Vref?

        Bleibt der ADC-Wert gleich, dann ist das Programm + Elektronik + Austesten einfacher.

        Wenn sich der ADC-Wert ändert, würde ich gerne ADC0 (PIN 1) freischalten. Wie geht das?

        Vielen Dank und eine gute Zeit
        Vim

        1. Hi Vim,

          ja, der ADC Wert bezieht sich immer auf die Referenzspannung. Standardreferenz ist VCC, sprich wenn die Spannung am analogRead Pin = VCC ist, dann ist der analogRead-Wert 1023. Alternativ kannst du die interne 1.1 Volt oder 2.56 V Referenz wählen. Aber dann sind das die maximalen Spannungen, die du messen kannst.

          ADC0 lässt sich im Prinzip benutzen, allerdings muss man dafür die Resetfunktion deaktivieren. Das Problem ist, dass man die Resetfunktion braucht, um den ATtiny85 zu programmieren. Das ist also keine so gute Idee. Mit einem Digispark geht das, aber das würde jetzt zu weit führen.

          Diesen Artikel würde ich dir noch ans Herz legen, wenn du mit dem Attiny85 etwas machen willst:

          https://wolles-elektronikkiste.de/attiny-mit-arduino-code-programmieren

          Grüße, Wolfgang

          1. Hi Wolfgang,
            vielen Dank für die Infos.

            Habe einen Workaround gefunden für den fehlenden ADC-Kanal.

            Mit einem Powerbank-Modul (TP5400) habe ich
            – das Laden des Akkus über einen USB-Eingang
            – permanent 5V Ausgang für den ATtiny85, also gleichbleibende Versorgungsspannung
            – Indirekt wieder einen freien ADC-Kanal, zu erfassen des Aufladevorganges.

            Leider brauche ich manchmal etwas länger, aber dann …

            Deine Seite ‚ATtiny mit Arduino Code programmieren‘ habe ich schon am Anfang meiner Versuche mit dem ATtiny85 gefunden, hatte aber zu wenig Grundlagen um es einzusortieren.

            Die ersten Versuche Blinken, Poti-Abfragen mit Attiny85 sind erfolgreich verlaufen.

            Nun kommt meine Faulheit:
            Laut Datenblatt kommen sie mit, 300µA im Betrieb aus, im Tiefschlaf (Sleep-Mode) sogar nur mit 0,1µA (bei 1,8V). Also theoretisch.

            Aber für den ATtiny85 habe ich noch kein Beispiel gefunden.
            Wenn es wie beim Nano ist, wäre der Code schon fast fertig, das wäre gut.
            Durch die festen 5V kann ich die zu erwartenden ADC-Wert auch berechnen.
            Denn ohne seriellen Monitor sehe ich noch älter aus als im Spiegel.

            Nochmal vielen Dank und auch ein schönes Wochenende
            Vim

  4. Hallo, danke sieht fast so aus wie ich es bräuchte?

    Timer, der eine Relais nach einer bestimmten Zeit ausschaltet und damit den gewählen Vorgang stoppt.
    Zeiteinstellung binär mit 8 Bits
    7,5 Minten = Jumper 1
    15 Minuten= Jumper 2
    30 Minuten=Jumper 3
    1 Stunde = Jumper 4
    2 Stunden= Jumper 5
    4 Stunden= Jumper 6
    8 Stunden = Jumper 7
    16 Stunden = Jumper 8
    Mit den obersten 2 Bits kann ich die Wahlzeit dann genügend genau voreinstellen-z.B. 24 Stunden = Jumper 7+8 gesetzt.
    Die abgelaufene Zeit könnte man ja per BCD-LED’s anzeigen, alle 7,5 Minuten ändert sich die Anzeige.

    Verzeihung, wenn das hier nicht ganz hereinpasst!

    1. Hallo, theoretisch kannst du natürlich den Watchdog-Timer x-mal auslösen lassen und dann dein Relais schalten. Ist nicht unbedingt im Sinne des Erfinders des Watchdog Timers, aber würde gehen. Alternativ könntest du die anderen Timer des ATmega328P in ähnlicher Form nutzen:
      https://wolles-elektronikkiste.de/timer-und-pwm-teil-2-16-bit-timer1
      Aber alle Timer lösen maximal in wenigen Sekunden aus und nicht in Stunden. Du müsstest immer wieder zusammenstückeln.
      Weitere Möglichkeiten zur Zeitmessung:
      1) Zeitabfrage über millis()
      2) Externe Timer, die für lange Zeiträume konzipiert sind, z.B: https://wolles-elektronikkiste.de/ltc6995-long-timer-low-frequency-oscillator
      3) Ein RTC Modul: https://wolles-elektronikkiste.de/ds3231-echtzeituhr
      4) Die Zeit über ein DCF77 Modul ermitteln: https://wolles-elektronikkiste.de/dcf77-funkuhr

      Vielleicht kommt ja irgendetwas davon in Frage.

      VG, Wolfgang

  5. Kleine Ergänzung zum ATTiny85 von Digispark in Verbindung mit der Arduino IDE :
    Wenn man den Watchdog benutzen möchte kann man nach obigen Rezepten verfahren, muss aber am Programm Anfang folgende Definition ergänzen:
    #define WDTCSR _SFR_MEM8(0x21) // 0x21 ist die Position des WDT im Speicher des ATTiny85.

    Dann erst klappt das Kompilieren.

    Grund: Über den Boardverwalter wird die Bibliothek von Digistump eingebunden.
    Dort existiert leider keine Definition des WDTCSR Registers.
    Das zu finden hat echt Zeit gekostet…..

    1. Hallo, vielen Dank, dass du die Erfahrung teilst und so anderen die Fehlersuche ersparst!

    2. Hi, danke für das Tutorial, hätte ich früher lesen sollen, inkl. Kommentare 😉
      Vielleicht noch als Ergänzung zu Detlef, beim ATTiny heißt das Register WDTCR (ohne das S). Zu finden im Datenblatt Kapitel 7.9.1.
      Grüße
      Marius

  6. Schönes Tutorial, jetzt bin ich endlich mal kurz davor das zu verstehen.
    WDTCSR |= (1<<WDCE) | (1<<WDE);
    WDTCSR = (1<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset

    Warum muss hier WDE zweimal aufgerufen werden? und könnte man beide Zeilen auch in einer Schreiben?

    1. Eine Frage hätte ich noch, wo sind die Werte, die hinter zB WDCE stehen definiert? Soweit ich das verstehe wird das Bit ja an einer entsprechenden Stelle gesetzt/geshiftet und zwar am Wert WDCE, oder?

      1. Zur ersten Frage: Man muss diese Sequenz einhalten. Das führt in die tiefen der Register des ATmega328P. Im Datenblatt ist das beschrieben. Es würde hier etwas zu weit führen, das zu erklären.

        Zur zweiten Frage:
        Es gibt diverse Dateien, die automatisch mit eingebunden werden ohne dass man es merkt, z.B. bei Verwendung des ATmega328P:
        C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr\iom328p.h

        Und da finden sich die ganzen Definitionen, z.B.:

        #define WDP0 0
        #define WDP1 1
        #define WDP2 2
        #define WDE 3
        #define WDCE 4
        #define WDP3 5
        #define WDIE 6
        #define WDIF 7

        Das heißt ein (1<<WDCE) heißt schlicht: (1<<4), sprich setze das Bit 4.

  7. Klasse Tutorial, aber in Tabelle 1 mit den Prescaler-Einstellungen befindet sich ein Fehler. Bei 16000 Oszillatorzyklen beträgt der timeout 125 ms oder 0.125 s, aber nicht 0.125 ms.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert