【STM32】按键单、双、三击、长按
1 前言
本人于去年这个时候准备了蓝桥杯嵌入式的比赛,其中涉及了按键的单击和长按,但是似乎双击和三击一直没考过。
一直基于火花页大佬的文章进行备战,其有一篇讲述了如何实现按键的双击与长按,链接如下:【STM32G431RBTx】备战蓝桥杯嵌入式→基本模块→KEY→长按(持续响应)以及双击_stm32怎么按键按一下就持续-CSDN博客
最近由于项目需要使用按键三击功能,网上众多代码无法真正实现,因此基于双击的逻辑进行了进一步扩展。
2 CUBEMX配置
以STM32G474VET6为例进行配置,配置一个定时器和按键输入引脚即可,主频32MHz。
定时器配置
配置定时器TIM3,10ms进行一次中断,记得打开NVIC中断!
按键输入引脚配置
配置PD6为按键输入引脚,按键另一侧接地,按下则输入为低电平。
3 代码部分
myinterrupt.c文件:
#include "myinterrupt.h"
#define LONG_PRESS_TIME 70 // 长按时间阈值(700ms)
#define DOUBLE_CLICK_WINDOW 20 // 双击时间窗口(200ms)
#define TRIPLE_CLICK_WINDOW 20 // 三击时间窗口(200ms)
struct keys key[4] = {0};
extern bool tri_sta;
unsigned char _10ms_cnt;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
_10ms_cnt++;
if(_10ms_cnt>=10)
{
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_result,10);
_10ms_cnt = 0;
}
if (htim->Instance == TIM3)
{
key[0].key_sta = HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_6);
switch (key[0].judge_sta)
{
case 0: // 初始状态
{
if (key[0].key_sta == 0)
{
key[0].judge_sta = 1;
key[0].key_time = 0; // 初始化按键持续时间
}
}
break;
case 1: // 按键按下确认状态
{
if (key[0].key_sta == 0)
{
key[0].judge_sta = 2; // 确认按下,进入下一个状态
}
else
{
key[0].judge_sta = 0; // 按键抖动或松开,回到初始状态
}
}
break;
case 2: // 检测按键持续状态
{
if (key[0].key_sta == 1 && key[0].key_time < LONG_PRESS_TIME)
{
if (key[0].triple_click_timerEN == 1 && key[0].triple_click_time <= TRIPLE_CLICK_WINDOW)
{
key[0].triple_key_flag = 1; // 触发三击事件
key[0].triple_click_timerEN = 0; // 停止三击计时器
}
else if (key[0].double_click_timerEN == 0)
{
key[0].double_click_timerEN = 1; // 开启双击计时器
key[0].double_click_time = 0;
}
else if (key[0].triple_click_timerEN == 0 && key[0].double_click_timerEN == 1)
{
key[0].triple_click_timerEN = 1; // 开启三击计时器
key[0].triple_click_time = 0;
}
key[0].judge_sta = 0; // 回到初始状态
}
else if (key[0].key_sta == 1 && key[0].key_time >= LONG_PRESS_TIME)
{
key[0].long_key_flag = 1; // 标记长按事件
key[0].judge_sta = 0; // 长按松开,回到初始状态
}
else
{
key[0].key_time++; // 按键持续时间累加
}
}
break;
}
// 双击计时器逻辑
if (key[0].double_click_timerEN == 1)
{
key[0].double_click_time++;
if (key[0].double_click_time >= DOUBLE_CLICK_WINDOW)
{
// 超时判定单击,但需要确保未进入三击状态
if (key[0].triple_click_timerEN == 0)
{
key[0].single_key_flag = 1; // 触发单击
}
key[0].double_click_timerEN = 0; // 停止双击计时器
}
}
// 三击计时器逻辑
if (key[0].triple_click_timerEN == 1)
{
key[0].triple_click_time++;
if (key[0].triple_click_time >= TRIPLE_CLICK_WINDOW)
{
key[0].double_key_flag = 1; // 超时判定双击
key[0].triple_click_timerEN = 0; // 停止三击计时器
}
}
}
}
myinterrupt.h头文件:
#ifndef __MYINTERRUPT_H_
#define __MYINTERRUPT_H_
#include "main.h"
#include "main.h"
#include "stdbool.h"
struct keys
{
unsigned char judge_sta; // 状态机状态
bool key_sta; // 按键状态
bool single_key_flag; // 单击事件标志
unsigned int key_time; // 按键持续时间
bool double_click_timerEN; // 双击计时器使能
unsigned int double_click_time; // 双击计时器时间
bool double_key_flag; // 双击事件标志
bool long_key_flag; // 长按事件标志
bool triple_click_timerEN; // 三击计时器使能
unsigned int triple_click_time; // 三击计时器时间
bool triple_key_flag; // 三击事件标志
};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
#endif
文件中配置了key结构体有四个,可以实现4个按键的检测,但代码中仅演示了1个按键,需要增加按键数量的只需要在定时器回调函数中添加for循环进行判断即可,前言中的链接中有相关代码,不会的可参考。
main.c文件:
#include "myinterrupt.h"
#include "stdbool.h"
extern struct keys key[4];
void key_func(void);
int main(void)
{
HAL_TIM_Base_Start_IT(&htim3);
while (1)
{
key_func();
}
}
/* USER CODE BEGIN 4 */
void key_func()
{
if(key[0].single_key_flag==1)//单击
{
//写逻辑
key[0].single_key_flag = 0;
}
if(key[0].double_key_flag==1)//双击
{
//写逻辑
key[0].double_key_flag = 0;
}
if(key[0].triple_key_flag==1)//三击
{
//写逻辑
key[0].triple_key_flag = 0;
}
if(key[0].long_key_flag==1)//长按
{
//写逻辑
key[0].long_key_flag = 0;
}
}
上述main.c删去了官方生成的代码,只放了需要自己写的代码,逻辑部分只需要在key_func中写 ,然后把此函数放进while(1) 循环。
4 实现
以一个LED为例进行演示,运行稳定。
按键单击、双击、三击演示视频
作者:tjd6666