[经验分享] 单片机按键示例功能

[复制链接]
1563|0
晓伍 发表于 2025-9-9 08:58 | 显示全部楼层 |阅读模式
一、简介
1.1 功能需求
短按:按键按下时间 < 1s,触发一次短按事件;
长按:按键按下时间 ≥ 1s,触发一次长按事件;
3秒内统计按键次数:在3秒内统计短按次数(长按不计入)。
1.2 软件设计思路
使用 状态机 检测按键状态(按下、释放、长按、短按);
使用 滴答定时器 记录按下时间;
使用 软件定时器 实现3秒统计窗口;
二、代码
2.1 按键驱动头文件 key.h
#ifndef __KEY_H
#define __KEY_H

#include "main.h"          // HAL 头文件,提供 GPIO 口与 HAL_GetTick
#include <stdbool.h>

/* 用户配置:引脚、电平、长按阈值、统计窗口时长 */
/* BUTTON_USER定义 */
#define RCC_BUTTON_USER_CLK       RCC_PERIPH_CLK_GPIOB
#define BUTTON_USER_PORT          GPIOB
#define BUTTON_USER_PIN           GPIO_PIN_10
#define KEY_PRESSED_LEVEL             0                        // 0 = 低电平为按下
#define KEY_LONG_MS                   1000                     // ≥1 s 算长按
#define KEY_STAT_WINDOW_MS            3000                     // 3 s 统计窗口

/* 状态机状态 */
typedef enum {
    KEY_STA_IDLE = 0,
    KEY_STA_PRESSED,
    KEY_STA_SHORT,
    KEY_STA_LONG
} KeyStateEnum;

/* 按键结构体 */
typedef struct {
    /* 底层硬件信息 */
    GPIO_t        *port;
    uint16_t      pin;
    uint8_t       pressedLevel;   // 按下时的电平值
    uint32_t      longThd;        // 长按阈值 ms
    uint32_t      winThd;         // 统计窗口 ms

    /* 运行期状态 */
    KeyStateEnum  state;
    uint32_t      tPress;         // 本次按下时刻
    uint32_t      tRelease;       // 本次释放时刻
    uint8_t       shortCnt;       // 窗口内短按计数
    uint32_t      winStart;       // 统计窗口起始时刻
    bool          winActive;      // 统计窗口是否打开
} Key_t;

extern Key_t gKey;

void KEY_Init(Key_t *k);
void KEY_Scan(Key_t *k);
void KEY_Process(Key_t *k);

#endif





2.2 按键驱动源文件 key.c

#include "key.h"
#include <stdio.h>

/**
* @brief  EXTI初始化
* @retval 无
*/
void KeyExtiInit(void)
{
    std_exti_init_t exti_init_config = {0};
    std_gpio_init_t button_init_config = {0};   

    /* 使能BUTTON_USER对应的GPIO时钟 */
    std_rcc_gpio_clk_enable(RCC_BUTTON_USER_CLK);

    /* 配置BUTTON_USER的GPIO */
    button_init_config.pin = BUTTON_USER_PIN;
    button_init_config.mode = GPIO_MODE_INPUT;
    button_init_config.pull = GPIO_PULLUP;
    std_gpio_init(BUTTON_USER_PORT, &button_init_config);

    /* 配置BUTTON_USER的EXTI */
    exti_init_config.line_id = BUTTON_USER_EXTI_LINE;
    exti_init_config.mode = EXTI_MODE_INTERRUPT;
        exti_init_config.trigger = EXTI_TRIGGER_FALLING;       

    exti_init_config.gpio_id = BUTTON_USER_EXTI_PORT;
    std_exti_init(&exti_init_config);

    /* 配置中断优先级 */
    NVIC_SetPriority(EXTI4_15_IRQn, NVIC_PRIO_0);
    /* 使能中断 */
    NVIC_EnableIRQ(EXTI4_15_IRQn);
}

Key_t gKey;

/* 读引脚,返回 true = 当前处于按下状态 */
static bool KEY_Read(const Key_t *k) {
    return std_gpio_get_input_pin(k->port, k->pin) == k->pressedLevel;
}

/* 初始化,把用户配置填进去 */
void KEY_Init(Key_t *k) {
    k->port        = BUTTON_USER_PORT;
    k->pin         = BUTTON_USER_PIN;
    k->pressedLevel= KEY_PRESSED_LEVEL;      // 0 = 低电平为按下
    k->longThd     = KEY_LONG_MS;            // ≥1 s 算长按
    k->winThd      = KEY_STAT_WINDOW_MS;     // 3 s 统计窗口

    k->state       = KEY_STA_IDLE;
    k->tPress      = 0;                      // 本次按下时刻
    k->tRelease    = 0;                      // 本次释放时刻
    k->shortCnt    = 0;                      // 窗口内短按计数
    k->winStart    = 0;                      // 统计窗口起始时刻
    k->winActive   = false;                  // 统计窗口是否打开
}

/* 状态机扫描,1 ms 周期调用 */
void KEY_Scan(Key_t *k)
{
    static bool lastRaw = false;
    bool        raw     = KEY_Read(k);
    uint32_t    now     = osKernelGetTickCount();

    switch (k->state)
        {
                case KEY_STA_IDLE:
                        if (raw && !lastRaw)           // 刚按下
                        {         
                                k->tPress = now;
                                k->state  = KEY_STA_PRESSED;
                        }
                        break;

                case KEY_STA_PRESSED:             // 释放
                        if (!raw)
                        {                     
                                k->tRelease = now;
                               
                                if ((k->tRelease - k->tPress) < k->longThd)
                                        k->state = KEY_STA_SHORT;
                                else
                                        k->state = KEY_STA_IDLE;
                        }
                        else if ((now - k->tPress) >= k->longThd)
                        {
                                k->state = KEY_STA_LONG;
                        }
                        break;

                case KEY_STA_SHORT:
                        /* 打开或延续统计窗口 */
                        if (!k->winActive)
                        {
                                k->winActive = true;
                                k->winStart  = now;
                                k->shortCnt  = 1;
                        }
                        else
                        {
                                k->shortCnt++;
                        }
                        k->state = KEY_STA_IDLE;
                        break;

                case KEY_STA_LONG:
                        /* 长按事件触发,不计入统计 */
                        k->state = KEY_STA_IDLE;
                        break;
    }
    lastRaw = raw;
}


/* 业务层:3 s 到点后打印统计值,并重置窗口 */
void KEY_Process(Key_t *k)
{
    if (!k->winActive) return;

    uint32_t now = osKernelGetTickCount();
       
    if ((now - k->winStart) >= k->winThd)
        {
        printf("3 s 内短按次数:%d\n", k->shortCnt);
               
                k->state       = KEY_STA_IDLE;
                k->tPress      = 0;                      // 本次按下时刻
                k->tRelease    = 0;                      // 本次释放时刻
                k->shortCnt    = 0;                      // 窗口内短按计数
                k->winStart    = 0;                      // 统计窗口起始时刻
                k->winActive   = false;                  // 统计窗口是否打开               
               
                if(system_info.flags.key_isr_flag == 1)
                {
                        if(KeyIsPressed() == false)
                        {       
                                printf("KeyIsPressed-2 \r\n");
                                system_info.flags.key_isr_flag = 0;
                        }
                }               
    }
}





2.3 主循环中调用
void AppTaskStart(void *argument)
{
        const uint16_t usFrequency = 10; /* 延迟周期 */
        uint32_t tick;
        /* 获取当前时间 */
        tick = osKernelGetTickCount();
       
        KEY_Init(&gKey);
       
    while(1)
    {
                KEY_Scan(&gKey);     // 10 ms 周期
        KEY_Process(&gKey);
               
                /* 相对延迟 */
                tick += usFrequency;                          
                osDelayUntil(tick);
    }
}


2.4 示例输出(串口):



————————————————
版权声明:本文为CSDN博主「LS·Cui」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37391577/article/details/151328430

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

101

主题

4362

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部