Guten Tag zusammen, 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? Ich komme nicht weiter. Danke und Gruß
Statemachine und dann kannst du dir in jedem Step neue Grenzbereiche für den Encoder/Darstellung laden.
Markus O. schrieb: > 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.
Markus O. schrieb: > Meinst du (bspw.) Switch Case? ja z.B. Hier mal ein kleiner Einblick: https://www.mikrocontroller.net/articles/Statemachine
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 hatte die Beschreibung nicht fortgeführt, da sich meine Probleme bis zu diesem Punkt beschränken.
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.
Michael B. schrieb: > Ü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.
:
Bearbeitet durch User
Hannes J. schrieb: > <000> 000 000 > oder >>000< 000 000 > oder > [000] 000 000 Hatte ich auch schon drüber nachgedacht, jedoch reicht der Platz im Display nicht?!
Hannes J. schrieb: > * Block-Zeichen (0xFF), oder > * im CG RAM erzeugte inverse Darstellung des jeweils zu blinkenden > Zeichens So tief bin ich da nicht drin. Arbeite nicht mit hex-zahlen und hab auch keine Ahnung von dem RAM und co. und dem einzelnen Setzen von Bits.
Markus O. schrieb: > Könnt ihr mir hier ein paar Ideen liefern, wie das am besten in C++ > (Arduino) zu realisieren wäre? Adam P. schrieb: > Hier mal ein kleiner Einblick: > https://www.mikrocontroller.net/articles/Statemachine Volle Zustimmung. Du brauchst Zustandsautomaten. 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. Hier ist mein Artikel zum Thema: http://stefanfrings.de/multithreading_arduino/index.html
Stefan F. schrieb: > Volle Zustimmung. Du brauchst Zustandsautomaten. Vielen Dank! So in etwa?
1 | void loop() |
2 | {
|
3 | if (millis() - FSM_lastTime >= FSM_interval) |
4 | {
|
5 | if(Wenn die Auswahl getroffen ist) |
6 | TimeView(); |
7 | FSM_lastTime = millis(); //Zeitstempel |
8 | }
|
9 | if(Encoder_in_1 == true || Encoder_in_2 == true){Encoder_active = true;Timers();} |
10 | if(SC_Eingabe_State == true && Encoder_active == true){MV_Time_choice();} |
11 | if(SC_Eingabe_State == true){SC_Eingabe();)} |
12 | }
|
13 | |
14 | |
15 | |
16 | void MV_time_choice() //Auswahl des MV´s und der Zeit. |
17 | {
|
18 | if (Encoder_in_1 == true) |
19 | {
|
20 | if (Encoder_in_2 == true) |
21 | {
|
22 | Encoder_count_val++; |
23 | if(Encoder_count_val > count_max) |
24 | {
|
25 | Encoder_count_val = count_min //Count min/max wird in den SC festgelegt |
26 | }
|
27 | }
|
28 | }
|
29 | if (Encoder_in_2 == true) |
30 | {
|
31 | if (Encoder_in_1 == true) |
32 | {
|
33 | Encoder_count_val--; |
34 | if(Encoder_count_val > count_min) |
35 | {
|
36 | Encoder_count_val = count_max |
37 | }
|
38 | }
|
39 | }
|
40 | }
|
41 | |
42 | void SC_Eingabe() |
43 | {
|
44 | switch(step) |
45 | case 1: count_min = 1; |
46 | count_max = 3; |
47 | SC_Ausgabe(); |
48 | |
49 | |
50 | }
|
51 | |
52 | void SC_Ausgabe() |
53 | {
|
54 | |
55 | switch(step) |
56 | case 1: set.Cursor(2, 1); |
57 | if(Timer2s == true){lcd.blink(Encoder_count_val);} |
58 | else{lcd.Write(Encoder_count_val);} |
59 | }
|
60 | |
61 | void SC_MV1() |
62 | {
|
63 | |
64 | }
|
65 | |
66 | void SC_MV2() |
67 | {
|
68 | |
69 | }
|
70 | |
71 | void SC_MV3() |
72 | { switch() |
73 | case 1: |
74 | |
75 | |
76 | }
|
77 | |
78 | void Timers() |
79 | {
|
80 | unsignet long Timestamp; |
81 | unsignet cost int duration = 2000; |
82 | |
83 | if(millis() >= Timestamp + duration) |
84 | {Timer2s = true;} |
85 | else {Timer2s = false; Timestamp = millis();} |
86 | |
87 | }
|
Stefan F. schrieb: > Adam P. schrieb: >> Hier mal ein kleiner Einblick: >> https://www.mikrocontroller.net/articles/Statemachine > > Volle Zustimmung. Du brauchst Zustandsautomaten. https://en.wikipedia.org/wiki/Finite-state_machine --> hier gibt es Beispiele! Mal Dir den (die) Zustandsautomaten auf und spiel diese durch mit Bleistift und Papier - solange bis ALLES 'wasserdicht' ist! Vorher brauchst du an 'switch', blinkende HD44780-Controller oder Timer-Flags gar nicht zu denken. just my 2ct
:
Bearbeitet durch User
Markus O. schrieb: > So in etwa? 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.
Markus O. schrieb: > Stefan F. schrieb: >> Volle Zustimmung. Du brauchst Zustandsautomaten. > > Vielen Dank! > > So in etwa? 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, wie es ungefähr funktioniert. Sogar 100% Arduino-konform ohne echte Timer-ISR.
Sigi S. schrieb: > Da fehlt LCD_I2C.h Jain. Das ist eine Arduino Lib, die kann und muss man über die Bibliotheksverwaltung installieren. Die heißt I2C_LCD by blackhawk > Für welche Atmega's ist das geeignet? Praktisch alle Arduinos, nicht nur AVRs, sind ja keine hardwarespezifischen Dinge drin.
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.
Peter N. schrieb: > 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. Schönen Start in die Woche!
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.
Juhu! Danke. Doofe Frage: Nach welchen Kriterien packe ich etwas zusammen in einen Switch-Case-Anweisungsblock und was getrennt? Also die Aufteilung nach...?
1 | switch (Titel_1) |
2 | case: 1 |
3 | case: 2 |
4 | case: 3 |
5 | ...
|
6 | |
7 | switch (Titel_2) |
8 | case: 1 |
9 | case: 2 |
10 | case: 3 |
11 | ...
|
:
Bearbeitet durch User
Stefan F. schrieb: > 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.
Stefan F. schrieb: > Für jeden blinkenden Bereich sind das mindestens zwei > Zustände. Meinst du, nicht blinkend = Zustand 1 und blinkend = Zustand 2 - also der Wechsel zwischen Text und Leere?
Markus O. schrieb: > Meinst du, nicht blinkend = Zustand 1 und blinkend = Zustand 2 - also > der Wechsel zwischen Text und Leere? Ich meine: hell und dunkel sind zwei Zustände, zwischen denen nach Ablauf einer Zeit gewechselt werden muss.
Markus O. schrieb: > 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.
Markus O. schrieb: >> Für jeden blinkenden Bereich sind das mindestens zwei >> Zustände. > > Meinst du, nicht blinkend = Zustand 1 und blinkend = Zustand 2 - also > der Wechsel zwischen Text und Leere? Ja. Brauchst du aber nicht unbedingt, wenn dir ein blinkender Cursor reicht. Das kann das LCD allein. Den muss man nur einmal ein oder aus schalten.
Falk B. schrieb: > Man nimmt dafür möglichst aber keine Zahlen sondern Namen, sei es per > #define oder enum. Das ist besser lesbar. Macht sich auch besser, wenn man einen Zustand einfügt. Alle folgenden Nummern korrekt zu ändern ist mühsam.
Stefan F. schrieb: > Ich kann an deiner Tabelle nicht klar erkennen, welches Ereignis welche > Reaktion auslöst. Ich werde diese noch einmal anpassen. Reicht für mich. Aber nächstes Mal! Oder wenn ich Probleme bei der Umsetzung bekomme :D
Falk B. schrieb: > Na alle Möglichkeiten Meinst du: switch (Zustand(Nummer)) case: 1 case: 2 case: 3 ...
Falk B. schrieb: > 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.
Ich glaube, dass meine Frage nicht verstanden wurde. Vielleicht verstehe ich aber auch eure Antwort nicht... Benötige ich mehrere switch-cases? Hier noch ein angepasstes Bsp.: switch (blinken) case 1: case 2: case 3: ... switch (Auswahl_MV) case 1: case 2: case 3: ... switch (Auswahl_Zeit_MV) case 1: case 2: case 3: ...
:
Bearbeitet durch User
Markus O. schrieb: > Ich glaube, dass meine Frage nicht verstanden wurde. In der Tat. Das hat baer eher den Grund, daß du das Konzept noch nicht verstanden hast. > Benötige ich mehrere switch-cases? Hier noch ein angepasstes Bsp.: Für die State machine brauchst du nur ein switch-Konstukt. > switch (blinken) Nein. Die Variable im switch ist eine Variable. Ein einzeln Fälle sind dann die Auswahlmöglichkeiten. Eher so
1 | typedef enum { grundansicht_s, mv1_s mv2_s, mv3_s, |
2 | einstellung1_s, einstellung2_s, einstellung3_s, |
3 | blinken1_s, blinken2_s} state_t; |
4 | |
5 | state_t state = grundansicht_s; |
6 | |
7 | void stateMachine() |
8 | {
|
9 | switch( state ) { |
10 | case grundansicht_s: |
11 | // Eingaben auswerten etc.
|
12 | break; |
13 | case mv1_s: |
14 | // Eingaben auswerten etc.
|
15 | break; |
16 | ....
|
Die Endung _s soll anzeigen, daß es ein State ist. Das ist eine persönliche Festlegung.
Markus O. schrieb: > Ich werde diese noch einmal anpassen. Reicht für mich. Dann ist gut. Du musst damit zurecht kommen nicht ich.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.