基于STM32F103 实现按键状态机

news2024/10/11 20:26:36

文章目录

  • 开发板
  • 开发环境
  • 前言
  • 按键消抖
    • 按键硬件原理图
    • 软件延时实现思路
  • 实验目的
  • 代码
    • 按键状态
    • 按键信息
    • 按键相关定义
    • 按键底层配置及状态获取
  • 总结

开发板

正点原子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上直接跑,因此不上传整个文件了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993168.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【随想】每日两题Day.3(实则一题)

题目&#xff1a;59.螺旋矩阵|| 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; …

Python进阶方法-Decorator装饰器

前言 在Python中&#xff0c;decorator&#xff08;装饰器&#xff09;是一种特殊的函数&#xff0c;主要用于修改或增强其他函数的功能。它可以在不修改原函数代码的情况下&#xff0c;通过在原函数的定义之前使用语法糖来对其进行修饰。 Decorator装饰器的作用 Decorator的…

端口信息收集

一、服务端口介绍 在渗透测试中对服务端口的收集非常重要&#xff0c;通过扫描服务开放的端口可判断对应开启的服务&#xff0c;通过所提供的这些服务的已知漏洞就可进行攻击。知名端口&#xff08;0-1023&#xff09;固定分配给某些服务的&#xff0c;动态端口&#xff08;10…

(高频面试1)Redis缓存穿透、缓存击穿、缓存雪崩

目录 一&#xff1a;缓存数据 1.1 应用场景 1.2&#xff1a;缓存数据出现的问题 1.2.1 缓存穿透 1.2.2 解决办法 1.2.3 缓存击穿 1.2.4 解决办法 1.2.5 缓存雪崩 1.2.6 解决办法 一&#xff1a;缓存数据 1.1 应用场景 数据库查询结果缓存是一种常见的缓存应用场景&a…

ansible 使用roles简单部署LAMP平台

目录 一、了解roles目录 二、基于构建LAMP平台创建roles目录 1、在192.168.115.148创建目录 2、书写php的测试页面 3、编写httpd角色的main.yml文件 4、编写mysql角色的main.yml文件 6、编写lamp的playbook 7、启动剧本 8、访问 一、了解roles目录 在Ansible中&#…

MultipartFile是什么

Multipart是一种file的类型 在我们进行文件上传时所发出的请求&#xff0c;我们页面对请求格式有明确的要求: 1.post提交表单方式 2.编码格式enctype必须是muitipart/form-data&#xff0c;这种格式适合传输数据量大的二进制数据文件 3.类型必须是file类 流程举例&#xf…

开源药店商城系统源码比较:哪个适合你的药品电商业务

在构建药品电商业务时&#xff0c;选择适合的药店商城系统源码是至关重要的决策之一。开源药店商城系统源码提供了快速入门的机会&#xff0c;但在选择之前&#xff0c;您需要仔细考虑您的需求、技术要求和可扩展性。本文将比较几个流行的开源药店商城系统源码&#xff0c;以帮…

追溯网络安全本源,原生安全范式框架v1.0外滩大会正式发布

9月8日&#xff0c;2023外滩大会网络安全分论坛在上海举行。论坛由蚂蚁集团和《信息安全研究》杂志社联合主办&#xff0c;以“开启原生安全范式&#xff0c;护航网络空间安全”为主题。会上蚂蚁集团与浙江大学网络空间安全学院重磅首发了一项引领性网络安全成果 “原生安全范式…

什么?你还不会打包给运维?!那快来看看

目录 一、首先是JAVA打包 我们只需要用maven打包即可&#xff0c;生成文件为xxxx.jar 二、Vue打包 而打包是运行 npm run build 只要把 dist 给运维就行了 PS&#xff1a;如果是线上运行&#xff0c;那你要注意 env这个文件&#xff01;&#xff01;&#xff01; ​编辑…

进程间通信(IPC)的方法:命名管道

使用管道时&#xff0c;一个进程的输出可成为另外一个进程的输入。 命名管道(Named pipe或FIFO)是一种类似于管道的特殊文件&#xff0c;但在文件系统上有一个名称&#xff0c;它允许以先进先出(FIFO, first in, first out)的方式存储有限数量的数据。它的使用类似于消息…

(STM32H5系列)STM32H573RIT6、STM32H573RIV6、STM32H573ZIT6嵌入式微控制器基于Cortex®-M33内核

一、应用 工业&#xff08;PLC、工业电机控制、泵和压缩机&#xff09; 智能家居&#xff08;空调、冰箱、冰柜、中央警报系统、洗衣机&#xff09; 个人电子产品&#xff08;键盘、智能手机、物联网标签、跟踪设备&#xff09; 智能城市&#xff08;工业通信、照明控制、数字…

2023秋冬系列丨追求本真的自然纯粹之美

2023年08月&#xff0c;上海&#xff0c;ZESH泽尚&#xff0c;中国轻奢皮具品牌宣布推出2023全新秋冬系列包袋&#xff0c;以“自然之道&#xff0c;纯粹之美”为主题重新定义东方美学。品牌建立之初就坚持贯彻东方美学设计与精湛制作工艺融合的理念。此次秋冬系列从中式禅宗学…

EagleSDR USB HAT FT600

给EagleSDR做了个USB 3.0的子卡&#xff0c;采用FT600方案&#xff0c;实物如下&#xff1a; 用FT600DataStreamerDemoApp测试&#xff0c;速度如下&#xff1a; 由于FT600是16bit的接口&#xff0c;如果用FT601的32bit接口&#xff0c;性能应该还会有大幅提升。 测试代码很简…

通过Idea或命令将本地项目上传至git

通过Idea或命令将本地项目上传至git 一、Git创建仓库 1、登录Gitee账号&#xff0c;点击新建 2、填写如下相关信息&#xff0c;点击创建 3、在此处可以复制项目链接 二、Idea配置和解绑git&#xff0c;提交项目 1、idea打开项目&#xff0c;操作如下 2、在弹框里选择…

【监控系统】Promethus整合Alertmanager监控告警邮件通知

【监控系统】Promethus整合Alertmanager监控告警邮件通知 Alertmanager是一种开源软件&#xff0c;用于管理和报警监视警报。它与Prometheus紧密集成&#xff0c;后者是一种流行的开源监视和警报系统。Alertmanager从多个源接收警报和通知&#xff0c;并根据一组配置规则来决定…

Linux中的用户和用户组

su和exit命令 su命令就是用于账户切换的系统命令&#xff0c;其来源英文单词:Switch User 语法: su [-] [用户名] - 符号是可选的&#xff0c;表示是否在切换用户后加载环境变量 &#xff08;建议带上&#xff09;参数:用户名&#xff0c;表示要切换的用户&#xff0c;用户名也…

怎么提高自己当众讲话的能力?

当众讲话是一项重要的沟通技能&#xff0c;它可以帮助你在各种场合中表达自己的观点、影响他人&#xff0c;并建立自信。虽然对很多人来说&#xff0c;当众讲话可能是一项挑战&#xff0c;但通过一些实践和技巧&#xff0c;你可以提高自己的当众讲话能力。下面是一些方法&#…

Java开发之Redis核心内容【面试篇 完结版】

文章目录 前言一、redis使用场景1. 知识分布2. 缓存穿透① 问题引入② 举例说明③ 解决方案④ 实战面试 3. 缓存击穿① 问题引入② 举例说明③ 解决方案④ 实战面试 4. 缓存雪崩① 问题引入② 举例说明③ 解决方案④ 实战面试 5. 缓存-双写一致性① 问题引入② 举例说明③ 解决…

第3章_瑞萨MCU零基础入门系列教程之开发环境搭建与体验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…