Метод реализации многоуровневого меню STM32+0.96
Выбор аппаратного обеспечения
- Микроконтроллер STM32F103C8T6
- Дисплей 0,96 Oled
- 5 кнопок
Подключение
- SCL —- PA1
- SDA —- PA2
- KEY_UP —- PA4
- KEY_DOWN —- PA5
- KEY_LEFT —- PA3
- KEY_RIGHT —- PA6
- KEY_OK —- PA7
Ссылка на исходный код:
Программа:
/**
* @brief Функция сканирования клавиш
*
* @param mode Режим 1 означает непрерывное сканирование, а режим 0 означает одиночное сканирование.
*/
void KeyScan(u8 mode)
{
static int keyCount = 0;
static int keyState = 0;
if(mode == 1) keyState=0;
if (keyState == 0 && (KEY_UP == 0||KEY_DOWN == 0||KEY_LEFT == 0||KEY_RIGHT == 0||KEY_OK == 0))
{
keyCount++;
if(keyCount>2)
{
keyState = 1;
keyCount=0;
if(KEY_UP == 0) KeyUp();
else if(KEY_DOWN == 0) KeyDown();
else if (KEY_LEFT == 0) KeyLeft();
else if (KEY_RIGHT == 0) KeyRight();
else if (KEY_OK == 0) KeyOk();
}
}else if (KEY_UP == 1 && KEY_DOWN == 1 && KEY_LEFT == 1 && KEY_RIGHT == 1 && KEY_OK == 1)
{
keyState = 0;
}
}
void KeyUp()
{
if(isKeyUp == 0)
isKeyUp=1;
LED=!LED;
}
Многоуровневый дизайн меню:
//Структура параметров страницы меню
struct MenuProperty_t
{
u8 MenuLen;//Общее количество пунктов меню на текущей странице меню
u8 scrollBarLen;//Длина полосы прокрутки, поскольку все они используют 16 символов размера, на странице меню может быть до четырех пунктов меню, а полоса прокрутки для пяти пунктов меню равна 1.
};
//Структура пункта прейскуранта
struct Menu_t{
struct MenuProperty_t *MenuProperty;//Параметры страницы меню, на которой расположен текущий пункт меню
u8 displayString[15];//Символ текущего пункта меню
void (*func1) (void);//Функция текущего пункта меню
void (*func2) (void);//Функция текущего пункта меню
struct Menu_t *fatherMenu;//Родительский пункт меню текущего пункта меню
struct Menu_t *childrenMenu;//Дочерний пункт меню текущего пункта меню
};
Определение страницы меню
Главное меню интерфейса считается меню первого уровня. Основной интерфейс обычно можно использовать для рисования некоторых забавных дизайнов пользовательского интерфейса. То, что я делаю в этом проекте, – это дизайн часов.На этой странице меню есть только один пункт меню, а полоса прокрутки равна 0.Поскольку инициализация не может быть заполнена сначала неинициализированными данными, инициализации его элемента подменю сначала присваивается значение NULL.
//Основной пользовательский интерфейс
struct MenuProperty_t MainUIProperty={1,0};
struct Menu_t MainUI=
{&MainUIProperty,"MainUI " ,NULL,NULL,NULL};
Главное меню – это дополнительное меню, которое используется для классификации элементов данных, которые я хочу отобразить. Родительским меню является MainUI, а элемент подменю сначала инициализируется значением NULL. Обратите внимание, что строка должна быть написана как можно короче из 15 символов, а пробелы также следует использовать для заполнения свободного места, чтобы последующие данные было легко обновить. На этой странице меню есть четыре пункта меню, а полоса прокрутки равна 0.
//Главное меню
struct MenuProperty_t menuMainProperty={4,0};
struct Menu_t menuMain[4]=
{
{&menuMainProperty,"last menu ", NULL,NULL, &MainUI,NULL},
{&menuMainProperty,"Animal ", NULL,NULL, &MainUI,NULL},
{&menuMainProperty,"Pid ", NULL,NULL, &MainUI,NULL},
{&menuMainProperty,"Time set ", NULL,TimeSetInit, &MainUI,NULL}
};
Подменю animal считается меню уровня 3. Это данные пункта меню animal, который вы действительно хотите отобразить. Родительским меню является menuMain, и для инициализации элемента подменю сначала устанавливается значение NULL. На этой странице меню есть шесть пунктов меню, а длина полосы прокрутки равна 2, поскольку с одной стороны отображается до 4 пунктов, прокручивайте вниз по одному за раз.
Примечание: Примечание: Если вы определяете один элемент, вы должны добавить & к адресу. Если вы определяете массив, вы можете использовать имя массива для получения первого адреса массива.
//anima Подменю l
struct MenuProperty_t setMenu1Property={6,2};
struct Menu_t setMenu1[6]=
{
{&setMenu1Property,"last menu ",NULL,NULL,menuMain,NULL},
{&setMenu1Property,"bull ",NULL,NULL,menuMain,NULL},
{&setMenu1Property,"bird ",NULL,NULL,menuMain,NULL},
{&setMenu1Property,"dog ",NULL,NULL,menuMain,NULL},
{&setMenu1Property,"bow ",NULL,NULL,menuMain,NULL},
{&setMenu1Property,"fish ",NULL,NULL,menuMain,NULL}
};
Функция обновления OLED-страницы
Обновите информацию о странице, если она есть на главной странице, просто очистите ее и нарисуйте картинку. если ее нет на главной странице, используйте наложение для достижения эффекта обновления.
void DisplayRefreash(struct Menu_t *nowMenu,u8 selectItem,u8 scrollBar)
{
int i = 0;
static u8 lastSelectItem=0;//Запишите последний индекс
if(nowMenu==&MainUI)//Когда я возвращаюсь в главное меню, поскольку экран занят не полностью, все экраны очищаются, а затем окрашиваются.
{
OLED_Clear();
MainUiSet();
}else
{
OLED_ShowChar(0,lastSelectItem*16, ' ',16,1);//Снимите последнюю стрелку указателя
OLED_ShowChar(0,selectItem*16, '>',16,1);//На этот раз нарисуйте указательную стрелку
for(i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->scrollBarLen);i++)
{
OLED_ShowString(8,i*16,nowMenu[i+scrollBar].displayString,16,1);
}
}
OLED_Refresh();
lastSelectItem = selectItem;
}
Функция обновления OLED-данных
Когда необходимо обновить данные каждой страницы, вам нужно только перезаписать последние данные в строке, поэтому вам придется каждый раз вводить полную строку символов, или вы можете достичь той же цели, имея одинаковую длину символов в каждой строке.
void DisplayRefreashData(struct Menu_t *nowMenu,u8 selectItem,u8 scrollBar)
{
int i = 0;
for(i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->scrollBarLen);i++)
{
OLED_ShowString(8,i*16,nowMenu[i+scrollBar].displayString,16,1);
}
OLED_Refresh();
}
Перед обновлением данных изменения данных могут быть использованы функцией Sprintf для переопределения отображаемой строки каждого пункта меню
void GuiDataDisplayRefresh()
{
if(menuPoint == setMenu1)
{
sprintf((char*)setMenu1[1].displayString,"bull %3d ",count1);
sprintf((char*)setMenu1[2].displayString,"bird %3d ",count2);
sprintf((char*)setMenu1[3].displayString,"dog %3d ",count3);
sprintf((char*)setMenu1[4].displayString,"bow %3d ",count4);
sprintf((char*)setMenu1[5].displayString,"fish %3d ",count5);
DisplayRefreashData(menuPoint,selectItem,scrollBar);
}
else if(menuPoint==&MainUI)
{
MainUiSet();
OLED_Refresh();
}
}
Инициализация меню
В основном это используется для инициализации подменю некоторых пунктов меню, а также указателя на текущее меню.
Глобальные переменные
menuPoint Текущее меню указывает на адрес
selectItem Текущий индекс 0-3
scrollBar Местоположение текущей полосы прокрутки, верхняя часть равна 0
void GuiInit()
{
MainUI.childrenMenu = menuMain;
menuMain[1].childrenMenu = setMenu1;
menuMain[2].childrenMenu = setMenu2;
menuMain[3].childrenMenu = setMenu3;
menuPoint = &MainUI;
DisplayRefreash(menuPoint,selectItem,scrollBar);
}
Ключевая функция управления
Кнопки вверх и вниз в основном используются для переключения текущего индекса и полосы прокрутки
Левая и правая клавиши в основном используются для реализации функциональных функций
void GuiControl()
{
if(isKeyUp==1)//Нажмите кнопку вверх
{
isKeyUp=0;//Флаг снят
selectItem--;//Указатель текущего меню на странице текущего меню --
if(selectItem<0&&scrollBar!=0)//Если оно меньше 0, но полоса прокрутки не равна 0, полоса прокрутки вычитается
{
selectItem = 0;
scrollBar--;
}else if(selectItem<0&&scrollBar==0)//Если оно меньше 0, а полоса прокрутки также равна 0, индекс будет перемещен в конец, и полоса прокрутки будет самой большой.
{
selectItem = menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen;
scrollBar = menuPoint->MenuProperty->scrollBarLen;
}
DisplayRefreash(menuPoint,selectItem,scrollBar);//Обновите дисплей
}else if(isKeyDown==1)//Аналогично кнопке "Вверх"
{
isKeyDown=0;
selectItem++;
//Если индекс превышает максимальное значение, но полоса прокрутки не имеет максимального значения, оставьте индекс на максимальном значении, а полосу прокрутки ++
if(selectItem>(menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen)&&scrollBar!=menuPoint->MenuProperty->scrollBarLen)
{
selectItem = menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen;
scrollBar++;
}
//Если индекс больше максимального значения, полоса прокрутки имеет максимальное значение и перемещается в первую позицию
else if(selectItem>(menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen)&&scrollBar==menuPoint->MenuProperty->scrollBarLen)
{
selectItem=0;
scrollBar =0;
}
DisplayRefreash(menuPoint,selectItem,scrollBar);
}else if(isKeyLeft==1)
{
//Если функция 1 текущего меню не пуста, выполните соответствующую функцию
if(menuPoint[selectItem+scrollBar].func1!=NULL)
{
menuPoint[selectItem+scrollBar].func1();
}
isKeyLeft=0;
DisplayRefreash(menuPoint,selectItem,scrollBar);
}else if(isKeyRight==1)
{
if(selectItem==0 && scrollBar==0 && menuPoint[selectItem].fatherMenu!=NULL)//Если индекс равен нулю, а родительское меню не пустое, укажите на родительский указатель
{
menuPoint = menuPoint[selectItem].fatherMenu;
}
else if(menuPoint[selectItem+scrollBar].childrenMenu!=NULL)//Если страница индексного подменю не пуста, укажите на подменю
{
if(menuPoint[selectItem+scrollBar].func2!=NULL)//Если функция 2 текущего меню не пуста, выполните соответствующую функцию
{
menuPoint[selectItem+scrollBar].func2();
}
menuPoint = menuPoint[selectItem+scrollBar].childrenMenu;
selectItem = 0;
}
else if(menuPoint[selectItem+scrollBar].func2!=NULL)//Если функция 2 текущего меню не пуста, выполните соответствующую функцию
{
menuPoint[selectItem+scrollBar].func2();
}
isKeyRight=0;
DisplayRefreash(menuPoint,selectItem,scrollBar);
}else if(isKeyOk==1)
{
isKeyOk=0;
DisplayRefreash(menuPoint,selectItem,scrollBar);
}
GuiDataDisplayRefresh();
}