Forum: Mikrocontroller und Digitale Elektronik Display Menü LCD 4x20


von Andreas (Gast)


Lesenswert?

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

von c++'lerin (Gast)


Lesenswert?

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.

von Marc989 (Gast)


Lesenswert?

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

von mthomas (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

@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

von Hannes L. (hannes)


Lesenswert?

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.

...

von Klaus (Gast)


Lesenswert?

@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

von Andreas (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

Hier das Programm in ASM. Es läuft auf Mega8535 mit Display 8x24.

...

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

Hier noch die Print-Routinen für das LCD...

...

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

Und noch die Treiber-Routinen für das LCD.

...

von Hannes L. (hannes)


Lesenswert?

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

...

von Andreas (Gast)


Lesenswert?

Peter hättest du vielleicht ein Beispiel das du zu verfügung stellen
könntest?
Wäre toll.

Gruß

Andreas

von Rudi Fischer (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

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

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

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.

...

von Rudi Fischer (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

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

von Ethan Arnold (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

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.

...

von MiGu (Gast)


Lesenswert?

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