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

2009-2-6 23:04 9102 9 13 分类: MCU/ 嵌入式

概述<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />


在嵌入式系控制系统中,通常使用按键(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;


  }


  //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

PARTNER CONTENT

文章评论4条评论)

登录后参与讨论

飞言走笔 2014-7-22 09:53

MARK

用户139848 2009-3-3 20:16

楼主好久没来更新了啊

用户139848 2009-3-2 12:43

楼主能帮忙解释一下如下几个类型,原型没有找到:LocalUI、cthis、TKey 还有这个函数: FUNCTION_SETTING 谢谢楼主,这些是在“lw_oopc.h”头文件里面定义的吗?

用户139848 2009-3-2 09:56

您好!我对您写的“面对对象的C语言(oopc)之按键例子”很感兴趣,为了能更深入的了解,能否麻烦您将“lw_oopc.h”头文件发到我邮箱,谢谢 alex_tricolor@qq.com
相关推荐阅读
用户526338 2009-03-11 20:26
[转帖] 大小端模式的区别
最近在学习USB,在看Keil C51代码的时候发现从PC机接收的USB数据在Keil C51环境里要交换高低字节,这是因为Keil的数据结构是大端模式,对于大端模式不是很清楚后来网上搜索发现有一篇文...
用户526338 2009-01-04 22:15
STM32之时钟树笔记
1          STM32有五个时钟源:HSI、HSE、LSI、LSE、PLL<?xml:namespace prefix = o ns = "urn:schemas-microsoft-...
用户526338 2009-01-01 15:30
STM32之GPIO笔记
1           STM32的输入输出管脚有下面8种可能的配置:(4输入+2输出+2复用输出)<?xml:namespace prefix = o ns = "urn:schemas-mi...
用户526338 2008-12-29 22:05
stm32学习中……
开始学习stm32f103,在这边留下一些心得体会,互相交流,自勉鼓励。...
EE直播间
更多
我要评论
4
9
关闭 站长推荐上一条 /3 下一条