一、碎碎念
谁家好人代码写完半年之后来写笔记的啊,代码写得差,请温柔的骂,该程序支持单击,双击,长按和组合按键
二、运行环境
2.1 硬件环境
如图,四个独立按键直接连接到MCU的IO口
2.2 软件环境
使用MRS(MounRiver Studio)创建的带RTT的工程
三、流程图
四、源码
volatile uint8_t key_state = KEY_STATE_IDLE;//按键状态机表示状态的变量
void thread_keyboard_entry(void* parameter)
{
thread_keyboard_hw_init();//初始化硬件相关的部分
rt_kprintf("create mb: key\r\n");
mailbox_key = rt_mb_create ("mail_key", 30, RT_IPC_FLAG_PRIO);//注册邮箱,这一段程序获取的键值会通过邮箱发送出去
if(mailbox_key == RT_NULL)
{
rt_kprintf("init mailbox key failed.\n");
}
volatile uint8_t key_sample,last_key_sample,last_key_value,key_single_value;
/*
*变量说明
*key_sample:每一次采样IO口获取到的当前键值
*last_key_sample:上一次采样IO口得到的键值,每一次程序结束的时候将会把key_sample更新到
* last_key_sample
*last_key_value:上一次有效按键动作,如果key_sample识别为非抖动的按键动作,会在部分状态更新
* last_key_value
key_single_value:单个按键有效动作的键值,如果key_sample识别为非抖动的按键动作并且只有一个按
* 键动作,会把key_sample更新到key_single_value
*/
volatile rt_tick_t key_press_time,long_press_last_time,release_time;
/*
*变量说明
*key_press_time:单个按键按下的时间,用来识别长按
*
*long_press_last_time:上一次发送长按键值的时间,发完之后更新该变量,长按键值发送时间间隔可调
*
*release_time:按键释放的时间,用来识别有没有双击
*/
while(1)
{
key_sample = ((uint16_t)(GPIOB->INDR&0x0000f000)) >> 12; //对IO的输入数据寄存器采样,获取当前时刻键值
if(key_sample == last_key_sample) //如果这次采样和上次采样的值相同(两次间隔10ms),则认为是有效的按键动作而非抖动,则继续处理
{
switch(key_state) //判断状态机当前状态,然后执行对应操作
{
case KEY_STATE_IDLE:
if((key_sample != NO_KEY_PRESS) && (last_key_value == NO_KEY_PRESS)) key_state = KEY_STATE_PRESS;
else key_state = KEY_STATE_IDLE;
last_key_value = key_sample;
break;
/*
*IDLE状态:如果当前的按键动作不是所有键都没有被按下的状态,
*并且上一次的按键动作是所有键都没有被按下的状态,则更新状态
*为有键按下状态,否则维持IDLE状态,并更新上一次有效的按键动作
*/
case KEY_STATE_PRESS:
if((key_sample == KEY1_PRESS) || (key_sample == KEY2_PRESS) || (key_sample == KEY3_PRESS) || (key_sample == KEY4_PRESS))
{
key_press_time = rt_tick_get();
key_single_value = key_sample;
key_state = KEY_STATE_SINGLE_PRESS;
}
else
{
key_state = KEY_STATE_IDLE;
rt_mb_send(mailbox_key,key_sample|0x1000);
// rt_kprintf("key combin value: %d\r\n",key_sample);
}
/*
*有键按下状态:如果只有一个按键被按下(这部分处理错了),
*则记录当前时间作为按键按下的时间(实际上至少有10ms的误差),
*记录当前采样到的IO口的值作为键值,然后切换到单个按键按下状态
*
*否则认为是多键按下,不再做其他处理,切换状态为IDLE状态,并发送组合键的键值
*/
break;
case KEY_STATE_SINGLE_PRESS:
if(key_sample == NO_KEY_PRESS)
{
release_time = rt_tick_get();
key_state = KEY_STATE_SINGLE_RELEASE;
}else
{
if((rt_tick_get() - key_press_time) > 1000)
{
long_press_last_time = rt_tick_get();
key_state = KEY_STATE_LONG_PRESS;
}
}
/*
*单个按键按下状态:程序运行到此状态时距离上一个状态至少过去了10个毫秒
*
*在当前状态会判断采样到的有效按键动作是不是没有键被按下的状态,
如果是则记录当前时间作为按键被释放的时间,然后切换到单个按键释放状态
*
*否则就一直判断当前时间是否距离按键被按下过去了1秒的时间,如果是就记录下当前
*时间作为上一次长按的时间,然后切换到长按状态
*/
break;
case KEY_STATE_LONG_PRESS:
if(key_sample == NO_KEY_PRESS)
{
key_state = KEY_STATE_IDLE;
}
else
{
if((rt_tick_get() - long_press_last_time) > 200)
{
// rt_kprintf("key long press: %d\r\n",key_single_value);
rt_mb_send(mailbox_key,key_single_value|0x2000);
long_press_last_time = rt_tick_get();
}
}
break;
/*
*长按状态:如果当前有效的按键动作是没有按键被按下,
*说明长按已经结束了,切换状态回IDLE状态
否则计算当前时间与上一次处理长按的时间,
*大于阈值就发送一次键值并更新上一次处理长按的时间
*/
case KEY_STATE_SINGLE_RELEASE:
if(key_sample == NO_KEY_PRESS)
{
if((rt_tick_get() - release_time) > 200)
{
// rt_kprintf("key single value: %d\r\n",key_single_value);
rt_mb_send(mailbox_key,key_single_value|0x3000);
key_state = KEY_STATE_IDLE;
}
}else
{
// rt_kprintf("key double value: %d\r\n",key_single_value);
rt_mb_send(mailbox_key,key_single_value|0x4000);
key_state = KEY_STATE_IDLE;
}
/*
*单个按键释放状态:如果没有按键被按下,并且阈值时间内一直没有键被按下
*则认为是一次按键单击,发送单机键值并切换状态为IDLE状态
*
*否则就认为是一次双击(这里处理是有问题的)
*发送双击的键值并切换状态为IDLE
*/
break;
default:
key_state = KEY_STATE_IDLE;
break;
}
}
last_key_sample = key_sample; //更新上一次按键采样值,用于下一次的比较
rt_thread_mdelay(10);
}
}