一.按键触发方式有很多种:
——只讲解两种模式:外部中断模式,定时器定时扫描模式。
——用到的管脚:PG2管脚——按键;定时器。外部中断。
两种模式的HAL库配置不一样:
外部中断模式——按键按下来了一个外部中断,在外部中断回调函数里头编写函数。
定时器定时扫描模式——每隔多少秒去扫描按键是否按下,就如同每隔一段时间我会去看看 按键是否按下。
——芯片:STM32H723ZGT6型号。
————————————————
二、STM32Cubemax配置——定时器模式
1.先定时器模式
——配置管脚PG2——选择GPIO_Input。
——按键低电平有效——管脚PG2连按键,按键的另外一个脚连接地。
——配置管脚其他参数——
——配置定时器2——
——主频设置多少可以在这里看——主频可以根据自己的设置定时器——这里有个计算公式。
——题外补充知识:
假设你有一个需求,需要一个定时器每0.01秒中断一次。假设微控制器的主时钟频率为 96MHz(即时钟周期为 1/96,000,000 秒),你想计算相应的预分频器值和自动重装载值。
- 选择一个合理的预分频器值。为了简化计算,我们可以首先选择一个预分频器值。假设我们选择预分频器值为 9600-1=9599。——(这意味着时钟频率被降低到了10 kHz(也就是96,000,000/9600=10kHZ,96MHz=96,000,000Hz),即每个时钟周期为 0.0001秒(也就是1/10,000秒,1秒等于1000ms,即是0.1ms))。
- 使用定时周期公式计算自动重装载值。我们需要定时器每0.01秒溢出一次,即定时周期为 0.01秒。
[ 0.01秒=(9599+1)×({自动重装载值}+1)/96,000,000]
——公式:定时周期=(预分频器值psc+1)×(自动重装载值Autoreload+1)×时钟周期;
——定时周期——0.01s也就是10ms。
——预分频器值——图片中设置9600-1=9599。——先分频,计算每个时钟周期多久。
——自动重装载值——图片中设置100-1。——每个时钟周期再乘以这个加1,就是你定时的时间。
——时钟周期——主时钟(96MHz)的倒数(1/主时钟频率)——1/96,000,000。
——通过调整预分频器值和自动重装载值,可以实现不同的定时周期。
——这个选项需要勾出来,允许定时器中断产生全局中断——
——最后生成代码——打开源文件,编辑按键按下的程序部分——
三、代码部分——定时器模式
这个代码有三个数据结构用于按键关联起来。调试的时候主要看这几个机构体的数据变化,调试使用。
————可以通用模型——改变前面管脚配置,和我定义的数组里头管脚就可以正常移植使用。
//主函数main函数里头while(1)前添加一个语句,主函数中需要添加的部分。
/* USER CODE BEGIN 2 */
// 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
// 使能定时器2更新中断并启动定时器
HAL_TIM_Base_Start_IT(&htim2);
bsp_InitKey();
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
Key_Operations();//处理长按和短按的情况
}
/* USER CODE END 3 */
//定时器回调函数里头需要编写
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)//确定是定时器二中断
{
bsp_KeyScan10ms(); //判断是短按还是长按。
}
}
//key.h文件里头的。
#ifndef __KEY_H
#define __KEY_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
typedef enum{
KEY_NONE = 0,
KEY_1_DOWN, //按键按下
KEY_1_UP, //按键弹起
KEY_1_LONG, //按键长按
}KEY_ENUM;
#define KEY_FIFO_SIZE 10
typedef struct {
uint8_t Buf[KEY_FIFO_SIZE]; /*键值缓冲区*/
uint8_t Read; /*读指针*/
uint8_t Write; /*写指针*/
}KEY_FIFO_T;
/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum
{
KID_K1 = 0,
}KEY_ID_E;
/*
按键滤波时间50ms, 单位10ms。
只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
*/
#define KEY_FILTER_TIME 5
#define KEY_LONG_TIME 100 /* 单位10ms, 持续1秒,认为长按事件 */
typedef struct {
uint8_t (*IsKeyDownFunc)(void); /*按键是否松手*/
uint8_t Count; /*滤波计数器*/
uint16_t LongCount; /* 长按计数器 */
uint16_t LongTime; /* 按键按下持续时间, 0 表示不检测长按 */
uint8_t State; /* 按键当前状态(按下还是弹起) */
uint8_t RepeatSpeed; /* 连续按键周期 */
uint8_t RepeatCount; /* 连续按键计数器 */
}KEY_T;
void bsp_InitKey(void);
void Key_Operations(void);
void bsp_KeyScan10ms(void);
#ifdef __cplusplus
}
#endif
#endif /* __MAIN_H */
//key.c文件里头的
#include "key.h"
#define LONG_PRESS_DURATION 100 // 定义长按的时间阈值,这里假设1000个计时器周期
#define KEY_BUTTON_PIN GPIO_PIN_2 // 假设按键连接到GPIO_PIN_2
#define HARD_KEY_NUM 1 /* 实体按键个数 */
#define KEY_COUNT 1 /*按键数量*/
KEY_FIFO_T Key_FIFO;
KEY_T KEY_Filter[HARD_KEY_NUM];
/* 依次定义GPIO */
typedef struct
{
GPIO_TypeDef* gpio;
uint16_t pin;
uint8_t ActiveLevel; /* 激活电平 */
}X_GPIO_T;
/* GPIO和PIN定义 */
static const X_GPIO_T s_gpio_list[HARD_KEY_NUM] = {
{GPIOG, GPIO_PIN_2, 0}, /* KEY1 ,低电平表示按下*/
};
extern TIM_HandleTypeDef htim2;
void key_handler(void);
/*
*********************************************************************************************************
* 函 数 名: KeyPinActive
* 功能说明: 判断按键是否按下
* 形 参:
@_id:按键编号
* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t KeyPinActive(uint8_t _id)
{
uint8_t state;
if((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0)
{
state = 0; //低电平
}
else
{
state = 1; //高电平
}
if(s_gpio_list[_id].ActiveLevel == state)
{
return 1;//按下
}else
{
return 0;//未按下
}
}
/*
*********************************************************************************************************
* 函 数 名: IsKeyDownFunc
* 功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。
* 形 参: 无
* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t IsKeyDownFunc(uint8_t _id)
{
/*实体按键*/
if(_id < HARD_KEY_NUM)
{
uint8_t i,count = 0,save =255;
/*判断有几个按键按下*/
for(i = 0;i < HARD_KEY_NUM;i++)
{
if(KeyPinActive(i))
{
count++;
save = i;
}
}
if(count == 1 && save == _id)
{
return 1;//只有一个按键按下,且该按键正确才有效
}
return 0;
}
return 0;
}
void bsp_InitKeyVar(void) //初始化按键变量
{
/* 对按键FIFO读写指针清零 */
Key_FIFO.Read = 0;
Key_FIFO.Write = 0;
for(int i=0;i<KEY_COUNT;i++)
{
KEY_Filter[i].LongTime = KEY_LONG_TIME;
KEY_Filter[i].Count = KEY_FILTER_TIME / 2;
KEY_Filter[i].State = 0; /* 按键缺省状态,0为未按下 */
KEY_Filter[i].RepeatSpeed = 0; /* 按键连发的速度,0表示不支持连发 */
KEY_Filter[i].RepeatCount = 0; /* 连发计数器 */
}
}
void bsp_InitKey(void)
{
bsp_InitKeyVar();
}
void bsp_PutKey(uint8_t KeyValue)
{
Key_FIFO.Buf[Key_FIFO.Write] = KeyValue;
if(++Key_FIFO.Write >= KEY_FIFO_SIZE){
Key_FIFO.Write=0;
}
}
int bsp_GetKey(void)
{
uint8_t ret;
if(Key_FIFO.Read == Key_FIFO.Write)
return KEY_NONE;
else{
ret = Key_FIFO.Buf[Key_FIFO.Read];
if(++Key_FIFO.Read >= KEY_FIFO_SIZE){
Key_FIFO.Read=0;
}
return ret;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_GetKeyState
* 功能说明: 读取按键的状态
* 形 参: _ucKeyID : 按键ID,从0开始
* 返 回 值: 1 表示按下, 0 表示未按下
*********************************************************************************************************
*/
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID)
{
return KEY_Filter[_ucKeyID].State;
}
/*
*********************************************************************************************************
* 函 数 名: bsp_DetectKey
* 功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
* 形 参: IO的id, 从0开始编码
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectKey(uint8_t i)
{
KEY_T *pBtn;
pBtn = &KEY_Filter[i];//滤波器
if(IsKeyDownFunc(i))
{
if(pBtn->Count < KEY_FILTER_TIME)
{
pBtn->Count = KEY_FILTER_TIME;
}
else if(pBtn->Count < 2 * KEY_FILTER_TIME)
{
pBtn->Count++;
}
else
{
if (pBtn->State == 0)
{
pBtn->State = 1;
/* 发送按钮按下的消息 */
bsp_PutKey((uint8_t)(3 * i + 1));
}
if (pBtn->LongTime > 0) //长按
{
if (pBtn->LongCount < pBtn->LongTime)
{
/* 发送按钮持续按下的消息 */
if (++pBtn->LongCount == pBtn->LongTime)
{
/* 键值放入按键FIFO */
bsp_PutKey((uint8_t)(3 * i + 3));
}
}
}
}
}
else
{
if(pBtn->Count > KEY_FILTER_TIME)
{
pBtn->Count = KEY_FILTER_TIME;
}
else if(pBtn->Count != 0)
{
pBtn->Count--;
}
else
{
if (pBtn->State == 1)
{
pBtn->State = 0;
/* 发送按钮弹起的消息 */
bsp_PutKey((uint8_t)(3 * i + 2));
}
}
pBtn->LongCount = 0;
pBtn->RepeatCount = 0;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_KeyScan10ms
* 功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,10ms一次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan10ms(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectKey(i);
}
}
void Key_Operations(void)
{
uint8_t state =bsp_GetKey();
switch(state)
{
case KEY_1_DOWN: /* K1键按下 */
key_handler();
break;
case KEY_1_LONG: /* K1键长按 */
// key_handler();
break;
default:
/* 其它的键值不处理 */
break;
}
}
——这比消抖来的更加好,调试期间使用的外部中断模式,试了很多次都是按一下发一次,会出现多次触发外部中断,长按的情况也会有出现多发情况。
——以后有机会出一版外部中断处理按键长按和短按的更加好的例子,这个有许多是参考网上说的,做了修改。HAL库cubemax配置部分暂时留到一起写。这个定时器控制具体细节原理后期有机会讲一讲。
以上仅仅属于本人学习心得,可供学习参考,禁止商用~