再挖一个仿真+两条有价值回复献给此地:单片机+LCD显示菜单(C源程序+proteus仿真) (众
http://cache.amobbs.com/bbs_upload782111/files_11/ourdev_539717.JPG (原文件名:ourmenu.JPG) 先把原帖原话发出来,免得大家又要去注册,又要回复,忙的不亦乐乎。
完整的源代码+proteus仿真随后传上来
一个简单却实用的菜单程序
如果编写简单的程序的话这个菜单函数就可以不用看了,但是当你编写到复杂的程序的时候就可能就会为人机界面和各个函数直接的连接而烦恼了,这个菜单函数就是用来解决这个问题,虽然没有UCGUI厉害,但是我觉得代码在2000行一下的话用起来还是不错的选择的.虽说简单但是用到了结构体大部分的知识了,希望c语言不太好的朋友自己补习一下好了.
主要程序:
CODE:
#define Null 0
/***********************
* 函数声明 *
***********************/
void ShowMenu(void);
void Menu_Change(unsigned char KeyNum);
/***********************
* 按键功能键宏定义 *
***********************/
#define UP '3'
#define Down '7'
#define Esc 'B'
#define Enter 'F'
#define Reset '0'
/**********************
* 目录结构体定义 *
**********************/
struct MenuItem
{
unsigned char MenuCount; //当前层节点数
unsigned char *DisplayString; //菜单标题
void (*Subs)(); //节点函数
struct MenuItem *ChildrenMenus; //子节点
struct MenuItem *ParentMenus; //父节点
};
/***********************
* 调用子函数区 *
***********************/
void NullSubs(void)
{
}
//----------------------以下为例子,请根据实际情况修改---------------------------
void TimeSet(void)
{
put_s("TimeSet");
}
void DateSet(void)
{
put_s("DateSet");
}
void AlertSet (void)
{
put_s("AlertSet");
}
//------------------------------------------------------------------------------ /***********************
* 结构体区 *
***********************/
//----------------------以下为例子,请根据实际情况修改---------------------------
struct MenuItem TimeMenu;
struct MenuItem FlashMenu;
struct MenuItem VoiceMenu;
struct MenuItem RobotMenu;
struct MenuItem MainMenu;
struct MenuItem TimeMenu=
{ //MenuCount DisplayString Subs ChildrenMenus ParentMenus
{4, "1.Time Set", TimeSet, Null, MainMenu},
{4, "2.Date Set", DateSet, Null, MainMenu},
{4, "3.AlertSet", AlertSet, Null, MainMenu},
{4, "4.Back", NullSubs, MainMenu, MainMenu},
};
struct MenuItem FlashMenu=
{ //MenuCount DisplayString Subs ChildrenMenus ParentMenus
{5, "1.Flash Record", NullSubs, Null, MainMenu},
{5, "2.Play", NullSubs, Null, MainMenu},
{5, "3.Pause", NullSubs, Null, MainMenu},
{5, "4.Flash Delete", NullSubs, Null, MainMenu},
{5, "5.Back", NullSubs, MainMenu, MainMenu},
};
struct MenuItem VoiceMenu=
{ //MenuCount DisplayString Subs ChildrenMenus ParentMenus
{5, "1.Voice Record" , NullSubs, Null, MainMenu},
{5, "2.Play", NullSubs, Null, MainMenu},
{5, "3.Pause", NullSubs, Null, MainMenu},
{5, "4.Voice Delete", NullSubs, Null, MainMenu},
{5, "5.Back", NullSubs, MainMenu, MainMenu},
};
struct MenuItem RobotMenu=
{ //MenuCount DisplayString Subs ChildrenMenus ParentMenus
{5, "1.Turn Left", NullSubs, Null, MainMenu},
{5, "2.Turn Right", NullSubs, Null, MainMenu},
{5, "3.Go Ahead", NullSubs, Null, MainMenu},
{5, "4.Go Back", NullSubs, Null, MainMenu},
{5, "5.Back", NullSubs, MainMenu, MainMenu},
};
struct MenuItem MainMenu=
{ //MenuCount DisplayString Subs ChildrenMenus ParentMenus
{5, "1.Time Set" , NullSubs, TimeMenu, Null},
{5, "2.Voice Center", NullSubs, VoiceMenu, Null},
{5, "3.Robot Control", NullSubs, RobotMenu, Null},
{5, "4.Flash Option", NullSubs, FlashMenu, Null},
{5, "5.Back", NullSubs, MainMenu, MainMenu},
};
//------------------------------------------------------------------------------ /***********************
* 全局变量声明区 *
***********************/
struct MenuItem (*MenuPoint) = MainMenu; //结构体指针,指向结构体后由内部函数指针指向功能函数
unsigned char DisplayStart = 0; //显示时的第一个菜单项
unsigned char UserChoose = 0; //用户所选菜单项
unsigned char DisplayPoint = 0; //显示指针
unsigned MaxItems; //同级最大菜单数
unsigned char ShowCount=2; //同屏显示菜单数
/***********************
*显示函数区 *
***********************/
void ShowMenu(void)
{
unsigned char n;
MaxItems = MenuPoint.MenuCount;//定义最大同级菜单
DisplayPoint = DisplayStart;
for(n=0;DisplayPoint<MaxItems&&n<ShowCount;n++)
{if(DisplayPoint==UserChoose)
LCD_write_string(0,n,"->");
LCD_write_string(2,n,MenuPoint.DisplayString);
}
}
void Menu_Change(unsigned char KeyNum)
{
if(KeyNum)
{
switch(KeyNum)
{
case UP:
UserChoose --;
if (UserChoose ==255)
{
UserChoose = 0;//上翻截至,如果要回滚赋值MaxItems-1
}
break;
case Esc:
if (MenuPoint.ParentMenus != Null)
{
MenuPoint = MenuPoint.ParentMenus;
UserChoose = 0;
DisplayStart = 0;
}
break;
case Down:
UserChoose ++;
if (UserChoose == MaxItems)
{
UserChoose = MaxItems-1;//下翻截至,如要回滚赋值为0
}
break;
case Enter:
if (MenuPoint.Subs != NullSubs)
{
(*MenuPoint.Subs)();
}
else if (MenuPoint.ChildrenMenus != Null)
{
MenuPoint = MenuPoint.ChildrenMenus;
UserChoose = 0;
DisplayStart = 0;
}
break;
case Reset:
MenuPoint = MainMenu;
UserChoose = 0;
DisplayStart = 0;
break;
default:break;
}
if (UserChoose%ShowCount==0) //一屏只能显示ShowCount行
DisplayStart = UserChoose;
else if(UserChoose==1||UserChoose== 3)
DisplayStart = UserChoose-1; //实现滚屏的关键
LCD_write_command(0x01); //液晶清屏,根据不同液晶函数自行修改
delay_nms(5); //液晶为慢速器件
ShowMenu();
}
} 睡醒顶一下 再来把那两条MVP回复发出来,不知道为什么,那位版版只顾着把资料发上来,对后面的回复却没有作答
回复之一:
工程里的KEY.H有逻辑错误,我看到的是
unsigned char get_key(void)
{
unsigned char i;
static unsigned char j;//按键记录
i=key_read();
if(i==0x00) //无有效按键按下
{
j=0x00; //清除按键记录
return 0x00; //程序退出
}
if(j==0) //为新按键
{
j=i; //保存本次结果
delay_nms(10); //延时去抖动
i=key_read();
if(i==j)
return i;
}
return 0x00;
}
修改一下
unsigned char get_key(void)
{
unsigned char i;
static unsigned char j;//按键记录
i=key_read();
if(i==0x00) //无有效按键按下
{
j=0x00; //清除按键记录
return 0x00; //程序退出
}
if(j!=i) //为新按键
{
j=i; //保存本次结果
delay_nms(10); //延时去抖动
i=key_read();
if(i==j)
return i;
}
return 0x00;
}
这样就行了 回复之二:
试过了,很好用。先谢了!
不过!!!
问题来了,一个指针在ICC中貌似没有办法设为空指针,一个函数指针也没法设置为空?
还有个问题,对于结构体数组的初始化来说,在定义时初始化最方便,直接把一大堆数据和指针赋值就好。可是,我在第一个主菜单的初始化中要用到在下面定义的结构体,于是,我把下面结构体的定义放到了上面,可是同时这个结构体又得用到主菜单这个结构体...于是初始化出问题了...
像这种初始化时需要相互指来指去的结构体初始化很麻烦啊!?
===================================================================
这个问题,我也想请教一下ourAVR里的高人,呵呵。
好了,这里先把完整程序+Proteus仿真传上来:
点击此处下载 ourdev_539737.rar(文件大小:97K) (原文件名:menu.rar)
本程序,支持滚屏,同级任意多任务,非树形菜单结构. 看来大家都午休了,自己顶一下 顶顶 mark MARK MARK. 下了,谢谢了 不错 不错 不错,顶一下 帮顶 mark make 拜读了lz的大文,收获不小
但我对本文中.c和.h文件的组织感到有些疑惑不解。
书上说:
.h文件应该包含:宏定义,函数声明等
还要加上#ifndef~~~~~防止重定义
可本文中,.h文件中却有函数定义,而且函数声明也没有#ifndef~~~~~~~~`
这种组织结构合适吗???
还有一点:
菜单返回时,没有返回到原来选中的父菜单,能够在结构体中的“共享区”(也就是每个结构体的第一个元素)设置标志,以供返回,否则都设成本机菜单数,有些浪费
第三点疑问:
lz的“空结构体”使用null代替,而nullsubs这个空函数能否也省去定义,也用null呢??
我对c的比较少,
不要见笑~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~ 好像以前有人在论坛发过类似的东东
菜单数组是不是应该存在flash里? 支持呀 正好想编一个菜单程序,参考一下先 记号 mark 新手,学习了,慢慢研究 学习了,慢慢研究 mark! mark! mark!慢慢学习 mark lz好啊,这个菜单调用子函数后退步出来啊 请教怎么改!!!谢谢!!! 不错,可以好好学习一下。 ding顶 MARK mark 不顶不行 的确,不顶不行 深夜轻轻地顶一下~~ 早上好!!顶一个 Mark 无意中看到这个帖子感觉很眼熟,后来才发现这个是我写的程序,抱歉,写得非常不好,这是07年6月单片机刚入门的时候和伙伴一起写的,当时是参照傻孩子的程序思路写的,不过还是写的比较差。我现在指出一下上面的不足之处:
1:如 19楼 jishanlaike所说的头文件应该是只声明不定义的,当时写的程序太少,没有注意到这点,
2:key.h这个函数写的不好,如果现在让我再写我肯定是用状态机来写了,还有,里面的那个10ms的延时是绝对不允许的,非常大的浪费,而且和实际的应用或者液晶等其他器件显示器件打架。不过值得说明的是,这个key.h功能是没有错的,后面那部分是为了实现按键锁定(长按时单次返回)和去抖动用的,比较不好理解,大家多看几次就明白了。
3:结构体写的有点啰嗦了,其实种菜单函数只要记录以下几个关键点就可以了:层数,同层最大条目数,当前条目数。
4:程序写得可移植性差了写,应该把驱动层和应用层区分开来,并定义好API接口,这样才方便复用,重复利用才是这个菜单的真正目的。
大概的构思如下:
应用层----------》具体项目需要用的功能,比如温度的采集,距离的测试结果处理等等
|
|
|
系统应用层:菜单函数,绘图函数,汉字处理函数,延时处理函数,按键处理函数,液晶处理函数等等,一些公用的,基本的应用。
|
|
|
底层驱动层:按键扫描函数,液晶驱动函数,串口驱动函数,IIC驱动函数等,这些都是和硬件直接大交道的函数了。
如果项目比较大的,可以考虑加入操作系统,那么整个结构就应该是:应用层----系统应用层----系统层----底层驱动层
现在工作比较忙了,暂时没有时间写这样的程序了,如果有的话我想再次写的时候会比这个好很多很多,^_^ 关于菜单的真不错,呵呵 我借宝地为论坛做那么九九牛一毛贡献,这个key.h,在某个按键一直按下去的时候,只执行一次,若要某个按键一直按下去连续执行某个动作的话可以这样:
unsigned char get_key(void)
{
unsigned char i;
static unsigned char j;//按键记录
i=key_read();
if(i==0x00) //无有效按键按下
{
j=0x00; //清除按键记录
return 0x00; //程序退出
}
//if(j!=i) //为新按键
else//只要是有效按键
{
j=i; //保存本次结果
//delay_nms(10); //延时去抖动
delay_nms(100); //延时加长一点
i=key_read();
if(i==j)
return i;
}
return 0x00;
}
这个论坛太好了,我在别的论坛从来不发言的,呵呵 Mark,好帖~! mark 这个要好好研究下 太强大了!!什么时候我才能达到那水平啊! 正在学习液晶,谢了 这个要好好研究下 mark 学习 支持 不错,学习了!呵呵,哪天有空也玩玩才行! 学习了,很不错的例子,谢谢分享,等我的这个搞好,也和大家交流一下,呵呵。 学习,谢谢。 学习 学习一下。。。。 学习了,慢慢研究 mark mark make 不错 谢谢 学习 谢谢 好好,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 回复【5楼】zcllom 星罗棋布
-----------------------------------------------------------------------
THANKS VERY MUCH ! mark 回复【3楼】zcllom 星罗棋布
-----------------------------------------------------------------------
不错,我正在找此方面的资料 回复【楼主位】zcllom 星罗棋布
-----------------------------------------------------------------------
先下来看看 回复【楼主位】zcllom 星罗棋布
-----------------------------------------------------------------------
我也刚刚自己参照着别人的程序编写了一个用PROTEUS仿真的LCD12864显示的程序,只有显示功能没有按键 回复【楼主位】zcllom 星罗棋布
-----------------------------------------------------------------------
源程序如下:
/***********************
端口定义如下:
PD为数据端口
PC1控制CS1(left)
PC2控制CS2
PC3控制RS
PC4控制RW
PC5控制E
PC6控制RST(复位端口)
*************************/
#include<iom16v.h>
#include<macros.h>
#define uchar unsigned char
#define uint unsigned int
/*******命令字定义*************/
#define Disp_On 0x3f
#define Disp_Off0x3e
#define Col_Add0x40
#define Page_Add0xb8
#define Start_Line 0xc0
#pragma data:code
const uchar nan[]={
/*-------南---------*/
/*------- 宋体12;此字体下对应的点阵为:宽x高=16x16------*/
0x14,0x24,0x44,0x84,0x64,0x1C,0x20,0x18,0x0F,0xE8,0x08,0x08,0x28,0x18,0x08,0x00,
0x20,0x10,0x4C,0x43,0x43,0x2C,0x20,0x10,0x0C,0x03,0x06,0x18,0x30,0x60,0x20,0x00
};
/*************************
1、 函数功能:时间延时函数
入口参数:ms
出口参数:
*************************/
void delay(uint ms)
{
uint i,j;
for(i=0;i<ms;i++)
for(j=0;j<1141;j++);
}
/*******************
2、函数功能:写命令
入口参数:cmd
出口参数:
*********************/
void write_com(uchar cmd)
{
PORTC&=~BIT(3);//RS=0
PORTC&=~BIT(4);//RW=0
PORTD=cmd;
delay(1);
PORTC|=BIT(5);//E=1
delay(1);
PORTC&=~BIT(5);//E=0
}
/*************************
3、函数功能:写数据
入口参数:dat
出口参数:
************************/
void write_dat(uchar dat)
{
PORTC|=BIT(3);//RS=1
PORTC&=~BIT(4);//RW=0
PORTD=dat;
delay(1);
PORTC|=BIT(5);//E=1
delay(1);
PORTC&=~BIT(5);//E=0
}
/**********************
4、函数功能:清除LCD内存程序
入口参数:pag,col,hz
出口参数:
***********************/
void clr_scr()
{
uchar j,k;
PORTC|=BIT(1);//CS=1,开右半屏
PORTC|=BIT(2);//cs=2,开左半屏
write_com(Page_Add+0);
write_com(Col_Add+0);
for(k=0;k<8;k++)
{
write_com(Page_Add+k);
for(j=0;j<64;j++)
write_dat(0x00);
}
PORTC&=~BIT(1);//CS1=0
PORTC&=~BIT(2);//CS2=0
}
/**********************************
5、 函数功能:指定位置显示16*16的程序
入口参数:pag,col,hz
出口参数:
************************************/
void hz_disp16(uchar pag,uchar col,const uchar *hz)
{
uchar j=0,i=0;
for(j=0;j<2;j++)
{
write_com(Page_Add+pag+j);
write_com(Col_Add+col);
for(i=0;i<16;i++)
write_dat(hz);
}
}
/*************************************
6、函数功能:指定位置显示8*16的程序
入口参数:pag,col,hz
出口参数:
***************************************/
void hz_disp8(uchar pag,uchar col,const uchar *hz)
{
uchar j=0,i=0;
for(j=0;j<2;j++)
{
write_com(Page_Add+pag+j);
write_com(Col_Add+col);
for(i=0;i<8;i++)
write_dat(hz);
}
}
/*******************************************
7、函数功能:LCD初始化程序
入口参数:
出口参数:
**********************************************/
void init_lcd()
{
delay(10);
PORTC&=~BIT(1);
PORTC&=~BIT(2);
delay(1);
write_com(Disp_Off);
write_com(Page_Add+0);
write_com(Start_Line+0);
write_com(Col_Add+0);
write_com(Disp_On);
}
void main()
{
DDRD=0XFF;
DDRC|=BIT(1);//PC从1~6均设为输入
DDRC|=BIT(2);
DDRC|=BIT(3);
DDRC|=BIT(4);
DDRC|=BIT(5);
DDRC|=BIT(6);
PORTD=0XFF;
PORTC=0XFF;
init_lcd();
clr_scr();
PORTC|=BIT(1);
PORTC&=~BIT(2);
while(1)
{
PORTC|=BIT(2);
PORTC&=~BIT(1);
delay(1);
hz_disp16(2,32,nan);
delay(2);
}
} 喜欢显示什么字可以在自已设置 本人刚刚学习,感觉楼主的程序看着有点麻烦 本来想把原理图给贴上去 可怎么也不能粘贴成功! 请注意本人使用的是ATMEGA16而不是89C51 mark 回复【楼主位】zcllom 星罗棋布
-----------------------------------------------------------------------
ddddddddd 下了,谢谢了 学习一下结构体~~~~~~~~~ 谢谢 非常好,谢谢 mark mark 学习 学习了./emotion/em003.gif 仿真文件呢? mark mark mark mark!!!!!!!!!!!!!! ding!!!!!!!!!!!!!!!! mark!~ 太好啦,又学了不少东西 好资料,收藏先 mark 顶一下,不过结构体应该放入flash区。不然。占内存太大。 看看! mark
页:
[1]
2