Forum: Compiler & IDEs konstante Menustruktur im FLASH ablegen


von Hagen R. (hagen)


Lesenswert?

Hi Experten,

gleich die nächste Frage.

Ich möchte ein variable Menustruktur im FLASH ablegen und habe Probleme 
mit dem richtigen Design, Typdeklarationen etc. pp.

Da ich noch nicht so fit bin hier mal das was ich mir am liebsten 
"wünschen" würde:
1
typedef int (*menuFunc_t)(const int msg, const int param);
2
typedef prog_char menuCaption_t;
3
4
typedef struct {
5
    menuCaption_t *caption;
6
    menuFunc_t func;
7
    menuDef_t submenus[];
8
} menuDef_t;
9
10
const menuDef_t menu = {"Hauptmenu", menuHauptmenu, {
11
                          {"Untermenu 1", menuU1, {
12
                            {"Unteruntermenu 1",  menuUU1, {
13
                            {NULL}}}}},
14
                          {"Untermenu 2", menuU2, {
15
                          {NULL}}}},
16
                        {NULL}
17
                       };

Ich weis das das so nicht geht, wäre aber ideal. Wichtig ist aber auch 
das die Captions im FLASH abgelegt sind, bzw. die ganze Struktur menu im 
FLASH landet.

Bisher meine ich das ich für die Strings globale Konstanten einzeln 
anlegen muß. Das gleiche gilt für die Strukturen der Submenus. Das führt 
aber dazu das die ganze Sache ziemlich unübersichtlich wird und auf 
Grund der fehlenden forward Deklarationsmöglichkeit ich diese 
Deklartation quasi in umgekehrter Reihenfolge durchführen muß. Das sind 
alles Punkte die finally die Deklaraion einer solchen Struktur 
unübersichtlich werden lassen, und das möchte ich im Grunde nicht.


Gruß hagen

von Andy (Gast)


Lesenswert?

Deinen Text kann man schwer lesen, weil das so ein unnötiger Mix aus 
Deutsch und Englisch ist. Arbeite bitte daran.

Hier war mal ein Beitrag dazu:
Beitrag "LCD Menü"
Der hat mir damals gut geholfen.

von Hagen R. (hagen)


Lesenswert?

>>Deinen Text kann man schwer lesen, weil das so ein unnötiger Mix
>>aus Deutsch und Englisch ist.

hm, du bist der Erste der mir das so sagt.

>> Arbeite bitte daran.

Ja werde ich Chef, nach den Erfordernisse im Programmiereralltag, wenn 
es dir Recht ist. Und da ist es eben so das man Englisch spricht ;)

Nichts für ungut. Deinen querverwiesenen Strang hatte ich schon 
durchgearbeitet, eigentlich hatte ich alles im Forum gelesen was mit 
Speisekarten zu tuen hat bevor ich diesen Faden hier im Forum geöffnet 
habe.

...

Nachdem ich nun alles an Docus, speziell zu GCC durchgearbeitet habe, 
entscheide ich mich für folgende Lösung:
1
typedef int (*menuFunc_t)(int msg, int param);
2
typedef prog_char menuCap_t;
3
4
typedef struct {
5
    const menuCap_t *caption;
6
    const menuFunc_t func;
7
    const prog_uint8_t parent;
8
    const prog_uint8_t submenu;
9
} menuDef_t PROGMEM;
10
11
const menuCap_t ss []   = "Hauptmenu";
12
13
const menuCap_t s0 []   = "Metronom";
14
const menuCap_t s1 []   = "Drumpad spielen";
15
const menuCap_t s2 []   = "Instrumente einstellen";
16
const menuCap_t s3 []   = "Konfiguration";
17
const menuCap_t s4 []   = "Drumpad auschalten";
18
19
const menuCap_t s00[]   = "Status";                         // aus/ein
20
const menuCap_t s01[]   = "Beats per Minute";               // 32-228
21
const menuCap_t s02[]   = "Ticks per Beat";                 // 1-8
22
const menuCap_t s03[]   = "Lautstärke";                     // 1-100%
23
const menuCap_t s04[]   = "zurück zum Hauptmenu";
24
25
...
26
...
27
28
const menuDef_t menu[] = {
29
  {ss   , fdef , -1,   2},                //  0
30
  {NULL , fplay, -1,  -1},                //  1 
31
32
  {s0   , fdef ,  0,   7},                //  2
33
  {s1   , fdef ,  0,   1},                //  3
34
  {s2   , fdef ,  0,  11},                //  4
35
  {s3   , fdef ,  0,  16},                //  5
36
  {s4   , foff ,  0,  18},                //  6
37
38
  {s00  , f00  ,  2,  -1},                //  7
39
  {s01  , f01  ,  2,  -1},                //  8
40
  {s02  , f02  ,  2,  -1},                //  9
41
  {s03  , f03  ,  2,  -1},                // 10
42
  {s04  , fback,  2,  -1}                 // 11
43
44
...
45
...
46
47
};

Also Titelzeile (Caption) zum Menu, Nachrichtenfunktion (Messageproc), 
übergeordnetes Menu (Parent) und untergeorndeter Menubaum (Submenu).

Statt für Parent und Submenu die Zeiger (Pointer) in der Konstanten Menu 
zu speichern benutze ich die Indizes in dieses Array.

Das ist die kompakteste Speicherstruktur die nur 6 Bytes pro Menueintrag 
benötigt und denoch alle relevanten Operationen ermöglicht, sprich 
Navigation innerhalb dieses Baumes -> Submenu öffnen, alle Childs zum 
Submenu finden, Nachrichten speziell pro Menu abarbeiten und wieder 
zurück zum übergeordneten Menu wechseln.

Großer Nachteil ist eben der Fakt das man beim späteren Einfügen eines 
neuen Menueintrages alles manuell nacharbeiten muß und nicht wie erhofft 
der Compiler einem die Arbeit dabei abnimmt. Allerdings, ich sage mal, 
wie oft kommt es vor das ich so was nochmal benötige und daran ständig 
rumbauen  muß ? ;)

Gruß Hagen

von Karl heinz B. (kbucheg)


Lesenswert?

> Allerdings, ich sage mal, wie oft kommt es vor das ich so was
> nochmal benötige und daran ständig rumbauen  muß ? ;)

Sag das nicht.

Ich bau meine Menüs meistens so auf

typedef void (*MenuFnct)( void );

struct MenuItem {
  const char* Text;
  MenuFnct*   Fnct;
};

struct Menue {
  const char* Caption;
  int         NrItems;
  MenueItem[] Items;
};

void StartMetronom()
{
}

void EndMetronom()
{
}

void MetronomMen()
{
  struct Menue menue = {
      "Metronom",
      4,
      { "Einstellen", ConfigMetronom },
      { "Starten", StartMetronom },
      { "Stoppen", StopMetronom },
      { "Zurück", NULL }
  }

  while( HandleMenu( menue ) )
    ;
}

void InstrumenteMen()
{
  struct Menue menue = {
     "Instrumente einstellen",
     3,
     { "Auswählen", SelectInstrument },
     { "Was weis ich", NochWas },
     { "Zurück", NULL }
  }

  while( HandleMenu( menue ) )
    ;
}

int main()
{
  struct Menu menue = {
    "Hauptmenü",
    2,
    { "Metronom", MetronomMen },
    { "Instrumente", InstrumentMen },
    { "Konfiguration", KonfigurationMen }
  };

  while( 1 )
    HandleMenu( menue );
}

Also: Im Menue gibt es eine Auflistung aller Menüpunkte.
Bei jedem Menüpunkt steht dabei, welche Funktion für
die Bearbeitung dieses Menüpunktes zuständig ist. So
eine Funktion kann ihrerseits allerdings wieder ein
Menü enthalten, dass mittels HandleMenü angezeigt
und bearbeitet wird.
HandleMenu macht nichts anderes als die Menüpunkte
anzuzeigen und Tasten auszuwerten. Wird ein Punkt
ausgewählt, so wird die entsprechende Funktion aufgerufen.
Kommt die Funktion zurück, so wird das Menü entsprechend
neu aufgebaut (die Funktion könnte ja ihrerseits ein
Menü hingepinselt haben, oder sonst irgendwie das
Dispaly zerstört haben) und weiter gehts. Ist der
Funktionspointer NULL, so wird nichts aufgerufen und
HandleMenu gibt einfach die Kontrolle an den Aufrufer
zurück.

HandleMenu kümmert sich also gar nicht um SubMenues.
Wozu auch? Wenn eine Funktion ein SubMenü braucht,
dann soll sie gefälligst eines vereinbaren und
seinerseits HandleMenu aufrufen.

Die Menüs müssen natürlich nicht in den Funktionen
definiert werden, sondern können auch global sein.
Ich hab sie nur deswegen Funktionslokal gemacht, damit
man sieht, wie Sub-Menues im Grunde durch Aufruf von
Funktionen realisiert sind.

von Hagen R. (hagen)


Lesenswert?

@Karl Heinz:
Hm, das ist garnicht so schlecht. Ich wollte zwar eine zu starke 
Verknüpfung von Deklaration eines Menus zu dessen Implementation 
(Callbacks) verhindern, aber im Grunde ist das ja sowieso unrealistisch.

Mein Gedanke war es eben das viele der Callbacks zu einem MenuItem eh 
immer eine Standard-Funktion aufrufen. Diese dient quasi als 
DefaultHandler und erledigt alle Standardreaktionen, wie Zeichnen, Keys, 
Messages, Timers etc.pp.

Die eigentlichen Spezial-Funktionen konzentrieren sich dann nur noch auf 
die Unterschiede und reichen die meisten Nachrichten an den 
DefaultHandler weiter. Quasi ein ganz ganz primitives OOP 
(objektorientiert) Design.

Aber der wichtigste Punkt deines Vorschlages ist das quasi ein Kette von 
verlinkten Callbacks zur Laufzeit enstehen würde, denn ich werde vom 
Hauptmenu-Callback zum nächsten SubMenu->Callback usw. usw. die 
Nachrichten durchreichen. Das hat den Vorteil das quasi alle 
übergeordneten Menus immer noch die Benachrichtigungskette 
abfangen/manipulieren können. Desweiten kann ich so lokal benötigte 
Variablen in einem SubMenu auf dem Stack ablegen statt sie entweder fix 
global zu deklarieren oder per malloc() arbeiten zu müssen.

Ich werde nach deiner Methode vorgehen allerdings mit dem Unterschied 
das je nach geöffneter Menu-Tiefe der MenuHandler von innen nach aussen 
die Nachrichten weitereicht -> quasi in umgedrehter Reihenfolge wie die 
Menus geöffnet wurden. Die ganze Kette der so verknüpften MenuHandler 
bleibt so lange bestehen wie die SubMenus geöffnet wurden. Die äußerste 
MenuHandler Funktion kehrt quasi garnicht mehr zum Aufrufer zurück, bzw. 
erst wenn das Drumpad ausgeschaltet wird. Der Ontop MenuHandler -> also 
der innerste -> der zum aktuellesten Menu, wird auch das 
Polling/Auswertung von Tasten etc. übernehmen.

Ich kann das so machen da in meinem Projekt alles in ISRs abläuft und 
ansonsten nur das Menu offen ist.

Gute Idee dein Vorschlag, das ist viel besser. Man kann so sogar die 
angezeigten SubMenus je nach Status dynamisch verändern.

Gruß Hagen


von Hagen R. (hagen)


Lesenswert?

Hi Leute,

also ich bin wirklich am verzweifeln und komme nicht weiter. 
Nachfolgendes Skeleton
1
#define MM_PLAY          0xFFF7
2
#define MM_EXIT          0xFFF9
3
#define MM_NEXT          0xFFFB
4
#define MM_PRIOR         0xFFFD
5
#define MM_SELECT        0xFFFF
6
7
typedef uint16_t (*menuFunc_t)(void);
8
typedef const PROGMEM char* menuCap_t;
9
10
typedef struct {
11
    menuCap_t  caption;
12
    menuFunc_t func;
13
} menuSub_t;
14
15
typedef struct {
16
    menuCap_t   caption;
17
    glcdColor_t colors[4];
18
    menuSub_t   menus[];
19
} menuDef_t;
20
21
22
uint16_t menuProcess(const menuDef_t menu) {
23
24
    return(MM_EXIT);
25
}
26
27
uint16_t menuMetronom(void) {
28
    
29
    return(MM_EXIT);
30
}
31
32
uint16_t menuInstrumente(void) {
33
    
34
    return(MM_EXIT);
35
}
36
37
uint16_t menuKonfiguration(void) {
38
    
39
    return(MM_EXIT);
40
}
41
42
uint16_t menuMain(void) {
43
44
    asm("nop");
45
46
    menuCap_t mcMain  = PSTR("Hauptmenü");
47
    menuCap_t mcPlay  = PSTR("Drum spielen");
48
    menuCap_t mcMetro = PSTR("Metronom einstellen");
49
    menuCap_t mcInstr = PSTR("Instrumente einstellen");
50
    menuCap_t mcKonf  = PSTR("Drum konfigureren");
51
    menuCap_t mcOff   = PSTR("Gerät ausschalten");
52
53
    menuDef_t mmMain  = {mcMain, {RED, WHITE, BLUE, BLACK},
54
                        {{mcPlay, MM_PLAY},
55
                         {mcMetro, menuMetronom},
56
                         {mcInstr, menuInstrumente},
57
                         {mcKonf, menuKofiguration},
58
                         {mcOff, MM_EXIT}
59
                         {0, 0}}};
60
61
    return(menuProcess(mmMain));
62
}

in der Funktion menuMain() möchte ich nun eine konstante und im FLASH 
gespeicherte Struktur mmMain anlegen. Egal was ich mache ich bekomme 
immer wieder Fehlermeldungen oder Warnungen das ein impliziter Typcast 
durchgeführt wurde, das eine "non-static initialization of flexible 
Member" vorliegt, "near initialization for mmMain" etc.pp.

Mein Ziel ist es das ich die TypeDef's oben so deklariere das später bei 
der realen Definition einer Menustruktur diese immer im FLASH als 
Konstante gespeichert wird.

Gruß Hagen

von Karl heinz B. (kbucheg)


Lesenswert?

1
uint16_t functionExit() {
2
  return MM_EXIT;
3
}
4
5
const char mcMain[] PROGMEM  = "Hauptmenü";
6
const char mcPlay[] PROGMEM  = "Drum spielen";
7
const char mcMetro[] PROGMEM = "Metronom einstellen";
8
const char mcInstr[] PROGMEM = "Instrumente einstellen";
9
const char mcKonf[] PROGMEM  = "Drum konfigureren";
10
const char mcOff[] PROGMEM   = "Gerät ausschalten";
11
12
const menuDef_t mmMain PROGMEM = {mcMain, {RED, WHITE, BLUE, BLACK},
13
                     {{mcPlay, functionPlay},
14
                      {mcMetro, menuMetronom},
15
                      {mcInstr, menuInstrumente},
16
                      {mcKonf, menuKonfiguration},
17
                      {mcOff, functionExit},
18
                      {0, 0}
19
           }
20
          };
21
22
uint16_t menuMain(void) {
23
24
    asm("nop");
25
26
27
    return(menuProcess(mmMain));
28
}

Noch ein paar Worte zum Stil:

uint16_t menuMetronom(void) {

Das void in der Argumentliste ist überflüssig.
Das ist kein Prototyp.

    return(MM_EXIT);

return ist kein Funktionsaufruf. Die Klammern können
weg.
    return MM_EXIT;

Sieht doch gleich besser aus :-)


von Dirk (Gast)


Lesenswert?

Hallo, ich verfolge diesen Thread mit grossen Interesse, aber ich den 
Unteschied zwischen deinem (Karl Heinz B.) und Hagens nicht. Koenntest 
du mich kurz aufklaeren? Ich seh den Fehler somit nicht :(

von Dirk (Gast)


Lesenswert?

+sehe

von Karl heinz B. (kbucheg)


Lesenswert?

Schau dir die Texte an.
1) raus aus der Funktion und global definieren
2) Texte nicht mittels Pointer allokieren, sondern
   in Arrays.
3) Die Menüstruktur ebenfalls per PROGMEM als globale
   Variable anlegen.

Alternativ könnte auch gehen (hab ich jetzt nicht probiert)
1
const menuDef_t mmMain PROGMEM = {mcMain, {RED, WHITE, BLUE, BLACK},
2
                     {{PSTR( "Drum spielen"),           functionPlay},
3
                      {PSTR( "Metronom einstellen"),    menuMetronom},
4
                      {PSTR( "Instrumente einstellen"), menuInstrumente}
5
                      {0, 0}
6
           }
7
          };

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmspeicher_.28Flash.29

von Hagen R. (hagen)


Lesenswert?

@Karl Heinz:

soweit bzw. ähnlich habe ich auch schon eine Lösung gefunden. Nun machen 
wir es mal komplizierter ;)

Wie sähe das ganze aus wenn man die Deklaration der Menu Strukturen 
_nicht_ global sondern lokal innerhalb einer Funktion machen möchte ??

Zu den anderen Bemerkungen von dir:

- return haste Recht

>>Das void in der Argumentliste ist überflüssig.
>>Das ist kein Prototyp.

Hm ich mache immer ein void da rein, liegt aber daran das ich aus der 
PASCAL/COBOL/PL4 Schiene komme und dort das ganze Typhandling/Variablen 
etc.pp. viel strikter sind. Ist also eine "Gewohnheit" und ich empfinde 
das irgendwie als sauber.

Das ist ja auch der Grund warum ich nicht damit zurecht komme wenn man 
in C hunderte Möglichkeiten habe einen Zeiger zu deklarieren. Wenn ich 
in PASCAL irgendwas als CONST deklariere dann ist das Ganze auch 
wirklich CONST und nicht wie im C das ein Zeiger als Member die Sache 
wieder nicht-CONST macht.

Egal, die wichtigste Frage ist also:

Wie deklariere ich so eine Struktur im FLASH lokal innerhalb einer 
Funktion ?

Das ist jetzt aber nur aus reinem Interesse und dem Bedürfnis den 
Zugriff auf diese Deklarationen stärker zu kapseln als wenn sie global 
verfügbar sind. Also eine ästetische Sache für mich ;)

Gruß Hagen

PS: nachfolgend das was ich bisher selber ausgewurschtelt hatte
1
typedef uint8_t (*menuFunc_t)(void);
2
typedef prog_char menuCap_t;
3
4
typedef struct {
5
    const menuCap_t* caption;
6
    const menuFunc_t func;
7
} menuSub_t PROGMEM;
8
9
typedef struct {
10
    const menuCap_t*  caption;
11
    const glcdColor_t colors[4];
12
    const uint8_t     count;
13
    const menuSub_t   menus[];
14
} menuDef_t PROGMEM;
15
16
17
#define MM_PLAY          0
18
#define MM_OFF           1
19
#define MM_BACK          2
20
#define MM_NONE          3
21
#define MM_NEXT          4
22
#define MM_PRIOR         5
23
#define MM_SELECT        6
24
#define MM_FUNC(value)   ((menuFunc_t)value)
25
26
27
menuCap_t mcMain[]  = "Hauptmenü";
28
menuCap_t mcPlay[]  = "Drum spielen";
29
menuCap_t mcMetro[] = "Metronom einstellen";
30
menuCap_t mcInstr[] = "Instrumente einstellen";
31
menuCap_t mcKonf[]  = "Drum konfigureren";
32
menuCap_t mcOff[]   = "Gerät ausschalten";
33
34
menuDef_t mmMain    = {mcMain, {RED, WHITE, BLUE, BLACK}, 5,
35
                      {{mcPlay, MM_FUNC(MM_PLAY)},
36
                       {mcMetro, mfMetronom},
37
                       {mcInstr, mfInstrumente},
38
                       {mcKonf, mfKonfiguration},
39
                       {mcOff, MM_FUNC(MM_OFF)}}};

das const der Members in den Structs habe ich reingemacht um die blöden 
Warnings bei der Deklaration von mmMain zu vermeiden.

von unsichtbarer WM-Rahul (Gast)


Lesenswert?

>Wie sähe das ganze aus wenn man die Deklaration der Menu Strukturen
>__nicht__ global sondern lokal innerhalb einer Funktion machen möchte ??

Was soll dir das bringen?
(nur interessehalber)
Es landet sowieso irgendwo im FLASH.

von Hagen R. (hagen)


Lesenswert?

Stärkere Restriktion bei den Zugriffsmöglichkeiten.

Auf eine globale Konstante kann jede Funktion zugreifen und deren 
deklarierte Variablen/Konstanten Namen sind global sichtbar.

In einer lokalen Funktion gilt alles auch nur lokal und somit ist die 
Scope dieser Strukturen restriktiver.

Ist einfach eine "Gewöhnungssache" die ich bei meiner professionellen 
Arbeit in anderen Sprachen so benutze. Einfach um sicheren Source zu 
benutzen. Was nicht global deklariert wurde kann auch nicht global 
"mißbraucht" werden. Was nur lokal gültig sein kann sollte auch nur 
lokal als gültig deklariert werden.

Wenn's jetzt mit GCC nicht geht kann ich durchaus damit leben, besonders 
wenn ich davon ausgehen muß das ich noch Tage nach einer Lösung suchen 
müsste. Schöner wäre es aber, und rein aus Interesse wüsste ich gern ob 
und wie es geht ;) (man lernt ja nie aus).

Gruß Hagen

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.