Forum: Mikrocontroller und Digitale Elektronik Menüführung in C


von Schiri (Gast)


Lesenswert?

Ich versuche eine Menüführung in c zu realisieren:

Es sieht in etwa wie folgt aus:
1
unsigned char flag = 0;
2
...
3
while(1)
4
{
5
        if ((!BUTBACK_READ && !BUTNEXT_READ && flag == 0))
6
        {
7
            DelayMs(500);
8
            if ((!BUTBACK_READ && !BUTNEXT_READ))
9
            {
10
                DelayMs(100);
11
                flag++;
12
            }
13
        }
14
15
        if (!BUTNEXT_READ && flag == 1)
16
        {
17
              DelayMs(100);
18
              flag++;
19
              
20
        }
21
22
           if (!BUTBACK_READ && flag == 1)
23
          {
24
              flag--;
25
              beep();
26
          }
27
28
29
          switch (flag)
30
          {
31
              case =0:
32
                  menu_anzeigen0(); break;
33
              case 1:
34
                  menu_anzeigen1(); break;
35
              case 2:
36
                  menu_anzeigen2(); break;
37
          }
38
39
}

Er setzt das Flag immer direkt = 2,was mir unverständlich ist.
Passt das in etwa so oder ist das totaler Schrott? Es soll schon eine 
relativ verschachtelte Menüführung werden.

Welche anderen Optionen gibt es in C?

Danke

von Cyblord -. (cyblord)


Lesenswert?

breaks vergessen.

Compilerwarnungen beachten!

von Karl H. (kbuchegg)


Lesenswert?

Erst mal Tasterkennung und Tastenentprellung vom Menü trennen. Das sind 
zwei Dinge, die so nicht zusammengehören.

Empfehlen kann ich die Danegger Entprellung aus dem Artikel 
Entprellung. Mit der ist der Teil Tastenerkennung erledigt und die 
weitere Auswertung ein Kinderspiel.
1
   currentMenu = 0;
2
   while( 1 ) {
3
4
     if( get_key_press( 1 << KEY_NEXT ) ) {
5
       if( currentMenu < maxNrMenuPoints - 1 )
6
         currentMenu++;
7
     }
8
9
     if( get_key_press( 1 << KEY_BACK ) ) {
10
       if( currentMenu > 1 )
11
         currentMenu--;
12
     }
13
14
    switch( currentMenue )
15
       .....
16
  }

Was du da mit den Makros und dem flag aufziehst, sieht nicht koscher 
aus. Da ich aber nicht weiss, was BUTBACK_READ liefert, enthalte ich 
mich der Stimme. Was diese Makros tun, musst du selber wissen. Und auch 
ob die 0 oder 1 bei nicht gedrückter Taste liefern.

von Cyblord -. (cyblord)


Lesenswert?

Oha nein die Breaks sind ja da.

von Klaus W. (mfgkw)


Lesenswert?

Ich sehe nicht recht, was du mit deinem Quelltext vorhast.

Aber evtl. wolltest du ab dem zweiten if... ein else if.. haben?
Weil so wie es hier steht kann er ja je nach BUT... ggf. mehrfach das 
flag++ machen, und kommt dann leicht auf flag==2.

(Nebenbei: flag ist jetzt kein besonders aussagekräftiger Name, ich sehe 
nicht was es markieren soll...)

von Oliver (Gast)


Lesenswert?

Ein Else hilft da auch nicht weiter, denn in der zweiten Runde ist flag 
dann trotzdem wieder 2.

Oliver

von Klaus W. (mfgkw)


Lesenswert?

Wenn du das in der zweiten "Runde" (der Durchlauf deiner Schleife?) 
wieder wie zu Anfang haben willst, musst du flag halt nach jedem 
Durchlauf zurücksetzen - sonst bleibt es natürlich auf seinem Wert 
stehen.

von Schiri (Gast)


Lesenswert?

Hallo,

ich habe die Menüführung ähnlich dem Vorschlag von Karl Heinz realisiert 
und es klappt auch wie gewünscht, danke dafür.

Jetzt möchte ich aber in den Untermenüs, dieselben Taster mit einer 
anderen Funktion abfragen, also in etwa so:
1
   currentMenu = 0;
2
   while( 1 ) {
3
4
     if( get_key_press( 1 << KEY_NEXT ) ) {
5
       if( currentMenu < maxNrMenuPoints - 1 )
6
         currentMenu++;
7
     }
8
9
     if( get_key_press( 1 << KEY_BACK ) ) {
10
       if( currentMenu > 1 )
11
         currentMenu--;
12
     }
13
14
    switch( currentMenue )
15
       case 0:
16
       menu_1(); break;
17
18
  }

In menu_1

von Schiri (Gast)


Lesenswert?

EDIT:
In Menu_1 etc. sollen jetzt wieder dieselben Taster abgefragt werden nur 
natürlich was anderes machen. Es handelt sich also um eine Art 
"verzweigtes Menü". Möchte natürlich keine fertigen Lösungen, sondern 
nur ein paar Rat- bzw. Vorschläge wie man sowas machen kann. Am besten 
wäre, wenn man dabei an das obige Beispiel anknüpfen könnte.

Schönen Dank

von Bastler (Gast)


Lesenswert?

Trenne das Ganze in drei Teile:

1. Tastenerkennung mit Entprellung
2. Aktionsmenü -> Hier werden Aktionen je nach aktuellen Menüpunkt 
ausgeführt (Switch case)
3. Anzeigemenü -> Hier werden Dinge angezeigt je nach aktuellen 
Menüpunkt (Switch case)

von Schiri (Gast)


Lesenswert?

Bastler schrieb:
> Trenne das Ganze in drei Teile:
>
> 1. Tastenerkennung mit Entprellung
> 2. Aktionsmenü -> Hier werden Aktionen je nach aktuellen Menüpunkt
> ausgeführt (Switch case)
> 3. Anzeigemenü -> Hier werden Dinge angezeigt je nach aktuellen
> Menüpunkt (Switch case)

Hmm, also in etwa so??

1
currentMenu = 0;
2
   while( 1 ) {
3
4
     if( get_key_press( 1 << KEY_NEXT ) ) {
5
       Switch(currentMenu)
6
       {
7
         case 0:
8
         currentMenu++;
9
         case 1:
10
         if( get_key_press( 1 << KEY_NEXT ) ) ...
11
         case 2:
12
         if( get_key_press( 1 << KEY_BACK ) ) ...
13
         ...
14
       }
15
     }
16
17
     if( get_key_press( 1 << KEY_BACK ) ) {
18
       Switch(currentMenu)
19
       {
20
         case 0:
21
         currentMenu--;
22
         case 1:
23
         if( get_key_press( 1 << KEY_BACK ) )
24
       }
25
     }
26
27
    switch( currentMenue )
28
       case 0:
29
       menu_1(); break;
30
       ...
31
32
  }

von Karl H. (kbuchegg)


Lesenswert?

Schiri schrieb:

>      if( get_key_press( 1 << KEY_NEXT ) ) {
>        Switch(currentMenu)
>        {
>          case 0:
>          currentMenu++;
>          case 1:
>          if( get_key_press( 1 << KEY_NEXT ) ) ...

Nope. Das wird nicht funktionieren. Du wirst es wohl kaum schaffen, die 
Taste genau in dem kurzen Zeitraum zwischen dem ersten Aufruf von 
get_key_press und dem zweiten AUfruf zu drücken. Dazu bist du nicht 
schnell genug.

Eine einfache Version besteht darin, dass man jedes Menü in eine eigene 
Funktion mit ihrer eigenen Schleife verpackt. WIrd ein Submenu 
ausgewählt, dann springt man in die betreffende Funktion, die dieses 
Submenue behandelt. Der Nachteil ist natürlich, dass der Prozessor dann 
in diesem Submenu hängt und nebenbei ausser Interrupts nichts anderes 
mehr macht.
1
void configMenu();
2
3
void mainMenu()
4
{
5
  uint8_t currentMenu = 0;
6
7
  while( 1 )
8
  {
9
    if( get_key_press( 1 << KEY_NEXT ) )
10
    {
11
      if( currentMenu < 1 )
12
        currentMenu++;
13
    }
14
15
    if( get_key_press( 1 << KEY_PREV ) )
16
      if( currentMenu > 0 )
17
        currentMenu--;
18
    }
19
20
    if( get_key_press( 1 << KEY_ACTION ) )
21
      if( currentMenu == 0 )
22
        configMenu();
23
        lcd_clear();
24
      }
25
26
      else if( currentMenu == 1 )
27
        return;
28
    }
29
30
    lcd_gotoxy( 0, 0 );
31
    if( currentMenu == 0 )
32
      lcd_puts( "Konfig" );
33
    else if( currentMenu == 1 )
34
      lcd_puts( "Exit   " );
35
  }
36
}
37
38
void configMenu()
39
{
40
  uint8_t currentMenu = 0;
41
42
  while( 1 )
43
  {
44
    if( get_key_press( 1 << KEY_NEXT ) )
45
    {
46
      if( currentMenu < 2 )
47
        currentMenu++;
48
    }
49
50
    if( get_key_press( 1 << KEY_PREV ) )
51
      if( currentMenu > 0 )
52
        currentMenu--;
53
    }
54
55
    if( get_key_press( 1 << KEY_ACTION ) )
56
      if( currentMenu == 0 )
57
        LightOn = !LightOn;
58
59
      else if( currentMenu == 1 )
60
        SoundOn = !SoundOn;
61
62
      else if( currentMenu == 2 )
63
        return;
64
    }
65
66
    lcd_gotoxy( 0, 0 );
67
    if( currentMenu == 0 )
68
      lcd_puts( LightOn ? "Licht ein" : "Licht aus" );
69
    else if( currentMenu == 1 )
70
      lcd_puts( SoundOn ? "Ton ein  " : "Ton aus  " );
71
    else if( currentMenu == 2 )
72
      lcd_puts( "Zurueck  " );
73
  }
74
}
75
76
int main()
77
{
78
  ....
79
80
81
  while( 1 )
82
  {
83
84
    // wenn der Benutzer auf 'Action' drückt, kommt er in die Menuesteuerung
85
    if( get_key_press( 1 << KEY_ACTION ) )
86
      mainMenu();
87
88
    ....
89
  }
90
}

Optimal ist das noch lange nicht. Aber es ist mal ein Anfang für ein 
simples Menuesystem. Hier kann man sich zb mal überlegen, wie man die 
prinzipiell gleichen Funktionen für die Menues zu einer allgemeinen 
Funktion zusammenfassen kann, so dass man all das was ein Menu ausmacht 
in einer Struktur sammelt und die gleiche Funktion datengetrieben 
arbeitet, so dass man nicht für jedes Submenu eine weitere Funktion 
schreiben muss.

: Bearbeitet durch User
von Schiri (Gast)


Lesenswert?

OK vielen Dank, sieht recht plausibel aus.
Das Problem ist, wenn ich einmal im MainMenu oder im configMenu hänge, 
wie komme ich bei Tasten Druck dann wieder in das vorherige Menu?

Diese Funktionalität muss in meinem Beispiel auf jeden Fall 
gewährleistet sein.
Ist dies irgendwie auch in dem obigen Beispiel möglich

von Uwe (de0508)


Lesenswert?

Hallo Schiri,

ich löse dass bei mir mit zwei Tasten, für die ich zwei Funktionen nutze 
- short_keypress und long_keypress - und somit 4 Events erhalte.

Hier mal die Tabelle zu den Events
1
-------------------------
2
Keypress | Key1  | Key2
3
-------------------------
4
short    | Links | Rechts
5
-------------------------
6
long     | Exit  | Ok
7
-------------------------

Wenn man PeDas Entprellroutine verwendet, dann kann man das so 
konfigurieren.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Schiri schrieb:
> OK vielen Dank, sieht recht plausibel aus.
> Das Problem ist, wenn ich einmal im MainMenu oder im configMenu hänge,
> wie komme ich bei Tasten Druck dann wieder in das vorherige Menu?

Entweder mit einer speziellen Taste oder aber so wie ich das 
vorgeschlagen habe, mit einem Menüpunkt "Zurück".
Aber egal wie, mit der programmteschnischen Umsetzung verschachtelter 
Menues hat das ja nichts zu tun. Denn egal wie du das dann konkret 
machst, dieses Problem hast du in jedem Fall. Bei verschachtelten Menüs 
gibt es nun mal 4 mögliche Aktionen:
 nächter Menüpunkt
 vorhergehender Menüpunkt
 Menüpunkt auswählen bzw. in ein Submenü einsteigen
 aus einem Submenü wieder eine Hierarchieebene aussteigen.

Und die musst du unterscheiden können. Egal wie.


> Diese Funktionalität muss in meinem Beispiel auf jeden Fall
> gewährleistet sein.
> Ist dies irgendwie auch in dem obigen Beispiel möglich

Du hast den Code aber schon studiert. Oder?

: Bearbeitet durch User
von Schiri (Gast)


Lesenswert?

OK Danke,

ich versuche meinen Code mal anzupassen und werde dann berichten. Vielen 
Dank schon einmal für den Denkanstoss.

Mein grundsätzliches Problem ist, dass ich keine "reinen" Action Tasten 
und keine reinen Next oder Back Tasten habe, d.h. je nachdem in welchem 
Menuunterpunkt ich mich befinde, muss ich immer abfragen was der 
jeweilige Taster überhaupt bezwecken soll/muss.

von Nop (Gast)


Lesenswert?

Wäre es nicht einfacher eine Baumstruktur aufzubauen mit einer 
geeigneten struct die auch gleichzeitig noch die Funktionspointer für 
die unterschiedlichen Aktionen des jeweiligen Menupunktes beinhalten?

von Schiri (Gast)


Lesenswert?

Hmmm,

wie (nur in etwa) würde das aussehen?

von Karl H. (kbuchegg)


Lesenswert?

Schiri schrieb:
> Hmmm,
>
> wie (nur in etwa) würde das aussehen?

zb ungefähr so
1
typedef void (*FnctPtr)(void);
2
3
struct MenuEntry
4
{
5
  const char* text;       // Text für den Menüpunkt
6
  uint8_t     idForNext;  // Welcher Menüeintrag ist der nächste wenn Next gedrückt wird
7
  uint8_t     idForBack;  // Welcher Menueeintrag ist der nächste wenn Back gedrückt wird
8
  uint8_t     ifForAction; // welches Submenue soll aufgerufen werden, wenn ... ???
9
  FnctPtr     function;   // Welche Funktion soll aufgerufen werden, wenn ... ???
10
};
11
12
void ToggleLight( void )
13
{
14
  LightOn != LightOn;
15
}
16
17
void ToggleSound( void )
18
{
19
  SoundOn != SoundOn;
20
}
21
22
struct MenuEntry[] = {
23
  { "Konfig  ",     1, 0xFF,    2, NULL },         // 0
24
  { "Exit    ",  0xFF,    0, 0xFE, NULL },         // 1
25
26
  { "Light   ",     3, 0xFF, 0xFF, ToggleLight },  // 2
27
  { "Sound   ",     4,    2, 0xFF, ToggleSound },  // 3
28
  { "Zurueck ",  0xFF,    3,    0, NULL }          // 4 
29
};

für jeden Menüpunkt gibt es eine Beschreibung: welcher Menüpunkt ist der 
nächste, wenn auf NEXT gedrückt wird, welcher Menüpunkt ist der nächste 
wenn auf PREV gedrückt wird und welcher Menüpunkt wird angesprungen wenn 
eine entsprechende Aktion auszuführen ist. Das kann entweder ein anderer 
Menüpunkt sein oder eine Funktion die auszuführen ist.
(Bei allen Id der Menüpunkte hab ich eingeführt, das 0xFF als 'kein 
Eintrag' angesehen wird, da ich die 0 brauche, weil 0 ja ein gültiger 
Index ins Array ist. 0xFE ist der Code an dieser Stelle, dass das 
Menüsystem überhaupt verlassen werden soll)

Da kann man dann eine einfache FUnktion schreiben, die ganz einfach beim 
ersten Menüpunkt anfängt und die Information, welche Taste gedrückt 
wurde mit den Informationen der Beschreibung verknüpft, um die 
gewünschte Aktion zu machen
1
void HandleMenu()
2
{
3
  uint8_t currentId = 0;
4
5
  while( currentId != 0xFE )
6
  {
7
    if( get_key_press( 1 << NEXT ) )
8
    {
9
      if( MenuEntry[currentId].idForNext != 0xFF )
10
        currentId = MenuEntry[currentId].idForNext;
11
    }
12
13
    if( get_key_press( 1 << PREV ) ) {
14
      if( MenuEntry[currentId].idForPrev != 0xFF )
15
        currentId = MenuEntry[currentId].idForPrev;
16
    }
17
18
    if( get_key_press( 1 << ACTION ) ) {
19
      if( MenuEntry[currentId].idForAction != 0xFF )
20
        currentId = MenuEntry[currentId].idForAction;
21
22
      else if( MenuEntry[currentId].function != NULL )
23
        MenuEntry[currentId].function();
24
    }
25
26
    lcd_gotoxy( 0, 0 )
27
    lcd_puts( MenuEntry[currentId].text );
28
  }
29
}

wie du allerdings mit nur 2 Tasten eine vernünftige Menüführung 
hinkriegen willst, ist mir noch schleierhaft. Zumindest 3 Tasten 
brauchst du mindestens. Denn 2 Tasten brauchst du ja schon, um durch die 
Menüs vorwärts und rückwärts zu navigieren.
Gut, bei kleinen Menüs könnte man auf das rückwärts navigieren 
verzichten und die Menüpunkte einer Ebene im Kreis durchlaufen lassen. 
Dann würden nur 2 Tasten reichen. Aber du willst ja anscheinend vorwärts 
und rückwärts navigieren können. Dann allerdings brauchst du noch eine 
3. Taste mit der Bedeutung "Diese Aktion jetzt ausführen". Gedanken 
lesen kann ein Computer nun mal nicht.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Schiri schrieb:

> Mein grundsätzliches Problem ist, dass ich keine "reinen" Action Tasten
> und keine reinen Next oder Back Tasten habe, d.h. je nachdem in welchem
> Menuunterpunkt ich mich befinde, muss ich immer abfragen was der
> jeweilige Taster überhaupt bezwecken soll/muss.

Ohne jetzt die Details zu kennen, kann ich dir davon nur abraten. 
Benutzer haben es gerne, wenn dieselben Tasten immer dieselben Aktionen 
machen. Oder zumindest eine thematisch ähnliche Aktion. Die 'Vorwärts' 
bzw. 'Rückwärts' Tasten können im Fall des Falles natürlich auch einen 
Wert erhöhen bzw. erniedrigen, oder eine Option nach der anderen 
durchschalten, aber zumindest die Bestätigungstaste ist immer dieselbe. 
Egal ob damit der aktuell angezeigte Menüpunkt bestätigt wird, oder ob 
damit eine Zahleneingabe bestätigt wird oder die Auswahl einer Farbe aus 
5 Möglichkeiten.
Die Metapher ist dann: Mit den Tasten '<+>' bzw. '<->' verändere ich das 
angezeite. Mit der dritten Taste 'OK' übernehme ich diese Veränderung. 
Was immer dann auch diese 'Übernahme' konkret bedeutet.
Das kann sich jeder merken und das kommt jedem natürlich vor, weil er 
das schon 100-te male bei anderen Geräten genau so gemacht hat.

: Bearbeitet durch User
von Schiri (Gast)


Lesenswert?

Danke sehr für die ausführlichen Erläuterungen und Anmerkungen.
Ich werde berichten :)

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.