Ostatnio zachciało mi się zmienić podejście do tworzenia menu. Do tej pory realizowałem je na konstrukcjach switch-case. Jednak ten sposób jest dość uciążliwy, jeżeli menu ma być mocno rozbudowane, ma posiadać submenu itp. Postanowiłem dokonać przesiadki na bardziej przyjazny sposób tworzenia menu. Odpowiedź była jasna: tablice, struktury i wskaźniki...
Poszukałem, pogmerałem i znalazłem kilka bardzo ciekawych "modeli" menu. Szczególnie do gustu przypadł mi pomysł podsunięty przez jednego z użytkowników forum elektroda.pl w tym wątku. Polecam Ci zaglądnięcie do tamtej dyskusji, ponieważ wielu doświadczonych programistów podsunęło w niej inne pomysły na realizację menu. Stąd wcale nie upieram się przy tym, że moja implementacja jest dla każdego tak samo wygodna jak dla mnie. Stąd zachęcam Cię do wypróbowania zarówno tego jak i innych sposobów generowania menu.

 Idea działania tak skonstruowanego menu jasno i klarownie przedstawiona jest tutaj. Wszystko opiera się na prostym automacie sekwencyjnym opisanym jago graf przejść pomiędzy poszczególnymi pozycjami menu. Przejścia do kolejnego stanu wyzwalane są zdarzeniami generowanymi przez użytkownika. Względem oryginału dokonałem szeregu modyfikacji i założeń, które pozwoliły na uzyskanie menu w stylu jak na poniższym obrazku:

Obrazek dostępny na www.elektroda.pl

Koncepcja działania menu zakłada, że:
- jezeli dana pozycja menu ma submenu to klikniecie ok powoduje wejście do submenu
- jezeli dana pozycja posiada wartość do edycji to klikniecie ok na tej pozycji powoduje zaprzestanie wyświetlania w drugiej linii kolejnej pozycji menu, zamiast tego wyświetlana jest wartość edytowana,
- jeżeli występuje kilka wartości do edycji to klawisz down przyjmuje funkcję przełącznika pomiędzy tymi pozycjami.
Struktura opisująca pojedynczą pozycję menu wygląda następująco:

enum{fastRep=15,slowRep=35};
typedef struct
{
 unsigned char next_state[4]; //przejścia do następnych stanów
 uint8_t KeyClickRepeat;
 uint8_t KeyNextRepeat;
 void (*callback)(unsigned char event); //funkcja zwrotna
 const unsigned char* Line[Lines]; //tekst dla 1. linii LCD
} menu_item;

 Z tak spreparowanej struktury tworzymy tablicę przechowywaną w pamięci programu - jest to tablica zawierająca poszczególne pozycje naszego menu. Aby ułatwić sobie jej konstruowanie (rozpiska kolejnych stanów automatu) warto jest wykorzystać typ wyliczeniowy enum. Dla przykładu menu o następującej strukturze:

#define E_IDDLE 0
#define E_Prev 1
#define E_Click 2
#define E_Next 3


volatile unsigned char menu_event = E_IDDLE;

const unsigned char MN01[] PROGMEM="1.Nastawy";
 const unsigned char subMN01_1[] PROGMEM="1.Wybieg";
 const unsigned char subMN01_2[] PROGMEM="2.Roznica Temp.";
 const unsigned char subMN01_3[] PROGMEM="3.Histereza";
 const unsigned char subMN01_4[] PROGMEM="4.Zakres Temp.";


const unsigned char MN02[] PROGMEM="2.Czas / Data";
 const unsigned char subMN02_1[] PROGMEM="1.Czas";
 const unsigned char subMN02_2[] PROGMEM="2.Data";

const unsigned char MN03[] PROGMEM="3.Ramki czasowe";
 const unsigned char subMN03_1[] PROGMEM="1.Ramka 1";
 const unsigned char subMN03_1_c[] PROGMEM="1.Czas aktywn.";
 const unsigned char subMN03_1_d[] PROGMEM="2.Dni aktywn.";
 const unsigned char subMN03_2[] PROGMEM="2.Ramka 2";
 const unsigned char subMN03_3[] PROGMEM="3.Ramka 3";
 const unsigned char subMN03_4[] PROGMEM="4.Ramka 4";
 const unsigned char subMN03_5[] PROGMEM="5.Ramka 5";
 const unsigned char subMN03_6[] PROGMEM="6.Ramka 6";
 const unsigned char subMN03_7[] PROGMEM="7.Ramka 7";
 const unsigned char subMN03_8[] PROGMEM="8.Ramka 8";

const unsigned char MN04[] PROGMEM="4.Termometry";
 const unsigned char subMN04_1[] PROGMEM="1.Zasilenie";
 const unsigned char subMN04_2[] PROGMEM="2.Powrot";
 const unsigned char subMN04_1_w[] PROGMEM="1.Wprogramuj";
 const unsigned char subMN04_1_s[] PROGMEM="2.Skasuj";

 const unsigned char MN05[] PROGMEM="5.Wyjscie";
 const unsigned char subMN05_1[] PROGMEM="1.Zapisz&wyjdz";
 const unsigned char subMN05_2[] PROGMEM="2.Odrzuc&wyjdz";

Po rozpisaniu stanów autoamtu jako enumy:

enum {//pozaMenu,
 Menu1,
 SubMenu1_1,
 SubMenu1_1_1,
 SubMenu1_2,
 SubMenu1_2_1,
 SubMenu1_3,
 SubMenu1_3_1,
 SubMenu1_4,
 SubMenu1_4_1,
 SubMenu1_4_2,
 Menu2,
 SubMenu2_1,
 SubMenu2_1_1,
 SubMenu2_1_2,
 SubMenu2_2,
 SubMenu2_2_1,
 SubMenu2_2_2,
 SubMenu2_2_3,
 Menu3,
 SubMenu3_1,
 SubMenu3_1_1,
 SubMenu3_1_1_1,
 SubMenu3_1_1_2,
 SubMenu3_1_1_3,
 SubMenu3_1_1_4,
 SubMenu3_1_2,
 SubMenu3_1_2_1,
 SubMenu3_2,
 SubMenu3_2_1,
 SubMenu3_2_1_1,
 SubMenu3_2_1_2,
 SubMenu3_2_1_3,
 SubMenu3_2_1_4,
 SubMenu3_2_2,
 SubMenu3_2_2_1,
 SubMenu3_3,
 SubMenu3_3_1,
 SubMenu3_3_1_1,
 SubMenu3_3_1_2,
 SubMenu3_3_1_3,
 SubMenu3_3_1_4,
 SubMenu3_3_2,
 SubMenu3_3_2_1,
 SubMenu3_4,
 SubMenu3_4_1,
 SubMenu3_4_1_1,
 SubMenu3_4_1_2,
 SubMenu3_4_1_3,
 SubMenu3_4_1_4,
 SubMenu3_4_2,
 SubMenu3_4_2_1,
 SubMenu3_5,
 SubMenu3_5_1,
 SubMenu3_5_1_1,
 SubMenu3_5_1_2,
 SubMenu3_5_1_3,
 SubMenu3_5_1_4,
 SubMenu3_5_2,
 SubMenu3_5_2_1,
 SubMenu3_6,
 SubMenu3_6_1,
 SubMenu3_6_1_1,
 SubMenu3_6_1_2,
 SubMenu3_6_1_3,
 SubMenu3_6_1_4,
 SubMenu3_6_2,
 SubMenu3_6_2_1,
 SubMenu3_7,
 SubMenu3_7_1,
 SubMenu3_7_1_1,
 SubMenu3_7_1_2,
 SubMenu3_7_1_3,
 SubMenu3_7_1_4,
 SubMenu3_7_2,
 SubMenu3_7_2_1,
 SubMenu3_8,
 SubMenu3_8_1,
 SubMenu3_8_1_1,
 SubMenu3_8_1_2,
 SubMenu3_8_1_3,
 SubMenu3_8_1_4,
 SubMenu3_8_2,
 SubMenu3_8_2_1,
 Menu4,
 SubMenu4_1,
 SubMenu4_1_1,
 SubMenu4_2,
 SubMenu4_2_1,
 Menu5,
 SubMenu5_1,
 SubMenu5_1_1,
 SubMenu5_2,
 SubMenu5_2_1,
};

Ma następującą postać po zakodowaniu w tablicy:

const menu_item menu[] PROGMEM= {
 // Iddle, Prev, Click, Next
 //{{ pozaMenu, pozaMenu, pozaMenu, Menu1},slowRep, NULL, {MN_iddle,NULL}}, //poza menu
/*********************************************************************************/
 {{ Menu1, Menu5, SubMenu1_1, Menu2},slowRep,slowRep, NULL, {MN01,MN02}}, //1.Nastawy
 //1.Nastawy
 {{ SubMenu1_1, Menu1, SubMenu1_1_1, SubMenu1_2},slowRep,slowRep, NULL, {subMN01_1,subMN01_2}}, //Wybieg
 {{ SubMenu1_1_1, SubMenu1_1, SubMenu1_1_1, SubMenu1_1_1},slowRep,slowRep, ustawWybieg, {subMN01_1,NULL}}, //Wybieg
 {{ SubMenu1_2, Menu1, SubMenu1_2_1, SubMenu1_3},slowRep,slowRep, NULL, {subMN01_2,subMN01_3}}, //Roznica Temperatur
 {{ SubMenu1_2_1, SubMenu1_2, SubMenu1_2_1, SubMenu1_2_1},fastRep,slowRep, ustawRozniceTemperatur, {subMN01_2,NULL}}, //Wybieg
 {{ SubMenu1_3, Menu1, SubMenu1_3_1, SubMenu1_4},slowRep,slowRep, NULL, {subMN01_3,subMN01_4}}, //Histereza
 {{ SubMenu1_3_1, SubMenu1_3, SubMenu1_3_1, SubMenu1_3_1},fastRep,slowRep, ustawHistereze, {subMN01_3,NULL}}, //Wybieg
 {{ SubMenu1_4, Menu1, SubMenu1_4_1, SubMenu1_1},slowRep,slowRep, NULL, {subMN01_4,subMN01_1}}, //ZakresTemp
 {{ SubMenu1_4_1, SubMenu1_4, SubMenu1_4_1, SubMenu1_4_2},fastRep,slowRep, ustawZakresTemp1, {subMN01_4,NULL}}, //Wybieg
 {{ SubMenu1_4_2, SubMenu1_4, SubMenu1_4_2, SubMenu1_4_1},fastRep,slowRep, ustawZakresTemp2, {subMN01_4,NULL}}, //Wybieg
/*********************************************************************************/
 {{ Menu2, Menu1, SubMenu2_1, Menu3},slowRep,slowRep, NULL, {MN02,MN03}}, //2.Data czas
 //2.Data czas
 {{ SubMenu2_1, Menu2, SubMenu2_1_1, SubMenu2_2},slowRep,slowRep, NULL, {subMN02_1,subMN02_2}}, //Data
 {{ SubMenu2_1_1, SubMenu2_1, SubMenu2_1_1, SubMenu2_1_2},slowRep,slowRep, ustawGodz, {subMN02_1,NULL}},
 {{ SubMenu2_1_2, SubMenu2_1, SubMenu2_1_2, SubMenu2_1_1},fastRep,slowRep, ustawMin, {subMN02_1,NULL}},
 {{ SubMenu2_2, Menu2, SubMenu2_2_1, SubMenu2_1},slowRep,slowRep, NULL, {subMN02_2,subMN02_1}}, //czas
 {{ SubMenu2_2_1, SubMenu2_2, SubMenu2_2_1, SubMenu2_2_2},slowRep,slowRep, ustawDzien, {subMN02_2,NULL}},
 {{ SubMenu2_2_2, SubMenu2_2, SubMenu2_2_2, SubMenu2_2_3},slowRep,slowRep, ustawMiesiac, {subMN02_2,NULL}},
 {{ SubMenu2_2_3, SubMenu2_2, SubMenu2_2_3, SubMenu2_2_1},slowRep,slowRep, ustawRok, {subMN02_2,NULL}},
/*********************************************************************************/
 {{ Menu3, Menu2, SubMenu3_1, Menu4},slowRep,slowRep, NULL, {MN03,MN04}}, //3.Ramki czasowe
 //3.Ramki czasowe
 {{ SubMenu3_1, Menu3, SubMenu3_1_1, SubMenu3_2},slowRep,slowRep, NULL, {subMN03_1,subMN03_2}}, //Data
 {{ SubMenu3_1_1, SubMenu3_1, SubMenu3_1_1_1, SubMenu3_1_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_1_1_1, SubMenu3_1_1, SubMenu3_1_1_1, SubMenu3_1_1_2},slowRep,slowRep, Ramka1StartH, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_1_1_2, SubMenu3_1_1, SubMenu3_1_1_2, SubMenu3_1_1_3},fastRep,slowRep, Ramka1StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_1_1_3, SubMenu3_1_1, SubMenu3_1_1_3, SubMenu3_1_1_4},slowRep,slowRep, Ramka1StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_1_1_4, SubMenu3_1_1, SubMenu3_1_1_4, SubMenu3_1_1_1},fastRep,slowRep, Ramka1StopM, {subMN03_1_c,NULL}},
 {{ SubMenu3_1_2, SubMenu3_1, SubMenu3_1_2_1, SubMenu3_1_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_1_2_1, SubMenu3_1_2, SubMenu3_1_2_1, SubMenu3_1_2_1},slowRep,slowRep, Ramka1Dni, {subMN03_1_d,NULL}},//Data
 {{ SubMenu3_2, Menu3, SubMenu3_2_1, SubMenu3_3},slowRep,slowRep, NULL, {subMN03_2,subMN03_3}}, //Data
 {{ SubMenu3_2_1, SubMenu3_2, SubMenu3_2_1_1, SubMenu3_2_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_2_1_1, SubMenu3_2_1, SubMenu3_2_1_1, SubMenu3_2_1_2},slowRep,slowRep, Ramka2StartH, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_2_1_2, SubMenu3_2_1, SubMenu3_2_1_2, SubMenu3_2_1_3},fastRep,slowRep, Ramka2StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_2_1_3, SubMenu3_2_1, SubMenu3_2_1_3, SubMenu3_2_1_4},slowRep,slowRep, Ramka2StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_2_1_4, SubMenu3_2_1, SubMenu3_2_1_4, SubMenu3_2_1_1},fastRep,slowRep, Ramka2StopM, {subMN03_1_c,NULL}},
 {{ SubMenu3_2_2, SubMenu3_2, SubMenu3_2_2_1, SubMenu3_2_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_2_2_1, SubMenu3_2_2, SubMenu3_2_2_1, SubMenu3_2_2_1},slowRep,slowRep, Ramka2Dni, {subMN03_1_d,NULL}},//Data
 {{ SubMenu3_3, Menu3, SubMenu3_3_1, SubMenu3_4},slowRep,slowRep, NULL, {subMN03_3,subMN03_4}}, //Data
 {{ SubMenu3_3_1, SubMenu3_3, SubMenu3_3_1_1, SubMenu3_3_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_3_1_1, SubMenu3_3_1, SubMenu3_3_1_1, SubMenu3_3_1_2},slowRep,slowRep, Ramka3StartH, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_3_1_2, SubMenu3_3_1, SubMenu3_3_1_2, SubMenu3_3_1_3},fastRep,slowRep, Ramka3StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_3_1_3, SubMenu3_3_1, SubMenu3_3_1_3, SubMenu3_3_1_4},slowRep,slowRep, Ramka3StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_3_1_4, SubMenu3_3_1, SubMenu3_3_1_4, SubMenu3_3_1_1},fastRep,slowRep, Ramka3StopM, {subMN03_1_c,NULL}},
 {{ SubMenu3_3_2, SubMenu3_3, SubMenu3_3_2_1, SubMenu3_3_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_3_2_1, SubMenu3_3_2, SubMenu3_3_2_1, SubMenu3_3_2_1},slowRep,slowRep, Ramka3Dni, {subMN03_1_d,NULL}}, //Data
 {{ SubMenu3_4, Menu3, SubMenu3_4_1, SubMenu3_5},slowRep,slowRep, NULL, {subMN03_4,subMN03_5}}, //Data
 {{ SubMenu3_4_1, SubMenu3_4, SubMenu3_4_1_1, SubMenu3_4_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_4_1_1, SubMenu3_4_1, SubMenu3_4_1_1, SubMenu3_4_1_2},slowRep,slowRep, Ramka4StartH, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_4_1_2, SubMenu3_4_1, SubMenu3_4_1_2, SubMenu3_4_1_3},fastRep,slowRep, Ramka4StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_4_1_3, SubMenu3_4_1, SubMenu3_4_1_3, SubMenu3_4_1_4},slowRep,slowRep, Ramka4StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_4_1_4, SubMenu3_4_1, SubMenu3_4_1_4, SubMenu3_4_1_1},fastRep,slowRep, Ramka4StopM, {subMN03_1_c,NULL}},
 {{ SubMenu3_4_2, SubMenu3_4, SubMenu3_4_2_1, SubMenu3_4_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_4_2_1, SubMenu3_4_2, SubMenu3_4_2_1, SubMenu3_4_2_1},slowRep,slowRep, Ramka4Dni, {subMN03_1_d,NULL}},//Data
 {{ SubMenu3_5, Menu3, SubMenu3_5_1, SubMenu3_6},slowRep,slowRep, NULL, {subMN03_5,subMN03_6}}, //Data
 {{ SubMenu3_5_1, SubMenu3_5, SubMenu3_5_1_1, SubMenu3_5_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_5_1_1, SubMenu3_5_1, SubMenu3_5_1_1, SubMenu3_5_1_2},slowRep,slowRep, Ramka5StartH, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_5_1_2, SubMenu3_5_1, SubMenu3_5_1_2, SubMenu3_5_1_3},fastRep,slowRep, Ramka5StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_5_1_3, SubMenu3_5_1, SubMenu3_5_1_3, SubMenu3_5_1_4},slowRep,slowRep, Ramka5StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_5_1_4, SubMenu3_5_1, SubMenu3_5_1_4, SubMenu3_5_1_1},fastRep,slowRep, Ramka5StopM, {subMN03_1_c,NULL}},
 {{ SubMenu3_5_2, SubMenu3_5, SubMenu3_5_2_1, SubMenu3_5_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_5_2_1, SubMenu3_5_2, SubMenu3_5_2_1, SubMenu3_5_2_1},slowRep,slowRep, Ramka5Dni, {subMN03_1_d,NULL}}, //Data
 {{ SubMenu3_6, Menu3, SubMenu3_6_1, SubMenu3_7},slowRep,slowRep, NULL, {subMN03_6,subMN03_7}}, //Data
 {{ SubMenu3_6_1, SubMenu3_6, SubMenu3_6_1_1, SubMenu3_6_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_6_1_1, SubMenu3_6_1, SubMenu3_6_1_1, SubMenu3_6_1_2},slowRep,slowRep, Ramka6StartH, {subMN03_1_c,NULL}},
 {{ SubMenu3_6_1_2, SubMenu3_6_1, SubMenu3_6_1_2, SubMenu3_6_1_3},fastRep,slowRep, Ramka6StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_6_1_3, SubMenu3_6_1, SubMenu3_6_1_3, SubMenu3_6_1_4},slowRep,slowRep, Ramka6StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_6_1_4, SubMenu3_6_1, SubMenu3_6_1_4, SubMenu3_6_1_1},fastRep,slowRep, Ramka6StopM, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_6_2, SubMenu3_6, SubMenu3_6_2_1, SubMenu3_6_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_6_2_1, SubMenu3_6_2, SubMenu3_6_2_1, SubMenu3_6_2_1},slowRep,slowRep, Ramka6Dni, {subMN03_1_d,NULL}},//Data
 {{ SubMenu3_7, Menu3, SubMenu3_7_1, SubMenu3_8},slowRep,slowRep, NULL, {subMN03_7,subMN03_8}}, //Data
 {{ SubMenu3_7_1, SubMenu3_7, SubMenu3_7_1_1, SubMenu3_7_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_7_1_1, SubMenu3_7_1, SubMenu3_7_1_1, SubMenu3_7_1_2},slowRep,slowRep, Ramka7StartH, {subMN03_1_c,NULL}},
 {{ SubMenu3_7_1_2, SubMenu3_7_1, SubMenu3_7_1_2, SubMenu3_7_1_3},fastRep,slowRep, Ramka7StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_7_1_3, SubMenu3_7_1, SubMenu3_7_1_3, SubMenu3_7_1_4},slowRep,slowRep, Ramka7StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_7_1_4, SubMenu3_7_1, SubMenu3_7_1_4, SubMenu3_7_1_1},fastRep,slowRep, Ramka7StopM, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_7_2, SubMenu3_7, SubMenu3_7_2_1, SubMenu3_7_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_7_2_1, SubMenu3_7_2, SubMenu3_7_2_1, SubMenu3_7_2_1},slowRep,slowRep, Ramka7Dni, {subMN03_1_d,NULL}},//Data
 {{ SubMenu3_8, Menu3, SubMenu3_8_1, SubMenu3_1},slowRep,slowRep, NULL, {subMN03_8,subMN03_1}}, //Data
 {{ SubMenu3_8_1, SubMenu3_8, SubMenu3_8_1_1, SubMenu3_8_2},fastRep,slowRep, NULL, {subMN03_1_c,subMN03_1_d}},
 {{ SubMenu3_8_1_1, SubMenu3_8_1, SubMenu3_8_1_1, SubMenu3_8_1_2},slowRep,slowRep, Ramka8StartH, {subMN03_1_c,NULL}},
 {{ SubMenu3_8_1_2, SubMenu3_8_1, SubMenu3_8_1_2, SubMenu3_8_1_3},fastRep,slowRep, Ramka8StartM, {subMN03_1_c,NULL}},
 {{ SubMenu3_8_1_3, SubMenu3_8_1, SubMenu3_8_1_3, SubMenu3_8_1_4},slowRep,slowRep, Ramka8StopH, {subMN03_1_c,NULL}},
 {{ SubMenu3_8_1_4, SubMenu3_8_1, SubMenu3_8_1_4, SubMenu3_8_1_1},fastRep,slowRep, Ramka8StopM, {subMN03_1_c,NULL}},//Data
 {{ SubMenu3_8_2, SubMenu3_8, SubMenu3_8_2_1, SubMenu3_8_1},fastRep,slowRep, NULL, {subMN03_1_d,subMN03_1_c}},
 {{ SubMenu3_8_2_1, SubMenu3_8_2, SubMenu3_8_2_1, SubMenu3_8_2_1},slowRep,slowRep, Ramka8Dni, {subMN03_1_d,NULL}},//Data
/*********************************************************************************/
 {{ Menu4, Menu3, SubMenu4_1, Menu5},slowRep,slowRep, NULL, {MN04,MN05}}, //4.Termometry
 //4.Termometry
 {{ SubMenu4_1, Menu4, SubMenu4_1_1, SubMenu4_2},slowRep,slowRep, NULL, {subMN04_1,subMN04_2}}, //Data
 {{ SubMenu4_1_1, SubMenu4_1, SubMenu4_1_1, SubMenu4_1_1},slowRep,slowRep, TermometrZasilanie, {subMN04_1,NULL}}, //Data
 {{ SubMenu4_2, Menu4, SubMenu4_2_1, SubMenu4_1},slowRep,slowRep, NULL, {subMN04_2,subMN04_1}}, //Data
 {{ SubMenu4_2_1, SubMenu4_2, SubMenu4_2_1, SubMenu4_2_1},slowRep,slowRep, TermometrPowrot, {subMN04_2,NULL}}, //
/*********************************************************************************/
 {{ Menu5, Menu4, SubMenu5_1, Menu1},slowRep,slowRep, NULL, {MN05,MN01}}, //5.Wyjscie
 //5.Wyjscie
 // Iddle, Prev, Click, Next
 {{ SubMenu5_1, Menu5, SubMenu5_1_1, SubMenu5_2},slowRep,slowRep, NULL, {subMN05_1,subMN05_2}}, //Data
 {{ SubMenu5_1_1, SubMenu5_1_1, SubMenu5_1_1, SubMenu5_2_1},slowRep,slowRep, opuscZapiszMenu, {NULL,NULL}}, //Data
 {{ SubMenu5_2, Menu5, SubMenu5_2_1, SubMenu5_1},slowRep,slowRep, NULL, {subMN05_2,subMN05_1}}, //czas
 {{ SubMenu5_2_1, SubMenu5_2_1, SubMenu5_2_1, SubMenu5_2_1},slowRep,slowRep, opuscBezZapisuMenu, {NULL,NULL}},
/*********************************************************************************/
 };

Teraz w pętli głównej wywołuję sobie wygodnie:

if(inMenu)
 {
 kUP(*up,25,25,Normal);
 kDOWN(*down,25,pgm_read_byte(&menu[current_menu].KeyNextRepeat),Normal);
 kOK(*ok,25,pgm_read_byte(&menu[current_menu].KeyClickRepeat),Normal);

 if (menu_event)
 {
 change_menu();
 UpdateLicznikAutowyjsciaMenu=true;
 }
 display_menu();
 }

Gdzie funkcje generujące eventy z klawiszy wyglądają tak:

void up(void)
{
 menu_event=E_Prev;
}
void down(void)
{
 menu_event=E_Next;
}
void ok(void)
{
 menu_event=E_Click;
}

Do nawigacji po menu, wywoływania zarejestrowanej funkcji zwrotnej oraz odświeżania wyświetlania aktualnej pozycji służą 2 poniższe funkcje:

void call_event_callback_fun(void)
{
void (*call)(unsigned char event); //wskaźnik na wskaźnik do funkcji w pamięci programu
memcpy_P(&call, &menu[current_menu].callback, sizeof(unsigned char (*)(unsigned char)));//ustaw wskaźnik na wskaźniku wlasciwej funkcji callback
if (call) //jezeli zadeklarowano funkcję callback dla zdarzenia to wywolaj ją
call(menu_event);
}


void change_menu()
{
 //przejdz do nastepnego
 volatile unsigned char previous_menu = 0;
 previous_menu=current_menu;
 current_menu = pgm_read_byte(&menu[current_menu].next_state[menu_event]); //stanu
 //wywolaj funkcje zwrotna
 if(previous_menu==current_menu)
 {
 call_event_callback_fun();
 }
 //skasuj zdarzenie
 menu_event = E_IDDLE;
}
void display_menu(void)
{
//Wyswietlenie migającego kursora (mozna przerobic zeby znaki ssalo z pamięci flash)
 bLCDxy(0,1);
 if(L_MenuZnak1/50)
 {
 bLCDtext(" ");
 }
 else
 {
 bLCDtext(">");
 }
 if(!L_MenuZnak1)
 L_MenuZnak1=100; //timer programowy 8bit rozdzielczosc 10ms
//wyswietlenie menu na wszystkich dostępnych liniach wyswietlacza
 for(uint8_t wiersz=0;wiersz<Lines;wiersz++)
 { unsigned int temp=0;
 temp = pgm_read_word(&menu[current_menu].Line[wiersz]);
 if(temp)
 {
 bLCDxy(1,wiersz+1);
 bLCDpgmtext((void *)temp);
 }
 }
 //wywołanie tutaj jest niezbędne, ponieważ w kodzie obsługi eventu zawarte jest wyswietlanie na ekranie
 call_event_callback_fun();
}
Wywoływanie odświeżania przynajmniej w moim przypadku (buforowy zapis do LCD z czyszczeniem bufora po każdym zapisie) jest niezbędne, aby prawidłowo wyświetlać poszczególne elementy menu.

Ponieważ w projekcie dla którego powstało to opracowanie miałem całe multum wartości do zmiany, musiałem napisać funkcje które umożliwią łatwą zmianę 1,2,3,4 wartości na jednym ekranie. Założenie było takie:
- aktualnie edytowana wartość otoczona jest z obu stron zdefiniowanymi znakami (u mnie są to ">" i "<").
- aktualnie edytowana wartość miga
- pomiędzy wartości mogę wstawiać zdefiniowany znak (np ":" przy edycji czasu)
- edytowana wartość moze byc wyswietlana z zadaną ilością miejsc po przecinku
Na chwilę obecną wygląda to tak:

enum {E_NextRout,E_NextEdit};
enum {noPoint,Point1,Point2,Point3};
void point(uint8_t decPoint)
{
 if(decPoint)
 {
 uint8_t str_len=0;
 for(uint8_t i=0;result[i]!=0;i++)
 {
 str_len++;
 }

 if(str_len>decPoint)
 {
 uint8_t i=0;
 do
 {
 result[str_len-i]=result[str_len-1-i];
 i++;
 }while(i<decPoint);
 result[str_len-i]=',';
 }
 }

}
uint16_t changer(unsigned char event,uint16_t min,uint16_t max,uint16_t Val, uint8_t radix, uint8_t dig,uint8_t decPoint,const uint8_t *symL,const uint8_t *symR,uint8_t keyBeh )
{
 bLCDpgmtext(symL);
 switch (event)
 {
 case E_Next:
 if(keyBeh)
 (Val)--;
 break;
 case E_Click:
 (Val)++;
 break;
 }
 if(min<=max)
 {
 if ((Val) < min)
 (Val) = min;
 if ((Val) > max)
 (Val) = max;
 }
 else
 {
 if ((Val) > min)
 (Val) = max;
 if ((Val) < max)
 (Val) = min;
 }
 val_conv(Val, result, radix, dig);
 point(decPoint);
 if(L_MenuZnak2/50)
 {
 for(uint8_t i=0;result[i]!=0;i++)
 {
 result[i]='_';
 }
 }
 bLCDtext(result);
 if(!L_MenuZnak2)
 L_MenuZnak2=75;
 bLCDpgmtext(symR);
 return Val;
}
enum {First,Second,Third};
u16 changeAndDisplay(u8 posx,u16 war1,u16 war2,u16 war3,u16 min,u16 max,u8 dpoint,u8 zeros,const uint8_t *sym,u8 actual,u8 noV,u8 LastSym,unsigned char event)
{
 uint16_t wartosci[3]={0,0,0};
 wartosci[0]=war1;
 wartosci[1]=war2;
 wartosci[2]=war3;
 if(actual!=First)
 posx++;
 bLCDxy(posx,2);
 for(uint8_t wartosc=1;wartosc<noV+1;wartosc++)
 {
 if((actual+1)==wartosc)
 wartosci[wartosc-1]=changer(event,min,max,wartosci[wartosc-1],Dec,zeros,dpoint,editL,editR,E_NextRout);
 else{
 val_conv(wartosci[wartosc-1], result, Dec, zeros);
 point(dpoint);
 bLCDtext(result);
 }
 if((noV==wartosc))
 {
 if(LastSym)
 {
 bLCDpgmtext(spacja);
 bLCDpgmtext(sym);
 }
 }
 else
 {
 if((wartosc>actual+1)||(wartosc<actual))
 {
 bLCDpgmtext(spacja);
 }
 bLCDpgmtext(sym);
 }
 }
 return wartosci[actual];
}
Cały kod byłby znacznie krótszy i prostszy gdybym operował wyłącznie na wskaźnikach do zmiennych. Jednakże problemem w moim przypadku było używanie zmiennych zarówno 16-to jak i 8-io bitowych. Stąd próba przekania zmiennej 8-io bitowej przez wskaźnik na zmienną 16-to bitową prowadziła do przekłamań przy edycji zmiennych. Użycie tych potworków w callbackach konkretnych pozycji menu wygląda następująco:
void ustawWybieg(unsigned char event)
{
 bLCDxy(6,2);
 ZPro.Wybieg=changer(event,0,90,ZPro.Wybieg,Dec,deg2,noPoint,editL,editR,E_NextEdit);
}
void ustawRozniceTemperatur(unsigned char event)
{
 bLCDxy(5,2);
 ZPro.RoznicaTemperatur=changer(event,0,100,ZPro.RoznicaTemperatur,Dec,deg3,Point1,editL,editR,E_NextEdit);
 bLCDpgmtext(stCelsj);
}
void ustawHistereze(unsigned char event)
{
 bLCDxy(5,2);
 ZPro.Histereza=changer(event,0,100,ZPro.Histereza,Dec,deg3,Point1,editL,editR,E_NextEdit);
 bLCDpgmtext(stCelsj);
}
void ustawZakresTemp1(unsigned char event)
{
 ZPro.TemperaturaMinimalna=changeAndDisplay(0,ZPro.TemperaturaMinimalna,ZPro.TemperaturaMaksymalna,0,450,250,Point1,deg3,stCelsj,First,2,1, menu_event);
}
void ustawZakresTemp2(unsigned char event)
{
 ZPro.TemperaturaMaksymalna=changeAndDisplay(0,ZPro.TemperaturaMinimalna,ZPro.TemperaturaMaksymalna,0,750,550,Point1,deg3,stCelsj,Second,2,1, menu_event);
}
///uint8_t hhhh=0,mmmm=0;

void ustawGodz(unsigned char event)
{
 TPro.pGodziny=changeAndDisplay(4,TPro.pGodziny,TPro.pMinuty,0,23,0,noPoint,deg2,dwukropek,First,2,0, menu_event);
 AktualizujRTC=true;
}
void ustawMin(unsigned char event)
{
 TPro.pMinuty=changeAndDisplay(4,TPro.pGodziny,TPro.pMinuty,0,59,0,noPoint,deg2,dwukropek,Second,2,0, menu_event);
 AktualizujRTC=true;
}
void ustawDzien(unsigned char event)
{
 TPro.pDzien=changeAndDisplay(0,TPro.pDzien,TPro.pMiesiac,TPro.pRok,31,1,noPoint,deg2,naw2,First,3,0, menu_event);
 bLCDxy(13,2);
 bLCDpgmtext(DniTygodnia[TSys.DzienTygodnia-1]);
 AktualizujRTC=true;
}
void ustawMiesiac(unsigned char event)
{
 TPro.pMiesiac=changeAndDisplay(0,TPro.pDzien,TPro.pMiesiac,TPro.pRok,12,1,noPoint,deg2,naw2,Second,3,0, menu_event);
 bLCDxy(13,2);
 bLCDpgmtext(DniTygodnia[TSys.DzienTygodnia-1]);
 AktualizujRTC=true;
}
void ustawRok(unsigned char event)
{
 TPro.pRok=changeAndDisplay(0,TPro.pDzien,TPro.pMiesiac,TPro.pRok,42,12,noPoint,deg2,naw2,Third,3,0, menu_event);
 bLCDxy(13,2);
 bLCDpgmtext(DniTygodnia[TSys.DzienTygodnia-1]);
 AktualizujRTC=true;
}

Jak widać wywołując te bloczki można od razu zdefiniować zachowanie klawisza next (u mnie down):
- jeżeli w submenu jest tylko jedna wartość do zmiany to klawisz służy do jej dekrementacji,
- jeżeli submenu zawiera kilka wartości do zmiany to klawisz służy do przełączania się pomiędzy nimi.
Poza tym bardzo przydatną rzeczą jest możliwość zdefiniowania czasu samopowtarzania dla klawiszy ok i next niezależnie dla każdej z pozycji menu. 

Zgodnie z tym co pisałem na początku - jest to jedynie propozycja sposobu realizacji menu. I wcale nie musi przypaść Ci do gustu. Niemniej jednak osobiście jestem z niej bardzo zadowolony. Fakt, męczące nieco jest tworzenie tablicy przejść automatu sekwencyjnego. Jednak wydaje mi się, że zaproponowany przeze mnie sposób jest na tyle intuicyjny i prosty, że nie sprawi nikomu większego problemu. Zatem zapraszam do własnych eksperymentów!

AKTUALIZACJA!!!
Ze względu na to, iż artykul ten nie opisuje w sposób dostatecznie wyczerpujący problemu menu (w takiej formie) - stworzyłem przykładowy projekt prezentujący działanie tak stworzonego menu. 
Począwszy od przykładu 08 menu zostanie zaimplementowane do wszystkich kolejnych przykładów.

Do pobrania:

Użyj opcji uploadu plików, aby przekazać mi kod do sprawdzenia. Postaram się zajrzeć do niego tak szybko jak będzie to możliwe. Paczkę z poprawionym kodem umieszczę w tym samym miejscu.

 

 

Jeżeli wystąpi sytuacja, w której zostanie wyświetlona zawartość folderu z innego działu - należy wyczyścić cache przeglądarki! Następnie przeładować stronę.

Komentarze  

 
# ST WebSite - Menu wielopoziomowe AVR Cradca prawny 2017-03-24 19:11
Nie wiem co do końca autor miał na myśli..ale i tak jest całkiem nieźle.



Check out my blog post - radca prawny: http://salonkultury.pl/pl/producer/Marfiel-Press/23/2/default/2
Odpowiedz | Odpowiedz z cytatem | Cytować
 

Licznik odwiedzin

Mapa odwiedzin



 

Copyright © ST WebSite 2017

Stronę hostuje FutureHost. []

Strona korzysta z plików cookie. Dane przechowywane na Twoim komputerze służą wyłą…cznie do poprawienia funkcjonalnoś›ci witryny. Jeżeli tego nie akceptujesz - powinieneś› ją… opuś›cić‡.

Akceptujesz ciasteczka z tej witryny?