基于状态机实现的按键释放、消抖、单击、双击、长按等功能

STM32实现按键单击、双击、长按、连按功能,使用状态机,无延时,不阻塞 使用定时器状态机实现按键单击、双击、长按、连按功能。消抖时间可调,长按时间可调,双击判定时间可调,连按单击间隔可调。无延时不阻塞,稳定触发,移植简单 阅读详情

状态机理论:
在这里插入图片描述

每次只能执行状态机中的一个状态

注意:状态转移时有时会根据需要发生不同的动作(可根据不同条件发生不同的动作),此处的发生动作指返回按键码值(代表那个按键的对应状态)
如果是在长按状态在向释放状态迁移过程中,执行了获取长按的按键码值,则代表着是在长按结束时,系统响应长按。
在这里插入图片描述

关于状态机几点疑问:
1.为什么在释放状态判断单击的
2.可以在其他状态来判断是否单击吗

问题一:
在按键的释放状态(KEY_RELEASE)中判断是否是单击的设计主要是为了精确处理双击逻辑,并确保按键事件的准确性。这种设计有几个关键点:

  1. 处理双击逻辑:

    • 当用户快速点击两次按键时,系统需要判断这是否是一次双击。为此,在第一次按键松开后的一段时间内,系统进入 KEY_RELEASE 状态,等待查看用户是否会在双击时间窗口内再次按下按键。
    • 如果在此期间用户没有进行第二次按键按下操作,则可以确认这是一次单击,并返回相应的按键码值。
  2. 时间窗口管理:

    • 在按键松开时,系统会记录此时的时间点,并在 KEY_RELEASE 状态中不断检查时间间隔。
    • 如果时间间隔超过设定的双击时间窗口,则判定这次按键为单击。同时,系统会清除 singleClickNum,以便为下一次按键操作做好准备。
    • 这种管理方式使得系统能够在短时间内响应双击输入,而不会因为过早判断为单击而错过双击操作。
  3. 按键事件的准确性:

    • 通过在 KEY_RELEASE 状态中处理单击判断,系统能够更准确地区分单击和双击事件,避免误判。这种设计也确保了用户的输入被正确识别,不会因为按键的抖动或其他原因导致错误的事件触发。

问题二:

可以在其他状态中判断单击,具体取决于你想实现的逻辑和系统响应的需求。例如:

  • 在KEY_SHORTPRESS状态下判断单击:
    在这个状态下,系统已经确认了按键的短按行为。此时你可以根据 singleClickNum 的值直接判断是否是单击,并在判断后返回相应的按键码值。这样做的好处是避免了在释放状态下做出判断,逻辑更为简洁。缺点是需要增加一个状态用来处理单击逻辑。

  • 在KEY_CONFIRM状态下判断单击:
    虽然一般不建议在按键消抖确认状态下直接判断单击,但在特定应用场景中,如果消抖时间窗口较短且对单击检测要求不高,也可以尝试在这个状态下处理单击逻辑。

总体而言,关键是根据应用场景选择在哪个状态进行单击判断,以确保系统的响应速度和按键事件的准确性。

  • 如果用户可能会进行双击操作,那么在 KEY_SHORTPRESS 状态中立即返回单击码值,因为返回单码值后会清零点击的次数和点击的初始时间,这样就会失去处理双击的机会。因此,设计上需要额外增加逻辑来处理双击。

总结:第一次单击需要等待一段时间,用于区分单击和双击,不能第一次单击后立马判断。最后在单击动作执行的下一个状态去判断,这样即等待,而且没有延迟。
在第一次按下的一瞬间会记录按下时间,此时立即判断是否为单击,没有等待一会儿(双击时间窗),如何判断后续是否还有点击呢?不像双击,一双击完,不用管后续是否还有点击。

  1. 在 KEY_CONFIRM 状态判断
    你可以在按键消抖确认的 KEY_CONFIRM 状态结束后,立即判断按键是否符合单击条件并返回结果。

优点:

消抖后直接判断按键状态,减少状态转换的复杂性。
缺点:

需要引入额外的逻辑来区分是单击还是双击操作。如果用户快速双击,可能无法准确检测到第二次按下,从而导致误判。
3. 在 KEY_RELEASE 状态判断(当前方式)
当前的设计是利用 KEY_RELEASE 状态来处理短按和双击的判断,这是一种常见且有效的设计方式。

优点:

充分利用 KEY_RELEASE 状态的等待时间,能够准确判断用户的操作(单击还是双击),并在最后阶段决定返回什么码值。
逻辑清晰,容易理解和维护。
缺点:

状态之间的逻辑跳转较多,但通过良好的注释可以很好地解释流程。
4. 合并逻辑,减少状态
你可以尝试将 KEY_SHORTPRESS 和 KEY_RELEASE 的逻辑合并,使得在按键松开后立即判断是单击还是双击。

优点:

可以减少状态的数量,简化状态机的设计。
缺点:

可能导致状态逻辑过于复杂,难以维护。在高频率操作下,可能会出现错误判断。
总结
在按键的状态机设计中,选择在哪个状态判断短按取决于你需要实现的功能复杂度和代码的易读性。通常情况下,在 KEY_RELEASE 状态判断短按和双击是较为常见的设计,因为它能有效地管理单击和双击的事件。

如果你想在其他状态进行判断,需要额外小心处理状态转移和事件管理,以确保不会因为过早或过晚的判断导致按键事件的误判。当前的设计已经很好地平衡了复杂度和功能性,如果没有特殊需求,不建议做大的调整。

分析该状态机:

四个状态:释放,消抖、短按、长按,三个动作:三个返回值:长按码值、单击按码值、双击码值。

关于长按在释放时执行还是在按下时执行分析:

如果是在长按状态在向释放状态迁移过程中,执行了获取长按的按键码值,则代表着是在长按结束时,系统响应长按。如果是在短按状态向长按状态迁移时,发生的动作(返回长按码值),则代表着是在长按开始时,执行了长按的动作。

在此代码中,按键状态主要有以下四种:

KEY_RELEASE (释放松开状态):表示按键处于未按下或已松开的状态。在这个状态下,代码检测是否有按键按下,如果按下,转移到消抖确认状态。

KEY_CONFIRM (消抖确认状态):用于处理按键的消抖过程。在这个状态下,代码检测按键是否稳定按下。如果按键稳定按下超过设定的消抖时间窗(CONFIRM_TIME),则转移到短按状态。如果按键在消抖时间窗内松开,返回释放状态。

KEY_SHORTPRESS (短按状态):表示按键已稳定按下,等待判断是短按、双击还是长按。在这个状态下,如果按键松开,转移回释放状态,并记录单击次数。如果按键持续按下超过长按时间窗(LONGPRESS_TIME),则转移到长按状态。

KEY_LONGPRESS (长按状态):表示按键已持续按下超过设定的长按时间窗。在这个状态下,如果按键松开,转移回释放状态,并返回长按的按键码值。

#include <stdint.h>
#include "gd32f30x.h"
#include "systick.h"
#include "delay.h"

// 定义按键引脚和RCU配置的结构体
typedef struct
{
	rcu_periph_enum rcu;  // 外设时钟
	uint32_t gpio;        // GPIO端口
	uint32_t pin;         // GPIO引脚
} Key_GPIO_t;

// 按键引脚配置表,只配置了一个按键
static Key_GPIO_t g_gpioList[] = 
{
	{RCU_GPIOC, GPIOC, GPIO_PIN_4},  // key1
};

// 定义按键的最大数量
#define KEY_NUM_MAX (sizeof(g_gpioList) / sizeof(g_gpioList[0]))

// 按键状态枚举
typedef enum
{
	KEY_RELEASE = 0,         // 释放松开
	KEY_CONFIRM,             // 消抖确认
	KEY_SHORTPRESS,          // 短按
	KEY_LONGPRESS            // 长按
} KEY_STATE;

// 定义时间窗口常量
#define CONFIRM_TIME                 10       // 按键消抖时间窗10ms
#define DOUBLE_CLICK_INTERVAL_TIME   300      // 双击时间窗300ms
#define LONGPRESS_TIME               1000     // 长按时间窗1000ms

// 定义按键信息结构体
typedef struct
{
	KEY_STATE keyState;               // 按键当前状态
	uint8_t singleClickNum;           // 单击次数
	uint64_t firstIoChangeSysTime;    // 第一次按键状态改变的时间
	uint64_t firstReleaseSysTime;     // 第一次按键释放的时间
} Key_Info_t;

// 定义全局按键信息数组,保存所有按键的状态信息
static Key_Info_t g_keyInfo[KEY_NUM_MAX];

/**
***********************************************************
* @brief 按键硬件初始化
* @param 无
* @return 无
***********************************************************
*/
void KeyDrvInit(void)
{
	for (uint8_t i = 0; i < KEY_NUM_MAX; i++)
	{
		// 启用对应GPIO端口的时钟
		rcu_periph_clock_enable(g_gpioList[i].rcu);
		
		// 配置GPIO引脚为上拉输入模式,速度为2MHz
		gpio_init(g_gpioList[i].gpio, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, g_gpioList[i].pin);
	}
}

/**
***********************************************************
* @brief 扫描按键状态并返回按键码值
* @param keyIndex 按键索引
* @return 按键码值,短按返回0x01,双击返回0x51,长按返回0x81
***********************************************************
*/
static uint8_t KeyScan(uint8_t keyIndex)
{
	volatile uint64_t curSysTime;
	uint8_t keyPress;
	
	// 读取当前按键状态,0表示按下
	keyPress = gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin);

	switch (g_keyInfo[keyIndex].keyState)
	{
		case KEY_RELEASE:  // 按键释放状态
			if (!keyPress)  // 如果按键被按下
			{ 
				// 切换到消抖状态,并记录按键按下的系统时间
				g_keyInfo[keyIndex].keyState = KEY_CONFIRM;
				g_keyInfo[keyIndex].firstIoChangeSysTime = GetSysRunTime();
				break;
			}

			// 如果存在未处理的单击
			if (g_keyInfo[keyIndex].singleClickNum != 0)
			{
				curSysTime = GetSysRunTime();
				// 判断是否超出双击时间窗,如果超出,认为是单击
				if (curSysTime - g_keyInfo[keyIndex].firstReleaseSysTime > DOUBLE_CLICK_INTERVAL_TIME)
				{
					g_keyInfo[keyIndex].singleClickNum = 0;
					return (keyIndex + 1); // 返回单击按键码值
				}
			}
			break;
			
		case KEY_CONFIRM:  // 按键消抖确认状态
			if (!keyPress)
			{
				curSysTime = GetSysRunTime();
				// 如果按键按下稳定超过消抖时间窗,则切换到短按状态
				if (curSysTime - g_keyInfo[keyIndex].firstIoChangeSysTime > CONFIRM_TIME)
				{
					g_keyInfo[keyIndex].keyState = KEY_SHORTPRESS;
				}
			}
			else  // 如果按键松开,则回到释放状态
			{
				g_keyInfo[keyIndex].keyState = KEY_RELEASE;
			}
			break;
			
		case KEY_SHORTPRESS:  // 短按确认状态
			if (keyPress)
			{
				g_keyInfo[keyIndex].keyState = KEY_RELEASE;
				g_keyInfo[keyIndex].singleClickNum++;  // 记录单击次数
				
				// 第一次单击,记录按键释放时间
				if (g_keyInfo[keyIndex].singleClickNum == 1)
				{
					g_keyInfo[keyIndex].firstReleaseSysTime = GetSysRunTime();
					break;
				}
				else
				{
					curSysTime = GetSysRunTime();
					// 如果双击间隔时间内按下第二次,认为是双击
					if (curSysTime - g_keyInfo[keyIndex].firstReleaseSysTime <= DOUBLE_CLICK_INTERVAL_TIME)
					{
						g_keyInfo[keyIndex].singleClickNum = 0;
						return (keyIndex + 0x51); // 返回双击按键码值
					}
				}
			}
			else
			{
				curSysTime = GetSysRunTime();
				// 按键按下时间超过长按时间窗,认为是长按
				if (curSysTime - g_keyInfo[keyIndex].firstIoChangeSysTime > LONGPRESS_TIME)
				{	
					g_keyInfo[keyIndex].keyState = KEY_LONGPRESS;
				}
			}
			break;
			
		case KEY_LONGPRESS:  // 长按状态
			if (keyPress)
			{
				g_keyInfo[keyIndex].keyState = KEY_RELEASE;
				return (keyIndex + 0x81); // 返回长按按键码值
			}
			break;
			
		default:
			g_keyInfo[keyIndex].keyState = KEY_RELEASE;
			break;
	}
	return 0;
}

/**
***********************************************************
* @brief 获取按键码值
* @param 无
* @return 按键码值,短按返回0x01 0x02 0x03,长按返回0x81 0x82 0x83,没有按下返回0
***********************************************************
*/
uint8_t GetKeyVal(void)
{
	uint8_t res = 0;

	for (uint8_t i = 0; i < KEY_NUM_MAX; i++)
	{
		res = KeyScan(i);
		if (res != 0)
		{
			break;
		}
	}
	return res;
}

/**
***********************************************************
* @brief 带消抖的按键扫描
* @param keyIndex 按键索引
* @return 按键码值,按下返回keyIndex+1,否则返回0
***********************************************************
*/
static uint8_t KeyScanWithBlock(uint8_t keyIndex)
{
	uint8_t keyPress;
	
	keyPress = gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin);

	if (keyPress)  // 无按键按下
	{ 
		return 0;
	}

	DelayNms(CONFIRM_TIME);  // 消抖延时

	keyPress = gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin);

	if (keyPress)  // 无按键按下
	{ 
		return 0;
	}
	return (keyIndex + 1);
}

/**
***********************************************************
* @brief 带消抖的按键获取
* @param 无
* @return 按键码值,按下返回按键码值,否则返回0
***********************************************************
*/
uint8_t GetKeyValWithBlock(void)
{
	uint8_t res = 0;

	for (uint8_t i = 0; i < KEY_NUM_MAX; i++)
	{
		res = KeyScanWithBlock(i);
		if (res != 0)
		{
			break;
		}
	}
	return res;	
}

方法二:一个状态对于一个返回值

#include <stdint.h> // 包含标准整数类型定义
#include "gd32f30x.h" // 包含GD32F30x系列微控制器的寄存器定义和相关功能
#include "systick.h" // 包含系统定时器相关的函数和定义
#include "delay.h" // 包含延时函数的声明和定义

// 定义按键的GPIO配置结构体
typedef struct {
    rcu_periph_enum rcu; // 微控制器的时钟使能枚举
    uint32_t gpio; // GPIO端口地址
    uint32_t pin; // 引脚编号
} Key_GPIO_t;

// 定义按键GPIO列表,这里定义了一个按键连接到GPIOC的第4位
static Key_GPIO_t g_gpioList[] = {
    {RCU_GPIOC, GPIOC, GPIO_PIN_4},  // key1
};

#define KEY_NUM_MAX (sizeof(g_gpioList) / sizeof(g_gpioList[0])) // 计算按键数量

// 定义按键状态枚举类型
typedef enum {
    KEY_RELEASE = 0,         // 按键未按下或已释放
    KEY_DEBOUNCE,            // 按键按下消抖状态
    KEY_SHORTPRESS,          // 按键短按
    KEY_DOUBLECLICK_WAIT,    // 等待判断是否双击状态
    KEY_LONGPRESS            // 按键长按
} KEY_STATE;

// 定义按键处理的时间参数
#define CONFIRM_TIME                10       // 消抖时间10ms
#define DOUBLE_CLICK_INTERVAL_TIME  300      // 双击间隔时间300ms
#define LONGPRESS_TIME              1000     // 长按时间1000ms

// 定义按键信息结构体,包含按键状态、点击次数和时间信息
typedef struct {
    KEY_STATE keyState; // 当前按键状态
    uint8_t clickCount;  // 记录点击次数,用于区分单击和双击
    uint64_t lastPressTime;   // 记录按键最后一次按下的时间
    uint64_t lastReleaseTime; // 记录按键最后一次释放的时间
} Key_Info_t;

// 初始化按键信息数组,数量为按键数量
static Key_Info_t g_keyInfo[KEY_NUM_MAX];

// 按键硬件初始化函数,配置GPIO为上拉输入模式
void KeyDrvInit(void) {
    for (uint8_t i = 0; i < KEY_NUM_MAX; i++) {
        rcu_periph_clock_enable(g_gpioList[i].rcu); // 使能GPIO时钟
        gpio_init(g_gpioList[i].gpio, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, g_gpioList[i].pin); // 初始化GPIO
    }
}

// 静态函数,用于扫描按键状态
static uint8_t KeyScan(uint8_t keyIndex) {
    // 获取当前系统运行时间
    uint64_t curSysTime = GetSysRunTime();
    // 读取按键状态,这里假设按下为0
    uint8_t keyPress = !gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin); // 按键逻辑可能需要根据实际硬件调整

    // 根据按键当前状态和时间判断按键动作
    switch (g_keyInfo[keyIndex].keyState) {
        case KEY_RELEASE:
            if (keyPress) { // 如果按键被按下
                g_keyInfo[keyIndex].keyState = KEY_DEBOUNCE; // 迁移到消抖状态
                g_keyInfo[keyIndex].lastPressTime = curSysTime; // 记录按下时间
            }
            break;

        case KEY_DEBOUNCE:
            if (keyPress) {
                if (curSysTime - g_keyInfo[keyIndex].lastPressTime > CONFIRM_TIME) {
                    g_keyInfo[keyIndex].keyState = KEY_SHORTPRESS; // 迁移到短按状态
                    g_keyInfo[keyIndex].clickCount++; // 增加点击次数
                }
            } else {
                g_keyInfo[keyIndex].keyState = KEY_RELEASE; // 如果按键释放,迁移到释放状态
            }
            break;

        case KEY_SHORTPRESS:
            if (!keyPress) { // 如果按键释放
                g_keyInfo[keyIndex].lastReleaseTime = curSysTime; // 记录释放时间
                if (g_keyInfo[keyIndex].clickCount == 1) {
                    g_keyInfo[keyIndex].keyState = KEY_DOUBLECLICK_WAIT; // 迁移到双击等待状态
                } else {
                    // 如果不是双击,返回单击事件,并重置点击次数
                    g_keyInfo[keyIndex].keyState = KEY_RELEASE;
                    g_keyInfo[keyIndex].clickCount = 0;
                    return (keyIndex + 0x01); // 返回按键单击码值
                }
            } else if (curSysTime - g_keyInfo[keyIndex].lastPressTime > LONGPRESS_TIME) {
                // 如果按下时间超过长按时间,迁移到长按状态
                g_keyInfo[keyIndex].keyState = KEY_LONGPRESS;
            }
            break;

        case KEY_DOUBLECLICK_WAIT:
            if (keyPress) { // 如果在等待双击期间按键被按下
                g_keyInfo[keyIndex].keyState = KEY_DEBOUNCE; // 重新进入消抖状态
            } else if (curSysTime - g_keyInfo[keyIndex].lastReleaseTime > DOUBLE_CLICK_INTERVAL_TIME) {
                // 如果超过双击间隔时间,确认为单击并返回
                g_keyInfo[keyIndex].keyState = KEY_RELEASE;
                g_keyInfo[keyIndex].clickCount = 0;
                return (keyIndex + 0x51); // 返回按键双击码值
            }
            break;

        case KEY_LONGPRESS:
            if (!keyPress) { // 如果长按后按键释放
                g_keyInfo[keyIndex].keyState = KEY_RELEASE; // 返回释放状态
                return (keyIndex + 0x81); // 返回按键长按码值
            }
            break;

        default:
            g_keyInfo[keyIndex].keyState = KEY_RELEASE; // 任何未知状态都重置为释放状态
            break;
    }
    return 0; // 如果没有按键事件或按键事件已处理,则返回0
}

// 获取按键码值的函数,遍历所有按键并调用KeyScan函数
uint8_t GetKeyVal(void) {
    uint8_t res = 0; // 初始化结果为0

    for (uint8_t i = 0; i < KEY_NUM_MAX; i++) {
        res = KeyScan(i); // 调用KeyScan函数扫描按键
        if (res != 0) {
            break; // 如果有按键事件发生,返回结果并退出循环
        }
    }
    return res; // 返回按键码值,如果没有按键事件则返回0
}
  1. 实现细节
    KEY_RELEASE:初始状态或按键松开后的状态,等待按键按下。
    KEY_DEBOUNCE:按键按下后,进入消抖状态,确保按键状态稳定。
    KEY_SHORTPRESS:消抖结束后,进入短按状态,检测是否为双击或长按。
    KEY_DOUBLECLICK_WAIT:短按后松开,等待双击的第二次按下,如果超时,返回单击事件。
    KEY_LONGPRESS:在短按状态下,按键持续按住超过长按时间,进入长按状态,松开后返回长按事件。
按键状态机(实现单击,长按,双击)的模块分享 此文章为学习经验分享,帮助和我一样的小白理解按键的状态机,能够自己实现想要实现的功能。(附函数模块哦) 阅读详情

相关推荐

状态机按键消抖

最近研究了一下状态机消抖,不占用MCU资源的非阻塞消抖方式。

Yin_w的博客 2834

按键扫描

按键扫描代码 按键是比较复杂。可以用状态机表示。 每10mS执行一次键盘扫描任务 0、无键,若有键进入状态1 1、第一次检到有键。若有键进入状态2,同时将键值送缓冲区。 若无键进入状态0。 2、若有键则计时,按键超过300ms进入状态3,同时将键值送缓冲区。 若无键进入状态1。   表示第一次按键后如果长按键则300ms后认为有一次按键。 3、若有键则

SHK242673的专栏 1952

c语言按键长按双击,51单片机实现单按键单击、双击、长按功能

#include "keyboard.h"/***************************************************************************程序功能:一个按键的单击、双击、长按。三种按键方式,然后做不同的处理。这里以P1口的LED变化作为测试单击:点亮P1口的第一个LED(低电平点亮)双击:点亮P1口的第二个LED(低电平点亮)长按:点亮P1口...

weixin_42135773的博客 4579

状态机思想(以按键为例)

以按键为例实现状态机

zHzAim的博客 2469

ESP32定时中断实现单、双击、长按等功能的按键状态机Arduino代码

下载:ESP32定时中断实现单击、双击、长按等功能的按键状态机Arduino代码。-嵌入式文档类资源-优快云下载 一、问题的提出。 按键处理一直都是嵌入系统必须要做的事情,而在很多实时要求较高的系统里面,采用传统延时防抖读取按键的过程容易产生阻塞。会使系统来不及处理其他事务。如显示扫描,串行接收,WIFI通信等,都是需要及时获得CPU控制权的,如果按键长时间阻塞,就会影响到这些事务的处理。 二、实现原理。 本人使用定时中断按键状态机原理。封装一个CLASS,用于ESP32控制 VS1053解码的播

liyong_sbcel的专栏 5484

按键双击和单击c语言,【按键】[独立按键] - 单击,双击,三击以及N击

下面这段是引言,和按键程序实质内容没有任何关系,可以当P话,可直接跳到分割线的地方。最近自己在琢磨按键程序,之前弄了,按键单击程序,程序可以实现读取按键的几个状态,然后根据需求使用。单击按键的几个状态如下:单击后,马上返回按键值单击后,释放之后,返回按键值单击后,按住不放,多次返回按键值但是在其上面扩展双击和三击以及多击程序时,遇到了麻烦(其实就是不会啦),于是在网上看了某个老师的经典按键程序,里...

weixin_31143391的博客 4324

[007] [STM32] 以面向对象的思想编写按键程序

CubeMX配置 RCC:配置HSE为晶体/陶瓷谐振器 SYS:Debug选择SW模式 时钟:选择HSE作为PLL时钟源,将其改为8MHz(默认),然后选择PLLCLK作为系统时钟源,设为系统时钟为72MHz 按键引脚:PA0(wk_up)下拉输入,PA15(key1)和PC5(key0)上拉输入 LED引脚:PA8(红灯)和PD2(黄灯)设为推挽输出,默认输出高电平 TIM6:向上计数模式(TIM6&7只支持向上计数),psc = 72 - 1,arr = 1000 - 1,即1ms更新中断,

柯西的彷徨的博客 3472

STC单片机单击、双击、长按状态机程序

STC单片机单击、双击、长按状态机程序

perseverance51博客 1199

01 使用 C 语言结构体设计模块化按键检测

嵌入式按键模块化设计摘要 本文介绍了一个AI Agent设计基于C语言结构体的模块化按键检测系统的完整过程。设计核心是将按键功能划分为硬件配置、状态管理和事件检测三个独立层次,通过结构体分层实现模块化: 需求分析:涵盖6种按键事件(按下/释放/单击/双击/长按/持续长按),采用边沿触发机制确保事件不重复上报。 数据结构设计: 硬件配置层(ButtonConfig):存储引脚、消抖时间等静态参数 状态管理层(Button):包含状态机、计时器等运行时变量 管理层(ButtonManager):统一管理多个按键

我是黑核阿泽:外表黑盒,内藏智造宇宙。专注给硬件装“大脑”,让它们像人一样思考干活。这里记录嵌入式AI、智能体与机器人的进化。不堆术语,只分享如何让万物从死物变活物。一起见证硬件觉醒的奇妙时刻! 420

【阿里云大模型高级工程师ACP习题集】2.7 通过微调增强模型能力 (上篇)(⭐️⭐️⭐️ 重点章节!!!)

【摘要】这是我精心整理出对应的习题集,里面足足有 30 道题目,从基础概念到深度拓展,全方位覆盖章节重点、难点。不管你是想检验自己的学习成果,还是想针对薄弱环节进行强化训练,它都能完美适配!

davidkorhenn的博客 1603

Matlab子图排版终极指南:从tight_subplot到tiledlayout的实战对比

本文深入对比Matlab中两大子图排版工具:社区经典函数tight_subplot与官方现代布局管理器tiledlayout。针对默认subplot产生的子图间距与边缘距离问题,详细解析两者的设计哲学、参数控制与实战应用,帮助用户根据版本与需求选择最佳方案,实现高效、精确且美观的多子图排版。

weixin_29048309的博客 360

三行代码按键消抖 独立按键 矩阵按键 长按 短按 双击

独立按键抖动产生原因;普通软件延时消抖;标志位软件延时消抖;标志位定时器延时消抖2、矩阵键盘的识别与编码;查询扫描;定时扫描;中断扫描;使用定时器状态机的方法进行按键的扫描识别3、定时器三行代码 矩阵键盘长短按的识别 ...

#法外狂徒张三的博客 3万+

多模态特征融合方法总结

由于本人的研究是文本、语音和图像三者模态数据的融合,所以针对的是这三种模态的特征融合方法进行总结。 首先,多模态特征融合的方法大体分为三种

华辰烟雨的博客 9万+

【详解~按键状态机~功能Plus】2.实现单击、双击、长按的功能(基于STM32)

按键状态机讲解见:【详解~按键状态机】1.实现短按长按的功能 本文长按短按的基础上,增加了双击功能。 文章目录1.问题描述2.单击、双击、长按的定义3.代码变更讲解3.1 宏定义3.2 结构体、枚举型3.3 按键全局变量3.4 函数定义关键函数3.5 定时器中断及main函数定时器函数main函数4.实验验证5.总结 1.问题描述 使用一个按键,实现长按、单击、双击操作。运用状态机思想,提高效率。 外设:io口、定时器 2.单击、双击、长按的定义 事.

qq_44078824的博客 1万+

手把手教你用STM32的ADC读取PT100模块,实现高精度温度采集(附完整代码)

本文详细介绍了如何利用STM32的ADC模块读取PT100温度传感器信号,实现高精度温度采集系统。从硬件选型、ADC配置校准到温度计算算法和软件滤波技术,提供完整的实现方案和优化技巧,帮助工程师快速构建稳定可靠的温度监测系统。

weixin_29185199的博客 347

说说通用报表控件

我们已经开发有2款WINCC报表控件,一种是固定模板,一种是自定义模板。但是它们有个共同特点,就是数据都是来自WINCC的归档数据库。使用WINCC归档数据库的好处是不需要自己写脚本保存数据。但是有些情况下,我们需要自己保存数据,或者第三方系统通过数据表的方式给我们提供数据。那这样的话,我们之前的两款控件就不支持了。正好有个客户有类似需要,于是加了一天班写了个通用报表控件。之所以说是通用,是因为它可以插入任何支持.NET控件的组态软件中,比如WINCC、IFIX等。并且它不依赖于组态软件自己...

HwLib_的博客 3551

【STM32】状态机实现定时器按键消抖,处理单击、双击、三击、长按事件

众所周知,普通的机械按键会产生抖动,可以采取硬件上加电容来滤波,也可以考虑用软件来消抖。这里笔者分享一种基于状态机的按键消抖策略,可以实现单击双击三击长按事件的读取。按键时间也可以自己设置。这种方法需要消耗掉定时器资源,还有额外的RAM支出。如图矩形框内描述,最终键值的确定需要标志位和计数值,因此一个按键结构体应该这样定义//按键端口//按键PIN//按键类型//按压计数器//高电平计数器//按压标志//松手标志//按键键值。

勤学以惜时 8707

修改 Alist 密码时报错?看看这个坑怎么解决!

启动服务后,再修改密码”。以后再遇到类似问题,先检查服务是否正常运行,说不定就能少踩不少坑。如果还有其他 Alist 的问题,欢迎在评论区留言交流~

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1154

STM32F4按键状态机--单击、双击、长按

STM32F4按键状态机--单击、双击、长按

wwwqqq2014的博客 5193

《AgentScope-Java 深入浅出教程》第3章 消息系统(Message)

在 AgentScope 中,消息(Msg)是最核心的数据结构。│ 消息的作用 ││ ││ │ 用户输入 │ ────────────┐ ││ ▼ ││ │智能体通信│ ◄────► │ 消息对象 │ ◄────► │ LLM API │ ││ ▲ ││ │ ││ │ 历史存储 │ ────────────┘ ││ │用户输入:封装用户的问题和指令智能体输出:封装智能体的回复和工具调用智能体通信:在多智能体系统中传递信息LLM API 交互:转换为 LLM 可理解的格式历史存储。

步子哥的博客 229

AgentScope实战训练营- Java从零开始快速构建Agent完整教程

本文基于阿里巴巴开源的 AgentScope Java 框架,从零开始手把手带你构建 AI 智能体应用。内容涵盖环境搭建、基础对话、工具调用、结构化输出、多Agent流水线、Hook可观测性、会话持久化、MCP协议、RAG知识库集成、多模态工具、技能系统、Spring Boot Web部署等21个核心模块,附带完整可运行代码示例,帮助Java开发者快速掌握Agent开发全流程。

program哲学 863

STM32按键识别 状态机( 单击 双击 长按)可以根据情况自己在添加多击 长按

STM32按键识别 状态机( 单击 双击 长按)可以根据情况自己在添加多击 长按 在做嵌入式产品时按键是最常用的输入信号了,按键多的时候我们可以实现很多的功能,有时候资源不够,但又需要实现较多的功能时,这就需要按键复用了,常常一个按键包含短按,长按,或者双击,N击,赋予不同的功能,这和具体项目有关。我们通常做的方法是识别I/O引脚电平,然后加以适当防抖处理,那么简单的按键单击功能就可以实现了。 先...

康明的博客 6757

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

蓝桥杯嵌入式之按键单击,双击与长按 (上)

使用STM32G431RBT6实现按键的单击/双击/长按功能,采用定时器中断和状态机实现

m0_72982674的博客 2413

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

51单片机独立按键长按与短按(长按触发连发功能)以及双击功能

独立按键的长按、短按与双击的判断,运用了状态的分析,短按和长按的区别为按键按下的时间不同所以从短按状态到长按状态的条件为按键按下的时间超过设定的长按时间,而二者的共同点为:按键只按下一次。而双击与前者的不同在于双击按键按下了两次。 我在这先识别【单击】和【长按】然后在有一次按键按下的基础下,且必须是【单击】的情况下再去在规定的间隔内检测【双击】。 由于在我们按键按下的时候会有抖动,实际中会有这种情况:按键松开了但没松完,或者按键没有完全按下,只是很轻的按单片机也会判断为【单击】,所以我们在这需要设置判.

m0_63192327的博客 2万+

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

51单片机——按键,定时器消抖,短按,长按,三行代码,状态机

0.序 今天太晚了,先不写介绍了,代码中注释非常详细,很方便移植 1.分析 占坑,有时间再截图分析 2.代码 #include <KEY.H> // 按键 Key0-P4.6 Key1-P3.6 Key2-P3.5 sbit key0=P4^6; sbit key1=P3^6; sbit key2=P3^5; u16 ul_ms=0; //在定时器中++ u8 uc_Key_flag = 0; //在定时器中每10ms置1,定时器延时 u8 keyPressFlag...

去流浪 1万+

单片机:实现单个按键的单击、双击、长按功能(完整源码)

单片机:实现单个按键的单击、双击、长按功能(完整源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 2471
上一篇: stm32部分元器件
下一篇: 关于GD32外挂8080LCD芯片的疑问(非面向公开的,用于记录踩坑点)
物语系列
博客等级 码龄6年 582粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值