当前位置: 首页 > news >正文

嵌入式软件架构--按键消息队列2(组合键,按键转义与三种消息模式)

回顾

复习上一节内容。
在这里插入图片描述
通过在多任务调度中每10ms调用keyMain函数检测16位的按键标志keyF(单击),keyDF(双击标志),keyLF(长按)得到按键物理按键码(跟接线有关),getKeyNum函数通过16位按键标志位得到按键位置码,将按键位置码通过表格KeyMap映射为虚拟按键码。将按键的虚拟按键码与时间戳,按键事件类型包装成事件写入到消息队列中。

UINT8 const KeyMap[16] = 
{'1', '2', '3', VK_ENTER,'4', '5', '6', VK_CONTROL,'7', '8', '9', VK_SHIFT,'*', '0', '#', VK_ALT
};

组合键

组合键实现的功能是Ctrl+‘0’产生字符‘A’,Ctrl+‘1’产生字符‘B’,以此类推。

实现步骤

1,在key.h中添加四个宏定义,四个控制按键的标志位置

//四个控制按键的位置码
#define KEY_ENTER     0x08
#define KEY_CONTROL   0x80
#define KEY_SHIFT     0x0800
#define KEY_ALT       0x8000

2,在led.c中添加函数并声明

//这里使用单个静态数码管进行测试
void combinationKey(void *pMsg)//<= KEY_S
{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按下,并且字符是0-9{if(msg.code >= '0' && msg.code <= '9'){led_write(LedChars[msg.code - '0' + 10]);// 前十个字符是‘0’-‘9’}}else{if(msg.code >= '0' && msg.code <= '9'){led_write(LedChars[msg.code - '0']); 						}						}}}
}

3,将combinationKey函数放到任务列表中,20ms调用一次
4,在KerMain中启用keyScan,keyDScan,keyLScab函数扫描按键。
5,开启时间片任务task_exec();

按键转义

分别是使用Ctrl,Shift,Alt三个键配合左侧12个按键产生其他字符。映射表如下

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   
};

消息生成函数

void creatKeyMsg(KEY_S *p,KEY_TYPE *pKeyF,UINT8 event)
{MSG_S msg;UINT8 num;if(*pKeyF && (isMsgQueueFull(&p->msgQueue) == FALSE))//这里是每10ms产生一次消息,并不是使用while将所有标志位全部转换为消息{num = getKeyNum(pKeyF);msg.code = KeyMap[num];//ASCII 码msg.time = TimeStamp;if(event == KEY_D_EVENT || event == KEY_L_EVENT)//双击长按不做转义{msg.event = event;}else{if(num != 0x07 && num != 0x0b && num != 0x0f && num != 0x03)//排除Enter,Ctrl,Shift,Alt;{switch(p->keyDown & 0x8880)//这里检测的是持续按下状态,Ctrl,Shift,Alt三个中是否有键按下{case KEY_CONTROL:{msg.code = ConbCtrl[num];break;}case KEY_SHIFT:{msg.code = ConbShift[num];break;								    }case KEY_ALT:{msg.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);//这句原来在getKeyNum,清零标志位。}
}

生产者消费者模型的三种模式

路由模式

生产者产生各种不同类型的消息(这里指四种按键消息类型)
消费者有多个,不同的消费者各自只需要某一种类型的消息,这时就需要将不同类型的消息路由到不同的队列中,简单说就是将产生的消息进行分类发送到不同的队列

实现步骤:
1, key.c创建两个消息队列。
2,在按键结构体中增加路由表数组,因为按键事件有四种类型,这里数组的容量为4。

typedef struct
{KEY_TYPE  keyS2;KEY_TYPE  keyS1;KEY_TYPE  keyFall;KEY_TYPE  keyF;KEY_TYPE  keyDown;
#if(D_CLICK_EN)KEY_TYPE  keyDF;UINT8     keyDCnt[KEY_NUM];UINT8     keyDTime;
#endifKEY_TYPE  keyLF;UINT8     keyLCnt[KEY_NUM];UINT8     keyLTime;PFUN_KEY_READ  pf;//===============消息队列开始==============================MSG_QUEUE_S msgQueue;
//===============消息队列结束==============================//===============路由模式开始==============================MSG_QUEUE_S * route[4];
//===============路由模式结束==============================	}KEY_S;

3,初始化路由表,使其指向对应的队列。

void initKey(KEY_S *p,
#if(D_CLICK_EN)	
UINT8 dTime,
#endif
UINT8 lTime,PFUN_KEY_READ pf)
{UINT8 i;p->keyDown = 0x00;p->keyF = 0x00;p->keyFall = 0x00;p->keyS1 = ~0x00;p->keyS2 = ~0x00;
#if(D_CLICK_EN)p->keyDF = 0x00;p->keyDTime = dTime;
#endifp->keyLF = 0x00;p->keyLTime = lTime;for(i=0;i<KEY_NUM;i++){
#if(D_CLICK_EN)p->keyDCnt[i] = ~0x00;
#endifp->keyLCnt[i] = ~0x00;}p->pf = pf;
//====================消息队列初始化===================================//initMsgQueue(&p->msgQueue);//====================消息队列初始化结束===============================//====================路由模式初始化===================================initMsgQueue(&Queue0);initMsgQueue(&Queue1);p->route[0]=&Queue0;p->route[1]=&Queue0;p->route[2]=&Queue1;p->route[3]=&Queue1;//====================路由模式初始化结束================================
}

4,产生消息并路由

void createRouteMsg(KEY_S * p,KEY_TYPE *pKeyF,UINT8 event){MSG_S msg;UINT8 num;MSG_QUEUE_S *pQ;if(*pKeyF){//一次产生一个消息num=getKeyNum(pKeyF);msg.code=KeyMap[num];//ASCII 码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;}	}pQ=p->route[msg.event];if(isMsgQueueFull(pQ)==FALSE){saveMsgQueue(pQ,&msg);*pKeyF&=~(1<<num);//清零按键标志位}		}
}

5,将createRouteMsg函数放到kMain函数中测试。

void keyMain(void *pMsg)
{KEY_S *p = pMsg;//keyScan(p,p->pf());
#if(D_CLICK_EN)//keyDScan(p);
#endif//keyLScan(p);//keyF 按键单击标志位if(p->keyF){//按键单击事件createRouteMsg(p,&p->keyF,KEY_CHAR_EVENT);}if(p->keyDF){//按键双击事件createRouteMsg(p,&p->keyDF,KEY_D_EVENT);}	if(p->keyLF){//按键长按事件createRouteMsg(p,&p->keyLF,KEY_L_EVENT);}
}

在这里插入图片描述
可以看到Queue0中有三个消息,两个字符消息分别为’2’,‘3’,还有一个控制消息Enter.在这里插入图片描述
Queue1中有6个消息, 分别是字符消息123的长按和短按。

在这里插入图片描述

订阅模式

多个消费者需要生产者的所有消息。
生产者创建一个订阅数组,存放消费者订阅的消息队列的指针
如果说路由模式是将不同的消息类型存入到不同的队列中,那么订阅模式就是将所有的消息给所有队列都发一遍。

有个问题,不同的消费者消费消息的速度不同可能会产生消费慢的队列存不进消息的情况,这里有几种解决办法:
1,加大消息队列的大小
2,提高消费速度,即放在更短的时间片内
3,使用链表实现队列,动态增长。

实现步骤
0,msgQueue.h添加一个宏定义,作为订阅数组的尾标识

#define MSG_NULL         (MSG_QUEUE_S *)0

1,在按键结构中添加订阅数组

typedef struct
{KEY_TYPE  keyS2;KEY_TYPE  keyS1;KEY_TYPE  keyFall;KEY_TYPE  keyF;KEY_TYPE  keyDown;
#if(D_CLICK_EN)KEY_TYPE  keyDF;UINT8     keyDCnt[KEY_NUM];UINT8     keyDTime;
#endifKEY_TYPE  keyLF;UINT8     keyLCnt[KEY_NUM];UINT8     keyLTime;PFUN_KEY_READ  pf;//===============消息队列开始==============================MSG_QUEUE_S msgQueue;
//===============消息队列结束==============================//===============路由模式开始==============================MSG_QUEUE_S * route[4];
//===============路由模式结束==============================	//===============订阅模式开始==============================MSG_QUEUE_S * Subscribe[4];
//===============订阅模式结束==============================	}KEY_S;

2,initKey中初始化订阅数组

//====================订阅模式初始化===================================initMsgQueue(&Queue0);initMsgQueue(&Queue1);p->Subscribe[0]=&Queue0;p->Subscribe[1]=&Queue1;p->Subscribe[2]=MSG_NULL;p->Subscribe[3]=MSG_NULL;//====================订阅模式初始化结束================================

3,生成消息并存入所有队列

void creatSubscribeMsg(KEY_S * p,KEY_TYPE *pKeyF,UINT8 event){MSG_S msg;UINT8 num,i,len;MSG_QUEUE_S *pQ;if(*pKeyF){//一次产生一个消息num=getKeyNum(pKeyF);msg.code=KeyMap[num];//ASCII 码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;}}len=sizeof(p->Subscribe)/sizeof(MSG_QUEUE_S*);for(i=0;i<len;i++){//或者使用while(p->Subscribe[i]!=NULL)pQ=p->Subscribe[i];if(pQ!=NULL&&isMsgQueueFull(pQ)==FALSE){saveMsgQueue(pQ,&msg);		}		}		*pKeyF&=~(1<<num);//清零按键标志位}
}

4,测试

void keyMain(void *pMsg)
{KEY_S *p = pMsg;//keyScan(p,p->pf());
#if(D_CLICK_EN)//keyDScan(p);
#endif//keyLScan(p);//keyF 按键单击标志位if(p->keyF){//按键单击事件creatSubscribeMsg(p,&p->keyF,KEY_CHAR_EVENT);}if(p->keyDF){//按键双击事件creatSubscribeMsg(p,&p->keyDF,KEY_D_EVENT);}if(p->keyLF){//按键长按事件creatSubscribeMsg(p,&p->keyLF,KEY_L_EVENT);}
}

在这里插入图片描述
在这里插入图片描述
可以看到Queue0,Queue1中的消息是一样的。

主题模式

路由模式是将需要类型的消息存入到队列,订阅模式则是一股脑全部存进去,主题模式是一种折中的方案选择性存储。
在这里插入图片描述
在队列中加入一个标识符合集,如果消息类型与标识符合集中的类型相同则消息存入队列,其实就是队列设置了入队条件,选择性的存入消息,不在那么无脑全存。
比如上图中第一个队列 需要字符消息和双击消息,则这两种消息都会存入到队列1中。队列2需要字符消息和长按消息。
消费消息的速度不同可能会产生存不进消息的情况还是同样的处理方式。

1,在按键结构中定义主题栏

//===============主题模式开始==============================MSG_QUEUE_S * subject[4];
//===============主题模式结束==============================	

2,在队列结构中创建主题的标识符集 msgQueue.h

typedef struct{MSG_S msg[MSG_NUM];UINT8 num;UINT8 save;UINT8 get;UINT8 id[2];
}MSG_QUEUE_S;

3,增加一个队列Queue2,并初始化。

//====================主题模式初始化===================================initMsgQueue(&Queue0);//队列0需要字符消息和双击消息Queue0.id[0]=KEY_CHAR_EVENT;Queue0.id[1]=KEY_D_EVENT;initMsgQueue(&Queue1);//队列1需要字符消息和长按消息Queue1.id[0]=KEY_CHAR_EVENT;Queue1.id[1]=KEY_L_EVENT;initMsgQueue(&Queue2);//队列2只需要控制消息Queue2.id[0]=KEY_DOWN_EVENT;Queue2.id[1]=0xff;p->subject[0]=&Queue0;p->subject[1]=&Queue1;p->subject[2]=&Queue2;p->subject[3]=MSG_NULL; //====================主题模式初始化结束================================

4,生成消息并入队

void creatSubjectMsg(KEY_S * p,KEY_TYPE *pKeyF,UINT8 event){MSG_S msg;UINT8 num,i,len,j;MSG_QUEUE_S *pQ;if(*pKeyF){//一次产生一个消息num=getKeyNum(pKeyF);msg.code=KeyMap[num];//ASCII 码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;}}len=sizeof(p->subject)/sizeof(MSG_QUEUE_S*);	for(i=0;i<len;i++){pQ=p->subject[i];if(pQ!=NULL&&isMsgQueueFull(pQ)==FALSE){for(j=0;j<2;j++){if(msg.event==p->subject[i]->id[j]){//匹配标识符saveMsgQueue(pQ,&msg);	break;//匹配到了就可以跳出}			}				}		}		*pKeyF&=~(1<<num);//清零按键标志位				}
}

5,测试

void keyMain(void *pMsg)
{KEY_S *p = pMsg;//keyScan(p,p->pf());
#if(D_CLICK_EN)//keyDScan(p);
#endif//keyLScan(p);//keyF 按键单击标志位if(p->keyF){//按键单击事件creatSubjectMsg(p,&p->keyF,KEY_CHAR_EVENT);}if(p->keyDF){//按键双击事件creatSubjectMsg(p,&p->keyDF,KEY_D_EVENT);}if(p->keyLF){//按键长按事件creatSubjectMsg(p,&p->keyLF,KEY_L_EVENT);}
}

在这里插入图片描述
Queue0两个字符类型加三个双击共5个
在这里插入图片描述
Queue1两个字符类型加三个长按类型
在这里插入图片描述
Queue2只有一个控制类型 没有问题。

http://www.dtcms.com/a/479049.html

相关文章:

  • 电商平台有哪些网站名山东省城乡建设厅网站
  • Vue 3 + Vite:现代前端开发新范式-前端开发的”涡轮增压引擎”-优雅草卓伊凡
  • 前端-Vuex
  • 微信小说网站开发商丘网络科技有限公司
  • 避免时区问题的最佳实践, 数据库, mybatis
  • 望江县建设局网站发布网页
  • Codeforces1058(Div.2) A至F题解
  • MCP原理与实践1-原理部分(详细)
  • 云栖实录|人工智能+大数据平台加速企业模型后训练
  • WordPress整站下载器长春火车站建在哪里
  • 做电影平台网站怎么赚钱吗营销型网站建设多少钱
  • CF1057 BCD
  • 网站开发外文翻译中国纪检监察报网站
  • 医疗级能效革命:医院 “AI + 中央空调” 节能改造全解析
  • [Linux系统编程——Lesson11.进程控制:等待]
  • 甘南网站建设vi设计网站有哪些
  • 网站开发的一般流程是什么建个网站有收
  • 网站开发完成如何上线vs能建设网站吗
  • 婚车租赁网站怎样做wordpress 去掉主题版权
  • yolo的各模块扫盲
  • 宁波营销网站建设外包软件项目管理流程
  • 部门网站的开发 意义二次元wordpress博客
  • Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
  • 网站开发 前景wordpress魔方
  • 湖南平台网站建设找哪家凤城市网站建设
  • 常规的长焦镜头有哪些类型?能做什么?
  • docker图形化管理
  • flash网站制作实例做做做做网站
  • wordpress写的网站成都哪家公司做网站好
  • 继续接入备案 增加网站 区别怎么打广告吸引客户