概述<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
在嵌入式系控制系统中,通常使用按键(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 接口函数: 3个设定,1个按键扫描,3个服务,
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》
附一、《KeyApp.c》
#include "KeyDrv.h"
* 按键抬起 ------------------------------------------------
void KeyUp(const u8 idx)
{
}
* 按键按下 ------------------------------------------------
void KeyDown(const u8 idx)
{
}
* 按键长按 ------------------------------------------------
void KeyPress(const u8 idx)
{
}
附二、《KeyDrv.h》
/******************** (C) COPYRIGHT 2009 *************************************
* File Name : KeyDrv.h
* Author : Hero.feng
* Version : V1.0.0
* Date : 2009-02-05
* Description : 按键驱动,功能如下:
(1)实现按键的三种事件(按下KeyDown/弹起KeyUp/长按KeyPress)
(2)长按2s后,自动触发1次KeyPress事件;
2s后,再次触发1次KeyPress事件;
以后每隔40ms触发1次KeyPress事件,直到按键弹起
(长按键到时间可以通过设定LongPressTimes[]来修改)
(3)可选择按键长按再弹起时是否触发KeyUp事件(默认不触发)
(4)可选择按键长按是否多次触发KeyPress事件(默认不触发)
****************************************************************************
* 使用方法:
* (1)在reset函数中
(a)定义按键指针变量并初始化
(b)设置按键与MCU引脚对应
* (2)main主循环中调用按键扫描函数(25ms扫描1次)
程序自动检测按键状态,触发Key_Up/Down/Press事件
* (3)在KeyApp文件的相关事件中编写应用程序
****************************************************************************/
#ifndef __KEYDRV_H
#define __KEYDRV_H
#include "stm32f10x_type.h"
#include "lw_oopc.h"
typedef enum
{
KEY_STS_UP = 0,
KEY_STS_DN,
KEY_STS_LONG_PRESS1,
KEY_STS_LONG_PRESS2,
KEY_STS_LONG_PRESS3,
}KeyStatus;
INTERFACE(IA) //按键接口
{
//服务类函数接口:
void (*KeyUp)(const u8 idx);
void (*KeyDown)(const u8 idx);
void (*KeyPress)(const u8 idx);
//扫描、设定类函数接口:
void (*KeyScan)(void *t, const u8 idx);
void (*set_KeyPin)(void *t, const u32 *GPIOx, const u32 GPIO_Pin);
void (*set_KeyPressUpCmd)(void *t, const FunctionalState EnableFLG);
void (*set_KeyPressContinuedCmd)(void *t, const FunctionalState EnableFLG);
};
CLASS(Key)
{
IMPLEMENTS(IA);
KeyStatus KeySTS; //按键状态:抬起、按下、长按1、长按2、长按3
FunctionalState KeyPressUpEnable; //按键长按抬起后是否触发Up事件(=ENAble触发)
FunctionalState KeyPressContinuedEnable;//多次触发KeyPress事件(=ENAble触发)
u16 Up_SameTimes; //电平采用相同次数
u16 Dn_SameTimes; //电平采用相同次数
u16 LongPressCTR; //长按键时间计数器
u32 *GPIO_Addr; //按键对应的MCU端口号
u32 GPIO_Pin; //按键对应的MCU引脚号
};
DECLARE_INITIALIZE(Key); //声明按键函数 NewKey,返回一个指针
extern void KeyUp(const u8 idx);
extern void KeyDown(const u8 idx);
extern void KeyPress(const u8 idx);
#endif /* __KEYDRV_H */
附三、《KeyDrv.c》
#include "KeyDrv.h"
#define KEYSCANLOOPTIME 25 //25ms扫描1次
//由于按键的按下与抬起都会有10~20ms的抖动毛刺存在,为了获取稳定的按键信息
//须要避开这段抖动期(本程序采用2回一致原则,采样周期25ms)
#define KEY_PRESS_DLY 2 //连续2次一致(25ms×2),则确认引脚电平
#define KEY_LONGPRESS_DLY (2000 / KEYSCANLOOPTIME) //长按2s后触发KeyPress事件
//长按2s触发1次,再长按2s触发1次,以后根据预设的速度,每200(100)ms触发1次
// KEY_STS_UP KEY_STS_DN
static U16 LongPressTimes[]={KEY_LONGPRESS_DLY, KEY_LONGPRESS_DLY,
KEY_LONGPRESS_DLY, KEY_LONGPRESS_DLY, 100 / KEYSCANLOOPTIME, 100 / KEYSCANLOOPTIME,100 / KEYSCANLOOPTIME};
// KEY_STS_LONG_PRESS1 KEY_STS_LONG_PRESS1 KEY_STS_LONG_PRESS2 KEY_STS_LONG_PRESS3 冗余
static U8 Key_ReadPinStatus(void *t);
static void KeyScan(void *t, const u8 idx);
static void set_KeyPin(void *t, const u32 *GPIOx, const u32 GPIO_Pin);
static void set_KeyPressUpCmd(void *t, const FunctionalState EnableFLG);
static void set_KeyPressContinuedCmd(void *t, const FunctionalState EnableFLG);
BEGIN_INITIALIZE(Key)
FUNCTION_SETTING(IA.KeyUp,KeyUp);
FUNCTION_SETTING(IA.KeyDown,KeyDown);
FUNCTION_SETTING(IA.KeyPress,KeyPress);
FUNCTION_SETTING(IA.KeyScan,KeyScan);
FUNCTION_SETTING(IA.set_KeyPin,set_KeyPin);
FUNCTION_SETTING(IA.set_KeyPressUpCmd,set_KeyPressUpCmd);
FUNCTION_SETTING(IA.set_KeyPressContinuedCmd,set_KeyPressContinuedCmd);
VARIABLE_SETTING(KeySTS,KEY_STS_UP);
VARIABLE_SETTING(KeyPressUpEnable,DISABLE);
VARIABLE_SETTING(KeyPressContinuedEnable,DISABLE);
VARIABLE_SETTING(Up_SameTimes,KEY_PRESS_DLY+1);
VARIABLE_SETTING(Dn_SameTimes,KEY_PRESS_DLY+1);
VARIABLE_SETTING(LongPressCTR,0);
VARIABLE_SETTING(GPIO_Addr,0);
VARIABLE_SETTING(GPIO_Pin,0);
END_INITIALIZE
/* 扫描按键,连续2次一致-----------------------------------*/
static void KeyScan(void *t, const u8 idx)
{
TKey *cthis = (TKey *)t;
//--使用局部变量防止指针别名引起的变量重载--
KeyStatus KeySTS;
U16 Up_SameTimes;
U16 Dn_SameTimes;
U16 LongPressCTR;
KeySTS = cthis->KeySTS;
Up_SameTimes = cthis->Up_SameTimes;
Dn_SameTimes = cthis->Dn_SameTimes;
LongPressCTR = cthis->LongPressCTR;
if(Key_ReadPinStatus(cthis) != 0) //按键抬起
{
if(Up_SameTimes <= KEY_PRESS_DLY) Up_SameTimes++; //抬起计数
if(Up_SameTimes >= KEY_PRESS_DLY) Dn_SameTimes = 0; //按下清零
}
else //按键按下
{
if(Dn_SameTimes <= KEY_PRESS_DLY) Dn_SameTimes++; //按下计数
if(Dn_SameTimes >= KEY_PRESS_DLY) Up_SameTimes = 0; //抬起清零
//Dn确认时清零长按键计数器,否则计数;计数到达若允许多次触发,则清零重新计数
if(Dn_SameTimes <= KEY_PRESS_DLY) LongPressCTR = 0; //长按清零
else if(LongPressCTR <= LongPressTimes[KeySTS]) LongPressCTR++;
else if(cthis-> KeyPressContinuedEnable) LongPressCTR = 0;
}
//******** 2回一致 ********
//KeyUp事件(上次为按下状态 或者 允许长按键后的Up事件触发)
//置键的状态为Up状态
if(Up_SameTimes == KEY_PRESS_DLY)
{
if(KeySTS == KEY_STS_DN || cthis->KeyPressUpEnable) cthis->IA.KeyUp(idx);
KeySTS = KEY_STS_UP;
}
//KeyDn事件
//置键的状态为Dn状态
if(Dn_SameTimes == KEY_PRESS_DLY)
{
cthis->IA.KeyDown(idx);
KeySTS = KEY_STS_DN;
}
//KeyPress事件
//置键的状态为Press状态(=PRESS1/PRESS2/PRESS3)
if(LongPressCTR == LongPressTimes[KeySTS])
{
cthis->IA.KeyPress(idx);
if(KeySTS < KEY_STS_LONG_PRESS3) KeySTS++;
else KeySTS = KEY_STS_LONG_PRESS3; //冗余指令,加强抗干扰
}
//变量值保存
cthis->KeySTS = KeySTS;
cthis->Up_SameTimes = Up_SameTimes;
cthis->Dn_SameTimes = Dn_SameTimes;
cthis->LongPressCTR = LongPressCTR;
}
/* 读取按键所在的pin电平------------------------------------*/
static U8 Key_ReadPinStatus(void *t)
{
TKey *cthis = (TKey *)t;
return (*(cthis->GPIO_Addr) & cthis->GPIO_Pin) != 0 ? (U8)1U8)0;
}
/* 设置按键所对应的引脚------------------------------------*/
static void set_KeyPin(void *t, const u32 *GPIOx, const u32 GPIO_Pin)
{
TKey *cthis = (TKey *)t;
cthis->GPIO_Addr = (u32 *)GPIOx;
cthis->GPIO_Pin = GPIO_Pin;
}
/* 使能(失能)长按键后的UP事件(ENABLE:触发;DISABLE:不触发)*/
static void set_KeyPressUpCmd(void *t, const FunctionalState EnableFLG)
{
((TKey *)t)->KeyPressUpEnable = EnableFLG;
}
/* 使能(失能)长按是否多次触发KeyPress事件(ENABLE:触发;DISABLE:不触发*/
static void set_KeyPressContinuedCmd(void *t, const FunctionalState EnableFLG)
{
((TKey *)t)-> KeyPressContinuedEnable = EnableFLG;
}
https://static.assets-stash.eet-china.com/album/old-resources/2009/2/6/6d4e4148-9465-46b7-bcd0-8629b17b8611.rar
飞言走笔 2014-7-22 09:53
用户139848 2009-3-3 20:16
用户139848 2009-3-2 12:43
用户139848 2009-3-2 09:56