嵌入式软件架构--显示界面架构(工厂流水线模型,HOME界面,命令界面)
显示界面
界面设计
一共设计有8个界面,通过界面号访问

硬件包含8个数码管和12个按键
*号键用来删除
#号键用来确认和命令提示

界面的抽象

//界面号枚举
typedef enum
{HOME_UI,CMD_UI,CALL_UI,OPEN_UI,ERROR_UI,SUCCEED_UI,PASSWORD_UI,SET_PW_UI
}UI_E;
//界面消息结构
typedef struct{MSG_QUEUE_S *pQueue;//字符消息队列由右侧12个按键产生MSG_QUEUE_S *pOpenQueue;//开门消息队列用左侧四个控制按键产生ASBUF_S echo;//存储用户输入的按键消息用于回显UI_E newUi;//新界面号UI_E oldUi;//旧界面号
}DIS_MSG;typedef struct{void (*init)(void);//界面初始化函数UI_E (*proc)(DIS_MSG );//界面消息轮询函数
}UI_S;
用户输入存储缓冲
#define ABUF_LEN 8
#define WIN0 0
#define WIN1 1
typedef struct{INT8 dat[ABUF_LEN];//存储用户输入UINT8 idx;//当前存储个数
}ABUF_S;
//函数实现放在文章末尾
void initAbuf(ABUF_S *pBuf);
void saveAbuf(ABUF_S *pBuf,INT8 dat);
BOOL isABufFull(ABUF_S *pBuf);
UINT8 AbufCnt(ABUF_S *pBuf);
void delAbuf(ABUF_S *pBuf);
BOOL abufCmp(ABUF_S *p1, ABUF_S *p2);
工厂流水线模型
流水线就是上一个工位的产出流入下一个工位的输入,上下工位通过加工品沟通。
事件传递流程

界面的切换原理
将界面封装成结构体里面包含每个界面的初始化函数和消息循环函数,初始化函数只需要调用一次,消息循环函数轮询等待消息,它的返回值是界面号,如果返回的界面号与上次存储的界面号不一致我们就切换界面。当需要切面界面时,就调用新界面的初始化函数初始化界面并且不断调用新界面的消息循环函数等待新消息,直到下一次界面切换。如果返回的界面号与上次存储的界面号一致不切换界面。
根据上面的原理搭建界面架构
1,路由按键消息
//initkey函数中initMsgQueue(&Queue0);initMsgQueue(&Queue1);p->route[KEY_CHAR_EVENT]=&Queue0;//将12个字符按键消息路由到QUEUE0p->route[KEY_DOWN_EVENT]=&Queue1;//将4个控制按键消息路由到QUEUE1
//路由消息
void keyMain(void *pMsg)
{KEY_S *p = pMsg;//keyScan(p,p->pf());if(p->keyF){//按键单击事件createRouteMsg(p,&p->keyF,KEY_CHAR_EVENT);}
}
2,界面初始化与切换
#include "display.h"
DIS_MSG DisMsg;//显示消息变量
/*
每添加一个界面就要将其界面初始化函数和消息轮询函数添加到数组中
并且添加的界面顺序一定要与.h中的界面号能对应上,因为我们是通过
界面号来访问这些函数
*/
UI_S const Ui[]={{home_init,home_proc},{cmd_init,cmd_proc}};
void displayInit(void){DisMsg.newUi=HOME_UI;DisMsg.oldUi=HOME_UI;cmd_init();DisMsg.pOpenQueue=Key.route[1];DisMsg.pQueue=Key.route[0];
}
/*
函数功能:界面切换函数
参数说明:参数1:
返回值:void
*/
void displayMain(void * pMsg){DisMsg.newUi=Ui[DisMsg.oldUi].proc(&DisMsg);//不断调用对应界面的消息轮询函数if(DisMsg.newUi!=DisMsg.oldUi){//如果返回的界面号不同则切换界面DisMsg.oldUi=DisMsg.newUi;Ui[DisMsg.newUi].init();//调用新界面的初始化函数}
}
HOME界面
/*
函数功能:HOME界面初始化函数
参数说明:参数1:
返回值:void
*/
void home_init(void){setWinCmd(&Dleds,WIN0 ,LED_TEXT,0,0,0,NULL);setWinNum(&Dleds,1);setWinSize(&Dleds,WIN0,0,8);writeLed(&Dleds,WIN0,"%-8s","L-71");
}
/*
函数功能:HOME界面消息轮询函数
参数说明:参数1:
返回值:void
*/
UI_E home_proc(DIS_MSG *pMsg){initMsgQueue(pMsg->pOpenQueue);if(msgQueueNum(pMsg->pQueue)){return CMD_UI;}return HOME_UI;
}
命令界面
1,把用户输入的信息先存储到echo,然后显示
2,对用户输入的命令进行解析
输入#,(判断是1#或者0#)
输入超时,101
输入*,删除一个字符
输入0-9放入echo,再解析

0,中间全局变量
DIS_MSG DisMsg;//按键消息
UINT32 Clk;//时间戳用来判断超时
UINT8 MatchCode;//保存开门键
1,定义命令解析结构体
//命令匹配结构体
typedef struct{UINT8 len;//命令长度INT8 dat[4];UI_E ui;//返回界面号UINT8 match;//匹配码 开门键
}CMD_S;
预设命令
CMD_S const ImmCmd[] =
{{2,"0#",PASSWORD_UI},//0# 进入密码界面{2,"1#",SET_PW_UI},//1# 进入密码设置界面
};
CMD_S const TimeOutCmd[]=
{{3,"101",CALL_UI,VK_ENTER},//命令长度,门牌号,返回界面号,开门键{3,"201",CALL_UI,VK_SHIFT},{3,"102",CALL_UI,VK_CONTROL},{3,"202",CALL_UI,VK_ALT},
};
2,初始化命令界面
/*
函数功能:命令界面初始化函数
参数说明:参数1:
返回值:void*/
void cmd_init(void){initAbuf(&DisMsg.echo);//回显缓冲区初始化setWinCmd(&Dleds,WIN0 ,LED_TEXT,0,0,0,NULL);//普通文本显示setWinNum(&Dleds,1);setWinSize(&Dleds,WIN0,0,8);//设置显存起始位置长度writeLed(&Dleds,WIN0,"%-8s",DisMsg.echo.dat); //输入回显 刚开始为空
}
3,命令消息循环函数
具体流程:
a,检查队列是否有消息,读取消息,对消息按键码进行分类解析。
如果是*表示要删除一个用户输入的字符,回显,界面不切换
如果是#表示进行命令匹配看到底是要进入密码界面还是密码设置界面
如果都不是说明为0-9即用户在输入门牌号
b,超时判断:每次进来都会进行输入超时判断,将用户输入与预设的超时命令
进行匹配,如果匹配正确则保留开门键匹配码,匹配不上则返回ERROR_UI到错误界面。
/*
函数功能:命令循环解析函数
参数说明:参数1:消息
返回值:界面号
*/
UI_E cmd_proc(DIS_MSG *pMsg){MSG_S msg;BOOL status;CMD_S tempCmd;if(msgQueueNum(pMsg->pQueue)){//有消息getMsgQueue(pMsg->pQueue,&msg);Clk=msg.time;switch(msg.code){case '*'://删除一个字符并回显{delAbuf(&pMsg->echo);writeLed(&Dleds,WIN0,"%-8s",DisMsg.echo.dat);return CMD_UI;}case '#':{saveAbuf(&pMsg->echo,msg.code);//保存字符writeLed(&Dleds,WIN0,"%-8s",DisMsg.echo.dat);//回显//匹配命令status=testCmd(&pMsg->echo,&tempCmd,ImmCmd,sizeof(ImmCmd)/sizeof(CMD_S));if(status == TRUE ){return tempCmd.ui;//两种情况密码界面还是设置界面}return ERROR_UI; }default://0-9字符{saveAbuf(&pMsg->echo,msg.code);//保存字符writeLed(&Dleds,WIN0,"%-8s",DisMsg.echo.dat);//回显return CMD_UI;}}}if(timeOut(Clk,2000)==TRUE){//超时判断status=testCmd(&pMsg->echo,&tempCmd,TimeOutCmd,sizeof(TimeOutCmd)/sizeof(CMD_S));if(status == TRUE ){return tempCmd.ui;MatchCode=tempCmd.match;//存储开门键}return ERROR_UI; } return CMD_UI;
}
命令比较函数
/*
函数功能:命令比较函数
参数说明:参数1:用户输入的按键字符信息参数2:返回匹配成功命令参数3:预设命令参数4:命令结构数组长度即包含多少命令
返回值:TRUE:命令匹配成功FALSE:命令匹配失败
*/
//testCmd(&pMsg->echo,&tempCmd,ImmCmd,sizeof(ImmCmd)/sizeof(CMD_S));
BOOL testCmd(ABUF_S *p,CMD_S * pTemp,CMD_S const * pCmd,UINT8 len){UINT8 i;for(i=0;i<len;i++){if(p->idx == pCmd[i].len){//先拿长度过滤一下if(!strcmp(p->dat,pCmd[i].dat)){*pTemp =pCmd[i];//这是匹配成功要返回的命令结构return TRUE;}}}return FALSE;
}
超时判断函数
//TimeStamp是UINT32一个全局时间戳,在systick_handler函数中每1ms自增
BOOL timeOut(UINT32 clk,UINT32 delayTime)
{if(TimeStamp - clk >= delayTime){return TRUE;}else{return FALSE;}
}
TimeStamp从0不断自增到0xffffffff,当超出最大值又从0开始增加,当cmd_proc中记录的clk大于TimeStamp时,TimeStamp-clk的结果还是正确的。

以下是ABUF_S 相关函数的实现。
void initAbuf(ABUF_S *pBuf){UINT8 i;pBuf->idx=0x00;for(i=0;i<ABUF_LEN;i++){pBuf->dat[i]=0;}
}
void saveAbuf(ABUF_S *pBuf,INT8 dat){if(pBuf->idx<=ABUF_LEN){pBuf->dat[pBuf->idx]=dat;pBuf->idx++;}
}
BOOL isABufFull(ABUF_S *pBuf){return (pBuf->idx <=ABUF_LEN)? TRUE:FALSE;
}UINT8 AbufCnt(ABUF_S *pBuf){return pBuf->idx;
}
void delAbuf(ABUF_S *pBuf){if(pBuf->idx){pBuf->idx--;pBuf->dat[pBuf->idx]=0x00;}
}
BOOL abufCmp(ABUF_S *p1, ABUF_S *p2){return (p1->idx==p2->idx && !strcmp(p1->dat,p2->dat))?TRUE:FALSE;}
