Strana 1 z 1

Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 04 Okt 2022, 00:27
od používateľa romiadam
Ahojte,
ako sa robi najsikovnejsie (da sa neskor doplnit podla potreby), menu pre 2x20 znakovy LCD displej?
Menu by mohlo byt takto nejako:
image.png
Riadky by sa dookola "tocili" pomocou TL1 a TL2. Ak sa TL1 a TL2 stlacia naraz tak sa vojde do podmenu (ak to mneu ma svoje podmenu), toho riadku, ktory je tam kde su na krabicke namalovane dve sipky. A ked sa vojde do podmenu, tak tam sa zase dalsie riadky (podmenu) "rotuju" pomocou tlacidiel TL1 aleo TL2 a potvrdzuje sa TL1+TL2.

Napriklad podmenu nastavenie Datumu a casu - by som s TL1 posuval kurzor, TL2 meni hodnoty nad kurzorom a TL1+TL2 zapisal cas do RTC a skocilo by sa z podmenu o jednu uroven spat hore.

Mam v hlave viac algoritmov, ale vzdy sa zamotam a nakoniec je to komplikovane. Tak sa chcem opytat, ci na taketo menu neexistuje nejaka univerzalna sablona ze ako sa to robi.

Diki vopred za typy.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 04 Okt 2022, 00:59
od používateľa miso156
Programatori to vascinou robia cez struktury.

Ja to robim cez jednoduchu stavovu masinu, pozri google "finite-state machine".
Jednoduchu som napisal, pretoze v podstate na to pouzivam len niekolko prikazov switch.

Napriklad rotovanie menu pomocou jednej premennej "menu_state" ktora drzi aktualny stav/poziciu v menu:


Kód: Vybrať všetko

if(TL1 == pressed) {  //check for TL1 button
  menu_state++;
  if(menu_state > 2) {  //round menu_state in range 0 - 2
    menu_state = 0;
  }
  update_menu();
}

void update_menu() {

  switch(menu_state) {   //print menu according menu_state right after TL1 was pressed
    case 0:
      LCD_print(Cas);  //ak je menu_state rovne 0 vypis cas
      break;
    case 1:
      LCD_print(Datum);  //ak je menu_state rovne 1 vypis datum
      break;
    case 2:
      LCD_print(Settings);  //ak je menu_state rovne 2 vypis Settings
      break;
    default:
      break;
  }
  
} //end of update_menu()  
Ked chces napriklad podmenu pre Settings, vytvoris dalsi takyto switch a taktiez este jednu premenu "settings_menu_state", ktora drzi aktualny stav Settings podmenu. To ale musis na zaciatku pri stlaceni TL1 zas zvysovat settings_menu_state a nie menu_state (to je zatial kym si v podmenu konstantne) a skakat iba do druheho switchu - toho pre Settings podmenu.

Pozn: Ten druhy switch sa da umiestnit aj priamo do case 2 prveho a zjednodusit si zivot, len sa to niekomu moze zdat neprehladne.
Pozn2: Ak chces aj nieco vykonat pri aktualnej polozke v menu (zmenit nejaku premennu, nieco vypocitat, atd..), mozes to pisat priamo do jednotlivych case-ov.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 04 Okt 2022, 02:44
od používateľa romiadam
Super, zajtra sa na to pozriem podrobnejsie.

Vidis, ten state machine ma ani nenapadol, a pri tom sme to mali aj v skole pre PLC (dialkove studium na starobu :D ).

Skusim to cez ten state machine. To sa mi videlo jednoduche.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 04 Okt 2022, 07:58
od používateľa balu
Mám menu udělané tak, že mám jeden krátký program, který se stará o zobrazení a obsluhu menu a používá k tomu strukturu, která říká, co se má zobrazit a jaká akce se při jednotlivých položkách má provést. Počet řádků a délka řádku se definuje při překladu, takže je jedno, jestli máš displej 2x20, 4x20, 2x16, 4x16 nebo grafický (na 128x64 dostaneš 8 řádků). Tomuto programu pak přes pointry předkládám ony struktury, které říkají, co má dělat. Každá struktura obsahuje položky jednotlivých řádků, případně záhlaví, každá položka obsahuje, co se má zobrazit a co se má při provést při akci (stisku "enter"). Díky tomu je použitý jenom jeden program nezávisle na tom, kolik položek a podmenu vlastní menu obsahuje. Program zpracovává buď 4 tlačítka (nahoru, dolů, enter, escape) nebo rotační kodér, kde se escape simuluje dlouhým stiskem (podmíněný překlad). U dvouřádkového displeje máš jednu nevýhodu - pokud chceš mít zobrazené záhlaví, tak zbývá jenom jeden řádek na položky menu, u jednořádkového displeje pak nemáš k dispozici záhlaví vůbec.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 04 Okt 2022, 21:54
od používateľa balu
Takhle vypadá definice typů pro veškerá menu :

Kód: Vybrať všetko

typedef struct MenuItemStruct		// Položka menu
{
	char *ItemText;					// Text položky, pokud není definovaná programově generovaná položka.
#if (USERMENU_SELECT_DISPLAY == USERMENU_USE_CHAR_DISPLAY)
	void (*UserPaintLine)(void);	// Programově generovaný text položky (například položka obsahující v textu například nastavovanou hodnotu) - CHAR Displej
#else
	void (*UserPaintLine)(unsigned char *ScreenData);	// Programově generovaný text položky (například položka obsahující v textu například nastavovanou hodnotu) - GFX Displej
#endif
	void *OnEnterKey;				// Které menu se má zobrazit při krátkém stisku Enter.
	void *OnLongEnterKey;			// Které menu se má zobrazit při dlouhém stisku Enter.
	bool (*OnEnterCall)(MenuStateStruct);		// Funkce, která se začne volat při stisku Enter. Vrací TRUE pro setrvání v menu, FALSE pro ukončení.
} MenuItemStruct;

typedef struct MenuStruct			// Struktura menu
{
	unsigned char ItemCount;		// Počet položek
	unsigned char ItemIndex;		// Index vybrané položka
	char *MenuLabel;				// Záhlaví menu
#if (USERMENU_SELECT_DISPLAY == USERMENU_USE_CHAR_DISPLAY)
	void (*MenuLabelUserPaint)(void);	// Uživatelsky definované záhlaví - CHAR Displej.
#else
	void (*MenuLabelUserPaint)(unsigned char *ScreenData);	// Uživatelsky definované záhlaví - GFX Displej.
#endif
	void *OnEscapeKey;				// Nadřazené menu (NULL = žádné)
	MenuItemStruct MenuItem[LONGESTMENU];		// Položky menu (počet položek odpovídá nejdelšímu menu
	unsigned char TopIndex;			// Index (0..x) první zobrazené položky (pro scrolování menu)
	bool (*CallFunction)(MenuStateStruct);		// Funkce, která se volá po stisku Enter. Vrací TRUE pro setrvání v menu, FALSE pro ukončení.
} MenuStruct;
Definice proměnných pro jednotlivá menu :

Kód: Vybrať všetko

MenuStruct HlavniMenu, ServisniMenu, RucniMenu, PirMenu, DisplayMenu, CasyMenu;
Inicializace proměnných pro menu a jejich submenu :

Kód: Vybrať všetko

	// Nastavit hlavní menu
	HlavniMenu.ItemIndex = 0;
	HlavniMenu.ItemCount = 3;
	HlavniMenu.MenuLabel = (char *)&HlavniMenuItemText[0];
	HlavniMenu.MenuItem[0].ItemText = (char *)&HlavniMenuItemText[1];
	HlavniMenu.MenuItem[0].OnEnterKey = &DisplayMenu;
	HlavniMenu.MenuItem[1].ItemText = (char *)&HlavniMenuItemText[2];
	HlavniMenu.MenuItem[1].OnEnterKey = &CasyMenu;
	HlavniMenu.MenuItem[2].ItemText = (char *)&HlavniMenuItemText[3];
	HlavniMenu.MenuItem[2].OnEnterKey = &ServisniMenu;

	// Nastavit Displej menu
	DisplayMenu.ItemIndex = 0;
	DisplayMenu.ItemCount = 3;
	DisplayMenu.MenuLabel = (char *)&DisplayMenuItemText[0];
	DisplayMenu.MenuItem[0].ItemText = (char *)&DisplayMenuItemText[1];
	DisplayMenu.MenuItem[0].OnEnterCall = NastaveniJasu;
	DisplayMenu.MenuItem[0].UserPaintLine = PolozkaMenuJas;
	DisplayMenu.MenuItem[1].ItemText = (char *)&DisplayMenuItemText[2];
	DisplayMenu.MenuItem[1].OnEnterCall = NastaveniKontrastu;
	DisplayMenu.MenuItem[1].UserPaintLine = PolozkaMenuKontrast;
	DisplayMenu.MenuItem[2].ItemText = (char *)&DisplayMenuItemText[3];
	DisplayMenu.MenuItem[2].OnEnterCall = SaveParametersToEEPROM;
	DisplayMenu.OnEscapeKey = &HlavniMenu;

	// Nastavit servisní menu
	ServisniMenu.ItemIndex = 0;
	ServisniMenu.ItemCount = 4;
	ServisniMenu.MenuLabel = (char *)&ServisniMenuItemText[0];
	ServisniMenu.MenuItem[0].ItemText = (char *)&ServisniMenuItemText[1];
	ServisniMenu.MenuItem[0].OnEnterCall = NastavitNormalniDisplej;
	ServisniMenu.MenuItem[1].ItemText = (char *)&ServisniMenuItemText[2];
	ServisniMenu.MenuItem[1].OnEnterCall = NastavitDiagDisplej;
	ServisniMenu.MenuItem[2].ItemText = (char *)&ServisniMenuItemText[3];
	ServisniMenu.MenuItem[2].OnEnterKey = &PirMenu;
	ServisniMenu.MenuItem[3].ItemText = (char *)&ServisniMenuItemText[4];
	ServisniMenu.MenuItem[3].OnEnterKey = &RucniMenu;
	ServisniMenu.OnEscapeKey = &HlavniMenu;

	// Nastavit ruční menu
	RucniMenu.ItemIndex = 0;
	RucniMenu.ItemCount = 4;
	RucniMenu.MenuLabel = (char *)&RucniMenuItemText[0];
	RucniMenu.MenuItem[0].ItemText = (char *)&RucniMenuItemText[1];
	RucniMenu.MenuItem[0].OnEnterCall = RucniSvetloKancelar;
	RucniMenu.MenuItem[1].ItemText = (char *)&RucniMenuItemText[2];
	RucniMenu.MenuItem[1].OnEnterCall = RucniSvetloWC;
	RucniMenu.MenuItem[2].ItemText = (char *)&RucniMenuItemText[3];
	RucniMenu.MenuItem[2].OnEnterCall = RucniVentilatorWC;
	RucniMenu.MenuItem[3].ItemText = (char *)&RucniMenuItemText[4];
	RucniMenu.MenuItem[3].OnEnterCall = RucniSvetloPodesta;
	RucniMenu.OnEscapeKey = &ServisniMenu;

	// Nastavit PIR menu
	PirMenu.ItemIndex = 0;
	PirMenu.ItemCount = 2;
	PirMenu.MenuLabel = (char *)&PirMenuItemText[0];
	PirMenu.MenuItem[0].ItemText = (char *)&PirMenuItemText[1];
	PirMenu.MenuItem[0].UserPaintLine = &PolozkaMenuPirSchody;
	PirMenu.MenuItem[0].OnEnterCall = &NastaveniPirSchody;
	PirMenu.MenuItem[1].ItemText = (char *)&PirMenuItemText[2];
	PirMenu.MenuItem[1].UserPaintLine = &PolozkaMenuPirKancl;
	PirMenu.MenuItem[1].OnEnterCall = &NastaveniPirKancl;
	PirMenu.OnEscapeKey = &ServisniMenu;

	// Nastavit menu časů
	CasyMenu.ItemIndex = 0;
	CasyMenu.ItemCount = 4;
	CasyMenu.MenuLabel = (char *)&CasyMenuItemText[0];
	CasyMenu.MenuItem[0].ItemText = (char *)&CasyMenuItemText[1];
	CasyMenu.MenuItem[0].OnEnterCall = NastaveniDobaSviceniPodesty;
	CasyMenu.MenuItem[0].UserPaintLine = PolozkaMenuDobaSviceniPodesty;
	CasyMenu.MenuItem[1].ItemText = (char *)&CasyMenuItemText[2];
	CasyMenu.MenuItem[1].OnEnterCall = NastaveniPovoleniVentilatoru;
	CasyMenu.MenuItem[1].UserPaintLine = PolozkaMenuPovoleniVentilatoru;
	CasyMenu.MenuItem[2].ItemText = (char *)&CasyMenuItemText[3];
	CasyMenu.MenuItem[2].OnEnterCall = NastaveniStartVentilatoru;
	CasyMenu.MenuItem[2].UserPaintLine = PolozkaMenuStartVentilatoru;
	CasyMenu.MenuItem[3].ItemText = (char *)&CasyMenuItemText[4];
	CasyMenu.MenuItem[3].OnEnterCall = NastaveniDobaBehuVentilatoru;
	CasyMenu.MenuItem[3].UserPaintLine = PolozkaMenuDobaBehuVentilatoru;
	CasyMenu.OnEscapeKey = &HlavniMenu;
Proměnná, která říká, jaké menu je zrovna na displeji zobrazeno.

Kód: Vybrať všetko

MenuStruct *ActiveMenu;				// Pointer na proměnnou aktuálně zobrazeného menu (NULL = žádné menu nezobrazeno)
Hotovo. Jedna funkce obsluhuje celý systém menu.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 04 Okt 2022, 22:19
od používateľa romiadam
Super, dakujem pekne. Teraz na to nemam cas sa podrobne pozriet, zajtra mam pohovor na novu prac tak sa pripravujem, ale zajtra sa na to pozriem podrobne.

Velmi pekne vam dakujem. Nepredpokladal som ze sa vam bude chciet pisat/kopirovat aj kody. Velmi si to cenim. Dakujem.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 05 Okt 2022, 08:42
od používateľa Atlan
Este zvaz ti ti treba na to mmalom displeji zobrazovat predchadzajuce a nasledujuce polozky.
inak menu ide vyrobit a takto. a viac tlacidiel je vyhodou pac nemusis riesit podmienky.

vstup do menu je stlacenim tlacidla encodera,aj prechod na dalsiu polozku

Kód: Vybrať všetko

//****************************************************************************************************************************
//------------------------------------------ TDA7313 setings menu ------------------------------------------------------------

	if ((pagetmp==pageAM1))		//AudioMenu 1 set bass
			{
			if (encup)		{TDA7313bassup();  TCNT3=200;encup=0;}
   			if (encdown)	{TDA7313bassdown();TCNT3=200;encdown=0;}
			if (band)  		{WRsetTDA7313();SaveSet();band=0;}
			if (tlenc)  	{pagetmp=pageAM2;TDA7313treble(treble);TCNT3=400;tlenc=0;} 
			}
	if ((pagetmp==pageAM2))		//AudioMenu 2 set treble
			{
			if (encup)		{TDA7313trebleup();  TCNT3=200;encup=0;}
   			if (encdown)	{TDA7313trebledown();TCNT3=200;encdown=0;}
			if (band)  		{WRsetTDA7313();SaveSet();band=0;}
			if (tlenc)  	{pagetmp=pageAM3;if(TDA7313isLOUD==1) nLOUDon();else nLOUDoff();TCNT3=400;tlenc=0;} 
			}

	if ((pagetmp==pageAM3))		//AudioMenu 3 set LOUDnes
			{
			if (encup)		{nLOUDon(); TCNT3=400;encup=0;}
   			if (encdown)	{nLOUDoff();TCNT3=400;encdown=0;}
			if (band)  		{WRsetTDA7313();SaveSet();band=0;}
			if (tlenc)  	{pagetmp=pageAM4;if(AuxOnOff==4) nAUXon();else nAUXoff();TCNT3=400;tlenc=0;} 
			}
	if ((pagetmp==pageAM4))		//AudioMenu 4 set on/off AUX
			{
			if (encup)		{nAUXon(); TCNT3=400;encup=0;}
   			if (encdown)	{nAUXoff();TCNT3=400;encdown=0;}
			if (band)  		{WRsetTDA7313();SaveSet();band=0;}
			if (tlenc)  	{pagetmp=pageAM5;LCD_Position(1,0);lcd_string("Save press BAND ");TCNT3=300;tlenc=0;} 
			}
	if ((pagetmp==pageAM5))		//AudioMenu 5 set save setings
			{
			if (encup)		{TCNT3=255;encup=0;}
			if (encdown)	{TCNT3=255;encdown=0;}
			if (band)  		{WRsetTDA7313();SaveSet();band=0;}
			if (tlenc)  	{pagetmp=backpagetmp;LCD_Position(1,0);lcd_string("    No Save     ");TCNT3=100;tlenc=0;} 
			}
//------------------------------------------ end TDA7313 setings -------------------------------------------------------------



Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 05 Okt 2022, 12:34
od používateľa romiadam
Este zvaz ti ti treba na to mmalom displeji zobrazovat predchadzajuce a nasledujuce polozky.
Netreba. Len som limitovany 2-mi tlacidlami. Mozno este 1 tlacidlo mozem pridat (zrusim ledku). A mozno aj dve. Musim preskumat moznosti.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 05 Okt 2022, 12:48
od používateľa Atlan
K tym 2 tlacidlam pripoj cez 2 diody tretie. Budes mo mat na vyvolanie menu, ukoncenie menu res prechod na dlasiu polocku.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 05 Okt 2022, 19:50
od používateľa balu
Tak ono se dá vymyslet ledacos. Například :
2 tlačítka
  1. 1 - nahoru (doleva)
  2. 2 - dolů (doprava)
  3. dlouhým stiskem 1 - ESC
  4. dlouhým stiskem 2 - ENTER,
  5. současný stisk SAVE
  6. současný dlouhý stisk LOAD DEFAULT -
Rázem z nich máš pomocí SW hned 6...

Velice důležité je, aby ovládání bylo intuitivní.
romiadam napísal:Mozno este 1 tlacidlo mozem pridat (zrusim ledku).
Třetí tlačítko můžeš přidat, aniž bys rušil LEDku - pin můžeš multiplexovat :

Zapojíš LED z pinu přes odpor do GND. V případě potřeby ještě do série přidat diodu(y).
Z téhož pinu před odpor 1k tlačítko do GND. Tlačítko i LEDka musí mít každé svůj odpor, jinak by to nefungovalo.

PIN jako výstupní : LOG. 1 = LED svítí, LOG 0 = LED nesvítí.
PIN jako vstupní, zapnutý pull-up - LOG. 1 = tlačítko nestisknuté, LOG. 0 = tlačítko stisknuté

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 05 Okt 2022, 21:20
od používateľa Miko6005
Tak ono se dá vymyslet ledacos
Tak to ti musim dat za pravdu ze ano ale, v beznej praxi pri pouzivani v radach operatorov vzdy radim aby bolo ovladanie co najintuitivnejsie s pomerne velkym displejom. Pricom sa este odporucam vyhnut farebne zobrazovanie ako modre /slabomodre pismena na bielom pozadi, strasne zle sa to cita na uz mierne znecistenom displeji.

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 06 Okt 2022, 02:33
od používateľa romiadam
Pani klobuk dole, velmi pekne dakujem.

miso156,
Switch case poznam. Toto je velmi dobry napad. :thumbup:

balu,
ty tam pouzivas pointre a tie este nemam nastudovane a z toho dovodu som sa v tom kode trosku stratil. Odlozim si tento sposob na neskor, ked sa dostanem k pointrom.
Tak ono se dá vymyslet ledacos. Například :
2 tlačítka

1 - nahoru (doleva)
2 - dolů (doprava)
3 - dlouhým stiskem 1 - ESC
4 - dlouhým stiskem 2 - ENTER,
5 - současný stisk SAVE
6 - současný dlouhý stisk LOAD DEFAULT -

Rázem z nich máš pomocí SW hned 6...
super napad. Moznosti 1-4 urcite pouzijem. :thumbup:
Třetí tlačítko můžeš přidat, aniž bys rušil LEDku - pin můžeš multiplexovat :
No ta ks tymto si ma dostal uplne. Toto by ma nikdy nenapadlo. :potlesk:


Atlan,
to tvoje audio menu je tiez velmi jednoduche. :thumbup: Aj toto sa mi paci.

Tak bud to spravim cez Switch Case alebo cez If else.

Dakujem velmi pekne za pomoc. Nakopli ste ma viac nez dost. :thumbup:

Re: Ako sa robi menu pre 2x20 znakovy LCD displej

Napísané: 06 Okt 2022, 13:55
od používateľa balu
romiadam napísal:ty tam pouzivas pointre a tie este nemam nastudovane a z toho dovodu som sa v tom kode trosku stratil.
To není kód programu, to je jenom nadefinování toho, jak má menu vypadat a jak se má chovat. Vlastní rutinu jsem sem nedával.
No - vlastně inicializace proměnných kód programu je ...