配合uC/OS-II使用的12864菜单

 菜单做出来的效果看就像是这样子

首先,介绍一下菜单的结构

其大概就是这个样子的,一个粉红色大方框就是一个基本单元,基本单元的内容主要包括两个方面:父-表示这一块的内容从属,子-具体选项(父系社会)。然后,为了方便可以添加一些附加信息-就像最起码要记得子女的个数。具体使用C语言定义一个结构体如下:

struct _MENU_FATHER;//菜单头部的类型声明,叫做不完全声明

typedef struct _MENU_SON
{
	INT8U name[16]; // 菜单项目名称,儿子的小名,只有开头能用大写字母,其他一般小写
	INT8U style;    //Style='m'表示指向下一个是菜单,Style='p'代表优先级表示指向下一个是函数
	union           //m表示儿子的后代还是儿子(会是孙子的父亲),表示儿子的后代是女儿(没有后续了)
    {
		struct _MENU_FATHER *menuSon;   //子菜单的子菜单,儿子的儿子
		int                 prio;       //该菜单任务的优先级,儿子的女儿
    }Category;

} MENU_SON;

typedef struct _MENU_FATHER
{
	INT8U     title[16];      //言明上承何处,父亲的名字,当然要尊称大名,就全部用大写字母代替
	MENU_SON *menuson;        //子菜单的地址,大儿子的住处
	INT8U         line;       //正好表示最后一个子菜单在数组中的序号,儿子的个数-1

}MENU_FATHER;

任意一个基础单元只会有一个父类,却可以有多个子类,这里规定子类只有两种情况,接下来就开始进行具体的工作

/*********************************全局变量的定义*******************************************/
u8 CURSOR=0,PAGE=0;//用于记录当前LCD显示的页数与行数
                   //CURSOR(0-2)代表当前LCD中的光标的位置,0表示光标在第一行,注意父类的名字永远搞怪在前头
	           //PAGE表示当前的页数,Index(就是子菜单的地址)=PAGE*3+CURSOR
/************************************结构体变量定义******************************************/
MENU_FATHER *TitleCur;//当前的菜单控制块指针,表示当前研究的家的父类
MENU_FATHER *MenuTbl[3];//假设有3层菜单,抄袭uC/OS操作系统的OSPrioTbl
                        //用于记录历史,这样容易原路返回,你进了子菜单处理完了数据后,肯定要回到主菜单的啊
INT8U CurrentLevel=0;//用于记录当前的菜单层次,默认是主菜单=0

//第一层菜单,只有最后一公里是具体到函数的,不用再次一一列出,因为他们无法充当一家之主
MENU_FATHER MainMenuTitle;//主菜单,是这个菜单的头部
MENU_FATHER DisplayTitle,ConfigTitle;//第一层菜单

做完这些基本工作之后,就可以开始着手准备建立一个自己的多层菜单界面了

就拿视频中的这个简单的例子来说

首先,写好各个界面。一共分为两层。

第一层主界面定义如下

MENU_SON MainMenuBody[]=//为父已经在前面已经定义了,这里是定义and赋值孩儿们
{
	{"Data Display" ,'m' ,.Category.menuSon=&DisplayTitle },//
	{"Config Info"  ,'m' ,.Category.menuSon=&ConfigTitle  },
	{"LCD Adjust"   ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },
	{"LCD Adjust1"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },//后面这些主要是用来演示用的
	{"LCD Adjust2"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },
	{"LCD Adjust3"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },
	{"LCD Adjust4"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },
	{"LCD Adjust5"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },
	{"LCD Adjust6"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO },
	{"LCD Adjust7"  ,'p' ,.Category.prio   =LCD_ADJUST_PRIO }
};	

 然后,组建一个家庭

/*******************************************************************************
* FunctionName   : MainMenuTitleInit()
* Description    : 主菜单初始化
*****************************************************************************/
static void MainMenuTitleInit(void)//主菜单
{
	u8 i=0;
	char *p="   -= MENU =-";
	
	while((*p)!='\0'){
		MainMenuTitle.title[i++]=*(p++);
	}
	MainMenuTitle.title[i]='\0';
    /*******MainMenuTitle菜单赋值****/
	MainMenuTitle.menuson=MainMenuBody;
	MainMenuTitle.line=sizeof(MainMenuBody)/sizeof(MainMenuBody[0])-1;//正好表示最后一个子菜单在数组中的序号
}

依次对另外两个style=‘m’的进行组建家庭

/*************************定义与赋值************************/
MENU_SON DisplayBody[]=
{
	{"RS485" ,'p' ,.Category.prio  =  RS_DISP_PRIO },
	{"NRF"   ,'p' ,.Category.prio  = NRF_DISP_PRIO },
	{"SI4463",'p' ,.Category.prio  =  SI_DISP_PRIO }
};
MENU_SON ConfigBody[]=
{
	{"RS485" ,'p' ,.Category.prio  =  RS_CONF_PRIO },
	{"NRF"   ,'p' ,.Category.prio  = NRF_CONF_PRIO },
	{"SI4463",'p' ,.Category.prio  =  SI_CONF_PRIO }
};

/*************************组建家庭************************/
static void DisplayTitleInit(void)//
{
	u8 i=0;
	char *p="DISPLAY";
	while((*p)!='\0'){
		DisplayTitle.title[i++]=*(p++);
	}
	DisplayTitle.title[i]='\0';
	DisplayTitle.menuson=DisplayBody;
	DisplayTitle.line=sizeof(DisplayBody)/sizeof(DisplayBody[0])-1;
}
static void ConfigTitleInit(void)//
{
	u8 i=0;
	char *p="CONFIG";
	while((*p)!='\0'){
		ConfigTitle.title[i++]=*(p++);
	}
	ConfigTitle.title[i]='\0';
	ConfigTitle.menuson=ConfigBody;
	ConfigTitle.line=sizeof(ConfigBody)/sizeof(ConfigBody[0])-1;
}

看到这里,也许你会想,为什么不把“组建家庭”写成一个函数呢,这样不是方便多了吗?

其实我也这么想过,最后得出的结论是answer

/**@Q:这里为什么不写成函数的形式而是采用一个一个的赋值呢,
因为指针和数组名是不同的结构,他们sizeof的结果完全不同****/

好的,接下来就是整合一下 

/*******************************************************************************
* FunctionName   : MenuInit()
* Description    : 菜单初始化函数
*****************************************************************************/
void MenuInit(void)
{
	MainMenuTitleInit();
	ConfigTitleInit();
	DisplayTitleInit();
	
	MenuTbl[0]=&MainMenuTitle;//MenuTbl是用来记录多层次菜单路径的,MenuTbl[0]里就是主菜单,无法动摇
	TitleCur=&MainMenuTitle;//开始当然是从主菜单开始显示
	CurrentLevel=0;//默认主菜单的level=0	
}

接着就是编写按键的处理程序

/*****************************************************************************
* FunctionName   : Key_Up()
* Description    : 上键的处理函数
*****************************************************************************/
void Key_Up(void) // 向上
{
	if(CURSOR!=0)//能不能上天?
		CURSOR--;
	else{//CURSOR=0,已经到达了该页的顶部
		if(PAGE!=0){//如果上到了第一项,但是还没有到达第一页,这时候就要从上一页底部重新开始
			PAGE--;//上一页
			CURSOR=2;//底部
		}
		else{//PAGE=1,到达首项
			PAGE=TitleCur->line/3;//切换到底部
			CURSOR=TitleCur->line%3;//最后一项
		}
	}
}
/*******************************************************************************
* FunctionName   : Key_Down()
* Description    : 下键的处理函数
*****************************************************************************/
void Key_Down(void) // 向下
{
	CURSOR++;
	if(PAGE>=TitleCur->line/3){//如果是最后一页	
		if(CURSOR>TitleCur->line%3){//如果最后一页还超过了最后一项,就重头开始
			PAGE=0;
			CURSOR=0;	
		}
	}
	else
	{
		if(CURSOR>2){//如果光标超过了屏幕就说明要换页了
			PAGE++;
			CURSOR=0;
		}
	}
}
/*******************************************************************************
* FunctionName   : Key_Enter()
* Description    : 确认键的处理函数
*****************************************************************************/
void Key_Enter(void)
{
//MENU_FATHER *TitleCur;//当前的菜单控制块指针
//MENU_FATHER *MenuTbl[5];//假设有5层菜单,我感觉已经足够了
//INT8U CurrentLevel=0;//用于记录当前的菜单层次,默认是主菜单=0

	if(TitleCur->menuson[PAGE*3+CURSOR].style=='m'){
		CurrentLevel++;//指示当前的菜单增加了一个层次
		if(CurrentLevel>7)CurrentLevel=7;//前面假设最多有8层菜单,我感觉已经足够了
		MenuTbl[CurrentLevel]=TitleCur->menuson[PAGE*3+CURSOR].Category.menuSon;//记录当前菜单,方便退出
		TitleCur=MenuTbl[CurrentLevel];//加载新的菜单
		PAGE=0;CURSOR=0;//进入新的一页
	}
	else if(TitleCur->menuson[PAGE*3+CURSOR].style=='p'){
		Switch2Son(TitleCur->menuson[PAGE*3+CURSOR].Category.prio);//执行相关函数
	}
}
/*******************************************************************************
* FunctionName   : Key_Esc()
* Description    : 退出键的处理函数
*****************************************************************************/
void Key_Esc(void)
{
	if(CurrentLevel!=0){
		MenuTbl[CurrentLevel]=(MENU_FATHER *)0;//先把原来被占用的清除
		CurrentLevel--;
		TitleCur=MenuTbl[CurrentLevel];
		PAGE=0;CURSOR=0;//不具有记忆功能,一起从头开始
	}
}
/*******************************************************************************
* FunctionName   : Switch2Son()
* Description    : 挂起当前任务,切换到子菜单指向的任务
*****************************************************************************/
void Switch2Son(int TaskResume)
{ 
	OSTaskResume(TaskResume);//挂起当前任务
	OSTaskSuspend(OS_PRIO_SELF);//切换到新任务
}

猜你喜欢

转载自blog.csdn.net/qq_35629563/article/details/82884226
今日推荐