单片机按键示例功能

一、简介

1.1 功能需求

  • 短按:按键按下时间 < 1s,触发一次短按事件;
  • 长按:按键按下时间 ≥ 1s,触发一次长按事件;
  • 3秒内统计按键次数:在3秒内统计短按次数(长按不计入)。

1.2 软件设计思路

  • 使用 状态机 检测按键状态(按下、释放、长按、短按);
  • 使用 滴答定时器 记录按下时间;
  • 使用 软件定时器 实现3秒统计窗口;

二、代码

2.1 按键驱动头文件 key.h

c 复制代码
#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

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 主循环中调用

c 复制代码
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 示例输出(串口):

相关推荐
【ql君】qlexcel6 小时前
MCU上电到运行的全过程
单片机·嵌入式硬件·mcu·启动过程
搞一搞汽车电子6 小时前
S32K3平台eMIOS 应用说明
开发语言·驱动开发·笔记·单片机·嵌入式硬件·汽车
pQAQqa6 小时前
FreeRTOS项目(2)摇杆按键检测
stm32·单片机·嵌入式硬件·freertos
小莞尔8 小时前
【51单片机】【protues仿真】基于51单片机停车场的车位管理系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
一川月白7099 小时前
51单片机---硬件学习(跑马灯、数码管、外部中断、按键、蜂鸣器)
单片机·学习·51单片机·外部中断·蜂鸣器·数码管·跑马灯
weixin_4684668511 小时前
树莓派32位与64位系统安装teamviewer远程软件
linux·单片机·自动化·树莓派·远程控制·vnc·teamviewer
沐欣工作室_lvyiyi12 小时前
2025-2026单片机物联网毕业设计题目推荐(定稿付款)
单片机·物联网·课程设计
曙曙学编程13 小时前
stm32——独立看门狗,RTC
c语言·c++·stm32·单片机·嵌入式硬件
sheepwjl13 小时前
《嵌入式硬件(四):温度传感器DS1820》
单片机·嵌入式硬件