Go Down

Topic: Projektvorstellung: Library für das Erstellen von Menus für LCD Displays (Read 16894 times) previous topic - next topic

cr0n0s1

Guten Tag Arduino-Gemeinde,

ich arbeite seit einigen Wochen an einer Library für das einfache und dynamische Erstellen von Menüs, optermiert für ein LCD Keypadshield (LCD Display mit Button für das navigieren durch ein Menü). Die Library die ich vorstellen werde, ermöglicht es schnell und ohne viele Handgriffe ein komplexes Menü zu erstellen. Im folgenden würde ich nun einfach damit beginnen ein Menü mit einigen Unterpunkten auf zubauen.

Schritt 1.: Planung

Als erstes überlegen wir uns was wir für unsere Menü brauchen. Ich habe als Beispiel folgendes Menü aufgebaut:

Settings -> Sound
              Display
              Control
              LED      -> Test LED

Jeder dieser Namen stellt ein MenüItem da. Jedes MenüItem bekommt nun eine nummer zu gewiesen. Dadurch wird es möglich die Items zu verwalten und unter einander bekannt zu machen, sprich wenn man später einen Button drückt ist klar welches Item als nächstes auf dem Display erscheinen soll. Wichtig dabei ist das jedes Item eine einmalige Nummer bekommt.

001 Settings -> 002 Sound
                    102 Display
                    202 Control
                    302 LED       ->  033 Test LED

Der Punkt 033: Test Led wird später wenn der Selectbutton gedrückt wurde eine LED zum leuchten bringen.
Nach dem wir nun das Menü geplant haben und jedem Menüpunkt eine Nummer zugewiesen haben können wir Anfangen unser Menü zu programmieren.


Schritt 2.: Anlegen des Menüs und der Items

Als nächtes werden die benötigten Objekte angelegt. Wir beginngen mit dem Menu

Code: [Select]

// Create a Menu object (The frame you feed with MenuItems)
Menu myMenu;


das Menu objekt verwaltet alle MenuItems und weiß wo hin es geht, wenn ein Button (z.B. Rechts oder Links) gedrückt wurde.

Danach folgen die MenuItems welche die einzelnen Menüpunkte darstellen.

Code: [Select]

// Create the MenuItems
//                Name     Number For  Back  up  down
MenuItem item001("Settings", 001, 002, 001, 001, 001);
MenuItem item002("Sound", 002, 002, 001, 302, 102);
MenuItem item102("Display", 102, 102, 001, 002, 202);
MenuItem item202("Controll", 202, 202, 001, 102, 302);
MenuItem item302("LEDs", 302, 033, 001, 202, 002);
MenuItem item033("Test LED", 033, 033, 302, 033, 033);


Wie schon in dem Kommentarbeschrieben wird jetzt jedem Item bekannt gemacht, welcher sein Nachbar ist und von hin es geht wenn der jeweilige Button gedrückt wird. Da wir vorher eine ordentliche Planung durchgeführt haben ist es nun einfach die Items anzulegen.

Schritt 3.: Verbinden der Items und dem Display mit dem Menü


Da diese Libary auf ein Menü für LCD Displays ausgelegt ist brauchen wir natürlich auch ein solches Display. Dieses legen wir an und machen es dem Menü bekannt, damit es Ausgaben auf dem Display tätigen kann.

Code: [Select]

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
lcd.begin(16, 2);
 
// Add the display to the menu
myMenu.addLCD(&lcd);


Danach können wir die zuvor erzeugen MenuItems dem Menü bekannt machen.

Code: [Select]

// Add the items to the menu
myMenu.addItem(item001);
myMenu.addItem(item002);
myMenu.addItem(item102);
myMenu.addItem(item202);
myMenu.addItem(item302);
myMenu.addItem(item033);


Ab diesem Punkt ist das Menü erstellt und funktionsfähig. Danach muss natürlich noch festgelegt werden, was passiert wenn die jeweiligen Button gedrückt werden. Dies sollte man aber aus dem folgendem kompletten Beispiel entnehmen.
Zudem werde ich bei Intresse die Libary freigeben. Ich hoffe das meine Erläuterung verständlich genung war und bei einigen Intresse geweckt hat. Außerdem bitte ich um viele Verbesserungsvorschläge bezüglich dieser Vorstellung und natürlich der Libary. Kritik nehme ich eben so gerne an, aber ich bitte um schonende Worte, da dies das erste mal ist das ich solch ein Projekt vorstelle.

In diesem Sinne bin ich auf die Kommentare gespannt und nun das komplette Beispiel

Code: [Select]

#include <LiquidCrystal.h>
#include <Menu.h>
#include <MenuItem.h>

// Create a Display object
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Create a Menu object (The frame you feed with MenuItems)
Menu myMenu;

// Create the MenuItems
//                Name     Number For  Back  up  down
MenuItem item001("Settings", 001, 002, 001, 001, 001);
MenuItem item002("Sound", 002, 002, 001, 302, 102);
MenuItem item102("Display", 102, 102, 001, 002, 202);
MenuItem item202("Controll", 202, 202, 001, 102, 302);
MenuItem item302("LEDs", 302, 033, 001, 202, 002);
MenuItem item033("Test LED", 033, 033, 302, 033, 033);

int readButton; // Variable to store the last pressed Button
int select;  

void ledTest(){
 digitalWrite(13, HIGH);
 delay(3000);
 digitalWrite(13, LOW);
}

void setup(){
 lcd.begin(16, 2);
 
 // Add the display to the menu
 myMenu.addLCD(&lcd);
 
 // Add the items to the menu
 myMenu.addItem(item001);
 myMenu.addItem(item002);
 myMenu.addItem(item102);
 myMenu.addItem(item202);
 myMenu.addItem(item302);
 myMenu.addItem(item033);
 
 myMenu.begin(001); // Start at the Menu with at the MenuItem you want
 
 pinMode(13, OUTPUT); // Set pinMode for Test LED
}

void loop(){
 readButton = analogRead(0); // read the value from a pushed button
 
 // Check if a button was press
 if(readButton < 50){
   myMenu.goForward();
 }
 if(readButton > 50 && readButton < 195){
   myMenu.goUp();
 }
 if(readButton > 195 && readButton < 380){
   myMenu.goDown();
 }
 if(readButton > 380 && readButton < 555){
   myMenu.goBack();
 }
 
 // Check if selectbutton was press
 if(readButton > 555 && readButton < 790){
   select = myMenu.select(); //use select() to check if the item you are at has a runable function
   
   switch(select){
     case 033: //if found a funtion for a item run it
       ledTest();
       break;
       
     default:
       break;
   }
 }
 readButton = 1000;

}

mde110

...Zudem werde ich bei Intresse die Libary freigeben. Ich hoffe das meine Erläuterung verständlich genung war und bei einigen Intresse geweckt hat. Außerdem bitte ich um viele Verbesserungsvorschläge bezüglich dieser Vorstellung und natürlich der Libary. Kritik nehme ich eben so gerne an, aber ich bitte um schonende Worte, da dies das erste mal ist das ich solch ein Projekt vorstelle...


Ich hätte Interesse an der Lib und würde sie mir auf jeden Fall mal durchschauen.

BerndJM

Hi,

bei deinem Beispielcode taucht bei mir ein "geistiges Fragezeichen" auf:
Was passiert bei readButton = 50 (oder 195 oder 380) ?

Grüßle Bernd
Theoretisch gibt es keinen Unterschied zwischen Theorie und Praxis ...

michael_x

Ich wünsche mir ein Menü - System, das im Flash liegt und somit schon fertig kompiliert geladen wird.
Wieviel RAM brauchen deine MenuItem-Elemente? Verbrät ein addItem () - Aufruf noch etwas zusätzlich? (Wohl nicht, aber geht nur mit Variablen im RAM)

Ein int im RAM, das den aktuellen Zustand des gesamten MenüSystems kennzeichnet, sollte eigentlich reichen.
Der statische Teil des aktuellen MenuItem sollte natürlich nur jeweils einmal ausgegeben werden, das passt auch noch in die Status-Variable.

Wie passen Wert-Einstell Funktionen (z.B. über up/down Taster) in dein Menü-System?

Das Ganze aus einfach konfigurierbaren Bausteinen zusammenzustellen, ist ein guter Ansatz!

mkl0815

Guter Ansatz, gut strukturiert. Detailliert kann man aber erst was dazu sagen, wenn wir einen Blick auf die Lib selbst geworfen haben.
Was ich noch für Praktisch halten würde wäre ein dynamischer Aufbau. Also wenn man statt mit den Integer-IDs gleich mit den Zeigern auf die Child- und Parent-Elemente arbeitet.
Toll wäre auch, wenn man die select()-Funktion eines Menuitems gleich beim Anlagen als Funktionspointer "void (*p)(void *)" übergeben könnte. Der Parameter "void*" kann dann wieder ein Zeiger auf ein beliebiges Objekt sein.

Mario.

cr0n0s1

Hallo und danke für das Intresse,
ich habe nun den oberen Beitrag bearbeitet und die Lib als .zip angehängt also alle die Intresse haben einfach downloaden. Zudem hab ich vor, ein paar fragen zu beantworten ;)


Hi,

bei deinem Beispielcode taucht bei mir ein "geistiges Fragezeichen" auf:
Was passiert bei readButton = 50 (oder 195 oder 380) ?

Grüßle Bernd


Dieser Abschnitt bezieht sich komplett auf das Benutzen eines KeyPadShields (http://www.dfrobot.com/wiki/index.php?title=Arduino_LCD_KeyPad_Shield_%28SKU:_DFR0009%29) Letztendlich ist dieser Code nur dafür da, um zu ermitteln welcher Button gedrückt wurde und somit durch das Menü zu navigieren.


Wieviel RAM brauchen deine MenuItem-Elemente? Verbrät ein addItem () - Aufruf noch etwas zusätzlich?


Wie viel RAM es genau verbrät kann ich die leider nicht sagen.
Ja addItem() benötigt auch noch Speicher. Allerdings hab ich vor, wie auch schon andere sagten, per Zeiger auf die Items zu zeigen.
Weiter Infos solltest du dir aber aus der Lib holen können.


Das Ganze aus einfach konfigurierbaren Bausteinen zusammenzustellen, ist ein guter Ansatz!


Danke, das war meine Motivation eine solche Lib zubasteln. Es freut mich das ich das anscheinend geschafft habe.


Am Ende möchte ich nochmals sagen das ich mich sehr über Verbesserungsvorschläge freue. Ich hoffe das mein Code nicht ganz so grausam ist und keine hoch heiligen unaufgeschriebenen Programmierregeln bricht.
Also dann viel Spass mit der Lib ;)

michael_x

Der Anhang ist der am ersten Post ?

Quote
Ich hoffe das mein Code nicht ganz so grausam ist und keine hoch heiligen unaufgeschriebenen Programmierregeln bricht.


Wenn du ohne String arbeitest, ist es "nicht ganz so grausam"  ;)

Wie gesagt, ein Menu im PROGMEM Flash-Speicher aber "konfiguriert statt programmiert", wäre gut.

mkl0815

Hmm doch die String Klasse. Die würde ich auf jeden Fall entsorgen. Da Du mit den Strings selbst nichts machst, außer sie zu speichern, sollte einfach "String" gegen "char*", oder sogar "const char*" ausgetauscht werden.

Als nächstes würde ich auf jeden Fall von den "künstlichen" Indizes auf Zeiger umstellen, dann sparst Du Dir die Funktion getNeighbour(), da jeder Eintrag von forward, back, up und down gleich der passende Zeiger auf das nächste Item ist. Damit man einen definierten Start hat, würde ich ein "root" Element im Menu-Objekt definieren, welches immer den ersten Eintrag liefert. Zusätzlich sollte jedes MenuItem entsprechende Methoden getForward(), getBack(), getUp und getDown() haben, damit man auf die angehängten Elemente zugreifen und damit durch den Baum / Graph navigieren kann.

Mario.

cr0n0s1


Der Anhang ist der am ersten Post ?

Wie gesagt, ein Menu im PROGMEM Flash-Speicher aber "konfiguriert statt programmiert", wäre gut.


1.) Ja aus dem ersten Post.
2.) Wie soll ich dieses "konfiguriert statt programmiert" verstehen?


Hmm doch die String Klasse. Die würde ich auf jeden Fall entsorgen. Da Du mit den Strings selbst nichts machst, außer sie zu speichern, sollte einfach "String" gegen "char*", oder sogar "const char*" ausgetauscht werden.


Okay, dazu Fragen. Warum sind Strings schlecht? Sind das nicht letztendlich auch nur Char arrays? Was haben Chars als Vorteil gegenüber Strings?
Und wenn ich jetzt von String auf Char wechsel ist das doch für den User "relativ" aufwendig da er einen Namen z.B. so anlegen muss:
Code: [Select]

char name[5] = {'H', 'a', 'l', 'l', 'o'};



Als nächstes würde ich auf jeden Fall von den "künstlichen" Indizes auf Zeiger umstellen, dann sparst Du Dir die Funktion getNeighbour(), da jeder Eintrag von forward, back, up und down gleich der passende Zeiger auf das nächste Item ist. Damit man einen definierten Start hat, würde ich ein "root" Element im Menu-Objekt definieren, welches immer den ersten Eintrag liefert. Zusätzlich sollte jedes MenuItem entsprechende Methoden getForward(), getBack(), getUp und getDown() haben, damit man auf die angehängten Elemente zugreifen und damit durch den Baum / Graph navigieren kann.

Das wäre der nächste Schritt gewesen, allein Schon damit ein Item nicht zwei mal existiert.

Unterm Strich: Würdet ihr diese Lib benutzen wenn ihr gerade ein Projekt  amlaufen habt, bei dem ihr deratige Menüs braucht?

mkl0815



Okay, dazu Fragen. Warum sind Strings schlecht? Sind das nicht letztendlich auch nur Char arrays? Was haben Chars als Vorteil gegenüber Strings?
Und wenn ich jetzt von String auf Char wechsel ist das doch für den User "relativ" aufwendig da er einen Namen z.B. so anlegen muss:
Code: [Select]

char name[5] = {'H', 'a', 'l', 'l', 'o'};


Strings sind "schlecht", die Klasse bei jeder String-Operation den knappen Speicher fragmentiert und am Ende jeder String mehr Platz weg nimmt, als der gleiche char*.

Einen C-String kann man auch folgendermaßen deklarieren:
Code: [Select]

char* name1 = "Hallo";
char name2[] = "Hallo";

Beide Variablen enthalten auch schon das abschliessende '\0' Zeichen. Was Dein "char name[5]" nicht macht. Dein "Hallo" hat als C-String kein Ende.
Und ein char* name = "Hallo" ist genau so aufwändig wie ein String-Objekt zu erzeugen.
Mario.

cr0n0s1


Code: [Select]

char* name1 = "Hallo";
char name2[] = "Hallo";

Beide Variablen enthalten auch schon das abschliessende '\0' Zeichen. Was Dein "char name[5]" nicht macht. Dein "Hallo" hat als C-String kein Ende.
Und ein char* name = "Hallo" ist genau so aufwändig wie ein String-Objekt zu erzeugen.
Mario.


Ahh super, das war mir vorher nie so bewusst, dann seh ich auch kein problem dadrin das ganze mit Chars zu machen ;)

michael_x

Quote
Ahh super, das war mir vorher nie so bewusst, dann seh ich auch kein problem dadrin das ganze mit Chars zu machen

Sorry, hätt' ich vielleicht vorher besser erklären sollen. Vermutlich kannst du dir gar nicht vorstellen, was passiert, wenn du schreibst:

String text = "Hallo";
text = text + "!";

text liefert zwar jetzt "Hallo!", aber der alte String("Hallo") liegt immer noch als Müll rum, weil er zu klein für den neuen text ist.

Ein char array ausreichender Größe ist im Endeffekt einfacher, wenn du erstmal dran gewöhnt bist, wie es zu benutzen ist.

Und wenn du dir dann angewöhnst, feste Texte gar nicht in den RAM zu legen ;)

Quote
Wie soll ich dieses "konfiguriert statt programmiert" verstehen?

So wie du es machst, mit einer Liste von MenuElementen, die quasi in einer Tabelle definiert haben, wie sie zusammenhängen und welche Texte sie anzeigen, statt alles per Programm zu codieren.


Nur hab ich in meinem 328 nur noch 800 byte RAM frei, und ein SD Card Sektor ist halt mal 512 byte groß, da bin ich etwas knickig. Ausserdem geht's auch um's Prinzip, aus sportlichem Ehrgeiz...

mde110

Eure Erläuterungen zu Strings sind sehr interessant und gut zu Wissen.

mde110

Wie würde denn die richtige Implementierung der PROGMEM in die Lib aussehen?

Lib_X.h
Code: [Select]
const char **_flashText;

Lib_X.cpp
Code: [Select]
char ProgMemBuffer[20];
strcpy_P(ProgMemBuffer, (char*)pgm_read_word(_flashText));


*.ino
Code: [Select]
prog_char strServo1[]               PROGMEM = {"Servos AN"};

PROGMEM const char *MenServo_Strings[] =
{   
  strServo1
};

KlasseX._flashText = &MenServo_Strings[0];


Funktionieren tuts, ist das auch die optimalste Art ohne Verbesserungsmöglichkeiten??

mde110

Könnte man evtl. die neue Funktion F("") irgendwie mit benutzen?
Damit wäre die Einbindung leichter.

Z.B.:
Code: [Select]

KlasseX.InitText(F("Servos AN"));

Go Up