基于STM32F103 实现按键状态机

开发板

正点原子STM32F103ZET6战舰

开发环境

stm32cubeMX Clion

前言

在单片机使用按键时,为了消除按键的抖动,往往采用delay的方法消除,但是这种方式使CPU处于空等的状态,不能进行其他任务,直到结束delay延时函数,这种阻塞的方式不利于多任务的情形。因此采用状态机就能实现非阻塞,从而让CUP更高效执行任务。

按键消抖

按键硬件原理图

本次用到KEY0 KEY1 KEY2
在这里插入图片描述
由原理图可知当我们按键按下呈现低电平,理想波形如下:
在这里插入图片描述
实际波形:
在这里插入图片描述
因此我们要消除按下和抬起时抖动。这个抖动我们软件上延迟个5-10ms再次确认电平状态,如果任然是低电平则认为按键按下,抬起同上。

软件延时实现思路

首先我们摒弃直接delay_ms(10)这种方法,我们既要达到延时10ms左右时间又要不阻塞CPU,可采取以下方法:
可以开启一个定时器,10为周期,在回调函数里面内进行判断。
我这里采用获取滴答定时器中断的tick计数,当两次获取tick差值为10时也就实现了以上效果,本实验还将实现长按检测,当差值达到设定值时,即可判断当前处于长按状态。因此采用系统滴答定时器中断计数这种方式会更加方便。具体实现可查看代码。

实验目的

实现采用状态机方式检测各个按键状态并实现对应功能(支持长按)
KEY0按下 红灯亮,松开红灯灭,长按红灯一直亮直至松开
KEY1按下 绿灯亮,松开绿灯灭,长按绿灯一直亮直至松开
KEY0按下 蜂鸣器响,蜂鸣器关,长按蜂鸣器一直响亮直至松开

代码

按键状态

// 按键状态机
typedef enum _KeyState
{
    KEY_NULL = 0,    //表示无按键按下
    KEY_DOWN_JUDDER, //按下抖动
    KEY_DOWN,        //按下
    KEY_LONG,        //长按
    KEY_UP_JUDDER,   //抬起抖动
    KEY_UP,          //抬起
}KeyState;

按键信息

本实验采用三个按键

// 按键ID
typedef enum _KeyId
{
    KEY_ID_0 = 0,
    KEY_ID_1 ,
    KEY_ID_2 ,
    KEY_ID_MAX
}KeyId;
// 按键结构体
typedef struct _keyInfo{

    KeyState keyState ;     //按键状态机
    uint8_t preKeyState;    //上一次按键的状态
    int32_t preTime;        //上次时间
    int32_t curTime;        //当前时间
}KeyInfo;

按键相关定义

可自行修改

#define  KEYPRESS    1          //按下电平
#define  KEYRELEASE  0          //抬起电平

#define KEYPRESSTIME  10       //消除抖动10ms
#define KEYLONGPRESSTIME  1000 //短按超过1秒算长按

按键底层配置及状态获取

bsp_key.c

/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 21:13
* @version: 2.0
* @description: 
********************************************************************************/




#include "bsp_key.h"

KeyInfo keyInfo[KEY_ID_MAX];
KeyValueBuffer keyValueBuffer[KEY_ID_MAX];

/**
 * 获取按键电平值
 * @param keyID
 * @return
 */
GPIO_PinState GetKeyValue(uint8_t keyID)
{
    GPIO_PinState value = GPIO_PIN_RESET;
    switch (keyID) {
        case KEY_ID_0:
            value = HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin);
            break;

        case KEY_ID_1:
            value = HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin);
            break;

        case KEY_ID_2:
            value = HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin);
            break;

        default:

            break;
    }
    return value;
}


/**
 * 写入一次状态
 * @param keypad_status
 */
void KeyValueWrite(uint8_t  id,KeyState keypad_status)
{
    keyValueBuffer[id].valBuffer[keyValueBuffer[id].write] = keypad_status;
    if (++keyValueBuffer[id].write >= KEY_ID_MAX)
    {
        keyValueBuffer[id].write = 0;
    }
}

/**
 * 读取一次状态
 * @return
 */
KeyState KeyValueRead(uint8_t  id)
{
    KeyState key_event;
    if (keyValueBuffer[id].read == keyValueBuffer[id].write)
    {
        return KEY_NULL;
    }
    else
    {
        key_event = keyValueBuffer[id].valBuffer[keyValueBuffer[id].read];
        if (++keyValueBuffer[id].read >= KEY_ID_MAX)
        {
            keyValueBuffer[id].read = 0;
        }
        return key_event;
    }
}

/**
 * 判断案件是否被按下
 * @param keyId
 * @return
 */
uint8_t GetKeyIOState(KeyId keyId)
{
    GPIO_PinState key_value = GetKeyValue(keyId);

    if (key_value == GPIO_PIN_RESET){

        return KEYPRESS;                // 按键被按下
    }else{

        return KEYRELEASE;              // 按键松开
    }
}

/**
 *
 */
void KeyInfoInit()
{
    for (int i = 0; i < KEY_ID_MAX; i++) {
        keyInfo[i].keyState = KEY_NULL;
        keyInfo[i].preKeyState = KEY_NULL;
        keyInfo[i].preTime = 0;
        keyInfo[i].curTime = 0;
    }
}

bsp_key.h

/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 21:13
* @version: 2.0
* @description: 
********************************************************************************/




#ifndef KEY_STATE_BSP_KEY_H
#define KEY_STATE_BSP_KEY_H

#include "gpio.h"
#include "Retarget.h"

#define  KEYPRESS    1          //按下电平
#define  KEYRELEASE  0          //抬起电平

#define KEYPRESSTIME  10       //消除抖动10ms
#define KEYLONGPRESSTIME  1000 //短按超过1秒算长按

// 按键ID
typedef enum _KeyId
{
    KEY_ID_0 = 0,
    KEY_ID_1 ,
    KEY_ID_2 ,
    KEY_ID_MAX
}KeyId;

// 按键状态机
typedef enum _KeyState
{
    KEY_NULL = 0,    //表示无按键按下
    KEY_DOWN_JUDDER, //按下抖动
    KEY_DOWN,        //按下
    KEY_LONG,        //长按
    KEY_UP_JUDDER,   //抬起抖动
    KEY_UP,          //抬起
}KeyState;

//buffer结构体
typedef struct _KeyValueBuffer
{
    GPIO_PinState valBuffer[KEY_ID_MAX];    //键值缓冲区
    uint8_t read;					        //缓冲区读指针1
    uint8_t write;                          //缓冲区写指针1
} KeyValueBuffer;

// 按键结构体
typedef struct _keyInfo{

    KeyState keyState ;     //按键状态机
    uint8_t preKeyState;    //上一次按键的状态
    int32_t preTime;        //上次时间
    int32_t curTime;        //当前时间
}KeyInfo;

extern KeyInfo keyInfo[KEY_ID_MAX];

void KeyInfoInit(void);
uint8_t GetKeyIOState(KeyId keyId);
void KeyValueWrite(uint8_t  id,KeyState keypad_status);
KeyState KeyValueRead(uint8_t  id);

#endif //KEY_STATE_BSP_KEY_H

bsp_led.c

/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 21:13
* @version: 2.0
* @description: 
********************************************************************************/




#include "bsp_led.h"

/**
 * 设置LED0状态
 * @param flag
 */
void SetLed0State(uint8_t flag)
{
    if(flag){
        HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);
    }else{
        HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);
    }
}

/**
 * 设置LED1状态
 * @param flag
 */
void SetLed1State(uint8_t flag)
{
    if(flag){
        HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
    }else{
        HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
    }
}

bsp_beep.c

/********************************************************************************
* @author: majj
* @email: 11289856135@qq.com
* @date: 2023/9/8 21:14
* @version: 2.0
* @description: 
********************************************************************************/




#include "bsp_beep.h"

void SetBeepState(uint8_t flag)
{
    if(flag){
        HAL_GPIO_WritePin(BEEP_GPIO_Port,BEEP_Pin,GPIO_PIN_SET);
    } else{
        HAL_GPIO_WritePin(BEEP_GPIO_Port,BEEP_Pin,GPIO_PIN_RESET);
    }
}

按键状态机轮询任务

/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 22:36
* @version: 2.0
* @description: 
********************************************************************************/

#include "key_status_controller.h"

/**
 * 执行按键任务
 * @param keyID
 * @param keyState
 */
void OperateKeyTask(KeyId keyID,KeyState keyState)
{
    switch (keyID) {
        case KEY_ID_0:
            if(keyState == KEY_DOWN){
                SetLed0State(1);
            }else if(keyState == KEY_LONG){
                printf("KEY0 --长按\r\n");
                SetLed0State(1);
            } else if(keyState ==KEY_NULL){
                SetLed0State(0);
            }
            break;
        case KEY_ID_1:
            if(keyState == KEY_DOWN){
                SetLed1State(1);
            }else if(keyState == KEY_LONG){
                SetLed1State(1);
            } else if(keyState ==KEY_NULL){

                SetLed1State(0);
            }
            break;
        case KEY_ID_2:
            if(keyState == KEY_DOWN){
                SetBeepState(1);
            }else if(keyState == KEY_LONG){
                printf("KEY2 --长按\r\n");
                SetBeepState(1);
            } else if(keyState ==KEY_NULL ){
                SetBeepState(0);
            }
            break;
        default:
            break;
    }

}

/**
 * 检测按键
 * @param keyID 
 */
void DetectKeyState(KeyId keyID)
{
    //读取按键状态信息
    KeyState keyState = keyInfo[keyID].preKeyState;
    //读取按键电平变化
    uint8_t keyValue = GetKeyIOState(keyID);

    switch (keyState) {
        case KEY_NULL:
            if(keyValue == KEYPRESS){
                //检测按键按下
                keyInfo[keyID].keyState = KEY_DOWN_JUDDER;
                keyInfo[keyID].preTime =  keyInfo[keyID].curTime;
            } else{

                keyInfo[keyID].curTime =  0;
                keyInfo[keyID].preTime =  0;
            }

            break;
        case KEY_DOWN_JUDDER:
            if(keyValue == KEYPRESS){
                keyInfo[keyID].curTime = HAL_GetTick();

                if((keyInfo[keyID].curTime -  keyInfo[keyID].preTime) > KEYPRESSTIME){
                    //确认按下
                    keyInfo[keyID].keyState = KEY_DOWN;
                    keyInfo[keyID].preTime =  keyInfo[keyID].curTime;
                }
            } else{
                keyInfo[keyID].keyState = KEY_NULL;
            }

            break;

        case KEY_DOWN:
            keyInfo[keyID].curTime = HAL_GetTick();
            if((keyInfo[keyID].curTime -  keyInfo[keyID].preTime) > KEYLONGPRESSTIME){
                //确认按下
                keyInfo[keyID].keyState = KEY_LONG;
                keyInfo[keyID].preTime =  keyInfo[keyID].curTime;

            }else{
                //短按
                if(keyValue == KEYRELEASE){
                    keyInfo[keyID].keyState = KEY_UP_JUDDER;
                    keyInfo[keyID].preTime = keyInfo[keyID].curTime;
                }
            }

            break;

        case KEY_LONG:
            keyInfo[keyID].curTime = HAL_GetTick();
            if(keyValue == KEYRELEASE){
                //抬起抖动
                keyInfo[keyID].keyState = KEY_UP_JUDDER;
                keyInfo[keyID].curTime = HAL_GetTick();
                keyInfo[keyID].preTime =  keyInfo[keyID].curTime;
            }

            break;

        case KEY_UP_JUDDER:
            if(keyValue == KEYRELEASE){
                keyInfo[keyID].curTime = HAL_GetTick();
                if((keyInfo[keyID].curTime -  keyInfo[keyID].preTime) > KEYPRESSTIME){
                    //确认抬起
                    keyInfo[keyID].keyState = KEY_UP;
                    KeyValueWrite(keyID,keyInfo[keyID].keyState);
                }
            }

            break;

        case KEY_UP:

            keyInfo[keyID].keyState = KEY_NULL;
            KeyValueWrite(keyID,keyInfo[keyID].keyState);

            break;

        default:

            break;

    }
    //执行动作任务
    OperateKeyTask(keyID,keyState);
    //记录本次状态
    keyInfo[keyID].preKeyState = keyInfo[keyID].keyState;

}

/**
 * 扫描按键
 */
void KeyScanTask()
{
    for(int i = 0; i < KEY_ID_MAX; i++){
        DetectKeyState((KeyId)i);
    }
}

总结

以上以实现了一个简单粗略的方式的按键状态机,有很多地方相对不足,仅供参考。本例程本人采用stm32cube+clion的方式,无法在keil5上直接跑,因此不上传整个文件了。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用HAL库编写STM32F103C8按键的代码,其中PA0和PA1被用作按键输入: ```c #include "stm32f1xx_hal.h" GPIO_InitTypeDef GPIO_InitStruct; void GPIO_Init(void); void EXTI_Init(void); void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); int main(void) { HAL_Init(); GPIO_Init(); EXTI_Init(); while (1) { // do nothing } } void GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } void EXTI_Init(void) { __HAL_RCC_AFIO_CLK_ENABLE(); HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); HAL_NVIC_EnableIRQ(EXTI1_IRQn); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_0) { // PA0 button pressed // do something } else if (GPIO_Pin == GPIO_PIN_1) { // PA1 button pressed // do something } } void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } void EXTI1_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1); } ``` 在这个例子中,我们使用了HAL库中的GPIO和EXTI模块来初始化GPIOA的PA0和PA1引脚,并设置为输入模式。我们还初始化了外部中断EXTI,并设置了中断优先级,最后使用HAL_NVIC_EnableIRQ()函数启用中断。在按键按下时,HAL_GPIO_EXTI_Callback()函数将被调用,我们可以在其中进行相应的操作。我们还实现了EXTI0_IRQHandler()和EXTI1_IRQHandler()函数来处理中断。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值