STM32f103实现按键长按 短按 双击

注:刚刚优化了这个bug,写在了下篇文章中(下篇文章)🐱‍🐉。

今天来分享一个使用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内部时钟,可以不配置,默认是一直开着的
    
    //配置TIM
    TIM_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中断,寻去中断更新
    
    //配置NVIC
    NVIC_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 isEnableKey1
        RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK,ENABLE); //key1所在的时钟
    #endif
    #if isEnableKey2
        RCC_APB2PeriphClockCmd(KEY2_GPIO_CLK,ENABLE); //key2所在的时钟
    #endif
    #if isEnableKey3
        RCC_APB2PeriphClockCmd(KEY3_GPIO_CLK,ENABLE); //key3所在的时钟
    #endif
    #if isEnableKey4
        RCC_APB2PeriphClockCmd(KEY4_GPIO_CLK,ENABLE); //key4所在的时钟
    #endif
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    
    //配置KEY
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    
    #if isEnableKey1
        GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
        GPIO_Init(KEY1_GPIO_PORT,&GPIO_InitStructure);
    #endif
    
    #if isEnableKey2
        GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN;
        GPIO_Init(KEY2_GPIO_PORT,&GPIO_InitStructure);
    #endif
    
    #if isEnableKey3
        GPIO_InitStructure.GPIO_Pin = KEY3_GPIO_PIN;
        GPIO_Init(KEY3_GPIO_PORT,&GPIO_InitStructure);
    #endif
    
    #if isEnableKey4
        GPIO_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 isEnableKey1
    if(KEY1 == RESET) //检测到按键1
    {
        delay_ms(20); //消抖
        if(KEY1 == RESET) //再次检测按键1
        {
            Key1_Press = 1; //记录此时按键状态
        }
    }else
        Key1_Press = 0;
    #endif
    
    #if isEnableKey2
    if(KEY2 == RESET) //检测到按键2
    {
        delay_ms(20); //消抖
        if(KEY2 == RESET) //再次检测按键2
            Key2_Press = 1; //记录此时按键状态
    }else
        Key2_Press = 0;
    #endif
    
    #if isEnableKey3
    if(KEY3 == RESET) //检测到按键3
    {
        delay_ms(20); //消抖
        if(KEY3 == RESET) //再次检测按键3
            Key3_Press = 1; //记录此时按键状态
    }else
        Key3_Press = 0;
    #endif
    
    #if isEnableKey4
    if(KEY4 == SET) //检测到按键4  注意的是按键四是高电平检测到按键按下
    {
        delay_ms(20); //消抖
        if(KEY4 == SET) //再次检测按键4
            Key4_Press = 1; //记录此时按键状态
    }else
        Key4_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 isEnableKey1
            case 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 isEnableKey2
            case 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 isEnableKey3
            case 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 isEnableKey4
            case 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;
            #endif
            
            default:
                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); //初始化USART
    TIME2_Init(10000,72); //初始TIM2
    Key_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;
        }
    }
}

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

<think>好的,我现在需要回答用户关于如何在STM32F103中使用switch case实现按键开关功能的示例代码。首先,我需要理解用户的需求。他们想要一个基于状态机的按键处理程序,使用switch case结构来管理按键的不同状态,比如短按按、双击等。根据提供的引用资料,特别是引用[2]、[3]、[5],这些资料提到了使用状态机和定时器来处理按键事件,包括消抖和多击检测。 首先,用户可能希望代码结构清晰,易于移植和修改。因此,我需要设计一个模块化的代码结构,将按键的GPIO初始化、定时器配置以及中断处理分开。参考引用[4]中的KEY_Scan函数,该函数使用了静态变量来跟踪按键状态,但用户要求使用switch case,这可能更适合状态机的方式。 接下来,我需要确定按键的状态转移。通常,按键的状态可能包括:释放状态(KEY_RELEASE)、消抖等待(KEY_DEBOUNCE)、按下确认(KEY_PRESSED)、按检测(KEY_LONG_PRESS)、以及可能的双击检测(KEY_DOUBLE_CLICK)等。每个状态通过定时器中断来更新,使用switch case处理不同状态下的逻辑。 然后,考虑到按键消抖,需要在检测到按键按下后延时一段时间再次检测,以消除机械抖动。引用[4]中的代码使用了delay_ms(10)进行消抖,但在中断中使用阻塞延时可能不太合适,因此应该通过定时器中断的非阻塞方式来处理,比如在状态机中记录时间。 另外,按和双击的检测需要计时。例如,按可能需要持续按下超过一定时间(如1秒),而双击需要在特定时间窗口内检测到两次按下。引用[5]提到使用定时器中断记录时间,比较时间差来判断按键持续时间,这可以通过在状态机中维护时间计数器来实现。 现在,我需要将这些元素整合到一个使用switch case的状态机中。例如,在定时器中断服务例程中,每个按键都有一个状态变量,根据当前状态执行不同的操作。例如,当按键处于释放状态时,检测到引脚低电平则进入消抖状态,启动消抖计时;消抖结束后确认按下,进入按下状态,并开始计时按;如果在一定时间内释放,则视为短按,否则触发按。 代码结构方面,可能需要定义按键的结构体,包含引脚、状态、计时器、点击次数等字段。参考引用[3],其中提到使用多个按键并允许启用或禁用,因此结构体数组可以支持多个按键的配置。 最后,示例代码需要展示如何初始化GPIO和定时器,实现状态机处理,并在主循环或中断中调用按键扫描函数。同时,需要处理按键事件,如开关LED,这可以通过回调函数或直接操作GPIO实现。 需要确保代码符合STM32的标准库函数,比如使用HAL库或标准外设库。由于用户没有指定具体库,示例可能需要使用通用的配置方法,或者注明需要根据实际情况调整GPIO和定时器的初始化部分。 可能的挑战包括正确处理状态转移的时机,避免遗漏状态,以及确保定时器中断的频率足够快以检测按键事件。例如,定时器中断每10ms触发一次,用于更新按键状态和计时。 总结步骤: 1. 定义按键状态枚举和结构体。 2. 初始化按键GPIO和定时器中断。 3. 在定时器中断中更新每个按键的状态机。 4. 使用switch case处理不同状态,包括消抖、按下、按、双击等。 5. 在主程序中处理按键事件,如控制LED开关。 现在需要将这些思考转化为示例代码,并确保符合用户提供的引用中的方法,同时使用switch case结构。需要检查代码中的状态转移是否正确,是否处理了消抖和多次点击,以及是否能够检测按和短按。此外,代码应易于移植,允许用户配置按键数量和引脚,如引用[3]中所述。</think>### STM32F103使用switch-case实现按键开关功能示例 以下是基于状态机设计的按键处理代码,通过`switch-case`实现短按按和双击检测,并控制LED开关[^2][^5]: #### 1. 硬件配置 ```c //按键定义(根据硬件连接修改) #define KEY1_PIN GPIO_Pin_0 #define KEY1_PORT GPIOA #define LED_PIN GPIO_Pin_13 #define LED_PORT GPIOC ``` #### 2. 状态机定义 ```c typedef enum { KEY_RELEASE, // 释放状态 KEY_DEBOUNCE, // 消抖检测 KEY_PRESSED, // 按下确认 KEY_LONG_PRESS, // 按状态 KEY_DOUBLE_CHECK // 双击检测 } KeyState; typedef struct { KeyState state; // 当前状态 uint32_t pressTime; // 按下时间戳 uint8_t clickCount; // 点击次数 } KeyHandle; ``` #### 3. 按键处理函数(定时器中断中调用) ```c KeyHandle key1 = {KEY_RELEASE, 0, 0}; void KEY_Handler(void) { switch(key1.state) { case KEY_RELEASE: if (GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == 0) { // 检测按下 key1.state = KEY_DEBOUNCE; key1.pressTime = HAL_GetTick(); // 记录按下时间 } break; case KEY_DEBOUNCE: if (HAL_GetTick() - key1.pressTime > 15) { // 消抖15ms if (GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == 0) { key1.state = KEY_PRESSED; key1.clickCount++; } else { key1.state = KEY_RELEASE; } } break; case KEY_PRESSED: if (GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == 1) { // 已释放 if (HAL_GetTick() - key1.pressTime < 1000) { // 短按 if (key1.clickCount == 1) { GPIO_ToggleBits(LED_PORT, LED_PIN); // 开关LED } key1.clickCount = 0; key1.state = KEY_RELEASE; } } else if (HAL_GetTick() - key1.pressTime > 1000) { // 按1秒 GPIO_WriteBit(LED_PORT, LED_PIN, Bit_RESET); // 强制关灯 key1.state = KEY_LONG_PRESS; } break; case KEY_LONG_PRESS: if (GPIO_ReadInputDataBit(KEY1_PORT, KEY1_PIN) == 1) { key1.state = KEY_RELEASE; } break; case KEY_DOUBLE_CHECK: // 双击检测扩展 // 可在此添加双击处理逻辑 break; } } ``` #### 4. 定时器初始化(示例使用TIM2) ```c void TIM2_Init(void) { TIM_TimeBaseInitTypeDef timer; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); timer.TIM_Prescaler = 7200 - 1; // 10kHz计数频率 timer.TIM_CounterMode = TIM_CounterMode_Up; timer.TIM_Period = 100 - 1; // 10ms中断周期 timer.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, &timer); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); } ``` #### 5. 中断服务函数 ```c void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { KEY_Handler(); // 每10ms执行一次按键扫描 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } ``` #### 实现说明 1. **状态机流程**:通过`KEY_RELEASE → KEY_DEBOUNCE → KEY_PRESSED`状态转移完成基础检测 2. **按判断**:当持续按下超过1秒时触发按动作(示例中关闭LED) 3. **扩展性**:在`KEY_DOUBLE_CHECK`状态可添加双击检测逻辑[^3] 4. **消抖处理**:通过15ms延时消除机械抖动[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值