乐趣区

关于嵌入式:MCU软件最佳实践独立按键

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");
         }
     }
    }

退出移动版