Простое Меню, W1602D

Фото


Видео
Отдельная благодарность Михаилу, в основе используются его труды )
Возникла задача организовать вывод информации, дисплеи TFT и графический 128х64 оказались избыточными, а в заначке был W1602 ...
Да к тому же требовалось выводить информацию динамически и с возможностью просмотра различных параметров.
На таком дорогом (400 р.), большом по габаритам и абсолютно бестолковом по функционалу дисплее, это возможно только с помощью перехода по соответствующим параметрам в меню.
Для всего этого нужна структура, например:
struct sMenu
{
    uint8 Тип;
    uint8 Количество пунктов;
    char * Заголовок[Язык]:
    void (*Указатель) (для перехода в функцию)
    struct sMenu[15], "пункты" в меню
}

Параметр «Тип» в структуре, позволяет использовать различные ветвления уже в самом «пункте» меню.
Используя подобную модель, теоретически можно очень долго переходить из структуры в структуру, при условии сохранения указателя и количества шагов.

Конечно такие возможности редко кому нужны, и дальше речь пойдет о более простом примере:

Объявим массив структур:
// главное меню
struct sMenuItem MainMenuItem[3] = {tMENU_SUB,3,{MenuMainCaptEng,MenuMainCaptRus},0,{&MenuMainPar1,&MenuMainPar2,&MenuMainPar3},
// меню настроек
tMENU_SUB,2,{MenuSetupInfoCaptEng,MenuSetupInfoCaptRus},0,{&MenuSettings_Par1,&MenuSettings_Par2},
// меню информации
tMENU_SUB,1,{MenuInfoCaptEng,MenuInfoCaptRus},0,{&MenuInfo}

перед этим должны быть объявлены вложенные структуры:
// параметры
struct sMenuItem MenuMainPar1 = {tMENU_EXE,1,{MenuMainPar1CaptEng,MenuMainPar1CaptRus},ParamData0};
struct sMenuItem MenuMainPar2 = {tMENU_EXE,1,{MenuMainPar2CaptEng,MenuMainPar2CaptRus},ParamData1};
struct sMenuItem MenuMainPar3 = {tMENU_EXE,1,{MenuMainPar3CaptEng,MenuMainPar3CaptRus},ParamData2};
// меню настроек
struct sMenuItem MenuSettings_Par1 ={tMENU_EXE,1,{MenuMainPar1CaptEng,MenuMainPar1CaptRus},ParamSetupData0};
struct sMenuItem MenuSettings_Par2 ={tMENU_EXE,1,{MenuMainPar2CaptEng,MenuMainPar2CaptRus},ParamSetupData1};
// меню информации
struct sMenuItem MenuInfo = {tMENU_EXE,1,{MenuI_AboutCaptEng,MenuI_AboutCaptRus},About};

а так же заголовки, они будут отображаться в меню:
const char MenuMainCaptEng[] = {"Menu"};
const char MenuMainCaptRus[] = {"Меню"};
//
const char MenuMainPar1CaptEng[] = {"Param1"};
const char MenuMainPar1CaptRus[] = {"Параметр1"};
const char MenuMainPar2CaptEng[] = {"Param2"};
const char MenuMainPar2CaptRus[] = {"Параметр2"};
const char MenuMainPar3CaptEng[] = {"Param3"};
const char MenuMainPar3CaptRus[] = {"Параметр3"};
//
const char MenuSetupInfoCaptEng[] = {"Setup Info"};
const char MenuSetupInfoCaptRus[] = {"Настройки"};
//
const char MenuInfoCaptEng[] = {"Info"};
const char MenuInfoCaptRus[] = {"Информация"};
//
const char MenuI_AboutCaptEng[] = {"About..."};
const char MenuI_AboutCaptRus[] = {"О нас"};

дальше напишем функцию

void StartMenu(struct sMenuItem* MenuItem)
{
    uint8_t PosCurItemMenu;
    uint8_t CountItemMenu;
    uint8_t Key;
    void (*pointer)();
    uint8_t Type=MenuItem->Type;
    if ((Type==tMENU_SUB)||(Type==tMENU_INFO))
    {
    	CountItemMenu = MenuItem->SubMenuCount;
	Key =0;
	PosCurItemMenu =0;
	DrawMenuHeader(MenuItem->Caption[Language], CurMenu, CountItemMenu,0);
	DrawItem(MenuItem->SubMenu[PosCurItemMenu]->Caption[Language],PosCurItemMenu, CountItemMenu,0);
	if (Type==tMENU_SUB)
	{
        	PosCurItemMenu =0;
		do 
		{
	              if(Key != KeyGet())
		      {
		        Key = KeyGet();
		        // Здесь должна быть задержка

			switch (Key)
			{
			    case KEY_UP:
				if (PosCurItemMenu!=0)
				{
			            PosCurItemMenu --;
			            DrawMenuHeader(MenuItem->Caption[Language], CurMenu, CountItemMenu,0);
			            DrawItem(MenuItem->SubMenu[PosCurItemMenu]->Caption[Language],PosCurItemMenu, CountItemMenu,0);
				}
			     break;
			    case KEY_DOWN:
			        if ((CountItemMenu>0)&&(PosCurItemMenu<CountItemMenu-1))
				{
				    PosCurItemMenu ++;
				    DrawMenuHeader(MenuItem->Caption[Language], CurMenu, CountItemMenu,0);
				    DrawItem(MenuItem->SubMenu[PosCurItemMenu]->Caption[Language],PosCurItemMenu, CountItemMenu,0);
				}
			    break;
			    case KEY_LEFT:
				if (CurMenu!=0)
				{
			            CurMenu --;
				    MenuItem = &MainMenuItem[CurMenu];
				    CountItemMenu = MenuItem->SubMenuCount;
				    PosCurItemMenu =0;
				    DrawMenuHeader(MenuItem->Caption[Language], CurMenu, CountItemMenu,0);
				    DrawItem(MenuItem->SubMenu[PosCurItemMenu]->Caption[Language],PosCurItemMenu, CountItemMenu,0);
				}
			    break;
			    case KEY_RIGHT:
			        if (MENU_COUNT>0)
				{
				    if(CurMenu < MENU_COUNT-1)
				    {    
					CurMenu ++;
					MenuItem = &MainMenuItem[CurMenu];
					CountItemMenu = MenuItem->SubMenuCount;
					PosCurItemMenu =0;
				        DrawMenuHeader(MenuItem->Caption[Language], CurMenu, MENU_COUNT,0);
					DrawItem(MenuItem->SubMenu[PosCurItemMenu]->Caption[Language],PosCurItemMenu, CountItemMenu,0);	
				    }
				}
			    break;
			    case KEY_ENT:
				switch (MenuItem->SubMenu[PosCurItemMenu]->Type)
				{
			            case tMENU_EXE:
					DrawMenuHeader(MenuItem->SubMenu[PosCurItemMenu]->Caption[Language], CurMenu, MENU_COUNT,1);
				        pointer = MenuItem->SubMenu[PosCurItemMenu]->ExecuteFunction;	
					(*pointer)();
					break;
			            default: break;
			    }
			    break;
				}
			     }
			} while ((Key!=KEY_ESC));
		}
		else {}
	}
}

и функцию рисования заголовка:

void DrawMenuHeader(const char Caption[],uint8_t pos, uint8_t posEnd, uint8_t SubFlag)
{
    LCD_ClearLine(0);
    LCD_goto(0,3);
    LCD_Print((char*)&Caption[Language]);
    if(SubFlag==0)
    {
	if((pos>0)&&(MENU_COUNT>0))
	{
            LCD_goto(0,0); LCD_Print("<");
    	}else
	{
	    LCD_goto(0,0); LCD_Print(" ");
	}
	if(pos<MENU_COUNT-1)
	{
	    LCD_goto(0,14); LCD_Print(">");
	}else
	{
	    LCD_goto(0,14); LCD_Print(" ");
	}
	}else
	{
	    LCD_goto(0,2); LCD_Print("(");
	    LCD_goto(0,13); LCD_Print(")");
	}
}

рисования содержимого:
void DrawItem(const char *Caption, uint8_t pos, uint8_t allpos, uint8_t SubFlag)
{
    LCD_ClearLine(1);
    LCD_goto(1,0);
    if(SubFlag==0)
    {
    	LCD_Printchar(ch[pos]);
    	LCD_Print(")");
    	LCD_goto(1,2);
    	LCD_Print((char*)&Caption[Language]);
    }
    else
    {
    	LCD_Print((char*)&Caption[Language]);
    }

    if(SubFlag==0)
    {
    	if(pos<allpos-1)
	{
	    LCD_goto(1,15);
	    LCD_Printchar(0xDA);
	}
	else
	{
	    {
		LCD_goto(1,15);
		LCD_Printchar(' ');
	    }
	}
	if(pos>0)
	{
	    LCD_goto(0,15);
	    LCD_Printchar(0xD9);
	}else
	{
	    {    
		LCD_goto(0,15);
		LCD_Printchar(' ');
	    }
        }
    }
}

и конечную функцию, не буду расписывать их все, для примера достаточно одной:
void ParamData0(void)
{
// выводим для примера - IP адрес
    char data[3];
    uint8_t Key;
    uint8_t IP[4]={192,168,011,102};
    LCD_ClearLine(1);
    LCD_goto(1,0);
    RetChar(IP[0], data);
    LCD_Print(data); LCD_Printchar('.');
    RetChar(IP[1], data);
    LCD_Print(data); LCD_Printchar('.');
    RetChar(IP[2], data);
    LCD_Print(data); LCD_Printchar('.');
    RetChar(IP[3], data);
    LCD_Print(data); LCD_Printchar('.');
    do
    {
    	//здесь обновляем дисплей, если нужно
    	Key = KeyGet();
    } while (Key!=KEY_ESC);
}

«RetChar» возвращает байт в трех символах по указателю char.
  • +5
  • 29 июня 2014, 19:16
  • khomin

Комментарии (6)

RSS свернуть / развернуть
Каким образом LCD_Print определяет сколько символов выводить? Она выводит строку, потому так делать нельзя
LCD_Print(data);
У вас после массива чаров data может быть и не null ведь…
0
Строки лучше давать массивом, а не набором переменных. Еще я бы добавил проверку и установку рабочей области дисплея, чтобы код был универсальным: и для 2х16, и для 4х20.
0
Возможно ли скинуть на почту весь проект целиком? dmalash@gmail.com Как раз возникла задача сделать меню. Данный пример заинтриговал.
0
Для заголовков меню нужно использовать prog_uint8_t (в случае AVR и GCC) или что-то подобное для других архитектур, а не const char. Иначе при инициализации все ваши заголовки будут засунуты в оперативку, которой и так кот наплакал. А при развитом меню, ни на что другое не останется.
0
Функция для печати из памяти программ, соответственно, тоже должна быть отдельная. А для разветвленных меню разумнее использовать массив указателей на обработчики в той же программной памяти. И софтовый стек вызовов для возврата по меню. Вынимая из стека указатели, отматываем кто кого вызвал. Иначе в кейсах погрязнете.
0
Судя по тегам — у автора МК нормальной архитектуры. При портировании на гарвард — да, может потребоваться учитывать, в зависимости от компилятора.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.