使用定时器计时,给按键的按下、抬起的计时消抖,在大循环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;
}
}