• stm32【按键处理:单击、连击、长按】


    原文: https://blog.csdn.net/qq_40102829/article/details/108927767

    按键处理
    测试平台:STM32F103C8T6
    库版本:官方标准库3.5.0版本

    按键:机械按键(需消除抖动影响)或触摸按键,
    单片机硬件需求:定时器1个,IO口外部中断功能

    按键处理是单片机底层驱动的一个基础应用,本文说的按键处理为独立按键的单击,连击和长按的识别(不是按键矩阵的实现)。在51单片机入门时,通常是通过主循环查询I/O口状态来进行按键识别的,但是占用资源较多,而且实时性较差;进阶的会使用定时器进行识别,关于定时器实现按键检测的例程,网上有很多,但是观看下来发现能详细描述独立按键的不同动作识别的例程不多,故写此文

    注: 文中首次出现的代码块会标注[xxx.c]或[xxx.h],表明该代码是属于对应的文件,未标注的即为重复出现的
    目录
    按键处理
    1、电路设计
    2、程序设计
    2.1、原理
    2.2、基础配置
    2.3、按键处理结构体
    2.4、外部中断处理函数
    2.5、按键处理函数
    2.5.1、按键按下处理
    2.5.2、按键松开处理
    2.6、按键事件类型识别
    3、测试结果
    4、总结
    1、电路设计
    独立按键的电路设计很简单,这是使用的是开发板上的按键,IO到按键后直接下拉到地,那么按键按下时为低电平,常态为高电平,也就是说IO口需要配置成内部上拉。

    需要注意的是,这种设计是不够完善的,对于机械按键最好可以加上一个旁路电容来消除抖动,或者在程序里需要加上消抖延迟,本文是基于触摸按键进行测试的,因此解决了抖动问题。

    2、程序设计
    2.1、原理
    独立按键处理无非是对于IO口电平的识别,但是要实现比较不同动作的识别,还需要对按键的时间进行判断,理清按键时间和按键状态的关系有助于接下来的程序编写

    /*
    常态为高电平
    按键动作 两次动作间隔 按键动作
    | continus | idle |
    _____________ _____________ _____________
    | | | |
    |_____________| |_____________|

    */
    1
    2
    3
    4
    5
    6
    7
    8
    9
    按键时间分为两种【按下后的持续时间:continus】【按键松开后的空闲时间:idle】,这两个时间可以根据实际测试确定
    这里作两个设定:
    1、当【continus】在500ms内时,为短按,否则为长按
    2、当【idle】小于400ms,为连击动作,否则为可认为一次完整的按键事件结束
    对这两个时间进行组合判断,就可以识别出按键的事件类型【单击、连击、长按】

    短按和长按的通过【continus】来区分
    单击是连击的一种特殊状态,都划分为短按,可以通过【idle】的时间来区分,可设置一个变量来记录连击的次数

    2.2、基础配置
    主要包括IO口配置,外部中断配置、定时器配置

    [key_process.c]
    /*******************************************************************************
    * @brief 以外部中断形式配置按键GPIO
    * @param 无
    * @retval 无
    ******************************************************************************/
    void EXTI_GPIO_Config(void)
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    /* GPIO config*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //内部上拉输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* EXTI line mode config */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource8);
    EXTI_InitStructure.EXTI_Line = EXTI_Line8;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //上升下降沿中断
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    ▲这里使用的是PA8管脚,内部上拉,外部中断选择上下沿都触发,也就是说按键按下和松开都会进行一次触发

    [key_process.c]
    /*******************************************************************************
    * @brief 计数定时器配置函数
    * @param TIMx 使用的定时器
    * @retval 无
    ******************************************************************************/
    void TIMx_Config(TIM_TypeDef* TIMx)
    {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    // 自动重装载寄存器周的值(计数值)
    TIM_TimeBaseStructure.TIM_Prescaler= 71; // 时钟预分频数为 71,则驱动计数器的时钟 CK_CNT = CK_INT / (71+1)=1M
    TIM_TimeBaseStructure.TIM_Period=1000; // 累计 TIM_Period 个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频因子 ,基本定时器没有,不用管
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
    // 初始化定时器
    TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);

    TIM_ARRPreloadConfig(TIMx, ENABLE); //使能TIM重载寄存器ARR

    TIM_ClearFlag(TIMx, TIM_FLAG_Update); // 清除计数器中断标志位

    TIM_ITConfig(TIMx,TIM_IT_Update,ENABLE); // 开启计数器中断

    TIM_Cmd(TIMx, DISABLE); // 关闭定时器的时钟,等待使用
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    ▲为了后面方便计算,这里定时器配置为1ms触发一次,当然也可以设定为其他触发时间,只要后面的时间数据计算好就行

    [key_process.c]
    /*******************************************************************************
    * @brief 按键外部中断NVIC配置,与使用的GPIO保持一致
    * @param 无
    * @retval 无
    ******************************************************************************/
    static void NVIC_EXTI_GPIO_Config(void)
    {
    NVIC_InitTypeDef NVIC_InitStructure;

    /* Configure one bit for preemption priority */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

    /* 配置中断源 */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    }

    /*******************************************************************************
    * @brief TIM2中断NVIC配置,与计数使用的定时器保持一致
    * @param 无
    * @retval 无
    ******************************************************************************/
    static void NVIC_TIM2_Config(void)
    {
    NVIC_InitTypeDef NVIC_InitStructure;
    // 设置中断组为 0
    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);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    ▲对于NVIC的配置就随各位喜欢了

    2.3、按键处理结构体
    以下是完整的头文件(除去函数声明等)

    [key_process.h]

    #define KEY_PIN GPIO_Pin_8 //注意查看中断服务函数是否与该管脚对应

    //定时器为1ms定时
    #define KEY_TIME_IDLE 400 //按键动作空闲时间
    #define KEY_TIME_CONTINUS 500 //按键动作持续时间
    #define KEY_TIME_OUT 2000 //按键超时

    //事件类型
    #define EVENT_NONE_CLICK 0x00 //无动作
    #define EVENT_SHORT_CLICK 0x01 //短按
    #define EVENT_DOUBLE_CLICK 0x02 //连击
    #define EVENT_LONG_CLICK 0x03 //长按

    //按键状态
    #define KEY_STATE_IDLE 0x00 //空闲
    #define KEY_STATE_PRESS 0x01 //按下
    #define KEY_STATE_RELEASE 0x02 //松开


    typedef struct{
    struct{
    u8 check :1; //查询标志
    u8 key_state :2; //单次按键动作【0:无动作/1:按下/2:松开】
    u8 once_event :1; //一次完整按键事件,置1时可以进行事件识别,必须手动清零
    u8 press_time :1; //单次按键动作持续时间【0:短按/1:长按】
    }flag;

    u8 event_current_type :4; //当前按键事件类型【00:无动作】【01:短按】【10:连击】【11:长按】
    u8 event_previous_type :4; //之前按键事件类型,需手动更新

    u8 press_cnt; //按下次数,复位值为1,最大连击次数为256次

    u16 time_idle; //按键空闲时间计数器
    u16 time_continus; //按键动作持续时间计数器

    }KEY_PROCESS_TypeDef;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    ▲每个变量的作用在注释里已经标明,这里对单击连击长按表述为一次完整的【事件类型】

    2.4、外部中断处理函数
    [key_process.c]
    /*******************************************************************************
    * @brief This function handles EXTI9_5 interrupt request.与使用的GPIO保持一致
    * @param 无
    * @retval 无
    ******************************************************************************/
    void EXTI9_5_IRQHandler(void)
    {
    // delay_ms(50); //消抖,机械按键需要消抖
    EXTI->PR = EXTI_Line8; //清除中断标志位

    if((GPIOA->IDR & KEY_PIN) == 0){
    key.flag.key_state = KEY_STATE_PRESS; //按下
    key.flag.check = 1;
    key.time_continus = 0; //按键持续时间置零,准备开始计时
    }
    else{
    key.flag.key_state = KEY_STATE_RELEASE; //松开
    key.flag.check = 1;
    key.time_idle = 0; //按键空闲时间置零,准备开始计时
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ▲如果是机械按键,那么外部中断函数里的消抖是必须的
    在配置外部中断功能时,是使用上下沿中断触发的,因此在每次触发时都需要读取IO口的状态,以判断此次触发是上升沿还是下降沿触发,对【key.flag.key_state】赋值
    【key.flag.check】是内部查询标志,按下或松开都触发
    【key.time_continus】和【key.time_idle】分别在按下和松开时都清零,其计数是在定时器里实现的

    2.5、按键处理函数
    包含定时器中断函数、按键处理函数、按键事件识别函数(该函数也可放在主循环)

    [key_process.c]
    /*******************************************************************************
    * @brief This function handles TIM2 interrupt request.与计数使用的定时器保持一致
    * @param 无
    * @retval 无
    ******************************************************************************/
    void TIM2_IRQHandler(void)
    {
    // TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);
    TIM2->SR = 0x0000; //清除中断标志位
    KEY_Process();
    KEY_Scan();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ▲使用定时器TIM2,这里【KEY_Process】是核心的判断函数,定时器设定为1ms触发一次,也就是【KEY_Process】每1ms调用一次,这点很关键
    【KEY_Scan】是按键事件类型识别函数

    [key_process.c]
    /*******************************************************************************
    * @brief 按键处理函数
    * @param 无
    * @retval 无
    ******************************************************************************/
    void KEY_Process(void)
    {
    switch(key.flag.key_state){
    //【按键按下】
    case KEY_STATE_PRESS :
    //在按键按下时从0开始计时,直到超时
    if(key.time_continus < KEY_TIME_OUT){key.time_continus++;}

    //发生长按事件
    if(key.time_continus > KEY_TIME_CONTINUS){
    if(key.event_current_type != EVENT_NONE_CLICK){ //识别长按前的按键事件
    if(key.press_cnt > 1){key.press_cnt--;}
    key.flag.once_event = 1;
    }
    else{
    key.flag.press_time = 1; //【0:短按/1:长按】识别此次为长按
    key.flag.key_state = KEY_STATE_IDLE; //主动结束按下动作,进入无动作状态,保证在长按的按下过程中就识别出长按事件

    key.event_current_type = EVENT_LONG_CLICK; //分配当前按键事件类型
    key.flag.once_event = 1; //产生按键事件
    key.press_cnt = 1;
    key.time_idle = KEY_TIME_OUT; //按键空闲时间超时
    }
    }

    //按下时进行一次判断
    if(key.flag.check){
    key.flag.check = 0;
    if(!key.flag.press_time){ //判断上一次按键类型
    //判断上一次按键动作空闲时间
    if(key.time_idle < KEY_TIME_IDLE){ //若上一次按键动作后的空闲时间在规定时间内,说明发生了连击事件,一次完整的按键事件还未结束
    key.press_cnt++;
    }else{key.press_cnt = 1;}
    }
    key.flag.press_time = 0; //【0:短按/1:长按】此次为短按
    }
    break;

    //【按键松开】,若是长按,不会进入该判断
    case KEY_STATE_RELEASE:
    //在按键按下时从0开始计时,直到超时
    if(key.time_idle < KEY_TIME_OUT){key.time_idle++;}

    //松开时进行一次判断
    if(key.flag.check){
    key.flag.check = 0;

    //判断此次按键动作
    if(!key.flag.press_time){ ///长按会屏蔽短按
    if(key.press_cnt > 1){ //连击事件
    key.event_current_type = EVENT_DOUBLE_CLICK; //分配按键事件类型
    }
    else{ //单击事件
    key.event_current_type = EVENT_SHORT_CLICK; //分配按键事件类型
    key.press_cnt = 1; //连击次数置1
    }
    }
    }

    //按键松开后判断此次按键动作后的空闲时间,从而判断此次动作是否结束
    if(key.time_idle > KEY_TIME_IDLE){ //空闲时间超时,认为一次完整的按键事件结束
    if(!key.flag.press_time){ //松开前是短按标志,则产生按键事件,这里是为了屏蔽长按后的松手动作
    key.flag.once_event = 1; //产生按键事件
    key.flag.key_state = KEY_STATE_IDLE;//进入无动作状态
    }
    }
    break;
    //【按键无动作】
    default :
    break;
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    ▲按键处理函数是一个switch判断,分为三部分:
    1、按键按下
    2、按键松开
    3、按键无动作(不做任何处理)
    switch的判断值【key.flag.key_state】是在外部中断函数中进行赋值的,是人为控制的

    2.5.1、按键按下处理
    按下时分为三部分处理
    一、【key.time_continus】计时,在2000ms超时前进行自加

    if(key.time_continus < KEY_TIME_OUT){key.time_continus++;}
    1
    这里就涉及到其前边提到的【KEY_Process()】每1ms调用一次
    当按键按下且未松开时:
    【key.flag.key_state】在外部中断函数中赋值为【KEY_STATE_PRESS】,【key.time_continus】清零
    退出外部中断函数后,在定时器中断内,switch判断会进入【KEY_STATE_PRESS】
    【key.time_continus】从零开始计数,每1ms加一

    二、【key.time_continus】超时判断(长按 / 短按)

    if(key.time_continus > KEY_TIME_CONTINUS){
    if(key.event_current_type != EVENT_NONE_CLICK){
    if(key.press_cnt > 1){key.press_cnt--;}
    key.flag.once_event = 1;
    }
    else{
    key.flag.press_time = 1; //【0:短按/1:长按】识别此次为长按
    key.flag.key_state = KEY_STATE_IDLE; //主动结束按下动作,进入无动作状态,保证在长按的按下过程中就识别出长按事件

    key.event_current_type = EVENT_LONG_CLICK; //分配当前按键事件类型
    key.flag.once_event = 1; //产生按键事件
    key.press_cnt = 1; //连击次数置1
    key.time_idle = KEY_TIME_OUT; //按键空闲时间超时
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    当【key.time_continus】 >【 KEY_TIME_CONTINUS】时(这里设定为500ms见头文件定义,时长可自由定义),判断为长按

    进入长按判断后先判断当前按键是否有短按事件(单击或连击),如果有,产生事件标志,给到【void KEY_Scan(void)】进行处理,在下一个中断内再进行长按处理(该判断是为了解决短按后快速接一个长按,只能识别出长按事件的BUG)

    长按处理会分配当前事件类型,主动进入无动作状态(不进入按键松开判断)

    【key.flag.press_time】是长短按标志,用来区分长按和短按(短按也包括连击),长按会屏蔽短按
    【key.flag.once_event = 1】产生按键事件,则该说明可以进行按键事件类型的判断了。

    三、按下时的单次判断

    //按下时进行一次判断
    if(key.flag.check){
    key.flag.check = 0;
    if(!key.flag.press_time){ //判断上一次按键类型
    //判断上一次按键动作空闲时间
    if(key.time_idle < KEY_TIME_IDLE){ //若上一次按键动作后的空闲时间在规定时间内,说明发生了连击事件,一次完整的按键事件还未结束
    key.press_cnt++;
    }else{key.press_cnt = 1;}
    }
    key.flag.press_time = 0; //【0:短按/1:长按】此次为短按
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    该判断只会在按下时执行一次,而不是1ms执行一次
    【key.flag.press_time】在按下时一定会识别为短按,只有进入长按时才会置1
    在按下时对上一次的按键动作空闲时间进行判断【key.time_idle < KEY_TIME_IDLE】,可识别是否为连击,若是连击则【key.press_cnt++】,记录连击次数;否则置1

    ▼对于长按的判断,是为了屏蔽长按后快速单击的错误识别,若没有该判断,长按按后快速单击,会将长按和单击识别为一个事件,即2连击

    if(!key.flag.press_time){ }
    1
    2.5.2、按键松开处理
    松开时分为三部分处理
    一、【key.time_idle】计时,在2000ms超时前进行自加

    if(key.time_idle < KEY_TIME_OUT){key.time_idle++;}
    1
    当按键松开时:
    【key.flag.key_state】在外部中断函数中赋值为【KEY_STATE_RELEASE】,【key.time_idle】清零
    退出外部中断函数后,在定时器中断内,switch判断会进入【KEY_STATE_RELEASE】
    【key.time_idle】从零开始计数,每1ms加一

    二、松开时的单次判断

    //松开时进行一次判断
    if(key.flag.check){
    key.flag.check = 0;

    //判断此次按键动作
    if(!key.flag.press_time){ //长按会屏蔽短按
    if(key.press_cnt > 1){ //连击事件
    key.event_current_type = EVENT_DOUBLE_CLICK; //分配按键事件类型
    }
    else{ //单击事件
    key.event_current_type = EVENT_SHORT_CLICK; //分配按键事件类型
    key.press_cnt = 1; //连击次数置1
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    当按键松开时执行一次,对上一次按键按下的动作进行判断,注意的是这里是通过【key.flag.press_time】排除了长按的松手事件判断,并且根据【key.press_cnt】分配按键事件类型

    三、此次松开时间判断

    //按键松开后判断此次按键动作后的空闲时间,从而判断此次动作是否结束
    if(key.time_idle > KEY_TIME_IDLE){ //空闲时间超时,认为一次完整的按键事件结束
    if(!key.flag.press_time){ //松开前是短按标志,则产生按键事件,这里是为了屏蔽长按后的松手动作
    key.flag.once_event = 1; //产生按键事件
    key.flag.key_state = KEY_STATE_IDLE;//进入无动作状态
    }
    }
    1
    2
    3
    4
    5
    6
    7
    在第二步分配好按键事件类型后,还需要判断此次松开后的时间,用来判断是否为完整的一次事件
    当【key.time_idle > KEY_TIME_IDLE】时,识别为完整的事件结束,产生按键事件标志,进入无动作状态,通知主程序进行按键类型判断

    if(!key.flag.press_time){ }
    1
    ▲因为长按的松手也会产生中断,这是用来屏蔽长按的松手动作

    2.6、按键事件类型识别
    对于事件类型的识别是靠三个变量
    【key.flag.once_event】一次完整按键事件,置1时可以进行事件识别,必须手动清零
    【key.event_current_type】当前事件类型
    【key.press_cnt】连击次数,在连击事件时有效

    [key_process.c]
    /*******************************************************************************
    * @brief 使用模板,按键事件判断例子,在主函数循环调用该函数 或 通过定时器定时查询
    将printf替换成需要处理的函数即可使用单击、连击、长按等按键功能
    * @param 无
    * @retval 无
    ******************************************************************************/
    void KEY_Scan(void)
    {
    if(key.flag.once_event){
    key.flag.once_event = 0;

    switch(key.event_current_type){
    case EVENT_SHORT_CLICK : printf("单击\r\n");break;
    case EVENT_DOUBLE_CLICK : printf("%d连击\r\n",key.press_cnt);break;
    case EVENT_LONG_CLICK : printf("长按\r\n");break;
    default: printf("none\r\n");break;
    }
    //事件处理完需更新前态和现态
    key.event_previous_type = key.event_current_type;
    key.event_current_type = EVENT_NONE_CLICK;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    该函数可以在主循环调用或者在定时器中断中调用,这里是选择放在定时器中断内处理,用户处理内容直接替换到【printf函数】的位置就行了,或者通过外部标志进行链接

    ▼对于按键处理结构体的初始化值如下

    [key_process.c]
    /*******************************************************************************
    * @brief 按键配置初始化函数,在主函数调用
    * @param 无
    * @retval 无
    ******************************************************************************/
    void KEY_Config(void)
    {

    NVIC_EXTI_GPIO_Config();
    NVIC_TIM2_Config();

    EXTI_GPIO_Config();
    TIMx_Config(TIM2);

    //初始化
    key.flag.check = 0;
    key.flag.key_state = KEY_STATE_IDLE;
    key.flag.once_event = 0;
    key.flag.press_time = 0;

    key.event_current_type = EVENT_NONE_CLICK;
    key.event_previous_type = EVENT_NONE_CLICK;
    key.press_cnt = 1;

    key.time_continus = 0;
    key.time_idle = KEY_TIME_OUT;

    //使能定时器
    TIM2->CR1 |= 0x0001;

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    3、测试结果

    测试包括普通的按键事件,即每个按键事件的空闲时间间隔均超过【KEY_TIME_IDLE】
    特殊测试条件:
    1、短按(单击或连击)松开后在【KEY_TIME_IDLE】时间内接一个长按,可识别出短按的事件和长按事件
    2、长按松开后在【KEY_TIME_IDLE】内接短按(单击或连击),可识别出长按事件和短按事件

    4、总结
    除去基础的IO配置和定时器配置函数外,主要的函数有:
    【void EXTI9_5_IRQHandler(void)】外部中断处理函数
    【void TIM2_IRQHandler(void)】定时器中断处理函数
    【void KEY_Process(void)】按键处理函数
    【void KEY_Scan(void)】按键事件判断函数
    【void KEY_Config(void)】初始化函数

    该方案使用的是基于外部中断的上下沿中断进行判断的,但考虑到有些单片机只能配置成上升沿或者下降沿检测,无法对上下沿都进行判断,移值上有一定的缺陷,这里给出一些修改思路,若是只使用上升沿或者下降沿触发,可在外部中断触发后(即按键按下触发),通过定时器循环读取IO口状态来判断按键是否松开,按键的松开处理按照【2.4章节】进行处理就行,只不过该处理需要放进定时器中断内的【void KEY_Process(void)】函数之前,其他的无需修改

    ▼头文件中的定义可以改变按键识别的灵敏度

    //定时器为1ms定时
    #define KEY_TIME_IDLE 400 //按键动作空闲时间
    #define KEY_TIME_CONTINUS 500 //按键动作持续时间
    #define KEY_TIME_OUT 2000 //按键超时

    Talk is cheap, show me the code
  • 相关阅读:
    换个角度看Salesforce之基础配置学习笔记(二)
    换个角度看Salesforce之基础配置学习笔记(一)
    C# LINQ学习笔记
    Oracle使用总结
    UML图及Visio 2010使用总结
    常见的DOS命令
    ansible笔记
    jsoncpp1.9.4源码解析
    fabric链码
    fabric数据结构
  • 原文地址:https://www.cnblogs.com/birdBull/p/15771371.html
Copyright © 2020-2023  润新知