03、定时器+按键
按键需要消抖,用软件延时消抖会占用整个资源,导致整个程序卡死。反之,用定时器则可以提高资源利用率。用轮询的方式扫描按键,用多状态的思想消除按键抖动感觉非常巧妙。
首先将上一个例程文件复制一份再命名为Ag_02,在bsp文件夹中创建interrupt.c和interrupt.h。打开.ioc文件,参考芯片数据手册对相应GPIO口配置为input并设置为上拉模式。
配置定时器,随便选TIM几,将时钟源选定为内部时钟,将分频系数设置为80-1(在第一个博客中配置时钟树时将频率配置为80MHz),得到定时器工作频率为1MHz,将Counter设置为10000-1得到定时频率为100Hz(定时间隔10ms)(定时器工作频率=外部总线频率除以Prescaler)(定时频率=定时器工作频率除以Counter)
在NVIC栏勾选TIM2使能定时器中断。点击生成代码。
将interrupt.c包含进工程文件。
interrupt.h:
#ifndef _INTERRUPT_H
#define _INTERRUPT_H
#include "main.h"
#include "stdbool.h" //后面定义的布尔类型需要包含这个头文件
struct KEY_STATE //创建一个结构体,用来查看按键的状态
{
bool flag; //按键按下与否标志位
bool state; //按键状态(不确定)
uint judge; //步骤
};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef * htim);//定时器溢出中断回调函数
#endif
interrupt.c:
#include "interrupt.h"
struct KEY_STATE key_state[4]={0}; //定义一个KEY_STATE类型的结构体数组,共四个按键
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef * htim) //调用HAL库函数,定时器中断回调函数
{
if(htim->Instance==TIM2) //判断是否是由TIM2引发的中断
{
key_state[0].state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key_state[1].state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key_state[2].state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key_state[3].state=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0); //将引脚电位读到state变量
for(int j=0;j<4;j++) //轮询扫描按键
{
switch(key_state[j].judge) //设置三个步骤来消抖
{
case 0:
{
if(key_state[j].state==0) //一开始检测到按键按下
key_state[j].judge=1; //进入状态2
}
break;
case 1:
{
if(key_state[j].state==0) //仍为按下
{
key_state[j].flag=1; //状态标志位置1
key_state[j].judge=2; //进入状态3
}
else
key_state[j].judge=0;
}
break;
case 2:
{
if(key_state[j].state==1) //松开按键
key_state[j].judge=0; //回到状态1
}
break;
}
}
}
}
在主函数while中加入功能代码:
if(key_state[0].flag==1)
{
LCD_DisplayStringLine(Line1,(unsigned char *)dis);
key_state[0].flag=0;
}
if(key_state[1].flag==1)
{
LCD_DisplayStringLine(Line2,(unsigned char *)dis);
key_state[1].flag=0;
}
if(key_state[2].flag==1)
{
LCD_DisplayStringLine(Line3,(unsigned char *)dis);
key_state[2].flag=0;
}
if(key_state[3].flag==1)
{
LCD_DisplayStringLine(Line4,(unsigned char *)dis);
key_state[3].flag=0;
}
要注意的是,我们在interrupt.c中定义的结构体及其参量要想在主函数中调用的话,需要加:
/* USER CODE BEGIN PTD */
extern struct KEY_STATE key_state[];
/* USER CODE END PTD */
同时,不要忘记在主函数中打开定时器中断:
HAL_TIM_Base_Start_IT(&htim2);
编译无误,下载完成!
长按键
蓝桥杯有时会考到长按键和短按键的设计。我们可以在上述基本按键功能的基础上,增加一个变量来记录按下的时间,以此区分长按键和短按键的不同功能。
interrupt.c:
#include "interrupt.h"
struct KEY_STATE key_state[4]={0}; //定义一个KEY_STATE类型的结构体数组,共四个按键
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef * htim) //调用HAL库函数,定时器中断回调函数
{
if(htim->Instance==TIM2) //判断是否是由TIM2引发的中断
{
key_state[0].state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key_state[1].state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key_state[2].state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key_state[3].state=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0); //将引脚电位读到state变量
for(int j=0;j<4;j++) //轮询扫描按键
{
switch(key_state[j].judge) //设置三个步骤来消抖
{
case 0:
{
if(key_state[j].state==0) //一开始检测到按键按下
{
key_state[j].judge=1; //进入状态2
key_state[j].time=0;
}
}
break;
case 1:
{
if(key_state[j].state==0) //仍为按下
{
key_state[j].judge=2; //进入状态3
}
else
key_state[j].judge=0;
}
break;
case 2:
{
if(key_state[j].state==1) //松开按键
{
key_state[j].judge=0; //回到状态1
if(key_state[j].time<70) //判断是否按下时间小于700ms
{
key_state[j].short_flag=1; //短按键
}
}
else
{
key_state[j].time++;
if(key_state[j].time>70)
{
key_state[j].long_flag=1; //长按键
}
}
}
break;
}
}
}
}
记得在.h文件结构体中增加相应的变量,主函数也相应修改功能为长短按键逻辑判读:
if(key_state[0].short_flag==1)
{
sprintf(dis,"short");
LCD_DisplayStringLine(Line1,(unsigned char *)dis);
key_state[0].short_flag=0;
}
if(key_state[0].long_flag==1)
{
sprintf(dis,"long");
LCD_DisplayStringLine(Line1,(unsigned char *)dis);
key_state[0].long_flag=0;
}
编译无误,下载完成!