【星云 Orbit-F4 开发板】基于STM32F407按键状态机的设计与实现

embedded/2025/2/28 21:51:11/

【星云 Orbit-F4 开发板】基于STM32F407按键状态机设计与实现


引言

在嵌入式系统中,按键触发是最常见的用户交互方式之一。除了基本的单个按键触发,许多应用场景需要检测多种按键事件,如单击、双击、长按和按住等。本文将详细介绍如何使用STM32F407的GPIO引脚实现多功能按键触发功能,包括单击、双击、长按和按住事件的检测。


硬件准备

在开始编程之前,确保您已经准备好以下硬件:

  1. STM32F407开发板:板载STM32F407VGT6(Cortex-M4/168MHz)作为主控芯片。
  2. 按键模块:5个独立按键,用于测试不同按键事件。
  3. 连接线:用于连接按键和开发板。
  4. 限流电阻:若干个220Ω至330Ω的电阻,用于保护电路。

硬件连接

将按键连接到STM32F407的GPIO引脚:

  1. 按键1:连接到GPIOA的PA8引脚。
  2. 按键2:连接到GPIOB的PB15引脚。
  3. 按键3:连接到GPIOB的PB14引脚。
  4. 按键4:连接到GPIOB的PB13引脚。
  5. 按键5:连接到GPIOB的PB12引脚。

GPIO配置

在STM32F407中,GPIO引脚可以配置为多种模式,以适应不同的应用需求。以下是本教程中使用的GPIO配置:

  1. 按键引脚配置
    • 配置GPIOA的PA8、GPIOB的PB15、PB14、PB13和PB12引脚为上拉输入模式(GPIO_MODE_INPUT),并启用上拉电阻(GPIO_PULLUP)。
// 使能GPIOA和GPIOB时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();// 配置GPIO引脚为上拉输入模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pull = GPIO_PULLUP;// 配置PA8
GPIO_InitStruct.Pin = GPIO_PIN_8;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);// 配置PB15
GPIO_InitStruct.Pin = GPIO_PIN_15;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);// 配置PB14
GPIO_InitStruct.Pin = GPIO_PIN_14;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);// 配置PB13
GPIO_InitStruct.Pin = GPIO_PIN_13;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);// 配置PB12
GPIO_InitStruct.Pin = GPIO_PIN_12;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

按键扫描模块

为了实现多功能按键触发功能,我们需要对按键扫描模块进行详细设计。

bsp_key.h 文件

#ifndef __BSP_KEY_H
#define __BSP_KEY_H#include "stm32f4xx_hal.h"#define KEY_NUM_MAX         5     // 按键数量
#define SCAN_INTERVAL       5     // 扫描间隔(ms)typedef enum {KEY_EVT_NONE         = 0x00,  // 无事件KEY_EVT_PRESS        = 0x01,  // 按下事件KEY_EVT_RELEASE      = 0x02,  // 释放事件KEY_EVT_SINGLE_CLICK = 0x04,  // 单击事件KEY_EVT_DOUBLE_CLICK = 0x08,  // 双击事件KEY_EVT_LONG_PRESS   = 0x10,  // 长按事件KEY_EVT_HOLD         = 0x20   // 按住事件
} KeyEvent_Type;#define KEY_CODE(evt)     ((evt) & 0xFF)    // 获取按键代码
#define KEY_EVENT(evt)    ((evt) >> 8)      // 获取事件类型void KEY_GPIO_Init(void);         // 初始化GPIO
void Key_Scan(void);              // 扫描按键状态
uint16_t Key_GetEvent(void);      // 获取按键事件#endif /* __BSP_KEY_H */

bsp_key.c 文件

#include "bsp_key.h"// 按键映射表
typedef struct {GPIO_TypeDef *port;   // GPIO端口uint16_t pin;         // 引脚uint8_t id;           // 按键ID
} KeyGPIO_Def;static const KeyGPIO_Def KeyMap[KEY_NUM_MAX] = {{GPIOA, GPIO_PIN_8, 0},  // 按键1: PA8{GPIOB, GPIO_PIN_15, 1}, // 按键2: PB15{GPIOB, GPIO_PIN_14, 2}, // 按键3: PB14{GPIOB, GPIO_PIN_13, 3}, // 按键4: PB13{GPIOB, GPIO_PIN_12, 4}  // 按键5: PB12
};// 按键状态枚举
typedef enum {KEY_STATE_RELEASED,   // 释放状态KEY_STATE_DEBOUNCE,   // 去抖动状态KEY_STATE_PRESSED,    // 按下状态KEY_STATE_WAIT_RELEASE // 等待释放状态
} KeyState_Type;// 按键控制块
typedef struct {KeyState_Type state;      // 当前状态uint32_t pressStartTime; // 按下开始时间uint8_t clickCount;       // 点击次数
} Key_CB_Type;static Key_CB_Type Key_CB[KEY_NUM_MAX]; // 按键控制块数组
static uint16_t Key_EventReg = 0;      // 事件注册器
static uint32_t Key_TimeCount = 0;    // 时间计数器#define DEBOUNCE_TIME       (20 / SCAN_INTERVAL)    // 去抖动时间(20ms)
#define LONG_PRESS_TIME     (1000 / SCAN_INTERVAL) // 长按时间(1000ms)
#define HOLD_INTERVAL       (200 / SCAN_INTERVAL)   // 按住间隔(200ms)
#define DOUBLE_CLICK_TIME   (350 / SCAN_INTERVAL)  // 双击时间(350ms)void KEY_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pull = GPIO_PULLUP;// 初始化所有按键for (uint8_t i = 0; i < KEY_NUM_MAX; i++) {GPIO_InitStruct.Pin = KeyMap[i].pin;HAL_GPIO_Init(KeyMap[i].port, &GPIO_InitStruct);}
}uint8_t ReadKeyState(uint8_t keyID) {return (HAL_GPIO_ReadPin(KeyMap[keyID].port, KeyMap[keyID].pin) == GPIO_PIN_RESET) ? 1 : 0;
}void Key_FSM_Handler(uint8_t keyID) {Key_CB_Type *cb = &Key_CB[keyID];uint8_t keyState = ReadKeyState(keyID);uint16_t event = 0;switch (cb->state) {case KEY_STATE_RELEASED:if (keyState) {cb->state = KEY_STATE_DEBOUNCE;cb->pressStartTime = Key_TimeCount;cb->clickCount = 0;}break;case KEY_STATE_DEBOUNCE:if ((Key_TimeCount - cb->pressStartTime) >= DEBOUNCE_TIME) {if (keyState) {cb->state = KEY_STATE_PRESSED;cb->clickCount++;event = (keyID << 8) | KEY_EVT_PRESS;} else {cb->state = KEY_STATE_RELEASED;}}break;case KEY_STATE_PRESSED:if (!keyState) {cb->state = KEY_STATE_WAIT_RELEASE;cb->pressStartTime = Key_TimeCount;event = (keyID << 8) | KEY_EVT_RELEASE;} else if ((Key_TimeCount - cb->pressStartTime) >= LONG_PRESS_TIME) {event = (keyID << 8) | KEY_EVT_LONG_PRESS;cb->pressStartTime = Key_TimeCount - HOLD_INTERVAL;}break;case KEY_STATE_WAIT_RELEASE:if ((Key_TimeCount - cb->pressStartTime) >= DOUBLE_CLICK_TIME) {event = (cb->clickCount >= 2) ? ((keyID << 8) | KEY_EVT_DOUBLE_CLICK) :((keyID << 8) | KEY_EVT_SINGLE_CLICK);cb->state = KEY_STATE_RELEASED;cb->clickCount = 0;} else if (keyState) {cb->state = KEY_STATE_DEBOUNCE;cb->pressStartTime = Key_TimeCount;cb->clickCount = (cb->clickCount < 2) ? cb->clickCount + 1 : 2;}break;}if (event) {Key_EventReg = event;}
}void Key_Scan(void) {static uint32_t lastScanTime = 0;Key_TimeCount++;if ((Key_TimeCount - lastScanTime) < SCAN_INTERVAL) {return;}lastScanTime = Key_TimeCount;for (uint8_t i = 0; i < KEY_NUM_MAX; i++) {Key_FSM_Handler(i);}
}uint16_t Key_GetEvent(void) {uint16_t temp = Key_EventReg;Key_EventReg = 0;return temp;
}

按键扫描详细过程

按键状态机

按键扫描的核心是状态机,用于处理按键的不同状态和事件。状态机的状态转换如下:

  1. KEY_STATE_RELEASED(释放状态)

    • 按键处于释放状态,等待按下。
    • 如果按键被按下,状态转换为KEY_STATE_DEBOUNCE,并记录按下开始时间。
  2. KEY_STATE_DEBOUNCE(去抖动状态)

    • 等待去抖动时间(DEBOUNCE_TIME),确保按键按下是有效的。
    • 如果按键保持按下,状态转换为KEY_STATE_PRESSED,触发KEY_EVT_PRESS事件。
    • 如果按键释放,状态返回为KEY_STATE_RELEASED
  3. KEY_STATE_PRESSED(按下状态)

    • 按键处于按下状态,等待释放或长按检测。
    • 如果按键释放,状态转换为KEY_STATE_WAIT_RELEASE,触发KEY_EVT_RELEASE事件。
    • 如果按键长按时间超过LONG_PRESS_TIME,触发KEY_EVT_LONG_PRESS事件。
  4. KEY_STATE_WAIT_RELEASE(等待释放状态)

    • 等待双击时间(DOUBLE_CLICK_TIME),检测是否为双击事件。
    • 如果按键再次被按下,状态转换为KEY_STATE_DEBOUNCE,并增加点击次数。
    • 如果按键保持释放,状态返回为KEY_STATE_RELEASED,并根据点击次数触发KEY_EVT_SINGLE_CLICKKEY_EVT_DOUBLE_CLICK事件。

事件处理

Key_FSM_Handler函数中,根据按键的状态和时间计数,触发相应的事件:

  • 单击事件:当按键在DEBOUNCE_TIME内被按下并释放。
  • 双击事件:当按键在DOUBLE_CLICK_TIME内被按下两次。
  • 长按事件:当按键按下时间超过LONG_PRESS_TIME
  • 按住事件:当按键在HOLD_INTERVAL内持续按下。

时间管理

Key_TimeCount用于记录系统运行时间,按键扫描间隔由SCAN_INTERVAL控制。通过比较时间差,实现去抖动、长按和双击检测。


主程序实现

在主程序中,我们需要初始化按键并在主循环中扫描按键状态,处理检测到的事件。

#include "stm32f4xx_hal.h"
#include "bsp_key.h"int main(void) {HAL_Init();SystemClock_Config();KEY_GPIO_Init();while (1) {Key_Scan();uint16_t event = Key_GetEvent();if (event != KEY_EVT_NONE) {uint8_t key_id = KEY_CODE(event);KeyEvent_Type evt_type = KEY_EVENT(event);switch (evt_type) {case KEY_EVT_SINGLE_CLICK:// 处理单击事件break;case KEY_EVT_DOUBLE_CLICK:// 处理双击事件break;case KEY_EVT_LONG_PRESS:// 处理长按事件break;case KEY_EVT_RELEASE:// 处理释放事件break;default:break;}}}
}

测试与验证

  1. 编译与下载

    • 将代码编译并下载到STM32F407开发板中。
  2. 观察按键状态

    • 连接好硬件后,按下按键,观察是否能够正确检测到单击、双击、长按和按住等事件。
  3. 验证多功能触发

    • 测试单击、双击、长按和按住等功能,确保每种事件能够正确触发相应的处理逻辑。

总结

通过本教程,您已经掌握了如何使用STM32F407的GPIO引脚实现多功能按键触发功能,包括单击、双击、长按和按住事件的检测。这种设计不仅提高了用户交互的灵活性,也为后续的项目扩展奠定了良好的基础。希望本教程对您有所帮助,祝您在嵌入式开发的道路上取得更大的成功!


http://www.ppmy.cn/embedded/168889.html

相关文章

Burp Suite Professional 2024版本安装激活指南

文章目录 burpsuite简介Burp Suite的主要组件&#xff1a;Burp Suite的版本使用场景 下载地址使用教程 burpsuite简介 Burp Suite 是一个广泛使用的网络安全测试工具&#xff0c;特别是在Web应用程序安全领域。它主要用于发现和修复Web应用中的安全漏洞&#xff0c;特别适用于渗…

计算机毕业设计Hadoop+Spark+DeepSeek-R1大模型民宿推荐系统 hive民宿可视化 民宿爬虫 大数据毕业设计(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【Linux】责任链模式和消息队列

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 生活总是不会一帆风顺&#x…

前端面试真题 2025最新版

文章目录 写在前文CSS怪异盒模型JS闭包闭包的形成闭包注意点 CSS选择器及优先级优先级 说说flex布局及相关属性Flex 容器相关属性&#xff1a;Flex 项目相关属性 响应式布局如何实现是否用过tailwindcss&#xff0c;有哪些好处好处缺点 说说对象的 prototype属性及原型说说 pro…

windows下安装pyenv+virtualenv+virtualenvwrapper

1、下载pyenv 进入git官网&#xff0c;打包下载zip到本地 2、解压到安装目录 解压下载好的pyenv-win-master.zip到自己的安装目录&#xff0c;如D:\Program Files 3、配置环境变量 右击桌面 此电脑 --> 属性 --> 高端系统设置 --> 环境变量 --> 新建系统变量…

ubuntu:换源安装docker-ce和docker-compose

更新apt源 apt换源&#xff1a;ubuntu&#xff1a;更新阿里云apt源-CSDN博客 安装docker-ce 1、更新软件源 sudo apt update2、安装基本软件 sudo apt-get install apt-transport-https ca-certificates curl software-properties-common lrzsz -y3、指定使用阿里云镜像 su…

《Somewhat Practical Fully Homomorphic Encryption》笔记 (BFV 源于这篇文章)

文章目录 一、摘要二、引言1、FHE 一般分为三个逻辑部分2、噪声的管理3. 贡献点4. 文章思路 三、基础数学知识四、基于 RLWE 的加密1. LWE 问题2. RLWE 问题3. RLWE 问题的难度和安全性 五、加密方案1. LPR.ES 加密方案2. Lemma 1 (引理 1)3. Optimisation/Assumption 1 (优化/…

JavaScript系列(91)--持续集成实践

持续集成实践 &#x1f504; 持续集成&#xff08;Continuous Integration&#xff0c;简称CI&#xff09;是现代前端开发流程中的重要环节&#xff0c;它通过自动化构建、测试和部署&#xff0c;帮助团队更快速、更可靠地交付高质量代码。本文将详细介绍前端持续集成的实践方…