电子产业一站式赋能平台

PCB联盟网

搜索
查看: 146|回复: 0
收起左侧

如何在STM32嵌入式开发中优雅地处理按键(单击、双击、长按)?

[复制链接]

1001

主题

1001

帖子

8809

积分

高级会员

Rank: 5Rank: 5

积分
8809
发表于 2025-2-10 08:00:00 | 显示全部楼层 |阅读模式

34yh3pfzj3z64013144.gif

34yh3pfzj3z64013144.gif
1 a) Y; e: B( E6 g/ n
点击上方蓝色字体,关注我们
. ?0 E  z" R1 ~* c. N7 R4 `. t& D& n2 Q, h
通过这些方法,我们能够有效而优雅地处理按键事件,提升用户交互体验。, B# Q) D1 a" \
# O3 @; [& X9 D6 m+ M5 F- f. o
下面从硬件和软件两个层面给出详细的解决方案:: d1 D. L# N# O" s; t

8 r2 u+ y8 e/ H

f052hwpmlsc64013244.jpg

f052hwpmlsc64013244.jpg
& e1 y$ n6 ]0 f* d2 S( {

! N! @0 L( n4 P6 `$ j) ]4 i, @16 l1 M# l/ V5 V% `
按键去抖动! h3 m( U! e5 y6 r2 k. q3 o' A
按键在物理层面上具有机械抖动特性,即按下或松开时会产生多次的电平波动,导致微控制器读取到多个错误的状态变化。
  d( V6 |+ f  o- ?1 D+ g. M4 Y% G. a; N' |# {
解决方案通常有两种:
% d+ G% f- F+ j- E  W; J; P/ d3 Q; C( d! G  D
1.1 软件去抖动
. @0 M+ ?' Y9 f) h+ K$ _通过延时去抖动是最简单的方式,即在检测到按键状态变化时,等待一个小的时间间隔,再读取按键状态。* v8 P0 [8 b+ e- C: ]

: {% C3 u. y0 c  U# A$ Q常用的做法是:设置一个去抖动的时间窗口(比如20-50ms),在按键状态变化时,程序等待一段时间后再读取按键状态。% `* W2 Z/ A/ E

# q  o" u& l% q+ R比如,假设按键按下时,判断是否按键稳定,并防止过早检测到重复变化。, x. d  B- x& R  s3 I  l/ @
$ q# l! ^  ?! k$ c, Y
示例代码:( a3 Y* M1 d" f* |# q
' @6 S4 l/ G* n1 x# }
  • #define DEBOUNCE_TIME 50  // 去抖动时间(单位:ms)
    ! }# g( j; j7 d. [volatile uint32_t lastDebounceTime = 0;uint8_t lastButtonState = 0;
    - ~1 o" K1 Y: F  f! t; i/ ovoid ButtonHandler(void) {    uint8_t currentButtonState = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);    if (currentButtonState != lastButtonState) {        lastDebounceTime = HAL_GetTick();    }. v3 t7 t2 h" o8 d7 n
        if ((HAL_GetTick() - lastDebounceTime) > DEBOUNCE_TIME) {        if (currentButtonState != lastButtonState) {            lastButtonState = currentButtonState;            if (currentButtonState == GPIO_PIN_SET) {                // 处理按键按下逻辑            } else {                // 处理按键松开逻辑            }        }    }}1.2 硬件去抖动
    ! v- \5 `5 L- x2 }2 O  p( w硬件去抖动可以通过RC滤波器(电阻和电容)来实现,使用硬件设计的方式来滤除按键抖动信号,这种方法可以减少CPU负担。* j) z7 d/ {3 V' N; G" C5 Y( Y
    20 w" ^8 b/ N9 R4 u* p* ^/ X  h
    按键事件处理  |& E; V! Q7 _
    一旦解决了去抖动问题,接下来就是根据不同的按键模式(单击、双击、长按)来识别和响应按键事件。& d8 w8 N7 Z6 j

    % k, Y1 o  N+ s/ C2 p5 E2 X我们可以通过计时器和状态机来实现。
    # v& Z9 c/ `) \. Y$ R
    . T: E2 d3 \6 P4 J4 W; r2.1 单击检测* E8 m9 T# d; K
    单击是指按键被快速按下和松开。为了检测单击,通常我们通过检测按键按下事件,并在一定的时间内等待松开。. g. ^! x4 {/ B: |
    ( T: g9 s- t) Q* c! r* `. U
    如果按键按下和松开之间的时间小于某个阈值,我们认为是单击。
    ' P: S0 o, W: ]& |9 o% _7 p
    : r9 X/ J2 M6 }7 `2.2 双击检测
    % |: `" i, w) x% Q1 G双击是指按键被连续点击两次,通常要求两次按下和松开之间的时间小于某个阈值,且两次按下事件之间的时间间隔小于一定时间。
    . O- O6 ^! X( |: n& u" m6 E. _
    - z+ T  ]/ a8 e; Y2.3 长按检测* x; [" x$ q* @- ^" h1 e) l7 I
    长按是指按键保持按下超过某个阈值,通常用定时器来检测按下时间。7 l5 B# R1 e) W- }) p6 I
    4 u( O0 M3 t9 o& t3 n
    2.4 设计思路
    ' l2 z% D! v) i/ s我们通过一个简单的状态机来控制不同的按键模式,结合定时器来实现按键的时序逻辑。6 f- L1 i$ `* K8 h5 R. o# k4 _
    2 P0 |- J3 m8 B& h
    主要流程如下:
    3 x. G% {; y9 P' k
  • 使用一个定时器(如HAL定时器)来记录按键按下和松开的时间。
  • 设定超时时间来区分不同类型的按键事件。
  • 使用状态机或标志位来判断是单击、双击还是长按。0 }: ?$ ]' |! s% K3 e, N
    $ e. u+ E5 P+ y
    按键事件管理流程:按下按键时,记录当前时间(按下时间戳)。
    ; Z0 ?# B" C; m- N0 ~
    : S, n! E9 d2 w1 [- K' z& _松开按键时,计算按下与松开的时间差:
    . b! v+ t7 V8 Y
  • 如果时间差小于某个阈值(例如500ms),则是单击事件。
  • 如果按下与松开之间的时间差小于双击阈值(例如300ms),则判定为双击。
  • 如果按下时间超过某个阈值(例如1500ms),则判定为长按。
    1 Z8 Z& j( }- w# \* U

    7 L5 ^+ e5 C+ L" P0 f# S9 \双击检测需要检查两个按下事件之间的时间间隔是否小于一个设定的时间(例如300ms)。
      D& v* k9 N( R, ?" w4 `
      b! ], O: v; i" o4 O, o6 z示例代码设计:. t- e2 ]" I3 U  \
    / p; z) Y1 f' N9 r  m7 o
  • #define SINGLE_CLICK_TIME    300  // 单击最大时间间隔(ms)#define DOUBLE_CLICK_TIME    600  // 双击最大时间间隔(ms)#define LONG_PRESS_TIME      1500 // 长按时间(ms)  K8 F* l5 L  V
    typedef enum {    BUTTON_IDLE,    BUTTON_PRESSED,    BUTTON_RELEASED,    BUTTON_DOUBLE_CLICK_WAIT} ButtonState;
    7 s' J0 c) A; ]% R7 D* Vvolatile ButtonState buttonState = BUTTON_IDLE;volatile uint32_t buttonPressTime = 0;volatile uint32_t buttonReleaseTime = 0;/ y! w& ^# ^1 H4 l* n
    void ButtonHandler(void) {    uint8_t currentButtonState = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);7 h6 S6 s/ S9 `* Q) v3 M
        switch (buttonState) {        case BUTTON_IDLE:            if (currentButtonState == GPIO_PIN_RESET) {  // 按键按下                buttonPressTime = HAL_GetTick();                buttonState = BUTTON_PRESSED;            }            break;
    ; E+ K  S$ T# H1 f+ S        case BUTTON_PRESSED:            if (currentButtonState == GPIO_PIN_SET) {  // 按键松开                buttonReleaseTime = HAL_GetTick();                uint32_t pressDuration = buttonReleaseTime - buttonPressTime;
      K" [0 C2 h' j" I                if (pressDuration                     // 单击事件                    HandleSingleClick();                } else if (pressDuration >= LONG_PRESS_TIME) {                    // 长按事件                    HandleLongPress();                } else {                    // 进入双击等待状态                    buttonState = BUTTON_DOUBLE_CLICK_WAIT;                }            }            break;7 k; `  R7 N" a' f9 O
            case BUTTON_DOUBLE_CLICK_WAIT:            if (currentButtonState == GPIO_PIN_RESET) {                uint32_t currentTime = HAL_GetTick();                if (currentTime - buttonReleaseTime                     // 双击事件                    HandleDoubleClick();                    buttonState = BUTTON_IDLE;  // 处理完后回到初始状态                } else {                    buttonState = BUTTON_IDLE;  // 超过双击最大时间间隔,回到初始状态                }            }            break;    }}
    # y' {' W) S' R6 y" Nvoid HandleSingleClick() {    // 处理单击事件}: E6 ?3 N$ f+ O1 g# Q" e1 m
    void HandleDoubleClick() {    // 处理双击事件}
    , d3 \* `. \- p6 S# E0 R, h0 ~void HandleLongPress() {    // 处理长按事件}
    # ?! u7 N. R0 j% Y, r& _5 p

    ptjql1mvsts64013344.jpg

    ptjql1mvsts64013344.jpg
    , [+ p, G: s" f3 n* j

    21vy5sjtsfi64013444.gif

    21vy5sjtsfi64013444.gif
    0 K$ w/ Q* e. z8 ~1 a  W# [& l; a5 Z
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表