记录一下自己学习的过程
1.硬件连接
用的是c8t6的最小系统板,通过面包板连接按键,将PB11口用作按键输入。同时还连接了一块oled的屏方便查看现象。
2.代码部分
核心思想和代码总体框架来自博客:(7条消息) stm32【按键处理:单击、连击、长按】_Elven-C的博客-CSDN博客_stm32 按键中断多次进入最佳解决办法作了一些简化。
首先是头文件部分
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#include "delay.h"
#include "OLED.h"
#define KEY_NULL 0 //无事件
#define KEY_LONG 1 //长按事件
#define KEY_SHORT 2 //短按事件
#define KEY_DOUBLE 3 //连按事件
#define KEY_DOWN 1 //按键按下状态
#define KEY_UP 0 //按键松手状态
#define KEY_NONE 2 //按键初始状态
#define KEY_CONTINUE 50 //按下的最长时间
#define KEY_IDLE 40 //松手最长时间
#define KEY_PIN PBin(11)//PB11用作按键输入
void Key_Init(void);
int Key_Scan(void);
void Key_Process(void);
#endif
Key.c部分
struct KEY
{
u8 key_prevent; //前一次按键事件
u8 key_event; //当前按键事件
u8 key_state; //按键状态 按下或松开
u8 key_cnt; //按键按下的次数
u8 key_continue; //按键按下的时间
u8 key_idle; //按键松手的时间
u8 key_flag; //按键状态发生改变的标志
u8 key_event_flag; //产生一次按键事件的标志
}key={KEY_NULL,KEY_NONE,0,0,0,0,0};//结构体初始化
GPIO和定时器的配置
void Exit_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //配置为上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11);
EXTI_DeInit();
EXTI_InitStructure.EXTI_Line = EXTI_Line11;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
//上升下降沿中断,这样按下或松手就都能触发中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void TIM2_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Prescaler= 71;//预分频
TIM_TimeBaseStructure.TIM_Period=9999; //相当于每10ms进入一次中断
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,向上计数
// 初始化定时器
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM2, ENABLE); //使能TIM重载寄存器ARR
TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 清除计数器中断标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 开启计数器中断
TIM_Cmd(TIM2, DISABLE); // 关闭定时器的时钟,等待使用
}
//配置中断
void NVIC_Exit_GPIO_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void NVIC_TIM2_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
中断函数
void EXTI15_10_IRQHandler()
{
Delay_ms(20); //按键消抖
if(KEY_PIN==0) //KEY_PIN==0代表按键按下
{
key.key_flag=1; //代表按键状态发生改变的标志
key.key_state=KEY_DOWN; //按键状态
key.key_continue=0; //按键按下的时间清零
}
else //else的情况就是按键松手
{
key.key_flag=1;
key.key_state=KEY_UP;
key.key_idle=0; 按键松手的时间清零
}
EXTI_ClearITPendingBit(EXTI_Line11); //清除中断标志
}
void TIM2_IRQHandler(void) //定时器中断每隔10ms进入一次中断
{
Key_Process(); //每隔10ms调用一次
TIM_ClearITPendingBit(TIM2 , TIM_IT_Update);//清除中断标志位
}
Key_Process();
void Key_Process(void)
{
switch(key.key_state)
{
case KEY_DOWN://按键按下进入
{
if(key.key_continue=KEY_CONTINUE)
{
if(key.key_cnt>1)//这个if判断是防止连按之后在长按会被判定为一次长按
{
OLED_ShowString(4,1,"DOUBLE");//这个if判断把连按之后的长按判断为连按
OLED_ShowNum(2,1,key.key_cnt,2);//的一部分
key.key_event=KEY_DOUBLE;
key.key_prevent=key.key_event;
}
else//正常的长按
{key.key_event=KEY_LONG; //当按下的时间超出了设定的值后判断为长按
key.key_event_flag=1; //产生了一次按键事件
key.key_prevent=key.key_event;
OLED_ShowString(4,1,"LONG "); //打印到OLED屏上观察
}
}
if(key.key_flag)//按键按下就会进入这个if判断
{
key.key_flag=0;//清除按键状态发生改变的标志位
if(key.key_idle=KEY_IDLE)//大于松手时间,代表按键事件产生
{
//这个if判断防止长按被识别为单击,应为这一部分是靠按下次数来判断
//单击或长按,而单击和长按按下次数都为1,但长按的按键事件一定会判定为
//长按,通过key.key_event判断当前是长按还是单击
if(key.key_cnt==1 && key.key_event!=KEY_LONG)
{
key.key_event=KEY_SHORT;
key.key_prevent=key.key_event;
OLED_ShowString(4,1,"SHORT ");
OLED_ShowNum(2,1,key.key_cnt,2);
}
else if(key.key_cnt>1)//按下次数为>1次的情况
{
if(key.key_prevent==KEY_LONG)
//这个if判断防止前一次的长按被当作连按的一部分
key.key_cnt--;//减去长按的一次计数
if(key.key_cnt==1)//一次说明为单击
{
key.key_event=KEY_SHORT;
key.key_prevent=key.key_event;
OLED_ShowString(4,1,"SHORT ");
OLED_ShowNum(2,1,key.key_cnt,2);
}
else//大于1次为连击
{
key.key_event=KEY_DOUBLE;
OLED_ShowString(4,1,"DOUBLE");
OLED_ShowNum(2,1,key.key_cnt,2);//oled输出连击次数
}
}
}
break;
}
}
}
void Key_Init(void)
{
Exit_GPIO_Config();
TIM2_Config();
NVIC_Exit_GPIO_Config();
NVIC_TIM2_Config();
TIM_Cmd(TIM2, ENABLE); //使能时钟
}
main.c部分
#include "Key.h"
int main()
{
SystemInit();
OLED_Init();
OLED_ShowString(1,1,"Ready:");
Key_Init();
while(1)
{
}
}
测试
测试