www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Programmieren einer Menüstruktur in C


Autor: Stefan S. (mphunter)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tach zusammen!

Hab da ein Problem bei der Realisierung meiner Menüstruktur in C.
Ich habe vier Taster zur Bedienung meiner Menüstruktur. Ich habe ein 
Grundmenü was sich wiederrum in mehrere Untermenüs aufsplittet. Kann ich 
diese Menüführung nur über eine if Anweisung mit einer while Schleife 
aufbauen oder weiß jemand von Euch einen besseren Rat?
Bin dankbar für jeden Ratschlag!

Autor: Olli (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Deine Frage ist in etwa so präzise wie "Ich möchte ein großes Haus 
bauen. Wie viele Fenster brauch ich?"

Autor: Der Micha (steinadler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Natürlich kannst du das,

du speicherst in einer Variable den Tastencode (0-3) und wertest dann 
mit Hilfe von "switch" - "case" das ganze aus.

if geht auch; ich persönlich finde switch case schöner.
Zumindest wenn mehr als ein Befehl ausgeführt werden soll.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Funtionspointer wären auch eine Alternative.

Autor: whitenoise (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
...ja, oder eine funktion für alles, der über gibst du dann eine liste 
von funktionspointern..

gruß,
whitenoise

Autor: Latissimo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich wäre für switch!

für jede Auswahlmöglichkeit(zb. im Hauptmenü sind 6 untermenüs 
aufrufbar)
eine switch-Anweisung nutzen. Im Untermenü dann wieder ein switch

switch(hautpmenü)

switch(untermenü1)
.
.
.

ich weiß, das mit den üs geht so net, also nicht auf mir rumhacken... ;)

Autor: I_ H. (i_h)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dazu hab ich mir irgendwann mal folgendes zusammenprogrammiert:
// menu entry structure
struct UImenuEntry
{
  const char* name;  // entry name
  int id;      // entry ID
};


// simple menu structure
struct UImenu
{
  const char* menuName;    // menu name
  int mid;      // menu ID
  
  char entries;      // entry count
  struct UImenuEntry e[];    // entries  
};

Im Einsatz sieht das dann so aus:
// menus

struct UImenu menuMain =
{
  "Main Menu",       // menu name
  100,         // menu id
  5,         // number of entries
  {
    {"set OCR2", 1000},   // entries
    {"set preOn", 1001}, 
    {"set On", 1002},
    {"set postOn", 1003}, 
    {"submenu", 101}, 
  }
};


struct UImenu menuSub1 =
{
  "Sub Menu", 
  101, 
  2, 
  {
    {"Test 4", 1010}, 
    {"back", 100}
  }
};

struct UImenu *menuList[]={&menuMain, &menuSub1, NULL}; 


[...]

  while(1)
  {
    // enter menu
    int ret=UImenuExec();
    
    switch(ret)
    {
      //
      //  Main Menu
      //
      
      case 1000:  UIchgInt("set OCR2", setOCR2, getOCR2);
          break;  
          
      case 1001:  
          UIchgIntPtr("set preOn", &delayPreOn);
          break;
          
      case 1002:  
          UIchgIntPtr("set On", &delayOn);
          break;
          
      case 1003:  
          UIchgIntPtr("set postOn", &delayPostOn);
          break;

      // 
      // Submenu 1
      //
      
      case 1010:
          // tuwas
          break;
      
    }    
  }    
}

UImenuExec zeigt das menu an und gibt eine dem ausgewählten Eintrag 
entspr. ID zurück (Eintrag anwählen und irgend'n festgelegten button 
drücken).
UIchgIntPtr ist eine Subroutine in der man Werte eingeben kann (mit 4 
Tasten).

In 64k IDs ist auch genug Platz die Menu IDs unterzubringen, so 
funktionieren auch submenus ohne das UImenuExec was zurückgibt.

Autor: Thilo M. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe mich grade auch mit einem Menü 'rumgeschlagen.
Ich rufe zunächst ein Hauptmenü (gesonderte Endlosschleife) auf, in dem 
kann über swich/case ein Untermenü von 16 gewählt werden (einzelne 
Funktionen mit enthaltener Endlosschleife).
Durch die Displayausgaben, EEPROM lesen/Beschreiben, Werte verändern und 
begrenzen usw. frisst das Ding ordentlich Speicher. In meinem Regler 
(mit Sonderfunktionen) frisst das Menü beim mega644 schon 15% des Flash.
OK, da kann ich mit leben, aber Optimierung tut da auch Not, werde mich 
nach Abschluss des Projektes mal dranmachen und in die Codesammlung 
setzen.

Autor: I_ H. (i_h)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bei meiner Variante ist die Auswertung recht einfach, ich weis ehrlich 
gesagt nicht wie man da ~10kB Flash verheizen kann.

UImenuExec muss nur folgendes machen:
- menu ID von aktuell ausgewähltem menu und Eintragsnummer (pos. in 
array) in statischer variable speichern

- menuList nach passender menu ID für aktuelles Menu durchsuchen (1 
Schleife)
- Funktion aufrufen die dieses Menu mit aktuell gewähltem Eintrag 
malt/bei oben/unten Taste die Menueinträge durchgeht, bei ok Nummer des 
gewählten Eintrags zurückgibt
- Gucken ob Menu mit der ID des gewählten Eintrags vorhanden - wenn ja, 
als neue menu ID benutzen und von vorn anfangen, ansonsten Eintrags-ID 
zurückgeben

Und das war's eigentlich schon.

Autor: Thilo M. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>ich weis ehrlich gesagt nicht wie man da ~10kB Flash verheizen kann.

Mit der Menüstruktur selbst nicht. Das braucht auch fast nix.
Ich will aber in den einzelnen Untermenüs auch was tun. Bei vielen 
Untermenüs (bei mir 16) kommt da schon Code zusammen.
Z.B. einen Bildschirm anzeigen (immer Anders), eine Variable ausgeben, 
diese durch Tastendruck hoch, bzw: 'runterzählen, die Variable entweder 
im EEPROM abspeichern oder direktes Verlassen des Untermenüs. Die 
Tastenabfragen und Absicherungen gegen Überlauf usw. sind in vielen 
Untermenüs verschieden, darum kann hier nix zusammengefasst werden.
Das ist wie beim Windoof auch, sobald Benutzereingaben gemacht werden 
können müssen Gürtel und Hosenträger her. :)
Das kostet schon Platz. ;)

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ I_ H.
Ich versuche gerade deine Menüstruktur nachzubauen.
Kannst du auch noch deine "UImenuExec" Funktion posten.
Viele Dank!

Autor: I_ H. (i_h)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
// current menu and menu list
int UImid;
struct UImenu **UImenuList;

// menu mode - 0 -> 2line display, 1 -> 1line display (lower)
char UImode;

// last id and mid, used to point to same 
// entry on menu recall
int UImidLast=-1;
int UIidLast;



// menu key masks
// #define KEY_UP    0x10
// #define KEY_DOWN  0x20
// #define KEY_SELECT  0x02 

#define MENU_LINE1 2
#define MENU_LINE2 3




// show menu entry
// l -> display line to use
// n -> entry number+1; note: 0 means show menu itself
// s -> if(s) show entry as selected
void UIshowEntry(struct UImenu *menu, char line, char n, char s)
{
  char baseLine, i;
  
  // menu sizes
  const int menuWidth=16;
  
  // selected entry
  if(line==0)  baseLine=MENU_LINE1;
  else    baseLine=MENU_LINE2;
  
  // clear display area and goto start
//   LCDgoto(base);
  LCDgotoXY(0, baseLine);
  
  for(i=0;i<menuWidth;i++) LCDsendChar(' ');
  
//   LCDgoto(base);    
  LCDgotoXY(0, baseLine);  
  
  
  // show entry
  if(n!=0)
  {
    
    if(s)  LCDsendString(">");
    else  LCDsendString(" ");
    
    LCDgotoXY(1, baseLine);
    LCDsendString(menu->e[n-1].name);
    if(UIsearchMid(menu->e[n-1].id)!=-1)  LCDsendChar('z'+4);
    
//     LCDgoto(base+menuWidth-1);        
    LCDgotoXY(menuWidth-1, baseLine);
    if(s)  LCDsendString("<");
    else  LCDsendString(" ");
  }
  
  // show menu
  else
  {
    if(s)  LCDsendString(">= ");
    else  LCDsendString(" = ");
    
    LCDgotoXY(3, baseLine);    
    LCDsendString(menu->menuName);
    
//     LCDgoto(base+menuWidth-3);        
     LCDgotoXY(menuWidth-3, baseLine);        
    if(s)  LCDsendString(" =<");
    else  LCDsendString(" = ");
  }
  
  return;
}

// select menu item
// return number of selected entry
// n is preselected entry
int UImenuSel(struct UImenu *menu, char n)
{
  char chg=1;  // anything has changed?
  
  // if 2line, select 1st entry per default
  // else, select menu as default
  signed char sel;
  
//   if(UImode==0)  sel=n; //+1;  
//   else    sel=n;
  sel=n+1;
  
  while(1)
  {
    // if anything changed
    if(chg)
    {
      // 2line display
      if(UImode==0)
      {
        // unselected entry
        signed char usel=sel-1;
        if(usel<0)  usel=menu->entries;
        
        UIshowEntry(menu, 0, usel, 0);
      
        // selected entry
        UIshowEntry(menu, 1, sel, 1);
      }
      
      // 1line display
      if(UImode==1)
      {
        // show selected entry
        UIshowEntry(menu, 1, sel, 1);
        
        // show entry number far right
        LCDgoto(0x40+20-2);
        LCDsendInt(sel);
      }
      
      chg=0;
    }
    
    // on key UP
    if(INPgetReset(KEY_UP))  
    {
      sel--;
      if(sel<0)  sel=menu->entries;
      
      chg=1;
    }
    
    // on key DOWN
    if(INPgetReset(KEY_DOWN))  
    {
      sel++;
      if(sel>menu->entries)   sel=0;
      
      chg=1;
    }
    
    // on key SELECT
    if(INPgetReset(KEY_OK))  
    {
      if(sel!=0)  return sel-1;
    }
    
    EventTick();
  }
}

// search if menu with given mid exists
// if so, return number of entry in UImenuList
// else, return -1
int UIsearchMid(int mid)
{
  int n=0;
  
  while(1)
  {
    if(UImenuList[n]==NULL) return -1;
    if(UImenuList[n]->mid==mid) return n;
    
    n++;
  }
  
  // dummy
  return 0;
}

// menu exec function
int UImenuExec()
{
  while(1)
  {
    // search menu number of mid
    int n=UIsearchMid(UImid);
    
    // sth failed
    if(n==-1) return -1;
    
    // show menu and let user select entry
    int ret;
    
    if(UImidLast==UImid)  ret=UImenuSel(UImenuList[n], UIidLast);
    else      
    {
      UImidLast=-1;
      ret=UImenuSel(UImenuList[n], 0);
    }
    
    // get id of selected entry
    int id=UImenuList[n]->e[ret].id;
    
    // if there's a menu with this id as mid, goto submenu
    if(UIsearchMid(id)!=-1)
    {
      UImid=id;
    }
    // else return id 
    else
    {
      UImidLast=UImid;
      UIidLast=ret;
    
      return id;
    }
    
    // event tick
    EventTick();
  }
  
  return 0;
}

Nicht besonders sauber programmiert, aber es funktioniert fehlerfrei 
(solange die menustruktur in ordnung ist).

Das EventTick() kann weg (gehört zu einem einfachen Multimthreading wo 
Tasks einstellbar alle xyz ms aufgerufen werden, daher in jeder 
while-schleife), irgendwo in dem Code ist auch noch die Möglichkeit das 
Menu 1- oder 2-zeilig anzeigen zu lassen (UImode).

Die Ausgabe ist in etwa so:
>== Menu Name ==<   -- selektierter Eintrag, daher die > <
  Item 1 
  Item 2
  .
  .
  .

Verweist die ID von Item xyz auf ein Submenu wird noch "->" angehängt 
(ist ein Zeichen, das 'z'+4).
INPgetReset gehört zum Key-Handler, gibt für die gegebene Key-Mask 
ungleich 0 zurück falls die Taste gedrückt wurde, und setzt den Status 
der Taste zurück falls dem so ist.

Autor: I_ H. (i_h)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ach ja, wär sicher net falsch das mal so umzuändern, dass die 
Strukturdaten im Flash liegen.

Autor: Bruno (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Kannst dir dazu auch mal diesen Thread angucken:

Beitrag "strlen bei mehrdimensionalen Strings"

Autor: I_ H. (i_h)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Meinst du weil die Zeilen vorher immer geleert werden? Wie gesagt, als 
ich den Code geschrieben habe wollte ich eigentlich was ganz anderes 
machen, brauchte dazu aber ein menu.

Außerdem spar ich so die lib-funktion ;)

Richtig Sinn macht das Menu übrigens erst mit dem Event-gedödel. Das 
besteht aus einem Array mit einem Eintrag/Task, da kann man einen 
Funktionszeiger reinschreiben und das Ausführungsintervall angeben (zB. 
alle 100msec).
Über einen Timer wird dann ermittelt welche Funktionen ausgeführt werden 
müssen, und das wird dann bei EventTick gemacht.

So kann man nebenher zB. ein paar Werte ausgeben, und muss sich dann 
garnimmer drum kümmern.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
I_ H. wrote:
> Ach ja, wär sicher net falsch das mal so umzuändern, dass die
> Strukturdaten im Flash liegen.

Jep, besonders deine strings. Ansonsten: Die Idee sieht sehr sauber aus 
muss ich sagen.

Autor: I_ H. (i_h)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In den prinzipiellen Aufbau ist auch verhältnismäßig die meiste Zeit 
geflossen... den Rest kann man ja bei Bedarf auch neu implementieren ;). 
Frei nach dem Motto Klassen sind dafür da dem ganzen Müll ein sauberes 
Interface zu verpassen (in dem Fall structs und funktionen).

So schaut dann übrigens ein etwas komplexeres Menu aus:
// menu structure
struct UImenu menuAkku =
{
  "Akku", 
  200, 
  4, 
  {
    {"Info", 210}, 
    {"Diag", 230},
    {"Prog", 220},
    {"leave", 2000}
  }
};

struct UImenu menuAkkuInfo = 
{
  "Info", 
  210, 
  5, 
  {
    {"sh. state", 2100}, 
    {"sh. energy", 2101}, 
    {"sh. all", 2102},
    {"hide", 2103},
    {"back", 200}
  }  
};

struct UImenu menuAkkuDiag =
{
  "Diag", 
  230, 
  4, 
  {
    {"set charge", 2300}, 
    {"set disch.", 2301}, 
    {"reset en.", 2302}, 
    {"back", 200}
  }
};

struct UImenu menuAkkuProg =
{
  "Prog",
  220, 
  6, 
  {
    {"Setup", 240}, 
    {"do disch.", 2200}, 
    {"do deep d.", 2201},
    {"do charge", 2202},
    {"stop", 2203}, 
    {"back", 200}
  }
};

struct UImenu menuAkkuProgSet =
{
  "Setup", 
  240, 
  8, 
  {
    {"charge", 2400}, 
    {"d.charge", 2406},
    {"cur. cur.", 2401},
    {"volt thres.", 2402},
    {"ch. time", 2403},
    {"d.ch. time", 2404},
    {"void time", 2405}, 
    {"back", 220}
  }
};

Es gibt noch ein 2. Menu im Zahlenraum 100, den Zeiger auf das Struct 
mit den Menus (UImenuList) kann man ja umladen. Man könnte jetzt auch 
noch für jeden Eintrag ein char mit Attributen dazunehmen, mir ist nur 
bisher noch nix sinnvolles eingefallen.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.