14798|45

4008

帖子

0

TA的资源

版主

楼主
 

按键程序的编写 [复制链接]

 
你可能会想:按键吗这个很简单啊,按下一个电平抬起一个电平根据io状态进入不同的处理就行了有什么复杂的?
    不是这样的我问问你,你的按键灵敏有效吗?运行资源占用多少?能支持复合按键长按键功能吗?复杂的按键逻辑该怎么实现呢?其实按键是一个看似简单做好不容易的东西,话说回来什么东西做好也要花些心思啊.网上经常见到初学者的按键程序不是delay就是中断,实在看着别扭.今天做个教程分析一下我以前写的按键驱动程序,这个程序开销很小,功能却很强估计我下半辈子都会用它了,51下编的,如果在其它U上稍微改改就行了,原理是一样的.另外借助这个驱动讲解一下单片机程序的一般结构.
    首先我们来分析一下按键程序的特点,第一点按键程序的实时性要求不高,这一点决定了按键驱动放到大循环里比较好.所谓实时性就是cpu对于按键状态变化是否要及时处理的程度,你可以想象的出来键按下去了晚个几十毫秒反应的话你根本感觉不出来,况且按键是一定要消抖的,消抖最少也会产生100ms左右的时间滞后.这里你可能会想了当然是越及时处理越好了,但是事情是有两面性的把有限的资源按排到最需要的地方才是合理的.

    另起一段再啰嗦一整段哈,这段和按键无关.我们可以把事件的处理的及时性分成三类1:while(事件){处理}这是实时性处理最强的,但是这个时候你的单片机不能干别的了,什么时候需要这么做呢?举个例子吧比如你要做一个红外遥控编码的获取程序,实际就是测量脉冲的上下沿的宽度,测量的精度高对于分析编码和发射编码是有好处的,你的程序可以这么写if(检测到红外编码开始) {关闭中断;while(编码结束){测量记录上升沿宽;测量记录下降沿宽;}开启中断;}.因为红外编码时间也就是几个毫秒因此让程序专注于检测对整体影响不会很大.那么类似的if(事件){处理}else{做别的事}可不可以兼顾呢?这个时候你在"做别的事"里时间长短就是对实时性的影响了,平均响应时间最坏情况不说了很好理解吧.特别提及一点就是大循环里的语句会受中断影响,因为中断的到来是不可预知的,严格的时间控制是要关闭中断的.2:安排到中断里处理,但是注意的一点就是中断服务程序不能拖太长的时间,复杂的工作是不能安排到服务里完成的.3:安排到大循环里执行,这是实时性最差的,随着功能越来越多大循环的执行时间会变得越来越长.另外再提及一点,不要只会把程序写成if(事件) {处理;}的结构,当程序复杂了你会陷入绝境的.通过全局变量或位变量可以把事件和处理分割开,把程序按一定的组件分块编写通过函数完成功能这才是重要的,面向对象是个思想,结构混乱自然就没有逻辑了.以后再听到谁说面向对象就是C++和java我们一起鄙视他好吧.
    按键程序的第二个特点是功能复合,这一点决定了按键驱动程序最好分成两块:按键扫描和按键获取函数.下面会说扫描函数,所谓功能复合是指一个状态可能对应多种处理方式,一个键按下去了但是在程序的不同部分需要不同的处理方式,比如置数时某个键表示数值加,翻屏时这个键表示显示往上翻等等.从对象的角度讲按键的处理应该属于按键对象之外的东西,不应包含在对象内部,很多初学者的按键程序简单的没有问题,但是程序复杂了以后按键的逻辑再也理不清了.
    按键程序的第三个特点是一定要消抖,考虑到这一点你的扫描程序必须要定时运转.消抖就是过一段时间再看取按键状态作比较然后决定按键扫描结果,否则会引起多个按键事件.初学者一看到延时马上想起delay()其实这是个很菜的方法,全都是死循环的延时方法,更有甚者居然能delay(500)ms,真是没话说了.扫描函数的全部内容就是根据按键扫描结果的比较切换按键扫描状态,按键状态只有三种1无按键2有按键的消抖期3消抖结束待取.如果能保证每间隔一定的时间调用一次扫描函数那么扫描函数对某一个变量的计数就可以实现计时的目的,不再需要其他额外的定时了.
   这里又来一个啰嗦段,程序里面用计数来定时是个很常用的办法,这也是初学者要学会的.程序里一般都会有很多个需要周期性的执行的函数调用或者需要定时的时间,怎么解决呢?其实你可以安排一个
全局量TimeBase每隔固定时间++,也没必要非得要用一个定时器实现,你的程序中肯定有很多个这种周期性的地方,加后来个判断(避免条件重复成立) if ((TimeBase & (2N-1))==0) {调用函数;} 2N是2的n次方等于2,4,8,16...这就相当于分频了,不需要额外的变量.对于不相干的几个定时时间可以加几个全局量timer1,timer2....就解决了,TimeBase++;后执行if (timer1!=0) timer1--;if (timer2!=0) timer2--;...用到的时候如果timerx==0定时时间到否则就是没到定时,要多少有多少啊.另外说明一点这里因为& (2N-1)所以才变得简单的,否则你势必要做一次除法取模数运算才能实现,比如你的时基是1ms你想要一个10ms的分频时间就不得不(TimeBase%10)==0了,10ms是必须的吗?你可以让它不是的.作为一个程序高手一遇到数字首先会想到的是2的N次方行不行,这样处理起来简单,机器最喜欢二进制记住这点省很多事.所以如果非特殊情况真的应该忘记十进制,特殊的情况就是要人机交互的地方你不得不显示成十进制给人家.如果人类天生就是8个手指头这个事情就好理解了.按键程序中的扫描函数调用不需要很快,因为100ms消抖还是105ms消抖其实都无所谓,这个能理解吧?间隔越小精度越高.能接受的最慢速度开销最小,实际调用频率设定为128HZ.

接下来看程序了...
    程序中的书写时给变量起个切实有意义的名字;保持标准的语言书写规范;必要的地方使用宏定义;添加必要的注释;这些花不了多少时间但是初学者没有这个习惯.其实良好的书写习惯不仅是为
了方便别人阅读和理解,更多的是为了你自己的积累.对于一个程序员来说最有价值的程序莫过于自己亲自调试编写过的代码了,自己写的程序过不了多长时间就会不记得了,即使大致的框架是有的但很多细节肯定是记不清了,程序中的注释能帮你很快的回忆起来,而且通过宏定义可以大大减少程序修改的工作量,这些可能的修改情况你之前就已经考虑过了.特别的是当你拿到一个项目或者和别人讨论一个项目时因为你好有这一部分代码,能实现那些功能你的心里是很有数的,稍微改改调试一下就能用了这是件再愉快都没有的事情了.如果你真的不想让别人看到你的源代码你可以采用编译成库文件的方式只把库文件给他,没必要难为自己.

/********************key.h**************************/
#define KEY_PORTX  P3  //按键io口
//键盘扫描码,此数值等于此按键按抬起时port口上的数值(抬起高电平,按下拉低)
#define KEY_1 C_Bit7
#define KEY_2 C_Bit6
#define KEY_3 C_Bit5
#define KEY_4 C_Bit4
#define KEY_5 C_Bit3
#define KEY_6 C_Bit2
#define KEY_CODE (KEY_1+KEY_2+KEY_3+KEY_4+KEY_5+KEY_6)
//说明:以下定义的所指时间均以Key_Scan调用的时间间隔为单位
//按键去抖时间和长按键计时单位 此数值不能大于127
#define KEY_DEJITTER_TIME  16
//长时间按连发功能使用
#define PRESS_KEY_LONGTIME_MESSAGE //如果不用注释掉本行即可
//长按键计时以消抖以后开始计算//长按键时,按下按键每超过一次(下值*按建消抖时间)时就返回一个带长按标记的Key_ID值
#define PRESS_KEY_LONGTIME_VALUE 16  //必须为2的n次整数
#define PRESS_KEY_LONGTIME_FLAG  ~KEY_CODE //所有其它未使用的按键位均为标记
  
extern void  Key_Scan(void); //按键扫描
extern uint8 Key_Get(void); //按键获取
//下面两个变量只在程序调试时引出
extern uint8  Press_Key_ID; //按键值
extern int8 Press_Key_Time; //持续按键时间

/********************key.c**************************/
#include "51.h"
#include "Key.h"
#ifdef PRESS_KEY_LONGTIME_MESSAGE
uint8 KeyTimeBase;
#endif
uint8 Press_Key_ID=0; //按键扫描码
int8  Press_Key_Time=-KEY_DEJITTER_TIME;
//键盘扫描
void Key_Scan(void)
{
uint8 tmp=0;
#ifdef PRESS_KEY_LONGTIME_MESSAGE
KeyTimeBase++;
#endif
//非消抖时间键盘扫描,置建值
if (Press_Key_Time==-KEY_DEJITTER_TIME || Press_Key_Time>=0)
{
  //单行按键,就一句,如果是4*4按键,用扫描代码替换这句,总之这里tmp就是新的扫描结果
  tmp = ~KEY_PORTX & KEY_CODE;
  if (tmp==0)
  {//无键按下
   Press_Key_ID=0;
   Press_Key_Time=-KEY_DEJITTER_TIME;
  }
  else
  //有键按下
    if (Press_Key_ID != tmp)  //按键状态有改变
   {
    Press_Key_ID = tmp;
       Press_Key_Time=-KEY_DEJITTER_TIME+1; //进入消抖期
   }
  //常有状态保持不变Press_Key_Time=0待取
}
// 消抖期键码不变 keytime++
else
  Press_Key_Time++;
}
//获取键值,如果有按键返回键码(已消抖)
//如果长按按键每隔一段时间返回PRESS_KEY_LONGTIME_FLAG标记的扫描码
uint8 Key_Get(void)
{
uint8 ret=0;
if (Press_Key_ID && Press_Key_Time>=0)
  if (Press_Key_Time==0) //有待取按键
  {
   ret=Press_Key_ID;
   Press_Key_Time++;
#ifdef PRESS_KEY_LONGTIME_MESSAGE
   KeyTimeBase=0;
  }
  else //>0 有长按键
  {
  //每个消抖时间Press_Key_Time+1
   if (KeyTimeBase >= KEY_DEJITTER_TIME)
   {
    KeyTimeBase=0;
    Press_Key_Time++;
    //长按
    if ((Press_Key_Time & (PRESS_KEY_LONGTIME_VALUE-1)) ==0)
     ret=Press_Key_ID|PRESS_KEY_LONGTIME_FLAG;
   }
#endif
  }
return ret;
}
/********************main.c*************************/
//128HZ调用,驱动扫描
Key_Scan();
//头文件中重新定义按键名称比如 #define KEY_LEFT  KEY_1其它的省略了...
//需要按键处理的地方
  switch (Key_Get()) {
   case KEY_LEFT:
   break;
   case KEY_RIGHT:
   break;
   case KEY_UP:
   break;
   case KEY_DOWN:
   break;
   case PRESS_KEY_LONGTIME_FLAG|KEY_UP: //长按温度设置快加
    C1Temp_Setting+=10;
   break;
   case PRESS_KEY_LONGTIME_FLAG|KEY_DOWN: //长按温度设置快减
    C1Temp_Setting-=10;
   break;
   case KEY_SHIFT:
   
   break;
   case KEY_ENTER:   
   break;
   case PRESS_KEY_LONGTIME_FLAG|KEY_ENTER:   
   break;
   case KEY_SHIFT|KEY_LEFT: //组合键置温度设置
   
   break;
   case KEY_SHIFT|KEY_RIGHT: //组合键置温度报警范围
   
   break;
   case KEY_SHIFT|KEY_UP:  //组合键置延时
   
   break;
   case KEY_SHIFT|KEY_DOWN: //组合键置
   
   break;
  }//end switch

教程就到这里了,一点经验之谈希望对大家有用.



[ 本帖最后由 huo_hu 于 2013-4-15 08:32 编辑 ]

Key.rar

10.56 KB, 下载次数: 168

Key_Lib

此帖出自51单片机论坛

最新回复

“不要只会把程序写成if(事件) {处理;}的结构,当程序复杂了你会陷入绝境的.通过全局变量或位变量可以把事件和处理分割开,把程序按一定的组件分块编写通过函数完成功能这才是重要的” 这段写得绝对精彩  详情 回复 发表于 2018-4-15 17:36

点评

楼主这个真心不错  详情 回复 发表于 2013-4-18 10:16
小东西,大学问的感觉 :)  详情 回复 发表于 2013-4-12 17:56
点赞 关注(8)
 

回复
举报

2781

帖子

417

TA的资源

五彩晶圆(中级)

沙发
 
不错啊
此帖出自51单片机论坛
 
个人签名
 
 

回复

2万

帖子

74

TA的资源

管理员

板凳
 

回复 楼主 huo_hu 的帖子

小东西,大学问的感觉
此帖出自51单片机论坛
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
个人签名

加油!在电子行业默默贡献自己的力量!:)

 
 

回复

135

帖子

0

TA的资源

纯净的硅(初级)

4
 
待续.................................................
此帖出自51单片机论坛
 
 
 

回复

954

帖子

0

TA的资源

纯净的硅(初级)

5
 
顶起。。。。。。。。。。。。
此帖出自51单片机论坛
 
 
 

回复

2

帖子

0

TA的资源

一粒金砂(初级)

6
 
刚好要写按键处理的程序,坐听楼主高论。
此帖出自51单片机论坛
 
 
 

回复

31

帖子

0

TA的资源

一粒金砂(中级)

7
 
梦做到一半正在高潮时突然醒了
此帖出自51单片机论坛
 
 
 

回复

1149

帖子

3

TA的资源

五彩晶圆(初级)

8
 
支持下,很实用!
此帖出自51单片机论坛
 
 
 

回复

5276

帖子

5

TA的资源

裸片初长成(中级)

9
 
最好用个流程框图
没耐心呀
此帖出自51单片机论坛

点评

等我整理一下再发个LIB库工程打包,直接用就是了  详情 回复 发表于 2013-4-13 23:15
 
个人签名没工作,没女人老婆,没宽带 ,  没钱
 
 

回复

4008

帖子

0

TA的资源

版主

10
 

回复 9楼 wangfuchong 的帖子

等我整理一下再发个LIB库工程打包,直接用就是了
此帖出自51单片机论坛

点评

开源一起研究怎样!!!!!!!!!!  详情 回复 发表于 2013-4-14 23:22
 
 
 

回复

3404

帖子

6

TA的资源

裸片初长成(初级)

11
 
我以前用定时器做过,没有做组合键,做了连跑功能。用着也不错,不过没有楼主的这个强大!顶起……
此帖出自51单片机论坛
 
 
 

回复

135

帖子

0

TA的资源

纯净的硅(初级)

12
 

回复 10楼 huo_hu 的帖子

开源一起研究怎样!!!!!!!!!!
此帖出自51单片机论坛
 
 
 

回复

7

帖子

0

TA的资源

一粒金砂(中级)

13
 
我是菜鸟。。想问一下,
你先#define KEY_DEJITTER_TIME  16
然后
if (Press_Key_ID != tmp)  //按键状态有改变
    {
     Press_Key_ID = tmp;
        Press_Key_Time=-KEY_DEJITTER_TIME+1; //进入消抖期
    }
   //常有状态保持不变Press_Key_Time=0待取

这里面
        Press_Key_Time=-KEY_DEJITTER_TIME+1;
不是每次进入if,都是Press_Key_Time=-16+1么?
可能出现Press_Key_Time=0的情况吗?
求指教
此帖出自51单片机论坛
 
 
 

回复

4008

帖子

0

TA的资源

版主

14
 
Press_Key_Time无按键时值为-16,一旦+1为-15则进入消抖期,所以前面执行键扫描的条件是==-16
if (Press_Key_Time==-KEY_DEJITTER_TIME || Press_Key_Time>=0) 进入消抖期就不执行键扫了
如果消抖结束则Press_Key_Time==0再次进入键扫,此后一直是0直到有一个KeyGet函数到来将其+为1之后每次键扫+1...
此帖出自51单片机论坛
 
 
 

回复

11

帖子

0

TA的资源

一粒金砂(初级)

15
 
思想很重要,代码有点难理解啊
此帖出自51单片机论坛
 
 
 

回复

4

帖子

0

TA的资源

一粒金砂(初级)

16
 

回复 楼主 huo_hu 的帖子

楼主这个真心不错
此帖出自51单片机论坛
 
 
 

回复

6

帖子

0

TA的资源

一粒金砂(中级)

17
 

LIB库工程

LIB库工程,有时间吗?希望上传啊
此帖出自51单片机论坛

点评

附件里有  详情 回复 发表于 2013-10-17 11:48
 
 
 

回复

4008

帖子

0

TA的资源

版主

18
 

回复 17楼wkm5135 的帖子

附件里有
此帖出自51单片机论坛
 
 
 

回复

4008

帖子

0

TA的资源

版主

19
 
最近更新的按键程序,用了位变量不用keyget了.

#include "REG52.h"                //C52基本寄存器头文件

//数据类型重新定义
typedef  unsigned char      boolean;     // Boolean value type
typedef  unsigned long int  uint32;      // Unsigned 32 bit value
typedef  unsigned short     uint16;      // Unsigned 16 bit value
typedef  unsigned char      uint8;       // Unsigned 8  bit value
typedef  signed long int    int32;       // Signed 32 bit value
typedef  signed short       int16;       // Signed 16 bit value
typedef  signed char        int8;        // Signed 8  bit
//位值定义
#define C_Bit0                0x01
#define C_Bit1                0x02
#define C_Bit2                0x04
#define C_Bit3                0x08
#define C_Bit4                0x10
#define C_Bit5                0x20
#define C_Bit6                0x40
#define C_Bit7                0x80
//51扩展存储访问数据类型
#define CBYTE ((unsigned char volatile code  *) 0)
#define DBYTE ((unsigned char volatile data  *) 0)
#define PBYTE ((unsigned char volatile pdata *) 0)
#define XBYTE ((unsigned char volatile xdata *) 0)
#define CWORD ((unsigned int volatile code  *) 0)
#define DWORD ((unsigned int volatile data  *) 0)
#define PWORD ((unsigned int volatile pdata *) 0)
#define XWORD ((unsigned int volatile xdata *) 0)
//空操作
extern void _nop_ (void);

/****************************按键接口定义                     ****************************************/
#define KEY_PORTX                                                        P1                                //按键io口
//按键位值,没有用0
#define KEY_1                                                                 C_Bit1
#define KEY_2                                                                 C_Bit2
#define KEY_3                                                                 C_Bit3
#define KEY_4                                                                 C_Bit4
#define KEY_5                                                                 0                                                //
#define KEY_6                                                                 0                                                //
//从端口读取按键执(有按键0无按键1)
#define GET_KEY_ID()                                        (~KEY_PORTX&(KEY_1+KEY_2+KEY_3+KEY_4+KEY_5+KEY_6))

#define TIME_1S_VALUE                                168                //1S计数值,KeyScan()调用周期单位
#define KEY_LONGTIME_VALUE                        4                //长按几秒产生一次event        (0~15)
//字符串转换函数识别串结束的标志
#define CHG_FUN_BUFENDCHR        'S'

extern uint8 Key_Event;               
extern bit Key_Event_Down;                        //按下
extern bit Key_Event_Up;                        //抬起
extern bit Key_Event_Flag;                        //有按键事件,需要调用key_get

extern uint8 Press_Key_ID;                        //按键事件的报告值
extern uint8 Key_Port_State;                //消抖后的状态值
extern uint8 Key_Port_Compare;        //状态比较值
extern uint8 Key_Time;                                        //按键时间       
extern void Key_Scan(void);
bdata uint8 Key_Event=0;                        //按键事件位
//flag:down:up=100长按键,110dowm,101up
sbit Key_Event_Flag                                        = Key_Event^0;        //此位置1状态有效,处理后清除此位避免重复
sbit Key_Event_Down                                        = Key_Event^1;        //按下
sbit Key_Event_Up                                        = Key_Event^2;        //抬起
sbit Key_Dejitter_End                                = Key_Event^3;        //此位=1:消抖结束

//4567位用做秒计数
#define KEY_1S_TIMECOUNT                        Key_Event+=0x10;        //1秒累计
#define KEY_EVENT_LONGTIMELOG                (Key_Event>=(KEY_LONGTIME_VALUE<<4))
#define KEY_LONGTIME_COUNTCLEAR                 Key_Event&=0x0f;

uint8 Press_Key_ID=0;                //按键事件的报告值
uint8 Key_Port_State=0;                //消抖后的状态值
uint8 Key_Port_Compare=0;        //状态比较值
uint8 Key_Time=0;                        //按键时间

//键盘扫描
void Key_Scan(void) {
        uint8 tmp=GET_KEY_ID();        //获取新的按键值
        //按键值和前次扫描结果比较,如果不同置消抖初始状态
        if (Key_Port_Compare != tmp) {//按键状态有改变
                Key_Event=0;//重新开始,状态清除,
                Key_Time=0;
                Key_Port_Compare = tmp;
                return;
        }
        //比较结果相同
        Key_Time++;
        if (Key_Dejitter_End==0) {//消抖未结束,时间继续累加
                if (Key_Time>=8) {//消抖结束
                        //刷新按键事件
                        Key_Dejitter_End=1;Key_Event_Down=0;Key_Event_Up=0;Key_Event_Flag=0;
                        //置事件类型和按键值               
                        if (Key_Port_Compare==Key_Port_State) {
                                KEY_1S_TIMECOUNT;
                                if (KEY_EVENT_LONGTIMELOG) {
                                        Key_Event_Flag=1;
                                        KEY_LONGTIME_COUNTCLEAR;
                                }
                                Press_Key_ID=Key_Port_Compare;        //无按键id=0
                                return;
                        }
                        if (Key_Port_Compare>Key_Port_State) {
                                Key_Event_Down=1;Key_Event_Flag=1;
                                Press_Key_ID=Key_Port_Compare-Key_Port_State;                        //更新值
                                Key_Port_State=Key_Port_Compare;
                        } else {//<0
                                Key_Event_Up=1;Key_Event_Flag=1;
                                Press_Key_ID=Key_Port_State-Key_Port_Compare;                        //更新值
                                Key_Port_State=Key_Port_Compare;
                        }                       
                }
        return;
        }
        if (Key_Time==TIME_1S_VALUE) {
                Key_Dejitter_End=0;
                Key_Time=0;
        }
}

此帖出自51单片机论坛
 
 
 

回复

19

帖子

4

TA的资源

一粒金砂(初级)

20
 
好好好
此帖出自51单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表