很多产品都会用到io按键这一成本很低的,技术也很容易实现的产品。如果是刚入行的单片机工程师来说,写一个完整的具有实用性和稳定性和扩展性的按键驱动还是比较困难的,这篇博客主要是结合自己的实际经验之谈,解析一下普通的Io按键驱动如何写和实现,如果有大神看出有错误的东西,请指教。此篇作为学习和交流之用。谢谢大家。
首先普通的io 口按键来说的话,主要分为两种情况,一种是低推按键,另一种是高推按键。低推按键指的就是没有按下去的时候是高电平,按下去之后会检测到低电平,io口一般配置为内部上拉输入的模式。高推按键的话就是指平常为低电平,按下去之后检测的io口应该为高电平,io设置为下拉输入的情况。图1-0为高推按键,图1-2为低推按键。
这种轻触按键按下去松手都会有一个抖动的过程,所以大家在看很多关于按键驱动这类文章的话很多都会有一个防抖的程序,这是能更稳定的检测。实际工作中,一般会根据单片机芯片的特性及资源来写这类驱动的代码。最简单的应该需要一个按下去的检测以及松手的检测,然后加入短按,长按,连续按,双击等比较全面的操作。可能还需要检测同时按这些功能。所以就需要一个比较完整的驱动代码来实现这一类的问题。其实这种驱动写好了,以后对于类似的像矩阵按键,遥控按键还有触摸按键等都会有一个很好的参考作用。废话不说了,下面就开始进行干货的派发。
首先,配置好io脚之后,第一步就是检测什么时候按下去,这个检测得出键值一般放在主循环函数里面,对于8位单片机系统来说应该是最快的。
//例如,一个低推按键的io口为PB1口,检测放在主循环里面
#define KEY_NO 0xff
#define KEY_1 0x01
#define KEY_PORT PB1
//取得键值的函数
u8 Get_key_value(void)
{
u8 key_value;
if(!KEY_PORT)//KEY_PORT代表PB1,按下去的时候为低电平
{
key_value=KEY_1;
}
else
{
key_value=KEY_NO;
}
return key_value;
}
void main(void)
{
Sys_init();
while(1)
{
asm(clrwdt);//清除看门狗
gu8_key_value=Get_key_value();//放的位置为主循环,一般在us级别内就能扫一次
if(main_loop)//1ms进来一次
{
main_loop=0;
}
}
}
上面这段小代码就是取得键值的方法,上面说过,按键一般都会有抖动的存在,所以需要清除抖动的影响,抖动按照我的理解以及工作的经验来看,应该是发生在按下去的瞬间,会有一段小波动,以及松手的时候也会有小波动,按着不放的情况下应该有几率存在一定的毛刺。所以一个保险的按键驱动这些方面都要考虑清楚。为了不阻塞单片机工作,我们可以将去抖程序利用单片机的定时器计时来解决。上面的程序主程序的主循环里面就已经默认规划好了1ms进入一次中断,防止按键抖动以及判断长短按这些都需要放在这里面。
//加入一段防抖函数,放在1ms中断里面
void key_value_filter(u8 key_value)
{
static u16 key_press_cnt;//按下去的检测
static u8 key_release_cnt;//松手的检测
static u8 back_key;//存储按下去的键值
if(key_value!=KEY_NO)
{
key_press_cnt++;
key_release_cnt=0;//松手计数清零
if(key_press_cnt==15)//有些按键的作用只有短按,实际工作中具体分析
{
}
else if(key_press_cnt==1000)//到达长按的阈值,1s
{
}
else if(key_press_cnt>=1300)//到达了长按不放的阈值,1.3s
{
key_press_cnt=1000;//防止超过参数的最大值,同时防止长按继续触发
}
}
else
{
key_release_cnt++;//检测到松手电平了,不要马上判断说是松手了
if(key_release_cnt>=30)//约30ms稳定的松手电平之后就判断为松手,
{
key_release_cnt=0;
if(key_press_cnt>=15 && key_press_cnt<1000)//大于15ms小于1s判断为短按
{
//待会继续补充
}
key_press_cnt=0;//只有真正松手了才会清掉这个计数
}
}
}
上面这段去抖函数只是一个框架,这里面还是有很多需要优化的地方,突出的思想就是检测到一个按键真正按下至少需要15ms的稳定电平,同样松手也要检测几十毫秒才能真正判断是松手。中间按下去之后计时,这个时间就可以用来判断短按,长按和连续按着不放了。好了,主要思想大家应该懂了,为什么说C语音移植性和可修改能力强呢,因为其中会运用到大量的宏和简便易懂的参数名。现在就对上面这一小段代码进行优化,看完后大家就会懂了。
//加入一段防抖函数,放在1ms中断里面
//宏定义
//具体需要的时间以后直接改变宏的参数就ok了!
#define KEY_SHORT_CNT 15 //短按标准
#define KEY_LONG_CNT 1000
#define KEY_HOLD_CNT 1300
#define KEY_RELEASE_CNT 30 //松手
//定义按键按下去的状态
#define KEY_SHORT_UP 0x01//短按松手放开
#define KEY_SHORT 0x05 //执行一次短按之后松开手之后不需要呼应
#define KEY_LONG_UP 0x02//长按松手后执行消息
#define KEY_HOLD 0x03//长按不放,需要持续性消息
#define KEY_LONG 0x04 //长按
//原来的防抖函数升级成防抖加上执行动作的按键主函数
void key_value_filter(u8 key_value)
{
static u16 key_press_cnt;//按下去的检测
static u8 key_release_cnt;//松手的检测
static u8 back_key;//存储按下去的键值
u8 key_state=KEY_NO;//初始化键值的状态
if(key_value!=KEY_NO)
{
if( back_key!=key_value)//快速变换按键的情况下,以最新的按键为主
{
back_key=key_value;
key_press_cnt=0;
}
key_press_cnt++;
key_release_cnt=0;//松手计数清零
if(key_press_cnt==KEY_SHORT_CNT )//有些按键的作用只有短按,实际工作中具体分析
{
key_state=KEY_SHORT;
}
else if(key_press_cnt==KEY_LONG_CNT)//到达长按的阈值,1s
{
key_state=KEY_LONG;
}
else if(key_press_cnt>= KEY_HOLD_CNT)//到达了长按不放的阈值,1.3s
{
key_press_cnt=KEY_LOMG_CNT;//防止超过参数的最大值,同时防止长按继续触发
key_state=KEY_HOLD;
}
}
else
{
key_release_cnt++;//检测到松手电平了,不要马上判断说是松手了
if(key_release_cnt>=KEY_RELEASE_CNT)//约30ms稳定的松手电平之后就判断为松手,
{
key_release_cnt=0;
if(key_press_cnt>=KEY_SHORT_CNT && key_press_cnt<KEY_LONG_CNT)//大于15ms小于1s判断为短按
{
//待会继续补充
key_state=KEY_SHORT_UP;
}
else if(key_press_cnt>=KEY_LONG_CNT)
{
key_state=KEY_LONG_UP;
}
key_press_cnt=0;//只有真正松手了才会清掉这个计数
}
}
}
上面通过一个static u16 key_press_cnt以及定时器中断就能判断出短按,长按,长按不放等的key的状态,这时候基本就是一个成型的按键驱动了,在工程应用中,只要通过不同的键值及状态去执行不同的功能,就能完成简单以及比较复杂点点的工程了。这篇博文就结束了,下面继续下一个博文会对按键深一步解析和增加内容