前言
( `/ a; \ k! _: R编程开发环境:STM32CubeIDE
) w9 Z% n2 r# g9 Q 按键的单击、双击、长按等在MCU编程中是比较常见且常用的事件,本文章基于STM32来实现,具体思路用在其他MCU也是如此。
: l; J& m/ w: P+ g# v, a- h# D& @- W: b" p/ ~. l1 J1 G
具体思路
" ^$ e: R4 |6 I- K初始化一个全局标记
( @, d. ^$ \5 G! M$ A1 H* H+ \按键中断事件发生后置位标记
9 U9 A' J, j/ N& X( hwhile死循环中一直检测这个标记,如果被置位那么进行消抖,然后再次检测连接KEY的IO是否处于按下状态,如是则认为本次按键有效
8 j! |% @- K1 z2 r6 d' Z第一次按键事件有效后,启动定时器定时300ms,在此定时期间内如果有二次按下那么就是双击,如果没有按下,等到300ms定时时间到后读取IO电平,如果处于松开状态那么本次就是单击事件,如果还是按下状态那么就再次启动700定时器,700ms过后再次读取IO电平是否处于按下状态,如是那么就是长按。
V4 h( Z% ?1 g' O, b工程配置/ ~7 r" Z8 S0 |; I/ @! i7 @
我是使用STM32CUBEMX配置生成的工程,配置按键:
( N! Y. y- ~, B9 x- Z$ |; Y2 e/ s; o1 c6 M' W
6 s. F( j8 P+ }" _/ C3 o2 m
& G: L2 Y. @9 m% g! RSTM32的定时器有向上、向下计数模式,向上计数就是从0计数到N然后触发中断,向下计数模式就是倒计时到0,由于我们需要二次启动定时器来检测长按,所以再次配置为向下计数模式,分频为7199,计数3000就是定时300ms:( H+ r( o( T" B$ X1 L& c
0 t4 Q0 Y5 ] O2 w z/ w
' I' W3 c# t: _/ }
% t0 @! v' A5 n# s' h8 q6 m中断配置:1 `( Y; u% S) _4 L- M/ F/ W0 _
) z1 G. D. }4 B$ s
' g5 Q5 @) ]* l3 Y1 E; G: ^# {; C8 ?- E: w% |% q* K1 _6 W) t+ o2 c
代码实现+ i7 P3 H& ~; b8 ^
$ Q8 Z# i: i* G9 {- /*
( u+ f# U; V5 Y2 Y: H+ v" g" e8 R - * key_state位说明:, P9 C7 _8 V& ?; B* _
- *" P; e% J- N: Q1 t
- * bit[15]:按键事件完成标志位: p0 T) G& o5 v: S3 L" N( i
- * bit[14]:按键中断触发标志位
0 p. L) \& X E+ { D1 q, Q' m - * bit[13]:长按事件触发需要启动第二次定时器标志位! S8 d9 D$ j J% L- U
- * bit[12~0]: 按键计数,=1:单击 =2:双击 =3:长按# y. g% i* T- R/ G' p; p# `3 `
- */% C8 g# U: }9 a* c
- __IO uint16_t key_state = 0;
$ D9 ~7 q* {6 e+ ? - # ~. K$ h: z( x
- int __io_putchar(int ch)
- f6 s/ p6 [7 d3 U0 o - {4 [3 t8 g2 C @( \( u* [
- HAL_UART_Transmit(&huart1, (uint8_t*) &ch, 1, 100);
! F- ^8 }# N6 s% i9 q0 d - return ch;; l+ a% i$ ]' C1 N% G" _. p0 ^2 I$ Z
- }7 [: H' s! D) |: r
7 `. X) J4 U5 F7 t# y- void TIM4_Stop(void)) t+ n7 E ^& C2 j( D, T
- {; l8 [7 f, M$ d
- HAL_TIM_Base_Stop_IT(&htim4);1 T4 o& S9 ?% R3 f* z/ u! l
- }
5 t1 Z- T4 T6 O# S: _5 F) [1 O - $ R1 p2 o. K0 ^1 L6 m" ~+ o+ J9 x
- void TIM4_Start(uint16_t timeout)
! }, z7 H3 F; R1 v% y - {) [6 B9 I, h A f+ Q
- __HAL_TIM_SET_COUNTER(&htim4, timeout);7 ]& v: z2 `, L9 j; H1 C. i& t
- HAL_TIM_Base_Start_IT(&htim4);, t$ H% x) a9 b! C- z
- }
+ F% z, X- S6 K% v7 | - 3 V) S7 S- T2 w4 R' W! r
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)9 x2 D4 |5 v+ G, D5 ?% K* G
- {- {; h# ^, k5 N
- if(htim->Instance == htim4.Instance)
. f/ u# _2 W! W9 M) Q( j W$ @ - {- }( q; G! \; H/ W. J
- TIM4_Stop();& E( c' r+ m* l, A ] b' G, j
- if(key_state & 0X2000) // 如果是700ms的超时
& n3 _* z# \. e, B8 W7 F7 @ - {
' e: t# g9 F) s! j* l: z* p - // 再次检测按键如果是按下状态,那么就是长按事件,否则事件无效
# H1 n0 M; F5 H2 n! h - if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET)
3 R# p- c7 V, L7 s9 {; D - {( I! N4 k" T, a# y+ y7 U
- key_state = 0X8003;
: u2 M& q" J# k K - }else* p5 g+ O4 X! ~3 N# W, s
- {" r W$ R! O+ u1 W# Z, S
- key_state = 0X8000;4 k) H! v; c0 K0 Z! `. w" z
- }
5 o% ]) L w& E( `3 C, w' H0 ? - }9 P, ?8 ?' B9 j: Y* O& l3 j
- else // 如果是300ms的超时
- A1 l4 W2 f9 f* r% \ - {
$ w. @4 t6 I2 E% _ q( y3 B) h. n - // 300ms时间到再次检测按键状态如果仍处于按下状态,那么就启动700ms的超时,1 I/ q- G$ C7 c
- // 700ms时间到后再次检测按键如果是按下状态,那么就是长按事件了。
) t7 `/ A1 H- }) B6 U& W - if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) // 按键处于按下状态
4 y0 w* ]7 R; q; m" w2 S& {& @ - {
( z- b8 O$ _+ b1 Q2 E7 D/ c- c) O5 c6 \ - key_state |= 0X2000; // 标记启动了700ms的超时定时
7 J- @1 c/ }. c - TIM4_Start(7000); // 启动700ms超时
+ b Y$ [1 z7 T4 n- [% e- F - }, ^0 U* P$ n2 z2 L9 |
- else // 按键处于松开状态
5 a6 f [# l) s; B0 q# y - {/ l- ^1 Q9 Y& G3 F) N& z
- if((key_state & 0X1FFF) == 1) // 按键按下计数=1,是单击事件
) w0 _ Z3 t2 l! V0 d: G' h - {
# p8 m0 k9 l4 g% h! v- ~ - key_state = 0X8001;- N, t7 s+ F4 Z
- }else if((key_state & 0X1FFF) >= 2) // 按键按下计数>=2,是双击事件$ H: A/ j& G1 S: F9 q* @5 m
- {
( k u7 {" c) _' `9 r9 F - key_state = 0X8002;+ u6 N7 b. ~ O% m5 E
- }else // 按键按下计数为其他值,本次按键事件无效% U4 W4 \8 k+ H: g% n
- {
% v# C i5 V- H K) J# J! _/ W4 y - key_state = 0;
* y C" W6 G) P+ K - }) W5 _0 r2 @+ w2 G/ H/ {
- }& [ d9 F! w; t9 Z# J3 c( W% v# g
- }* w, x3 f! a6 X$ q( e. Q, S5 B
- }
/ m& z# i! i, I e - }8 R4 a$ ?! W0 q
/ F" C6 C: R2 n2 H+ ?- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)9 a7 u) M+ P, K2 k8 H( j T3 Y
- {
4 n) F9 @0 ?: t) ^) m9 J+ U - switch(GPIO_Pin)' F& p) i/ q4 O$ x6 X T
- {
2 z# p5 i* v0 U( G% I - case KEY_Pin:% k0 `% j6 y* `1 n& E
- if((key_state & 0X8000) == 0)
) `% l9 O+ x q' P - {1 T$ m, U, ] f+ e0 v0 E% W* \: K. C' K
- key_state |= 0X4000; // 标记按键中断触发,在while死循环中进行消抖和的启动定时器
" _9 c3 z2 A/ m4 |7 |. h; Z1 B - }
d2 d4 m6 f- C, Z" z& ? - break;8 N- w* f6 b1 O7 \- _4 u
- default:break;3 u7 k$ Y# D( E
- }' S! W+ \" n C* s) r) s
- }* a. t5 P: e8 K7 t
" _: ]0 J: q2 K5 e8 ` d% o- int main(void)
9 i5 D, H6 ^( W - {2 W8 _" b1 L, c9 }$ [+ }
- HAL_Init();. @' v; E; f; K' S4 ~: _7 \7 a
- SystemClock_Config();
6 ]! ?9 L; b K1 T" A - MX_GPIO_Init();* n" l' o( [, C; w3 I
- MX_USART1_UART_Init();
/ M5 a7 D/ b/ L& y. |5 X8 C3 a7 e - MX_TIM4_Init();
5 O7 [, r7 ]( r! h/ P - __HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE); // STM32的定时器初始化完毕之后如果不清理中断标记为会有直接进入中断的问题7 T1 p' }2 o. n9 x+ H
- * \2 W+ z6 R# K2 L2 X
- while (1)
6 d. W% F: ?3 t/ f - {0 w6 ?% Y1 E4 }3 _; }
- if(key_state & 0X4000) // 按键中断触发* h7 `" F; G6 h# R; Q
- {
# t) N$ n* A$ \+ U1 A- L( g - key_state &= ~0x4000; // 清除事件% m. E, d6 R8 a" `! s0 a) O" [+ ~* P
- HAL_Delay(60); // 消抖延时
N' ]$ E m# ]! f* @) W* `
, m1 a# w. ^$ r7 \8 j3 ?- // 消抖完毕之后再次检测按键是否为按下状态,如是则本次按键有效,否则视为无效并清除标识。' p$ s% g7 ~3 a9 H4 F2 L
- if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET), V3 z" F4 W3 C z0 f7 C
- {
4 @; }0 U/ u! N- c9 ^& [ - if((key_state & 0X1FFF) == 0) // 首次按下时启动300ms超时定时器- M. ^3 r3 `5 h: g* A
- {
. ^- s$ {1 ~5 x8 o - TIM4_Start(3000);
; _+ D) B: p0 K3 S: t) b) E2 D - }( {' t0 y8 \/ j' K4 b
- key_state++; // 按键按下计数增加
# {1 v3 X- J8 N- o* O3 e& _7 a2 W2 x - }% g3 U0 u; ~6 R1 \# ^. e& u) n
- }+ r9 O6 {5 T# T' L2 o2 z# W v. {
- 2 O; S1 L' |7 K! P/ _3 j) Y( o
- if(key_state & 0X8000) // 按键事件发成了
7 y8 A3 C; ~6 n$ H - {
% B7 a E0 b6 q9 S. `: d1 m& f - switch(key_state)
$ |; i4 {% B( Y! d; R% m - {
6 V7 k6 w# I! R, w. G - case 0X8001:3 w& R% k/ s. X) k4 X; U' Y
- printf("single\r\n");
2 w$ V- i7 N' ^* A9 u: i$ R; V - break;$ `+ X/ J/ K! d+ g5 W( c
- case 0X8002:
2 {1 s+ s% Z0 _3 c5 e W - printf("double\r\n");- M/ Q% z* T% x5 `0 n1 B
- break;0 L# k. t; t# x# B7 Y$ e e' f
- case 0X8003:
, L1 @. S6 |3 d; G - printf("hold\r\n");
$ c! v/ ^( C7 F; Y( @( P& E - break;
7 u5 A, b1 l8 m9 n1 y- z9 e - default:break;6 @& u; t" l/ {) v0 X# | s
- }* A! `% q, e1 k1 d
- key_state = 0; // 清除事件/ Z6 C+ R! B* }8 v
- }! |/ J, P5 q \: ?0 l
- }
2 L B+ |0 |/ @7 m5 G% A9 i - }7 r( ~8 M: v: a. |5 C4 s! k; `" ^
复制代码
- p4 c/ k; I$ y6 h& Q- X; K实验效果7 \# y! @# o5 L) ?2 D2 e+ |
按键单击、双击、长按实验效果如下:3 Q2 n& w! \0 C* {2 w* g0 ^; O P
: N. X; R, P; c$ K7 `
3 _$ m4 q6 t+ w" D
; ^# l* W( Z+ F+ v# u
9 r. X& `! {& b' i. [9 Q
|