普通的按键在触发时总会有5~10ms的抖动,在主循环内检测按键和直接使用中断都需要延时消抖,对正常的程序执行产生一定影响,在这里使用状态机加定时器中断的方式来实现stm32按键的检测。
一、GPIO,定时器和中断配置
我使用的板子按键连接在A0、E2、E3、E4这四个IO口上,因此我初始化GPIOA和GPIOE这两个GPIO口,TIM6是一个基本定时器,只有基本的定时功能,在这里用来实现按键的定时检测。
void KEY_Init(void)
{
//结构体定义
GPIO_InitTypeDef GPIO_KEY_InitStrctural;
TIM_TimeBaseInitTypeDef TIM_BASEInitStructural;
NVIC_InitTypeDef NVIC_InitStructural;
//GPIO和定时器时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
//IO口配置,我A0口的按键接VCC,因此初始化为下拉输入,其他按键接GND,初始化为上拉输入
GPIO_KEY_InitStrctural.GPIO_Mode = GPIO_Mode_IPD;
GPIO_KEY_InitStrctural.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOA, &GPIO_KEY_InitStrctural);
GPIO_KEY_InitStrctural.GPIO_Mode = GPIO_Mode_IPU;
GPIO_KEY_InitStrctural.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_Init(GPIOE, &GPIO_KEY_InitStrctural);
//定时器配置,10ms进一次中断
TIM_BASEInitStructural.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_BASEInitStructural.TIM_CounterMode = TIM_CounterMode_Up;
TIM_BASEInitStructural.TIM_Period = 100-1;
TIM_BASEInitStructural.TIM_Prescaler = 7200-1;
TIM_TimeBaseInit(TIM6, &TIM_BASEInitStructural);
//定时器中观使能和中断标志位清零
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
//NVIC配置,优先级可以自己修改
NVIC_InitStructural.NVIC_IRQChannel = TIM6_IRQn;
NVIC_InitStructural.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructural.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructural.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructural);
//定时器使能
TIM_Cmd(TIM6, ENABLE);
}
二、按键检测函数的实现
这里在按键松开的时候将按键值返回,想要修改成按下时返回按键值在第二个case中对按键值判断完毕后直接返回就可以了
static uint8_t KEY_Get_Num(void)
{
enum {
KEY_STATE_INIT,
KEY_STATE_WAIT_RELEASE,
KEY_STATE_RELEASE
};
static uint8_t KEY_State = KEY_STATE_INIT;
static uint8_t key_return = NO_KEY_PRESS;
switch(KEY_State)
{
case KEY_STATE_INIT:
//有一个按键按下就进入等待按键松开状态
if(KEY_WK_UP || !KEY1 || !KEY2 || !KEY3)
KEY_State = KEY_STATE_WAIT_RELEASE;
break;
case KEY_STATE_WAIT_RELEASE:
//这些具体的按键含义都在头文件定义,头文件在文章最后
if(KEY_WK_UP)
key_return = KEY_WK_UP_PRESS;
else if(!KEY1)
key_return = KEY1_PRESS;
else if(!KEY2)
key_return = KEY2_PRESS;
else if(!KEY3)
key_return = KEY3_PRESS;
else
key_return = NO_KEY_PRESS;
KEY_State = KEY_STATE_RELEASE;
break;
case KEY_STATE_RELEASE:
//按键都不按下
if(!(KEY_WK_UP) && KEY1 && KEY2 && KEY3)
{
KEY_State = KEY_STATE_INIT;
return key_return;
}
break;
}
return 0;
}
三、按键返回函数和中断处理函数
我们定义一个全局变量"key_num"用来保存要返回给主函数的按键值,在中断处理函数中调用按键检测函数获得按键值,将它赋给"key_num",在主函数中就可以调用按键返回函数来获得是哪个按键按下了
(1)按键返回函数
uint8_t key(void)
{
return key_num;
}
(2)中断处理函数
void TIM6_IRQHandler(void)
{
//判断中断标志位
if(TIM_GetITStatus(TIM6, TIM_IT_Update))
key_num = KEY_Get_Num();
//清理标志位,确保不会错误的进入中断
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}
四、.h文件编写
#ifndef __KEY_H
#define __KEY_H
//这里使用宏对位带进行封装,编译操作
#define KEY_WK_UP PAIn(0)
#define KEY1 PEIn(4)
#define KEY2 PEIn(3)
#define KEY3 PEIn(2)
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY_WK_UP_PRESS 4
#define NO_KEY_PRESS 0
//函数声明,在主函数中调用
void KEY_Init(void);
uint8_t key(void);
#endif