Hallo an alle! Hat irgendjemand von euch schon mal ein Menu für ein LCD realisiert? Über drei Tasten steuerbar über 2 tasten (gleichzeitig) ins Menu. 1 Taste hoch. 1 Taste runter. 1 bestätigen. Mir geht eigentlich nicht darum wie man es tun sollte sondern wie es am sinnvollsten wäre es zu implementieren? Danke Gruß Andreas
Das macht man am besten objektorientiert (also in C++) Eine Klasse für einen Menueintrag und eine Klasse für ein Menu. Das ganze kann man dan beliebig kombinieren und erweitern. In C shreibste dich damit tot. Z.B. so (muss natürlich noch für dein µC angepasst werden):
1 | // menu.cpp
|
2 | #include "menu.hpp" |
3 | |
4 | entry::entry(string t, int i) |
5 | {
|
6 | text = t; |
7 | id = i; |
8 | next = NULL; |
9 | }
|
10 | |
11 | entry::entry(string t, menu* n) |
12 | {
|
13 | text = t; |
14 | id = 0; |
15 | next = n; |
16 | }
|
17 | |
18 | menu::menu(string n) |
19 | {
|
20 | count = 0; |
21 | name = n; |
22 | }
|
23 | |
24 | void menu::add_entry(entry *a) |
25 | {
|
26 | entrys.push_back(a); |
27 | count++; |
28 | }
|
29 | |
30 | bool isnum(string a) |
31 | {
|
32 | for (int c = 0; c < a.length(); c++) |
33 | {
|
34 | if (!('0' <= a[c] && a[c] <= '9')) return false; |
35 | }
|
36 | return true; |
37 | }
|
38 | |
39 | int menu::run(menu *last) |
40 | {
|
41 | string choice; |
42 | int start = 0; |
43 | int end = 2; // LCD-Display-Height minus 2 |
44 | int t; |
45 | do
|
46 | {
|
47 | do
|
48 | {
|
49 | cout << name << endl; |
50 | for(int i = start; i < end; i++) |
51 | {
|
52 | cout << i+1 << ": " << entrys[i]->text << endl; |
53 | }
|
54 | cout << "u/d/b/n>"; // _u_p, _d_own, _b_ack, _n_umber |
55 | cin >> choice; |
56 | if (choice == "u" && start > 0) { start--; end--; } |
57 | else if (choice == "d" && end < count) { start++; end++; } |
58 | else if (choice == "b") if (last != NULL) return |
59 | last->run(this); |
60 | }
|
61 | while(!isnum(choice)); |
62 | t = atoi(choice.c_str()); |
63 | }
|
64 | while(t > count || t == 0); |
65 | if(entrys[t-1]->next != NULL) t = entrys[t-1]->next->run(this); |
66 | else t = entrys[t-1]->id; |
67 | return t; |
68 | }
|
1 | // menu.hpp
|
2 | #ifndef MENU_H
|
3 | #define MENU_H
|
4 | |
5 | #include<iostream> |
6 | #include<vector> |
7 | #include<string> |
8 | #include<cstdlib> |
9 | using namespace std; |
10 | |
11 | class entry; |
12 | class menu; |
13 | |
14 | class menu |
15 | {
|
16 | public:
|
17 | menu(); |
18 | menu(menu &); |
19 | menu(string n); |
20 | void add_entry(entry*); |
21 | int run(menu *last = NULL); |
22 | private:
|
23 | string name; |
24 | vector<entry*> entrys; |
25 | int count; |
26 | };
|
27 | |
28 | class entry |
29 | {
|
30 | public:
|
31 | entry(); |
32 | entry(entry &); |
33 | entry(string t, int i); |
34 | entry(string t, menu *n); |
35 | friend int menu::run(menu *last = NULL); |
36 | private:
|
37 | menu *next; |
38 | string text; |
39 | int id; |
40 | };
|
41 | |
42 | #endif
|
1 | // sample main.cpp
|
2 | #include<iostream> |
3 | #include<vector> |
4 | #include<string> |
5 | using namespace std; |
6 | #include"menu.hpp" |
7 | |
8 | main() |
9 | {
|
10 | menu *menu0 = new menu("Where do you go?"); |
11 | |
12 | menu *menu1 = new menu("North/South?"); |
13 | entry *entry10 = new entry("I go to Korea", menu1); |
14 | entry *entry11 = new entry("North", 11); |
15 | entry *entry12 = new entry("South", 12); |
16 | |
17 | entry *entry20 = new entry("I go to England", 20); |
18 | |
19 | menu *menu2 = new menu("East/West?"); |
20 | entry *entry30 = new entry("I go to Germany", menu2); |
21 | entry *entry31 = new entry("East.", 31); |
22 | entry *entry32 = new entry("West.", 32); |
23 | |
24 | entry *entry40 = new entry("I dunno.", 40); |
25 | |
26 | menu0->add_entry(entry10); |
27 | menu0->add_entry(entry20); |
28 | menu0->add_entry(entry30); |
29 | menu0->add_entry(entry40); |
30 | menu1->add_entry(entry11); |
31 | menu1->add_entry(entry12); |
32 | menu2->add_entry(entry31); |
33 | menu2->add_entry(entry32); |
34 | |
35 | switch(menu0->run()) |
36 | {
|
37 | case 11: cout << "North? Nice." << endl; break; |
38 | case 12: cout << "South? Nice." << endl; break; |
39 | case 20: cout << "England? Nice." << endl; break; |
40 | case 31: cout << "East? Nice." << endl; break; |
41 | case 32: cout << "West? Nice." << endl; break; |
42 | case 40: cout << "Huh?" << endl; break; |
43 | }
|
44 | |
45 | return 0; |
46 | }
|
cout und cin musste durch lcd-output und schalter-abfrage ersetztn. Ich weiß nicht wie weit die C++ lib ist. vielliecht musste noch ne kleine vector- und string-klasse schreiben; das geht aber schnell.
Hi, habe sowas mal in der Firma realisiert. Natürlich in C. das ganze läuft eventorientiert. Jedes tastedrücken erzeugt ein event. natürlich muss man sich immer merken in welchem menü man sich grade befindet. über geschachtelte case anweisung kann dann der tastendruck und die aktuelle ausgabe realisiert werden. unten nur eine grobe skizze: CASE TASTE1 case menu1: funktion A set aktives menu2 case menu1: funktion B set aktives menu3 case menu1: funktion C set aktives menu4 CASE TASTE2 case menu3: funktion D set aktives menu3 case menu4: funktion E set aktives menu4 CASE TASTE3 case menu2: funktion F set aktives menu3
Der aufgezeigte C++-Ansatz ist fuer kleine Mikroconroller nicht gut geeignet. Switch/Case-Ketten koennen bei Erweiterungen unuebersichtlich werden. Ein Ansatz wie er z. B. im Projekt "tinymenu" (avrfreak.net academy user-projects) ersichtlich wird, erscheint mir zumindest brauchbarer.
@c++'lerin "In C shreibste dich damit tot." Aber ganz im Gegenteil ! Du legst einfach ne Struktur an, die aus dem Text und dem dazugehörenden Funktionspointer besteht. Dann einfach damit eine Tabelle schreiben, die alles enthält. Und dann einfach den Index mit den Tasten hoch und runter zählen, den Text anzeigen und die Funktion aufrufen. Als maximalen Index nimmt man per sizeof(table)/sizeof(structur) die Anzahl Elemente in der Tabelle oder legt einen letzten Eintrag fest (Null-Text, Null-pointer). D.h. man kann ganz easy weitere Punkte hinzufügen oder welche löschen. Und für Untermenüs muß man nur weitere Tabellen anlegen, die Auswahlfunktion kann die gleiche bleiben (rekursiver Aufruf). Sieht übersichtlich aus und ist einfach zu erweitern. Das wird vor allem nicht so ein ellenlanges Case-Monster wie dieses C++ Beispiel. Und solche manuellen Zählungen (entry10..entry40) werden schnell unübersichtlich und fehleranfällig in größeren Programmen (wie war nochmal der letzte bisher vergebene entry ?). Ich arbeite gerne mit Tabellen, das ist kompakt und Code sparend. Peter
Auch in AVR-ASM geht das gut mit Tabellen. Die Ausgaberoutine braucht Basisadresse (Tabellenanfang) und Menüpunktnummer. Der Aufruf der Menüpunkte erfolgt nach Auswertung der (im Timer-Interrupt entprellten) Tasten mittels ijmp über Z-Pointer. ...
@Peter @HanneS Könntest Ihr ein kleines Beispiel als Codfragment hier Posten! Ich könnte den Ansatz demnächst brauchen. Vielen Dank im Vorraus! Gruß Klaus
Vielen Dank für eure regen Antworten. Peter seine Variante klingt sehr vielversprechend und sinnvoll. Danke Peter. Werde das mal umsetzen, natürlich nicht mehr heute. Es ist ja nu auch Schlafenszeit. Andreas
Habe eben noch einen Fehler im Hauptprogramm entdeckt und beseitigt: TIM0_OVF: ;Timer0 Overflow Handler in srsk,sreg ;SREG sichern sbr flags,1<<lcdtakt ;Timer0 Überlauf: LCD_Out freigeben out sreg,srsk ;SREG wiederherstellen reti ; Hatte ich bisher übersehen... ...
Peter hättest du vielleicht ein Beispiel das du zu verfügung stellen könntest? Wäre toll. Gruß Andreas
Hallo, ... der Thread ist zwar schon ein paar Tage alt, aber ich hoffe, er wird noch gelesen. @...HanneS... : Da ich auch noch ein paar 8x24-er LCDs rumliegen habe, kamen mir die Routinen (s.o.) sehr gelegen. Jedoch scheint in dem Locate x,y-Macro ein kleiner Fehler zu sein: Im RAM-Ringpuffer werden doch vor dem eigentlichen auszugebenen Zeichern die DD-Ram-Addr. des LCD hinterlegt. Bei mir ergibt sich jedoch folgende Zuordnung: Locate 0,0 -> Addr.0x00 lt. Datenblatt Zeile 0 Locate 1,0 -> Addr.0x30 lt. Datenblatt Zeile 2 Locate 2,0 -> Addr.0x60 lt. Datenblatt Zeile 4 Locate 3,0 -> Addr.0x90 lt. Datenblatt Zeile 6 Locate 4,0 -> Addr.0x18 lt. Datenblatt Zeile 1 Locate 5,0 -> Addr.0x48 lt. Datenblatt Zeile 3 Locate 6,0 -> Addr.0x78 lt. Datenblatt Zeile 5 Locate 7,0 -> Addr.0xA8 lt. Datenblatt Zeile 7 Ich habe es bisher nur trocken durchpropiert, jedoch nicht getestet. Hab ich irgendwo nen Denkfehler?? Gruß Rudi
Es gibt vermutlich verschiedene Versionen dieses Displays. Das Datenblatt, welches ich von Pollin habe, stimmt nicht mit der Realität meiner Displays überein. Siehe Anhang (Datenblattauszug): mein Display (und daher auch mein Locate) hat die Aufteilung, die links mit Bleistift nachgetragen wurde. Falls deine Displays anders organisiert sind, dann musst du Locate an deine Displays anpassen. Wenn du anhand meiner Aufteilung den Algo ermittelt hast, dürfte das kein Problem sein. Viel Spaß... Übrigens lässt sich das noch vereinfachen. Wenn man das LCD nur bei der ersten Initialisierung löscht, während des Normalbetriebs auf das Löschen verzichtet und stattdessen Leerzeichen schreibt, dann genügt immer die kurze "Wartezeit", da nur LCD-Clear die lange Wartezeit benötigt. Man kann sich also das Umschalten des Timers sparen oder das Ausgabeflag gleich nebenbei von einem anderen, sowiso laufenden Timer zyklisch setzen. Das Löschen per Leerzeichen wäre dann: locate 0,0 ;erste Zeile, erste Spalte print 32,192 ;192 Leerzeichen schreiben Berichte mal, was draus geworden ist... Bit- & Bytebruch, guten Rutsch... ...
Dies (Anhang) war ein Test mit meinem Locate. Da dazu mal schnell ein Programm für das 4x27 (aber mit 8x24-Treiber, noch ohne Ringbuffer) missbraucht wurde, stehen da noch ein paar andere Sachen auf dem Display, auf die es aber hier nicht ankam. ...
So ist das, wenn man sich auf Datenblätter verlässt - man ist verlassen !!! Getestet habe ich, wie schon erwähnt, die Displays nur kurz mit div. Par-Port-Programmen, jedoch nicht mit dem ATMegaxx. Nur so als Fkt.-Test, ohne mir über die Zeilenaddressierung klar zu sein. Nun aber im aktuellen Projekt spielt das sehr wohl eine Rolle. Danke auch für den Tip, der Vereinfachung. Sicher weniger Programmieraufwand, aber zeitintensiver 192 Byte * 20µsec = 3,84 msec(gegenüber 1,25 msec beim ClearHome-Befehl), bevor alles gelöscht ist. Aber einfacher - werde ich mal testen. Im nächsten Jahr werde ich das mal testen ;-) Bis dahin und Guten Rutsch Rudi PS: @...HanneS... Werde die Raketen noch manuell zünden, obwohl das Zünduhr-Pgm. dafür bestensgeeignet wäre !!
Man muss ja das Display nicht löschen, wenn man darauf achtet, dass man immer volle Zeilen schreibt und denzufolge alle vorherigen Zeichen überschreibt. Im Zünduhr-Programm wird das Display nur beim Init gelöscht. Ich zünde übrigens garkeine Raketen mehr, das muss ich nicht unbedingt haben. In meiner Jugend habe ich genug Knallzeugs gebaut und gezündet (ohne dass es jemals "eng" wurde), da kann ich inzwischen gut drauf verzichten. Die Zünduhr kommt aber heute Nacht zum Einsatz (mit einem "Verteiler"), die habe ich nämlich nicht für mich gebaut, sondern für einen Nachbarn, der vor einigen Monaten seine Pyrotechnik-Ausbildung abschloss und nun offiziell Feuerwerke Klasse 4 abbrennen darf. Das wird heute der zweite Einsatz, aber erstmalig mit "Verteiler" (weiteres von der Uhr über Draht seriell gesteuertes Zündgerät für 31 Zündkreise). Geplant ist später der Einsatz von Zünduhr (16 Kanäle) mit 2 Verteilern (je 31 Kanäle). Das System lässt sich bis zu 6 Verteiler ausbauen, wobei es für Zündbefehle noch den "logischen" Verteiler 7 (H) gibt, auf den Verteiler 1 und 2 (B und C) synchron reagieren. Das ermöglicht Synchronzündungen auf beiden Seiten. Der erste Einsatz (nur Hauptgerät mit 16 Zündkreisen) vor einigen Wochen hat dem "Betreiber" schon die Finger gerettet. Zwei für Luntenzündung zugelassene Vulkane (vom deutschen Fachhandel) detonierten ohne Verzögerung. Bei Luntenzündung wär das peinlich geworden. An der PC-Software muss ich noch etwas basteln. Aber erstmal sehen, was der "Betreiber" da für Wünsche äußert. Bis jetzt ist neben Schusslistentausch mit der Uhr nur das Erstellen, Editieren und Laden/Speichern der Schusslisten vorgesehen. Eine "Simulation" wäre noch interessant, aber leider stimmen die vom Hersteller angegebenen Abbrennzeiten von Batterien und Vulkanen selten mit der Realität überein. Aber irgendeine Art Simulation muss noch rein, um im Vorfeld die Musiksynchronität austesten zu können. Guten Rutsch... - Und passt beim Knallen auf Eure Finger auf!!! ...
@Hannes: Was für ein Datenblatt hast Du denn von Pollin bekommen? Ich hab Bestell-Nr. 120387 gekauft (4 Zeilen, 1xM50530 und 1xM50521) und weder Datenblatt dazubekommen noch ist ein Download verfügbar. Nciht mal die Belegung der 15 Pins ist nachvollziehbar.
Das von Dir genannte LCD (120387) habe und kenne ich nicht, das gibt es vermutlich auch noch nicht lange. Ich hatte vor langer Zeit mal ein 8x24 mit Controller M50530 und bekam über Ebay etliche von dieser Sorte nachgekauft (gebraucht, aus Zerlegung von Telefonen). Die sind zwar teils leicht zerkratzt, aber ich mache ja keine Produktion für den Verkauf. Woher das Datenblatt zum M50530 stammt, kann ich jetzt gar nicht sagen. Ich hänge es mal mit an, vielleicht hilft es Dir oder jemand Anderen ja weiter. ...
@ peter dannegger Du hast oben von einer Menüstruktur gesprochen, die als Tabelle angelegt ist und in Untermenüs verzweigen kann. Eine flache Menüstruktur ist klar - da gibt es nur einen Menüindex. Wie erweitert man diese Tabelle/Struktur nun um Untermenüs? Wie verwaltet man dann die jeden Index der Unter- und Untermenüs?? Bitte ein kleines Programm anfügen. Danke, MiGu.
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.