momentan versuche ich, einen ATtiny13 schlafen zu legen und per Watchdog zu wecken. Im Prinzip funktioniert das auch, nur dass der Watchdog doppelt so lange läuft, wie erwartet.
Der ATtiny13 läuft mit internem Oszillator (9,6MHz) und CLKDIV8, was für den Watchdog aber keine Rolle spielen sollte.
Der Watchdog wird wie folgt eingstellt, wie ich es in einem Tutorial gefunden habe:
Hallo,
eventuell solltest du den WDTIE erst aktivieren bevor du ihm die Prescaler einstellst.
Aber bitte nicht böse sein falls es nicht stimmt, ist bei mir schon etwas her und habe es seit damals nicht mehr verwendet (und dann auch noch fremder, angepasster, Arduino-Code):
Interessant, dass der Tiny13 WDCE braucht für den Prescaler. Ist nicht so beim Tinyx5 - nur nebenbei.
Vielleicht stört ihn
WDTCR = (1 << WDCE);
WDTCR = 1 << WDP3;
und man sollte
WDTCR |= (1 << WDCE);
WDTCR |= (1 << WDP3);
nehmen?
Zumindest die beim Prescaler, denn ich könnte mir vorstellen, dass er das gesetzte WDCE noch sehen will, das überschreibt man aber bei der Zuweisung.
Rätselhaft bleibt aber, warum er dann die längste Zeit nimmt.
Korrekt, da (Zeile 3) gehört ein |= hin, ansonsten überschreibt man das Register komplett, und setzt nicht nur das eine Bit, was man setzen möchte.
Ebenso sind die Klammern in Zeile 2 ohne Funktion.
Das Tutorial ist doof.
Ja! Dein Bild zeigt es doch: WDCE braucht man nur um WDE auszuschalten.
Beim Tiny13 steht zusätzlich drin, dass man es auch fürs Ändern von WDPx setzen muss.
@Joni
Wie stellst du fest, dass der Attiny 9-10 us schläft? Der Watchdog ist auf 4s eingestellt und du hast den Watchdog Interrupt auch aktiviert, d.h. für einen Reset des Attinys muss der Watchdog zweimal auslösen, sprich er läuft dann 8s nominal was mit den beobachteten 9-10s recht gut korreliert.
Stellst du das Aufwachen über den Watchdog-Reset in deinem Test fest? Dann würde es passen und dein Denkfehler liegt darin begründet, dass du den Watchdog-Interrupt "vergessen" hast.
Auch denkbar, dass du im Watchdog-Interrupt einen Pin toggelst. Hast du bedacht, dass du das Interrupt-Bit des Watchdogs nach dem Auftreten des Interrupts erneut setzen musst? Auch dann würden nämlich die 9-10s passen zu deiner Beobachtung.
Korrekt, da (Zeile 3) gehört ein |= hin, ansonsten überschreibt man das
Register komplett, und setzt nicht nur das eine Bit, was man setzen
möchte.
Ist in dem Fall aber völlig egal. WDCE muss lediglich gesetzt sein, wenn man die WDPx-Bits setzen muss und das ist da der Fall. Dass man dann zeitgleich WDCE löscht ist für das Setzen der WDPx-Bits irrelevant und es muss ja geklappt haben da, hätte es nicht geklappt, der Watchdog sonst alle 16ms kommen würde, d.h. der Mikrocontroller müsste alle 16ms aufwachen und nicht alle 9-10s ;)
Ja! Dein Bild zeigt es doch: WDCE braucht man nur um WDE auszuschalten.
Beim Tiny13 steht zusätzlich drin, dass man es auch fürs Ändern von WDPx
setzen muss.
@Uwe D.: Ich habe wohl gestern Abend deinen Ausschnitt nicht voll erfasst 🙄.
Ja, du hast recht, es steht auch beim Tinyx5 drin, trotzdem braucht man WDCE für den WD-Timer nicht.
Z.B. das im Anhang läuft so wie es soll, für Tiny25. (Für den Tiny13 muss man WDIE durch WDTIE ersetzen) ohne WDCE zu verwenden. Und selbst wenn man nach einigen Zyklen die WDPs auf einen neuen Prescaler-Wert stellt, geht das ohne WDCE - gerade nochmal ausprobiert und in anderen Programmen auch mehrfach so verwendet.
Es ist im Datenblatt unklar ausgedrückt; tatsächlich ist WDCE nur bei aktiviertem WDE notwendig - ein Sicherheitsfeature, um weder die Zeiten noch den WD-Reset versehentlich zu löschen. Es wird (vermutlich) beim Tiny13 genauso sein.
Joni benötigt die zweite Zeile nicht und auch die erste ist redundant und nur für den WD-Reset relevant.
WDCE muss lediglich gesetzt sein, wenn
man die WDPx-Bits setzen muss und das ist da der Fall.
Siehe oben. Das kann beim Tiny13 so sein (kann ich mangels Device nicht testen). Der ist aber sehr ähnlich zum Tiny25, da steht es auch drin, trotzdem braucht man es nicht für den reinen Timerinterruptbetrieb.
Und, er hat den WDE gar nicht in Verwendung, also nur Timer zum Wecken, kein Reset. Außer er hätte die Fuse gesetzt.
Da Joni nur einen Programmausschnitt zeigt: es könnte auch ein ganz trivialer Grund sein: Er toggelt eine LED mit dem Interrupt und dann ist die 4s an und 4s aus und er wollte eigentlich eine Periode von 4s?
Beim WDE in deinem Codeschnipsel braucht man die Timed Sequence, ja.
Ist jedoch nur WDIE aktiv, wie beim TO, dann nicht. Man kann dann den Teil
WDTCR |= (1 << WDCE);
einfach weglassen!
In meinem Beispiel gibt es gar keine Timed Sequence; man setzt die Prescalerbits und aktiviert den Interupt Enable. Man kann auch irgendwann später die WDPx neu setzen für eine neue Zeit - alles ohne WDCE!
Ist jedoch nur WDIE aktiv, wie beim TO, dann nicht. Man kann dann den
Teil
WDTCR |= (1 << WDCE);
einfach weglassen!
Ich zitiere aus dem Datenblatt des ATTiny13:
• Bit 4 - WDCE: Watchdog Change Enable
This bit is used in timed sequences for changing WDE and prescaler
bits. To clear the WDE bit, and/or change the prescaler bits,
WDCE must be set.
Danach muss auch zum Ändern des Prescalers das WDCE-Bit gesetzt sein, vorher lässt sich WDPx nicht ändern.
Aber ohne konkrete Rückmeldung vom TE ist sowieso alles nur stochern im Nebel. Für mich klingt es schwer danach, dass WDE gesetzt sit oder das Fusebit für den Watchdog ist gesetzt. Dann macht das beobachtete Verhalten nämlich durchaus sinn ;)
Ja, kenne ich und steht beim auch Tiny25 so drin. Es ist imho einfach unklar im DB ausgedrückt: den Prescaler kann man trotzdem im WDIE-Betrieb ohne weiteres verändern. Nur nicht im WDE-Betrieb.
Danach muss auch zum Ändern des Prescalers das WDCE-Bit gesetzt sein,
vorher lässt sich WDPx nicht ändern.
Nach meinen Erfahrungen eben nur wenn WDE oder die Fuse aktiv sind. Da ist es auch sinnvoll - aus Sicherheitsgründen.
Oben habe ich ein korrekt laufendes Beispiel für den Tiny25 angehängt (watchdog2.c). Da wird weder WDCE noch WDE angefasst. Könnte man ja mal ausprobieren ... (Tiny13: WDIE durch WDTIE ersetzen)
Aber ohne konkrete Rückmeldung vom TE ist sowieso alles nur stochern im
Nebel.
wdt Handling erledigen die 3 Macros von wdt.h
wdt_reset(), wdt_disable(), wdt_enable(timeout)
.. und man kann es ohne oder mit wdt ISR machen.
Ich verstehe leider die Makros dort nicht gut genug, aber mir scheint, dass wdt_enable(timeout) auch WDE aktiviert. Das will ich üblicherweise nicht.
Auch in den Datenblattbeispielen ist immer WDE beteiligt.
Warum eigentlich nicht? Das ist doch eigentlich der übliche Einsatzzweck: Interrupt und System-Reset nutzen. Ich frage nur aus reinem Interesse, soll kein Vorwurf sein. Ich nutze auch idR den Interrupt + System-Reset. Sollte ich mal hängen bleiben und das WDIE-Bit nicht rechtzeitig wieder setzen gibts halt den Systemreset. In der ISR des WDT mache ich üblicher Weise übrigens nichts.
Ich frage nur aus reinem Interesse, soll kein Vorwurf sein.
Ich fasse das auch nicht so auf.
Warum eigentlich nicht?
Nun, ich habe mehrere Programme 24/7 am laufen, z.T. seit Jahren, die eher weniger komplex sind. Welche, die mit 3 AAA oder einer LiIon und welche, die mit 5V Wandwarze bzw. mit eigenem Trafonetzteil betrieben werden.
Bisher hatte ich bei keinem einen Hänger gesehen, der den WD-Reset notwendig gemacht hätte. Und wenn dem so wäre, würde ich das auf einen HW- oder SW-Fehler zurückführen und würde die Ursache suchen.
Hardware ohne µC hat ja auch selten einen Watchdog und ist nicht zwangsweise unempfindlicher auf externe Störungen.
Ich verwende den WD-Interrupt meist als einfachen stromsparenden Timer, bei dem die Zeittoleranz des 128kHz Oszillators nicht stört, meist im Zusammenhang mit sleep. Zugegeben, diese Toleranz ist manchmal der Hauptnachteil.
Außerdem habe ich die Befürchtung, dass ich mit der Kombination aus Interrupt und Reset mehr Chancen für Programmierfehler habe, 😀 z.B. den, nicht rechtzeitig den WD zu quittieren oder in der ISR den WDIE neu zu setzen (wie ich hier schon gesehen habe).
In der ISR des WDT mache ich üblicher Weise übrigens nichts.
Ich schon, meist habe ich da eine Variable, die den Mulitplikator für die Basiszeit darstellt und in der ISR dekrementiert wird. Wie auch in dem obigen Beispiel. Damit kann man 'endlos' lange Zeiten (grob natürlich) erreichen - endlos bez. der Lebenserwartung eines Menschen 😉.
Bisher hatte ich bei keinem einen Hänger gesehen, der den WD-Reset
notwendig gemacht hätte.
Ist auch meine Erfahrung.
Ich habe es lieber, wenn mir der MC ordentlich auf die Finger haut, wenn ich einen SW-Fehler gemacht habe, damit ich ihn suchen und beheben kann.
Der Watchdog würde den Fehler nur verschleiern und der Kunde ärgert sich über den Schluckauf des Gerätes.
Gegen EMI hilft der Watchdog eh nicht, da kriegt er die hängende CPU nicht mehr raus, bzw. wenn durch EMI der Flash korrumpiert wurde.
Bisher hatte ich bei keinem einen Hänger gesehen, der den WD-Reset
notwendig gemacht hätte. Und wenn dem so wäre, würde ich das auf einen
HW- oder SW-Fehler zurückführen und würde die Ursache suchen.
So ähnlich gehe ich dabei vor: Ich schau erst nach wenn ich merke, dass das Watchdog ständig das System resetet denn wenn der Watchdog mir den Mikrocontroller resetet habe ich sicher einen Zustand erreicht, den ich nicht bedacht hatte. Wir sind ja alle nicht fehlerfrei. Der Systemreset ist bei mir also mehr Angst als sonst was ;)
Den Interrupt des Watchdogs nutze ich idR auch nur um den Mikrocontroller aufzuwecken. Daher brauch ich im Interrupt auch nichts machen. Uhren und ähnliches, dafür hat der Mikrocontroller Timer die IMHO besser geeignet sind als der Watchdog. Klar, man kann dafür auch den Watchdog nutzen, keine Frage. Mir wäre der Watchdog dafür aber zu ungenau bzw. das wäre mir zu viel Gezumpel um das in adequate Zeitintervalle zu schubsen.
@ M. K.
Ja, für Uhren ist das nicht geeignet, da braucht man Quarz und die Timer. Selbst das stellt nicht immer zufrieden; meist ist ein RTC besser ...
Timer brauchen meist etwas mehr Strom (speziell bei Batteriebetrieb relevant) und Funktionen wie die eines Monoflops wie z.B. Treppenlichtautomat, limitierte Einschaltzeit für ein Spielzeug, ein sehr langsamer astabiler MV (keep alive für eine Powerbank z.B.) u.ä., selbst für eine langsam blinkende Melde-LED nutze ich das, all das hat selten hohe Anforderung an die zeitliche Präzision. Denn dann würde man es anders lösen, klar - immer schön abhängig von den Anforderungen.
Hier ging es aber um einen reinen Interruptbetrieb (so aus dem Code des TO zu entnehmen) und meiner Behauptung, dass man dann den WDCE nicht bemühen muss um den Prescaler zu setzen oder zu ändern.
Wir sind ja alle nicht fehlerfrei.
Ja leider 😉.
Der Systemreset ist bei mir also mehr Angst als sonst was ;)
Gut, jeder hat eine andere Vorgehensweise. Und wenn es nur selten mal rumpelt, dann sind unsere beiden Varianten schwer zu debuggen.
das wäre mir zu viel Gezumpel um das in adequate
Zeitintervalle zu schubsen.
Ich finde die Timer anstrengender zu programmieren. Aber auch wieder: jeder hat eine andere Vorgehensweise oder auch nur Gewohnheit ...
Ich hab auch gemerkt, daß ein Uhrenquarz am MC nicht das gelbe vom Ei ist. Frequenzen an anderen Pins (PWM, Multiplex) lassen den Quarz schnell ungenau werden.
Eine schöne externe RTC ist der RX8010. Quarz ist mit eingebaut, also kaum zu stören. Im SO-8 auch für ältere Leute noch gut zu löten und bei 160nA mit nem Goldcap auch Monate zu stützen. Reichelt hat ihn lagernd.
return in einer ISR ist überflüssig. Ebenso das Flag löschen, das passiert alles automatisch wenn die ISR implementiert ist. Es genügt ein
1
ISR(WDT_vect){
2
}
zu schreiben. Tritt nun ein Interrupt auf wird die ISR angesprungen und die Automatismen werden die entsprechenden Flags löschen. Von Hand ist da nichts zu machen.
Das Löschen des Watchdogs Interupt Flag in loop() ist daher auch überflüssig und den Prescaler muss man auch nicht ständig neu setzen, das genügt ihn einmal zu setzen.
Alles richtig, was du schreibst.
Auch die cli() und sei() Makros braucht man nicht, mit Ausnahmen eines sei() beim Setup. Naja, kann sein, dass er ein lange laufendes Programm hat und nur einmal für 4s schlafen lassen will. Das kann man aus dem Programmschnipsel nicht entnehmen.
Und das WDCE, wie oben ausführlich diskutiert, auch nicht.
Ich bin mir nicht sicher, ob das nicht sogar schädlich ist. Normalerweise (in ASM) steht ein 'RETI' - Interrupt Return - am Ende der Routine.
Die macht der Compiler aber von sich aus.
-- Gerade mal getestet: nein, ist nicht schädlich; es wird vom Compiler entfernt. Er setzt nur ein 'RETI' unabhängig ob ein "return;" da ist oder nicht.
Der Vollständigkeit halber: Es gibt jedoch noch die Spezialvariante "ISR(XXXX_vect, ISR_NAKED);", die braucht (in C) aber ein "reti();" am Ende, kein "return;".
Kommt halt darauf an, ob man das I-Flag dabei wieder setzten will, oder
nicht.
Wo sollte es sinnvoll sein, das nicht zu wollen? Einen "deferred handler" würde man sinnvollerweise aus der ISR aufrufen und vorher händisch sei() aufrufen.
Und alles andere konstruiert effektiv eine Art Trap. Der unterbroche Code (der unvorhersagbar ist) läuft weiter, aber mit gesperrten Interrupts. Wozu sollte sowas gut sein?
Die macht der Compiler aber von sich aus.
-- Gerade mal getestet: nein, ist nicht schädlich; es wird vom Compiler
entfernt. Er setzt nur ein 'RETI' unabhängig ob ein "return;" da ist
oder nicht.
Ein return ist ja auch kein RETI. Es ist die Anweisung an den Compiler, springe zur letzten schließenden Klammer. Und da steht dann bei einem Interrupthandler der Epilog, der erstmal benutzte Register rettet und den Stack aufräumt.
So tief bin ich in den Details nicht drin.
Der TO hat in seinem letzten Beispiel ein return in der ISR, was ich bisher noch in keinem Beispiel gesehen hatte. Mir war nur nicht klar, ob das ggf. schädlich sein könnte.
Der TO hat in seinem letzten Beispiel ein return in der ISR, was ich
bisher noch in keinem Beispiel gesehen hatte.
Es kommt durchaus vor, daß man an mehreren Stellen den Interrupt verlassen möchte, z.B. beim I2C-Handler.
return ist ein Sprungbefehl in C, wie auch goto, break, continue.
In einer ISR geht return natürlich nur ohne Argument.
Ein return ist ja auch kein RETI. Es ist die Anweisung an den Compiler,
springe zur letzten schließenden Klammer. Und da steht dann bei einem
Interrupthandler der Epilog, der erstmal benutzte Register rettet und
den Stack aufräumt.
Wie ist bitte die Anweisung "Springe zur letzten schließenden Klammer" zu verstehen? Ein RETI wie auch ein RET (return) springt zu der letzten Adresse die auf dem Stack steht. RETI aktiviert das globale "Interrupt-Enable" Flag wieder, RET nicht.