AVR: Watchdog löst keinen Interrupt aus

#7999411
Lesenswert?

Hallo zusammen,

ich glaube, mein Watchdog muss mal in die Hundeschule und ein bisschen nachsitzen. Er löst keinen Interrupt aus, obwohl (1<<WDIE) und sei().

Ich versuche meinem Arduino Uno R3 mit ATmega328P beizubringen, die LED nur dann ein- oder auszuschalten, wenn eine gewisse Anzahl WD-Timeouts erreicht ist. Zwischen den WD-Timeouts soll der uC im Power-down-Modus schlafen (macht er) und beim WD-Timeout wieder aufwachen. Wacht er aber nicht. Die Hauptschleife wird genau einmal durchlaufen. Danach liegt der uC im Koma.

Warum bellt der faule Hund nicht? Hat jemand einen Tipp?

Angehängte Dateien:
Beitrag #7999432 wurde vom Autor gelöscht.
#7999454
Lesenswert?

Was auf jeden Fall nicht ok ist: -- wdt_ticks muss volatile sein

-- Es ist nicht gut, in der Hauptschleife wdt_ticks als Zählerstand abzufragen. Besser: In der ISR einen bool setzen, wenn es soweit ist, und den in der Hauptschleife abfragen. Der Vergleich ==0 kann auch mal schief gehen.

Ansonsten: Die seriellen while-Sachen vorübergehend rauswerfen, um sicher zu sein, dass es da keine Endlosschleifen gibt. Und: Die LED in die ISR einbauen, damit man sicher sehen kann, ob die ISR durchlaufen wird.

#7999592
Lesenswert?

StefanK schrieb:

...nur, dass ich noch WDCE setze...

Gerade ausprobiert: das zweite Setzen von WDCE scheint keinen Einfluß zu haben. Also den Ratschlägen der anderen Teilnehmer folgen ...


Minimalprogramm nachgereicht. Ist in Assembler, vielleicht bringt es trotzdem einen Erkenntnisgewinn.

Angehängte Dateien:
: Bearbeitet durch User
#7999621
Lesenswert?

Danke schon mal für die Anregungen! Ich sehe mir das mal genauer an.

LED in der ISR ansteuern: Probiere ich zu debug-Zwecken gern mal aus. Sobald der WDT-timeout Mechanismus funktioniert soll im HP ein umfangreicher Code ausgeführt werden, gefühlt eher zuviel für die ISR.

Zu den uint16_t wdt_ticks: Volatile probiere ich mal aus. Meinem Verständnis nach ist wdt_ticks eine globale Variable, auf die sowohl die WD-ISR, als auch das Hauptprogramm etc zugreifen können. Nur tun sie das erwartungsgemäß nicht gleichzeitig, denn der WDT sollte den Interrupt nur alle paar Sekunden auslösen. Bis dahin hat das Hauptprogramm alles erledigt, längst geschlafen und wertet wdt_ticks an der Stelle des Aufwachens nicht aus. Nach dem Aufwachen muss das HP ohnehin noch auf die Abarbeitung der ISR warten, bevor es dran ist. So sollte es keine Konflikte zwischen ISR und HP geben.

Zu atomar: Das ist sinnvoll, wenn es mehrere Interrupts gibt und man nicht weiß wann einer auftritt, das stimmt. Im Moment gibt es nur den WD-Interrupt. Es sei denn, dass das Arduino spezifische Serial.print UART-Interrupts im Gepäck hat. Aber auch die serielle Übertragung ist in sehr viel kürzerer Zeit (<1ms) und längst erledigt bevor der WDT-timeout im Sekundenbereich wieder erwartet wird.

Die while (UCSR0A...) Schleifen: Bewirken nur, dass die Übertragung nicht bereits nach dem ersten übertragenen Zeichen vom Powerdown Schlaf abgewürgt werden. Das ist eh nur zum debuggen und fliegt wieder raus, sobald der WDT Mechanismus funktioniert.

In der WD-ISR ein Flag setzen: Generell eine gute Idee und häufige Praxis. Mir ist in meinem Fall nur nicht wirklich klar, warum die Abfrage isr_flag == TRUE etwas grundsätzlich anderes sein soll als wdt_ticks == 0?

#7999636
Lesenswert?

StefanK schrieb:

Meinem Verständnis nach ist wdt_ticks eine globale Variable, auf die sowohl die WD-ISR, als auch das Hauptprogramm etc zugreifen können.

Aber der Optimizer weiß nicht, dass die Variable außerhalb der Main-Loop geändert wird. So kann es sein, dass sie ganz weg optimiert wird oder in einem Register zwischengespeichert und nie wieder gelesen wird.

Ich kann zwischen dem Programm und den zahlreichen Beispielen im Netz keinen wirklichen Unterschied erkennen. Deswegen stellt sich mir die Frage, wie das Programm übersetzt und hochgeladen wird.

#7999638
Lesenswert?

S. L. schrieb:

Gerade ausprobiert: das zweite Setzen von WDCE scheint keinen Einfluß zu haben.

Ja, hätte mich auch gewundert, ist höchstens überflüssig.

Minimalprogramm nachgereicht. Ist in Assembler, vielleicht bringt es trotzdem einen Erkenntnisgewinn.

Interessant am Minimalprogramm ist die Tatsache, dass WDCSR sofort nach sleep_enable gemacht wird.

reset: ldi r16,(1<<SE)+(1<<SM1) ; power-down out SMCR,r16 ldi r16,(1<<WDE)|(1<<WDCE) sts WDTCSR,r16 ldi r16,(0<<WDE)|(1<<WDIE)|(1<<WDP2)|(1<<WDP1) ; 1 s, Interrupt-Mode sts WDTCSR,r16 sbi DDRB,0 sei

Das ist ein Unterschied zu meinem Prog. Bin leider nicht so asm-fit, werde diese Sequenz aber mal so ausprobieren. Bei asm weiß man wenigstens was genau passiert. Bei den C-Funktionen sleep_enable(), sleep_mode(), sleep_cpu(), sleep_disable() etc. weiß ich nicht, was sich dahinter verbirgt.

#7999656
Lesenswert?

Mario M. schrieb:

Aber selbst wenn wdt_ticks komplett weg optimiert würde, muss wenigstens der Text seriell ausgegeben werden.

Ich glaube nicht, dass wdt_ticks weg-optimiert wird, denn der Arduino sendet nach einem Neustart bzw Reset per Button über die Serial.print Funktionen einmalig folgenden Text:

wdt_ticks: 7

7 ist der Initialwert. Das zeigt, dass die Hauptschleife wenigstens genau einmal bis zum Sleep läuft.

Wäre auch ganz schön frech, würde wdt_ticks weg-optimiert...

Mache morgen weiter.

#7999873
Lesenswert?

Leute, es läuft! Danke für die vielen hilfreichen Tipps!

Die WD-Konfiguration und sleep Sequenz waren schon richtig. Die von S.L. vorgeschlagene prägnante asm sleep-Sequenz probiere ich noch aus.

LED testweise in der ISR hat gezeigt, dass die WD-ISR wie erwartet kommt.

volatile rockt! Man kann nicht vorsichtig oder misstrauisch genug sein ;-) Krass die Optimierung!

Die zweite while(!(UCSR0A & (1<<RXC0)) war endlos. Weg damit. Die erste führt wenigstens dazu, dass Serial.print eine halbwegs lesbare Rückmeldung liefert.

Das führt mich zu einer weiteren Frage: Wie bekomme ich es hin, dass die Serial.print Funktion vor dem Betreten des Schlafs ordentlich abgearbeitet wird? Ich brauche sie zum debuggen. Wie kann ich ihr die benötigte Zeit verschaffen?

Angehängte Dateien:
#7999885
Lesenswert?

Frank D. schrieb:

richtig, Bits von hand gesetzt funktionierte, vielleicht sind die immer noch kaputt.

Guter Hinweis. Das probiere ich als nächstes anhand des Beispiels von S.L. aus. Dann weiß man genau was passiert.

@Georg M. (g_m) An Wolle habe ich mich orientiert. Seine WD-Konfiguration und sleep_*() Sequenz funktionieren.

@ S.L. TXC0 wird gleich ausprobiert. Die while Schleife gibt's ja schon ;-)

(Firma: Gast) #7999931
Lesenswert?

StefanK schrieb:

Das führt mich zu einer weiteren Frage: Wie bekomme ich es hin, dass die Serial.print Funktion vor dem Betreten des Schlafs ordentlich abgearbeitet wird? Ich brauche sie zum debuggen. Wie kann ich ihr die benötigte Zeit verschaffen?

Habe dein Programm mal "vereinfacht"

StefanK schrieb:

volatile rockt!

Ist hier nicht nötig. Siehe Anhang.

Angehängte Dateien:
#7999944
Lesenswert?

StefanK schrieb:

volatile rockt!

Ist eigentlich das falsche Werkzeug für den Zweck, aber funktioniert. Und es ist einfacher, einem Anfänger ein "Schreib halt volatile davor" hinzuwerfen, als Seitenweise Erklärungen zu atomarem Variablenzugriff, Memory- und Optimization Barriers usw. zu schreiben, die dann sowieso nicht verstanden werden.

Vorschlaghammer passt immer, egal ob das jetzt eine Schlitz-, Phillips-, oder Pozidriv-Schraube ist.

Arduino F. schrieb:

Ist hier nicht nötig. Siehe Anhang.

Was ich dann zum weiteren Stromsparen oft noch drinhabe:

1
void yield() {
2
    set_sleep_mode(SLEEP_MODE_IDLE);
3
    sleep_mode();
4
}

Das überschreibt eine "weak" Funktion aus dem Framework, und längere delay() sparen automatisch Strom, werden aber auch ein bischen ungenauer, weil die erst mit dem nächsten IRQ beendet werden. Da aber im IDLE der Timer für millis() weiterläuft, ist der IRQ nicht soweit weg.

Damit geht dann auch das Busy-Waiting stromsparend:

1
 while(bufferSize > Serial.availableForWrite()) yield();
#7999952
Lesenswert?

StefanK schrieb:

volatile rockt! Man kann nicht vorsichtig oder misstrauisch genug sein ;-) Krass die Optimierung!

Ich nenne diese Strategie "defensive Codierung", zahlt sich immer aus. Man spart damit viel Zeit für unnötige Fehlersuche. Wenn das Programm einmal läuft, kann man immer noch Dinge weglassen/verbessern, um irgendwo 3 Byte oder 2 Mikrosekunden einzusparen.

In der WD-ISR ein Flag setzen: Mir ist in meinem Fall nur nicht wirklich klar, warum die Abfrage isr_flag == TRUE etwas grundsätzlich anderes sein soll als wdt_ticks == 0?

Es ist grundsätzlich unsauberer Code. Auch dann, wenn es in diesem Fall funktioniert. Morgen ändert du irgendwas anderes und schon rächt sich die Schlamperei. Wenn du den Zustand eines Zählers abfragst, und der sich per Interrupt oder DMA zu beliebigen Zeiten ändern kann, was kann dann passieren? Der Zähler erreicht irgendwann den betreffenden Zustand, aber bevor du das gemerkt hast, ist er womöglich schon einen Tick weiter und die Abfrage mit == geht schief.

Nebenbei: Dein µP ist ein 8-Bit-µP, für wdt_ticks hast du aber 16 bit genommen. Also sind z. B. für wdt_ticks++ zwei Operationen nötig: Erst unteres Byte inkrementieren, und im Falle eines Überlaufs dann das obere. Wenn zwischen diesen beiden Operationen ein Interrupt kommt und auf die Variable zugreift, sieht er einen falschen Wert (unteres Byte schon neu, oberes Byte noch alt). In deinem Beispiel kommt das nicht vor, aber auch so etwas kann zu schwer erkennbaren Fehlern führen. Deswegen: Bei allen Variablen, die in ISRs vorkommen, dreimal hinsehen, ob der Code sauber ist.

: Bearbeitet durch User
#8000000
Lesenswert?

S. L. schrieb:

Abfrage auf TXC0?

Es geht. Mit while(!(UCSR0A & (1<<TXC0)) läuft die Serial.print Funktio n bis zum Ende.

@Rolf (rolf22)

Ich verstehe genau was Sie meinen und gebe Ihnen völlig recht. Ein Flag mit handshake ist der sichere Weg und gute, saubere Praxis, vor allem bei mehreren Interrupts oder Multitaskingprozessen. Hier gibt es ja nur einen, den WD-ISR alle 8s, der selbst nicht unterbrochen wird. Unter anderen Umständen hätte ich es natürlich anders gemacht.

@Arduino F.

Danke für das ATOMIC_BLOCK Beispiel. Ich sehe es mir genau an. Dass ich den Vorschlag ATOMIC bisher nicht berücksichtigt habe liegt zum einen an den oben genannten Gründen (nur ein Interrupt) und and der Tatsache, dass ich nicht erwartet habe, dass der Optimierer eine globale Variable weg optimiert, die an 2 Stellen - im HP und in der ISR - verwendet wird?

Wieso weiß der Optimierer nicht, dass eine Variable, die im HP gelesen, außerhalb des HP geändert wird, obwohl es offensichtlich ist? Da liegt das Problem doch eher beim Optimierer als beim Entwickler der von solchen Tücken nichts ahnt. Es gibt ja nicht Mal eine Warnung.

Angehängte Dateien:
#8000005
Lesenswert?

StefanK schrieb:

Wieso weiß der Optimierer nicht, dass eine Variable, die im HP gelesen, außerhalb des HP geändert wird, obwohl es offensichtlich ist?

Weil der Optimierer nicht sehen kann, wann und wie (und ob überhaupt) die ISR aufgerufen wird. Das macht nachher der Linker, der die Vektortabelle befüllt. Da ist's für den Compiler/Optimierer aber schon zu spät.

Deshalb gibt's "memory/optimization barriers" als Hinweis an den Compiler: "Schau mal lieber frisch im RAM nach, die Variable könnte sich geändert haben".

Die sind praktischerweise in cli()/sei() bzw. dem ATOMIC_BLOCK schon eingebaut.

: Bearbeitet durch User
#8000020
Lesenswert?

StefanK schrieb:

Wieso weiß der Optimierer nicht, dass eine Variable, die im HP gelesen, außerhalb des HP geändert wird, obwohl es offensichtlich ist?

Der Compiler implementiert die Sprache C++ genau so, wie diese definiert ist. Und die Sprachdefinition setzt auf einem Modell auf, daß solche „Nebenkriegsschauplätze“ wie Interruptroutinen gar nicht vorsieht. Der Compiler darf davon ausgehen, daß er den gesamten Programmfluß sieht, und er kann und darf darin alles rauswerfen, was nichts erkennbares tut. Dazu gehören Variablen, die sich niemals ändern.

StefanK schrieb:

Da liegt das Problem doch eher beim Optimierer als beim Entwickler der von solchen Tücken nichts ahnt. Es gibt ja nicht Mal eine Warnung.

Auch wenns nicht jeder hören will: programmieren lernt man nur teilweise durch teilweise and error. Und auf dieses Problem stößt jeder Programmieranfänger seit Jahrzehnten, der mit Mikrocontrollern rumspielt. Entsprechend vielfältig ist die Dokumentation dazu, die sich im Netz angesammelt hat.

Oliver

: Bearbeitet durch User
#8000021
Lesenswert?

@Ernst B Verstehe. Das Prinzip 'in dubio pro reo' gilt zum Zeitpunkt des Optimierens nicht, und dem Verdacht, die ISR könnte genutzt werden wird später nicht mehr nachgegangen. Anstatt das zu verbessern, legt man die Existenzgrundlage für die Code-Checktool Entwickler ;-)

Wie sähe es denn aus, wenn in der ISR ein global definiertes handshake Flag gesetzt und im HP gelesen würde? Fiele das dem Optimierer dann auch sofort zum Opfer?

(Firma: Gast) #8000028
Lesenswert?

StefanK schrieb:

Fiele das dem Optimierer dann auch sofort zum Opfer?

Der Compiler versucht Variablen in Registern zu halten. Die ISR ändert die Variable im Speicher.

Also muss man eine Memory Barrier setzen, damit der geänderte Wert wieder in Registern landet. Und umgekehrt.

Dazu: Der Zugriff auf solche Variablen muss atomar gestaltet werden.

: Bearbeitet durch User
#8000031
Lesenswert?

StefanK schrieb:

Verstehe. Das Prinzip 'in dubio pro reo' gilt zum Zeitpunkt des Optimierens nicht, und dem Verdacht, die ISR könnte genutzt werden wird später nicht mehr nachgegangen.

Weil's nicht geht, ohne komplett auf jede Art der Optimierung zu verzichten.

Compilieren und Linken sind zwei unterschiedliche Arbeitsschritte. Code kann in getrennten Source-Files liegen. Und muss noch nichtmal auf demselben Rechner kompiliert worden sein.

Wenn ich dir ein vorkompiliertes Object-File mit der ISR schicke, und dein Compiler dein Hauptprogramm Kompilierst, und nachher linkst du beides zusammen, woher soll dein Compiler wissen, was in meiner Datei verändert wird? Oder "Verändert werden wird", wenn ich dir das File erst nächste Woche schicke?

Gedankenexperiment: Wenn dein Compiler das könnte, machen wir einen Deal: Ich schick dir nächste Woche ein Object-File mit 6 Konstanten aus dem Wertebereich 1-49. Du teilst mir heute mit, welche Zahlen ich dann in dem File stehen haben werde.

#8000036
Lesenswert?

Oliver S. schrieb im Beitrag

Der Compiler implementiert die Sprache C++ genau so, wie diese definiert ist. Und die Sprachdefinition setzt auf einem Modell auf, daß solche „Nebenkriegsschauplätze“ wie Interruptroutinen gar nicht vorsieht.

Nun ja, wenn man (als Anfänger) seit Jahrzehnten auf dieselben Probleme stößt, wäre es dann nicht mal an der Zeit, dieselben zu beheben?

Wieso behandelt man eine ISR nicht wie eine erforderliche Routine auch wenn sie im Code nicht explizit aufgerufen wird?

Selbstverständlich lernt man aus solchen Fehlern. Natürlich kann man mit nicht idealen Umständen leben, auch wenn ich das schon etwas weniger selbstverständlich finde.

Wir verwenden ja schließlich den auf uC zugeschnittenen avr-gcc.

#8000044
Lesenswert?

Arduino F. schrieb im Beitrag

Serial verwendet ISR.

Danke für die Bestätigung. Ich hatte es schon vermutet, aber in meinem Fall jedoch kein Risiko für Konflikte gesehen, da der WD-ISR im Sekundenbereich kommt und die serielle Kommunikation direkt nach dem abgelaufenen WD-ISR beginnt und lange vor dem Auftreten des nächsten WD-ISR erledigt ist. Inzwischen läuft mein Beispiel mit meiner Annahme.

Natürlich ist die Verwendung von ATOMIC_BLOCK die richtige Strategie für komplexere und weniger vorhersagbare Szenarien. Ich speichere Ihr Beispiel in meine Beispielsammlung. Vielen Dank nochmal dafür.

#8000054
Lesenswert?

Εrnst B. schrieb:

Weil's nicht geht, ohne komplett auf jede Art der Optimierung zu verzichten.

Natürlich ginge das alles.

Nochmal: es ist so, weil die Sprachdefinition so ist, wie sie ist. Und das schon seit dem letzten Jahrtausend.

StefanK schrieb:

Natürlich ist die Verwendung von ATOMIC_BLOCK die richtige Strategie für komplexere und weniger vorhersagbare Szenarien.

ATOMIC_BLOCK ist genau für solche Fälle wie diesem hier gemacht, und das absolut vorhersehbar.

Oliver

: Bearbeitet durch User
#8000224
Lesenswert?

StefanK schrieb:

Nun ja, wenn man (als Anfänger) seit Jahrzehnten auf dieselben Probleme stößt, wäre es dann nicht mal an der Zeit, dieselben zu beheben?

Anfänger behebt man dadurch, daß die dazu lernen, und irgendwann keine Anfänger mehr sind. Anders geht es nun mal nicht.

Weils aber für jeden in diesem Umfeld immer was dazuzulernen gibt, egal, wie wenig Anfänger man ist, ist das eine Lebensaufgabe.

Hier mal ein schönes Beispiel für eine andere "Falle". Ist C++, geht aber genauso in C.

1
#include <iostream>
2
#include <cstdint>
3
#include <limits>
4

5
int main()
6
{
7
   int result = 0;
8
   uint8_t add = 0xff;
9

10
   int numIterations = std::numeric_limits<int>::max() / add;
11
   std::cout << "numIterations: " << numIterations << std::endl;
12

13
   for (int i = 0; i < numIterations; i++)
14
   {
15
      result += add;
16
   }
17

18
   std::cout << "result before last add: " << result << std::endl;
19

20
   result += add;
21

22
   if (result < 0)
23
      std::cout << "negative " << result << std::endl;
24
   else
25
      std::cout << "positive " << result << std::endl;
26
}

Ohne Optimierung kompiliert gibt das Programm "negative -2147483521" aus, mit Optimierung dagegen "positive -2147483521".

Das da undefined behaviour im Spiel ist, ist klar, aber das Ergebnis erstaunt dann trotzdem immer wieder. Der Compiler nutzt den Sprachstandard gnadenlos aus. Hält man sich nicht dran, geht es schief.

Oliver

: Bearbeitet durch User
#8000486
Lesenswert?

Oliver S. schrieb im Beitrag

wie wenig Anfänger man ist, ist das eine Lebensaufgabe.

Das ist sehr treffend formuliert, insbesondere wenn es um ein sehr komplexes Thema geht wie SW-Entwicklung für uC. Das kann mitunter sehr hart sein, vor allem was den C-Standard angeht.

Der Grund, warum ich weiter oben schier vom Glauben abgefallen bin, ist die Tatsache, dass es trotz Jahrzehnte langer Verwendung von Interrupts - einem der zentralsten Themen in der uC Entwicklung - im avr-gcc immer noch ohne jegliche Warnung zur Weg-Optimierung einer globalen Variablen in einer ISR kommt. Es ist m.E. einfach nicht mehr zeitgemäß, dem Optimierer des avr-gcc da nicht eine entsprechende Regel beizubringen.

Zurück zu Ihrem Beispiel von oben. Ich nehme an es geht um unterschiedliche Datentypen signed, unsigned bzw. type casts? Wie kommt man aus der geschilderten Falle? Würde der Compiler warnen? Würden Check-Tools wie z.B. MISRA auf die Falle aufmerksam machen?

: Bearbeitet durch User
(Firma: Gast) #8000490
Lesenswert?

StefanK schrieb:

im avr-gcc immer noch ohne jegliche Warnung zur Weg-Optimierung einer globalen Variablen in einer ISR kommt.

Falsch!

StefanK schrieb:

vor allem was den C-Standard angeht.

C++! Du hast ein C++ Programm gezeigt, kein C. Wobei das in diesem Fall keinen Unterschied macht.

Egal, wem du dein anfängliches versagen in die Schuhe schieben möchtest... Die Ursache liegt in deinem mangelhaftem Verständnis über die Zusammenhänge.

Prognose: Dein Vorschlag, diese Optimierungen aus C/C++ zu entfernen, wird nicht umgesetzt werden. Auch nicht in den nächsten 30 Jahren.

Dein Meckern ist damit sinnfrei.

StefanK schrieb:

Wie kommt man aus der geschilderten Falle?

Lernen, was in ein undefined behavier führt und konsequent vermeiden. Also: Sorgfalt und Disziplin.

: Bearbeitet durch User
Persönliche Seite #8000495
Lesenswert?

Arduino F. schrieb:

Dein Vorschlag, diese Optimierungen aus C/C++ zu entfernen, wird nicht umgesetzt werden.

Tatsächlich ist das schon seit über 10 Jahren umgesetzt. Man muss nur die richtige Option setzten so dass Signed Overflow nicht mehr UB ist sondern Wrap Around, also Modulo 2^n im entsprechenden Restsystem.

: Bearbeitet durch User
Persönliche Seite #8000516
Lesenswert?

StefanK schrieb:

dass es trotz Jahrzehnte langer Verwendung von Interrupts

  • einem der zentralsten Themen in der uC Entwicklung - im avr-gcc immer noch ohne jegliche Warnung zur Weg-Optimierung einer globalen Variablen in einer ISR kommt. Es ist m.E. einfach nicht mehr zeitgemäß, dem Optimierer des avr-gcc da nicht eine entsprechende Regel beizubringen.

Wenn man keine Optimierung möchte, dann kann man die funktionsweise deaktivieren.

Hilft aber auch nicht wirklich wenn man die Sprache nicht kennt. Bei jeder Wissenslücke eines x-beliebigen Programmiers einen Compiler zu verhundsen ist nicht wirklich zielführend.

Ist halt wie immer: Know your tools! Und dazu gehört eben auch die verwendete Programmiersprache.

#8000520
Lesenswert?

Arduino F. schrieb im Beitrag

...dein anfängliches versagen in die Schuhe schieben... Auch nicht in den nächsten 30 Jahren... Dein Meckern...

Es scheint, als kippe gerade die Stimmung und als verlassen wir gerade die sachliche und konstruktive Ebene. Das finde ich schade. Können wir bitte wieder dazu zurückkehren und konziliant miteinander umgehen? Was hat Sie verärgert?

Ich zweifle keineswegs an Ihrer Erfahrung, im Gegenteil. Ich arbeite gerade an Ihrem Codebeispiel, für das ich Ihnen danke.

Gast #8000521
Lesenswert?

Hallo,

habe jetzt nicht den ganzen Thread gelesen, aber mir sind folgende Zeilen unklar (mache allerdings aktuell auch nix mit AVR):

1
ldi     r16,(1<<WDE)|(1<<WDCE)
2
sts     WDTCSR,r16
3
ldi     r16,(0<<WDE)|(1<<WDIE)|(1<<WDP2)|(1<<WDP1)  ; 1 s, Interrupt-Mode
4
sts     WDTCSR,r16

WDTCSR wird einmal mit WDE gesetzt und nur zwei Befehle weiter mit WDE gelöscht beschrieben. Ist das OK so? Gibt es irgendwelche Timing-Vorgaben? Wie gesagt, sorry, wenn mein Geschreibsel keinen Sinn macht. War nur so ein Bauchgefühl beim Überfliegen!

ciao

Marci

#8000527
Lesenswert?

Marci W. schrieb:

WDTCSR wird einmal mit WDE gesetzt und nur zwei Befehle weiter mit WDE gelöscht beschrieben. Ist das OK so?

Ja

Gibt es irgendwelche Timing-Vorgaben?

Ja, diese WDE setzen/löschen Sequenz muß innerhalb von 4 Takten passieren. Was dem TO auch noch auf die Füße fallen wird, falls der mal sein Programm im debug-Modus baut.

Oliver

: Bearbeitet durch User
#8000549
Lesenswert?

Oliver S. schrieb:

Marci W. schrieb:

WDTCSR wird einmal mit WDE gesetzt und nur zwei Befehle weiter mit WDE gelöscht beschrieben. Ist das OK so?

Wenn das innerhalb von 4 Taktzyklen gemacht wird, bewirkt die Sequenz, dass der WD nach Ablauf des WD-Timeout nur einen Interrupt auslöst und keinen Systemreset.

@Oliver

Was dem TO auch noch auf die Füße fallen wird, falls der mal sein Programm im debug-Modus baut.

Keine Sorge, ich bin da inzwischen einen Schritt weiter. Meine WD-Konfigurationssequenz sieht so aus:

cli(); WDTCSR = (1 << WDCE) | (1 << WDE); WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP1); // 1s
sei();

Arduino F. macht es so: ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { wdt_reset(); WDTCSR |= (1 << WDCE) | (1 << WDE); WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP1); // 1s
}

: Bearbeitet durch User
Persönliche Seite #8000592
Lesenswert?

StefanK schrieb:

1
WDTCSR = (1 << WDCE) | (1 << WDE);
2
WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP1); // 1s

Weil C/C++ kein Timing garantiert, es sich aber um eine timed Sequence handelt, sollte das per (Inline) Asm gemacht werden:

1
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
2
    {
3
        __asm ("sts %0,%1" "\n\t"
4
               "sts %0,%2"
5
               :: "n" (&WDTCSR),
6
               "d" ((uint8_t) ((1 << WDCE) | (1 << WDE))),
7
               "d" ((uint8_t) ((1 << WDIE) | (1 << WDP2) | (1 << WDP1)))
8
               : "memory");
9
    }
#8000603
Lesenswert?

Johann L. schrieb im Beitrag

Weil C/C++ kein Timing garantiert, es sich aber um eine timed Sequence handelt, sollte das per (Inline) Asm gemacht werden

Danke Johann für den Hinweis. Was könnte dazwischen kommen, sofern Interrupts vorher deaktiviert cli() und erst danach wiedet aktiviert sei() werden?

Gilt Dein Einwand auch für die ATOMIC Variante von Arduino F.?

Persönliche Seite #8000608
Lesenswert?

StefanK schrieb:

Danke Johann für den Hinweis. Was könnte dazwischen kommen, sofern Interrupts vorher deaktiviert cli() und erst danach wieder aktiviert sei() werden?

Prinzipiell darf der Compiler zwischen den beiden SFR-Zugriffen andere Instruktionen einfügen, so dass das Timing nicht mehr hinkommt.

Gilt Dein Einwand auch für die ATOMIC Variante von Arduino F.?

Ja. Und auch die Inline Asm Sqeuenz muss atomar sein.

: Bearbeitet durch User
#8000617
Lesenswert?

... darf der Compiler ...

Zu Heinrich Spoerls Zeiten war freilich ein 'Compiler' unbekannt, sonst hätte er vielleicht seinen Bömmel sagen lassen: "Wo simmer denn dran? Aha, heute krieje mer de C-Compiler. Also, wat is en C-Compiler? Da stelle mer uns janz dumm. Und da sage mer so: En C-Compiler, dat is ene jroße schwarze Raum, der hat hinten un vorn e Loch. Dat eine Loch, dat is de Quelldatei. Und dat andere Loch, dat krieje mer später."

: Bearbeitet durch User
#8001089
Lesenswert?

Arduino F. schrieb:

StefanK schrieb:

Inzwischen läuft mein Beispiel mit meiner Annahme.

Der Weg in die Hölle ist mit falschen Annahmen gepflastert.

Ich verstehe nicht, was dich davon abhält, es richtig zu machen, wenn du doch jetzt weißt wie und warum es richtig geht.

Ich habe Dein Beispiel ausprobiert. Es funktioniert und war lehrreich. Sinnvoll ist die ATOMIC Methode in einem Programm, das eine 16/32bit Variable in einem höher prioren unterbrechenden Prozess in schneller Folge updated (z.B. Motordrezahlerfassung) und in einem langen Hauptprogramm mit vielen Abfragen der Variablen die Kohärenz erfordern. In meinem Fall bringt das aber nichts. Was die Vorsicht angeht, ist es ein Unterschied, ob ich in einer gefährlichen Großstadt wie Minneapolis, Cali oder Medellin lebe oder auf dem Dorf.

Ich halte es mit George: Der Weg zur Hölle ist mit guten Vorsätzen gepflastert, nicht mit schlechten.

#8001215
Lesenswert?

StefanK schrieb:

Wenn Du Dir so sicher bist, dass es in meinem Fall ein Problem gibt

Ich verrate dir mal was aus der Industriepraxis: Wenn frisch eingestellte Absolventen ihre ersten Programme schreiben, dann brauchen die oft Hilfe, bis ihre Programme laufen. Und es dauert lange, bis die laufen.

Wenn denen ihr Fachbetreuer sagt, "man mache" bestimmte Dinge nicht so, sondern anders, dann kommt in sehr vielen Fällen sofort die Antwort: "Warum denn anders? Was ich gemacht habe, läuft doch!" Manche von denen lernen dann in 2 Jahren oder so, dass das kein fachlich akzeptables Argument ist. Andere lernen das nicht, weil sie aus ihrer Selbstverteidigungs-Ecke nie rauskommen (können). Ist eine reine Charakterfrage.

#8001254
Lesenswert?

Das verstehe ich ja alles. Und streiten wir auch nicht darüber was eine Beleidigung ist. Bleiben wir sportlich und fair.

Die Hypothese, dass ohne ATOMIC Probleme möglich sind, kennen und akzeptieren wir alle.

Arduino F. soll nun den Beweis liefern, was im Ausgsgangsbeispiel dazu führt, dass

1.) wdt_ticks außerhalb der WD-ISR verändert wird und im Augenblick der Änderung

2.) die Abfrage von wdt_ticks == 0 korrumpierend unterbricht.

Das interessiert mich.

#8001266
Lesenswert?

StefanK schrieb:

Arduino F. soll nun den Beweis liefern, was im Ausgsgangsbeispiel dazu führt, dass

Schau ein bischen über den Tellerrand hinaus. Der TE hat die wdt_ticks Variable als unit16_t deklariert, hat also ziemlich sicher vor, damit auch Zeiträume > 255 Ticks abzuzählen. Dass er jetzt beim Debuggen/Entwickeln erstmal nur die untersten drei Bits von den 16 verwendet hat vermutlich einfach den Grund dass sonst jeder Testlauf ewig braucht.

Sowas gibt schöne Fehler... bis 255 Sekunden funktioniert es immer, bei größeren Zeiträumen kommt ganz selten mal eine Fehlauslösung.

#8001280
Lesenswert?

StefanK schrieb:

Arduino F. soll nun den Beweis liefern, was im Ausgsgangsbeispiel dazu führt, dass

1.) wdt_ticks außerhalb der WD-ISR verändert wird und im Augenblick der Änderung

2.) die Abfrage von wdt_ticks == 0 korrumpierend unterbricht.

Das braucht er nicht. Die Erfahrung zeigt, daß die Statistik das schon hinbekommt, mit Unterstützung vom Murphy. Der Fehler wird auftreten. Irgendwann. Garantiert.

Und die Erfahrung lehrt, daß solche Fehler, die nur alle paar Minuten/Tage/Wochen vorkommen, extrem schwierig zu finden sind. Daher macht man sowas von vornherein richtig.

Oliver

#8001283
Lesenswert?

Εrnst B. schrieb:

Schau ein bischen über den Tellerrand hinaus.

Mache ich, versprochen! Hand aufs Herz. Andererseits will ich mir von Arduino F. auch nicht einreden lassen, dass im oben angehängten Code-Beispiel uint16_t wdt_ticks korrumpiert wird, nur weil er 30 Jahre Erfahrung hat. Einfach die Behauptung in den Raum zu schmeissen und dann ohne Beweis drauf zu beharren reicht mir nicht. Deshalb soll er seine Behauptung mit einem handfesten Beweis untermauern.

...Bits von den 16 verwendet hat vermutlich einfach den Grund dass sonst jeder Testlauf ewig braucht.

Ernst, das ist brilliant! Danke! Du bringst mich auf eine Idee! Ich setze auf WDTO_15MS dann dauert es nicht so lang, bis z.B. 65535 erreicht sind.

Und wenn nur ein tick fehlt, dann Asche auf mein Haupt ;-)

#8001293
Lesenswert?

StefanK schrieb:

Code-Beispiel uint16_t wdt_ticks korrumpiert wird

Das Problem ist, dass der AVR nur 8 Bit auf einmal lesen kann.

Der check "wdt_ticks == 0" wird also in zwei Checks übersetzt, einmal werden die oberen 8 bits auf 0 geprüft, dann die unteren. Oder andersherum, die Reihenfolge ist nicht festgelegt, und liegt im ermessen des Compilers.

Also ist Denkbar: -> wdt_ticks steht auf 0x00FF erster Teil des Checks läuft, High-Byte=0x00. ISR triggert, wdt_ticks++, steht jetzt auf 0x0100 Rücksprung aus der ISR in das Hauptprogram zweiter Zeil des Checks läuft, Low-Byte=0x00. Bedingung erfüllt, LED toggled.

Eigentlich hätte das aber erst in 3 Stunden passieren sollen...

#8001297
Lesenswert?

an StefanK:

Ich habe den Überblick verloren, dachte eigentlich, letztendlich sei nun alles klar. Könnten Sie mir in zwei oder drei einfachen Sätzen erklären, was noch bewiesen werden soll?

Verstehe ich es richtig: Im ursprünglichen Programm kann nichts schief gehen, weil das /sleep/ ('schlafen_gehen') unmittelbar vor dem Ende der while-Schleife steht, die Abfrage 'wdt_ticks == 0' direkt am Anfang, folglich der WDT-Interrupt mit seinem Setzen von wdt_ticks nicht in die (-se eine) Abfrage hineingeraten kann. Dann hätte ich aber, um die Intention zu verdeutlichen, /sleep/ und Abfrage von wdt_ticks unmittelbar hintereinander geschrieben.

#8001304
Lesenswert?

Hallo, StefanK schrieb im Beitrag #8001283:

Deshalb soll er seine Behauptung mit einem handfesten Beweis untermauern.

Stefan, ich glaube du hast nicht richtig verstanden worauf die Vielen, die dir geantwortet haben, hinaus wollen: es geht gar nicht so sehr um deinen speziellen Fall. Es geht vielmehr darum von vorne herein Situationen...

Arduino F. schrieb:

Lernen, was in ein undefined behavier führt und konsequent vermeiden. Also: Sorgfalt und Disziplin.

...zu vermeiden, dir irgend wann "auf die Füße fallen" können/werden. Oder mit anderen Worten:

Rolf schrieb:

Ich nenne diese Strategie "defensive Codierung", zahlt sich immer aus.

Glaub mir, die Leute die dir fundierte Tipps gegeben haben, sind durchweg Leute mit profunden Kenntnissen. Du tust gut daran ihre Tipps zu befolgen.

rhf

#8001308
Lesenswert?

@Ernst B.

Das von Dir geschilderte Szenario träfe zu, wenn die WD-ISR die Abfrage wdt_ticks == 0 genauso unterbricht, wie Du es skizzierst.

Die zeitliche Abfolge im Beispiel Programm ist so:

  • der uC wird am Ende der Hauptschleife schlafen geschickt.
  • WD-ISR weckt den uC, der WDT wird auf z.B. 1s zurückgesetzt
  • die Hauptschleife wartet auf das Ende der WD-ISR
  • die WD-ISR prüft und verändert wdt_ticks
  • die WD-ISR ist fertig
  • die Hauptschleife springt an den Anfang und prüft wdt_ticks == 0
  • am Ende der Hauptschleife wird der uC schlafen geschickt

Wie könnte zum Zeitpunkt der Prüfung von wdt_ticks == 0 die WD-ISR die begonnene Prüfung wdt_ticks == 0 stören? Da ist die vorige ISR schon vorbei und die neue nocht nicht da.

#8001320
Lesenswert?

S. L. schrieb:

an StefanK:

Ich habe den Überblick verloren, dachte eigentlich, letztendlich sei nun alles klar. Könnten Sie mir in zwei oder drei einfachen Sätzen erklären, was noch bewiesen werden soll?

Verstehe ich es richtig: Im ursprünglichen Programm kann nichts schief gehen, weil das /sleep/ ('schlafen_gehen') unmittelbar vor dem Ende der while-Schleife steht, die Abfrage 'wdt_ticks == 0' direkt am Anfang, folglich der WDT-Interrupt mit seinem Setzen von wdt_ticks nicht in die (-se eine) Abfrage hineingeraten kann. Dann hätte ich aber, um die Intention zu verdeutlichen, /sleep/ und Abfrage von wdt_ticks unmittelbar hintereinander geschrieben.

Die ursprünglichen Probleme sind behoben. Das waren zum einen die falsche WD-Konfiguration und die abgewürgte serielle Übertragung. Beide Hinweise zur Behebung kamen von Ihnen.

Ihre Zusammenfassung der zeitlichen Abfolge des Programmablaufs entspricht auch meinem Verständnis.

Verstehe ich Ihren Vorschlag richtig: sleep an den Anfang der Hauptschleife zu setzen? Das wäre problemlos möglich.

@Alle Ich weiß die fundierten Tipps und Ratschläge die hier freundlicher Weise genannt werden sehr zu schätzen und beherzige sie wann immer es erforderlich ist.

Wenn mir aber wie von Arduino F. unterstellt wird, ich hätte von meinem eigenen Code (oder auch sonst) keine Ahnung, dann möchte ich von ihm schon wissen, wodurch es in meinem einfachen Code angeblich zu einem Problem kommt, das mit ATOMIC behoben werden muss. Daher habe ich ihn aufgefordert mir einen Beweis zu liefern.

#8001332
Lesenswert?

an StefanK:

Um Arduino F. zu verstehen, muss man weit in die Vergangenheit zurückgehen: damals wurde die Arduino-Welt von den Meisten noch belächelt, und ein 'Arduino-Fanboy' musste nicht nur Häme&Spott, sondern auch so manche Anfeindung ertragen - so etwas hinterlässt Spuren; zumindest erkläre ich mir so seinen gelegentlichen Umgangston.

Mein Rat an Sie ist, an dieser Stelle den Fall zu schließen, es ist alles gesagt, gute Vor- und Ratschläge gab es zuhauf.

Guten Abend wünscht

S. Landolt

#8001396
Lesenswert?

StefanK schrieb:

Die zeitliche Abfolge im Beispiel Programm ist so

Mag ja sein, dass im Test-Programm im aktuellen Zustand das Timing immer so ist, dass kein Problem auftritt.

Dann kannst du dir die 4 Taktzyklen oder so, die der ATOMIC_BLOCK verursacht, sparen. Aber du sammelst auf die Art "Technical Debt". Irgendwann stellst du was im Program um, fügst eine zweite Wakeup-Source hinzu, hast eine längere Debug-Ausgabe, oder du oder jemand anders übernimmt Code-Teile per Copy&Paste.

Dann kann dir sowas auf die Füße fallen.

Ohne Not würde ich das nicht machen, sondern direkt korrekten Code schreiben.

Und wenn es nötig ist, gehört da m.M.n. ein Kommentar drüber, der erklärt, warum der Code zwar problematisch und unsynchronisiert ist, aber es in diesem speziellen Fall trotzdem funktioniert, solange Nebenbedingungen A, B und C eingehalten werden.

Damit ist dann dem Zukunfts-Stefan in vielen Jahren auch gleich klar, dass das Absicht war, und nicht eine Nachlässigkeit von einem unerfahrenen Programmierer.

Ist natürlich alles kein Muss, nur Hinweise wie du dir Sachen längerfristig erleichtern kannst.

#8001841
Lesenswert?

Εrnst B. schrieb:

Das sind weitsichtige und weise Worte. Um die Wogen zu glätten habe ich das ATOMIC inzwischen implementiert. Will ja weder mir im Jetzt und Hier noch der hoffentlich besseren Version meiner Selbst in der Zukunft im Weg stehen :-)

Da nun der WDT-Mechanismus funktioniert kommt anstatt des LED toggelns natürlich umfangreichere Funktionalität, die EEP-Schreiben, ADC und ev. INT0 enthält. Sollte ich INT0 implementieren ist ATOMIC selbstverständlich Pflicht;-) Um Konflikte zu vermeiden, ist das Ganze an den entscheidenden Stellen mit cli/sei geschützt.

Und ja, wer weiß, wann ich die Funktionalität erweitere oder auf Teile dieses Codes zurückgreife und sie anderswo weiterverwende.

Ist natürlich alles kein Muss, nur Hinweise wie du dir Sachen längerfristig erleichtern kannst.

Das ist das schöne an z.B. Deinen und den ähnlich lautenden Beiträgen vieler anderer Mitglieder hier. Dem Ratsuchenden steht es frei, den erteilten Rat anzunehmen oder abzulehnen, ohne daß eine Ablehnung persönlich genommen wird.

Noch eine kurze Rückmeldung zur Test-Idee von gestern. Ich habe WDTO auf 16ms, MAX_WDT_TICKS=65535 gesetzt und wdt_ticks wie gehabt seriell ausgeben lassen. Ein Durchlauf dauerte so nur ca. 40min. Es gab auch ohne ATOMIC während mehrerer Stunden keinen Glitch.

Schönes WE

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren