Forum: Mikrocontroller und Digitale Elektronik Menüführung und Blinken


von Markus O. (markusjo)


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ß

von Adam P. (adamap)


Lesenswert?

Statemachine und dann kannst du dir in jedem Step neue Grenzbereiche für 
den Encoder/Darstellung laden.

von Michael B. (laberkopp)


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.

von Markus O. (markusjo)


Lesenswert?

Meinst du (bspw.) Switch Case?

von Adam P. (adamap)


Lesenswert?

Markus O. schrieb:
> Meinst du (bspw.) Switch Case?

ja z.B.

Hier mal ein kleiner Einblick:
https://www.mikrocontroller.net/articles/Statemachine

von Markus O. (markusjo)


Lesenswert?

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.

von Markus O. (markusjo)


Lesenswert?

Ich hatte die Beschreibung nicht fortgeführt, da sich meine Probleme bis 
zu diesem Punkt beschränken.

von Peter N. (alv)


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.

von Hannes J. (Firma: _⌨_) (pnuebergang)


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.

: Bearbeitet durch User
von Markus O. (markusjo)


Lesenswert?

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?!

von Markus O. (markusjo)


Lesenswert?

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.

von Stefan F. (Gast)


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

von Markus O. (markusjo)


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
}

von Joe L. (joelisa)


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

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

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.

von Falk B. (falk)


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.

von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

Hier mal ein Beispiel, wie es ungefähr funktioniert. Sogar 100% 
Arduino-konform ohne echte Timer-ISR.

von Sigi S. (sermon)


Lesenswert?

Da fehlt LCD_I2C.h

Für welche Atmega's ist das geeignet?

von Falk B. (falk)


Lesenswert?

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.

von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

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.

von Falk B. (falk)


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.

von Markus O. (markusjo)


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!

von Markus O. (markusjo)


Lesenswert?

wenn Ihr*

von Markus O. (markusjo)


Angehängte Dateien:

Lesenswert?

Hier erst einmal die Tabelle.
Kommt das einigermaßen hin?

von Falk B. (falk)


Lesenswert?

Für den Anfang erstmal OK.

von Stefan F. (Gast)


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.

von Markus O. (markusjo)


Lesenswert?

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
von Falk B. (falk)


Angehängte Dateien:

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.

von Markus O. (markusjo)


Lesenswert?

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?

von Stefan F. (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Markus O. (markusjo)


Lesenswert?

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

von Markus O. (markusjo)


Lesenswert?

Falk B. schrieb:

Ich schau mal

von Markus O. (markusjo)


Lesenswert?

Falk B. schrieb:
> Na alle Möglichkeiten

Meinst du:

switch (Zustand(Nummer))
   case: 1
   case: 2
   case: 3
   ...

von Rolf (rolf22)


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.

von Markus O. (markusjo)


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

: Bearbeitet durch User
von Falk B. (falk)


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.

von Stefan F. (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.