一.机械按键抖动
在按下按键后金属弹片会来回震动影响I/O口的电平变化,影响检测和判断操作。
通常抖动时间为:5ms~10ms
影响:在不加消除抖动的情况下按下按键LED灯可能会出现失灵的情况,因为这时的判断按键情况通常是判断电平的高低,由于电平不停的发转,所以呀很难判断此时是否是被按下。
二.消除抖动的方法
1.软件延时消抖
按下按键后我们Delay一会儿跳过这个抖动时间过去,这样就轻松的解决了这个问题。不过这样会出现cpu等待的情况,这个时候CPU干不了其他事情,就这样干等着。明显有点屈才了。
2.定时器扫描按键消抖
让硬件自己隔段时间就去看看按键的状态,要是符合情况再让CPU去处理。这样就起到不断扫描的作用。
三.代码的实现(基于标准库)
1.变量的定义
// 按键状态变量
volatile uint8_t key_pressed = 0; // 0: 未按下, 1: 短按, 2: 长按
volatile uint32_t key_timer = 0; // 按键计时器
// LED状态变量
volatile uint8_t led_state = 0; // 0: LED灭, 1: LED亮
volatile
关键字是一种类型修饰符,用于告诉编译器某个变量的值可能会在程序外部被意外地改变。使用volatile
修饰的变量,编译器在每次使用时都会重新从内存中读取它的值,而不是使用缓存中的值。
2.定时器中断(Timer_Init)
定时器为1ms中断 ,频率为1000,可以通过改变ARR与PSC的值来调整中断频率。
这里的APB1是低速外设总线最大频率为36*10^6Hz.
#include "stm32f10x.h"
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟,TIM是外设时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数,计数的方式
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 3600 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断,开启了更新中断到NVIC的通路
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线,TIM2_IRQn是定时器2在NUVIC里的通道//中断号
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行,使能计数器
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //获取中断标志位
{
//可写操作
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清楚标志位
}
}
*/
3.I/O口的配置 (IO_Init)
IO_Init
函数用于初始化按键和LED引脚。按键配置为上拉输入模式,LED配置为推挽输出模式。
#include "stm32f10x.h"
// 定义按键和LED引脚
#define KEY_PIN GPIO_Pin_1
#define KEY_PORT GPIOB
#define LED_PIN GPIO_Pin_1
#define LED_PORT GPIOA
// 初始化按键和LED引脚
void IO_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA和GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
// 配置按键引脚为输入模式,上拉
GPIO_InitStructure.GPIO_Pin = KEY_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(KEY_PORT, &GPIO_InitStructure);
// 配置LED引脚为输出模式
GPIO_InitStructure.GPIO_Pin = LED_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LED_PORT, &GPIO_InitStructure);
// 初始化LED状态
GPIO_ResetBits(LED_PORT, LED_PIN); // LED灭,高电平
}
4.中断函数进行操作(TIM2_IRQHandler)
- 如果按键按下时间超过200ms,则认为是长按;否则,如果之前的状态是高电平(未按下),并且当前检测到低电平(按下),则认为是短按。
- TIM2_IRQHandler函数是TIM定时器的中断服务函数,用于实现按键扫描、消抖处理和LED控制。
- 使用
key_timer
变量来计时按键按下的时间。- 当按键释放时,如果之前检测到按键按下(无论是短按还是长按),则切换LED的状态。
/**
* 函 数:TIM2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void TIM2_IRQHandler(void){
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){ //判断是否是TIM2的更新事件触发的中断
static uint8_t last_key_state = 1; // 初始化为1,因为按键默认是高电平(未按下)
// 读取按键状态
uint8_t current_key_state = GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN);
// 消抖处理
if (current_key_state == 0) { // 按键按下(低电平)
key_timer++;
if (key_timer >= 200) { // 假设长按时间为200ms
key_pressed = 2; // 长按
}
else if (last_key_state == 1) { // 确保不是抖动
key_pressed = 1; // 短按
}
}
else {
// 按键释放
if (key_pressed == 1 || key_pressed == 2) {
// 如果之前检测到按键按下(无论是短按还是长按),则处理按键事件
key_pressed = 0; // 重置按键状态
// 切换LED状态
led_state = !led_state;
if (led_state) {
GPIO_SetBits(LED_PORT, LED_PIN); // LED亮
}
else {
GPIO_ResetBits(LED_PORT, LED_PIN); // LED灭
}
}
key_timer = 0; // 重置计时器
}
last_key_state = current_key_state; // 更新上一次按键状态
}
}
5.主函数
在
main
函数的主循环中,不需要添加额外的按键处理逻辑,因为所有的按键处理和LED控制都已经在中断服务函数中完成了。
int main(void)
{
IO_Init();
Timer_Init();
while(1){
//可以有其他的操作
}
}
6.结束语
标签:TIM2,灯亮,LED,NVIC,TIM,按键,GPIO From: https://blog.csdn.net/HFDTBS123/article/details/142895052
- 示例代码中的消抖时间和长按时间可能需要根据实际硬件进行调整。
- 在实际应用中,可能需要添加更多的按键处理逻辑或功能。
- 确保STM32的标准外设库已经正确包含在项目中,并且已经配置了正确的系统时钟(SystemCoreClock)。