HAL STM32通用定时器+EXTI实现单击/双击/长按功能
- ✨在使用USB功率计的时候,发现上面的一个按键实现多画面功能切换,于是探索了一下是如何实现的,将其实现的基本思路以及综合网上收集的相关实现方法,粗陋的整理了一下,将其基本功能实现了一下,具体的功能没有做,只是做了一个简单的框架。
🛠HAL STM32CubeMX工程配置
-
🌿基于
STM32F103
芯片。 -
🌿主要配置一个外部中断按键引脚。
-
🔖GPIO引脚按键外部使用电路参考:
-
🌿配置一个定时器
-
🌿NVIC中断使能以及优先级配置。
-
🌿其他串口和I2C配置用于显示和调试输出。(这里根据个人需求配置,个人推荐还是使用软件I2C实现比较好,硬件I2C如果程序比较复杂,cpu处理各种中断,容易导致硬件I2C出错,导致显示画面异常的情况。)
-
🌿其他时钟配置和工程生成就不展示了,根据个人情况配置。
⛳功能实现
- 🌿完善按键中断回调内容
//重写回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/*判断是中断引脚*/
if(GPIO_Pin == KEY_Pin)
{
Trg++;
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);//状态翻转
}
}
- 🌿定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
static uint32_t click = 0;
static char keyStatus = 0;
static char LongPress = 0;
if(htim ->Instance == TIM6)
{
if(++cnt_1ms >= 1000)
{
cnt_1ms = 0;
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);//状态翻转
}
if(Trg > 0)
{
keyStatus = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);//读取按键状态
click++;
if((click > _debounce_ms) && (keyStatus == 1) && (LongPress == 0)) //1000
{
if((Trg == 1) || (Trg > 2)) //单击
{
Trg = 0;
funIndex ++;
printf("_click_ms,%d\r\n", funIndex);
click = 0;//清零
}
if(Trg == 2) //双击
{
Trg = 0;
funIndex += 2;
printf("_doubleClick %d\r\n", funIndex);
click = 0;//清零
}
}
if((click > _press_ms) && (keyStatus == 0) && (LongPress == 0)) //1000
{
printf("_longPressStart\r\n");
LongPress = 1;
}
if((LongPress == 1) && (keyStatus == 1))
{
LongPress = 0;
Trg = 0;
funIndex += 3;//松开才赋值
click = 0;
printf("_longPressStop %d\r\n", funIndex);
}
}
if(funIndex > 8)funIndex = 0;
}
}
- 🌿oled驱动显示内容,可以根据个人使用的屏幕规格型号自行完善,个人使用的是SH1106 1.3“寸的OLED屏幕作为显示。
- 🌿按键菜单显示内容参考网上的内容。
unsigned char funIndex = 0;//9和0->1-1
void menu11(void);
void menu12(void);
void menu21(void);
void menu22(void);
void menu23(void);
void menu31(void);
void menu32(void);
void menu33(void);
void menu34(void);
//定义按键操作数据
KEY_TABLE table[9] =
{
{0, 0, 1, 0, 2, (*menu11)},
{1, 0, 1, 1, 4, (*menu12)},
{2, 2, 3, 0, 5, (*menu21)},
{3, 2, 3, 0, 7, (*menu22)},
{4, 4, 4, 1, 4, (*menu23)},
{5, 5, 6, 2, 5, (*menu31)},
{6, 5, 6, 2, 6, (*menu32)},
{7, 7, 8, 3, 7, (*menu33)},
{8, 7, 8, 3, 8, (*menu34)},
};
void ShowMenu(int16_t x, int16_t y,char *text)
{
OLED_ShowString(x, y, text, 12);//x,y,字符串,字体大小
}
//一级菜单1
void menu11(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu1-1", 16);
}
//一级菜单2
void menu12(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu1-2", 16);
}
//二级菜单1
void menu21(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu2-1", 16);
}
//二级菜单2
void menu22(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu2-2", 16);
}
//二级菜单3
void menu23(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu2-3", 16);
}
//三级菜单1
void menu31(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu3-1", 16);
}
//三级菜单2
void menu32(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu3-2", 16);
}
//三级菜单3
void menu33(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu3-3", 16);
}
//三级菜单4
void menu34(void)
{
// OLED_Clear();
OLED_ShowString(36, 6, "menu3-4", 16);
}
- 📝main主函数
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_I2C2_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
HAL_TIM_Base_Start_IT(&htim6);
ShowMenu(2, 0, "OneKey Mul-Menu");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
current = table[funIndex].operation;//根据需要获取对应需要执行的函数
(*current)();//执行获取到的函数
}
/* USER CODE END 3 */
}
📚工程源码
链接:https://pan.baidu.com/s/1eR14Lpfjqm7nA0lvwbv5xg
提取码:vs7f
🌼利用状态机的思想实现方法
- 📝定时器更新中断回调函数
// 按键状态定义
typedef enum {
KEY_IDLE = 0, // 按键空闲状态
KEY_DOWN, // 按键按下状态
KEY_UP, // 按键释放状态
KEY_WAIT, // 等待双击状态
KEY_DOUBLE, // 双击状态
KEY_LONG // 长按状态
} key_status_t;
// 按键状态变量
key_status_t key_status = KEY_IDLE;
// 按键按下时间
uint32_t key_down_time = 0;
// 按键释放时间
uint32_t key_up_time = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t msCount = 0;
if(htim->Instance == htim4.Instance) {
// 获取按键状态
uint8_t key = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
// 处理按键事件
switch(key_status) {
case KEY_IDLE:
if(key == GPIO_PIN_RESET) {
// 按键按下,进入按下状态
key_status = KEY_DOWN;
key_down_time = HAL_GetTick();
}
break;
case KEY_DOWN:
if(key == GPIO_PIN_SET) {
// 按键释放,进入释放状态
key_status = KEY_UP;
key_up_time = HAL_GetTick();
} else if(HAL_GetTick() - key_down_time > 1200) {
// 按键长按,进入长按状态
key_status = KEY_LONG;
// HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
printf("Key Press in LongState \r\n"); //输出执行信息
}
break;
case KEY_UP:
if(key == GPIO_PIN_RESET) {
// 按键再次按下,进入等待双击状态
key_status = KEY_WAIT;
} else if(HAL_GetTick() - key_up_time > 300) {
// 按键单击
printf("Key Press ClickKey \r\n"); //输出执行信息
key_status = KEY_IDLE;
}
break;
case KEY_WAIT:
if(key == GPIO_PIN_SET) {
// 等待超时,进入单击状态
key_status = KEY_IDLE;
} else if(HAL_GetTick() - key_up_time > 300) {
// 双击事件
printf("Key Press DoubleKey \r\n"); //输出执行信息
key_status = KEY_DOUBLE;
}
break;
case KEY_DOUBLE:
if(key == GPIO_PIN_SET) {
// 双击事件结束
printf("Key Press DoubleKey \r\n"); //输出执行信息
key_status = KEY_IDLE;
}
break;
case KEY_LONG:
if(key == GPIO_PIN_SET) {
// 长按事件结束
// HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
printf("Key Press LongKey \r\n"); //输出执行信息
key_status = KEY_IDLE;
}
break;
default:
break;
}
if(++msCount > 500) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8); //翻转电平,LED翻转
msCount = 0;
}
}
}
- 📓源码
链接:https://pan.baidu.com/s/1OdGuxL24ls6-iazaPHUlfw?pwd=vzc1
提取码:vzc1
🌾其他实现方法
- 🔖同样是通过定时器中断来实现计时和统计。
// 按键状态定义
uint8_t key_status = 0;
// 按键按下时间
uint32_t key_down_time = 0;
// 按键释放时间
uint32_t key_up_time = 0;
uint8_t KeyPress = 0;
uint8_t Key_Press_Count = 0;
uint8_t Key_Release_Time = 0;
uint16_t Key_Long_Press_Count = 0;
uint8_t LongKey_State = 0;
/* 更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t msCount = 0;
if(htim->Instance == htim4.Instance) {
// __HAL_TIM_CLEAR_IT(htim,TIM_IT_UPDATE);//清楚标志位
// 获取按键状态
KeyPress = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
if(!KeyPress) { //按键被按下
// KeyPress = 0;//清除按键标志位
if(key_status == 0) {
key_status = 1;
if(Key_Press_Count < 255)Key_Press_Count++;//记录按键按的次数。
Key_Release_Time = 150;//刷新检测松开的时间:300
}
if(++Key_Long_Press_Count > 750) {//1.5s
Key_Press_Count = 0;//计时清零
// printf("Key Press into LongState \r\n");
LongKey_State = 1;
}
} else {
key_status = 0;
Key_Long_Press_Count = 0;
if(Key_Release_Time)Key_Release_Time--; //松开时计时
}
if(Key_Release_Time == 0) {
if(LongKey_State==1 && KeyPress==1){
printf("Key Press LongKey \r\n");
LongKey_State =0;
}
if(Key_Press_Count == 1) { //单击
printf("Key Press ClickKey \r\n");
} else if(Key_Press_Count == 2) { //双击
printf("Key Press DoubleKey \r\n");
}
// ……连续3、4、5次按键操作,可在下面继续添加
Key_Press_Count = 0;
}
if(++msCount > 500) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); //翻转电平,LED翻转
msCount = 0;
}
}
}
- 📘程序源码:
链接:https://pan.baidu.com/s/1e92kJ8WtmISinxTOPbUDaQ?pwd=o4j2
提取码:o4j2