Menüführung und Blinken

OP #7454616
Lesenswert?

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ß

#7454631
Lesenswert?

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.

#7454671
Lesenswert?

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.

#7454690
Lesenswert?

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.

Gast #7454724
Lesenswert?

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

OP #7454873
Lesenswert?

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
}
#7454879
Lesenswert?

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

#7454969
Lesenswert?

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.

#7456343
Lesenswert?

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.

OP #7456496
Lesenswert?

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!

Gast #7456678
Lesenswert?

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.

#7456686
Lesenswert?

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.

Angehängte Dateien:
#7456731
Lesenswert?

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.

OP #7456738
Lesenswert?

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: ...

#7456760
Lesenswert?

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.

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