嵌入式软件架构--显示界面2(呼叫界面,密码界面)
开始
上一篇文章主要介绍了显示界面架构以及整个的命令流转过程并定义了HOME界面和密码界面,今天学习剩余6个的其他界面。下一篇文章进行整体的调试。
呼叫界面
在命令界面用户输入门牌号匹配成功后会进入呼叫界面
呼叫界面的设计
显存共8个字节,分成两个窗口显示,前6个字节显示用户输入门牌号,后两个字节显示倒计时。
执行流程
检测用户输入则立即切换回命令界面,检测到倒计时结束则切换回HOME界面,如果用户输入的匹配码能匹配上预设键就切换到开门界面。
101房间匹配按键是ENTER
102房间匹配按键是CONTROL
201房间匹配按键是SHIFT
202房间匹配按键是ALT
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},
};初始化
p->route[KEY_CHAR_EVENT]=&Queue0;//将12个字符按键消息路由到QUEUE0
p->route[KEY_DOWN_EVENT]=&Queue1;//将4个控制按键消息路由到QUEUE1
void call_init(void){initMsgQueue(DisMsg.pOpenQueue);//先清空//两个窗口setWinNum(&Dleds,2);//窗口0 文本显示 长度6,setWinCmd(&Dleds,WIN0,LED_TEXT,0,0,0,NULL);setWinSize(&Dleds,WIN0,0,6);writeLed(&Dleds,WIN0,"%-6s",DisMsg.echo.dat);//窗口1 倒计时15秒setWinCmd(&Dleds,WIN0,LED_DEC,0,10,15,NULL);setWinSize(&Dleds,WIN0,6,2);}消息循环函数
UI_E call_proc(DIS_MSG * pMsg){MSG_S msg;if(msgQueueNum(pMsg->pQueue)){return CMD_UI;}if(ledDecOver(&Dleds,WIN1)==TRUE){//倒计时结束返回到HOMEreturn HOME_UI;}if(msgQueueNum(pMsg->pOpenQueue)){getMsgQueue(pMsg->pOpenQueue,&msg);if(msg.code == MatchCode){//按键码匹配成功进入开门界面return OPEN_UI;}}return CALL_UI;
}
OPEN,SUCCED,ERROR界面
这三个界面很相似,开门界面和SUCCED界面就是字符移动显示一圈,结束返回到HOME界面如果中途有按键按下则进入命令界面。ERROR界面单纯闪烁3次,结束返回到HOME界面如果中途有按键按下则进入命令界面。
void open_init(void)
{Ledinit(&Dleds);setWinCmd(&Dleds,WIN0,LED_MOVE,0,5,4,MoveOv);setWinSize(&Dleds,WIN0,0,8);writeLed(&Dleds,WIN0,"%-8s","open");setWinNum(&Dleds,1);MoveOv[0] = 0x00;MoveOv[1] = 0x00;
}UI_E open_proc(DIS_MSG *pMsg)
{if(msgQueueNum(pMsg->pQueue)){return CMD_UI;}if(MoveOv[1]){return HOME_UI;}return OPEN_UI;
}void error_init(void)
{setWinCmd(&Dleds,WIN0,LED_FLASH_N,0,5,3,NULL);setWinSize(&Dleds,WIN0,0,8);writeLed(&Dleds,WIN0,"%-8s"," Error");setWinNum(&Dleds,1);
}UI_E error_proc(DIS_MSG *pMsg)
{if(msgQueueNum(pMsg->pQueue)){return CMD_UI;}if(FlashNOver(&Dleds,WIN0) == TRUE){return HOME_UI;} return ERROR_UI;
}void succeed_init(void)
{Ledinit(&Dleds);setWinCmd(&Dleds,WIN0,LED_MOVE,0,5,7,MoveOv);setWinSize(&Dleds,WIN0,0,8);writeLed(&Dleds,WIN0,"%-8s","succeed");setWinNum(&Dleds,1);MoveOv[0] = 0x00;MoveOv[1] = 0x00;
}UI_E succeed_proc(DIS_MSG *pMsg)
{if(msgQueueNum(pMsg->pQueue)){return CMD_UI;}if(MoveOv[1]){return HOME_UI;}return SUCCEED_UI;
}这里有必要回顾一下移动显示的原理:
其实就是将显存LED_S->Buf与窗口缓冲区LED_WIN_S->dat首位相互连接起来,这是一种类似于SPI通信的模式,显存头部移出一个字符到缓冲区的末尾,显存中剩余字符向前移动一位,空出的最后一位由缓冲区的第一个字符填充,再将缓冲区中的剩余字符向前移动一位。
需要注意的是使用LED_MOVE命令式setWinCmd函数的参数需要注意一下。
例如:setWinCmd(&Dleds,WIN0,LED_MOVE,0,5,4,MoveOv);
参数1:窗口数组指针
餐数2:第几个窗口
参数3:命令
参数4:计数器初始值一般为0
参数5:计数器重载值,1代表100ms,因为后台入口函数ledMain在任务组taskGroup2每100ms调用一次
参数6:属性attr在不同的函数中功能不同,这里代表的是需要移动的字符个数即窗口缓冲区的字符个数
参数7:MoveOv是一个UINT8数组,MoveO[0]用来记录移动次数每移动一次自增一次,当移动次数等于本窗口宽度LED_WIN_S->len与attr(需要移动字符个数)之和时MoveO[1]被置1表示移动一屏结束。
使用移动函数ledMove时必须指定LED_S->LED_EIN_S->p即setWinCmd的最后一个参数不然就是访问野指针。
总结一下attr的用法
ledMove中:表示需要移动的字符个数
ledDec中:表示倒计时开始的数字
disLedFlashN:表示闪烁次数
disLedGif:表示当前显示帧编号
void ledMove(LED_S *pLed,UINT8 winNum)
{UINT8 i;UINT8 temp;UINT8 *p = (UINT8 *)pLed->pWin[winNum].p;UINT8 winLen = pLed->pWin[winNum].len;if(ledDelay(pLed,winNum)==TRUE){temp = pLed->Buf[pLed->pWin[winNum].start];for(i=0;i<winLen-1;i++){pLed->Buf[pLed->pWin[winNum].start+i] = pLed->Buf[pLed->pWin[winNum].start+i+1];}pLed->Buf[pLed->pWin[winNum].len-1] = pLed->pWin[winNum].dat[0];for(i=0;i<pLed->pWin[winNum].attr-1;i++){pLed->pWin[winNum].dat[i] = pLed->pWin[winNum].dat[i+1];}pLed->pWin[winNum].dat[pLed->pWin[winNum].attr-1] = temp;p[0]++; if(p[0] == pLed->pWin[winNum].attr + winLen)//判断移动一屏结束{p[0] = 0x00;p[1] = 0x01;}}
}PASSWORD界面
1,2,后台密码显示函数实现:

功能:用户的输入存储在echo中,将echo直接给LED_WIN_S->p。将用户输入的密码显示为'-',最后一位输入的密码正常显示1秒后更改为'-'.
/*
函数功能:密码显示
参数说明:参数1:窗口数组指针参数2:第几个窗口返回值:void
*/
void disLedPassword(LED_S *pLed,UINT8 winNum)
{UINT8 len,i;ABUF_S *pBuf;if(pLed->pWin[winNum].attr & LED_FLUSH){//attr最高位表示是否要刷新显示pBuf=(ABUF_S *)pLed->pWin[winNum].p;//用户的输入会存到回显echo中,拷贝给p//用户输入密码的长度不能超过窗口长度len=(pBuf->idx <= pLed->pWin[winNum].len) ? pBuf->idx:pLed->pWin[winNum].len;if(!len){//len=0用户还没用输入密码则显示为空格for(i=0;i<len;i++){pLed->Buf[pLed->pWin[winNum].start+i]=LedChars[toIdx(' ')];}}else{for(i=0;i<len-1;i++){//最后一位不显示'-' 其他位显示'-'pLed->Buf[pLed->pWin[winNum].start+i]=LedChars[toIdx('-')];}//最后一位正常显示pLed->Buf[pLed->pWin[winNum].start+i]=pLed->pWin[winNum].dat[i];i++;//密码每填满剩余数码管显示空格for(;i<pLed->pWin[winNum].len;i++){pLed->Buf[pLed->pWin[winNum].start+i]=LedChars[toIdx(' ')];} } }//定时时间到将最后一位密码显示为'-'if(ledDelay(pLed,winNum)==TRUE){pLed->Buf[pLed->pWin[winNum].start+len-1]=LedChars[toIdx('-')];pLed->pWin[winNum].attr &=~LED_FLUSH;//清零刷新标志位}}这里增加了显示刷新函数 手动调用刷新界面
//刷新标志位置1函数 attr最高位
void flushLed(LED_S *pLed,UINT8 winNum)
{pLed->pWin[winNum].attr |= LED_FLUSH;
},2,密码界面初始化
密码界面初始化为两个窗口,第一个窗口长度为2显示“0P”(数码管无法显示“#”用P代替),第二个窗口长度为6,1秒后将最后一位改成‘-’。
void password_init(void){initAbuf(&DisMsg.echo);//回显要存储用户输入先清零setWinCmd(&Dleds,WIN0,LED_TEXT,0,0,0,NULL);setWinSize(&Dleds,WIN0,0,2);writeLed(&Dleds,WIN0,"%-2s","0P");setWinCmd(&Dleds,WIN1,LED_PASSWORD,10,10,LED_FLUSH,&DisMsg.echo);setWinSize(&Dleds,WIN1,2,6);setWinNum(&Dleds,2);
}3,消息循环函数
*:删除一个字符刷新并回到密码界面
#:比较密码就是比较回显中的密码与预设密码PW是否相等。匹配成功进入开门界面,否则显示ERROR返回到HOME。
字符0-9:保存输入,当长度大于预设密码长度时直接返回ERROR
超时判断:2秒没动作就返回ERROR
UI_E password_proc(DIS_MSG *pMsg){MSG_S msg;if(msgQueueNum(pMsg->pQueue)){getMsgQueue(pMsg->pQueue,&msg);Clk=msg.time;//记录时间用于超时判断switch(msg.code){case '*':{delAbuf(&pMsg->echo);//删除一个字符flushLed(&Dleds,WIN1);//刷新显示return PASSWORD_UI;} case '#':{ //比较输入密码和预设密码return (abufCmp(&pw,&pMsg->echo)==TRUE )? OPEN_UI:ERROR_UI;}default://输入0-9 存入回显 {saveAbuf(&pMsg->echo,msg.code);//保存输入的字符if(AbufCnt(&pMsg->echo)>pw.idx){//判断输入的密码长度大于密码长度直接返回。return ERROR_UI;}flushLed(&Dleds,WIN1);return PASSWORD_UI;} }}if(timeOut(Clk,2000)){//超时判断return ERROR_UI;}return PASSWORD_UI;}密码设置界面
1,初始化密码设置界面
与密码输入界面一致,密码设置需要输入密码两次,SetPwTimes记录输入次数
void setPw_init(void)
{initAbuf(&DisMsg.echo);setWinCmd(&Dleds,WIN0,LED_TEXT,0,0,0,NULL);setWinSize(&Dleds,WIN0,0,2);writeLed(&Dleds,WIN0,"%-2s","0P");setWinCmd(&Dleds,WIN1,LED_PASSWORD,10,10,LED_FLUSH,&DisMsg.echo);setWinSize(&Dleds,WIN1,2,6);setWinNum(&Dleds,2);SetPwTimes = 0;
}2,消息循环函数
静态变量temp保存用户第一次输入的密码,两次输入一致表示设置成功
UI_E setPw_proc(DIS_MSG *pMsg)
{MSG_S msg;static ABUF_S temp;BOOL result;if(msgQueueNum(pMsg->pQueue)){getMsgQueue(pMsg->pQueue,&msg);Clk = msg.time;switch(msg.code){case '*':{delAbuf(&pMsg->echo);flushLed(&Dleds,WIN1);return SET_PW_UI; }case '#':{if(!SetPwTimes){SetPwTimes = 0x01;temp = pMsg->echo;//保存第一次输入的密码initAbuf(&DisMsg.echo);//清空echo,为下次输入做准备flushLed(&Dleds,WIN1);//刷新return SET_PW_UI;}else{result = abufCmp(&DisMsg.echo,&temp);//比较两次输入是否一致if(result == TRUE){pw = temp;return SUCCEED_UI;}else{return ERROR_UI;}}}default:{saveAbuf(&pMsg->echo,msg.code);if(AbufCnt(&pMsg->echo) > pw.idx){return ERROR_UI;}flushLed(&Dleds,WIN1);return SET_PW_UI; }}}if(timeOut(Clk,2000)){return ERROR_UI;}return SET_PW_UI;
}总结
回顾一下整体的流程,开机进入HOME界面,有按键输入进入CMD界面,判断输入类型,*号为删除一个字符,0-9表示要输入门牌号,1#表示设置密码界面,0#表示密码开门界面。每一个界面都对应一个初始化函数和消息轮询函数,消息轮询函数返回界面号决定是否切换界面,初始化函数调用一次。displayMain函数是前台软件的入口函数100ms调用一次。ledMain是后台软件的入口,keyMain是按键检查的入口。按键路由消息到两个队列,ABUF缓存用户输入,前台任务负责人机交互,后台任务负责底层显示逻辑,驱动函数3ms一次刷新数码管。
开门方式有两种,密码开门和呼叫开门分别对应PASSWORD和CALL。
CMD界面是枢纽,CALL呼叫界面将用户输入按键码与预设的按键码进行比较(四个房间使用四个按键开门)决定是否进入开门界面。SUCCED,ERROR,OPEN这些状态显示界面只是进行一些提示。
这是显示界面的前台软件最后一章,下一步就可以开始整体调试。
