eefas 发表于 2023-5-29 22:00

STM32状态机思想实现单击、双击、长按 源程序



#include "stm32f10x.h"
#include "bitband_cm3.h"
#include "systick.h"
#define N_key    0             //无键
#define S_key    1             //单键
#define D_key    2             //双键
#define L_key    3             //长键
#define KEY_AN (GPIOA->IDR & 1<<0)
#define BEEF PCout(3)

/**********************************************************************
*函数名:delay_us
*功能:延迟1us
*参数:us最大2^24/9=1864135us
*返回:无
*备注:无
**********************************************************************/
void delay_us(u16 us)
{
      SysTick->LOAD = us * 9;      //装载计数值
      SysTick->VAL = 0;                //清空当前值
      SysTick->CTRL |= 1;      //使能计数器
      while(!(SysTick->CTRL & (1 << 16)));//等待计数结束
      SysTick->CTRL &=~ 1;//关闭计数
}

//LED初始化
void LED_Init(void)
{
#if 0
      RCC->APB2ENR |= 3<<3;//开启PB/PC口时钟
      GPIOB->CRL &=~(0XF<<4*1);//清PB1
      GPIOB->CRL |=(0X3<<4*1);//通用输出 50M
      GPIOC->CRL &=~(0XF<<4*5);//清PC5
      GPIOC->CRL |=(0X3<<4*5);//通用输出 50M
      GPIOB->ODR |=(1<<1);//默认给高电平,关灯,
      GPIOC->ODR |=(5<<1);
//      GPIOB->ODR &=~(1<<1);//点灯
//      GPIOC->ODR &=~(5<<1);
      #else
      GPIO_InitTypeDef GPIO_InitStruct;
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOB,ENABLE);//开启PB/PC口时钟.
      GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
      GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//通用推挽
      GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//50M
      GPIO_Init(GPIOB, &GPIO_InitStruct);//PB1
      GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
      GPIO_Init(GPIOC, &GPIO_InitStruct);//PC5
      GPIO_SetBits(GPIOB, GPIO_Pin_1);//默认给高电平,关灯,不能少了这步,因为输出数据寄存器默认值给低电平
      GPIO_SetBits(GPIOC, GPIO_Pin_5);
//      GPIO_ResetBits(GPIOB, GPIO_Pin_1);//开灯
//      GPIO_ResetBits(GPIOC, GPIO_Pin_5);

//      PBout(1)=0;
//      PCout(5)=0;
      #endif
}
//按键初始化
void KEY_Iint(void)
{
      RCC->APB2ENR |= 1<<2;//开启PA口时钟
}
//蜂鸣器初始化
void BEEF_Iint(void)
{
      RCC->APB2ENR |= 1<<4;//开启PC口时钟
      GPIOC->CRL &=~(0XF<<4*3);//清PC3
      GPIOC->CRL |=(0X3<<4*3);//通用输出 50M
}


/*
      驱动层
      1.完成按键的消抖,松手检测
      2.把过程细分为一个个状态
      3.实现长按与单击功能

      按键初始态
      按键确认态
      按键计时态
      等待按键释放态
*/
unsigned char key_driver()
{
    static u8 key_state = 0, key_time = 0;
    u8 key_return = N_key;

    switch (key_state)
    {
      case 0:                                    // 按键初始态
      if (!KEY_AN) key_state = 1;         // 键被按下,状态转换到按键消抖和确认状态
      break;

      case 1:                                                            // 按键消抖与确认态
      if (!KEY_AN)
      {
             key_time = 0;                  
             key_state = 2;                                                         // 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态,但返回的还是无键事件
      }
      else
             key_state = 0;                                                         // 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。
      break;

      case 2:                                                                                                                         // 按下键时间的计时状态
      if(KEY_AN)
      {
             key_return = S_key;                        // 此时按键释放,说明是产生一次短操作,回送S_key
             key_state = 0;                                                         // 转换到按键初始态
      }
      else if (++key_time >= 100)                     // 继续按下,计时加10ms(10ms为本函数循环执行间隔)
      {
             key_return = L_key;                        // 按下时间>1000ms,此按键为长按操作,返回长键事件
             key_state = 3;                                                         // 转换到等待按键释放状态
      }
      break;

      case 3:                                                                        // 等待按键释放状态,此状态只返回无按键事件
      if (KEY_AN) key_state = 0;                           // 按键已释放,转换到按键初始态
      break;
    }
    return key_return;
}

/*
      业务逻辑层
      1.单击、双击、长按的分配
*/
unsigned char key_read()
{
    static u8 key_m = 0, key_time_1 = 0;
    u8 key_return = N_key,key_temp;

    key_temp = key_driver();

    switch(key_m)
    {
      case 0:
            if (key_temp == S_key )
            {
               key_time_1 = 0;               // 第1次单击,不返回,到下个状态判断后面是否出现双击
               key_m = 1;
            }
            else
               key_return = key_temp;      // 对于无键、长键,返回原事件
            break;

      case 1:
            if (key_temp == S_key)             // 又一次单击(间隔肯定<500ms)
            {
               key_return = D_key;         // 返回双击键事件,回初始状态
               key_m = 0;
            }
            else                              
            {                                  // 这里500ms内肯定读到的都是无键事件,因为长键>1000ms,在1s前低层返回的都是无键
               if(++key_time_1 >= 30)
               {
                      key_return = S_key;      // 500ms内没有再次出现单键事件,返回上一次的单键事件
                      key_m = 0;               // 返回初始状态
               }
             }
             break;
    }
    return key_return;
}

/*
      单击:300ms~1000ms之间
      双击:300ms内
      长按:超过1s

      单击:控制LED1
      双击:控制LED2
      长按:控制蜂鸣器
*/
int main(void)
{
      LED_Init();
      KEY_Iint();
      BEEF_Iint();
      while(1)
      {
                switch(key_read())
                {
                        case N_key:
                              delay_ms(10);
                        break;
                        case S_key:
                              PBout(1)=!PBout(1);
                        break;
                        case D_key:
                              PCout(5)=!PCout(5);
                              break;
                        case L_key:
                              BEEF = !BEEF;
                              break;
                }
      }
      return 0;
}

呐咯密密 发表于 2023-5-30 16:46

非常好的按键实现方式,方便灵活

呐咯密密 发表于 2023-5-30 16:46

非常好的按键实现方式,方便灵活

dspmana 发表于 2023-6-7 13:47

使用定时器TIM来判断按键状态,利用定时器计数器的值来判断按键的状态(按下或者弹起),从而实现单击、双击和长按功能。具体实现方法如下:

LLGTR 发表于 2023-6-7 18:29

我觉得用RTOS实现会更容易一些。

天天向善 发表于 2023-6-7 18:30

要是加入中断方式会更好一些。

芯路例程 发表于 2023-6-7 18:31

不知道加到别的程序里面效果怎么样。

MessageRing 发表于 2023-6-7 22:46

很不错的实现方法

bartonalfred 发表于 2023-6-8 16:51

检测TIMx。stm32软件中用户长按闪两次,短按闪一次是需要检测TIMx的,将总体的实验值进行计算即可。

ingramward 发表于 2023-6-13 21:06

将按键连接到外部中断引脚上,在中断服务程序中处理按键事件。例如,当检测到按键按下时,启动定时器并等待一段时间,如果在这段时间内再次检测到按键按下,则认为是双击;如果在一定的时间内按键一直处于按下状态,则认为是长按。

Stahan 发表于 2023-6-13 23:13

多进几次中断,多几个变量就行了

kkzz 发表于 2023-6-14 10:55

只要在单击和双击之间加一个判断时间

hudi008 发表于 2023-6-14 14:05

主程序或者中断中循环执行               

modesty3jonah 发表于 2023-6-14 15:38

在主函数中轮询按键状态,并根据不同的按键事件执行相应的操作。例如,单击时执行一个函数,双击时执行另一个函数,长按时执行第三个函数。

biechedan 发表于 2023-6-14 18:27

单击与长按的区别在于按下后弹起时间的长短,如果按键一直按着,且大于一个时间值,判断为长按; 否则为非长按,那么就要继续判断是单击还是双击

MessageRing 发表于 2023-6-14 22:55

多加一个变量判断按下时常就行了

mollylawrence 发表于 2023-6-16 22:17

单击和双击时序图非常的相似,最大的区别就是按键按下后低电平的持续时间

Undshing 发表于 2023-6-16 22:21

加个判断定时器触发多少次就行了

beacherblack 发表于 2023-6-19 12:19

STM32如何实现连续按键            

lzmm 发表于 2023-6-21 13:43

在实现按键功能时,需要考虑按键消抖、误触发等问题
页: [1]
查看完整版本: STM32状态机思想实现单击、双击、长按 源程序