当前位置: 首页 > news >正文

STM32f103实现按键长按 短按 双击

今天来分享一个使用EXIT外部中断加TIM计时器实现按键长短按以及双击操作,不过笔者在双击上有点瑕疵,就是当你按下双击第一下停顿几秒按第二下依然会识别为双击操作,笔者猜测只要板子不停电即便到第二天按下第二下依旧会识别双击操作,正常来说2秒内如果没有按下第二下会认为双击操作失败,重新开始,不过整体使用效果还可以,解决这个问题的方案笔者抽时间在好好想想。

下面为大家介绍以下大体思路,我们知道每个I/O端口都会对应一个EXIT中断,由此我们可以使用EXIT的双边沿触发来实现TIM定时器的开启关闭,当检测按下时开启TIM定时器,当检测松开按键时关闭TIM定时器,从而记录按键按下的时间,根据按键按下的时间长短不同可以判断按键的长短按,另外需要设定标志位来判断双击,下面开始上代码。

首先是TIM计时器的代码,笔者使用的是TIM2计时器,大家找一个板子上没用到的计时器即可。

time2.h

/*********** @Author : 桃杬* @describe : TIM2计时器,在此处主要用来实现计时操作* @Data : 2024.06.06
***************/#ifndef _TIME2_H
#define _TIME2_H#include "stm32f10x.h"extern uint16_t Time_Count; //外部声明,让Time_Count成为全局变量void TIME2_Init(uint16_t arr,uint16_t psc);#endif

下面是time.c的代码,具体解释笔者在里面都有备注,需要注意的是笔者设置每次发生溢出的时间为10ms,即Time_Count每10ms都会计数加1,实际时间 = Time_Count * 10,单位ms。

time2.c

#include "time2.h"/******** TIM溢出次数 Tout(溢出时间) = (arr+1)/(psc+1)/Tclk(时钟分割)* 当 arr = 10000, psc = 72 时,10ms溢出一次
*******/
uint16_t Time_Count;void TIME2_Init(uint16_t arr,uint16_t psc)
{//定义TIM结构体TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定义NVIC嵌套向量中断NVIC_InitTypeDef NVIC_InitStrucure;//使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);TIM_InternalClockConfig(TIM2); //配置TIM2内部时钟,可以不配置,默认是一直开着的//配置TIMTIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //不用时钟分割TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数方式TIM_TimeBaseStructure.TIM_Period = arr - 1; //自动装载值TIM_TimeBaseStructure.TIM_Prescaler = psc - 1; //预分频系数TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //周期计数器值,这个是高级定时器,基本通用定时器不用开启TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除TIM的挂起标志TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //使能指定TIM中断,寻去中断更新//配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置优先级分组NVIC_InitStrucure.NVIC_IRQChannel = TIM2_IRQn; //中断源NVIC_InitStrucure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道NVIC_InitStrucure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStrucure.NVIC_IRQChannelSubPriority = 1; //子优先级(也是响应优先级)NVIC_Init(&NVIC_InitStrucure);
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){//进入中断表示定时器计数(CNT)溢出, 自增(10ms溢出一次, 可通过配置ARR, PSC和Tclk自定义溢出时间)Time_Count++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除定时器溢出中断}
}

接下来便是key.h代码,如果需要移植的话只需要修改每个按键定义以及中断部分,其他部分不需要修改,笔者这里使用了四个按键,如果大家是少于四个按键可以将按键启用的值改为0即可,即禁用不需要的按键,如将isEnableKey4 该为 0就会禁用按键4。如果大家的按键大于4个可以按照笔者写的格式增加按键,下面可以参考笔者写的代码。

key.h

/*********** @Author : 桃杬* @describe : 实现按键长短按 双击操作* @Data : 2024.06.06
***************/#ifndef _KEY_H
#define _KEY_H#include "stm32f10x.h"/**** 是否启用按键* 根据个人需求修改* 1 启用按键 0 失能按键* 默认都启用
****/
#define isEnableKey1 1
#define isEnableKey2 1
#define isEnableKey3 1
#define isEnableKey4 1#if isEnableKey1
//定义KEY1按键引脚以及中断
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOC //使能时钟端口
#define KEY1_GPIO_PORT GPIOC //KEY1端口
#define KEY1_GPIO_PIN GPIO_Pin_1 //KEY1引脚
#define KEY1_GPIO_EXTI_PORT_SOURCE GPIO_PortSourceGPIOC //外部中断源端口
#define KEY1_GPIO_EXTI_PIN_SOURCE GPIO_PinSource1 //外部中断源引脚
#define KEY1_EXTI_LINE EXTI_Line1 //EXTI事件线
#define KEY1_IRQN EXTI1_IRQn //中断源
#endif#if isEnableKey2
//定义KEY2按键引脚以及中断
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOC //使能时钟端口
#define KEY2_GPIO_PORT GPIOC //KEY2端口
#define KEY2_GPIO_PIN GPIO_Pin_5 //KEY2引脚
#define KEY2_GPIO_EXTI_PORT_SOURCE GPIO_PortSourceGPIOC //外部中断源端口
#define KEY2_GPIO_EXTI_PIN_SOURCE GPIO_PinSource5 //外部中断源引脚
#define KEY2_EXTI_LINE EXTI_Line5 //EXTI事件线
#define KEY2_IRQN EXTI9_5_IRQn //中断源
#endif#if isEnableKey3
//定义KEY3按键引脚以及中断
#define KEY3_GPIO_CLK RCC_APB2Periph_GPIOC //使能时钟端口
#define KEY3_GPIO_PORT GPIOC //KEY3端口
#define KEY3_GPIO_PIN GPIO_Pin_4 //KEY3引脚
#define KEY3_GPIO_EXTI_PORT_SOURCE GPIO_PortSourceGPIOC //外部中断源端口
#define KEY3_GPIO_EXTI_PIN_SOURCE GPIO_PinSource4 //外部中断源引脚
#define KEY3_EXTI_LINE EXTI_Line4 //EXTI事件线
#define KEY3_IRQN EXTI4_IRQn //中断源
#endif#if isEnableKey4
//定义KEY4按键引脚以及中断
#define KEY4_GPIO_CLK RCC_APB2Periph_GPIOA //使能时钟端口
#define KEY4_GPIO_PORT GPIOA //KEY4端口
#define KEY4_GPIO_PIN GPIO_Pin_0 //KEY4引脚
#define KEY4_GPIO_EXTI_PORT_SOURCE GPIO_PortSourceGPIOA //外部中断源端口
#define KEY4_GPIO_EXTI_PIN_SOURCE GPIO_PinSource0 //外部中断源引脚
#define KEY4_EXTI_LINE EXTI_Line0 //EXTI事件线
#define KEY4_IRQN EXTI0_IRQn //中断源
#endif//定义读取按键状态
#if isEnableKey1
#define KEY1 GPIO_ReadInputDataBit(KEY1_GPIO_PORT,KEY1_GPIO_PIN) //读取按键 LEFT
#endif#if isEnableKey2
#define KEY2 GPIO_ReadInputDataBit(KEY2_GPIO_PORT,KEY2_GPIO_PIN) //读取按键 RIGHT
#endif#if isEnableKey3
#define KEY3 GPIO_ReadInputDataBit(KEY3_GPIO_PORT,KEY3_GPIO_PIN) //读取按键 DOWN
#endif#if isEnableKey4
#define KEY4 GPIO_ReadInputDataBit(KEY4_GPIO_PORT,KEY4_GPIO_PIN) //读取按键 WAKEUP
#endif/******** 定义返回值状态 **********/
#if isEnableKey1
//KEY1返回值
#define KEY1_ShortPress_Value 0x0001  //短按
#define KEY1_LongPress_Value 0x0002   //长按
#define KEY1_DoubleClick_Value 0x0004 //双击
#endif#if isEnableKey2
//KEY2返回值
#define KEY2_ShortPress_Value 0x0010  //短按
#define KEY2_LongPress_Value 0x0020   //长按
#define KEY2_DoubleClick_Value 0x0040 //双击
#endif#if isEnableKey3
//KEY3返回值
#define KEY3_ShortPress_Value 0x0100  //短按
#define KEY3_LongPress_Value 0x0200   //长按
#define KEY3_DoubleClick_Value 0x0400 //双击
#endif#if isEnableKey4
//KEY4返回值
#define KEY4_ShortPress_Value 0x1000  //短按
#define KEY4_LongPress_Value 0x2000   //长按
#define KEY4_DoubleClick_Value 0x4000 //双击
#endif/**************** 函数部分 ********************/
void Key_Init(void); //初始化按键
void Key_Scan(void); //检测按键是否按下
uint16_t GetKeyNum(void); //返回按键值#endif

注意的是delay.h中是笔者写的简单的延迟函数,自行使用大家的延迟函数即可,只在消抖处用到了延时函数,其余部分没用用到。其次,中断处理双击中又嵌套了两个if else,时间上有点大,有点屎山代码的味道了😂,目前能想到的是开一个定时器,每当符合双击第一次时开启计数器,当检测到双击第二次时停止计数器,在2s内算是符合双击操作,否则丢弃,当然这只是笔者目前的想法🤣,后续笔者会完善以下。

key.c

#include "key.h"
#include "delay.h"
#include "time2.h"/*** 最终返回状态值 长按 短按 ***/
uint16_t Key_Value = 0x0000;/*** 记录上次最终返回状态值 ***/
uint16_t Old_Key_Value = 0x0000;//初次进入中断标志位
uint8_t Key_IT_Flag = 0;//初级进入双击标志位
uint8_t DoubleClickFlag = 0;//记录上次按键的值
uint16_t Old_KeyNum = 0x0000;/***按键按下宏定义***/
#if isEnableKey1
static uint8_t Key1_Press = 0;
#endif#if isEnableKey2
static uint8_t Key2_Press = 0;
#endif#if isEnableKey3
static uint8_t Key3_Press = 0;
#endif#if isEnableKey4
static uint8_t Key4_Press = 0;
#endif/*****
* @func : Key_Init(void)
* @describe : 初始化按键 外部中断 以及嵌套向量中断控制器
* @param : void
* @return : void
* @note : 移植时除了控制优先级和子优先级在此处需改外其他不需要修改这里
*****/
void Key_Init(void)
{//定义GPIO结构体GPIO_InitTypeDef GPIO_InitStructure;//定义外部中断EXTI机构体EXTI_InitTypeDef EXTI_InitStructure;//定义嵌套向量中断控制器NVIC结构体NVIC_InitTypeDef NVIC_InitStructure;//使能时钟#if isEnableKey1RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK,ENABLE); //key1所在的时钟#endif#if isEnableKey2RCC_APB2PeriphClockCmd(KEY2_GPIO_CLK,ENABLE); //key2所在的时钟#endif#if isEnableKey3RCC_APB2PeriphClockCmd(KEY3_GPIO_CLK,ENABLE); //key3所在的时钟#endif#if isEnableKey4RCC_APB2PeriphClockCmd(KEY4_GPIO_CLK,ENABLE); //key4所在的时钟#endifRCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//配置KEYGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;#if isEnableKey1GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;GPIO_Init(KEY1_GPIO_PORT,&GPIO_InitStructure);#endif#if isEnableKey2GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN;GPIO_Init(KEY2_GPIO_PORT,&GPIO_InitStructure);#endif#if isEnableKey3GPIO_InitStructure.GPIO_Pin = KEY3_GPIO_PIN;GPIO_Init(KEY3_GPIO_PORT,&GPIO_InitStructure);#endif#if isEnableKey4GPIO_InitStructure.GPIO_Pin = KEY4_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_Init(KEY4_GPIO_PORT,&GPIO_InitStructure);#endif#if isEnableKey1//配置KEY1外部中断及嵌套向量中断控制器GPIO_EXTILineConfig(KEY1_GPIO_EXTI_PORT_SOURCE,KEY1_GPIO_EXTI_PIN_SOURCE); //选择EXTI的信号源EXTI_InitStructure.EXTI_Line = KEY1_EXTI_LINE; //选择EXTI事件线EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //双边沿中断EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置优先级分组NVIC_InitStructure.NVIC_IRQChannel = KEY1_IRQN; //中断源NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级NVIC_Init(&NVIC_InitStructure);#endif#if isEnableKey2//配置KEY2外部中断及嵌套向量中断控制器GPIO_EXTILineConfig(KEY2_GPIO_EXTI_PORT_SOURCE,KEY2_GPIO_EXTI_PIN_SOURCE); //选择EXTI的信号源EXTI_InitStructure.EXTI_Line = KEY2_EXTI_LINE; //选择EXTI事件线EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //双边沿中断EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置优先级分组NVIC_InitStructure.NVIC_IRQChannel = KEY2_IRQN; //中断源NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级NVIC_Init(&NVIC_InitStructure);#endif#if isEnableKey3//配置KEY3外部中断及嵌套向量中断控制器GPIO_EXTILineConfig(KEY3_GPIO_EXTI_PORT_SOURCE,KEY3_GPIO_EXTI_PIN_SOURCE); //选择EXTI的信号源EXTI_InitStructure.EXTI_Line = KEY3_EXTI_LINE; //选择EXTI事件线EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //双边沿中断EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置优先级分组NVIC_InitStructure.NVIC_IRQChannel = KEY3_IRQN; //中断源NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级NVIC_Init(&NVIC_InitStructure);#endif#if isEnableKey4//配置KEY4外部中断及嵌套向量中断控制器GPIO_EXTILineConfig(KEY4_GPIO_EXTI_PORT_SOURCE,KEY4_GPIO_EXTI_PIN_SOURCE); //选择EXTI的信号源EXTI_InitStructure.EXTI_Line = KEY4_EXTI_LINE; //选择EXTI事件线EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //双边沿中断EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置优先级分组NVIC_InitStructure.NVIC_IRQChannel =KEY4_IRQN; //中断源NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级NVIC_Init(&NVIC_InitStructure);#endif
}/*****
* @func : Key_Scan(void)
* @describe : 检测按键状态
* @param : void
* @return : void
* @note : 1 按下按键 0 松开按键
*****/
void Key_Scan(void)
{#if isEnableKey1if(KEY1 == RESET) //检测到按键1{delay_ms(20); //消抖if(KEY1 == RESET) //再次检测按键1{Key1_Press = 1; //记录此时按键状态}}elseKey1_Press = 0;#endif#if isEnableKey2if(KEY2 == RESET) //检测到按键2{delay_ms(20); //消抖if(KEY2 == RESET) //再次检测按键2Key2_Press = 1; //记录此时按键状态}elseKey2_Press = 0;#endif#if isEnableKey3if(KEY3 == RESET) //检测到按键3{delay_ms(20); //消抖if(KEY3 == RESET) //再次检测按键3Key3_Press = 1; //记录此时按键状态}elseKey3_Press = 0;#endif#if isEnableKey4if(KEY4 == SET) //检测到按键4  注意的是按键四是高电平检测到按键按下{delay_ms(20); //消抖if(KEY4 == SET) //再次检测按键4Key4_Press = 1; //记录此时按键状态}elseKey4_Press = 0;#endif
}/*****
* @func : GetKeyNum(void)
* @describe : 返回按键值
* @param : void
* @return : 按键值
*       @ret : 11 按键1短按; 12 按键1长按; 13 按键1双击
*       @ret : 21 按键2短按; 22 按键2长按; 23 按键2双击
*       @ret : 31 按键3短按; 32 按键3长按; 33 按键3双击
*       @ret : 41 按键4短按; 42 按键4长按; 43 按键4双击
* @note : 返回值如 xx 形式
*         其十位代表按键几 个位则代表长短双击, 1 短按 2 长按 3 双击
*****/
uint16_t GetKeyNum(void)
{//最终返回值static uint8_t keyNum = 0;Key_Scan();if(!Key_IT_Flag){switch(Key_Value){#if isEnableKey1case 0x0001:keyNum = 11;  //按键1短按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x0002:keyNum = 12;  //按键1长按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x0004:keyNum = 14;  //按键1双击Old_Key_Value = 0x0000;Key_Value = 0x0000;break;#endif#if isEnableKey2case 0x0010:keyNum = 21;  //按键2短按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x0020:keyNum = 22;  //按键2长按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x0040:keyNum = 24;  //按键2双击Old_Key_Value = 0x0000;Key_Value = 0x0000;break;#endif#if isEnableKey3case 0x0100:keyNum = 31;  //按键3短按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x0200:keyNum = 32;  //按键3长按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x0400:keyNum = 34;  //按键3双击Old_Key_Value = 0x0000;Key_Value = 0x0000;break;#endif#if isEnableKey4case 0x1000:keyNum = 41;  //按键4短按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x2000:keyNum = 42;  //按键4长按Old_Key_Value = Key_Value;Key_Value = 0x0000;break;case 0x4000:keyNum = 44;  //按键4双击Old_Key_Value = 0x0000;Key_Value = 0x0000;break;#endifdefault:keyNum = 0;Key_Value = 0x0000;break;}}return keyNum;
}#if isEnableKey1
/*****
* @func : EXTI1_IRQHandler()
* @describe : 中断函数
* @param : void
* @return : void
* @note : 中断需要的处理的操作在此处执行
*****/
void EXTI1_IRQHandler()
{static uint8_t Key1State = 0; //静态按键1状态 0 松开 1按下if(EXTI_GetITStatus(KEY1_EXTI_LINE) != RESET){if(Key1_Press && Key1State) //松开按键{Key1State = 0; //松开标志Key_IT_Flag = 0; //退出中断标志位TIM_Cmd(TIM2,DISABLE); //关闭时钟if(Time_Count > 0 && Time_Count <= 20) //短按时间判断{if(!DoubleClickFlag) //第一次双击按下{DoubleClickFlag = 1; //改变标志位Old_KeyNum |= KEY1_DoubleClick_Value; //记录按下值Old_Key_Value = Old_KeyNum; //记录上次返回的状态值}else {Old_KeyNum &= 0x0000; //清除记录Old_KeyNum |= KEY1_DoubleClick_Value; //更新上一次的值if(Old_Key_Value != Old_KeyNum)  //上次最终返回的状态值与记录第一次双击按下值不一致Old_Key_Value = Old_KeyNum;  //更新最后返回状态值 else{DoubleClickFlag = 0; //重置双击按下标志位Old_Key_Value = 0x0000; //重置最后返回状态Old_KeyNum = 0x0000; //重置按下值Key_Value |= KEY1_DoubleClick_Value; //双击返回状态值}}}else if(Time_Count > 20 && Time_Count <= 40)  //短按时间判断Key_Value |= KEY1_ShortPress_Value;  //短按时间返回状态值else if(Time_Count > 40)  //长按时间判断Key_Value |= KEY1_LongPress_Value;  //长按时间返回状态值//            printf("Time_Count : %d\n",Time_Count); //测试时使用} else if(!Key_IT_Flag && !Key1State) //按下按键{Key1State = 1; //按下标志位Key_IT_Flag = 1; //已进入中断标志位Time_Count = 0; //每次按下从0开始计时TIM_Cmd(TIM2,ENABLE); //开启时钟}EXTI_ClearITPendingBit(KEY1_EXTI_LINE); //清除中断标志位}
}#endif#if isEnableKey2
/*****
* @func : EXTI9_5_IRQHandler()
* @describe : 中断函数
* @param : void
* @return : void
* @note : 中断需要的处理的操作在此处执行
*****/
void EXTI9_5_IRQHandler()
{static uint8_t Key2State = 0; //静态按键1状态 0 松开 1按下if(EXTI_GetITStatus(KEY2_EXTI_LINE) != RESET){if(Key2_Press && Key2State) //松开按键{Key2State = 0; //松开标志Key_IT_Flag = 0; //退出中断标志位TIM_Cmd(TIM2,DISABLE); //关闭时钟if(Time_Count > 0 && Time_Count <= 20) //短按时间判断{if(!DoubleClickFlag) //第一次双击按下{DoubleClickFlag = 1; //改变标志位Old_KeyNum |= KEY2_DoubleClick_Value; //记录按下值Old_Key_Value = Old_KeyNum; //记录上次返回的状态值}else {Old_KeyNum &= 0x0000; //清除记录Old_KeyNum |= KEY2_DoubleClick_Value; //更新上一次的值if(Old_Key_Value != Old_KeyNum)  //上次最终返回的状态值与记录第一次双击按下值不一致Old_Key_Value = Old_KeyNum;  //更新最后返回状态值 else{DoubleClickFlag = 0; //重置双击按下标志位Old_Key_Value = 0x0000; //重置最后返回状态Old_KeyNum = 0x0000; //重置按下值Key_Value |= KEY2_DoubleClick_Value; //双击返回状态值}}}else if(Time_Count > 20 && Time_Count <= 40)  //短按时间判断Key_Value |= KEY2_ShortPress_Value;  //短按时间返回状态值else if(Time_Count > 40)  //长按时间判断Key_Value |= KEY2_LongPress_Value;  //长按时间返回状态值//            printf("Time_Count : %d\n",Time_Count); //测试时使用} else if(!Key_IT_Flag && !Key2State) //按下按键{Key2State = 1; //按下标志位Key_IT_Flag = 1; //已进入中断标志位Time_Count = 0; //每次按下从0开始计时TIM_Cmd(TIM2,ENABLE); //开启时钟}EXTI_ClearITPendingBit(KEY2_EXTI_LINE); //清除中断标志位}
}#endif#if isEnableKey3
/*****
* @func : EXTI4_IRQHandler()
* @describe : 中断函数
* @param : void
* @return : void
* @note : 中断需要的处理的操作在此处执行
*****/
void EXTI4_IRQHandler()
{static uint8_t Key3State = 0; //静态按键1状态 0 松开 1按下if(EXTI_GetITStatus(KEY3_EXTI_LINE) != RESET){if(Key3_Press && Key3State) //松开按键{Key3State = 0; //松开标志Key_IT_Flag = 0; //退出中断标志位TIM_Cmd(TIM2,DISABLE); //关闭时钟if(Time_Count > 0 && Time_Count <= 20) //短按时间判断{if(!DoubleClickFlag) //第一次双击按下{DoubleClickFlag = 1; //改变标志位Old_KeyNum |= KEY3_DoubleClick_Value; //记录按下值Old_Key_Value = Old_KeyNum; //记录上次返回的状态值}else {Old_KeyNum &= 0x0000; //清除记录Old_KeyNum |= KEY3_DoubleClick_Value; //更新上一次的值if(Old_Key_Value != Old_KeyNum)  //上次最终返回的状态值与记录第一次双击按下值不一致Old_Key_Value = Old_KeyNum;  //更新最后返回状态值 else{DoubleClickFlag = 0; //重置双击按下标志位Old_Key_Value = 0x0000; //重置最后返回状态Old_KeyNum = 0x0000; //重置按下值Key_Value |= KEY3_DoubleClick_Value; //双击返回状态值}}}else if(Time_Count > 20 && Time_Count <= 40)  //短按时间判断Key_Value |= KEY3_ShortPress_Value;  //短按时间返回状态值else if(Time_Count > 40)  //长按时间判断Key_Value |= KEY3_LongPress_Value;  //长按时间返回状态值//            printf("Time_Count : %d\n",Time_Count); //测试时使用} else if(!Key_IT_Flag && !Key3State) //按下按键{Key3State = 1; //按下标志位Key_IT_Flag = 1; //已进入中断标志位Time_Count = 0; //每次按下从0开始计时TIM_Cmd(TIM2,ENABLE); //开启时钟}EXTI_ClearITPendingBit(KEY3_EXTI_LINE); //清除中断标志位}
}#endif#if isEnableKey4
/*****
* @func : EXTI0_IRQHandler()
* @describe : 中断函数
* @param : void
* @return : void
* @note : 中断需要的处理的操作在此处执行
*****/
void EXTI0_IRQHandler()
{static uint8_t Key4State = 0; //静态按键1状态 0 松开 1按下if(EXTI_GetITStatus(KEY4_EXTI_LINE) != RESET){if(Key4_Press && Key4State) //松开按键{Key4State = 0; //松开标志Key_IT_Flag = 0; //退出中断标志位TIM_Cmd(TIM2,DISABLE); //关闭时钟if(Time_Count > 0 && Time_Count <= 20) //短按时间判断{if(!DoubleClickFlag) //第一次双击按下{DoubleClickFlag = 1; //改变标志位Old_KeyNum |= KEY4_DoubleClick_Value; //记录按下值Old_Key_Value = Old_KeyNum; //记录上次返回的状态值}else {Old_KeyNum &= 0x0000; //清除记录Old_KeyNum |= KEY4_DoubleClick_Value; //更新上一次的值if(Old_Key_Value != Old_KeyNum)  //上次最终返回的状态值与记录第一次双击按下值不一致Old_Key_Value = Old_KeyNum;  //更新最后返回状态值 else{DoubleClickFlag = 0; //重置双击按下标志位Old_Key_Value = 0x0000; //重置最后返回状态Old_KeyNum = 0x0000; //重置按下值Key_Value |= KEY4_DoubleClick_Value; //双击返回状态值}}}else if(Time_Count > 20 && Time_Count <= 40)  //短按时间判断Key_Value |= KEY4_ShortPress_Value;  //短按时间返回状态值else if(Time_Count > 40)  //长按时间判断Key_Value |= KEY4_LongPress_Value;  //长按时间返回状态值//            printf("Time_Count : %d\n",Time_Count); //测试时使用} else if(!Key_IT_Flag && !Key4State) //按下按键{Key4State = 1; //按下标志位Key_IT_Flag = 1; //已进入中断标志位Time_Count = 0; //每次按下从0开始计时TIM_Cmd(TIM2,ENABLE); //开启时钟}EXTI_ClearITPendingBit(KEY4_EXTI_LINE); //清除中断标志位}
}#endif

最后main函数代码进行测试,大家注意的是需要对printf函数进行重写。

main.c

#include "stm32f10x.h"
#include "sys.h"
#include "usart.h"
#include "key.h"
#include "time2.h"int main()
{uint16_t keyNum = 0;USART1_Init(115200); //初始化USARTTIME2_Init(10000,72); //初始TIM2Key_Init(); //按键初始化while(1){keyNum = GetKeyNum();switch(keyNum){case 11:printf("按键1短按\n");printf("短按返回值 keyNum = %2d\n",keyNum);puts("");break;case 12:printf("按键1长按\n");printf("长按返回值 keyNum = %2d\n",keyNum);puts("");break;case 14:printf("按键1双击\n");printf("双击返回值 keyNum = %2d\n",keyNum);puts("");break;case 21:printf("按键2短按\n");printf("短按返回值 keyNum = %2d\n",keyNum);puts("");break;case 22:printf("按键2长按\n");printf("长按返回值 keyNum = %2d\n",keyNum);puts("");break;case 24:printf("按键2双击\n");printf("双击返回值 keyNum = %2d\n",keyNum);puts("");break;case 31:printf("按键3短按\n");printf("短按返回值 keyNum = %2d\n",keyNum);puts("");break;case 32:printf("按键3长按\n");printf("长按返回值 keyNum = %2d\n",keyNum);puts("");break;case 34:printf("按键3双击\n");printf("双击返回值 keyNum = %2d\n",keyNum);puts("");break;case 41:printf("按键4短按\n");printf("短按返回值 keyNum = %2d\n",keyNum);puts("");break;case 42:printf("按键4长按\n");printf("长按返回值 keyNum = %2d\n",keyNum);puts("");break;case 44:printf("按键4双击\n");printf("双击返回值 keyNum = %2d\n",keyNum);puts("");break;default:break;}}
}

最后的最后献上测试截图,感谢大家的观看👀。

相关文章:

STM32f103实现按键长按 短按 双击

今天来分享一个使用EXIT外部中断加TIM计时器实现按键长短按以及双击操作&#xff0c;不过笔者在双击上有点瑕疵&#xff0c;就是当你按下双击第一下停顿几秒按第二下依然会识别为双击操作&#xff0c;笔者猜测只要板子不停电即便到第二天按下第二下依旧会识别双击操作&#xff…...

【WP】猿人学13_入门级cookie

https://match.yuanrenxue.cn/match/13 抓包分析 抓包分析发现加密参数是cookie中有一个yuanrenxue_cookie 当cookie过期的时候&#xff0c;就会重新给match/13发包&#xff0c;这个包返回一段js代码&#xff0c;应该是生成cookie的 <script>document.cookie(y)(u)(a…...

分享一款提取抖音小店商家电话的软件使用教程

抖音作为一款国内非常流行的短视频分享平台&#xff0c;吸引了大量用户和商家。许多商家在抖音上开设了小店&#xff0c;但是抖音并没有提供直接获取商家电话的功能。本文将分享一款提取抖音小店商家电话的软件&#xff0c;并附带使用教程和代码。 教程 步骤一&#xff1a;安…...

反转链表的三种方法--面试必考(图例超详细解析,小白一看就会!!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐ 头插法 --- 创建新的链表 ⭐ 迭代法 --- 三指针 ⭐ 递归法 四、总结与提炼 五、共勉 一、前言 反转链表这道题&#xff0c;可以说是--链表专题--&#xff0c;最经典的一道题&#xff0c;也是在面试中频率最高的一道题目&…...

Springboot注意点

1.Usermapper里加param注解 2.RequestParam 和 RequestBody的区别&#xff1a; RequestParam 和 RequestBody的区别&#xff1a; RequestParam 和 RequestBody 是Spring框架中用于处理HTTP请求的两个不同的注 get请求一般用url传参数&#xff0c;所以参数名和参数的值就在ur…...

数组和指针的联系(C语言)

数组和指针是两种不同的数据类型&#xff0c;数组是一种构造类型&#xff0c;用于存储一组相同类型的变量&#xff1b;而指针是一种特殊类型&#xff0c;专门用来存放数据的地址。数组名除了sizeof(数组名)和&数组名表示整个数组外&#xff0c;其他情况下都表示的是首元素的…...

安全区域边界

文章目录 安全区域边界边界防护跨边界流量通过受控接口通信非法内联非法外联限制无线网络 访问控制启用基于白名单的访问控制策略优化访问控制表根据五元组控制根据会话状态控制根据应用协议和内容控制 入侵防范外部发起的攻击内部发起的攻击对新型攻击防范及时检测攻击行为 恶…...

力扣每日一题 6/6

2938.区分黑球与白球[中等] 题目&#xff1a; 桌子上有 n 个球&#xff0c;每个球的颜色不是黑色&#xff0c;就是白色。 给你一个长度为 n 、下标从 0 开始的二进制字符串 s&#xff0c;其中 1 和 0 分别代表黑色和白色的球。 在每一步中&#xff0c;你可以选择两个相邻的…...

游戏心理学Day05

第三章 游戏即学习 《超级马里奥》是游戏史上的经典之作&#xff0c;我们都记得第一次踩到敌人&#xff0c;第一次顶碎砖块时的快乐&#xff0c;也记得为了通过某个关卡而付出的努力和艰辛。当我们掌握了规律和技巧之后&#xff0c;这些难题就不再是难题&#xff0c;因为我们习…...

【C、C++编译工具】CLion工具介绍与安装

一、问题 最近突发奇想想学学最开始接触的语言C&#xff0c;之前大学的时候用的更多的工具还是VC&#xff0c;工作后慢慢接触了CLion&#xff0c;跟pycharm其实差不多&#xff0c;都是集成开发环境&#xff08;IDE&#xff09; 解释&#xff1a;什么是 IDE&#xff1f; 根据计…...

LabVIEW中进行步进电机的位置控制

在LabVIEW中进行步进电机的位置控制&#xff0c;通常涉及以下几个关键步骤&#xff1a;设置硬件、配置通信、编写控制算法和实施反馈控制。以下是一个详细的介绍。 硬件设置 步进电机&#xff1a;选择合适的步进电机&#xff0c;根据负载和应用需求选择适当的步数和转矩。 驱…...

目标检测-AnyLabeling标注格式转换成YOLO格式

Anylabel可以极大的增加数据的标注效率&#xff0c;但是其标注格式如何能转换成YOLO标注格式&#xff0c;具体内容如下所示。 关于AnyLabeling的其它详细介绍如下链接所示 https://blog.csdn.net/u011775793/article/details/134918861 Github链接 https://github.com/vietanhd…...

MongoDB管理内存使用

优化MongoDB内存使用&#xff0c;可以通过一下几点来降低系统内存占用&#xff0c;本次主要配置WiredTiger Cache来实现 WiredTiger Cache&#xff1a; MongoDB 使用 WiredTiger 存储引擎&#xff0c;其缓存使用最近最少使用 (LRU) 算法管理。频繁访问的数据会保留在内存中&am…...

【Elasticsearch】IK分词器的下载及使用

安装IK分词器 网址&#xff1a;https://github.com/infinilabs/analysis-ik 3.1.在线安装ik插件&#xff08;较慢,不推荐&#xff09; # 进入容器内部 es为容器名称 docker exec -it es /bin/bash# 在线下载并安装 7.17.21为镜像版本要与之前保持一致 ./bin/elasticsearch-pl…...

Hyper-SD: diffusion实时出图,一步搞定,字节出品

Hyper-SD: diffusion实时出图&#xff0c;一步搞定&#xff0c;字节出品 先看效果 Real-Time Generation Demo of Hyper-SD. Abstract 近来&#xff0c;一系列面向扩散模型&#xff08;Diffusion Models&#xff0c;DM&#xff09;的迭代紧凑式传播推断算法陆续出现&#xf…...

:长亭雷池社区版动态防护体验测评

序 长亭雷池在最近发布了动态防护功能&#xff0c;据说可以动态加密保护网页前端代码和阻止爬虫行为、阻止漏洞扫描行为等。今天就来体验测试一下 WAF 是什么 WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF …...

数据结构复习

基本概念和术语&#xff1a; 数据&#xff1a;是描述客观事物的符号&#xff0c;是计算机中可以操作的对象&#xff0c;是能被计算机识别&#xff0c;并输入给计算机处理的符号集合。 数据元素&#xff1a;是组成数据的&#xff0c;具有一定意义的基本单位&#xff0c;在计算机…...

小世界网络生成及其分析

研究背景: 小世界网络是一种介于规则网络和随机网络之间的网络模型,具有短平均路径和高聚集性的特点。这种网络模型被广泛应用于社交网络、互联网、生物网络等领域的研究中。研究小世界网络的生成和分析可以帮助我们理解和揭示复杂网络的结构和特性,以及网络中信息传播、动力…...

Flutter基础 -- Flutter布局练习(小项目)

目录 1. Splash 布局&#xff08;第一页&#xff09; 1.1 目标 1.2 当前效果图 1.3 创建 Splash 界面 1.4 设置 MaterialApp 1.5 设置 Splash 背景色 1.6 布局 Splash 界面 1.7 总结 2. Splash 圆角图片 2.1 目标 2.2 当前效果图 2.3 蓝湖下载图片 2.4 图片导入项…...

详解布隆过滤器,实现分布式布隆过滤器

什么是布隆过滤器&#xff1f; 原理 布隆过滤器是一种基于位数组&#xff08;bit array&#xff09;和多个哈希函数的数据结构。其核心原理是&#xff1a; 初始化一个长度为m的位数组&#xff0c;所有位初始化为0。使用k个不同的哈希函数将元素映射到位数组中的k个位置。当插…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...