这个“结构体菜单”晕了,还是我自己写个,各位指点一下
如题,看别人的代码,看来看去越看越晕(主要是程序逻辑看不懂),还是自己花了几个小时(我笨),按照自己的理解来写了个程序,采用结构体的菜单,有程序有图有真相,求指点。功能:
这个菜单程序不会阻塞主循环,运行菜单的同时还可以干别的事情
这个菜单在没有改变内容的时候,是不会刷屏幕的;
只有改变了状态的菜单项才会被刷新,没有被改变的菜单项是不会被刷新的
在进入子菜单之前会保存本级菜单的选择在哪个菜单项上,从子菜单返回的时候还会回到之前的菜单项上
按ESC(伪代码)可以直接返回上级菜单。
按ok键,如果是子菜单,就会进入子菜单,如果不是子菜单,而是要执行的程序,则执行程序,如果都没有,就会返回上级菜单
PS:
代码量相比之前我的土办法真的少了很多,但是内存方面相比我之前的代码却多了很多。。。看来这个结构体菜单不适合大量的菜单。。。尤其是内存吃紧的情况。
就这么简单的几个菜单,竟然吃了我138个字节的内存,我擦!!!!而我之前的代码,一个N个菜单项的菜单(N<4),最少只需要2.1个字节的内存。。。最多255个菜单项,也只需要4.3个字节的内存。
好吧,代码奉上,各位指点一下,那个显示的驱动之类的我就不上传了,见谅!
程序里NULL =0,UINT8=unsigned char
//main.h文件
#ifndef __MAIN_H__
#define __MAIN_H__
#include "keyscan.h"
xdata struct MenuRes
{
UINT8 MenuItemCount;
UINT8 *MenuItemString;
struct MenuRes *ParentMenus;
struct MenuRes *SubMenus;
void (*ExecFunction)();
};
extern xdata structMenuRes Menu_MainItems;
extern xdata structMenuRes Menu_SystemSet;
extern xdata structMenuRes Menu_TimeSet;
void SystemSet1(void);//测试而已
void SystemSet2(void);
void SystemSet3(void);
void TimeSet1(void);
void TimeSet2(void);
void Menu_Item_ChoiceForKey(void);//键盘操作
void Menu_Show_Menu(void);//显示菜单出来
#endif
//main.c
#include "Keyscan.h"
#include "stc_spi.h"
#include "uc1701x series.h"
#include "Main.h"
xdata structMenuRes Menu_MainItems={
{3,"SYSTEM SET",NULL,Menu_SystemSet,NULL},
{3,"TIME SET",NULL,Menu_TimeSet,NULL},
{3,"结构体菜单?FUCK!",Menu_MainItems,NULL,NULL},
};
xdata structMenuRes Menu_SystemSet={
{4,"SCREEN SET",Menu_MainItems,NULL,SystemSet1},
{4,"OLED SET",Menu_MainItems,NULL,SystemSet2},
{4,"FUCK SET",Menu_MainItems,NULL,SystemSet3},
{4,"BACK TO MAIN MENU",Menu_MainItems,NULL,NULL},
};
xdata structMenuRes Menu_TimeSet={
{3,"DATE SET",Menu_MainItems,NULL,TimeSet1},
{3,"TIME SET1",Menu_MainItems,NULL,TimeSet2},
{3,"BACK TO MAIN MENU",Menu_MainItems,NULL,NULL},
};
xdata struct {
UINT8 Select;//当前选择
UINT8 LastSelect;//上次的选择
UINT8 RePaint;//是否强制重画菜单,=1强制重画,=0自己决定是否该重画菜单
UINT8 ParentMenuSelect;//上级菜单在进入子菜单之前,选择的哪个菜单
struct MenuRes *MainPoint;//当前菜单
}ItemSelect={ 0, 0, 1, 0, Menu_MainItems};
void SystemSet1(void)
{
ST7565_Clear();//清屏
ST7565_Paint_MixStr(32,24,"SCREEN SET!",0);
ST7565_Paint_Push_Data();//将缓冲区的数据写入到Lcd
//按任意键退出,测试而已,就暂时这样吧
Key_Scan();//扫描键盘
while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
}
}
void SystemSet2(void)
{
ST7565_Clear();//清屏
ST7565_Paint_MixStr(32,24,"OLED SET!",0);
ST7565_Paint_Push_Data();//将缓冲区的数据写入到Lcd
Key_Scan();//扫描键盘
while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
}
}
void SystemSet3(void)
{
ST7565_Clear();//清屏
ST7565_Paint_MixStr(32,24,"FUCK!FUCK!!",0);
ST7565_Paint_Push_Data();//将缓冲区的数据写入到Lcd
Key_Scan();//扫描键盘
while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
}
}
void TimeSet1(void)
{
ST7565_Clear();//清屏
ST7565_Paint_MixStr(32,24,"DATE SET",0);
ST7565_Paint_Push_Data();//将缓冲区的数据写入到Lcd
Key_Scan();//扫描键盘
while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
}
}
void TimeSet2(void)
{
ST7565_Clear();//清屏
ST7565_Paint_MixStr(32,24,"TIME SET",0);
ST7565_Paint_Push_Data();//将缓冲区的数据写入到Lcd
Key_Scan();//扫描键盘
while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
void Menu_Show_Menu(void)
{
UINT8 xdata ItemCount;//不知道拿来干啥用的
if( ItemSelect.RePaint == 1)//如果要强制重画菜单
{
ST7565_Clear();//清屏
ST7565_Paint_Rectangle( 0, 0, 64, 128, 1);//先画个框框意思一下
ST7565_Paint_HLine(13,1,126,1);//横线
ST7565_Paint_MixStr(1,4,"我擦这是结构体的菜单",0);//画出标题,12x12新宋体
}
for(ItemCount = 0; ItemCount < ItemSelect.MainPoint[ ItemSelect.Select ].MenuItemCount; ItemCount++)//遍历所有的菜单,作为测试,这里只考虑4行菜单条目的情况,超过则不会显示
{
if(ItemSelect.Select == ItemCount || ItemSelect.LastSelect == ItemCount || ItemSelect.RePaint == 1)//一次只能选中一个菜单项
{
ST7565_Paint_MixStr( ((ItemCount + 1) * 12) + 3, 10, ItemSelect.MainPoint[ ItemCount ].MenuItemString, (ItemSelect.Select == ItemCount) ? 1 : 0 );//显示出来菜单
}
}
//显示缓存写好了,那么就刷吧。。。
if(( ItemSelect.Select != ItemSelect.LastSelect ) || ( ItemSelect.RePaint == 1 ))//如果有必要刷新,才会刷屏幕
ST7565_Paint_Push_Data();//将缓冲区的数据写入到Lcd
ItemSelect.LastSelect = ItemSelect.Select;//保存上次的选择
ItemSelect.RePaint = 0;//强制画过了,就转换到自由模式
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
void Menu_Item_ChoiceForKey(void)
{
Key_Scan();//最土的键盘扫描办法
if(Key_Trg & Key_Cancel)
{
if(ItemSelect.MainPoint[ 0 ].ParentMenus == NULL)//为空则为根菜单
{
return;//这里就啥也不干好了
}
else
{//不为空,就返回上一级菜单
ItemSelect.MainPoint = ItemSelect.MainPoint[ 0 ].ParentMenus;
ItemSelect.Select = ItemSelect.ParentMenuSelect;//上级菜单之前选择的那个菜单项
ItemSelect.RePaint = 1;//强制刷新一次
}
}
if(Key_Trg & Key_plus)//+++
{ //以下为偷懒之作
( ItemSelect.Select > ( ItemSelect.MainPoint [ 0 ].MenuItemCount - 2) ) ? ItemSelect.Select = 0 : ItemSelect.Select++;
}
if(Key_Trg & Key_Minus)//---
{ //同上
ItemSelect.Select < 1 ? ItemSelect.Select = ( ItemSelect.MainPoint [ 0 ].MenuItemCount - 1) : ItemSelect.Select--;
}
if(Key_Trg & Key_Ok)//选中某个菜单,按了确认
{
if(ItemSelect.MainPoint [ ItemSelect.Select ].ExecFunction == NULL)//判断是否有执行函数,如果没有执行函数,表示要进入下一级子菜单或者返回上级菜单
{
if(ItemSelect.MainPoint[ ItemSelect.Select ].SubMenus == NULL)//判断是否有子菜单,如果也没有子菜单,则肯定是返回上级菜单
{ //如果连上级菜单的指针也没有,那尼玛设计者就是NC
ItemSelect.MainPoint = ItemSelect.MainPoint = ItemSelect.MainPoint[ ItemSelect.Select ].ParentMenus;//好吧,就算这个是返回,那么返回上级菜单先
ItemSelect.Select = ItemSelect.ParentMenuSelect;//上级菜单之前选择的那个菜单项
}
else//如果有子菜单,那么进入子菜单
{
ItemSelect.MainPoint = ItemSelect.MainPoint = ItemSelect.MainPoint [ ItemSelect.Select ].SubMenus;//进入子菜单
ItemSelect.ParentMenuSelect = ItemSelect.Select;//进入子菜单之前,先保存本级菜单项的选择在哪个菜单项上
ItemSelect.Select = 0;//好吧,下一级菜单的选择项默认是第一个
}
}
else//如果有执行函数,那么就执行这个函数
{
( *ItemSelect.MainPoint[ ItemSelect.Select ].ExecFunction )();//执行之
}
ItemSelect.RePaint = 1;//强制刷新一次
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
void main(void)
{
//目前就只是128 x 64的OLED屏
//表示俺这个程序,如果没有必要,他是不会玩命刷屏幕的显示的
ST7565_Init();
Key_Time0Init();//键盘消抖定时器
while(1)
{
Menu_Item_ChoiceForKey();//键盘的操作
Menu_Show_Menu();//显示菜单出来
//其实以上2个函数可以写在一个函数里面,这样就不用搞那个全局的结构体ItemSelect了
//非阻塞式,这里还可以干别的事情
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
图片很慢,在编辑,一会上传,手机照的,OLED的小屏幕,很难照清楚,见谅!
终于传上来了
厉害 FUCK{:titter:}{:titter:} FUCK 淫得一个好菜单{:titter:} 自己弱爆了。 这么好的FCUK菜单,顶起。 newbie 发表于 2014-5-29 10:13
只是ItemSelect 是ram数据
struct MenuRes 的菜单信息不要放在xdata,放在flash区就节省不少ram了。 ...
明白您的意思了,下午我弄一下试试。。。谢谢 本帖最后由 lswhome 于 2014-5-29 13:22 编辑
测试了下,把xdata 改成code,这样只占用了8个字节。。。结构体7字节,一个变量,共计8个字节。。。不错,如果这样算是一个结构体的菜单的话,也就是说,我也知道怎么弄了?
剩下的工作就是要弄成无论多少个菜单项和多少个子菜单,都无需改变程序,只需改变结构体的内容,就都可以自动适应,还有就是菜单界面的美化以及图标啊标题啊动画啊之类的上面下功夫了。。。
我认为同样功能的一个产品,友好的漂亮的操作方便的人机界面会比不好的要更加吸引人。
编译结果:
Program Size: data=43.3 xdata=1032 const=1971 code=3505
菜单使用的内存都在xdata里,显示缓存占用了1024BYTE,1032-1024=8,所以只占用了8个字节,相比我之前的土办法,果然节省了内存以及代码空间。。。非常不错!
顺便求一个键盘的扫描算法,我用的太土了,貌似还是N年前的算法。无需贴程序也可以,告诉我思路就行,当然有代码更好。 不错,mark一下
菜单的我都收藏一下 呵呵,这菜单有意思。 mark。。。。学习一下 恭喜迈出第一步,真正的麻烦还在后面,就是具体的设置界面的处理。
另外按键的处理,使用状态机方式,用状态机 为关键字搜索本坛。 while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
}
这里就阻塞了吧,你main的while里面还怎么加任务,如果是在某个菜单项不按按键,就一直在这里等着咯?那后面的任务咋执行。
我在一个学术文章上看到的一个菜单结构跟你这个类似,不过他的结构体里面有菜单的ID,我自己照这个思路弄了一个,感觉维护起来简单多了。
不过我的按键扫描都是中断触发。keyscan函数里面是用RTX操作系统的阻塞等待事件来做的。然后菜单就是根据当前的menuID一直不停的刷,不过我的是数码管,哈哈。。。 jzkn 发表于 2014-5-29 16:51
while(Key_Trg==0)//等待按一个键
{
Key_Scan();//扫描键盘
哦,这个只是在一个执行函数里面的,按任意键返回那个函数,操作菜单的时候怎么会执行到这里呢???
您仔细看一下 mcu_lover 发表于 2014-5-29 16:41
恭喜迈出第一步,真正的麻烦还在后面,就是具体的设置界面的处理。
另外按键的处理,使用状态机方式,用状 ...
OK了。。。谢谢 我表示 这样的教学印象深刻 好玩 {:victory:}{:victory:}{:victory:}{:victory:} mcu_lover 发表于 2014-5-29 16:41
恭喜迈出第一步,真正的麻烦还在后面,就是具体的设置界面的处理。
另外按键的处理,使用状态机方式,用状 ...
后面的麻烦,第一步还没走好。 感谢分享,收藏、学习。 学习了!! mark,收藏备用 不错阿。 学习啦,收藏 学习了,不错,谢谢分享
页:
[1]