主控板:STM32G431RBT6
板载下载器:DAPLINK
软件:STM32CubeMX、Keil MDK5
文章目录
1、初始配置
- 继续在之前的工程中进行配置(注意备份)。首先根据数据手册配置按键,可以看到连接按键的引脚为PA0、PB0、PB1、PB2,将相应引脚设置为GPIO_Input以及上拉输入Pull-up:
- 再配置定时器,选择其中一个如TIM3进行配置并使能中断:
- 最后生成工程即可。
- 在keil中打开工程,在
bsp
文件夹中添加interrupt.c
和interrupt.h
两个文件。
2、代码编写
(1)单击按键
-
interrupt.c
#include "interrupt.h" struct keys key[4]={0,0,0}; //触发中断进入中断回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ //判断是否为TIM3 if(htim->Instance==TIM3){ //按键电平位 key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0); key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1); key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2); key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0); //轮询按键消抖 for(int i=0;i<4;i++){ switch(key[i].judge_sta){ case 0: { if(key[i].key_sta==0) key[i].judge_sta=1; }break; //10ms后按键仍按下 case 1: { if(key[i].key_sta==0){ key[i].judge_sta=2; key[i].single_flag=1; } else key[i].judge_sta=0; }break; //读取到松手 case 2: { if(key[i].key_sta==1){ key[i].judge_sta=0; } }break; } } } }
-
interrupt.h
#ifndef _INTERRUPT_H_ #define _INTERRUPT_H_ #include "main.h" struct keys{ unsigned char judge_sta;//判断进程 int key_sta;//标志位,判断按键是否按下 int single_flag;//确认按键按下 }; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); #endif
-
main.c增加的部分
#include "interrupt.h" extern struct keys key[]; //初始化 HAL_TIM_Base_Start_IT(&htim3); while (1){ if(key[0].single_flag ==1){ LCD_DisplayStringLine(Line3, (uint8_t *)" key0 down"); } if(key[1].single_flag ==1){ LCD_DisplayStringLine(Line5, (uint8_t *)" key1 down"); } }
(2)长按按键
- interrupt.h的结构体中增加变量
unsigned int key_time;//时间记录 bool long_flag;
- interrupt.c
//轮询按键消抖 for(int i=0;i<4;i++){ switch(key[i].judge_sta==0){ case 0: { if(key[i].key_sta==0){ key[i].judge_sta=1; key[i].key_time=0; } }break; //10ms后按键仍按下 case 1: { if(key[i].key_sta==0){ key[i].judge_sta=2; } else key[i].judge_sta=0; }break; //读取到松手 case 2: { if(key[i].key_sta==1){ key[i].judge_sta=0; if(key[i].key_time<70){ key[i].single_flag=1; } } else{ key[i].key_time++; if(key[i].key_time>70) key[i].long_flag=1; } }break; } }
- main.c
if(key[0].single_flag ==1){ LCD_DisplayStringLine(Line3, (uint8_t *)" key0 down"); } if(key[1].long_flag ==1){ LCD_DisplayStringLine(Line5, (uint8_t *)" key1 down"); }
(3)通过时间戳进行消抖和判断按键长短的方法
-
除了延时消抖和定时轮询之外还可以考虑使用全局时间戳对按键是否按下以及按键按下时间长短进行判断。
-
代码编写思路为:
- 结构体中有一个用于识别的状态位private_state,默认处于Release。一旦按键被按下,中断触发。
- 首先确认定时器,再检查状态为是否在Relase状态,如果是就检查按键电平是否被拉低,如果是进入May_Press状态,并记录此时的时间戳last_time。
- 按键被释放后再次触发中断,会再次进行处理,此时计算和上一次记录的时间戳last_time的差值。
- 如果差值小于10ms就认为是抖动,不修改按键状态而是直接将按键状态置回Relase状态;差值在10ms和1500ms之间就是短暂按键Short_Press,差值超出1500ms就认为是长按按键Long_Press。
-
interrupt.h:首先要定义一个结构体变量,定义按键的几个关键标志位,用于管理按键状态。
//定义一个 #define Is_ShortPress_Threshold 1500 typedef struct _Key_t { uint32_t last_time; uint8_t key_sta; enum { Release,//按键状态默认处于Release,也就是释放的状态 May_Press, }private_state; enum { No_Press, Short_Press, Long_Press, }state; }Key_t;
-
interrupt.c:将事件添加到中断处理函数中。
//触发中断进入中断回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ //判断是否为TIM3 if(htim->Instance==TIM3){ key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0); key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1); key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2); key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0); for(int i=0;i<4;i++){ if(key[i].private_state==Release){ if(key[i].key_sta == 0){ key[i].private_state=May_Press; key[i].last_time=HAL_GetTick(); } } else if(key[i].private_state==May_Press){ if(key[i].key_sta == 1){ if(((HAL_GetTick() - key[i].last_time)>10)&&((HAL_GetTick() - key[i].last_time)<Is_ShortPress_Threshold)){ key[i].state=Short_Press; key[i].private_state=Release; } else if((HAL_GetTick() - key[i].last_time)>Is_ShortPress_Threshold){ key[i].state=Long_Press; key[i].private_state=Release; } else key[i].private_state=Release; } } } } }
-
main.c:在主函数中的循环中添加显示函数,将按键与切换界面的事件对应起来:
void disp_proc(){ //for(int i=0;i++;i<4){ if(key[0].state==Short_Press){ LCD_Clear(0x000000); LCD_DisplayStringLine(Line1, (uint8_t *)" LCD Dispaly"); LCD_DisplayStringLine(Line3, (uint8_t *)" shortdown"); } else if (key[0].state==Long_Press){ LCD_Clear(0x000000); LCD_DisplayStringLine(Line1, (uint8_t *)" LCD Dispaly"); LCD_DisplayStringLine(Line3, (uint8_t *)" longdown"); } } //}
3、硬件显示结果
- 按键单击可以分别显示;长按键可以显示对应结果。
- 第二种方法的显示结果:
4、问题和总结
(1)按键
根据原理图,按键按下,引脚电平为逻辑0,否则为逻辑1。
(2)数值类型
使用bool类型时要包含头文件 #include <stdbool.h>。
(3)清屏函数
HAL库中已经定义了清屏函数:
void LCD_Clear(uint32_t Color);
//Color表示清平颜色,如黑色:0x000000,白色:0xFFFFFF
(4)时间戳
该函数返回自启动以来经过的毫秒数:
viod HAL_GetTick();
(5)枚举
使用enum
关键字定义一个枚举类型,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
在枚举中,变量初值默认为第一个枚举值,因此在排列枚举值中需要注意将希望的初值放在第一位。
(6)中断回调函数
//使能定时器3,开启TIM3的更新中断
HAL_TIM_Base_Start_IT(&htim3);
//定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用
//则更新中断(溢出)发生时执行下列函数体内的内容
HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
参考资料
(1)B站学习视频:【备战2024蓝桥杯 嵌入式组】CT117E-M4 新款开发板 3小时省赛模块 速成总结
(2)keil / C99中使用bool类型
(3)【STM32学习】——TIM定时中断
(4)一种相对高效的按键消抖方法
(5)C enum(枚举)
(6)stm32 - HAL_GetTick() 是否返回刻度或毫秒? (以及如何以微秒为单位进行测量)