从事单片机工作有几年了,一直想做一个系列总结,正好赶上今天下雨,俗话说:下雨天,宅家天。吃饱喝足,闲来无事,正好写篇博客,算是这个系列的开头第一篇,以后有时间就写点,这个“系列死了”也不奇怪。有不对的地方或者您有什么好的建议请留言,思想是碰撞的火花,请大家畅所欲言。
按键是单片机系统中最常用的一个东东,简单人机交互界面基本都有按键存在。犹记得刚开始接触单片机时候,读取一个按键IO值,就当做键值来处理,发现一次按键经常识别为好几次,查阅资料才发现要“消抖”,于是加个delay_ms(5),连续读取几次,确定单次有效值。这就是后来不怎么跑多任务,不注重实时性时经常‘玩’的方法,如果你还在用,那么下面的文字对你有用,如果你已经知道了,那就请飞过吧,反正我也是随便写写,你也就随性看看。
下面就是一个很典型的例子:
bool key_get_value(void)
{
bool bNewIoValue = 0;
static bool s_bOldIoValue = 0;
uint8_t i=0;
for(i=0;i<4;i++){
bNewIoValue = io_read();
if(s_bOldIoValue != bNewIoValue){
i=0;
s_bOldIoValue = bNewIoValue;
continue;
}
delay_ms(5);
}
return s_bOldIoValue;
}
我们来看看代码“阻塞在什么地方”?有代码分析可知,主要在delay_ms(5)这里,在这里通过“软件延时”来达到消除抖动的目的。那么我们换一种思路,假如我们的task执行到delay_ms(5)这里,便退出(让出运行权),同时启动一个“计数时钟”,当到5MS时候,就再起运行起来,这样就变为了“非阻塞”。为了达到这个目的,不管如何实现都需要一个“硬时基”,用于异步计数。
说下思路:就是tick时钟到,调用一次io_read(),并做消抖,然后获取一次有效按键值。好,到这里我们消除了delay_ms(5),那么我们继续思考,对于按键的值的“获得”我们可以说有“实时性要求”,例如:我按下按键,你要能够及时获得这个动作。但是按键值获得之后,按键的状态判断(down、short_press、long_press、repeat_press、up)相对来说可以暂缓,还有按键状态获得之后,其对应的处理函数又可分为紧急和非紧急两类(紧急类的先不讨论),非紧急类的又可以通过key_message_queue暂存按键消息,然后慢慢去执行消化。数据流程图如图所示:
这么处理的原因,是把有实时要求的任务和没有要求的任务分开,运用“生产者-消费者”模型。
思路介绍完了,下面说说具体模块:
注意:
1、最多255个按键;
2、每个按键都有按下、短按、长按、双击、抬起五个状态;
下载地址:http://download.csdn.net/download/wuhenyouyuyouyu/9952815
(下载分数限制,为什么不能设置为0分了?)
一、配置
public.h
宏KEY_MODE_SCHEDULER_IS_USER_CALL
1:按键的调度函数由用户调度;
0:按键的调度函数由系统自动调度;
宏KEY_NUM 配置按键个数
app_cfg.h
模块配置
#define KEY_ELIMINATE_JITTER_NUM 5 //按键消抖次数
#define KEY_MODE_QUEUE_LONG 10 //key消息队列大小
#define KEY_NORMAL_VALUE 1 //定义按键常态值
#define KEY_SCAN_CYCLE 10 //键值扫描周期:单位ms
#define KEY_SHORT_PRESSED_TIMER 1000 //短按判定时间:单位ms
#define KEY_LONG_PRESSED_TIMER 3000 //长按判定时间:单位ms
#define KEY_REPEAT_TIMER 500 //连按判定周期:单位ms
#define KEY_GET_VALUE_IS_INLINE_FUNCTION 0 //为inline函数还是callback函数
按键消息配置:为0,则不发送;为1,则发送
#define KEY_MODE_IS_ENABLE_MESSAGE_DOWN 1
#define KEY_MODE_IS_ENABLE_MESSAGE_SHORT 1
#define KEY_MODE_IS_ENABLE_MESSAGE_LONG 1
#define KEY_MODE_IS_ENABLE_MESSAGE_REPEAT 1
#define KEY_MODE_IS_ENABLE_MESSAGE_UP 1
加锁控制
......
二、用户实现函数
用户需提供按键扫描函数,原型bool key_mode_get_key_value(uint8_t chKeyID)
bool 按键状态
chKeyID 按键的ID
宏KEY_GET_VALUE_IS_INLINE_FUNCTION控制函数为inline类型,还是callback。
三、使用
int main(void)
{
key_message_t tKeyMessage;
//初始化
//关中断
......
USER_KEY_MODE_INIT(NULL);//key_mode_get_key_value()为inline函数
......
//开中断
while(1){
#if(KEY_MODE_SCHEDULER_IS_USER_CALL)
USER_KEY_MODE_SCHEDULER();
#endif
if(key_mode_get_message(&tKeyMessage)){
key_function[tKeyMessage.chKeyID](tKeyMessage.chKeyMessage);//按键处理
}
}
}
//心跳定时器
void systick(void)
{
......
USER_KEY_MODE_SCAN();
......
}
四、补充
#define __C99__
#ifndef __C99__
#define SAFE_ATOM_CODE(__CODE) {\
istate_t tState = GET_GLOBAL_INTERRUPT_STATE();\
DISABLE_GLOBAL_INTERRUPT();\
{\
__CODE;\
}\
SET_GLOBAL_INTERRUPT_STATE(tState);\
}
#else
#define SAFE_ATOM_CODE(...) {\
istate_t tState = GET_GLOBAL_INTERRUPT_STATE();\
DISABLE_GLOBAL_INTERRUPT();\
{\
__VA_ARGS__;\
}\
SET_GLOBAL_INTERRUPT_STATE(tState);\
}
#endif
五、技术交流群号
如果有BUG请留言或者进入技术群:344659218
六、修改记录
2017.08.29
1、修复BUG,用下面的函数替换掉原来的
/*****************************************************************************
* Function: key_mode_scan_sub_state
* PreCondition: None
* Input: void
* Output: void
* Side Effects: None
* Overview: KEY模块扫描子函数
* Note: 被key_mode_scheduler调用
*****************************************************************************/
static fsm_rt_t key_mode_judge_sub_state(key_mode_temp_t* ptKeyStruct)
{
key_mode_key_queue_t *ptThis = NULL;
#define RESET_FMS_JUDGE_SBU(__ID) {s_tKeyArray[__ID].tState = KEY_MODE_JUDGE_FSM_START;}
#define KEY_MODE_POST_MESSAGE(__MESSAGE) do{ \
ptThis = malloc_key_queue(); \
if(NULL != ptThis){ \
this.chKeyID = ptKeyStruct->chKeyID; \
this.tKeyMessage = __MESSAGE; \
this.ptNext = NULL; \
if(NULL == s_ptKeyModeMessageQueueTail){ \
s_ptKeyModeMessageQueueHead = ptThis; \
}else{ \
s_ptKeyModeMessageQueueTail->ptNext = ptThis; \
} \
s_ptKeyModeMessageQueueTail = ptThis; \
} \
}while(0)
switch(s_tKeyArray[ptKeyStruct->chKeyID].tState){
case KEY_MODE_JUDGE_FSM_START:
s_tKeyArray[ptKeyStruct->chKeyID].tState = KEY_MODE_JUDGE_FSM_DOWN;
s_tKeyArray[ptKeyStruct->chKeyID].tCnt = 0;
//break;
case KEY_MODE_JUDGE_FSM_DOWN:
if(0 == s_tKeyArray[ptKeyStruct->chKeyID].tLastValue && 0 != ptKeyStruct->chKeyValue){
s_tKeyArray[ptKeyStruct->chKeyID].tState = KEY_MODE_JUDGE_FSM_SHORT;
s_tKeyArray[ptKeyStruct->chKeyID].tCnt++;
#if(KEY_MODE_IS_ENABLE_MESSAGE_DOWN)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_DOWN);
#endif
}
SAFE_ATOM_CODE(
s_tKeyArray[ptKeyStruct->chKeyID].tLastValue = ptKeyStruct->chKeyValue;
)
break;
case KEY_MODE_JUDGE_FSM_SHORT:
if(0 != ptKeyStruct->chKeyValue){
s_tKeyArray[ptKeyStruct->chKeyID].tCnt++;
if(s_tKeyArray[ptKeyStruct->chKeyID].tCnt >= (KEY_SHORT_PRESSED_TIMER/(KEY_SCAN_CYCLE * KEY_ELIMINATE_JITTER_NUM * KEY_NUM))){
s_tKeyArray[ptKeyStruct->chKeyID].tState = KEY_MODE_JUDGE_FSM__LONGorREPEAT;
s_tKeyArray[ptKeyStruct->chKeyID].tCnt = 0;
}
}else{
#if(KEY_MODE_IS_ENABLE_MESSAGE_UP)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_UP);
#endif
RESET_FMS_JUDGE_SBU(ptKeyStruct->chKeyID);
SAFE_ATOM_CODE(
s_tKeyArray[ptKeyStruct->chKeyID].tLastValue = ptKeyStruct->chKeyValue;
)
break;
}
break;
case KEY_MODE_JUDGE_FSM__LONGorREPEAT:
if(0 != ptKeyStruct->chKeyValue){
s_tKeyArray[ptKeyStruct->chKeyID].tCnt++;
if(s_tKeyArray[ptKeyStruct->chKeyID].tCnt >= (KEY_LONG_PRESSED_TIMER/(KEY_SCAN_CYCLE * KEY_ELIMINATE_JITTER_NUM * KEY_NUM))){
s_tKeyArray[ptKeyStruct->chKeyID].tState = KEY_MODE_JUDGE_FSM_UP;
#if(KEY_MODE_IS_ENABLE_MESSAGE_LONG)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_LONG);
#endif
}
}else{
s_tKeyArray[ptKeyStruct->chKeyID].tState = KEY_MODE_JUDGE_FSM_REPEAT;
s_tKeyArray[ptKeyStruct->chKeyID].tCnt = 0;
SAFE_ATOM_CODE(
s_tKeyArray[ptKeyStruct->chKeyID].tLastValue = ptKeyStruct->chKeyValue;
)
#if(KEY_MODE_IS_ENABLE_MESSAGE_UP)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_UP);
#endif
}
break;
case KEY_MODE_JUDGE_FSM_REPEAT:
if(0 == ptKeyStruct->chKeyValue){
s_tKeyArray[ptKeyStruct->chKeyID].tCnt++;
if(s_tKeyArray[ptKeyStruct->chKeyID].tCnt >= (KEY_REPEAT_TIMER/(KEY_SCAN_CYCLE * KEY_ELIMINATE_JITTER_NUM * KEY_NUM))){
#if(KEY_MODE_IS_ENABLE_MESSAGE_SHORT)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_SHORT);
#endif
RESET_FMS_JUDGE_SBU(ptKeyStruct->chKeyID);
//return fsm_rt_cpl;
}
}else{
s_tKeyArray[ptKeyStruct->chKeyID].tState = KEY_MODE_JUDGE_FSM_UP;
#if(KEY_MODE_IS_ENABLE_MESSAGE_REPEAT)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_REPEAT);
#endif
}
break;
case KEY_MODE_JUDGE_FSM_UP:
if(0 == ptKeyStruct->chKeyValue){
#if(KEY_MODE_IS_ENABLE_MESSAGE_UP)
KEY_MODE_POST_MESSAGE(KEY_MESSAGE_UP);
#endif
RESET_FMS_JUDGE_SBU(ptKeyStruct->chKeyID);
SAFE_ATOM_CODE(
s_tKeyArray[ptKeyStruct->chKeyID].tLastValue = ptKeyStruct->chKeyValue;
)
}
break;
default:
while(1);
}
return fsm_rt_on_going;
}
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』