记录一下自己学习的过程
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重载寄存器ARRTIM_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){key.key_continue++;}//每隔10ms,key.key_continue++,if判断防止它一直增加//当按下的时间超出了设定的值后,说明此次为长按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){key.key_cnt++;}//判断松手的时间是否超时,没超时就说明是连按,按键按下次数加一else{key.key_cnt=1;}//否则已超时,此次只能为单机或长按//单击和连击的判断放到下面松手的部分通过按键次数来判断key.key_event=KEY_SHORT;}break;}//按键松开case KEY_UP:{key.key_flag=0;//清楚标志位,其实这一部分没有这个也一样if(key.key_idle<KEY_IDLE){key.key_idle++;}//松手时间小于设定值就增加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){}
}
测试
测试