单片机按键的定时器消抖处理

时间:2022-09-22 19:42:58

使用定时器计时,给按键的按下、抬起的计时消抖,在大循环while里不堵塞,可以同时检测到每个按键各自的“按下”“长按”“抬起”的状态。在此基础上,按键的长按一秒、两秒三秒,按键的双击,组合键功能(例如Ctrl+C)等已经很容易实现了。
button.c和button.h直接添加到工程里,编译然后修改到对应的布尔变量。user.c是应用文件。

一、源文件button.c

按键操作。不同的编译器里布尔变量类型的几种写法:bit - bool - _Bool - unsigned char。
函数Btn_InitNull(BtnControl_pst pBtn)对按键参数初始化,设置按键初始状态为“无动作”。
函数Btn_InitPress(BtnControl_pst pBtn)对按键参数初始化,设置按键初始状态为“长按”。
函数Btn_Scan(BtnControl_pst pBtn, _Bool bPin)是在main()的大循环里检测按键状态。

#include "button.h"



static _Bool Btn_Shake(BtnControl_pst pBtn, uint8_t site);



/**************************************************************************
Function:    Btn_InitNull
Description: 初始化按键参数,状态设为:S_NULL(无动作)
Input:       - pBtn 按键操作结构体
Return:      no
**************************************************************************/
void Btn_InitNull(BtnControl_pst pBtn)
{
 pBtn->status = S_NULL;
 pBtn->bAction = 0;
 pBtn->bDelay = 0;
}
/**************************************************************************
Function:    Btn_InitPress
Description: 初始化按键参数,状态设为:S_PRESS(长按)
Input:       - pBtn 按键操作结构体
Return:      no
**************************************************************************/
void Btn_InitPress(BtnControl_pst pBtn)
{
 pBtn->status = S_PRESS;
 pBtn->bAction = 1;
 pBtn->bDelay = 0;
}
/**************************************************************************
Function:    Btn_Scan
Description: 获取按键状态
Input:       - pBtn 按键操作结构体
 - bPin 按键引脚逻辑值
Return:      no
**************************************************************************/
void Btn_Scan(BtnControl_pst pBtn, _Bool bPin)
{
 if (!bPin)
 {
 if (pBtn->bAction) // 上一状态是按下的
 {
 pBtn->status = S_PRESS; // ===长按===
 }
 else if (Btn_Shake(pBtn, SITE_DOWN)) // 消抖延时(记忆“按下”位置)
 {
 pBtn->bAction = 1;
 pBtn->status = S_DOWN; // ===按下===
 }
 }
 else if (pBtn->bAction)
 {
 if (S_DOWN == pBtn->status)
 {
 pBtn->status = S_PRESS; // ===长按===
 }
 if (Btn_Shake(pBtn, SITE_UP)) // 消抖延时(记忆“抬起”位置)
 {
 pBtn->bAction = 0;
 pBtn->status = S_UP; // ===抬起===
 }
 }
 else
 {
 pBtn->status = S_NULL; // ===无动作===
 }
}
/**************************************************************************
Function:    Btn_Shake
Description: 使用定时器中断,做按键消抖延时
Input:       - pBtn 按键控制结构体
 - site 按键位置
Return:      执行标志
**************************************************************************/
static _Bool Btn_Shake(BtnControl_pst pBtn, uint8_t site)
{
 // 开启中断延时消抖
 if (!pBtn->bDelay)
 {
 pBtn->bExe = 0;
 pBtn->num = 0;
 pBtn->val = 0;
 pBtn->bDelay = 1; // 置位延时标志
 pBtn->site = site; // 保存按键电平
 }
 // 抖动处理
 else if (pBtn->site != site) // 检测是否抖动
 {
 pBtn->bDelay = 0; // 清零延时标志
 }
 // 按键动作处理
 else if (pBtn->bExe)
 {
 pBtn->bExe = 0; // 清零执行标志
 if (++(pBtn->num) >= NUM_SHAKE) // 消抖n次
 {
 pBtn->bDelay = 0; // 清零计时标志
 return 1; // ===执行===
 }
 }
 return 0; // ===不执行===
}

二、头文件button.h

定义了消抖时间,按键类型。

#ifndef _BUTTON_H_
#define _BUTTON_H_


#include "includes.h"



// 时间常数(最大为255)
#define TIMER_1MS_SHAKE_BTN 1 // 1 * 5 = 5ms 实际为4 ~5ms
#define TIMER_10MS_SHAKE_BTN 1 // 10 * 5 = 50ms 实际为40 ~50ms
#define TIMER_10MS_SHAKE_100MS 2 // 20 * 5 = 100ms 实际为90 ~100ms
#define TIMER_10MS_SHAKE_300MS 6 // 60 * 5 = 300ms 实际为290 ~300ms
#define TIMER_10MS_SHAKE_500MS 10 // 100 * 5 = 500ms 实际为490 ~500ms
#define TIMER_10MS_SHAKE_700MS 14 // 140 * 5 = 700ms 实际为690 ~700ms
#define TIMER_10MS_SHAKE_1200MS 24 // 240 * 5 = 1200ms 实际为1190 ~1200ms
#define TIMER_10MS_SHAKE_3000MS 60 // 600 * 5 = 3000ms 实际为2990 ~3000ms
#define TIMER_10MS_SHAKE_5000MS 100 // 1000 * 5 = 5000ms 实际为4990 ~5000ms
#define TIMER_10MS_SHAKE_12S 240 // 2400 * 5 = 12000ms 实际为11990~12000ms

// 消抖次数
#define NUM_SHAKE 5



// 单按键状态status
typedef enum BtnStaSingle
{
    S_NULL,               // 无动作
    S_DOWN,               // 按下
    S_PRESS,              // 长按
    S_UP                  // 抬起
} BtnStaSingle_st, *BtnStaSingle_pst;

// 按键位置
#define SITE_DOWN 0 // 按下
#define SITE_UP 1 // 抬起

// 按键控制
typedef struct BtnControl
{
    uint8_t status:2;     // 按键状态(枚举BtnStaSingle)
    uint8_t bAction:1;    // 动作标志
    uint8_t bDelay:1;     // 消抖延时标志
    uint8_t bExe:1;       // 消抖延时执行标志
    uint8_t site:1;       // 按键位置
    uint8_t num;          // 消抖次数[0,255]
    uint8_t val;          // 消抖延时计算值[0,255]
} BtnControl_st, *BtnControl_pst;



void Btn_InitNull(BtnControl_pst pBtn);
void Btn_InitPress(BtnControl_pst pBtn);
void Btn_Scan(BtnControl_pst pBtn, _Bool bPin);



/************************************************************************** Function: Timer_BtnShake Description: 按键消抖计时宏函数 Input: - stBtn 按键变量 - tDown 按下消抖时间 - tUp 抬起消抖时间 Return: no **************************************************************************/
#define Timer_BtnShake(stBtn, tDown, tUp) \
{                                                \
    if ((stBtn).bDelay)                          \
    {                                            \
        ++(stBtn).val;                           \
        switch ((stBtn).status)                  \
        {                                        \
        case S_NULL:                             \
            if ((stBtn).val >= (tDown))          \
            {                                    \
                (stBtn).val  = 0;                \
                (stBtn).bExe = 1;                \
            }                                    \
            break;                               \
        case S_PRESS:                            \
            if ((stBtn).val >= (tUp))            \
            {                                    \
                (stBtn).val  = 0;                \
                (stBtn).bExe = 1;                \
            }                                    \
            break;                               \
        default:                                 \
            break;                               \
        }                                        \
    }                                            \
}



#endif /* _BUTTON_H_ */

三、应用文件user.c

1、设置单片机定时器,写中断服务函数。
2、自定义按键变量,初始化按键变量参数。
3、在中断服务里调用按键消抖计时宏函数。
4、在while里扫描按键状态,根据得到的状态值做处理。
程序里做了2、3、4步骤,第1步根据不同的单片机去写。

// 端口设置
#define PIN_BUTTON RA0 // 按键



volatile BtnControl_st g_stBtn;



/**************************************************************************
Function:    main
Description: 主函数
Input:       no
Return:      no
**************************************************************************/
void main(void)
{
 Btn_InitNull(&g_stBtn); // 按键状态初始设为“无动作”

 while (1)
 {
 OperateProc(); // 按键操作
 }
}
/**************************************************************************
Function:    Isr
Description: 定时器中断服务 1ms
Input:       no
Return:      no
**************************************************************************/
void interrupt Isr()
{
 // ==================按键消抖延时==================
 Timer_BtnShake(g_stBtn, TIMER_1MS_SHAKE_BTN, TIMER_1MS_SHAKE_BTN);
}
/**************************************************************************
Function:    OperateProc
Description: 操作处理
Input:       no
Return:      no
**************************************************************************/
void OperateProc(void)
{
 Btn_Scan(&g_stBtn, PIN_BUTTON); // 按键扫描

 switch (g_stBtn.status)
 {
 case S_DOWN: // ===按下===
 break;
 case S_PRESS: // ===长按===
 break;
 case S_UP: // ===抬起===
 break;
 default:
 break;
 }
}