状态机应用之按键状态机

一、碎碎念

  谁家好人代码写完半年之后来写笔记的啊,代码写得差,请温柔的骂,该程序支持单击,双击,长按和组合按键

二、运行环境

2.1 硬件环境

        如图,四个独立按键直接连接到MCU的IO口

2.2 软件环境

        使用MRS(MounRiver Studio)创建的带RTT的工程

三、流程图

四、源码

volatile uint8_t key_state = KEY_STATE_IDLE;//按键状态机表示状态的变量

void thread_keyboard_entry(void* parameter)
{
    thread_keyboard_hw_init();//初始化硬件相关的部分

    rt_kprintf("create mb: key\r\n");
    mailbox_key = rt_mb_create ("mail_key", 30, RT_IPC_FLAG_PRIO);//注册邮箱,这一段程序获取的键值会通过邮箱发送出去
    if(mailbox_key == RT_NULL)
    {
        rt_kprintf("init mailbox key failed.\n");
    }

    volatile uint8_t key_sample,last_key_sample,last_key_value,key_single_value;
/*
*变量说明
*key_sample:每一次采样IO口获取到的当前键值
*last_key_sample:上一次采样IO口得到的键值,每一次程序结束的时候将会把key_sample更新到   
*                   last_key_sample
*last_key_value:上一次有效按键动作,如果key_sample识别为非抖动的按键动作,会在部分状态更新 
*                last_key_value
key_single_value:单个按键有效动作的键值,如果key_sample识别为非抖动的按键动作并且只有一个按 
*                 键动作,会把key_sample更新到key_single_value  
*/


    volatile rt_tick_t key_press_time,long_press_last_time,release_time;
/*
*变量说明
*key_press_time:单个按键按下的时间,用来识别长按
*
*long_press_last_time:上一次发送长按键值的时间,发完之后更新该变量,长按键值发送时间间隔可调   
*               
*release_time:按键释放的时间,用来识别有没有双击
*/

    while(1)
    {
        key_sample = ((uint16_t)(GPIOB->INDR&0x0000f000)) >> 12;    //对IO的输入数据寄存器采样,获取当前时刻键值
        if(key_sample == last_key_sample)    //如果这次采样和上次采样的值相同(两次间隔10ms),则认为是有效的按键动作而非抖动,则继续处理
        {
            switch(key_state)  //判断状态机当前状态,然后执行对应操作
            {
            case KEY_STATE_IDLE:
                if((key_sample != NO_KEY_PRESS) && (last_key_value == NO_KEY_PRESS)) key_state = KEY_STATE_PRESS;
                else key_state = KEY_STATE_IDLE;
                last_key_value = key_sample;
                break;
/*
*IDLE状态:如果当前的按键动作不是所有键都没有被按下的状态,
*并且上一次的按键动作是所有键都没有被按下的状态,则更新状态
*为有键按下状态,否则维持IDLE状态,并更新上一次有效的按键动作
*/
            case KEY_STATE_PRESS:
                if((key_sample == KEY1_PRESS) || (key_sample == KEY2_PRESS) || (key_sample == KEY3_PRESS) || (key_sample == KEY4_PRESS))
                    {
                        key_press_time = rt_tick_get();
                        key_single_value = key_sample;
                        key_state = KEY_STATE_SINGLE_PRESS;
                    }
                else
                    {
                        key_state = KEY_STATE_IDLE;
                        rt_mb_send(mailbox_key,key_sample|0x1000);
//                        rt_kprintf("key combin value: %d\r\n",key_sample);
                    }

/*
*有键按下状态:如果只有一个按键被按下(这部分处理错了),
*则记录当前时间作为按键按下的时间(实际上至少有10ms的误差),
*记录当前采样到的IO口的值作为键值,然后切换到单个按键按下状态
*
*否则认为是多键按下,不再做其他处理,切换状态为IDLE状态,并发送组合键的键值
*/

                break;
            case KEY_STATE_SINGLE_PRESS:
                if(key_sample == NO_KEY_PRESS)
                {
                    release_time = rt_tick_get();
                    key_state = KEY_STATE_SINGLE_RELEASE;
                }else
                {
                    if((rt_tick_get() - key_press_time) > 1000)
                    {
                        long_press_last_time = rt_tick_get();
                        key_state = KEY_STATE_LONG_PRESS;
                    }
                }
/*
*单个按键按下状态:程序运行到此状态时距离上一个状态至少过去了10个毫秒
*
*在当前状态会判断采样到的有效按键动作是不是没有键被按下的状态,
如果是则记录当前时间作为按键被释放的时间,然后切换到单个按键释放状态
*
*否则就一直判断当前时间是否距离按键被按下过去了1秒的时间,如果是就记录下当前
*时间作为上一次长按的时间,然后切换到长按状态
*/
                break;
            case KEY_STATE_LONG_PRESS:
                if(key_sample == NO_KEY_PRESS)
                {
                    key_state = KEY_STATE_IDLE;
                }
                else
                {
                    if((rt_tick_get() - long_press_last_time) > 200)
                    {
//                        rt_kprintf("key long press: %d\r\n",key_single_value);
                        rt_mb_send(mailbox_key,key_single_value|0x2000);
                        long_press_last_time = rt_tick_get();
                    }
                }
                break;
/*
*长按状态:如果当前有效的按键动作是没有按键被按下,
*说明长按已经结束了,切换状态回IDLE状态

否则计算当前时间与上一次处理长按的时间,
*大于阈值就发送一次键值并更新上一次处理长按的时间
*/
            case KEY_STATE_SINGLE_RELEASE:
                if(key_sample == NO_KEY_PRESS)
                {
                    if((rt_tick_get() - release_time) > 200)
                    {
//                        rt_kprintf("key single value: %d\r\n",key_single_value);
                        rt_mb_send(mailbox_key,key_single_value|0x3000);
                        key_state = KEY_STATE_IDLE;
                    }
                }else
                {
//                    rt_kprintf("key double value: %d\r\n",key_single_value);
                    rt_mb_send(mailbox_key,key_single_value|0x4000);
                    key_state = KEY_STATE_IDLE;
                }
/*
*单个按键释放状态:如果没有按键被按下,并且阈值时间内一直没有键被按下
*则认为是一次按键单击,发送单机键值并切换状态为IDLE状态
*
*否则就认为是一次双击(这里处理是有问题的)
*发送双击的键值并切换状态为IDLE
*/
                break;
            default:
                key_state = KEY_STATE_IDLE;
                break;
            }
        }
        last_key_sample = key_sample; //更新上一次按键采样值,用于下一次的比较
        rt_thread_mdelay(10);
    }

}

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个简单的示例代码,用于实现 STM32 的按键有限状态程序: ```c #include "stm32f4xx.h" #define KEY_DELAY 100000 typedef enum { KEY_STATE_IDLE = 0, KEY_STATE_PRESS, KEY_STATE_RELEASE, KEY_STATE_LONG_PRESS, KEY_STATE_NUM } key_state_t; typedef struct { GPIO_TypeDef* port; uint16_t pin; key_state_t state; uint32_t delay; } key_t; key_t keys[] = { {GPIOA, GPIO_Pin_0, KEY_STATE_IDLE, 0}, {GPIOA, GPIO_Pin_1, KEY_STATE_IDLE, 0}, {GPIOA, GPIO_Pin_2, KEY_STATE_IDLE, 0}, {GPIOA, GPIO_Pin_3, KEY_STATE_IDLE, 0} }; void init_key_gpio(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_Init(GPIOA, &GPIO_InitStructure); } void update_key_state(key_t* key) { GPIO_TypeDef* port = key->port; uint16_t pin = key->pin; GPIO_PinState state = GPIO_ReadInputDataBit(port, pin); switch (key->state) { case KEY_STATE_IDLE: if (state == GPIO_PIN_RESET) { key->state = KEY_STATE_PRESS; key->delay = 0; } break; case KEY_STATE_PRESS: if (state == GPIO_PIN_SET) { key->state = KEY_STATE_RELEASE; key->delay = 0; } else { key->delay++; if (key->delay > KEY_DELAY) { key->state = KEY_STATE_LONG_PRESS; } } break; case KEY_STATE_RELEASE: if (state == GPIO_PIN_RESET) { key->state = KEY_STATE_PRESS; key->delay = 0; } else { key->delay++; if (key->delay > KEY_DELAY) { key->state = KEY_STATE_IDLE; } } break; case KEY_STATE_LONG_PRESS: if (state == GPIO_PIN_SET) { key->state = KEY_STATE_RELEASE; key->delay = 0; } break; default: break; } } int main(void) { init_key_gpio(); while (1) { for (int i = 0; i < sizeof(keys)/sizeof(key_t); i++) { update_key_state(&keys[i]); switch (keys[i].state) { case KEY_STATE_PRESS: // do something when the key is pressed break; case KEY_STATE_LONG_PRESS: // do something when the key is pressed for a long time break; default: break; } } } } ``` 在这个示例代码中,我们定义了一个 `key_t` 结构体,用于存储每个按键状态和相关信息。在 `init_key_gpio` 函数中,我们初始化了按键对应的 GPIO 引脚,并将其配置为上拉输入模式。在 `update_key_state` 函数中,我们根据按键当前的状态和输入状态,更新按键状态。在 `main` 函数中,我们不断地遍历按键数组,调用 `update_key_state` 函数来更新按键状态。根据按键状态,我们执行相应的操作,例如触发一个事件或者修改某个变量的值。 需要注意的是,在实际使用中,我们还需要根据具体的硬件和应用需求,对代码进行一定的修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值