思路
菜单在调试小车的时候是非常重要的,我认为菜单的原理应该是,根据对应按键改变一个变量的值,这个变量的值发生改变时,整个菜单或者部分区域发生变化,变量不止一个。菜单的编写即是通过按键改变多个变量获取对应响应的过程。
这是我的菜单界面,并不是主界面哦
我们要实现的就是两个按键对应上下能够使得箭头上下并选中对应的子菜单(不一定有,想建立就建立),之后一个按键确认进去,一个返回按键,还有两个按键控制变量的加减。总共6个按键。
注意按键的操作要先实现,根据开始对引脚的初始化决定是==1还是==0,我设置的是引脚上拉电阻,所以是==0。大家可以拿板子试看看是==1还是==0.
我创立了俩变量page和arrow,以下的操作都是基于这两个变量进行操作的。
下面的代码是用stc32单片机用18TFT实现的。其他单片机一样的道理。
控制箭头上下
控制函数要依托与自己单片机显示屏的库函数。
我的显示屏库函数是这样的,上下只有八行,所以呢下面arrow是0-7,8个数嘛。
上下都是相对而言的,方向反了可以把加换成减,或者把之前想当减的按键当成加的按键(doge)。
#define key_enter P74 //K6
#define key_return P70 //K5
#define key_down P73 //K2
#define key_up P77 //K1
#define key_add P71//K3
#define key_sub P72//K4
//宏定义
int16 page=1,arrow=0;//控制菜单的俩变量
int16 page_last;//
void key_action()
{
if((key_up)==0)
{
delay_ms(300);
lcd_clear(WHITE);
arrow=arrow-1;
}
if((key_down)==0)
{
delay_ms(300);
lcd_clear(WHITE);
arrow=arrow+1;
}
if((arrow<0)) arrow=7;
else if((arrow>7))arrow=0;
}
菜单主函数
由于我的菜单有点复杂,大部分函数是我自己写的,你们是没有的,但是可以用这个描述以下大致框架,我会提供Menu_show_1函数,还有如何写子菜单的例子函数,其他要是你们依照自己的需求写的。所以下面的代码复制的时候要删去你没有的函数,fg.menu_open是我控制菜单打开的结构体变量,你们可以自己定义一个变量用于控制菜单的闭合。
//菜单主程序
void menu_main()
{
if(fg.menu_open==1)
{
switch(page)
{
case 1 :Menu_show_1(); break;//第一页
case 21:Inductance_show(); break;//第一页进入第一个选项
case 22:PID_Velocity_Show(); break;
case 23:Encode_Show(); break;
case 24:Gyro_Show(); break;
case 25:State_Show(); break;
case 26:Threshold_Show(); break;
case 27:yh_and_bz(); break;//tools
case 28:Go(); break;
// case 31:(); break;
case 32:Sudu_Huan_PD_deburg();break;
// default: break;
// case 241:Euler_Angle_Show(); break;//算力不足,舍去
case 214:AD_All_Show(); break;//page=262 page范围不够
case 251:State_Other_Show(); break;
case 261:AD_Threshold_Show(); break;
case 2141:Inductor_Flag_show(); break;
case 2142:AD_E_Show(); break;//All_Inductor_Show
case 2143:Nomolization_Show(); break;
case 2144:V_Nomolization_Show(); break;
}
}
if((key_return)==0){
fg.menu_open=1;
fg.chujie=0;
fg.xunji=1;
}
}
大家可以看上面的page变量它用于控制页面显示的是什么,注意page=21不是代表21页,2代表page1进入下一级子菜单,1代表从选中的第一个行进入的子菜单。不是很清晰的同学可以看看我下面的menu_show1函数。
确认进入子菜单
下面的代码有用到之前的控制箭头上下函数,把arrow放在lcd_showstr函数里面,可以控制箭头所在行数的变化,之后key_enter在下面根据arrow现在的值改变。
void Menu_show_1()//1
{
page_last=page;
page=1;
if(page!=page_last)
{
arrow=0;
}
key_action();
lcd_showstr(0,arrow,"->");lcd_showstr(20,0,"Indc_tracker");//page 21 enter 之后
lcd_showstr(20,1,"Set_Velocity"); //page 22
lcd_showstr(20,2,"Encode_suduhaun");//page 23
lcd_showstr(20,3,"Gyro_Show"); //page 24
lcd_showstr(20,4,"Elemens_open");//page 25
lcd_showstr(20,5,"Threshold_Show");//page 26
lcd_showstr(20,6,"Tools");//page 27
lcd_showstr(20,7,"extra_Can_shu");//page 27
if((key_enter)==0)
{
delay_ms(300);
lcd_clear(WHITE);
switch((arrow)+1)//进入第二页,前面一页的箭头选中选项
{
case 1:page=21;arrow=0;break;
case 2:page=22;arrow=0;break;
case 3:page=23;arrow=0;break;
case 4:page=24;arrow=0;break;
case 5:page=25;arrow=0;break;
case 6:page=26;arrow=0;break;
case 7:page=27;arrow=0;break;
case 8:page=28;arrow=0;break;
}
}
}
上面的switch函数我用的arrow+1,你们可以直接arrow,但是呢这个page就要重新修改一下,要换一种理解方式。上面的代码实现了确认进入下一级子菜单的功能,因为根据此时的arrow的值
按下确认按键之后page发生相应变化,顺便把arrow清零,上面又有一个关于page的switch函数,放主循环里面就实现了基础的菜单上下确认功能。
我顺便提供一些结构体变量,只看菜单思路的话下面是不用看的.
typedef struct{
//出界
uint8 chujie;
//出界不停车
uint8 Chujie_Not_Stop;
uint8 qishiwei,qscount_1,qscount_2;
//菜单打开
uint8 menu_open;
//电感处理
uint8 guiyi_hua,already_scan,gui_zhong,sao_miao;
//坡道
uint8 podao_1,podao_2,podao_3;
//速度环
uint8 sudu_huan,sudu_huan_2;
//圆环
uint8 yhcountL_1,yhcountL_2,yhcountL_3,yhcountL_4,yhcountL_5;
uint8 yhcountR_1,yhcountR_2,yhcountR_3,yhcountR_4,yhcountR_5;
uint8 yh_openL,yh_openR;
//入库
uint8 stop_1,stop_2,stop_3,already_ru,stop_4,stop_5,stop_6,stop_7,stop_8;
uint8 ruku_open;
//出库
uint8 go_0,go_1,go_2,go_3,already_chu,L_chu,R_chu;uint8 chuku_open;
uint16 go_ph_1,stop_ph_1;//平滑转弯
//左右出入库控制
uint8 chuku_L,chuku_R;
//避障
uint8 bz_1,bz_2,bz_3,bz_4,bz_5,bz_6,bz_7,bz_8,bz_9;
uint8 bz_L,bz_R, bz_open;
uint8 already_bz;
//编码器积分测量测量,
uint8 integral_ecod_open;
//陀螺仪积分测量
uint8 integral_gyro_open;
uint8 Quaternion_Euler_angles_open;
//历时测量
float counter;uint8 time_open;
//激光测距
uint8 Jkg_open;
//开关循迹
uint8 xunji,Fllow_Trail;
//转固定角启动位
uint8 Turn_engle_Enable;
//走固定时间启动位
uint8 Go_Set_Time_Enable;
//走固定路程启动位
uint8 Go_Set_Distance_Enable;
//for 进去
uint8 For_To_Go;
//蓝牙拟狗叫器
uint16 Bt_counter;
//切换竖电感循迹
uint8 Horizontal_Change_To_Vertical_Inductor_Fllow_Trail;
//确定速度
uint8 Velocity_Have_Determined;
//防止疯转
uint8 Is_Crazy,Is_Crazy_Count;
//单独获取电感值
uint8 Obtained_Inductance_Separately;
//堵转*
uint8 Is_Stuck_In;
}my;
my fg;
上面很多变量大家是用不上的,但是可以看看fg这个结构体变量里面有什么,可以自己添加,删去。用的时候记得全给置0初始化。
多级子菜单
上面菜单主界面有行,每行都可以建立一个子菜单进去,根据page和页面的对应关系可以试出来的。
回顾之前的菜单主函数里面的内容(不是主界面)
void menu_main()
{
if(fg.menu_open==1)
{
switch(page)
{
case 1 :Menu_show_1(); break;//第一页
case 21:Inductance_show(); break;//第一页进入第一个选项
case 22:PID_Velocity_Show(); break;
case 23:Encode_Show(); break;
case 24:Gyro_Show(); break;
case 25:State_Show(); break;
case 26:Threshold_Show(); break;
case 27:yh_and_bz(); break;//tools
case 28:Go(); break;
// case 31:(); break;
case 32:Sudu_Huan_PD_deburg();break;
// default: break;
// case 241:Euler_Angle_Show(); break;//算力不足,舍去
case 214:AD_All_Show(); break;//page=262 page范围不够
case 251:State_Other_Show(); break;
case 261:AD_Threshold_Show(); break;
case 2141:Inductor_Flag_show(); break;
case 2142:AD_E_Show(); break;//All_Inductor_Show
case 2143:Nomolization_Show(); break;
case 2144:V_Nomolization_Show(); break;
}
}
if((key_return)==0){
fg.menu_open=1;
fg.chujie=0;
fg.xunji=1;
}
}
刚开始page=1,这时menu_show_1就是上电后的主界面,在主循环里面执行的就是这个函数。此时也是循环menu_show_1函数。回想上面这个函数里面当确认按键(key_enter)按下后发生什么。
if((key_enter)==0)
{
delay_ms(300);
lcd_clear(WHITE);
switch((arrow)+1)//进入第二页,前面一页的箭头选中选项
{
case 1:page=21;arrow=0;break;
case 2:page=22;arrow=0;break;
case 3:page=23;arrow=0;break;
case 4:page=24;arrow=0;break;
case 5:page=25;arrow=0;break;
case 6:page=26;arrow=0;break;
case 7:page=27;arrow=0;break;
case 8:page=28;arrow=0;break;
}
}
我们根据之前arrow,即存贮箭头所在位置的变量此时的值。当按下确认按键后,page,即存贮页面变化位置的数值发生变化(注意理解的时候page21不是21页这样理解,上面有强调过),然后此时又会进入menu_main这个函数,如若arrow+1=1此时page为1,不会进入Menu_show_1函数,而是进入Inductance_show();函数。这时只是一级子菜单,但是菜单基本功能已实现。
在给出 Inductance_show()函数作为参考时,我想说的是我为什么用switch((arrow)+1)而不是switch((arrow)),其实我都有用,switch((arrow)+1)下面的case break语句中用于换页,switch((arrow))下面的case break语句用于执行这个按键不换页而是执行特定功能(比如Go发车),这样写的时候比较清晰,你也可以直接选择switch((arrow))然后换页和对应的执行功能不换页。以下是Inductance_show()函数。里面加减的操作其实也实现了,而且可以分不同的值加减,思路也可以看看。
float I_Kp_debrug_val=0.1,I_Kd_debrug_val=0.1;
float I_count1=0,I_count2=0;
void Inductance_show()//21
{
key_action();
lcd_showstr(0,arrow,"->");
lcd_showstr(20,0,"Go");
lcd_showstr(20,1,"I_Kp");lcd_showfloat(50,1, I_Kp, 5, 2);
lcd_showstr(20,2,"I_Kd");lcd_showfloat(50,2, I_Kd, 5, 2);
lcd_showstr(20,3,"AD_All&Scan");//page 214
lcd_showstr(20,4,"AD_L");lcd_showfloat(70,4,AD_L,4,4);
lcd_showstr(20,5,"AD_M");lcd_showfloat(70,5,AD_M,4,4);
lcd_showstr(20,6,"AD_R");lcd_showfloat(70,6,AD_R,4,4);
lcd_showstr(20,7,"Cret&S");lcd_showfloat(70,7,Correct_Val,4,2);
if((key_enter)== 0)
{
delay_ms(300);
lcd_clear(WHITE);
switch(arrow)
{
case 0:lcd_showstr(20,0,"Goodluck");
delay_ms(200);
Go_Flag_Init();
break;
case 1:I_count1++;break;
case 2:I_count2++;break;
case 7:fg.sao_miao=0;
break;
}
switch((arrow)+1)//换页
{
case 4:page=214;arrow=0;break;
}
}
if((key_return)== 0)
{
delay_ms(200);
lcd_clear(WHITE);
page=1;
}
if((key_add)==0)
{
switch(arrow)
{
case 1:I_Kp=I_Kp+I_Kp_debrug_val;break;
case 2:I_Kd=I_Kd+I_Kd_debrug_val;break;
case 3:fg.sao_miao=1;break;
}
}
if((key_sub)== 0)
{
switch(arrow)
{
case 1:I_Kp=I_Kp-I_Kp_debrug_val;break;
case 2:I_Kd=I_Kd-I_Kd_debrug_val;break;
case 3:fg.sao_miao=0;break;
}
}
if(I_count1==0)I_Kp_debrug_val=100;
if(I_count1==1)I_Kp_debrug_val=10;
if(I_count1==2)I_Kp_debrug_val=1;
if(I_count1==3)I_Kp_debrug_val=0.1;
if(I_count1==4)I_Kp_debrug_val=0.01;
if(I_count1==5)I_Kp_debrug_val=0.001;
if(I_count1==6)I_count1=0;
if(I_count2==0)I_Kd_debrug_val=100;
if(I_count2==1)I_Kd_debrug_val=10;
if(I_count2==2)I_Kd_debrug_val=1;
if(I_count2==3)I_Kd_debrug_val=0.1;
if(I_count2==4)I_Kd_debrug_val=0.01;
if(I_count2==5)I_Kd_debrug_val=0.001;
if(I_count1==5)I_count2=0;
}
上面可以看到有一个switch(arrow)和switch(arrow+1),大家可以直接看switch(arrow+1)里面的内容,他其实就是改变page的值,大家可以找到arrow+1与page的关系,现在page=21,然后case=4那么page为214,是不是直接组合呢(ᕑᗢᓫ∗),换页的时候page的值确定方式大家明白了吗。之后page为214时在menu_main函数里面创建case 214 对应的函数。大家写代码的时候可以把这个函数是page多少标出来,这样不容易乱。
其实加减一些想要的值上面已经实现了,其实也是在这个函数里面写一个if函数而已,很号写的。顺便给出page=214对应的AD_All_Show();函数,它里面还有子菜单。
返回操作
void AD_All_Show()//page 214
{
key_action();
lcd_showstr(0,arrow,"->");
lcd_showstr(20,0,"Inductor_Flag_show");
lcd_showstr(20,1,"AD_Show_All");
lcd_showstr(20,2,"Nomolization_Show");
lcd_showstr(20,3,"V_Nomolization_Show");
if((key_enter)== 0)
{
delay_ms(300);
lcd_clear(WHITE);
switch((arrow)+1)
{
case 1:page=2141;break;//Inductor_Flag_show
case 2:page=2142;break;//AD_E_Show
case 3:page=2143;break;//Nomolization_Show
}
}
if((key_return)== 0)
{
delay_ms(300);
lcd_clear(WHITE);
page=21;
arrow=0;//page=1;
}
}
大家可以看到switch((arrow)+1)这个地方,开始改变page即换页。返回呢就是直接清屏,page换成上一个页面所代表的值,其实可以用再建立一个page_last变量来存上一页page的值,但然后page=page_last;但是之前我这样写出现过burg,这样的进阶操作可以留给大家实现哦。
这样一步一步写switch((arrow)+1),改变page的值就行了,是不是很容易呢。
细节
写的时候呢,大家不知道有没有发现,每个我建立的函数,都是先key_action,之后show出来,然后是if switch函数,这个顺序可以直接套的,当然可以写你自己喜欢的顺序,但是希望不要出burg。然后是if(key_enter)里面俩switch 是先switch(arrow)再switch(arrow+1)
因为如果确认按键按下之后不止要换页的还有执行一段其他代码,有时候页面都换了才执行会出问题。当然也不是不能这样,你只要调试单片机的时候没有问题就行。
switch(arrow)
{
case 0:lcd_showstr(20,0,"Goodluck");
delay_ms(200);
Go_Flag_Init();
break;
case 1:I_count1++;break;
case 2:I_count2++;break;
case 7:fg.sao_miao=0;
break;
}
switch((arrow)+1)//换页
{
case 4:page=214;arrow=0;break;
}
总结
总体的代码框架就是这样了,不知不觉写的有点多,但是记住最基本的思路就是控制arrow和page这两个变量,在依据arrow的值改变page这就是菜单的基本操作,用的时候直接扔主循环就行如下
void main()
{
EA=0;
board_init();
EA=1;
//注意多重调用
//上面的初始化每个单片机都不一样
//但是调用的是直接把menu_main函数放下面的位置就行
while(1)
{
menu_main();//菜单
}
如果觉得帮助到你的话请给我点个赞吧(・ω・)。