简介:
单片机开发简易的人机交互界面时如果程序组织不好容易导致代码臃肿、不易读、甚至会导致改了这边那头运行出现错误的情况,CAFace正是这样一个界面程序的组织规范,或者说用户界面管理软件模块。它占用空间小、非常适合于8位或低端32位机实现简单的用户操作界面。它能使编程人员将精力集中在功能的实现上而不必为程序的结构花费更多的时间,并且使得以后产品功能的增减变得更加简单,易维护。
CAFace版本及说明:
CAFace V0.8精简版
结构简单、使用灵活,适用于较小的应用,如数码管或段式液晶用户界面,单片机资源较少的场合。
CAFace V1.0经典版
适用于较小的应用,适用于功能较相对繁琐的界面,资源相对丰富的系统应用(如MEGA128、ARM7、CORTEX-M3等)。
CAFace V1.3增强版
适用于相对复杂的用户界面的实现,如占阵液晶屏、彩色工业串口屏等等,可适用于基于操作系统的应用(如STM32+FreeRTOS), 可与消息传递结合应用。
应用示例
wnd.h
//wnd.h
#ifndef WND_H
#define WND_H
//窗口结构
typedef struct tag_WND
{
struct tag_WND * pParentWnd; //上级窗口
uint8_t *Buf; //窗口专用全局空间
void (*Load)(struct tag_WND *pWnd); //装入事件处理函数
void (*UnLoad)(struct tag_WND *pWnd); //销毁事件处理函数
void (*KeyEvent) (struct tag_WND *pWnd,uint8_t key); //按键处理函数
void (*TimerEvent)(struct tag_WND *pWnd,uint8_t flag);//时钟处理函数
}WND,*PWND;
//接口函数
void CreateWnd(PWND pWnd,void (*kf)(PWND ,uint8_t) , PWND parWnd, uint8_t buf_size);
void SetCallBackWnd(PWND pWnd,void (*Load)(PWND),void (*UnLoad)(PWND),void (*TimerEvent)(PWND,uint8_t));
void SetCurrentWnd(PWND pWnd);
void ExitCurrentWnd(void);
void CurrentKeyEvent(uint8_t key);
void CurrentTimerEvent(void);
#endif
wnd.c
//在程序中调用CreateWnd时总共为窗口分配的缓冲区大小
#define MAX_WND_BUF_SIZE 20
static PWND g_pCurrentWnd=0; //当前窗口
static uint8_t g_WndBuffer[MAX_WND_BUF_SIZE];//分配给窗口用的缓冲区
static uint8_t *g_pWndBuf=g_WndBuffer;//缓冲分配当前位置
//初始化窗口函数,参数:窗口对象指针,按键处理函数,父窗口指针,为窗口分配缓冲区大小
//对于一个窗口此函数是必须被调用的
void CreateWnd(PWND pWnd, \
void (*kf)(PWND ,uint8_t) , \
PWND parWnd, \
uint8_t buf_size)
{
uint8_t i;
pWnd->KeyEvent=kf; //设置按键处理函数
pWnd->pParentWnd=(struct tag_WND *)parWnd; //设置父窗口
pWnd->Load=0;
pWnd->UnLoad=0;
pWnd->TimerEvent=0;
//分配窗口缓冲区
pWnd->Buf=g_pWndBuf;
g_pWndBuf+=buf_size;
//窗口缓冲区清零
for(i=0;i<buf_size;i++)
pWnd->Buf[i]=0;
}
//设置窗口回调函数 ,分别设置装入时调用的Load,销毁时调用的UnLoad和窗口定时触发事件函数
//如果没有相应的函数,则指定0,此函数是可选的
void SetCallBackWnd(PWND pWnd,\
void (*Load)(PWND),\
void (*UnLoad)(PWND),\
void (*TimerEvent)(PWND,uint8_t))
{
pWnd->Load=Load;
pWnd->UnLoad=UnLoad;
pWnd->TimerEvent=TimerEvent;
}
//设置当前显示窗口
void SetCurrentWnd(PWND pWnd)
{
//如果当前窗口有销毁函数就调用
if(g_pCurrentWnd)
{
if(g_pCurrentWnd->UnLoad != 0)
g_pCurrentWnd->UnLoad(g_pCurrentWnd);
}
g_pCurrentWnd = pWnd;
if(pWnd->Load != 0)
pWnd->Load(g_pCurrentWnd);
}
//退出当前显示窗口,如果没有父窗口就不起作用
void ExitCurrentWnd(void)
{
//如果当前窗口的父窗口有效才执行
if(g_pCurrentWnd->pParentWnd == 0)
return ;
//将父窗口设置为当前窗口
SetCurrentWnd(g_pCurrentWnd->pParentWnd);
}
//按键事件处理函数,主程序发现有按键时需调用此函数
void CurrentKeyEvent(uint8_t key)
{
if(g_pCurrentWnd->KeyEvent != 0)
g_pCurrentWnd->KeyEvent(g_pCurrentWnd,key);
}
//定时事件处理函数,主程序需要按一定的周期调用这个函数
void CurrentTimerEvent(void)
{
static uint8_t flag=0;
flag=!flag;
if(g_pCurrentWnd->TimerEvent != 0)
g_pCurrentWnd->TimerEvent(g_pCurrentWnd,flag);
}
利用这个框架写了一个设置菜单程序,这个菜单程序包含6个可调数值的选项,它用5个按键实现对各选项的编辑。程序如下:
/********************************
CAFace实现的1602菜单演示程序
文件名:display.c
编译:WinAVR-20070525 或 VC++6.0
功能:本程序实现LCM1602上实现一个菜单系统
它响应加,减,切换,确认和取消等五个按键
可设置或调整6个参数.
操作方法:
1.上电后进入主界面 欢迎词 Welcome to www.chipart.cn
此时按"确认"键进入菜单选择界面
2.在菜单选择界面下可按"加","减"键选择菜单项
选择好菜单项后可按"确认"键进入当前项后调整其值.
3.调整参数时按加"减","键"进行调整,之后按"确认"则保存到EEPROM
后退出到上级界面,如果按"取消",则是不保存退出到上级界面.
4.菜单中第三项为时钟格式数据的调整,小时值和分钟值的调整用"切换"键
来互相切换.
*******************************/
WND g_MainWnd;//主界面窗口对象
WND g_MenuWnd;//菜单窗口对象
WND g_OptionWnd[6];//菜单选项窗口对象
uint8_t eep_Par[7] EEMEM={10,20,12,40,40,50,60}; //EEPROM数据,存储参数
//通用数值调整按键处理函数
//key:按键码 buf:要编辑的数值地址 max:最大值 min:最小值
void GeneralEditVal(uint8_t key,uint8_t *buf,uint8_t max,uint8_t min)
{
switch(key)
{
case KEY_INC:
if((*buf) < max)
(*buf)++;
break;
case KEY_DEC:
if((*buf)>min)
(*buf)--;
break;
case KEY_CANCEL:
ExitCurrentWnd();
break;
default:
break;
}
}
//通用时钟调整函数
void GeneralEditTime(uint8_t key,uint8_t *buf)
{
uint8_t max;
uint8_t *p;
if(buf[2]==0)
{
max=23;
p=buf;
}
else
{
max=59;
p=buf+1;
}
switch(key)
{
case KEY_INC:
if(*p<max)
(*p)++;
break;
case KEY_DEC:
if(*p>0)
(*p)--;
break;
case KEY_TAB:
if(buf[2]==0)
buf[2]=1;
else
buf[2]=0;
break;
case KEY_CANCEL:
ExitCurrentWnd();
break;
default :
break;
}
}
///主界面
void MainLoad(PWND pWnd)
{
lcd_clear();
lcd_display_string(3,0,"Welcome to");
lcd_display_string(1,1,"www.chipart.cn");
}
void MainKey(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
SetCurrentWnd(&g_MenuWnd);
}
///菜单界面
void MenuLoad(PWND pWnd)
{
lcd_clear();
lcd_display_string(5,0,"Menu");
lcd_display_number(9,0,pWnd->Buf[0]);
lcd_display_string(0,1,"ok");
lcd_display_string(10,1,"cancel");
}
void MenuKey(PWND pWnd,uint8_t key)
{
if(key==KEY_CANCEL)
{
ExitCurrentWnd();
return ;
}
else if (key==KEY_OK)
{
SetCurrentWnd(&g_OptionWnd[pWnd->Buf[0]]);
return ;
}
else if(key== KEY_INC)
{
if(pWnd->Buf[0] < 5)
++(pWnd->Buf[0]);
lcd_display_number(9,0,pWnd->Buf[0]);
}
else if(key == KEY_DEC)
{
if(pWnd->Buf[0] >0)
--(pWnd->Buf[0]);
lcd_display_number(9,0,pWnd->Buf[0]);
}
}
///选项1 编辑一个5~15的数据
void Option1Load(PWND pWnd)
{
lcd_clear();
lcd_display_string(0,0,"Parameter1:");
pWnd->Buf[0]=eeprom_read_byte(eep_Par);
}
void Option1Key(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
{
eeprom_write_byte(eep_Par,pWnd->Buf[0]);
ExitCurrentWnd();
}
else
GeneralEditVal(key,pWnd->Buf,15,5);
}
void Option1Timer(PWND pWnd,uint8_t flag)
{
if(flag)
lcd_display_number(7,1,pWnd->Buf[0]);
else
lcd_display_string(7,1," ");
}
///选项2 编辑一个15~25的数据
void Option2Load(PWND pWnd)
{
lcd_clear();
lcd_display_string(0,0,"Parameter2:");
pWnd->Buf[0]=eeprom_read_byte(eep_Par+1);
}
void Option2Key(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
{
eeprom_write_byte(eep_Par+1,pWnd->Buf[0]);
ExitCurrentWnd();
}
else
GeneralEditVal(key,pWnd->Buf,25,15);
}
///选项3 编辑一个时钟数据
void Option3Load(PWND pWnd)
{
lcd_clear();
lcd_display_string(0,0,"Parameter3:");
lcd_display_char(7,1,':');
pWnd->Buf[0]=eeprom_read_byte(eep_Par+2);
pWnd->Buf[1]=eeprom_read_byte(eep_Par+3);
}
void Option3Key(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
{
eeprom_write_byte(eep_Par+2,pWnd->Buf[0]);
eeprom_write_byte(eep_Par+3,pWnd->Buf[1]);
ExitCurrentWnd();
}
else
GeneralEditTime(key,pWnd->Buf);
}
void Option3Timer(PWND pWnd,uint8_t flag)
{
if(flag)
{
lcd_display_number(5,1,pWnd->Buf[0]);
lcd_display_number(8,1,pWnd->Buf[1]);
}
else
{
if(pWnd->Buf[2]==0)
{
lcd_display_string(5,1," ");
lcd_display_number(8,1,pWnd->Buf[1]);
}
else
{
lcd_display_string(8,1," ");
lcd_display_number(5,1,pWnd->Buf[0]);
}
}
}
///选项4 编辑一个35~45的数据
void Option4Load(PWND pWnd)
{
lcd_clear();
lcd_display_string(0,0,"Parameter4:");
pWnd->Buf[0]=eeprom_read_byte(eep_Par+4);
}
void Option4Key(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
{
eeprom_write_byte(eep_Par+4,pWnd->Buf[0]);
ExitCurrentWnd();
}
else
GeneralEditVal(key,pWnd->Buf,45,35);
}
///选项5 编辑一个45~55的数据
void Option5Load(PWND pWnd)
{
lcd_clear();
lcd_display_string(0,0,"Parameter5:");
pWnd->Buf[0]=eeprom_read_byte(eep_Par+5);
}
void Option5Key(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
{
eeprom_write_byte(eep_Par+5,pWnd->Buf[0]);
ExitCurrentWnd();
}
else
GeneralEditVal(key,pWnd->Buf,55,45);
}
///选项6 编辑一个55~65的数据
void Option6Load(PWND pWnd)
{
lcd_clear();
lcd_display_string(0,0,"Parameter6:");
pWnd->Buf[0]=eeprom_read_byte(eep_Par+6);
}
void Option6Key(PWND pWnd,uint8_t key)
{
if(key==KEY_OK)
{
eeprom_write_byte(eep_Par+6,pWnd->Buf[0]);
ExitCurrentWnd();
}
else
{
GeneralEditVal(key,pWnd->Buf,65,55);
}
}
//这是本文件对外的唯一接口函数,其它函数均不必在主程序中显式调用.
void DisplayInit(void)
{
uint8_t i,x;
//参数合法化处理,如果参数中有非法值,则初始化EEPROM
if(eeprom_read_byte(eep_Par)==0xff)
{
for(i=0;i<7;i++)
{
if(i==2)
x=12;
else
x=(i+1)*10;
eeprom_busy_wait();
eeprom_write_byte(eep_Par + i,x);
}
}
//主窗口初始化
CreateWnd(&g_MainWnd,MainKey,0,0);
SetCallBackWnd(&g_MainWnd,MainLoad,0,0);
//菜单窗口
CreateWnd(&g_MenuWnd,MenuKey,&g_MainWnd,1);
SetCallBackWnd(&g_MenuWnd,MenuLoad,0,0);
//第一个选项窗口
CreateWnd(&g_OptionWnd[0],Option1Key,&g_MenuWnd,1);
SetCallBackWnd(&g_OptionWnd[0],Option1Load,0,Option1Timer);
//第二个选项窗口
CreateWnd(&g_OptionWnd[1],Option2Key,&g_MenuWnd,1);
SetCallBackWnd(&g_OptionWnd[1],Option2Load,0,Option1Timer);
//第三个选项窗口
CreateWnd(&g_OptionWnd[2],Option3Key,&g_MenuWnd,3);
SetCallBackWnd(&g_OptionWnd[2],Option3Load,0,Option3Timer);
//第四个选项窗口
CreateWnd(&g_OptionWnd[3],Option4Key,&g_MenuWnd,1);
SetCallBackWnd(&g_OptionWnd[3],Option4Load,0,Option1Timer);
//第五个选项窗口
CreateWnd(&g_OptionWnd[4],Option5Key,&g_MenuWnd,1);
SetCallBackWnd(&g_OptionWnd[4],Option5Load,0,Option1Timer);
//第六个选项窗口
CreateWnd(&g_OptionWnd[5],Option6Key,&g_MenuWnd,1);
SetCallBackWnd(&g_OptionWnd[5],Option6Load,0,Option1Timer);
SetCurrentWnd(&g_MainWnd);//显示主界面
}
下面是它们的运行结果:
应用领域
1.数码管仪器仪表操作管理
2.段式液晶屏操作管理
3.LCD1602构成的显示系统
4.点阵型液晶模块显示系统
可到CAFace主页 下载源代码。