单片机按键处理方式(一)——典型的按键处理方式

前言

  按键处理是学习单片机的必修课之一。一次按键的过程,并非是一个理想的有一定宽度的电平脉冲,而是在按下、弹起过程中存在抖动,只有在中间阶段电平信号是稳定的。一次典型的按键过程是酱紫的:

  在抖动过程中,电平信号高低反复变化,如果你的按键检测是检测下降沿或上升沿或者是用外部中断检测按键,都可能在抖动时重复检测到多次按键。这就是在未消抖的按一次键显示值加1的程序中,出现按一次键显示值+2、+3甚至加更多的原因。

  对于按键消抖,常用的有硬件消抖和软件消抖。本文是我个人对按键处理的一些常见方法的总结,由于我本人不太懂硬件,所以这里只讨论独立按键的软件消抖实现。水平有限,如有错误请不吝指正。

硬件环境

  本文代码均在单片机STC90C516RD+、晶振12.0MHz硬件环境下试验通过。

带消抖的简单的按键处理

  最简单的消抖处理就是在首次检测到电平变化后加一个延时,等待抖动停止后再次检测电平信号。这也是大多数单片机教程讲述的消抖方式。但在实际应用中基本不用这种方式,原因后面讲,先看代码:

//方法一:带消抖的简单的按键处理

#include <reg52.h>

#define GPIO_KEY P1    //8个独立按键IO口
#define GPIO_LED P0    //8个LED灯,用于显示键值

unsigned char ScanKey();
void DelayXms(unsigned char x);

void main()
{
    unsigned char key;
    GPIO_LED = 0x00;    //初始化LED
    while (1)
    {
        key = ScanKey();    //读取键值
        // if (0xff != key)    //若有键按下,则更新LED的状态
        GPIO_LED = ~key;    //点亮LED
    }
}

unsigned char ScanKey()
{
    unsigned char keyValue = 0xff;    //赋初值,0xff表示没有键按下
    GPIO_KEY = 0xff;                //给按键IO口置位
    if (0xff != GPIO_KEY)            //检查按键IO口的电平,如有键按下则不为0xff
    {
        DelayXms(15);                //延时15ms,滤掉抖动。一般按键的抖动时间在10ms~20ms
        if (0xff != GPIO_KEY)        //再次检查按键IO口的电平
        {
            keyValue = GPIO_KEY;    //重复检测后表明有键按下,读取键值
        }
        // while (0xff != GPIO_KEY) ;    //等待按键弹起
    }
    return keyValue;
}

void DelayXms(unsigned char X)
{
    unsigned char i, j;
    do
    {
        i = 2;
        j = 240;
        do
        {
            while (--j);
        } while (--i);
    } while (--X);
}

  可以看到,在首次检测到电平由1->0后,延时了10ms,等待抖动过去,然后再检测按键的电平。你也许已经注意到了,在延时10ms期间,单片机闲置了,就暂停在那里等待延时完成,这对处理能力本就紧张的单片机来说无疑是个巨大的浪费。特别是当你在用单片机同时运行数码管动态扫描等对时序要求高功能的时候,按键消抖延时期间程序暂停了,数码管也就熄灭了,严重影响显示效果。

利用定时器消抖的按键处理

  为了避免单纯Delay()消抖所产生的问题,可以采用定时器来进行延时,这样就不用让单片机在那里干等了。

  一种简单的实现方式是设置一个全局状态变量,用来标志定时器延时时间已到,在第一次检测到电平变化时开启定时器,检测到定时器延时时间已到时关闭定时器并再次进行按键检测。代码如下:

//方法二:定时器延时消抖的按键处理

#include <reg52.h>

#define GPIO_KEY P1    //8个独立按键IO口
#define GPIO_LED P0    //8个LED灯,用于显示键值

unsigned char timeUp = 0;        //标志位
unsigned char th0Value = (65536 - 15000) / 256;    //15ms的定时器初值高8位
unsigned char tl0Value = (65536 - 15000) % 256;    //15ms的定时器初值低8位

unsigned char ScanKey();
void InitialTimer0();

void main()
{
    unsigned char key;
    GPIO_LED = 0x00;    //初始化LED
    InitialTimer0();
    while (1)
    {
        key = ScanKey();    //读取键值
        if (0xff != key)    //若有键按下,则更新LED的状态
            GPIO_LED = ~key;    //点亮LED
    }
}

void InitialTimer0()    //12MHz
{
    ET0 = 1;            //打开定时器0
    EA = 1;                //打开系统总中断开关
    TMOD &= 0xF0;        //清空定时器0的工作模式参数
    TMOD |= 0x01;        //设置定时器0的工作模式为模式1,16位定时器
    TH0 = th0Value;        //设置定时高8位初值
    TL0 = tl0Value;        //设置定时低8位初值
    TF0 = 0;            //清除TF0溢出标志
    TR0 = 0;            //关闭定时器0
}

void Timer0Interrupt() interrupt 1
{
    TH0 = th0Value;        //设置定时高8位初值
    TL0 = tl0Value;        //设置定时低8位初值
    timeUp = 1;            //定时器标志位置1
}

unsigned char ScanKey()
{
    unsigned char keyValue = 0xff;    //赋初值,0xff表示没有键按下
    if (0 == TR0 && 0xff != GPIO_KEY)
    {
        timeUp = 0;            //定时器标志位置0
        TH0 = th0Value;        //设置定时高8位初值
        TL0 = tl0Value;        //设置定时低8位初值
        TR0 = 1;            //开启定时器0,开始计时
    }
    if (1 == timeUp)
    {
        TR0 = 0;            //关闭定时器0
        keyValue = GPIO_KEY;                //读取键值
    }
    return keyValue;
}

  另一种方法是利用定时器0,每2ms左右中断一次,在中断服务程序中进行多次按键检测,当检测到10次按键按下状态时,则认为发生了一次有效的按键按下动作。这种方式与上一种相比,进行了多次检测,提高了按键检测的准确性。实现代码如下:

//方法三:定时器多次检测的按键处理

#include <reg52.h>

#define GPIO_KEY P1    //8个独立按键IO口
#define GPIO_LED P0    //8个LED灯,用于显示键值

unsigned char keyCur = 0xff;        //暂存当前键值
unsigned char keyPress = 0;            //按键按下状态标识
unsigned char th0Value = (65536 - 2000) / 256;    //2ms的定时器初值高8位
unsigned char tl0Value = (65536 - 2000) % 256;    //2ms的定时器初值低8位

unsigned char ScanKey();
void InitialTimer0();

void main()
{
    unsigned char key;
    GPIO_LED = 0x00;    //初始化LED
    keyCur = 0xff;    //初始化
    InitialTimer0();
    while (1)
    {
        key = ScanKey();    //读取键值
        if (0xff != key)    //若有键按下,则更新LED的状态
            GPIO_LED = ~key;    //点亮LED
    }
}

void InitialTimer0()    //12MHz
{
    ET0 = 1;            //打开定时器0
    EA = 1;                //打开系统总中断开关
    TMOD &= 0xF0;        //清空定时器0的工作模式参数
    TMOD |= 0x01;        //设置定时器0的工作模式为模式1,16位定时器
    TH0 = th0Value;        //设置定时高8位初值
    TL0 = tl0Value;        //设置定时低8位初值
    TF0 = 0;            //清除TF0溢出标志
    TR0 = 1;            //开启定时器0
}

void Timer0Interrupt() interrupt 1
{
    static unsigned char counter = 0;            //辅助计数
    static unsigned char keyLast = 0xff;        //记录上一次扫描时的键值
    TH0 = th0Value;        //设置定时高8位初值
    TL0 = tl0Value;        //设置定时低8位初值

    keyCur = GPIO_KEY;    //暂存当前键值
    if (0xff != keyCur && keyCur == keyLast)    //当前扫描时有键按下且与上一次按下的一致,则累加
        counter ++;
    else
    {
        counter = 0;
        keyLast = keyCur;
    }

    if (10 == counter)    //连续10次均有键按下且按按键未变,则认为时一次有效的按键
        keyPress = 1;
    else
        keyPress = 0;
}

unsigned char ScanKey()
{
    if (1 == keyPress)
        return keyCur;            //读取键值
    else
        return 0xff;
}

  至此,简单的按键单击实现实现告一段落。但往往实际中,我们不只要实现单击,还要实现双击、长按、连发等等功能,特别是在那些小尺寸、无法设置多个按键的项目中,一个按键往往需要通过不同的操作实现不同的功能。要实现这些复杂的功能,就需要引入一种设计模式——有限状态机模式。敬请期待下一篇:单片机按键处理方式(二)——状态机按键实现单击、双击、长按、连发(挖坑,待填)

时间: 2024-09-29 02:34:32

单片机按键处理方式(一)——典型的按键处理方式的相关文章

用按键精灵写的手机端按键精灵批量加群的脚本

前几天朋友想在手机上面多加几个群,可是一个个的手工操作太难受了,于是就用按键精灵帮他写了一个自动加群的代码.分享给大家. KeepScreen True Delay 2000 Dim scrWidth, scrHeight,a,x,y,i,intX,intY scrWidth = GetScreenX() scrHeight = GetScreenY() 'ShowMessage scrWidth&"--"&scrHeight TracePrint scrWidth T

单片机ISP、IAP和ICP几种烧录方式的区别

单片机ISP.IAP和ICP几种烧录方式的区别 玩单片机的都应该听说过这几个词.一直搞不太清楚他们之间的区别.今天查了资料后总结整理如下. ISP:In System Programing,在系统编程 IAP:In applicating Programing,在应用编程 ICP:In Circuit Programing,在电路编程 ISP是指可以在板级上进行编程,也就是不用拆芯片下来,写的是整个程序,一般是通过ISP接口线来写. IAP虽然同样也是在板级上进行编程,但是是自已对自已进行编程,

ajax的post提交方式和传统的post提交方式哪个更快?

如果同时用ajax和post提交先执行哪个呢?是ajax返回后再执行post呢还是同时执行? ajax的post提交方式和传统的post提交方式哪个更快? >> php 这个答案描述的挺清楚的:http://www.goodpm.net/postreply/php/1010000007305642/ajax的post提交方式和传统的post提交方式哪个更快.html

按键抖动的处理方法(按键外部中断)

当把按键设为外部中断时,存在按键抖动问题: 一种情况是没有按按键的时候,按键有时也会因为震动等原因误触发按键事件,引起程序误判.对于这种按键抖动,解决方式有: 并上一个小电容(比如0.1uF)即可解决. 延时检测:检测到按键中断后延时50ms判断这个引脚电平,然后再做进一步处理. 还有一种情况是按下按键时,由于按键本身的结构或质量问题.操作者的发力问题等,在操作者按下一次按键的过程中,实际上触发了数次按下-抬起的动作,引起程序误判.对于这种按键抖动,解决方式有: 延时检测:检测到按键中断后延时5

按键精灵如何调用Excel及按键精灵写入Excel数据的方法教程---入门自动操作表格

首先来建立一个新的Excel文档,在桌面上点击右键,选择[新建]-[Excel工作表],命名为[新手学员]. 现在这个新Excel文档是空白的,我们接下来会通过按键精灵的脚本来打开并写入一些数据.打开按键精灵软件,点击[新建],进入我的脚本界面,再点击进入[全部命令].在[全部命令]中选择[插件命令]-[office办公文档插件]-[打开Excel文档].在命令的下面可以看到命令的详细设置,点击[路径].在弹出窗口中选择[新手学员]的Excel文档,点击打开.可以在命令参数中看到引用的文档,点击

nRF52832 矩阵按键调试 同一列上的按键 任意两个按键 按下 检测不到低电平(电平拉不下来)

参考链接:https://blog.csdn.net/zhanghuaishu0/article/details/78505045 调试过程中发现 同一列上的按键 任意两个按键 按下 检测不到低电平(电平拉不下来),在网上找到一个类似的 资料说是:pin脚初始化时,配置的驱动能力不够.修改后测试正常了. 原GPIO初始化如下: nrf_gpio_cfg_output(GPIO0); 修改后如下: nrf_gpio_cfg( pin_number, NRF_GPIO_PIN_DIR_OUTPUT,

ESA2GJK1DH1K升级篇: 移植远程更新程序到STM32F103RET6型号的单片机,基于(GPRS模块AT指令TCP透传方式)

前言 上节实现远程更新是更新的STM32F103C8T6的单片机 GPRS网络(Air202/SIM800)升级STM32: 测试STM32远程乒乓升级,基于(GPRS模块AT指令TCP透传方式),定时访问升级 这节将告诉大家如何移植到其它型号的单片机. 这一节以 STM32F103RET6 (512KB Flash 64KB RAM) 为例 我使用我的这块板子 大家测试的时候可以按照下面的方式接到自己的GPRS模块(Air202 / SIM800) 单片机串口1 接到GPRS的AT指令配置串口

[转] jQuery按键响应事件keypress对应的按键编码keycode

原文地址:http://blog.csdn.net/chenhj1988918/article/details/7534922 keypress  api 文档:http://api.jquery.com/keypress/ event.keycode值大全 1 keycode 8 = BackSpace BackSpace 2 keycode 9 = Tab Tab 3 keycode 12 = Clear 4 keycode 13 = Enter 5 keycode 16 = Shift_L

键盘钩子监测按键后,获取键码及按键名称(MFC)

LRESULT CALLBACK LowLevelKeyboardProc(int nCode,WPARAM wParam,LPARAM lParam){ if(nCode ==HC_ACTION && wParam == WM_KEYDOWN) { KBDLLHOOKSTRUCT *kblp=(KBDLLHOOKSTRUCT*)lParam; CString temp; DWORD dwvk = kblp->vkCode; DWORD dwMsg = 1; dwMsg += kbl