前言:本文的OLED多级菜单UI为一个综合性的STM32小项目,使用多传感器与OLED显示屏实现智能终端的效果。项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换,搭配DHT11,RTC,LED,KEY等器件实现高度智能化一体化操作。后期自己打板设计结构,可以衍生为智能手表等小玩意。目前,项目属于裸机状态(CPU占用率100%),后期可能会加上RTOS系统。(本项目源码在本文末尾进行开源!)
硬件实物图:
一、多级菜单
随着工业化和自动化的发展,如今基本上所有项目都离不开显示终端。而多级菜单更是终端显示项目中必不可少的组成因素,其实TFT-LCD屏幕上可以借鉴移植很多优秀的开源多级菜单(GUI,比如:LVGL),而0.96寸的OLED屏幕上通常需要自己去适配和编程多级菜单。
网上的普遍采用的多级菜单的方案是基于索引或者结构树,其中,索引法居多。索引法的优点:可阅读性好,拓展性也不错,查找的性能差不多是最优,就是有点占用内存空间。
说明:本项目的多级菜单也是采用了索引法进行实现。
二、索引法多级菜单实现
网上关于索引法实现多级菜单功能有很多基础教程,笔者就按照本项目中的具体实现代码过程给大家讲解一下索引法实现多级菜单。特别说明:本项目直接使用了正点原子的精英板作为核心板,所以读者朋友复现代码还是很简单的。
首先,基于索引法实现多级菜单的首要条件是先确定项目中将使用到几个功能按键(比如:向前,向后,确定,退出等等)本项目中,笔者使用到了3个按键:下一个(next),确定(enter),退出(back)。所以,接下首先定义一个结构体,结构体中一共有5个变量(3+2),分别为:当前索引序号(current),向下一个(next),确定(enter),退出(back),当前执行函数(void)。其中,标红的为需要设计的按键(笔者这里有3个),标绿的则为固定的索引号与该索引下需要执行的函数。
typedef struct
{
u8 current; //当前状态索引号
u8 next; //向下一个
u8 enter; //确定
u8 back; //退出
void (*current_operation)(void); //当前状态应该执行的操作
} Menu_table;
接下来就是定义一个数组去决定整个项目菜单的逻辑顺序(利用索引号)
Menu_table table[30]=
{
{0,0,1,0,(*home)}, //一级界面(主页面) 索引,向下一个,确定,退出
{1,2,5,0,(*Temperature)}, //二级界面 温湿度
{2,3,6,0,(*Palygame)}, //二级界面 游戏
{3,4,7,0,(*Setting)}, //二级界面 设置
{4,1,8,0,(*Info)}, //二级界面 信息
{5,5,5,1,(*TestTemperature)}, //三级界面:DHT11测量温湿度
{6,6,6,2,(*ControlGame)}, //三级界面:谷歌小恐龙Dinogame
{7,7,9,3,(*Set)}, //三级界面:设置普通外设状态 LED
{8,8,8,4,(*Information)}, //三级界面:作者和相关项目信息
{9,9,7,3,(*LED)}, //LED控制
};
这里解释一下这个数组中各元素的意义,由于我们在前面先定义了Menu_table结构体,结构体成员变量分别与数组中元素对应。比如:{0,0,1,0,(*home)},代表了索引号为0,按向下键(next)转入索引号为0,按确定键(enter)转入索引号为1,按退出键(back)转入索引号为0,索引号为0时执行home函数。
在举一个例子帮助大家理解一下,比如,我们当前程序处在索引号为2(游戏界面),就会执行Playgame函数。此时,如果按下next按键,程序当前索引号就会变为3,并且执行索引号为3时候的Setting函数。如果按下enter按键,程序当前索引号就会变为6,并且执行索引号为6时候的ControlGame函数。如果按下back按键,程序当前索引号就会变为0,并且执行索引号为0时候的home函数。
再接下就是按键处理函数:
uint8_t func_index = 0; //主程序此时所在程序的索引值
void Menu_key_set(void)
{
if((KEY_Scan(1) == 1) && (func_index != 6)) //屏蔽掉索引6下的情况,适配游戏
{
func_index=table[func_index].next; //按键next按下后的索引号
OLED_Clear();
}
if((KEY_Scan(1) == 2) && (func_index != 6))
{
func_index=table[func_index].enter; //按键enter按下后的索引号
OLED_Clear();
}
if(KEY_Scan(1) == 3)
{
func_index=table[func_index].back; //按键back按下后的索引号
OLED_Clear();
}
current_operation_index=table[func_index].current_operation; //执行当前索引号所对应的功能函数
(*current_operation_index)();//执行当前操作函数
}
//按键函数
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;
if(mode)key_up=1;
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
HAL_Delay(100); //消抖
key_up=0;
if(KEY0==0)return 1;
else if(KEY1==0)return 2;
else if(WK_UP==1)return 3;
}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;
}
说明2点:
(1)由于是目前本项目是裸机状态下运行的,所以CPU占用率默认是100%的,所以这里使用按键支持连按时,对于菜单的切换更好些。
(2)可能部分索引号下的执行函数,需要使用到已经定义的3个按键(比如,本项目中的DInogame中)。所以,可以在需要差别化的索引号下去屏蔽原先的按键功能。如下:
if((KEY_Scan(1) == 1) && (func_index != 6)) //屏蔽掉索引6下的情况,适配游戏
{
(3)笔者这里是使用全屏刷新去切换功能界面,同时,没有启用高级算法去加速显示,所以可能在切换界面的时候效果一般。读者朋友可以试试根据自己的UI情况使用局部刷新,这样可能项目会更加丝滑一点。
本项目中的菜单索引图:
嵌入式物联网的学习之路非常漫长,不少人因为学习路线不对或者学习内容不够专业而错失高薪offer。不过别担心,我为大家整理了一份150多G的学习资源,基本上涵盖了嵌入式物联网学习的所有内容。点击下方链接,0元领取学习资源,让你的学习之路更加顺畅!记得点赞、关注、收藏、转发哦!
点击这里找小助理0元领取:扫码进群领资料