automatischer Reset bei Crash mit Watchdog oder Hardware"watchdog"

Ich habe folgendes Problem gehabt:
Meine Jalousie automatisierung sollte mit einem Dämmerungsschalter die jalousien hoch und runter fahren. Der sketch war relativ einfach, aber die Dauer, in der es lief war natürlich immens, so dass der controller immer mal wieder angestürzt ist, also habe ich die overflow von millis() versucht raus zu nehmen, was das problem aber noch verschlechtert hat.

Also wollte ich den Watchdog verwenden, um bei so einem Problem einen restart auszuprobieren, allerdings hat der bei mir nicht funktioniert, Da ich scheinbar den alten Bootloader bei meinem Nano verwende.

Der normale Watchdog funktioniert ungefähr so:

#include <avr/wdt.h>
void setup() {
  //Die Dauer, bis der Watchdog auslösen soll
  //wdt_enable(WDTO_15MS);
  //wdt_enable(WDTO_30MS);
  //wdt_enable(WDTO_60MS);
  //wdt_enable(WDTO_120MS);
  //wdt_enable(WDTO_250MS);
  //wdt_enable(WDTO_500MS);
  //wdt_enable(WDTO_1S);
  //wdt_enable(WDTO_2S);
  //wdt_enable(WDTO_4S);
  wdt_enable(WDTO_8S);
}

void loop() {
  // put your main code here, to run repeatedly:
  wdt_reset();
}

Dabei wird die WDTO_zeit gewartet, falls in diesem Zeitraum kein wdt_reset() durchgeführt wird, resettet sich der µC. Bei mir fing die Power LED aber nur an zu blinken und ein Manueller Reset hat auch nicht mehr geholfen, weil ich wohl noch den alten Bootloader drauf habe.

Diese Kompatibilitätsprobleme haben mich so gestört, dass ich ab jetzt nur noch Hardware Watchdogs verwenden werde. Das möchte ich hier kurz beschreiben.
Als erstes habe ich für einen Testaufbau einen Sockel an den NANO gelötet:

Damit habe ich dann den Pin 13 überwacht und im eigentlichen sketch eben diesen pin immer wieder geändert, damit mein Hardware Watchdog wusste, dass der haupt Controller noch läuft.

Als Watchdog habe ich den ATTINY85 genommen und folgenden Sketch auf ihn geladen:

int zeit = 0, vorher, jetzt, wartezeit = 5000;
void setup() {
  pinMode(3, OUTPUT);
  digitalWrite(3, HIGH);
  pinMode(1, INPUT_PULLUP);
  vorher = digitalRead(1);
}

void loop() {
  delay(1);
  jetzt = digitalRead(1);
  if (vorher == jetzt) {
    zeit += 1;
  }
  else {
    zeit = 0;
    vorher = jetzt;
  }
  if (zeit >= wartezeit) {
    digitalWrite(3, LOW);
    delay(100);
    digitalWrite(3, HIGH);
    zeit = 0;
  }
}

Die Dauer kann man dann mit der variable wartezeit einstellen. Bei mir reicht diese grobe Einstellung völlig aus.

Im nachhinein wollte ich aber noch sehen, ob der Watchdog irgendwann mal ausgelöst hat, so habe ich noch eine weitere LED angelötet, die nach einem Auslösen an geht:

Der Code dazu sieht so aus:

int zeit = 0, vorher, jetzt, wartezeit = 5000;
void setup() {
  pinMode(3, OUTPUT);
  digitalWrite(3, HIGH);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  delay(1000);
  digitalWrite(4, LOW);
  pinMode(1, INPUT_PULLUP);
  vorher = digitalRead(1);
}

void loop() {
  delay(1);
  jetzt = digitalRead(1);
  if (vorher == jetzt) {
    zeit += 1;
  }
  else {
    zeit = 0;
    vorher = jetzt;
  }
  if (zeit >= wartezeit) {
    digitalWrite(3, LOW);
    delay(100);
    digitalWrite(3, HIGH);
    zeit = 0;
    digitalWrite(4, HIGH);
  }
}

Somit bekommt man sehr gute Kontrolle über diese Funktion und kann sogar die interne LED blinken sehen, wenn der Controler noch läuft. wenn sich das so bewährt, wird die richtige Platine mit dem Layout bestellt und fertig.

auf dass die nächsten Googler fündiger werden, als ich.

MfG

Und wenn du einfach den neuen Bootloader auf deinen Nano brennst?

Ein Controller ist eigentlich gebaut daß er im Dauerbetrieb laufen kann/können muß. Wenn er dennoch blockiert kann das ein Softwarfehler, ein Hardwarefehler oder Störungen sein.
Ein Watchdog löst eigentlich nur die Auswirkingen und beseitigt nicht den Grund der Abstürze.

Grüße Uwe

Also ich habe etliche Projekte mit Mikrocontrollern (wie zum Beispiel Arduino Nano) die ununterbrochen laufen und das seit mehreren Jahren. Und ich kann mich nicht erinnern, dass da jemals ein Programm "abgestürzt" wäre.
Ja, es gab hin und wieder Probleme mit Störungen - meist hervorgerufen durch "stromhungrige Schaltvorgänge", etwa wenn ein Funkmodul (z.B. "GSM-Handy") kurzzeitig viel Strom wollte, was ein unterdimensioniertes Netzteil nicht liefern konnte und ähnlichem.
Aber da gilt es, die Ursache zu suchen um das Problem zu lösen, z.B. durch Entstörmaßnahmen, Pufferkondensatoren, leistungsfähigeres Netzteil etc.
Einen Watchdog sehe ich dabei nur als (schlechte) Notlösung.

Einen Watchdog sehe ich dabei nur als (schlechte) Notlösung.

Ich entnehme deinem Posting, dass dir sehr wohl bewusst ist, dass Störungen zu einem Betrieb mit unangenehmen Folgen führen kann.

Ich stimme dir zu, dass man Störungen am besten nahe der Ursache "unterbindet".

Leider kann niemand etwas über ALLE möglichen Störungen sagen, welche erst in der Zukunft stattfinden werden. Auch ist die Korrektheit eines Programms kaum beweisbar.

Ein Entwickler, welcher von sich behauptet, einen störungsfreien Betrieb sicherstellen zu können, wird sich vermutlich irren.

Der WDT hilft nicht gegen Fußpilz, und sonstiges Unwohlsein, aber er kann helfen schwere Schäden in Grenzen halten. Zumindest in zeitliche Grenzen. Es ist die letzte Rettung.

Ja, " automatischer Betrieb" und "Schadensminimierung" sind die Umstände/Gründe, welche zwangsläufig zum WDT führen.

Generationen von SPS Programmieren werden gezwungen, sich dem Diktat der WatchDogs zu unterwerfen.
Meiner Meinung nach: Zu Recht!


Im Falles des Nanos, würde ich auch wohl einen frischen Bootloader aufspielen, und den internen WDT nutzen.

Ansonsten gibt es auch noch externe WDT Bausteine.

ok, so genau wollte ich eigendlich gar nicht auf das eigentliche Projekt eingehen, da ich den Watchdog für allgemein sehr interessant halte, aber ok.

Also zu der Jalousiensteuerung muss man sagen, der sketch ist so unfassbar simpel, dass da eigentlich kein Programmierfehler drin sein kann:

if (jalousien == false && anderung == true && hell == true && digitalRead(autoschalter) == false && betatigt == false) {
    digitalWrite(hoch, HIGH);
    b = 1;
    zeit = millis();
    betatigt = true;
  }

und so werden nur die eingänge abgefragt und bei bestimmten Eingangskonstellationen die ausgänge geschaltet.

Jetzt kommt aber das Problem, ich habe, um die Parameter mit zu überwachen, ein I²C Display 0,96" 128x64 OLED Display angeschlossen, um die Parameter zu überwachen. Das verursacht ab und zu Abstürze.
Wenn ich hinten auf die Platine fasse... Absturz
Wenn ich am Stecker wackle... Absturz

Da das ja Jahre unbeaufsichtigt laufen soll, möchte ich nicht, weil ein Overflow des millis() passiert(den man natürlich im Programm raus filtern kann) oder irgendwas anderes, wie ein induziertes Störsignal in der 60m NYM Leitung, den Controller lahm legt.

Mit dem neuen Bootloader: Das habe ich noch nie gemacht, habe erstmal nur ganz stumpf alles in der IDE eingestellt und dann bootloader brennen gedrückt, wie ich das auch für die ATTINYs mache, das hat nicht geklappt, da habe ich das aber auch direkt verworfen. Die Gefahr ist mir dann auch zu hoch, dass ich aus versehen den WDT auf 15ms setze und der resettet sich dann ständig usw. Bei dem Hardware Watchdog kann ich das IC raus nehmen und ohne watchdog testen und ich habe eine LED, die an geht, die signalisiert, da ist mal resettet worden, so dass die Steuerung weiter läuft, ich aber noch sehen kann, dass im Programm noch ein Fehler ist.

Ich finde diese Lösung ideal, zumal ich vom Platz nicht wirklich eingeschränkt bin, da ich immer das Gehäuse RND RND 455-00065 nutze:

30064282-01.JPG

30064282-01.JPG

Wenn dein System durch Berührung abstürzt, hast du einen groben Fehler bei der Hardwareentwicklung gemacht.
Z.B. solltest du Verbindungen in Livesystemen löten und nicht einfach nur mit Jumperkabel stecken.
Das dein System abstürzt, wenn du das Display anfasst, darf auch nicht passieren.
Wo dein Fehler liegt, ist von hier schwer zu sagen, da wir dein Projekt kennen.

Und in deinem Sketch sehe ich keine eindeutige Verwendung von millis außer einer eimaligen Übergabe.

es ist momentan noch in der Prototypphase, also ist es Freiluftverdrahrung mit eben diesen Jumper Kabeln... also grob ja, fehler nicht direkt :slight_smile:

Wobei ich die Frontplattenbauteile immer mit Steckern verbinden werde. Als mensch aus der Praxis legt man doch wert auf Wartungsfreundlichkeit

Das ist ja auch nicht der ganze sketch.

hier ist das wieder abstellen:

jetztzeit = millis();
  //if (zeit >= jetztzeit)
  //  jetztzeit += 4294967295;

  lauft = ((jetztzeit - zeit) / 1000);

  if (lauft >= Dauer && betatigt == true) {
    digitalWrite(hoch, LOW);
    digitalWrite(runter, LOW);
    b = 0;
    a = 0;
    jalousien = !jalousien;
    betatigt = false;
  }

oben will ich den overflow von millis() abfangen zeit und jetztzeit sind long long. Das habe ich weg kommentiert, weil das zu ständigen Abstürzen geführt hat, ich weiß noch nicht warum. Aber so läuft es momentan komplett stabil, noch keine Abstürze aufgetreten.

oben will ich den overflow von millis() abfangen

Verwendest du Laufzeiten, welche länger als 49,x Tage sind?
Nein!
Also musst du dich auch nicht um millis() Überläufe kümmern.

Es ist schön zu wissen, dass sie existieren.
Aber bei einer korrekten Berechnung können sie dich nicht stören.


Außerdem sind deine Codefragmente untestbar.
Ohne Zusammenhang.

()

Ja, es kann natürlich passieren, dass nach 49,x Tagen die Jalousien runter gehen sollen und dann die Jetztzeit niedriger ist als zeit, damit würden die jalousien 49,x tage versucht runter zu machen, insofern muss man das ja schon abfangen.

und nun zu meinem Code. Ich bin kein gelernter Programmierer, ich habe mir alles selbst beigebracht und entsprechend aus Beispielsketches zusammen kopiert. Aus diesem Grund sieht der code richtig scheiße aus und das ist mir sogar peinlich... Ich erreiche zwar immer das, was ich möchte, aber die Wege sind evtl manchmal verworren und würden jeden ordentlichen Programmierer zur Verweifelung Treiben... Ein Beispiel, ist so schlimm, dass es wieder lustig ist: Ich kannte keine Arrays, also habe ich variablen angelegt:

int aa=0,ab=10,ac=100;
int ba=1,bb=11,bc=110;
int ca=2,cb=12,cc=120;

Das war mein Array... Aber so ist das halt als autodidakt.

also hier ist mein ganze sketch, mit den oben genannten vorbehalten:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

int autoschalter = 3, morgen = 4, lichttaster = 5, dammerung = 6;
int hoch = 7, runter = 8, licht = 9, lebt =13;
bool lichtgedruckt, anderungtaster, anderungmorgen, morgenvorher, betatigt, jalousien, lichtan, dammerungvorher, hell, lichtvorher = false, dammerungvorherlicht, lichttastervorher, anderung, anderunglicht;
long long zeit;
long long jetztzeit;
int Dauer = 30;
unsigned long lauft;
bool a, b, c;

void setup() {
  pinMode(lebt, OUTPUT);
  digitalWrite(lebt, HIGH);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.clearDisplay();
  display.println("booting...");
  display.display();
  pinMode(hoch, OUTPUT);
  pinMode(runter, OUTPUT);
  pinMode(licht, OUTPUT);
  pinMode(autoschalter, INPUT_PULLUP);
  pinMode(lichttaster, INPUT_PULLUP);
  pinMode(dammerung, INPUT_PULLUP);
  pinMode(morgen, INPUT_PULLUP);
  display.clearDisplay();
  display.println("booting finished...");
  display.display();
  display.clearDisplay();
  hell=digitalRead(dammerung);
  jalousien =hell;
  lichtan= !hell;
  digitalWrite(licht, lichtan);
  digitalWrite(lebt, HIGH);
  delay(100);
  digitalWrite(lebt, LOW);
}

void loop() {
  digitalWrite(lebt, HIGH);
  delay(100);
  digitalWrite(lebt, LOW);
  display.setCursor(0, 1);
  display.print("jalousien sind ");
  if (jalousien == 1)
    display.println("oben");
  else
    display.println("unten");

  display.setCursor(0, 11);
  display.print("es ist ");
  if (hell == 1)
    display.println("hell");
  else
    display.println("dunkel");

  display.setCursor(0, 21);
  display.print("Licht ist ");
  if (lichtan == 1)
    display.println("an");
  else
    display.println("aus");

  display.setCursor(0, 31);
  display.print("Hoch ist ");
  if (b == 1)
    display.println("an");
  else
    display.println("aus");

  display.setCursor(0, 41);
  display.print("Runter ist ");
  if (a == 1)
    display.println("an");
  else
    display.println("aus");

  display.setCursor(0, 51);
  display.print("dauer ist ");
  display.println(lauft);
  display.display();
  display.clearDisplay();

  hell = digitalRead(dammerung);
  if (hell == dammerungvorher) {
    anderung = false;
  }
  else {
    anderung = true;
    dammerungvorher = !dammerungvorher;
  }

  if (digitalRead(morgen) == morgenvorher) {
    anderungmorgen = false;
  }
  else {
    anderungmorgen = true;
    morgenvorher = !morgenvorher;
  }





  if (jalousien == false && anderung == true && hell == true && digitalRead(autoschalter) == false && betatigt == false) {
    digitalWrite(hoch, HIGH);
    b = 1;
    zeit = millis();
    betatigt = true;
  }
  if (jalousien == true && anderung == true && hell == false && betatigt == false && digitalRead(morgen) == true) {
    digitalWrite(runter, HIGH);
    a = 1;
    zeit = millis();
    betatigt = true;
  }
  if (jalousien == false && digitalRead(morgen) == false && betatigt == false && anderungmorgen == true) {
    digitalWrite(hoch, HIGH);
    b = 1;
    zeit = millis();
    betatigt = true;
  }
  if (jalousien == true && digitalRead(morgen) == false && betatigt == false && anderungmorgen == true) {
    digitalWrite(runter, HIGH);
    a = 1;
    zeit = millis();
    betatigt = true;
  }

  jetztzeit = millis();
  //if (zeit >= jetztzeit)
  //  jetztzeit += 4294967295;

  lauft = ((jetztzeit - zeit) / 1000);

  if (lauft >= Dauer && betatigt == true) {
    digitalWrite(hoch, LOW);
    digitalWrite(runter, LOW);
    b = 0;
    a = 0;
    jalousien = !jalousien;
    betatigt = false;
  }


  if (hell == dammerungvorherlicht) {
    anderunglicht = false;
  }
  else {
    anderunglicht = true;
    dammerungvorherlicht = !dammerungvorherlicht;
  }

  if (anderunglicht == true && hell == false && lichtan == false) {
    digitalWrite(licht, HIGH);
    lichtan = true;
  }
  if (anderunglicht == true && hell == true && lichtan == true) {
    digitalWrite(licht, LOW);
    lichtan = false;
  }
  if (digitalRead(lichttaster) == lichtgedruckt) {
    anderungtaster = false;
  }
  else {
    anderungtaster = true;
    lichtgedruckt = !lichtgedruckt;
  }
  if (anderungtaster == true && lichtan == false && digitalRead(lichttaster) == LOW) {
    lichtan = true;
    digitalWrite(licht, HIGH);
    delay(1000);
    anderungtaster == false;
  }
  if (anderungtaster == true && lichtan == true && digitalRead(lichttaster) == LOW) {
    lichtan = false;
    digitalWrite(licht, LOW);
    delay(1000);
    anderungtaster == false;
  }
}

Also bitte nicht Auslachen oder Bannen :slight_smile:

Ja, es kann natürlich passieren, dass nach 49,x Tagen die Jalousien runter gehen sollen und dann die Jetztzeit niedriger ist als zeit, damit würden die jalousien 49,x tage versucht runter zu machen, insofern muss man das ja schon abfangen.

Hmmm ...

Ja, das sachte ich ja...
Wenn die Intervalle kleiner als 49.X Tage sind, hast du kein Problem.
Wenn die Intervalle länger als 49,X Tage sind, verwendest du mit unsigned long den falschen Datentype.
long long ist auch nicht recht passend.

Dazu passen auch die Meldungen (teilweise)

E:\Programme\arduino\portable\sketchbook\sketch_jul23b\sketch_jul23b.ino: In function 'void loop()':
E:\Programme\arduino\portable\sketchbook\sketch_jul23b\sketch_jul23b.ino:144:13: warning: comparison of integer expressions of different signedness: 'long unsigned int' and 'int' [-Wsign-compare]
  144 |   if (lauft >= Dauer && betatigt == true) {
      |       ~~~~~~^~~~~~~~
E:\Programme\arduino\portable\sketchbook\sketch_jul23b\sketch_jul23b.ino:181:20: warning: statement has no effect [-Wunused-value]
  181 |     anderungtaster == false;
      |     ~~~~~~~~~~~~~~~^~~~~~~~
E:\Programme\arduino\portable\sketchbook\sketch_jul23b\sketch_jul23b.ino:187:20: warning: statement has no effect [-Wunused-value]
  187 |     anderungtaster == false;
      |     ~~~~~~~~~~~~~~~^~~~~~~~

Ich sehe bei dir keine Zeiten, welche länger als 49.X Tage sein/werden könnten.

doch auch wenn kürzere Intervalle da sind oder?

stellen wir uns vor, zum Zeitpunkt 4294967000 wird es dunkel, dann macht

if (jalousien == true && anderung == true && hell == false && betatigt == false && digitalRead(morgen) == true) {
    digitalWrite(runter, HIGH);
    a = 1;
    zeit = millis();
    betatigt = true;
  }

die Variable
zeit = 4294967000
a = 1
betatigt = true

wenn es nun weiter läuft, evtl 2-3 loops, da die dauer nicht erreicht wurde, ist millis() =15, dann kommt das Programm da an:

jetztzeit = millis();
  //if (zeit >= jetztzeit)
  //  jetztzeit += 4294967295;

  lauft = ((jetztzeit - zeit) / 1000);

  if (lauft >= Dauer && betatigt == true) {
    digitalWrite(hoch, LOW);
    digitalWrite(runter, LOW);
    b = 0;
    a = 0;
    jalousien = !jalousien;
    betatigt = false;
  }

Danach ist Variable
jetztzeit = 15
lauft = ((15-4294967000)/1000)=-4294967

und damit ist folgende Bedingung:
if (lauft >= Dauer && betatigt == true)

if (-4294967 >= 30 && true == true)

und um die Jalousien wieder abzuschalten erlangen wir erst wieder einen wert von 30 nach 4294967000 millisekunden, aus desem Grund muss bei einem Overflow, der natürlich erreicht ist, sobalt die einschaltzeit höher ist, als die aktuelle zeit, der wert 4294967295 addiert werden.

Wenn Du immer betrachtest:

if (aktuelleMillis - letzteMillis >= Intervall) tuWas();

Dann hast Du nie Probleme.
Wenn Du da welche siehst, hast Du das Verhalten von unsigned nicht verstanden.

Zum Verständnis habe ich das mal mit unsigned char (aka byte) nachgebildet, da das schneller beendet:

unsigned char aktuelleMillis = 0, letzteMillis = 0, dauer = 10;
int cnt = 0;
void setup() {
  Serial.begin(115200);
  Serial.println("Start");
  Serial.println("Initialwerte:");
  Serial.print("Dauer = "); Serial.println(dauer);
  Serial.print("letzte = "); Serial.println(letzteMillis);
  Serial.print("aktuell"); Serial.print('\t'); Serial.print("Diff."); 
  Serial.print('\t'); Serial.println("letze");
}

void loop() {
  if (cnt <= 300) { // Damit das nicht ewig läuft
    if ((unsigned char)(aktuelleMillis - letzteMillis) >= dauer) {
      // tu was
      Serial.print(aktuelleMillis); 
      Serial.print('\t'); Serial.print((unsigned char)(aktuelleMillis - letzteMillis));
      Serial.print('\t'); Serial.print(letzteMillis); Serial.println("\tAktion"); 
      letzteMillis = aktuelleMillis;
    }   
    else {
      Serial.print(aktuelleMillis); Serial.print('\t'); 
      Serial.println((unsigned char)(aktuelleMillis - letzteMillis));
      
    }  
    aktuelleMillis++;
    cnt++;
  }    
}

Schau Dir mal den Ablauf an.

Gruß Tommy

doch auch wenn kürzere Intervalle da sind oder?

Ein Überlauf wird dann automatisch kompensiert, wenn man "richtig" rechnet.

stellen wir uns vor, ...

:grin: Deine Vorstellung benötigt evtl. eine Justage :grin:

--
Die beiden Probleme "statement has no effect" schon behoben?

Ja, die sind behoben, danke, einfach ein = zu viel

Oh ja, habe es als unsigned deklariert, das ändert aber nichts an der tatsache, dass

lauft = ((15-4294967000)/1000)=-4294967 = 0 ist oder?

Aber auch bei

if (aktuelleMillis - letzteMillis >= Intervall) tuWas();

kann doch das passieren:
if (15 - 4294967280 >= 30000)

Und dann löst der nicht mehr aus...

ansonsten wenns mehr als 49,x Tage Intervalle sein sollen zb bis zu 35 Jahre:
https://forum.arduino.cc/index.php?topic=441162.0
https://www.faludi.com/2007/12/18/arduino-millis-rollover-handling/

McKaiver:
Aber auch bei

if (aktuelleMillis - letzteMillis >= Intervall) tuWas();

kann doch das passieren:
if (15 - 4294967280 >= 30000)

Und dann löst der nicht mehr aus...

15 - 4294967280 = 31
16 - 4294967280 = 32
usw

Beweis:

void setup() 
{
 Serial.begin(9600);
 Serial.println(15UL - 4294967280UL);
}

void loop() 
{

}

Tipp:
Immer, wenn du meinst, dass dich der Überlauf des millis() stört, erhältst du durch die Subtraktion automatisch einen Unterlauf.
Immer, Unweigerlich und kostenlos.
Dieser Unterlauf kompensiert den (beängstigenden) Überlauf.

Und damit existiert das Problem nur in der Projektion, und nicht in der Realität.

McKaiver:
if (15 - 4294967280 >= 30000)

Du hast Dir den Dir gegebenen Beispielsketch zur Erläuterung nicht angeschaut / ihn nicht verstanden.

Gruß Tommy

Ja, habe leider momentan keinen Zugriff auf nen Arduino, werde ich aber noch testen

Tommy56:
Zum Verständnis habe ich das mal mit unsigned char (aka byte) nachgebildet, da das schneller beendet:

unsigned char aktuelleMillis = 0, letzteMillis = 0, dauer = 10;

int cnt = 0;
void setup() {
  Serial.begin(115200);
  Serial.println("Start");
  Serial.println("Initialwerte:");
  Serial.print("Dauer = "); Serial.println(dauer);
  Serial.print("letzte = "); Serial.println(letzteMillis);
  Serial.print("aktuell"); Serial.print('\t'); Serial.print("Diff.");
  Serial.print('\t'); Serial.println("letze");
}

void loop() {
  if (cnt <= 300) { // Damit das nicht ewig läuft
    if ((unsigned char)(aktuelleMillis - letzteMillis) >= dauer) {
      // tu was
      Serial.print(aktuelleMillis);
      Serial.print('\t'); Serial.print((unsigned char)(aktuelleMillis - letzteMillis));
      Serial.print('\t'); Serial.print(letzteMillis); Serial.println("\tAktion");
      letzteMillis = aktuelleMillis;
    } 
    else {
      Serial.print(aktuelleMillis); Serial.print('\t');
      Serial.println((unsigned char)(aktuelleMillis - letzteMillis));
     
    } 
    aktuelleMillis++;
    cnt++;
  }   
}



Schau Dir mal den Ablauf an.

Gruß Tommy

Habe mir das mal angeschaut... wie kann das sein?

erst ist das passend mit der Dauer:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10 0 Aktion

und nach dem ersten Überlauf interpoliert der das selbstständig?
251 1
252 2
253 3
254 4
255 5
0 6
1 7
2 8
3 9
4 10 250 Aktion

in der Zeile steht es ja, dass es eigentlich nicht auslösen darf:
4 10 250 Aktion

letzte millis sind 250 aktuelle millis sind 4 und damit ist ((aktuelleMillis - letzteMillis) >= dauer) ja -246 und damit 0 oder setzt er obwohl es in unsigned ist das Führungsbit auf 1, sodass es -246 ist, aber mit 1000 0100 unsigned als 132 erkannt wird?

Ich habe nicht damit gerechnet, dass der ?compiler? so smart ist, oder sit es einfach nur logik, die ich noch nicht verstanden habe?