Multi_button 学习笔记

碎碎念:由于暂时签了一个offer,忙毕业论文相关的,好久没写了。今天写一下stm32中的状态机相关的代码学习。后续看上班岗位要写啥,估计会学习一下几个芯片或者rtos的api写法。还是要抽空学习总结知识。

先挂一个原作者的链接:

https://github.com/0x1abin/MultiButton.git

该项目是一个关于按键状态的小代码,代码量很小,其中比较值得注意的点是位域和状态机轮询的写法。相对应的还有一个项目则是事件驱动。我在自己的代码中,参考了AI,修改了部分宏定义为内联函数。而对于事件的回调操作,

由于使用的是轮询检查的方式,建议用于rtos系统交给任务队列处理或者用GPIO边沿中断处理。

基本准备

按键电路图

从图中可以看出使用的是没有硬件消抖的按键电路(也不重要,不管有没有消抖其实都会加数字防抖),同时需要芯片设置上拉,低电平有效。

  /*Configure GPIO pins : Key1_Pin Key0_Pin */
  GPIO_InitStruct.Pin = Key1_Pin|Key0_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

mutiButton 头文件

首先是相关头文件以及枚举数的定义。最开头的TICKs用于控制轮询的频率,随后是事前声明(一个好习惯,我以前不写事前声明的,值得学习),然后是控制事件的event以及state枚举数。

事件分为了8个事件,状态分为了5个状态

<details>
<summary>展开代码</summary>
#ifndef _MULTI_BUTTON_H
#define _MULTI_BUTTON_H

#include <stdint.h>
#include <string.h>

// 按键状态定义
#define TICKS_INTERVAL 5 // ms -timer interrupt interval
// 定时器中断/轮询的时间间隔,单位是毫秒(ms)所有以“ticks”为单位的计数器(短按/长按阈值、消抖计数等)都以此为基准。
// 增大它会降低时间分辨率并减少中断频率;减小它会提高分辨率但增加处理频率。
#define DEBOUNCE_TICKS 3 // MAX 7 (0~7) debounce filter depth
// 消抖滤波深度,以 ticks 为单位,需要连续多少个 ticks 的稳定读数才认为按键状态已稳定。3代表2^3-1
#define SHORT_TICKS (300 / TICKS_INTERVAL) // ms -short press time
#define LONG_TICKS (1000 / TICKS_INTERVAL) // ms -long press time
#define PRESS_PEPEAT_MAX_NUM 15            // maxmum repeat counter value 按键重复(repeat)计数器的最大值

// forword declaration
typedef struct _Button Button;

// Button callback function type
typedef void (*ButtonCallback)(Button *btn_handle);

// Button event type 8 events 
typedef enum
{
    BTN_PRESS_DOWN = 0,  // button press down event
    BTN_PRESS_UP,         // button released
    BTN_PRESS_REPEAT,     // repeated press detected
    BTN_SINGLE_CLICK,     // single click event
    BTN_DOUBLE_CLICK,     // double click event
    BTN_LONG_PRESS_START, // long press start event
    BTN_LONG_PRESS_HOLD,  // long press hold event
    BTN_EVENT_COUNT,      // total number of button events
    BTN_NONE_PRESS        // no button press event
} ButtonEvent;

// Button state machine states
typedef enum
{
    BTN_STATE_IDLE = 0,     // button idle state
    BTN_STATE_PRESS,        // button pressed state
    BTN_STATE_WAIT_RELEASE, // button wait release state
    BTN_STATE_REPEAT,       // button repeat state
    BTN_STATE_LONG_HOLD,    // button long press hold state
} ButtonState;


在Button的结构体中使用了位域的代码写法,用于节省空间,类似寄存器

<detail>
<summary>展开代码</summary>
struct _Button
{
    uint16_t ticks;                                 // tick counter 2 bytes
    uint8_t repeat : 4;                             // repeat counter (0-15 ) 0.5 byte
    uint8_t event : 4;                              // button event(0-15) 0.5 byte 与repeat共用1字节
    uint8_t state : 3;                              // button state(0-7) 1 byte
    uint8_t debounce_cnt : 3;                       // debounce counter(0-7) 1 byte
    uint8_t active_level : 1;                       // active GPIO level(0/1) 1 byte
    uint8_t button_level : 1;                       // current button level (0/1) 1 byte 上述公用1字节
    uint8_t button_id;                              // button identifier 1 byte
    uint8_t (*hal_button_level)(uint8_t button_id); // Hal function to read GPIO level 4 bytes
    ButtonCallback cb[BTN_EVENT_COUNT];             // callback functions for each event 4 bytes x 7 = 28 bytes
    Button *next;                                   // netxt button in linked list 4 bytes
};

然后就是相关的按键控制的函数,在代码中已经添加了相关注释

<details>
<summary>展开代码</summary>
#ifdef __cplusplus
extern "C"
{
#endif
    // Public API functions
    /*
    @brief 按键初始化
    */
    void button_init(Button *handle, uint8_t (*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id);
    /*
    @brief 绑定按键事件回调函数
    */
    void button_attach(Button *handle, ButtonEvent event, ButtonCallback cb);
    /*
    @brief 解绑按键事件回调函数
    */
    void button_detach(Button *handle, ButtonEvent event);
    /*
    @brief 获取按键事件
    */
    ButtonEvent button_get_event(Button *handle);
    /*
    @brief 启动按键处理 or 停止按键处理
    */
    int button_start(Button *handle);
    void button_stop(Button *handle);
    /*
    @brief 按键滴答处理函数,在定时器中断或轮询中调用
    */
    void button_ticks(void);
#ifdef __cplusplus
}

具体实现文件

在这里面使用了内联函数用于事件等安全检查相关定义,但是只保留了展位符号,BUTTON_ENTER_CRITICAL()以及BUTTON_EXIT_CRITICAL(),在注释中已经展示相关的中断禁用的功能,这里实际中需要考虑抢占的问题。

<details>
<summary>展开代码</summary>
#include "multi_button.h"
#include <string.h>
/* Optional concurrency protection:
 * Define these to appropriate primitives for your platform, e.g.:
 *   #define BUTTON_ENTER_CRITICAL()  __disable_irq()
 *   #define BUTTON_EXIT_CRITICAL()   __enable_irq()
 *
 * If left undefined, they are no-ops. 不做任何操作,用于占位,具体配置相关的功能,如禁用中断
 */
#ifndef BUTTON_ENTER_CRITICAL
#define BUTTON_ENTER_CRITICAL() \
    do                          \
    {                           \
    } while (0)

#define BUTTON_EXIT_CRITICAL() \
    do                         \
    {                          \
    } while (0)

#endif
/* Make head_handle volatile to reduce likelihood of compiler reordering/
 * caching when accessed from different contexts (e.g. ISR and main thread).
 * Note: volatile alone is not sufficient for full synchronization; use the
 * critical section macros above when modifying the list from different
 * contexts.
 */
static volatile Button *head_handle = NULL; // Head of the linked list of buttons

/*Forward declarations*/
static void button_handler(Button *handle);
static inline uint8_t button_level(Button *handle);
/* Safe event invocation helper:
 * - Read the callback pointer once into a local variable, then call through it.
 * - This avoids the classic "check then call" race if another context clears
 *   a callback between the check and the call.
 * 事件触发,使用内联函数取代宏
 */
static inline void event_invoke(Button *handle, ButtonEvent ev)
{
    ButtonCallback cb = NULL;
    if (!handle || ev >= BTN_EVENT_COUNT)
        return;
    /* single read of pointer */
    cb = handle->cb[ev];
    if (cb)
        cb(handle);
}

基本函数定义

这部分函数,主要用于在状态机中检测状态、读取事件等,其本质功能是为了封装,以及安全检查,减少代码量。

<details>
<summary>展开代码</summary>
/**
 * @brief  Initialize the button struct handle
 * @param  handle: the button handle struct
 * @param  pin_level: read the HAL GPIO of the connected button level
 * @param  active_level: pressed GPIO level
 * @param  button_id: the button id
 * @retval None
 */
void button_init(Button *handle, uint8_t (*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id)
{
    if (!handle || !pin_level)
        return; // invalid parameter

    memset(handle, 0, sizeof(Button));
    handle->event = (uint8_t)BTN_NONE_PRESS;
    handle->hal_button_level = pin_level;
    /* initialize sampled level to opposite of active so first sample won't
     * be misinterpreted as an immediate press */
    handle->button_level = (uint8_t)!active_level;
    handle->active_level = active_level;
    handle->button_id = button_id;
    handle->state = BTN_STATE_IDLE;
}

/**
 * @brief  Attach the button event callback function
 * @param  handle: the button handle struct
 * @param  event: trigger event type
 * @param  cb: callback function
 * @retval None
 *
 * Note: If callbacks can be attached/detached from a different context
 * than where button_ticks() runs, callers should ensure synchronization.
 * This function uses critical section macros to reduce race with the ISR.
 */

void button_attach(Button *handle, ButtonEvent event, ButtonCallback cb)
{
    if (!handle || event >= BTN_EVENT_COUNT)
        return; // invalid parameter

    BUTTON_ENTER_CRITICAL();
    handle->cb[event] = cb;
    BUTTON_EXIT_CRITICAL();
}

/**
 * @brief  Detach the button event callback function
 * @param  handle: the button handle struct
 * @param  event: trigger event type
 * @retval None
 *
 * Note: If callbacks can be attached/detached from a different context
 * than where button_ticks() runs, callers should ensure synchronization.
 * This function uses critical section macros to reduce race with the ISR.
 */

void button_detach(Button *handle, ButtonEvent event)
{
    if (!handle || event >= BTN_EVENT_COUNT)
        return; // invalid parameter

    BUTTON_ENTER_CRITICAL();
    handle->cb[event] = NULL;
    BUTTON_EXIT_CRITICAL();
}

/**
 * @brief  Get the button event that happened
 * @param  handle: the button handle struct
 * @retval button event
 */

ButtonEvent button_get_event(Button *handle)
{
    if (!handle)
        return BTN_NONE_PRESS;

    return (ButtonEvent)(handle->event);
}

/**
 * @brief  Get the repeat count of button presses
 * @param  handle: the button handle struct
 * @retval repeat count
 */
uint8_t button_get_repeat_count(Button *handle)
{
    if (!handle)
        return 0;

    return handle->repeat;
}

/**
 * @brief  Reset button state to idle
 * @param  handle: the button handle struct
 * @retval None
 */

void button_reset(Button *handle)
{
    handle->state = BTN_STATE_IDLE;
    handle->debounce_cnt = 0;
    handle->ticks = 0;
    handle->repeat = 0;
    handle->event = (uint8_t)BTN_NONE_PRESS;
}

/**
 * @brief  Check if button is currently pressed
 * @param  handle: the button handle struct
 * @retval 0: pressed, 1: not pressed, -1: error
 */
int8_t button_is_pressed(Button *handle)
{
    if (!handle)
        return -1;

    return (handle->button_level == handle->active_level) ? 1 : 0;
}

/**
 * @brief  Read button level with inline optimization
 * @param  handle: the button handle struct
 * @retval button level
 */
static inline uint8_t button_level(Button *handle)
{
    if (!handle)
        return 0;

    return handle->hal_button_level(handle->button_id);
}

状态机轮询

在这段函数中,展示了按键消抖(延迟查询以及复查),当通过消抖后检测事件,后文整理了相关的状态轮转方式。

状态机的写法主要是利用switch语法完成,而对于具体的行为则是使用回调函数进行解耦。

<details>
<summary>展开代码</summary>
static void button_handler(Button *handle)
{
    uint8_t read_gpio_level = button_level(handle);
    /* Increment ticks counter when not in idle state.
     * ticks counts number of sampling intervals since entering non-idle.
     */
    if (handle->state > BTN_STATE_IDLE)
    {
        handle->ticks++;
    }

    /*Button debounce handling*/
    /* If level changed, continue reading new level for debounce*/
    if (read_gpio_level != handle->button_level)
    {
        /* Continue reading same new level for debounce */
        if (++(handle->debounce_cnt) >= DEBOUNCE_TICKS)
        {
            handle->button_level = read_gpio_level;
            handle->debounce_cnt = 0;
        }
    }
    else
    {
        /*level does not change, reset debounce counter*/
        handle->debounce_cnt = 0;
    }
    /*-----------------State machine-------------------*/
    switch (handle->state)
    {
    case BTN_STATE_IDLE:
        if (handle->button_level == handle->active_level)
        { /*invoke press event*/
            handle->event = (uint8_t)BTN_PRESS_DOWN;
            event_invoke(handle, BTN_PRESS_DOWN);
            /*button pressed*/
            handle->repeat = 1;
            handle->state = BTN_STATE_PRESS;
            handle->ticks = 0;
        }
        else
        {
            handle->event = (uint8_t)BTN_NONE_PRESS;
        }
        break;
    case BTN_STATE_PRESS:
        if (handle->button_level != handle->active_level)
        { /*Button released*/
            handle->event = (uint8_t)BTN_PRESS_UP;
            event_invoke(handle, BTN_PRESS_UP);
            handle->ticks = 0;
            handle->state = BTN_STATE_WAIT_RELEASE;
        }
        else if (handle->ticks >= LONG_TICKS)
        { /* Long press detected*/
            handle->event = (uint8_t)BTN_LONG_PRESS_START;
            event_invoke(handle, BTN_LONG_PRESS_START);
            handle->state = BTN_STATE_LONG_HOLD;
        }
        break;
    case BTN_STATE_WAIT_RELEASE:
        if (handle->button_level == handle->active_level)
        {
            /* Button pressed again before release timeout: treat as new press (multiple presses) */
            handle->event = (uint8_t)BTN_PRESS_DOWN;
            event_invoke(handle, BTN_PRESS_DOWN);
            if (handle->repeat < PRESS_PEPEAT_MAX_NUM)
            {
                handle->repeat++;
            }
            /* Notify about repeat press sequence */
            event_invoke(handle, BTN_PRESS_REPEAT);
            handle->ticks = 0;
            handle->state = BTN_STATE_REPEAT;
        }
        else if (handle->ticks > SHORT_TICKS)
        {
            /*timeout reached,determine click type*/
            if (handle->repeat == 1)
            {
                handle->event = (uint8_t)BTN_SINGLE_CLICK;
                event_invoke(handle, BTN_SINGLE_CLICK);
            }
            else if (handle->repeat == 2)
            {
                handle->event = (uint8_t)BTN_DOUBLE_CLICK;
                event_invoke(handle, BTN_DOUBLE_CLICK);
            }
            handle->state = BTN_STATE_IDLE;
        }
        break;
    case BTN_STATE_REPEAT:
        if (handle->button_level != handle->active_level)
        { /* Button released*/
            handle->event = (uint8_t)BTN_PRESS_UP;
            event_invoke(handle, BTN_PRESS_UP);
            if (handle->ticks < SHORT_TICKS)
            {
                /* Released before short press timeout: go back to wait for more presses */
                handle->ticks = 0;
                handle->state = BTN_STATE_WAIT_RELEASE;
            }
            else
            {
                /* Released after short press timeout: end of press sequence */
                handle->state = BTN_STATE_IDLE;
            }
        }
        else if (handle->ticks >= SHORT_TICKS)
        {
            handle->state = BTN_STATE_PRESS;
        }
        break;
    case BTN_STATE_LONG_HOLD:
        if (handle->button_level == handle->active_level)
        { /* Continue holding: repeated long-hold events may be produced.
           * Application should be prepared for frequent callbacks (every tick
           * interval). If that is undesirable, throttle in the application
           * or modify this code to generate hold events less frequently. */
            handle->event = (uint8_t)BTN_LONG_PRESS_HOLD;
            event_invoke(handle, BTN_LONG_PRESS_HOLD);
        }else{
            /* Button released*/
            handle->event = (uint8_t)BTN_PRESS_UP;
            event_invoke(handle, BTN_PRESS_UP);
            handle->state = BTN_STATE_IDLE;
        }
        break;
    default:
    /*invalid state*/
    handle->state = BTN_STATE_IDLE;
        break;
    }
}

代码功能介绍

    • 先读取当前 GPIO 电平(通过 handle->hal_button_level(handle->button_id))。
    • 去抖:如果采样与上次稳定值不同,则增加 debounce_cnt;当 debounce_cnt 达到 DEBOUNCE_TICKS 时才把 button_level 更新为新电平;否则若采样与当前稳定值相同则把 debounce_cnt 复位。
    • 计时:在非 IDLE 状态下,ticks++ 用以测量按下保持时间(用于短按/长按判断)。
    • 状态转换(FSM):
      • IDLE -> PRESS(检测到按下): 触发 BTN_PRESS_DOWN 回调,repeat=1,ticks=0。
      • PRESS -> RELEASE(检测到松开): 触发 BTN_PRESS_UP 回调,ticks=0。
      • PRESS -> LONG_HOLD(ticks 超过 LONG_TICKS): 触发 BTN_LONG_PRESS_START,进入 LONG_HOLD;在 LONG_HOLD 状态会持续产生 BTN_LONG_PRESS_HOLD 事件直到松开。
      • RELEASE -> REPEAT(在短时窗内再次按下): 触发 BTN_PRESS_DOWN 与 BTN_PRESS_REPEAT(更新 repeat 计数)。
      • RELEASE -> IDLE(超时未再按): 根据 repeat 触发 BTN_SINGLE_CLICK / BTN_DOUBLE_CLICK。
      • REPEAT 根据后续 ticks/松开决定回 RELEASE 或 IDLE 或回 PRESS。
  • 因此状态机是基于“周期采样 + 去抖 + 基于 ticks 的定时阈值判定”的设计。每个采样周期都会评估一次去抖和状态转移。

启用、停止、单次轮询函数封装

/**
 * @brief  Start the button work, add the handle into work list
 * @param  handle: target handle struct
 * @retval 0: succeed, -1: already exist, -2: invalid parameter
 *
 * This function uses critical section macros to protect the shared list.
 */
int button_start(Button *handle){
    if(!handle)
        return -2; // invalid parameter
        /*Protect scanning and insertion against concurrent modification*/
    BUTTON_ENTER_CRITICAL();
    Button *target= (Button *)head_handle;
    while(target){
        if(target == handle){
            /*Check if already in list*/
            BUTTON_EXIT_CRITICAL();
            return -1; // already exist
        }
        target = target->next;
    }
    /*insert at head*/
    handle->next = head_handle;
    head_handle = handle;
    BUTTON_EXIT_CRITICAL();
    return 0; // succeed
}

/**
 * @brief  Stop the button work, remove the handle from work list
 * @param  handle: target handle struct
 * @retval None
 *
 * Removal uses the pointer-to-pointer technique and is protected by the
 * critical section macros to avoid races with traversal in button_ticks().
 */

void button_stop(Button *handle)
{
    if (!handle)
        return; /* parameter validation */

    BUTTON_ENTER_CRITICAL();

    Button **curr;
    for (curr = (Button **)&head_handle; *curr;)
    {
        Button *entry = *curr;
        if (entry == handle)
        {
            *curr = entry->next;
            entry->next = NULL; /* clear next pointer */
            BUTTON_EXIT_CRITICAL();
            return;
        }
        else
        {
            curr = &entry->next;
        }
    }

    BUTTON_EXIT_CRITICAL();
}

/**
 * @brief  Background ticks, timer repeat invoking interval 5ms
 * @param  None
 * @retval None
 *
 * Typically called from a periodic timer (e.g. every 5ms). If called from
 * an ISR, keep callbacks short. If callbacks should run in thread context,
 * have callbacks enqueue events to a queue and handle them later in a task.
 */
void button_ticks(void)
{
    /* Snapshot the head to a local variable to reduce time spent with the
     * (possible) volatile head_handle and to avoid holding the critical
     * section for the whole traversal. If the list is modified concurrently,
     * traversal may see a slightly stale view but won't crash (provided
     * modifications use the same critical macros). */
    Button *target;
    /* read volatile once */
    target = (Button *)head_handle;

    for (; target; target = target->next)
    {
        button_handler(target);
    }
}

自己需要初始化的函数示例

上述代码主要是对核心功能的设置,但是对于按键初始化、回调函数的设置并没有完成,可以参考原作者的几个示例文件,不过作者的示例文件主要是模拟代码,这里给出一个实际的按键结合代码,可能有点小bug。

从前文代码看出,按键组之间是通过链表进行整合管理的,在代码中需要对于不同的按键设置对应的回调函数,如果比较复杂,建议进行实例的拆分。

/*
 * Simple demo wiring multi_button callbacks to hardware and printing events.
 *
 * Usage notes:
 * - This example assumes:
 *     * MX_GPIO_Init() has configured Key0_Pin / Key1_Pin (inputs, pull-up)
 *       and LED0_Pin / LED1_Pin (outputs) as in your gpio.c.
 *     * printf is routed to UART via __io_putchar (you provided it).
 *     * button_ticks() will be called from the main loop (or a task)
 *       at a regular interval (e.g. every 5 ms). Calling button_ticks()
 *       from an ISR means callbacks would run in ISR context; avoid heavy
 *       operations in callbacks if you do that.
 *
 * - The callbacks below print event messages and toggle LEDs. LONG_PRESS_HOLD
 *   is throttled so it doesn't spam the console (it is called frequently).
 */
#include "button_demo.h"
#include "main.h" /* includes HAL and project pin defs */
#include "multi_button.h"
#include "gpio.h"
#include <stdio.h>

/* Forward: HAL read function for buttons used by multi_button */
static uint8_t button_gpio_read(uint8_t id);

/* Buttons (one per physical key) */
static Button btn0;
static Button btn1;

/* Throttle counters for LONG_PRESS_HOLD to avoid flooding UART */
static uint16_t long_hold_counters[2] = {0, 0};

/* --------- Callbacks for button 0 (Key0) --------- */
static void btn0_press_down_cb(Button *btn)
{
    printf("[BTN0] PRESS DOWN\r\n");
    /* Turn LED0 on (assuming active low LED; adjust if active high) */
    HAL_GPIO_WritePin(GPIOE, LED0_Pin, GPIO_PIN_RESET);
}

static void btn0_press_up_cb(Button *btn)
{
    printf("[BTN0] PRESS UP\r\n");
    /* Turn LED0 off */
    HAL_GPIO_WritePin(GPIOE, LED0_Pin, GPIO_PIN_SET);
}

static void btn0_single_click_cb(Button *btn)
{
    printf("[BTN0] SINGLE CLICK\r\n");
    /* Toggle LED1 to show a click */
    HAL_GPIO_TogglePin(GPIOE, LED1_Pin);
}

static void btn0_double_click_cb(Button *btn)
{
    printf("[BTN0] DOUBLE CLICK\r\n");
    /* Toggle LED1 twice quickly (visual cue) */
    HAL_GPIO_TogglePin(GPIOE, LED1_Pin);
    HAL_Delay(80);
    HAL_GPIO_TogglePin(GPIOE, LED1_Pin);
}

static void btn0_press_repeat_cb(Button *btn)
{
    uint8_t rpt = button_get_repeat_count(btn);
    printf("[BTN0] REPEAT count=%u\r\n", (unsigned)rpt);
}

/* Called once when long press threshold is reached */
static void btn0_long_start_cb(Button *btn)
{
    printf("[BTN0] LONG PRESS START\r\n");
    /* Indicate long press by turning LED0 on solid */
    HAL_GPIO_WritePin(GPIOE, LED0_Pin, GPIO_PIN_RESET);
}

/* Called frequently while holding long; throttle to ~200ms intervals to avoid spam */
static void btn0_long_hold_cb(Button *btn)
{
    uint8_t id = btn->button_id;
    long_hold_counters[id]++;
    /* Assuming button_ticks tick is ~5ms, 200ms ~= 40 ticks */
    if ((long_hold_counters[id] % 40) == 0)
    {
        printf("[BTN0] LONG HOLD (periodic)\r\n");
        /* blink LED1 to indicate continued hold */
        HAL_GPIO_TogglePin(GPIOE, LED1_Pin);
    }
}

/* --------- Callbacks for button 1 (Key1) - similar but distinct messages --------- */
static void btn1_press_down_cb(Button *btn)
{
    printf("[BTN1] PRESS DOWN\r\n");
    HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_RESET);
}

static void btn1_press_up_cb(Button *btn)
{
    printf("[BTN1] PRESS UP\r\n");
    HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_SET);
}

static void btn1_single_click_cb(Button *btn)
{
    printf("[BTN1] SINGLE CLICK\r\n");
    HAL_GPIO_TogglePin(GPIOE, LED0_Pin);
}

static void btn1_double_click_cb(Button *btn)
{
    printf("[BTN1] DOUBLE CLICK\r\n");
    HAL_GPIO_TogglePin(GPIOE, LED0_Pin);
    HAL_Delay(80);
    HAL_GPIO_TogglePin(GPIOE, LED0_Pin);
}

static void btn1_press_repeat_cb(Button *btn)
{
    uint8_t rpt = button_get_repeat_count(btn);
    printf("[BTN1] REPEAT count=%u\r\n", (unsigned)rpt);
}

static void btn1_long_start_cb(Button *btn)
{
    printf("[BTN1] LONG PRESS START\r\n");
    HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_RESET);
}

static void btn1_long_hold_cb(Button *btn)
{
    uint8_t id = btn->button_id;
    long_hold_counters[id]++;
    if ((long_hold_counters[id] % 40) == 0)
    {
        printf("[BTN1] LONG HOLD (periodic)\r\n");
        HAL_GPIO_TogglePin(GPIOE, LED0_Pin);
    }
}

/* --------- Helper: map button id to HAL GPIO reading --------- */
static uint8_t button_gpio_read(uint8_t id)
{
    /* Your gpio.c config uses pull-ups. Assume KEY pressed = GPIO_PIN_RESET (active low).
       Return 1 for pressed, 0 for released (multi_button expects level values; we use 1 as active) */
    if (id == 0)
    {
        return (HAL_GPIO_ReadPin(GPIOE, Key0_Pin) == GPIO_PIN_RESET) ? 1 : 0;
    }
    else if (id == 1)
    {
        return (HAL_GPIO_ReadPin(GPIOE, Key1_Pin) == GPIO_PIN_RESET) ? 1 : 0;
    }
    return 0;
}

/* --------- Initialization function to wire up buttons (call this from main) --------- */
void ButtonDemo_Init(void)
{
    /* Initialize button structs: pin read function, active level (1 = pressed in our mapping), id */
    button_init(&btn0, button_gpio_read, 1, 0);
    button_init(&btn1, button_gpio_read, 1, 1);

    /* Attach callbacks for btn0 */
    button_attach(&btn0, BTN_PRESS_DOWN, btn0_press_down_cb);
    button_attach(&btn0, BTN_PRESS_UP, btn0_press_up_cb);
    button_attach(&btn0, BTN_SINGLE_CLICK, btn0_single_click_cb);
    button_attach(&btn0, BTN_DOUBLE_CLICK, btn0_double_click_cb);
    button_attach(&btn0, BTN_PRESS_REPEAT, btn0_press_repeat_cb);
    button_attach(&btn0, BTN_LONG_PRESS_START, btn0_long_start_cb);
    button_attach(&btn0, BTN_LONG_PRESS_HOLD, btn0_long_hold_cb);

    /* Attach callbacks for btn1 */
    button_attach(&btn1, BTN_PRESS_DOWN, btn1_press_down_cb);
    button_attach(&btn1, BTN_PRESS_UP, btn1_press_up_cb);
    button_attach(&btn1, BTN_SINGLE_CLICK, btn1_single_click_cb);
    button_attach(&btn1, BTN_DOUBLE_CLICK, btn1_double_click_cb);
    button_attach(&btn1, BTN_PRESS_REPEAT, btn1_press_repeat_cb);
    button_attach(&btn1, BTN_LONG_PRESS_START, btn1_long_start_cb);
    button_attach(&btn1, BTN_LONG_PRESS_HOLD, btn1_long_hold_cb);

    /* Add buttons to the driver's work list */
    button_start(&btn0);
    button_start(&btn1);

    printf("Button demo initialized (btn0 id=0, btn1 id=1)\r\n");
}

/* Example main loop usage:
 *
 * int main(void) {
 *     HAL_Init();
 *     SystemClock_Config();
 *     MX_GPIO_Init();
 *     MX_USART1_UART_Init(); // assuming printf -> USART1
 *     MX_TIM5_Init();        // if you use hardware timer for delays
 *
 *     ButtonDemo_Init();
 *
 *     while (1) {
 *         button_ticks();   // call at fixed period (e.g. every 5 ms)
 *         HAL_Delay(5);
 *     }
 * }
 *
 * If you move button_ticks() into an RTOS task, ButtonDemo_Init() still applies,
 * but ensure button_ticks() runs in task context or that callbacks only enqueue
 * events (use xQueueSendFromISR / notifications if button_ticks runs in ISR).
 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值