嵌入式软件架构--按键消息队列3(测试)
开始
静态数码管与矩阵按键的联合测试
单击,双击,长按
要达到的效果是16个按键单击双击长按都能在数码管上显示相应的位置码0-F.
这里使用的是一位的共阳数码管只需要关心段选拉低就行。
驱动
//驱动函数给1位的共阳静态数码管,所以不用管位选
//PB8-PB15
void led_write(UINT8 dat)
{LED_PORT->ODR = (dat<<8);
}
显示函数
test函数20ms调用一次
/*
函数功能:按键单击双击长按的测试
参数说明:参数1:按键单击双击长按标志位(keyF,keyDF,keyLF)
返回值:void
*/
void test(void *pMsg){UINT8 i;KEY_TYPE keyF=(*(KEY_TYPE *)pMsg);for(i=0;i<KEY_NUM;i++){if(keyF&(1<<i)){led_write(LedChars[i]);}}(*(KEY_TYPE *)pMsg)=0x00;
}
这里是任务列表
//10ms
const TASK_S taskGroup0[]=
{{keyMain,&Key},{NULL,NULL},
};
//20ms
const TASK_S taskGroup1[]=
{{test,&Key.keyLF},//这里切换检测模式单击双击长按{NULL,NULL},
};
任务参数可以是以下标志位:单击Key.keyF,双击Key.keyDF,长按Key.keyLF
然后在keyMain中 不断调用对应的检测函数
void keyMain(void *pMsg)
{KEY_S *p = pMsg;keyScan(p,p->pf());
#if(D_CLICK_EN)keyDScan(p);
#endifkeyLScan(p);
}
组合键
从keyMain函数开始分析整体的流程很简单,按键按下产生按键标志位,检查标志位把按键事件进行分类包装成消息,消息写入按键对象的消息队列,添加一个20毫秒的消息解析函数,解析消息。
1,检测标志位
void keyMain(void *pMsg)
{KEY_S *p = pMsg;keyScan(p,p->pf());
#if(D_CLICK_EN)keyDScan(p);
#endifkeyLScan(p);//keyF 按键单击标志位if(p->keyF){//按键单击事件creatKeyMsg(p,&p->keyF,KEY_CHAR_EVENT);}if(p->keyDF){//按键双击事件creatKeyMsg(p,&p->keyDF,KEY_D_EVENT);}if(p->keyLF){//按键长按事件creatKeyMsg(p,&p->keyLF,KEY_L_EVENT);}
}
2,包装消息写入队列
右侧映射到左侧,Alt,Shift,Ctrl,Enter四个按键是控制按键
UINT8 const KeyMap[16] =
{'1', '2', '3', VK_ENTER,'4', '5', '6', VK_CONTROL,'7', '8', '9', VK_SHIFT,'*', '0', '#', VK_ALT
};
//按键码高效获取
UINT8 getKeyNum(KEY_TYPE *pKey)
{UINT8 y,x,num;KEY_TYPE key = *pKey;x = (key & 0xff)? 0 : 1;y = pro[(UINT8)((key) >> (x << 3))];num = y + x*8;*pKey &= ~(1 << num);//注意打开return num;
}
/*
函数功能:生成按键消息
参数说明:参数1:按键对象餐数2:按键码参数3:按键事件
返回值:void*/
void creatKeyMsg(KEY_S* p,KEY_TYPE *pKeyF,UINT8 event){MSG_S msg;//pKeyF每一位都表示一个按键是否按下,为了应对多个按键一起按下需要循环检测while(*pKeyF && (isMsgQueueFull(&p->msgQueue)==FALSE)){//获取按键码在进行映射ASCIImsg.code=KeyMap[getKeyNum(pKeyF)];//记录按键按下时间msg.time=TimeStamp;//判断按键事件类型,双击长按if(event == KEY_D_EVENT || event ==KEY_L_EVENT){msg.event=event;}else{if((msg.code>=32)&&(msg.code<=126)){msg.event=KEY_CHAR_EVENT;//字符事件}else{msg.event=KEY_DOWN_EVENT;//控制事件} }saveMsgQueue(&p->msgQueue,&msg); }
}
3,解析消息并显示
/*
函数功能:组合键测试
参数说明:参数1:按键对象指针返回值:void
*/
void combinationKey(void *pMsg){KEY_S * p=(KEY_S *)pMsg;MSG_S msg;if(msgQueueNum(&p->msgQueue)){//有消息getMsgQueue(&p->msgQueue,&msg);if(msg.event ==KEY_CHAR_EVENT){if(p->keyDown &KEY_CONTROL){//ctrl键按下 1显示a,2显示b,3显示c//这里需要对输入做一下限定,因为还有* #键if(msg.code >='0'&& msg.code<='9'){//LedChars偏移led_write(LedChars[msg.code-'0'+9]);}}else{//ctrl键未按下正常显示1234567890 * #不检测组合if(msg.code >='0'&& msg.code<='9'){led_write(LedChars[msg.code-'0']);}} }}
}
4,将消息解析函数添加到任务组
//20ms
const TASK_S taskGroup1[]=
{//{test,&Key.keyLF},{combinationKey,&Key},// {producer,&P2},{NULL,NULL},
};
5,最终效果
Ctrl+123456789分别显示’a’‘b’‘c’‘d’…
按键转义
按键转义是在组合键的升级,加入了shift,alt两个键。
1,定义映射表相关宏定义
#define VK_ENTER 0x0d
#define VK_CONTROL 0x11
#define VK_SHIFT 0x10
#define VK_ALT 0x12
#define VK_UP 24
#define VK_DOWN 25
#define VK_LEFT 27
#define VK_RIGHT 26#define KEY_ENTER 0x08
#define KEY_CONTROL 0x80
#define KEY_SHIFT 0x0800
#define KEY_ALT 0x8000
UINT8 const ConbCtrl[16] =
{'a', 'b', 'c', VK_ENTER,'d', 'e', 'f', VK_CONTROL,'g', 'h', 'i', VK_SHIFT,'j', 'k', 'l', VK_ALT
};
UINT8 const ConbShift[16] =
{'m', 'n', 'o', VK_ENTER,'p', 'q', 'r', VK_CONTROL,'s', 't', 'u', VK_SHIFT,'v', 'w', 'x', VK_ALT
};
UINT8 const ConbAlt[16] =
{'y', 'x', ',', VK_ENTER,'.', '?', '!', VK_CONTROL,'+', '-', VK_UP, VK_SHIFT,VK_DOWN, VK_LEFT, VK_RIGHT, VK_ALT
};
2,keyMain函数没有修改正常检测三种标志位并调用消息生成函数
3,消息生成函数加入shift,alt的长按功能
/*
函数功能:生成按键消息(按键转义shift,ctrl,alt)
参数说明:参数1:按键对象餐数2:按键码参数3:按键事件
返回值:void
*/void creatKeyMsg(KEY_S * p,KEY_TYPE *pKeyF,UINT8 event){MSG_S msg;UINT8 num;//有按键按下并且消息队列有空间存储//这里每次只产生一个消息并清零标志位if(*pKeyF && (isMsgQueueFull(&p->msgQueue)) ==FALSE){num=getKeyNum(pKeyF);//获取物理按键码msg.code=KeyMap[num];//物理按键码映射到ASCIImsg.time=TimeStamp;//双击长按事件if(event ==KEY_D_EVENT || event == KEY_L_EVENT){msg.event= event;}else{//先把enter ,ctrl,shift,alt四个按键刨除if(num!=0x07 && num !=0x0b && num !=0x0f && num !=0x03){switch(p->keyDown & 0x8880){case KEY_CONTROL://ctrlmsg.code=ConbCtrl[num];break;case KEY_SHIFT://SHIFTmsg.code=ConbShift[num];break;case KEY_ALT://ALTmsg.code=ConbAlt[num];break;default :break; }if(msg.code >=32 && msg.code <=126){msg.event =KEY_CHAR_EVENT;}else{msg.event =KEY_DOWN_EVENT;} } }saveMsgQueue(&p->msgQueue,&msg);*pKeyF &= ~(1 << num);//清零对应标志位 }}
4,消息解析显示
void combinationKey(void *pMsg){KEY_S * p=(KEY_S *)pMsg;MSG_S msg;if(msgQueueNum(&p->msgQueue)){//有消息getMsgQueue(&p->msgQueue,&msg);if(msg.event ==KEY_CHAR_EVENT){if(p->keyDown &KEY_CONTROL || p->keyDown & KEY_SHIFT || p->keyDown & KEY_ALT){//ctrl键按下 1显示a,2显示b,3显示cled_write(LedChars[toIdx(msg.code)]);}else{//ctrl键未按下正常显示123。。。if(msg.code >='0'&& msg.code<='9'){led_write(LedChars[msg.code-'0']);}} }}
}
命令式编程与动态数码管的测试
动态数码管的驱动
//段选
#define LED_PORT GPIOB
#define LED_PIN (GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15)
#define LED_PORT_RCC RCC_APB2Periph_GPIOB
//片选
#define LED_CHIP_PORT GPIOB
#define LED_CHIP_PIN (GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_CHIP_PORT_RCC RCC_APB2Periph_GPIOB//驱动
void ledDrv(UINT8 dat,UINT8 chip)
{UINT16 temp;GPIOB->BRR = 0xff00;//所有段选拉低,因为是共阴数码管,消影temp = GPIOB->ODR;//读取输出寄存器值,防止修改低八位temp &= 0x001f;//PB5-PB15清零temp |= chip << 5;//写入位选值temp |= (~dat) << 8;//字模是共阳的所以需要取反GPIOB->ODR = temp; //写入输出寄存器。
}
各种命令的测试
//================转换命令===========================
#define LED_TEXT 0X00 //普通文本显示
#define LED_FLASH 0x01 //闪烁显示
#define LED_FLASH_N 0x02 //多次闪烁
#define LED_DEC 0x03 //倒计时
#define LED_MOVE 0x04 //滚动
#define LED_PIC 0x05 //图片显示
#define LED_PASSWORD 0x06 //显示**
流程说明:
后台软件入口ledMain每100ms调用一次,ledMain调用ledcmd,ledcmd在根据命令类型调用对应的函数
void ledMain(void *pMsg){UINT8 i;LED_S *pLed=(LED_S *)pMsg;for(i=0;i<pLed->cnt;i++){ledCmd(pLed,i);}
}
void ledCmd(LED_S *pLed,UINT8 winNum)
{switch(pLed->pWin[winNum].cmd){case LED_FLASH:{disLedFlash(pLed,winNum);break;}case LED_FLASH_N:{disLedFlashN(pLed,winNum);break;}case LED_DEC:{ledDec(pLed,winNum);break;}case LED_MOVE:{ledMove(pLed,winNum);break;}case LED_PIC:{disLedGif(pLed,winNum);break;}case LED_PASSWORD:{disLedPassword(pLed,winNum);break;}default:{disLedText(pLed,winNum);break;}}
}
图片显示测试步骤:
1,添加任务到任务列表
//后台软件100ms调用一次
const TASK_S taskGroup2[]=
{//{consumer,&C1},{ledMain,&Dleds},{NULL,NULL},
};
//驱动函数3ms调用一次
const TASK_S taskGroup3[]=
{//{consumer,&C2},{updataLed,&Dleds},{NULL,NULL},
};const TASK_GROUP_S TaskList[]=
{{10, taskGroup0},{20,taskGroup1},{100,taskGroup2},{3,taskGroup3},
};
2,设置窗口
setWinNum(&Dleds,1);//只有一个窗口
setWinSize(&Dleds,0,0,8);//对窗口0设置显存初始地址为0,长度为8
setWinCmd(&Dleds,0,LED_PIC,0,1,0,(void*)MyGif);//图片显示,cnt=0,reload=1,attr=0;
仿真
1,闪烁显示仿真
int main(void)
{bspInit();initKey(&Key,50,200,arrayKeyCode);SysTick_Init(72);initQueue(&Queue);initMultiTask();setWinNum(&Dleds,1);//只有一个窗口setWinSize(&Dleds,0,0,8);//对窗口0设置显存初始地址为0,长度为8setWinCmd(&Dleds,0,LED_FLASH_N,0,1,3,NULL);//闪烁显示3次,reload 1秒,writeLed(&Dleds,0,"%s","123");while (1){ledCmd(&Dleds,0);//task_exec();}
}
可以看到显存中前三位交替变化三次
2,倒计时显示仿真
setWinNum(&Dleds,1);//只有一个窗口setWinSize(&Dleds,0,0,2);//对窗口0设置显存初始地址为0,长度为2,即使用两位数码管,显存起始地址的对应每一位数码管setWinCmd(&Dleds,0,LED_DEC,0,1,3,NULL);//倒计时显示,cnt=0,reload=1,attr=3即从3开始倒计时每秒一次递减
void ledDec(LED_S *pLed,UINT8 winNum){if(pLed->pWin[winNum].attr !=0xfe){//结束条件if(ledDelay(pLed,winNum)==TRUE){//延迟时间到UINT8 fmt[4]="% d";fmt[1]=pLed->pWin[winNum].len +'0';if(pLed->pWin[winNum].attr ==0xff || pLed->pWin[winNum].attr ==0xfe){writeLed(pLed,winNum,(const char *)fmt,0);} else{writeLed(pLed,winNum,(const char *)fmt,pLed->pWin[winNum].attr);}disLedText(pLed,winNum);pLed->pWin[winNum].attr--;} }}
buf[1]依次写入0x0d,0x25,0x9f,0x03,最终结束后attr值为0xfe
3,移动显示仿真
setWinNum(&Dleds,1);//只有一个窗口setWinSize(&Dleds,0,0,8);//对窗口0设置显存初始地址为0,长度为8setWinCmd(&Dleds,0,LED_MOVE,0,1,3,NULL);//3表示移动字符有三个writeLed(&Dleds,0,"%s","123");