前言
本次是基于0.96寸OLED的多级菜单设计,找了一下网上的菜单写法,发现很多是那种利用一个数组记录界面的,我觉得这样不是很直观。我希望写成那种可以不断延伸拓展的界面,界面数量,菜单级数可以任意指定的类型。
就像XMIND思维导图工具一样,因此我写了一个简单的多级菜单管理界面。
下面直接上代码
一、结构体定义
首先创造一些结构体存放界面所需的变量
#ifndef __GUI_096_H
#define __GUI_096_H
#include "main.h"
#include "oled.h"
#define Menu_level 5 //菜单级数
#define Menu_Length 20 //单个菜单长度
typedef struct oled_fun
{
uint8_t string[20]; //菜单名
void (*func) (void); //菜单函数
}GUI_Menu;
typedef enum GUI_Button
{
Gui_Up,
Gui_Down,
Gui_Right,
Gui_Left,
Gui_No,
}GUI_Button;
typedef struct Oled_Gui
{
uint8_t Pause; //Gui暂停标志位
uint8_t Menu_Len; //Gui菜单长度
GUI_Button Button; //Gui按键
uint8_t Begin_Index; //Gui菜单起始
uint8_t Now_Inedx; //Gui菜单选中
GUI_Menu Menu[Menu_Length]; //Gui菜单
void (*Last_Func[Menu_level]) (void); //Gui上级函数记录
void (*Now_Func) (void); //Gui选中函数
}Gui;
extern Gui My_Gui;
void gui_init();
void Menu_Refresh(void);
void Menu_Clear();
void Menu_Update(uint8_t len,GUI_Menu* the_next);
#endif///
二、相关函数
1.页面刷新
void Menu_Refresh()
{
OLED_Clear();
for(int i=My_Gui.Begin_Index;i<My_Gui.Menu_Len;i++)
{
if(i==(My_Gui.Now_Inedx))
OLED_ShowString(0,16*(i-My_Gui.Begin_Index),My_Gui.Menu[i].string,16,0);
else
OLED_ShowString(0,16*(i-My_Gui.Begin_Index),My_Gui.Menu[i].string,16,1);
if(i-My_Gui.Begin_Index==3)
break;
}
OLED_Refresh();
}
2.页面清除
void Menu_Clear()
{
memset( (uint32_t *)(&(My_Gui.Menu[0])), 0, My_Gui.Menu_Len*sizeof(My_Gui.Menu[0]));
My_Gui.Menu_Len=0;
My_Gui.Begin_Index=0;
My_Gui.Now_Inedx=0;
OLED_Clear();
OLED_Refresh();
}
3.菜单更新
void Menu_Update(uint8_t len,GUI_Menu* the_next)
{
memset( (uint32_t *)(&(My_Gui.Menu[0])), 0, My_Gui.Menu_Len*sizeof(My_Gui.Menu[0]));
My_Gui.Begin_Index=0;
My_Gui.Now_Inedx=0;
My_Gui.Menu_Len=len;
for(int i=0;i<len;i++)
{
My_Gui.Menu[i]=the_next[i];
}
Menu_Refresh();
}
4.GUI按键检测
void gui_init_tick()
{
//GUI没有暂停的情况下处理按键
if(My_Gui.Pause==0)
{
if(My_Gui.Button==Gui_Up)
{
My_Gui.Button=Gui_No;
if(My_Gui.Now_Inedx>0)
{
//向上移位选项然后刷新屏幕
My_Gui.Now_Inedx--;
if(My_Gui.Now_Inedx<My_Gui.Begin_Index)
My_Gui.Begin_Index=My_Gui.Now_Inedx;
Menu_Refresh();
}
}
else if(My_Gui.Button==Gui_Down)
{
My_Gui.Button=Gui_No;
//向下移位选项然后刷新屏幕
if(My_Gui.Now_Inedx<(My_Gui.Menu_Len-1))
{
My_Gui.Now_Inedx++;
if(My_Gui.Now_Inedx-My_Gui.Begin_Index>3)
My_Gui.Begin_Index=My_Gui.Now_Inedx-3;
Menu_Refresh();
}
}
else if(My_Gui.Button==Gui_Right)
{
My_Gui.Button=Gui_No;
//选择函数
if(My_Gui.Menu[My_Gui.Now_Inedx].func!=NULL)
{
//现函数进入上级存储
for(int i=0;i<Menu_level;i++)
{
if(My_Gui.Last_Func[i]==NULL)
{
My_Gui.Last_Func[i]=My_Gui.Now_Func;
break;
}
}
//修改现函数
My_Gui.Now_Func=My_Gui.Menu[My_Gui.Now_Inedx].func;
My_Gui.Now_Inedx=0;
My_Gui.Begin_Index=0;
My_Gui.Pause=1; //暂停GUI
}
}
else if(My_Gui.Button==Gui_Left)
{
My_Gui.Button=Gui_No;
//取出存储的函数
for(int i=Menu_level-1;i>=0;i--)
{
if(My_Gui.Last_Func[i]!=NULL)
{
My_Gui.Now_Func=My_Gui.Last_Func[i];
My_Gui.Last_Func[i]=NULL;
break;
}
}
My_Gui.Now_Inedx=0;
My_Gui.Begin_Index=0;
My_Gui.Pause=1;
}
}
else
My_Gui.Button=Gui_No;
}
5.初始化
GUI_Menu Menu1[]={{"A",.func=func_A},
{"AA",.func=func_B},
{"AAA",.func=func_C},
{"AAAA",.func=func_D},
{"AAAAA",.func=func_D},
{"AAAAAA",.func=func_D},
{"AAAAAAA",.func=func_D},
{"AAAAAAAA",.func=func_D},
};
void gui_init()
{
for(int i=0;i<Menu_level;i++)
My_Gui.Last_Func[i]=NULL;
My_Gui.Now_Func=gui_init;
My_Gui.Menu_Len=8;
My_Gui.Pause=0;
My_Gui.Button=Gui_No;
My_Gui.Begin_Index=0;
My_Gui.Now_Inedx=0;
for(int j=0;j<My_Gui.Menu_Len;j++)
My_Gui.Menu[j]=Menu1[j];
Menu_Refresh();
}
二、函数注意
本次是基于0.96寸OLED的多级菜单设计,可以看到我定义的结构体内部函数指针是返回空类型且没有输入变量。并且菜单调用的函数基本上可以分为三类,一类是仅仅运行一次,比如修改了什么参数的函数,一类是菜单更新,因为下一级可能不是函数而是一个菜单更新的函数,最后一类是那种while循环的类型,这种函数需要一个while()检测让函数停止,不然函数会一直运行跳转不出来,这个函数需要我们自己加上停止条件。
1.仅运行一次的函数
这类函数往往很快运行完之后直接跳出来。比如一个LED的翻转函数,前面需要先清除一下当前菜单。
void func_A(void)
{
Menu_Clear();
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
2.菜单切换的函数
这类函数往往是更新界面函数,向下一级函数跳转,可以直接参考我下面的写法进行调用。
GUI_Menu Menu2[]={{"B",.func=func_A},
{"中景园",.func=func_B},
};
void func_C(void)
{
Menu_Update(2,Menu2);
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
3.重复循环的函数
这类函数需要用户自己添加结束条件
void func_B(void)
{
Menu_Clear();
while(flag==1)
{
//用户自己添加内容
}
}
4.主函数循环和调用方法
开一个定时器内部进行不断调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
if(htim->Instance==TIM4)
{
//HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
//HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
}
else if(htim->Instance==TIM3)
{
gui_init_tick();
}
}
主函数的写法就很简单了
#include "sys.h"
int main(void)
{
hard_steup();
while(1)
{
if(My_Gui.Pause==1)
{
My_Gui.Now_Func();
My_Gui.Pause=0;
}
}
}