1. 引子

在进行mcu驱动和利用开发时,常常会遇到独立按键驱动的开发,独立按键仿佛是每一个嵌入式工程师的入门必修课。笔者翻阅了许多书籍(包含上大学时候用的书籍)同时查阅了网上许多网友的博客,无一例外,清一色采纳检测、延时、再检测,千篇一律,仿佛是按键过于简略,因而没有人违心新陈代谢。
本文介绍一种独立按键驱动,打消抖动不采纳软件延时(让CPU死等,mcu无休止的运行_nop_()函数,在略微简单利用场景,cpu工夫顾此失彼的场合,一个独立按键便让CPU如此耗力,是十分不明智的)。本驱动短小精悍,能够是实用于裸机利用,也可不便移植到rtos。

2. 思路

剖析独立按键按下的过程,分为稳固状态非稳固状态
依据经验值,抖动打消工夫为10ms左右;
累计检测到超过10ms按下状态,视为按键按下;
累计检测到超过10ms弹起状态,视为按键弹起;

2.1 按键驱动key.c

/** * @file: key.c * @brief: 按键驱动实现 */#include <reg52.h>#define STAT_RELEASED     (1)#define STAT_PRESSED      (0)unsigned char (*keyfn)(void);// 利用提供本函数实现,返回1示意读到高电平,返回0示意读到低电平unsigned char flg_down;unsigned char flg_up;unsigned char stat; // 默认是弹起状态unsigned char timer; void key_init(unsigned char (*fn)(void)){    stat = STAT_RELEASED;    keyfn = fn;} void key_scan(void){    if(keyfn && !keyfn()) // 有按键按下    {        if(timer < 1)  // 非稳态        {            timer++;            return;        }        if(stat == STAT_RELEASED)        {            stat = STAT_PRESSED;            // post down event            flg_down = 1;        }    }    else    {        if(timer > 0) // 非稳态        {            timer--;            return;        }        if(stat == STAT_PRESSED)        {            stat = STAT_RELEASED;            // post up event            flg_up = 1;        }    }}

2.2 按键对外接口key.h

/** * @file:key.h * @brief:按键驱动对外接口 */ /**  * @function: key_init  * @param fn: 函数指针,有返回值,读到高电平返回1,读到低电平返回0  * @brief: 按键模块初始化函数,传递io电平读取函数到按键模块  */extern void key_init(unsigned char (*fn)(void));extern unsigned char flg_down;extern unsigned char flg_up;extern void key_scan(void); // 10ms周期调用

3 试验

假如单片机P2.1口为按键;
按下和弹起时,串口打印输出

为了不便试验,增加串口驱动、定时器驱动程序。

3.1 串口驱动

/** * @file: uart.c * @brief: 串口驱动,波特率9600bps,10bit模式 * */#include <reg52.h> void uart_init(void){    SCON = 0X50; // 10bit 可变波特率模式        //T1: SM1SM0=10,8bit auto reload,波特率9600bps    TMOD = (TMOD & 0X0F) | (1 << 5);    TH1 = TL1 = 0XFD;    TR1 = 1;        ES = 1;    EA = 1;    TI = 1; // start transmit if using putchar provided by c51 lib} void uart_isr(void) interrupt 4{    if(RI)    {        RI = 0;    }    if(TI)    {    }}
/** * @file: uart.h */// 串口模块extern void uart_init(void);

3.2 定时器驱动

/** * @file: timer0.c * @brief: 产生10ms事件 */#include <reg52.h>int systick;unsigned char flg_10ms;unsigned char flg_50ms;unsigned char flg_sec;void timer_init(unsigned char ms){    TMOD = (TMOD & 0XF0);  // 模式0:13bit 定时器模式,最大计数值8192    TH0 = (8192 - ms * 1000) / 32; // TH0的8位保留13bit初值的高8bit    TL0 = (8192 - ms * 1000) % 32; // TL0的低5位用来存储13bit初值得低5bit        TR0 = 1;        ET0 = 1;    EA = 1;}  void timer_isr(void) interrupt 1{    TR0 = 0;    timer_init(1);        systick++;    if(systick % 10 == 0)    {        flg_10ms = 1;        if(systick % 50 == 0)        {            flg_50ms = 1;            if(systick % 1000 == 0)            {                flg_sec = 1;            }        }    }}
/** * @file:timer0.h */// 定时器模块extern unsigned char flg_10ms;extern unsigned char flg_50ms;extern unsigned char flg_sec;extern void timer_init(unsigned char ms);

3.3 编写利用

  1. 初始化串口、定时器、按键模块
  2. 10ms为周期扫描按键
  3. 监测按键事件并解决

    #include <reg52.h>#include <timer0.h>#include <uart.h>#include <key.h>#include <stdio.h>sbit button = P2^1; unsigned char kfn(void){ return button? 1: 0;} void main(void){ uart_init(); timer_init(1); key_init(kfn); while(1) {     if(flg_10ms)     {         flg_10ms = 0;         key_scan(); // 10ms扫描1次     }     if(flg_down)     {         flg_down = 0;         printf("key pressed\r\n");     }     if(flg_up)     {         flg_up = 0;         printf("key released\r\n");     } }}