面向对象的C语言(OOPC)之按键

概述

在嵌入式系控制系统中,通常使用按键(Key)来实现人机交互,完成一些控制功能。一般地,按键在按下(KeyDown)和抬起(KeyUp)的过程中会存在1020ms的抖动毛刺,为了获取稳定的按键信息,必须通过一定的方法来避开这段不稳定的抖动期。

本文介绍了一种软件去抖动的方法,并采用面向对象的程序设计,将按键进行封装起来,对外提供统一的接口,生成单独的按键驱动文件,便于程序的移植(程序在STM32F103(ST)M16C/62P (RENESAS)系统上调试通过)。

1          按键软件去抖方法

1.1         按键在按下(KeyDown)和抬起(KeyUp)的过程中会存在1020ms的抖动毛刺,软件采用“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.hKeyDrv.cKeyApp.c。其中KeyDrv.hKeyDrv.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)定义按键指针变量并初始化(例定义4Key)

     (c)设置按键与MCU引脚对应,并设置相关属性

      * #include "KeyDrv.h"

      * 定义按键指针变量(最多可以创建256Key)-----------------------

        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后,自动触发1KeyPress事件;

                          2s后,再次触发1KeyPress事件;

                          以后每隔40ms触发1KeyPress事件,直到按键弹起

                          (长按键到时间可以通过设定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

//由于按键的按下与抬起都会有1020ms的抖动毛刺存在,为了获取稳定的按键信息

//须要避开这段抖动期(本程序采用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;

  }

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值