mein aktuelles Projekt erweist sich, für mich, als komplizierter als gedacht.
Das fertige Projekt soll nur drei Ausgänge zeitlich steuern. Die komplette Eingabe erfolgt über einen Encoder, der auch einen Taster beinhaltet. Dargestellt wird es, erst einmal, über ein 16x2 Zeichen Display.
Das Hauptproblem ist nun die Eingabe.
Darstellung des Displays: QM1 QM2 QM3 ------------ Aktoren
000 000 000 min. ------- Zeit
Wird an dem Encoder gedreht, soll damit, als erstes, der zu nutzende Ausgang ausgewählt werden (1-3). Die derzeitige Auswahl soll mittels blinken dargestellt werden, also soll dann eine der "000" blinken. Mit der Bestätigung, durch das Drücken des Tasters, soll als nächstes die Zeit eingestellt werden. Mit dem erneutem drehen des Encoders soll sich also "000" ändern. Wird länger als zwei Sekunden nicht gedreht, soll, als Hinweis, die Aktuell eingestellte Zeit anfangen zu blinken.
Ich drehe mich da irgendwie im Kreis, da der Variablenwert, der über den Encoder bestimmt wird, im ersten Step eine Begrenzung von 1-3 haben soll (Drei Aktoren). Initial steht der Wert auf 0, wenn keine Auswahl zu treffen ist. danach soll der Einstellbereich zwischen 0-255 liegen, da ich den Datentyp byte gewählt habe, weil mir max. 255min. reichen. Dann muss noch das zeitabhängige Blinken rein.
Könnt ihr mir hier ein paar Ideen liefern, wie das am besten in C++ (Arduino) zu realisieren wäre?
Die derzeitige Auswahl soll mittels blinken dargestellt werden, also
soll dann eine der "000" blinken. Mit der Bestätigung, durch das Drücken
des Tasters, soll als nächstes die Zeit eingestellt werden. Mit dem
erneutem drehen des Encoders soll sich also "000" ändern. Wird länger
als zwei Sekunden nicht gedreht, soll, als Hinweis, die Aktuell
eingestellte Zeit anfangen zu blinken.
Unlogisches kann ein uC nicht umsetzen, kein Wunder dass du es nicht programmieren kannst.
Wenn nach Kanalauswahl schon die 000 blinkt, kann sie nicht nach 2 Sekunden zu blinken anfangen, denn sie blinkt schon.
Deine halbgare Beschreibung übersieht wann es mit dem Blinken wieder aufhören soll.
Und schön, dass man die Zeit verstellen kann, eine Bestätigung der eingestellten Zeit durch Knopfdruck soll offenbar nicht erfolgen, ein Abbrechen der Neueinstellung "ah, wenn ich nun drehe ändert sich das, wollte ich gar nicht" ist damit nicht vorgesehen und nicht möglich.
Übrigens gibt es auf solchen Displays auch einen blinkenden Cursor, den könnte man ja auch nutzen.
Soll immer dann, bis zur fertigen Eingabe, anfange zu blinken, wenn in der Auswahl des Ausgangs und der Auswahl der Zeit länger als zwei Sekunden keine Änderung erfolgt. Tritt eine Änderung ein, hört es auf zu blinken.
Ich frage Drehencoder so ab:
Wird der Encoder betätigt, springt das Programm in eine Interrupt-Routine.
Dort wird festgestllt, was mit dem Encoder angestellt wurde: 1* rechts gedreht = R-Flag gesetzt, 1* links gedreht = L-Flag gesetzt, gedrückt = T-Flag gesetzt.
Und wieder raus aus der Interrupt-Routine.
Das Hauptprogramm wartet an den entsprechenden Stellen, welche Flags gesetzt wurden, addiert oder subtrahiert 1 zum Flagzähler, löscht die Flags, macht etwas anhand des Inhalts des Flagzählers und wartet dann wieder auf neue Flags.
Übrigens gibt es auf solchen Displays auch einen blinkenden Cursor, den
könnte man ja auch nutzen.
Und darüber hinaus gehendes Blinken wird bei HD44780-kompatiblen Anzeigen ziemlich ärgerlich. Dann darf man abwechselnd timer-gesteuert die zu blinkenden Ziffern und entweder
Space
Block-Zeichen (0xFF), oder
im CG RAM erzeugte inverse Darstellung des jeweils zu blinkenden Zeichens
schreiben. Letzteres ist durch das CG RAM auf 4 oder 8 Zeichen gleichzeitig begrenzt.
Da blinken auch noch hässlich aus sieht werden statt dessen oft Marker verwendet:
1
<000> 000 000
2
oder
3
>000< 000 000
4
oder
5
[000] 000 000
sind relativ gängige Versionen.
Dazu nach Auswahl den Cursor (blinkend oder nicht) auf die letzte Ziffer der markierten Zahl setzen.
Einen für die Eingabe, einen für die Ausgabe auf dem Display und dann noch jeweils einen für die drei Ausgänge.
Die Kommunikation zwischen diesen findet über Variablen statt. Zum Beispiel ein boolean mit dem Namen "zahlSollBlinken".
Versucht nicht, die Eingabe mit der Ausgabe in einem Stück Code zu kombinieren. Das mag einem anfangs einfacher vorkommen, aber man verfährt sich damit ganz schnell in eine Sackgasse. Der Knackpunkt ist, dass Eingaben zu jeder beliebigen Zeit stattfinden können, dass sie beliebig lange dauern können und dass es vielfältige Abbruch-Bedingungen gibt.
Nein, ganz falsch. Ich glaube du hast die Artikel über Zustandsautomaten nicht verstanden. Es hätte mich auch gewundert, wenn du das in so kurzer Zeit verstanden und für dein Projekt umgesetzt hättest. So etwas braucht mehr Zeit.
Nö. Du brauchst eine Statemachine, wie es schon ein halbes Dutzend Mal gesagt wurde. Dort bestimmt dein Zustand, wie auf Eingaben reagiert wird. Man braucht natürlich noch andere Variablen für diverse Informationen.
3x für die drei Zahlen
1x für den Zustand (Position) auf dem LCD
1x für den aktuellen Modus (Cursor bewegen oder Zahl einstellen)
Man braucht Hilfsfunktionen für das Auslesen des Drehgebers, welche teilweise in einem Timer-Interrupt läuft. Das kann man ggf. auch mit dem Würg-Around millis() ganz Arduino-konform machen. Ebenso braucht man eine Funktion zur Entprellung des Tasters, ebenfalls in einem Timer Interrupt, ggf. dem gleichen. Damit kann man quasi parallel die Eingabe des Drehgebers erfassen und in der State machine dann auswerten.
Tastendruck wechselt zwischen den Modi Cursor bewegen und Zahl verändern. Das kann man ggf. sogar mit 2 State machines machen.
Drehgeberbewegung verändert je nach aktivem Modus die aktuelle Zahl oder den Cursor.
Hier mal ein Beispiel zur Nutzung eines echten Timer-Interrupts, ist halt AVR-spezifisch. Für eine Drehgeberauswertung reichen 10ms Periodendauer eher nicht, da muss man eher auf 1ms runter. Das geht nur sehr eingeschränkt mit der millis() Variante.
Ich frage Drehencoder so ab:
Wird der Encoder betätigt, springt das Programm in eine
Interrupt-Routine.
Das macht man sinnvollerweise NICHT so, siehe Drehgeber. Wurde schon millionenfach durchgekaut.
Dort wird festgestllt, was mit dem Encoder angestellt wurde: 1* rechts
gedreht = R-Flag gesetzt, 1* links gedreht = L-Flag gesetzt, gedrückt =
T-Flag gesetzt.
Und wieder raus aus der Interrupt-Routine.
Nö.
Das Hauptprogramm wartet an den entsprechenden Stellen, welche Flags
gesetzt wurden, addiert oder subtrahiert 1 zum Flagzähler, löscht die
Flags, macht etwas anhand des Inhalts des Flagzählers und wartet dann
wieder auf neue Flags.
Nicht sinnvoll.
Man liest besser die Anzahl der Schritte seit dem letzten Mal aus und verarbeitet die. Meist ist es nur +/-1. Wenn aber mal "länger" (ein paar Dutzend bis hunderte Millisekunden) nichts gelesen werden konnte, weil lange Funktionen ausgeführt werden mussten (Bildaufbau, lange Berechnung, Welt retten, Pi nachrechnen ;-), dann sind dauch auch mal sehr viele Schritte und trotzdem hat man keinen Schritt verloren. Deine Methode ist darauf angewiesen, daß man JEDEN Schritt immer sehr schnell im Hauptprogramm erkennt und auswertet. Das ist oft nicht möglich aber auch nicht nötig.
Danke erst einmal für die ganze Hilfe. Das mit der Entprellung ist bekannt. Das hatte ich in der Vergangenheit, beim Encoder, mit RC-Gliedern gelöst. Funktioniert wunderbar!
Hatte ich berechnet und die Feinabstimmung mit einem Oszilloskop vorgenommen. Glaube die mögliche Auflösung lag dann bei 10ms. mehr war nicht möglich, da die Nachprellung länger wäre.
Ich befasse mich jetzt erstmal intensiv mit den Statemachines und melde mich dann gerne mit dem Ergebnis wieder.
Eine Sache würde ich noch gerne anmerken. Ich schätze es, das ihr mir helft, einige zerreißen mich aber auch gefühlt, sind herablassend - so fühlt es sich für mich zumindest an. Wäre freundlich, wenn ich da mehr drauf achtet.
Wenn dein Display den Zustand "Blinkend" nicht kennt, musst du das in Software umsetzen. Für jeden blinkenden Bereich sind das mindestens zwei Zustände.
Ich kann an deiner Tabelle nicht klar erkennen, welches Ereignis welche Reaktion auslöst.
Wenn die Tabelle in Ordnung ist, lässt sie sich 1:1 in ein Diagramm überführen, dass alle möglichen Ereignisse darstellt.
Ich kann an deiner Tabelle nicht klar erkennen, welches Ereignis welche
Reaktion auslöst.
Naja, ist nicht so ganz gut dargestellt. Es gehören jeweils die Spalten
Encoder l/r und nächster Zustand sowie encoder button == 1 und nächster Zustand zusammen. Ist ein wenig irritierend. Könnte man anders darstellen. Siehe Anhang.
Doofe Frage: Nach welchen Kriterien packe ich etwas zusammen in einen
Switch-Case-Anweisungsblock und was getrennt?
Na alle Möglichkeiten, welche die Variable enthalten kann. Allen voran natürlich der Zustand des Automaten. Man nimmt dafür möglichst aber keine Zahlen sondern Namen, sei es per #define oder enum. Das ist besser lesbar. Siehe Statemachine.
Allen voran natürlich der Zustand des Automaten. Man nimmt dafür möglichst
aber keine Zahlen sondern Namen, sei es per #define oder enum. Das ist
besser lesbar.
Besser 'enum'. Oft ändert man ja die Zustände während der Entwicklung mehrmals. Dann merkt es der Compiler, wenn man im 'switch (state)' ein 'case' einzufügen oder zu löschen vergessen hat. Merkt er es nicht, muss man (lange) den Fehler suchen.