Hallo Jungs,
ich habe langsam irgendwie eine Denkblockade und komme nicht weiter.
Ich programmiere auf dem ESP32 in Arduino einen LED-Tisch.
Mein bisheriges Menü funktioniert zwar, besteht aber aus gefühlt 236000
if then / else - Bedingungen. Das wollte ich jetzt etwas besser machen
und habe hier viel im Forum gelesen und versucht, mein eigenes Menü zu
bauen. Das klappt aber nicht so ganz. Ich komme mit dem aktuellen
Menü-Punkt immer irgendwo raus, nur nicht da, wo ich eigentlich hin
soll.
Mein Code hat schon einige tausend Zeilen, deshalb hier nur der wichtige
Teil.
Die "printxxx"-Funktionen sind nur Dummys für die späteren Programme.
Die Funktionen sind deklariert und der Funktionsaufruf funktioniert
auch.
Es werden halt nur die falschen Funktionen und Untermenüs aufgerufen...
Die Idee war zu schauen, ob ein Untermenü hinterlegt ist
(FunktionPointer xxx.Menue) oder eine Programm hinterlegt ist
(FunktionPointer xxx.Funktion).
Ich muss halt immer wieder in der loop() starten, da noch viele andere
Dinge dort abgearbeitet werden (Fernsteuerung, Tasten auslesen,
I2C-Kommunikation, Webserver, später OTA, ...).
Habe schon viel rumprobiert, aber ich sehe mitlerweile den Wald
wahrscheinlich nicht mehr...
Wo liegt denn mein blöder Denkfehler?
Viele Grüße!
pete
1
//defines.h
2
3
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*x))
4
5
typedefvoid(*FunktionPointer)(void);//Zeiger auf Funktionen deklarieren
6
7
structMenueAufbau{uint8_tProgrammEbene;//Ebene des ProgrammEintrages im Menü
8
charText[50];//Bezeichnung
9
FunktionPointerFunktion;//Funktionszeiger auf Programm
10
FunktionPointerMenue;//Funktionszeiger auf Untermenü
11
int16_tFPS;//FPS als Abspielgeschwindigkeit
12
};
13
14
15
constMenueAufbauMenue_Animationen[]=
16
{
17
{1,"Farb-Fader",Print2,NULL,50},
18
{1,"moving Qubes",Print3,NULL,50},
19
{1,"Snakelines",Print4,NULL,50},
20
{1,"Matrix",NULL,NULL,50},
21
{1,"Knight Rider",NULL,NULL,50},
22
{1,"Lemming",NULL,NULL,50},
23
{1,"Zurück",NULL,NULL,}
24
};
25
26
constMenueAufbauMenue_Spiele[]=
27
{
28
{1,"Slider",Print5,NULL,50},
29
{1,"Spiel2",Print6,NULL,50},
30
{1,"Spiel3",NULL,NULL,50},
31
{1,"Spiel4",NULL,NULL,50},
32
{1,"Spiel5",NULL,NULL,50},
33
{1,"Zurück",NULL,NULL,}
34
};
35
36
37
constMenueAufbauMenue_Ledplayer[]=
38
{
39
{1,"Bluetooth",Print7,NULL,50},
40
{1,"Spektrum Analyzer",Print8,NULL,30},
41
{1,"LED-Player - ext1",Print9,NULL,50},
42
{1,"ext2 - frei",NULL,NULL,50},
43
{1,"Zurück",NULL,NULL,}
44
};
45
46
47
constMenueAufbauMenue_Einstellungen[]=
48
{
49
{1,"Helligkeit",Print10,NULL,50},
50
{1,"Textfarbe",Print11,NULL,50},
51
{1,"Textgeschwindigkeit",Print12,NULL,50},
52
{1,"weitere Einstellungen",weitereE,NULL,50},
53
{1,"Animation bei Start",NULL,NULL,50},
54
{1,"Bluetooth Einstellungen",NULL,NULL,50},
55
{1,"Einstellungen Speichern",NULL,NULL,50},
56
{1,"Werkseinstellungen",NULL,NULL,50},
57
{1,"Zurück",NULL,NULL,}
58
};
59
60
constMenueAufbauMenue_weitereE[]=
61
{
62
{2,"weitere1",Print13,NULL,50},
63
{2,"weitere2",Print14,NULL,50},
64
{2,"weitere3",Print15,NULL,50}
65
};
66
67
constMenueAufbauMenue_Haupt[]=
68
{
69
{0,"alle abspielen",Print1,NULL,50},
70
{0,"Animationen",NULL,Animationen,50},
71
{0,"Spiele",NULL,Spiele,50},
72
{0,"LED-Player",NULL,Ledplayer,50},
73
{0,"Einstellungen",NULL,Einstellungen,50}
74
};
1
//led-table.ino
2
#include"defines.h"
3
4
volatileuint32_tmillis_old=0;//für speicherung vergangene Zeit seit letztem Frame
... da hast Du so eine hübsche Variable namens aktuelles_Menu, aus der
Du allerhand Informationen rausholst.
Bloß leider hast Du dem armen Ding nirgends einen Wert zugewiesen.
Doch, am Ende von void HandleMenue werden doch die Daten des jeweiligen
Funktionsaufruf geschrieben, Simi bleiben doch die Daten des letzten
Aufrufes darin.
Oder hab ich hier schon Mist gemacht?
> FunktionPointer Menue; //Funktionszeiger auf Untermenü
Weshalb ist das Untermenü nicht vom Typ "MenueAufbau"?
Dann könntest du eine schöne, hierarchische Struktur aus Menüs aufbauen
und es sollte nicht mehr möglich sein, sich falsch durch zu hangeln.
Es kann auch nicht schaden "eltern_menue" hinzu zu fügen. Dann weisst du
für jedes MenueAufbau sofort was die nächsthöhere Ebene ist.
Des weiteren, deine Variablen Namen sind etwas eigenartig...
Du hast sowas wie:
aktuelles_Menue
Menue_Ledplayer
Tasten_spezial
Hast du dir irgendwelche Regeln aufgestellt wann du ein Wort groß
schreibst und wann nicht?
Würde empfehlen wenn du underscore(unterstrich)-variablen verwendest,
garkeine Groß-Buchstaben mehr zu nehmen, ausser für Abkürzungen.
Der Unterstrich reichts chon um dem gehirn zu sagen dass es zwei
getrennte Worte sind. Zusätzliche Groß-und Kleinschreibung ist also mehr
Information als notwendig und die musst du dir merken.
Pete P. schrieb:> Doch, am Ende von void HandleMenue werden doch die Daten des jeweiligen> Funktionsaufruf geschrieben, Simi bleiben doch die Daten des letzten> Aufrufes darin.>> Oder hab ich hier schon Mist gemacht?
Stimmt, tatsächlich. Aber wieso machst Du das so kompliziert?
Warum schreibst Du da nicht einfach
Alex G. schrieb:>> FunktionPointer Menue; //Funktionszeiger auf Untermenü> Weshalb ist das Untermenü nicht vom Typ "MenueAufbau"?
Ich kann doch nicht den noch nicht fertig definierten struct in sich
selbst schon wieder benutzen, oder?
Danke für die Tips, werde ich auf jeden Fall beherzigen.
Aber habt ihr meinen Logikfehler finden können?
Markus F. schrieb:> Warum schreibst Du da nicht einfachaktuelles_Menue => Menue[MenueCounter[aktuelle_MenueEbene]];>> sondern einen Roman??
Ich wusste nicht, dass das so einfach geht ;) Danke.
Pete P. schrieb:> Ich kann doch nicht den noch nicht fertig definierten struct in sich> selbst schon wieder benutzen, oder?
Das Stichwort lautet "Vorwärtsdeklaration"!
Schreibst einfach:
struct MenueAufbau;
davor.
> Aber habt ihr meinen Logikfehler finden können?
Etwas schwierig bei dem Code durchzublicken...
Auf jeden Fall kann man menüs aber einfacher bauen (wobei ich zugeggeben
selbst sowas noch nie in eienr nicht-objektorientierten Sprache gemacht
habe)
Auch wenn das äußerst frustrierend ist.. ich würde dir empfehlen den
Part nochmal von Vorne anzufangen. Immerhin hast du was gelernt!
Hallo Pete,
ich sehe den Fehler auch nicht was wohl an dem vielen Rauschen in deinem
Code liegt. Das nimmt dir die Sicht. Wie willst du da nach Fehlern
suchen. solche Codeabschnitte lagere ich daher immer in Funktionen aus
und benenne sie nach dem, was sie tun. In der Main und den obersten
Schichten hast du dann nur das Prüfen von States und Funtionsaufrufe und
keine Berechnungnen die die Übersicht nehmen.
pete schrieb:
1
}elseif(Prog_gestartet_old<Prog_gestartet){
2
//Programm gestartet
3
Prog_gestartet_old=Prog_gestartet;
4
fps=aktuelles_Menue.FPS;
5
periode_millis=1000/fps;
6
}elseif(Prog_gestartet_old>Prog_gestartet){
7
//Programm beendet
8
Prog_gestartet_old=Prog_gestartet;
9
fps=50;
10
periode_millis=1000/fps;
Du blockierst den Programmablauf für die Tastenauswertung, das macht man
nicht. Du kannst dir auch eine Logik in der main Loop basteln, die das
Entprellen übernimmt.
Du kannst die Tastenauswertung auch schön abstrahieren.
Die Tasten definierst du dann als enum, dann darfst du schöner prüfen.
Prüfen sieht dann z.B. so aus.
1
if(Tasten_spezial&(TAST_ENTER|TAST_ABBRUCH))
Du übergibst eine Kopie der Array-Struktur an die Funktion MenuHandler.
Imho reicht da ein Zeiger. Das spart RAM und macht den Code schneller.
pete schrieb:> Die Funktionen sind deklariert und der Funktionsaufruf funktioniert> auch.> Es werden halt nur die falschen Funktionen und Untermenüs aufgerufen...
ich verstehe nicht richtig, was du damit sagen willst. Funktioniert also
deine Tastenauswertung nicht? Wieso schickst du dir nicht an den
kritischen Stellen eine Debug-Ausgabe mit dem aktuellen Zustand deiner
Tasten und deiner State-Machine und deines jeweiligen Funtions-pointers
raus?
Du musst deine Code so umstricken, dass er schrittweise durch deinen
Fehlerfall durchschreitet. Dann schaust du dir zu jedem Schritt deine
Debug-Ausgabe mit den relevanten Parametern an. Damit grenzt du deinen
Fehler ein, bis du ihn gefunden hast.
Die Tasten kommen über I2C Portexpander, und auch das klappt.
Ich habe gerade nochmal genau getestet:
Die oberste Ebene (Ebene 0) funktioniert. Ich kann die einzelnen
Unterpunkte durchdrücken und print1() startet und beendet wie es soll.
Der Sprung in die Untermenüs klappt aber nicht, der UntermenüCounter
wird richtig gesetzt, aber angezeigt wird wieder die oberste Ebene
(Ebene 0).
Ich habe die ganzen Serialprints aus dem Code oben entfernt, mit denen
ich versuche zu debuggen. Die verwirren Codefremde eher.
Ich komme halt einfach nicht in die Untermenüs. Obwohl ich schon einige
Tage versuche, das Ganze zu debuggen. ;(
Das ganze wird erheblich einfacher und übersichtlicher, wenn man die
Tastenabfrage nicht mit der Mainloop vermanscht.
Z.B. ein Timerinterrupt entprellt und liefert Tastenereignisse an die
Mainloop. Die Mainloop muß dann nur noch die Ereignisse abholen und
auswerten.
Drückt man eine Taste, dann liest die Mainloop genau ein Ereignis und
danach wieder 0, d.h. sie muß sich nichts merken. Es wird je Tastendruck
nur eine Aktion ausgeführt.
Peter D. schrieb:> Das ganze wird erheblich einfacher und übersichtlicher, wenn man die> Tastenabfrage nicht mit der Mainloop vermanscht.>> Z.B. ein Timerinterrupt entprellt und liefert Tastenereignisse an die> Mainloop. Die Mainloop muß dann nur noch die Ereignisse abholen und> auswerten.> Drückt man eine Taste, dann liest die Mainloop genau ein Ereignis und> danach wieder 0, d.h. sie muß sich nichts merken. Es wird je Tastendruck> nur eine Aktion ausgeführt.
Das und eine tabellengetriebene Zustandsmaschine. Die Tabellen sind die
Menüs, in denen die anzuzeigenden Texte, erlaubte Aktionen und
zugehörige Zustandsübergänge stehen.
Beispiel: Die Dateien menu.h mit den Tabellen und die Mainloop in main.c
aus
http://ww1.microchip.com/downloads/en/DeviceDoc/AVRButterfly_application_rev07.zip
Das muss man nicht genau so machen, aber es zeigt die Idee. Deklarieren,
statt jeden Fall in eigenem Code abhandeln.
pete schrieb:> Habe schon viel rumprobiert, aber ich sehe mitlerweile den Wald> wahrscheinlich nicht mehr...
Naja, sauber strukturiert sieht anders aus.
Ich habe vor gefühlten ´zig Jahren anlässlich des 1.Wettbewerbs ein
Grundgerüst für ein beliebig erweiterbares Menüsystem veröffentlicht:
https://www.mikrocontroller.net/articles/Tinykon
Zwar habe ich die Menübeispiele (zu Demonstrationszwecken) damals für
die Dosbox von Win98 programmiert, aber die grundlegende Menüstruktur
ist auf beliebige Rechner bzw Hardwaresituationen übertragbar, einfach
indem die hardwareabhängigen Funktionen geändert werden.
pete schrieb:> ich habe langsam irgendwie eine Denkblockade und komme nicht weiter.> Ich programmiere auf dem ESP32 in Arduino einen LED-Tisch.> Mein bisheriges Menü funktioniert zwar, besteht aber aus gefühlt 236000> if then / else - Bedingungen. Das wollte ich jetzt etwas besser machen
Na dann mach es besser.. und das fängt damit an, daß du zuerst deinen
ganzen Spaghetti-Code weglöschst und dann erstmal mit Nachdenken über
deine Randbedingungen anfängst.
Also:
- wie soll dein Menü denn aussehen? Grafisch und bunt, herausklappbare
Untermenüs, animiert oder nicht, oder einfach eine oder mehrere
Textzeilen?
- wie soll dein Menü denn benutzt werden? per Mausklick, per
Funktionstasten und wenn wie viele? Drei Tasten (rauf runter enter) oder
fünf Tasten oder sonstwas?
Für die allereinfachste Version (die Dreitasten-Version) machst du das
am besten mit einem Array aus passenden Struct's. Etwa so:
1
structMenuRecord
2
{char*Text;
3
charFlags;
4
void(*onRauf)(void);
5
void(*onEnter)(void);
6
void(*onRunter)(void);
7
unsignedcharbeiEnter;
8
};
9
10
conststructMenuRecordDasMenue[sovielduwillst]=
11
{undhierdanndiediversenEinträge
12
};
13
unsignedcharMenuePosition;
14
booledit;
Du brauchst zum Darstellen nur eine einzige Funktion, die sich an
MenuePosition orientiert. Diese Variable ist der Index des Eintrages,
der gerade der fokussierte Eintrag ist.
Mit den Tasten für rauf und runter wandert man im Menü herum. Zum
Eintreten in ein Untermenü lädt man MenuePosition mit dem Wert in
beiEnter. Ebenso geht es zum Verlassen des Untermenüs.
Die Flags müssen je ein Bit für "erster" und "letzter" haben, denn in
jeder Menüseite gibt es einen ersten Eintrag und einen letzten Eintrag.
Dort kann man die Bewegungen per rauf und runter entweder stoppen oder
nach Karussell-Art umschlagen.
Wenn dein Menü auch zum Editieren gedacht ist, dann brauchst du edit und
die Funktionen für onRauf und onRunter. Mit einmal onEnter drücken setzt
man edit auf true und dann soll die Funktion zum Maneuvrieren nicht im
Menü herumwandern, sondern die besagten Funktionen onRauf und onRunter
ausführen, die dann die entsprechenden Editierfunktion ausführen sollen.
Den jeweiligen Text des Menü-Eintrages kannst du gestalten wie du
willst. z.B. auch mit Sonderzeichen, die du in der Darstell-Funktion
passend interpretieren kannst.
So.
W.S.