Forum: Mikrocontroller und Digitale Elektronik Menüstruktur mit Text im Atmel


von Flo (Gast)


Lesenswert?

Hallo zusammen,
Ich habe mir für einen ATmega64 eine Menüstruktur gebastelt.
Das Menü ist sehr umangreich. Insgesamt 61 Menüs mit Untermenüs.
Das struct sieht so aus:
1
struct Menu {
2
  char *Name;
3
  // Untermenues
4
  struct Menu *Menus[12];
5
  struct Menu *ParentMenu;
6
  // Anzahl von Untermenues
7
  uint8_t MenuCount;
8
  // Index des Cursors in den Untermenues
9
  uint8_t CursorIndex;
10
  Function function;
11
  uint16_t data;
12
} Menu;

Initialisieren tue ich es so (auszugsweise):
1
struct Menu MainMenu;
2
MainMenu.Name =  "MainMenu";
3
MainMenu.data =  0x0000;
4
MainMenu.MenuCount = 3;
5
MainMenu.function = MAIN;
6
7
struct Menu MAIN_;
8
MAIN_.Name =  "MAIN:";
9
MAIN_.data =  0x0000;
10
MAIN_.MenuCount = 0;
11
MAIN_.function = Text_Display;
12
13
struct Menu Controls;
14
Controls.Name =  "Controls";
15
Controls.data =  0x0000;
16
Controls.MenuCount = 7;
17
Controls.function = Normal;
18
19
struct Menu Setup;
20
Setup.Name =  "Setup";
21
Setup.data =  0x0000;
22
Setup.MenuCount = 4;
23
Setup.function = Normal;
24
25
26
MainMenu.Menus[0] = &MAIN_;
27
MainMenu.Menus[1] = &Controls;
28
MainMenu.Menus[2] = &Setup;


Irgendwie scheint er aber mit seinem Speicher nicht klar zu kommen.
Will ich mir z.B. den Name von Setup ausgeben lassen, kommt manchmal 
"Setup", manchmal "Setup/&%§$(%" und manchmal nur Müll.
Sollte ich die structs vielleicht static deklarieren?
Ich hatte auch schon probiert, ein \0 am Ende des Strings einzufügen 
(was er ja eigentlich selber macht) aber das hat auch nur kurz 
funktioniert.

Am Anfang lief auch alles reibungslos. Nur als das Menü immer größer 
wurde, fingen die Probleme an. Deshalb denke ich an ein Speicherproblem.
Mit static, const und volatile habe ich schon rumprobiert und auch schon 
alles Mögliche gelesen.
Aber ich komme einfach nicht weiter.

Hilfe ! :)

von ozo (Gast)


Lesenswert?

Kann es vielleicht sein, dass der Speicher wirklich voll ist? Dann nach 
ordentlich was auf dem Stack und man trifft sich in der Mitte. Würde auf 
die Problembeschreibung zutreffen. Schau doch mal nach der 
Speicherbelegung und rechne mal nach, wieviel Platz dann noch für den 
Stack bleibt. Als Abhilfe könntest du versuche, die ganzen Strings in 
den Flash zu packen.

von Flo (Gast)


Lesenswert?

Ja, das würde auf jeden Fall zutreffen.
Wenn ich nämlich noch eine Zeile Code einfüge, egal was sie macht, dann 
tritt ein anderer Fehler auf.
Also kommen sich da irgendwie die Speicherbereiche in die Quere.

Wie kann ich berechnen, wie viel Platz der Stack braucht?
Wie packe ich die Strings am Besten in den Flash? Habe im Tutorial etwas 
gelesen, das erscheint mir aber recht umständlich.
Soll ich vielleicht gleich alle Menüs im Flash speichern?
Wie mache ich das und wie greife ich später drauf zu?


Ich hatte noch einen sehr merkwürdigen Fehler.
Ich habe vor main eine Variable deklariert.
static volatile uint8_t test = 0;

In main habe ich mir die Variable dann ausgeben lassen und sie hatte auf 
einmal 136 gespeichert. Es wird von nirgendwo anders auf die Variable 
zugegriffen.
Könnte also auch von dem Speicherproblem kommen (nein, muss 
eigentlich...).

Warum warnt avr-gcc eigentlich nicht, wenn es ein Speicherproblem geben 
könnte? Er müsste das doch berechnen können?


Hier mal die Speicherbelegung, die avr-gcc meldet:
1
1>AVR Memory Usage
2
1>----------------
3
1>Device: atmega64
4
1>Program:   21322 bytes (32.5% Full)
5
1>(.text + .data + .bootloader)
6
1>Data:       1992 bytes (48.6% Full)
7
1>(.data + .bss + .noinit)
8
1>EEPROM:        3 bytes (0.1% Full)
9
1>(.eeprom)


Danke :)

von Randy N. (huskynet)


Lesenswert?

Naja der Speicher ist ja in zwei große Teile geteilt: am Anfang befinden 
sich die statischen Daten (und der Heap, wenn du einen hast, was ich 
aber jetzt mal nicht vermute) und vom Ende beginnend wächst der Stack 
zum Anfang des RAMs. Die statischen Daten lassen sich berechnen - klar, 
die stehen ja zum Compilierzeitpunkt fest. Der Stack ist ja aber 
abhängig von der Programmausführung, wenn du also viele Funktionen 
womöglich noch rekursiv aufrufst, wird der Stack schnell in den 
Datenbereich wachsen, wodurch es dann zu genau diesen Fehlern kommen 
kann. Wenn du aber nur z.B. eine Main-Funktion mit z.B. nur wenigen 
lokalen Variablen hast, wird der Stack nie wachsen. Deshalb kann man die 
Stackgröße nicht (wirklich) berechnen.

Ok, wie du dein Problem lösen kannst...naja, also die Möglichkeit, die 
Menüs und alle Strings komplett in den Flash auszulagern würde ich auf 
jeden Fall nutzen, da der Rest Speicherverschwendung ist (es sei denn, 
du änderst an den Menüs zur Laufzeit was). Um das auszulagern muss man 
die Strukturen nur mit einem Attribut versehen und man muss sie im 
Programm über andere Befehle ansprechen (siehe AVR-GCC-Tutorial). Und 
dann kannst du halt mal sehen, ob du viele Funktionen rekursiv aufrufst, 
oder oft große Parameter übergibst. Die Menu-Struktur solltest du 
beispielsweise nie komplett einer anderen Funktion als Argument 
übergeben, sondern immer (wenn möglich) einen Zeiger darauf, da die 
Menu-Struktur schon recht groß ist und diese immer, wenn du sie als 
Argument an eine neue Funktion übergibst, neu im Stack kopiert wird. Die 
Alternative zu Zeigern wäre hier, diese Argumente als "const" zu 
übergeben, aber dazu siehe C-Tutorials im Netz.

Grüße
Randy

von Flo (Gast)


Lesenswert?

Ok,
Dann werde ich als erstes mal das Menü in den Flash verbannen.
Allerdings habe ich noch ein Verständnisproblem.
Also das Programm vom Atmel ist im Flash. Damit die Variablen und das 
Menü doch auch? Wo kommt das Menü denn dann hin, wenn ich es für den 
Flash markiere?
Was passiert dann, wenn ich einen Menüeintrag aus dem Programm aufrufe. 
Dann wird er doch auch wieder auf den Stack oder in den RAM geladen? 
Wann wird er dort wieder entladen?

Im Moment habe ich so viele Fragen, dass ich garnicht weiss, wo 
anzufangen ;)
Tutorials habe ich schon ein paar gelesen. Die werde ich mit meinem 
neuen Wissen jetzt nochmal querlesen.

Danke für Eure Hilfe bisher!

von Flo (Gast)


Lesenswert?

Ach, noch was:
Ich gebe mir über USB viele Debug-Ausgaben.

Z.B. so:
1
usb_puts("Menu active");
Wie kann ich diese Strings in den Flash bringen?
Kann ich die in den Flash casten, oder muss ich die als Variable 
deklarieren und dann auslesen?

von Hannes L. (hannes)


Lesenswert?

> Also das Programm vom Atmel ist im Flash.

Ja...

> Damit die Variablen und das
> Menü doch auch?

Nööö...

AVRs haben Harvard-Architektur, da liegen Programm und Daten in 
verschiedenen Speichern mit verschiedenen Adressräumen.

Programm liegt im Flash (ROM) und kann nur vom Programmer (oder 
Bootloader) verändert werden.

Variablen (auch Arrays) liegen im SRAM (oder notfalls in Registern) und 
können zur Laufzeit verändert werden.

Konstanten sind meist Bestandteil des Programmcodes und liegen daher 
erstmal im Flash. Meist werden sie aber Variablen zugewiesen, damit 
liegt auch eine Kopie davon im SRAM.

Konstanten-Arrays lassen sich auch (als reine Datenbereiche) im Flash 
ablegen, sie lassen sich aber zur Laufzeit nicht mehr verändern, sind 
halt konstant.

In ASM könnte ich Dir auch erklären, wie man das macht (.db und LPM), 
von C habe ich allerdings keine Ahnung (ist mir zu kryptisch), da muss 
es irgendwie "um die Ecke" gemacht werden (Stichwort "Progmem"), denn 
normales ANSI-C sieht das erstmal nicht vor, das ist für 
v.Neumann-Architektur entworfen worden.

...

von Randy N. (huskynet)


Lesenswert?

> Also das Programm vom Atmel ist im Flash. Damit die Variablen und das
> Menü doch auch? Wo kommt das Menü denn dann hin, wenn ich es für den
> Flash markiere?

Genau, alles liegt auch jetzt im Flash. Wie Hannes Lux schon sagte haben 
die AVRs aber Havard-Architektur, d.h. es gibt alle Adressen mehrmals: 
Einmal beginnt der RAM bei Adresse 0, der Flash beginnt bei Adresse 0 
und der EEPROM beginnt bei Adresse 0. Damit du jetzt dein Menü im Code 
wie jede andere normale Variable (die ja auch im RAM liegen) nutzen 
kannst, muss das komplette Menü bei jedem Einschalten des 
Mikrocontrollers komplett in den RAM geladen werden (das macht der 
C-Compiler vor der main() automatisch für dich). Wenn du jetzt das Menü 
für den Flash markierst, belässt der Compiler das dort, es wird also 
nicht beim Einschalten zusätzlich in den RAM geladen. Da es aber wie 
schon gesagt die Adressen dann mehrmals gibt kannst du die als im-Flash 
markierte Variable nicht wie eine normale Variable in C verwenden. Du 
musst jedesmal spezielle Funktionen (definiert in pgmspace.h) verwenden, 
um darauf zugreifen zu können.

> Was passiert dann, wenn ich einen Menüeintrag aus dem Programm aufrufe.
> Dann wird er doch auch wieder auf den Stack oder in den RAM geladen?
> Wann wird er dort wieder entladen?

Da du spezielle Befehle verwenden musst, arbeitest du direkt mit dem 
Flash, das Menü wird also nie in den RAM geladen.

Ok ein Beispiel wie man das in C anstellt: Also erstmal die pgmspace.h 
includen und dann die Variablen, die im Flash bleiben sollen 
folgendermaßen definieren:
1
static uint8_t MeinArray[] PROGMEM = { 1, 2, 3, 4, 5 };

Jetzt hast du also eine neue Variable MeinArray, die sich aber auf den 
Flash bezieht. So kann man sie nun lesen:
1
pgm_read_byte(MeinArray + 1); // Zweites Element

MeinArray ist ein Zeiger wie jeder andere in den RAM auch, nur dass er 
eben in den Flash zeigt. Deshalb kann man zu MeinArray auch einfach 1 
addieren und erhält somit das zweite Element. Würdest du MeinArray ohne 
pgm_read_byte() verwenden, würdest du irgendeinen Wert aus dem RAM 
bekommen, der zufälligerweise an dieser Adresse steht. Eine 
Fehlermeldung bekommst du da nicht - der AVR selbst weiß auch nicht, 
dass MeinArray im Flash liegt.

> Wie kann ich diese Strings in den Flash bringen?

Für Strings gibt es ein Makro:
1
usb_puts_P(PSTR("Menu active")));

Das PSTR sorgt automatisch dafür, dass der String dahinter im Flash 
abgelegt wird, ABER ACHTUNG: die Funktion usb_puts kannst du dann nicht 
mehr wie sie jetzt ist verwenden, denn sie muss ja selbst auch die 
entsprechenden Befehle verwenden, um aus dem Flash zu lesen. Deshalb 
habe ich die Methode in dem Beispiel gleich in "usb_puts_P" umbenannt, 
was sich glaub ich so als Standard eingebürgert hat. Also bei 
Methodenname + _P beziehen sich die Zeiger auf den Flash. So könnte die 
Methode aussehen:
1
void usb_puts_P(PGM_P str)
2
{
3
    char chr = pgm_read_byte(str);
4
  
5
    while (chr != '\0')
6
    {
7
        usb_putc(chr);
8
9
        chr = pgm_read_byte(++str);
10
    }
11
}

Ich hoffe jetzt sind alle Klarheiten beseitigt :-)

Grüße
Randy

von Flo (Gast)


Lesenswert?

Ok, super, jetzt geht es richtig vorwärts bei mir :D

Ich habe meine Menu structs jetzt alle mit "static" deklariert.

Vorher sah es so aus:
1
1>AVR Memory Usage
2
1>----------------
3
1>Device: atmega64
4
1>Program:   20152 bytes (30.7% Full)
5
1>(.text + .data + .bootloader)
6
1>Data:       1476 bytes (36.0% Full)
7
1>(.data + .bss + .noinit)
8
1>EEPROM:        3 bytes (0.1% Full)
9
1>(.eeprom)

Mit static dann so:
1
1>AVR Memory Usage
2
1>----------------
3
1>Device: atmega64
4
1>Program:   18600 bytes (28.4% Full)
5
1>(.text + .data + .bootloader)
6
1>Data:       3489 bytes (85.2% Full)
7
1>(.data + .bss + .noinit)
8
1>EEPROM:        3 bytes (0.1% Full)
9
1>(.eeprom)

Wo sind die structs jetzt? Zugreifen darauf kann ich immer noch ganz 
normal.

Wie bringe ich die structs in den Flash und wie rufe ich sie daraus 
wieder auf? Wären dann die Zeiger auf die Untermenüs noch korrekt? Also 
trägt der Kompiler dann dort die Flash-Adressen der Untermenüs ein?

Wenn ich sie "const" deklariere, wären sie dann im Flash? Aber dann 
funktioniert meine Initialisierung nicht mehr.

Ich versuche gerade, die variablen Teile (ParentMenu und CursorIndex) 
wegzuoptimieren. Dann wären die Menüs tatsächlich komplett statisch und 
würden während der Laufzeit nie verändert werden.

von Flo (Gast)


Lesenswert?

Ich habe mal mit dem PSTR experimentiert. Wenn ich das vor eine Variable 
schreibe, dann verringert sich der "Data" Bereich bei Memory Usage.
Also Data bezeichnet den RAM, richtig?
D.h. dass die Variable nun nur noch im Flash bleibt und nicht mehr in 
den RAM geladen wird.
Zugreifen darauf dann so, wie Du beschrieben hast mit "pgm_read_byte" 
bzw. entsprechenden Funktionen.
Es macht immer mehr "Klick" ;)

von Randy N. (huskynet)


Lesenswert?

> Also Data bezeichnet den RAM, richtig?

Genau :-)

"Program" ist der Flash, der kann ruhig voll werden

"Data" sind die statischen Variablen im RAM. Alles was also nicht von 
Data belegt wird hast du im RAM für die dynamischen Daten (Stack...) 
noch frei

"EEPROM" bezeichnet, wer hätte das gedacht, den EEPROM

> Es macht immer mehr "Klick" ;)

:-)

von Flo (Gast)


Lesenswert?

Ok, dann war meine static Sache oben kontraproduktiv, weil er nun alles 
ins RAM gelegt hat.

Mit PROGMEM ändert sich garnichts. Das AVR-GCC Tutorial hört leider 
genau an der Stelle auf..

Kannst Du mir die Fragen von oben noch beantworten?

---
Wie bringe ich die structs in den Flash und wie rufe ich sie daraus
wieder auf? Wären dann die Zeiger auf die Untermenüs noch korrekt? Also
trägt der Kompiler dann dort die Flash-Adressen der Untermenüs ein?
Funktionieren die Zuweisungen überhaupt oder muss ich die Menus mit 
einer Mega-Codezeile erstellen?

Wenn ich sie "const" deklariere, wären sie dann im Flash? Aber dann
funktioniert meine Initialisierung nicht mehr.

Ich versuche gerade, die variablen Teile (ParentMenu und CursorIndex)
wegzuoptimieren. Dann wären die Menüs tatsächlich komplett statisch und
würden während der Laufzeit nie verändert werden.
---

Soweit schon mal vielen Dank :)

von Hannes L. (hannes)


Lesenswert?

Wenn ich es richtig verstanden habe, ist ein Struct doch eine 
benutzerdefinierte Variable, also eine Zusammenfassung mehrerer 
Variablen.

Ein Menü besteht nun aber aus einem Array von fixen Menütexten 
(Konstanten, gehören ins Flash), einem Array mit den Adressen der 
Routinen für den indirekten Sprung (IJMP) über Z-Pointer (Konstanten, 
gehören ins Flash) und einigen Variablen wie z.B. aktueller Menüpunkt, 
Parameterlisten zu jedem Menüpunkt (Variablen (Veränderliche!) gehören 
nunmal ins RAM).

Du musst nun darauf achten, dass Du Konstanten und Variablen sauber 
trennst, damit Du die Konstanten im Flash lassen kannst, die Variablen 
aber ins RAM kommen. Überprüfe also, ob Dein Struct da nicht Konstanten 
und Variablen zusammenfasst, denn dann wird es mit der Trennung Flash / 
RAM wohl nicht so einfach (wenn überhaupt möglich).

Sorry, ich sehe das aus ASM-Sicht, der AVR aber auch, er kann kein C, er 
kann nur Maschinencode, eine andere Schreibweise von ASM.

C wurde für ursprünglich 16-bittige Computer nach v.Neumann-Architektur 
entwickelt, um damit die Eigenheiten der 8-Bitter und der 
Harvard-Architektur zu berücksichtigen, muss man neben dem eigentlichen 
C auch noch die ganzen Tricks beherrschen, mit denen man die Grenzen des 
ANSI-C aushebelt um den AVR effizient zu programmieren bzw. dessen 
(relativ knappen) Ressourcen nicht unnötig zu verschwenden.

Für alle, die mir jetzt widersprechen möchten, nein, ich verteufele C 
nicht, aber meine AVR-Projekte sind so klein, dass ich auch mit ASM 
zurecht komme, da muss ich nicht auch noch C mit all seinen Tücken 
lernen.

...

von Simon K. (simon) Benutzerseite


Lesenswert?

Flo wrote:
> Ok, dann war meine static Sache oben kontraproduktiv, weil er nun alles
> ins RAM gelegt hat.

Nein nein, das siehst du falsch :-) Die Daten liegen nach wie vor im 
RAM. ALLERDINGS listet dieses kleine Tool, dass die AVR Device Memory 
usage anzeigt, nur die Daten auf, die entweder global oder statisch 
sind. Sprich: Der Speicher wurde auch vorher belegt, nur nicht in der 
Statistik angezeigt. Die Daten wurden alle auf dem Stack erzeugt.

Und nein, dieses kleine Tool kann leider nicht die Stackbelegung zur 
Compile-Zeit berechnen. Das geht nicht, da man ja beispielsweise auch 
rekursive Funktionen schreiben kann (dessen Stack-Usage man nicht vorher 
voraussagen kann).

Das ist der ganze Budenzauber ;)

von Simon K. (simon) Benutzerseite


Lesenswert?

Hannes Lux wrote:
> Du musst nun darauf achten, dass Du Konstanten und Variablen sauber
> trennst, damit Du die Konstanten im Flash lassen kannst, die Variablen
> aber ins RAM kommen. Überprüfe also, ob Dein Struct da nicht Konstanten
> und Variablen zusammenfasst, denn dann wird es mit der Trennung Flash /
> RAM wohl nicht so einfach (wenn überhaupt möglich).

Hannes hat das Problem schon geblickt :-) Ich hab mir da letztens mal 
Gedanken drum gemacht: Vom Prinzip her muss man es so realisieren, dass 
die Konstanten Daten des Menüs in einen Struct-Datentyp kommen, und die 
veränderbaren Daten (aktuelle Menüposition etc...) in einen anderen 
Datentyp kommen.
Zu jedem Menüpunkt legt man jetzt jeweils eines der beiden Structs an. 
Das Struct mit den konstanten Daten landet im Flash, das andere im RAM. 
Anschließend fügt man noch einen Verweis vom konstanten Struct auf das 
Struct, was im RAM liegt hinzu (per Pointer zB) und fertig isset.

von Flo (Gast)


Lesenswert?

Ok, das ist einleuchtend.
Aber wie deklariere ich jetzt ein struct, dass in den Flash soll??
Ich habe schon viel gesucht und einiges mit Progmem und const probiert, 
aber ohne Erfolg.

Vielen Dank an euch für die ausführlichen Erklärungen!
Ich hatte bisher auch eher kleine Programme. Zumindest keine, die soviel 
RAM verwenden und daher keine Probleme. Sparsam mit Ressourcen gehe ich 
schon um.
Allerdings wusste ich hier nicht weiter ;)

Mit euren Erklärungen habe ich es schon geschafft, die RAM Belegung um 
die Hälfte zu reduzieren, ohne das Menü anzufassen. Das Programm scheint 
jetzt auch stabil zu laufen.
Wenn ich noch das Menü in den Flash kriege, bin ich bei ca. 8% RAM 
Nutzung.

von Hagen R. (hagen)


Lesenswert?

In meiner GLCD fürs S65 mache ich's so:
1
// menu system
2
typedef prog_char                menuCap_t;     // Titeltext im FLASH
3
typedef const menuCap_t* PROGMEM menuCap_a;     // array von Titeltexten im FLASH
4
5
typedef struct {                                // Menu Item
6
    const menuCap_t* caption;                   // Titel
7
    const uint8_t    type;                      // Typus -> MM_NONE, MM_FUNC, MM_MENU oder ein Kommando wie MM_BACK, MM_OFF etc.pp.
8
    const prog_void* func;                      // bei MM_FUNC ein Funktionszeiger vom typ menuFunc_t, bei MM_MENU ein Zeiger auf menuDef_t
9
} menuSub_t PROGMEM;
10
11
typedef struct {
12
    const menuCap_t*  caption;                  // Titel
13
    const prog_void*  icon;                     // Symbol
14
    const glcdColor_t mbkcolor;                 // Titel Hintergrund Farbe
15
    const glcdColor_t mlicolor;                 // Titel Hintergrund Lichtreflex
16
    const glcdColor_t mshcolor;                 // Titel Hintergrund Schatten
17
    const glcdColor_t mfgcolor;                 // Titel Schrift
18
    const glcdColor_t ibkcolor;                 // Item Hintergrund
19
    const glcdColor_t ifgcolor;                 // Item Schrift
20
    const glcdColor_t sbkcolor;                 // selektiertes Item Hintergrund
21
    const glcdColor_t slicolor;                 // selektiertes Item Lichtreflex
22
    const glcdColor_t sshcolor;                 // selektiertes Item Schatten
23
    const glcdColor_t sfgcolor;                 // selektiertes Item Schrift
24
    const glcdColor_t vshcolor;                 // editiertes Item Rahmen
25
    const glcdColor_t vfgcolor;                 // editiertes Item Schrift
26
    const uint8_t     count;                    // Anzahl Items
27
    const menuSub_t   items[];
28
} menuDef_t PROGMEM;
29
30
typedef uint8_t (*menuFunc_t)(const menuDef_t* menu, uint8_t index, uint8_t event); // Callback für ein MenuItem

Angewendet dann so
1
// menu definition
2
menuCap_t mcMain[]            = "Hauptmenü";
3
menuCap_t mcPlay[]            = "Drum spielen";
4
menuCap_t mcMetronom[]        = "Metronom";
5
6
// Mainmenu
7
menuDef_t mmMain     = {mcMain, home_bmp, YELLOW, LTYELLOW, DKYELLOW, RED, RGB(0xFF, 0xFF, 0xB0), BLACK, DKGRAY, DKGRAY, DKYELLOW, WHITE, RED, DKGRAY, 5,
8
                       {{mcPlay          , MM_PLAY, 0},
9
                        {mcDeviceOff     , MM_OFF , 0},
10
                        {mcMetronom      , MM_FUNC, menuMain},
11
                        {mcInstrumente   , MM_FUNC, menuMain},
12
                        {mcKonfig        , MM_MENU, &mmConfig}}};

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

>Zu jedem Menüpunkt legt man jetzt jeweils eines der beiden Structs an.
>Das Struct mit den konstanten Daten landet im Flash, das andere im RAM.

Das kann man sich sparen wenn man das Konzept des Menusystemes 
entsprechend anpasst.

Bei meinem System gibt es im Grunde nur die Strukturen der Menusysteme 
die alle im FLASH liegen.

Nun gibt es einen globalen Menuhandler, das ist eine Funktion wie 
nachfolgende
1
// main processing function of menu system
2
extern uint8_t menuProcess(const menuDef_t* menu);

der man zb. das Hauptmenu oben, mmMain, als Parameter übergibt. Diese 
Funktion arbeitet eine Schleife ab solange bis das Menu mit dem Kommando 
MM_EXIT verlassen wird.
Dabei ruft menuProcess() periodisch die externe Funktion menuGetEvent() 
auf, die natürlich im Hauptsource vom Programmierer zu implementieren 
ist. Innerhalb dieser "innersten" Funktion kann man auch dann denjenigen 
Code unterbringen der normalerweise in deiner main()-Schleife enthalten 
ist.
In menuProcess() werden also alle Zustandsvariablen für das aktuelle 
Menu in lokalen Variablen gehalten, also zb. der Index auf das aktuell 
selektierte MenuItem dieses Menus.

Wird nun ein Submenu in menuProcess() ausgewählt so wird menuProcess() 
rekursiv mit diesem ausgewählten Menu aufgerufen. So entsteht im Stack 
eine rekursive "Datenstruktur" die die Reihenfolge der Menuebenen die 
man immer tiefer werdend aufgerufen hat.

Irgendwelche globalem SRAM Variablen sind also garnicht nötig und der 
SRAM Verbrauch ist dynamisch und richtet sich nach der Aufruftiefe des 
aktuell ausgewählten Menus.

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Hier mal die Supportfunktionen die der Programmierer in seinem 
Mainsource implementieren muß.
1
// Menu Event Handling
2
uint8_t menuGetEvent(void) {
3
4
    while(1) {
5
      wdt_reset();
6
      if (timer_event >= 5) {  //50ms
7
        timer_event -= 5;
8
        return MM_TIMER;
9
      }
10
      if (rotary_switch & 0x02) {
11
        rotary_switch = 0;
12
        return MM_SELECTLONG;
13
      }
14
      if (rotary_switch) {
15
        rotary_switch = 0;
16
        return MM_SELECT;
17
      }
18
      if (rotary_delta) {
19
        return MM_STEP;
20
      }
21
      sleep_mode();
22
    }
23
}
24
25
int8_t menuGetStep(void) {
26
27
    return rotary_get();
28
}
29
30
void menuEventFlush(void) {
31
32
    rotary_get();
33
    rotary_switch = 0;
34
}

Man sieht das menuGetEvent() als innerste Funktion die durch 
menuProcess() aufgerufen wird quasi Timerintervalbasiert zurückkehrt, 
oder bei einem Event wie dem Rotary Encoder, Tastatur etc.pp. sofort 
nach menuProcess() zurückkehrt. Sollte aber genügend Rechenzeit 
vorhanden sein so könnte man statt des Sleep() Aufrufes auch noch andere 
Arbeiten erledigen lassen. Also alles was man sonst in Main() gemacht 
hat.

Die Main() sieht dann so aus
1
int main(void) {
2
3
    mainInit();
4
    mainLoad();
5
    mainLogo();
6
7
    rotary_delta = 0;
8
    rotary_switch = 0;
9
10
    while (menuProcess(&mmMain) == MM_PLAY) {
11
      mainTest();
12
      mainLogo();
13
    }
14
15
    mainStop();
16
}

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

und hier noch die menuProcess()
1
uint8_t menuProcess(const menuDef_t* menu) {
2
3
    uint8_t selected = 0;
4
    uint8_t event = MM_PAINT;
5
    while (event < MM_BACK) {
6
      if (event == MM_PAINT) menuPaint(menu, selected, MP_ALL);
7
        else if (event == MM_PAINTITEM) menuPaint(menu, selected, MP_ITEM | MP_SELECTED);
8
      event = menuGetEvent();
9
      if (event == MM_SELECT) {
10
        uint8_t type = pgm_read_byte(&menu->items[selected].type);
11
        if (type == MM_FUNC) {
12
          event = ((menuFunc_t)pgm_read_word(&menu->items[selected].func))(menu, selected, MM_SELECT);
13
        } else if (type == MM_MENU) {
14
          event = menuProcess((menuDef_t*)pgm_read_word(&menu->items[selected].func));
15
        } else {
16
          event = type;
17
          continue;
18
        }
19
        menuEventFlush();
20
      } else {
21
        uint8_t newselected = selected;
22
        if (event == MM_STEP) newselected += menuGetStep();
23
          else if (event == MM_NEXT) newselected++;
24
            else if (event == MM_PRIOR) newselected--;
25
//        if ((int8_t)newselected < 0) newselected = pgm_read_byte(&menu->count) -1;
26
//        if (newselected >= pgm_read_byte(&menu->count)) newselected = 0;
27
        if ((int8_t)newselected < 0) newselected = 0;
28
        if (newselected >= pgm_read_byte(&menu->count)) newselected = pgm_read_byte(&menu->count) -1;
29
        if (newselected != selected)  {
30
          if (pgm_read_byte(&menu->count) > (GLCD_HEIGHT / 22 -1)) {   // if menu has more lines as pass on screen we must scroll
31
            menuPaint(menu, newselected, MP_ALLITEMS | MP_SELECTED);
32
          } else {
33
            menuPaint(menu, selected, MP_ITEM);
34
            menuPaint(menu, newselected, MP_ITEM | MP_SELECTED);
35
          }
36
        }
37
        selected = newselected;
38
      }
39
    }
40
    if (event == MM_BACK) event = MM_PAINT;
41
    return event;
42
}

Sie ist zuständig dafür das das aktuelle Menu gezeichnet wird, es wertet 
die Events von menuGetEvent() aus, also zb. Rotaryencoder, Tastatur, 
Timer, verwaltet das selektierte Menuitem, zeichnet dieses entsprechend 
selktiv neu, führt die Aktionen zum MenuItem aus, zb. rekursiver Aufruf 
eines Submenus oder Anwendungsspezifisch Menu Callback usw.

Der rekursive Stackverbrauch beläuft sich auf 1 Byte "selected" und 2 
Bytes Returnaddresse beim rekursiven Aufrauf eines Untermenus.
Wenn man zb. 3 Menuebenen tief ist so hat man 7 Bytes SRAM dafür 
verbraucht.

Gruß Hagen

von Hagen R. (hagen)


Angehängte Dateien:

Lesenswert?

Mal ein Bild wie das dann aussieht. Das ist das "Einstellungsmenu". 
Dessen Menuitems können mehr als 5 sein, sollte dies der Fall sein so 
kann man das Menu automatisch scrollen lassen. Wie man sieht gibt es 
grundsätzlich drei Arten von Menuitems

1.) Items die eine Standardaktion auslösen wenn man sie selektiert, wie 
zb. "<< Exit". Dieses Menuitem beendet quasi die menuProcess() Schlewife 
und kehrt zum Aufrufer zurück. Das ist in diesem Fall selber 
menuProcess() das das Hauptmenu verwaltet.

2.) Items die Untermenus aufrufen. Wählt man diese aus so wird rekursiv 
menuProcess() mit dem Menu das diesem Item zugeordnet ist aufgerufen.

3.) Items die verschiednene Parameter darstellen und editieren können. 
Im Displymodus werden diese Paramater nur dargestellt, zb. "Minuten bis 
Standby" zeigt rechts eine 5 an. Oder man ist im Editmodus, natürlich 
nur das selektierte Menuitem, hier "MIDI Port". Den Editor erkennt man 
an der roten Umrandung. In diesem Editmodus wählt man per Rotaryencoder 
aus einer Liste von Strings den Wert der Schnittstelle aus, hier 
"RS232", "MIDI", "BEIDE".

Somit kann ein Menu und dessen Menuitems nicht nur das Menu anzeigen 
sondern zeigt auch noch die eingestellten Parameter an und kann diese im 
Menu selber auch noch editieren. Diese Anzeige/Editierung der 
individuellen Parameter wird per Menu/Menuitem-Callback Funktionen 
ermöglicht. Oben der Aufruf in menuProcess()
1
if (type == MM_FUNC) {
2
          event = ((menuFunc_t)pgm_read_word(&menu->items[selected].func))(menu, selected, MM_SELECT);

Gruß Hagen

von Flo (Gast)


Lesenswert?

Ich habe mir mal die besten Ideen abgeguckt und mein Menü optimiert.
"Name" und "Data" lade ich komplett aus dem Flash, der Rest bleibt noch 
im RAM.
Bei gleichem Funktionsumfang komme ich jetzt auf folgende 
Speicherbelegung:
1
1>AVR Memory Usage
2
1>----------------
3
1>Device: atmega64
4
1>Program:   19336 bytes (29.5% Full)
5
1>(.text + .data + .bootloader)
6
1>Data:        499 bytes (12.2% Full)
7
1>(.data + .bss + .noinit)
8
1>EEPROM:        3 bytes (0.1% Full)
9
1>(.eeprom)

Das Programm läuft bis jetzt fehlerfrei. Es sollte nun auch genug 
Speicher haben.

Also nochmal vielen Dank an alle, die mir hier so tatkräftig, schnell 
und umfangreich geholfen haben!!

von Daniel S. (sany)


Lesenswert?

Kannst du mal den vollständigen Code Posten wenn das ginge?
Arbeite gerade ebenfalls an deinem Menü und mein Compiler sagt mir bei:
1
  menuCap_t mcMain[]    ="Hauptmenue";
2
  menuCap_t mcProperties[]  = "Einstellungen";
3
  menuCap_t mcSecurity[]  = "Sicherheit";
4
5
  menuDef_t mmMain = {mcMain,2,{{mcProperties, 0, 0},{mcSecurity, 0,0}}}; // Diese Zeile mag er nicht!...

../control.c:45: error: non-static initialization of a flexible array 
member
../control.c:45: error: (near initialization for 'mmMain')

von Daniel S. (sany)


Lesenswert?

1
typedef prog_char      menuCap_t; // Titeltext im Flash
2
typedef const menuCap_t*  PROGMEM menuCap_a; // array von Titeltexten im Flash
3
4
typedef struct{
5
  const menuCap_t*   caption;
6
  const uint8_t    type;
7
  const prog_void*   funct;
8
} menuSub_t PROGMEM;
9
10
typedef struct{
11
  const menuCap_t*  caption; // Title
12
  const uint8_t    count;
13
  const menuSub_t    items[];
14
} menuDef_t PROGMEM;
15
16
typedef uint8_t (*menuFunct_t)(const menuDef_t* menu, uint8_t index, uint8_t event); // callback

von Daniel S. (sany)


Lesenswert?

Oder hab ich den Code nun falsch verstanden, das ich nun die 
Fehlermeldungen krieg?

von Stefan E. (sternst)


Lesenswert?

1
menuDef_t mmMain = {mcMain,2,{{mcProperties, 0, 0},{mcSecurity, 0,0}}}; // Diese Zeile mag er nicht!...
Wo genau hast du diese Zeile stehen?
Ich vermute der Fehlermeldung nach, dass du versuchst, mmMain als lokale 
Automatic-Variable anzulegen.

von Daniel S. (sany)


Lesenswert?

Diese Zeile steht in meiner Main in einer while(1) schleife.?

von Stefan E. (sternst)


Lesenswert?

Dann mach daraus eine globale Variable.
(oder eine statische lokale)

von Daniel S. (sany)


Lesenswert?

Du meinst also
1
static menuDef_t mmMain = {mcMain,2,{{mcProperties, 0, 0},{mcSecurity, 0,0}}}; // Diese Zeile mag er nicht!...

oder was meinst du ? ;)

von Stefan E. (sternst)


Lesenswert?

Verstehe ich das richtig, du willst mit verschachtelten Strukturen (die 
sogar Arrays variabler Länge enthalten) rumhantieren, aber die 
unterschiedlichen Variablen-Klassen sind dir nicht so recht klar?

Was ist nur aus der guten alten Vorgehensweise geworden, sich erstmal 
mit einfachen Beispielen die Grundlagen anzueignen?

von Daniel S. (sany)


Lesenswert?

Joup, verstehst du ganz richtig...

Die Klassen sind mir schon klar, nur welche der vielen soll ich denn als 
Static anlegen?

Bei dem Kollegen, muss der Code ja wohl funktioniert haben...

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.