概述
在嵌入式系控制系统中,通常使用按键(Key)来实现人机交互,完成一些控制功能。一般地,按键在按下(KeyDown)和抬起(KeyUp)的过程中会存在10~20ms的抖动毛刺,为了获取稳定的按键信息,必须通过一定的方法来避开这段不稳定的抖动期。
本文介绍了一种软件去抖动的方法,并采用面向对象的程序设计,将按键进行封装起来,对外提供统一的接口,生成单独的按键驱动文件,便于程序的移植(程序在STM32F103(ST)和M16C/62P (RENESAS)系统上调试通过)。
1 按键软件去抖方法
1.1 按键在按下(KeyDown)和抬起(KeyUp)的过程中会存在10~20ms的抖动毛刺,软件采用“2回一致”原则,主循环中每25ms对按键输入口进行采样,如果连续2次采样一致,则确认按键的输入信息。
1.2 Key输入确认后,可以在确认值的“上升沿”(上图中水绿色的1处)触发KeyUp抬起事件,在“下降沿”(上图中粉色的0处)触发KeyDown事件。同样,也可以通过计数器来触发长按键KeyPress事件。
2 OOPC(面向对象的C语言)
对于嵌入式系统的开发,OOPC是一个非常不错的选择,既有C语言的小巧、高效性,又有C++的封装、继承。笔者学习了高焕堂先生编写的《UML+OOPC嵌入式C语言开发精讲》,获益匪浅。本按键驱动文件就是使用了书中OOPC的思想完成。我的理解还不够深刻,在这里只是抛砖引玉,大家互相交流。
在MCU硬件资源越发强悍的今天,感觉嵌入式系统的开发者也过上了“有钱人”的生活,不需要再节衣缩食,过多的考虑ROM/RAM的占用以及代码的运行速度,而可以更加注重软件的可重用性、可维护性等。
按键程序分3个文件:KeyDrv.h、KeyDrv.c、KeyApp.c。其中KeyDrv.h、KeyDrv.c属于驱动文件,KeyApp属于应用文件。对于用户而言,只需要在KeyApp文件中的KeyUp/Down/Press事件中编写自己的应用程序即可,代码非常清爽。
3 Key的功能
(1)实现按键的三种事件(抬起KeyUp、按下KeyDown、长按KeyPress)
(2) KeyPress事件的变速度触发(例如:开始2s触发1次,再1s触发1次,最后40ms触发1次。时间可设)
(3)可选择按键长按再弹起时是否触发KeyUp事件(默认不触发)
(4)可选择按键长按是否多次触发KeyPress事件(默认不触发)
4.1 3个设定(可以在进入主循环前进行设定---reset)
set_KeyPin() //设置按键的位置,在MCU哪个引脚上
set_KeyPressUpCmd () //使能(失能)按键长按再弹起时是否触发KeyUp事件
set_KeyPressContinuedCmd () //使能(失能)按键长按是否多次触发KeyPress事件
4.2 按键扫描 (主循环中每隔20~25ms调用,推荐25ms调用1次)
KeyScan() //按键扫描,确认按键状态,并自动触发下面3跟服务类的事件
4.3 3个服务(在KeyApp文件中,可以在3个服务函数中写应用程序)
KeyUp () //抬起事件
KeyDown () //按下事件
KeyPress () //长按事件
对于用户而言,只需要在KeyApp文件中的KeyUp/Down/Press事件中编写自己的应用程序即可,代码非常清爽,同时便于移植。
5 用法详细示例
* (1) 在reset函数中
(a)包含key的头文件
(b)定义按键指针变量并初始化(例定义4个Key)
(c)设置按键与MCU引脚对应,并设置相关属性
* #include "KeyDrv.h"
* 定义按键指针变量(最多可以创建256个Key)-----------------------
TKey *Key1,*Key2;
* 按键指针初始化----------------------------------------------
Key1 = (TKey *)NewKey();
Key2 = (TKey *)NewKey();
* 按键pin对应-------------------------------------------------
Key1->IA.set_KeyPin(Key1,(u32 *)GPIOA,GPIO_Pin_0);
Key2->IA.set_KeyPin(Key2,(u32 *)GPIOC,GPIO_Pin_13);
* 使能(失能)长按键后的UP事件(ENABLE:触发;DISABLE:不触发)-
Key1->IA.set_KeyPressUpCmd(Key1, DISABLE);
Key2->IA.set_KeyPressUpCmd(Key2, DISABLE);
* 使能(失能)长按是否多次触发KeyPress事件(ENABLE:触发;DISABLE:不触发)-
Key1->IA.set_KeyPressContinuedCmd(Key1, ENABLE);
Key2->IA.set_KeyPressContinuedCmd(Key2, ENABLE);
* (2)在main主循环中调用扫描函数(25ms扫描1次),程序自动检测按键状态并触发相关事件
#include "KeyDrv.h"
Key1->IA.KeyScan(Key1,1); //按键pin扫描
Key2->IA.KeyScan(Key2,2); //按键pin扫描
* (3)在KeyApp文件的相关事件中编写应用程序(idx为按键的索引号,main函数扫描时指定)
#include "KeyDrv.h"
* 按键抬起 ------------------------------------------------
void KeyUp(const u8 idx)
{
}
* 按键按下 ------------------------------------------------
void KeyDown(const u8 idx)
{
}
* 按键长按 ------------------------------------------------
void KeyPress(const u8 idx)
{
}
6 备注 程序中运用的技巧:
用局部变量防止指针别名引起的重载,详见《arm嵌入式系统开发:软件设计与优化》第5章 高效的C编程
7 附录:《KeyApp.c》、《KeyDrv.h》、《DeyDrv.c》