你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 GPIO—按键轮询

[复制链接]
STMCU小助手 发布时间:2022-4-28 20:53
12.1 关于按键
2 @) U+ u0 C( e$ r  H0 N, c前面控制LED灯是让GPIO输出高低电平,而获取按键则是读取GPIO电平,从而获知用户是否按下按键。0 C% L  k' a6 y: Q) |* D

0 n  @! w* w% v3 D% @" b0 q按键监测一般有两种:按键扫描和按键中断。按键扫描是间隔很短时间反复查询GPIO状态,从而得知是否有按键动作,这种方式代码简单,但比较耗资源。按键中断而是通过按键产生中断信号,从而实现按键的检测,这种方式需要使用到中断机制,需要对MCU了解深入一点,效果是最好的。' Q4 C. [' P4 C3 `

5 n! j7 D3 Z- q4 w2 v! X本节先介绍按键扫描,理解按键的基本原理,下一章再介绍按键中断,同时了解STM32F103的中断使用方法。
" B4 o  g6 q& B9 c! Z' h1 [: [* c/ P; q/ }1 ^
按键一般占用一个GPIO口,通过监测该GPIO的电平变化得知按键操作,典型的电路如图 12.1.1 所示。当所需按键比较多时,则可以采用矩阵按键减少GPIO的占用。矩阵按键需要通过编程扫描等方式实现对多个按键的监控,这里以最简单的独立按键为基础进行介绍。
% d6 g) y: z( J% s& C; o9 d
7 T( B* @6 R% n, I7 G% x3 L HCXQS2@B6Z%8(G)O$}H`{}A.png 6 @2 n; w6 a" t: v

- U0 H3 c' e4 F7 i图 12.1.1 按键与MCU的连接方式示意图- @& u6 D# Q( Z( A5 n
可以看到,在没有按下按键时,电源3.3V通过电阻连接到MCU的PA0脚上,此时MCU读取PA0的电平就是3.3V的高电平。在按键按下时,电源3.3V经过电阻,再经过按键连接到了地,此时PA0连接到接地的一端,读到的电平就是0V的低电平。由此,MCU就可用过读取对应引脚的电平值,得知按键的变化。
4 J# X5 W4 ~3 W. z: Y+ F
3 i9 p2 n+ ^" a( C2 d1 J常用的按键都是机械触点式按键,机械式按键在按下或释放的过程中,由于机械弹性作用的影响,会伴随机械抖动,如图 12.1.2 所示。, `" i+ m8 A. B$ q  u! u9 T
/ I( b! |# D8 _5 ^8 U  m+ R' b4 J3 R

9 L* }* c3 B7 V. B J}%W8`E]}Q(CJPAEPFG8FA2.png
  ~: L- W9 H  I. R5 J9 D1 w2 ^1 @3 i* A, V) y9 `1 I
图 12.1.2 机械按键抖动示意图8 t- n$ v  a" u$ K) t/ {4 v( Y

0 W! X$ o' `" G1 Q
* z0 @/ p3 ^7 s; `1 I抖动的时长与机械开关特性相关,一般为5-10ms。在这抖动过程中,会产生多次高低电平,导致被识别为多次按键操作。为了避免机械触点按键检测误判,必须消抖处理。按键消抖可以硬件上处理,即在按键旁并联电容,吸收抖动的电平。也可以软件处理,即通过延时,避开抖动。/ w, _/ i: s( w7 O/ q+ h7 k! V
: @7 x5 Z( M. @: {
由此,首先获取对应引脚的电平得知按键状态,再硬件或软件消除抖动。
6 [1 S( V' G- Z, ^+ y6 s6 y8 s5 D8 Y! F1 v
8 ~# ~( t- {% S, u
12.2 硬件设计
0 a3 V' R4 f. Z8 c2 ^6 q开发板上有两个按键,一个是复位按键(K1,红色),另一个是用户按键(K2,白色),这里只有用户按键能够编程控制。/ U0 N! f! U: [3 K
" x4 Y4 v7 Z/ u& q
如图 12.2.1 为开发板用户按键部分的原理图,电容C15用于硬件去抖,可以看作不存在。按键松开时,3V3经过上拉电阻R20到网络标号KEY,网络标号KEY另一端连接MCU的PA0,此时读取PA0电平为高电平;按键按下时,3V3经过上拉电阻R20,再通过按键接地,此时读取PA0电平为低电平。4 M) M! a# ~! ]. Z, y
$ D0 S% |) V& n
由此可知,按键按下,GPIO引脚电平变低,反之为高,按键所接GPIO为PA0。! @) G: C0 V* l2 i- G  B1 P% ]4 u+ P

, F3 j+ m/ K6 x FU[36R~I8ZR%]K%{3L@E2~E.png
' k* @9 f# w2 u
* N% i% [" A2 T: q8 `1 z4 f图 12.2.1 用户按键原理图: W" w3 M0 D! p  y

2 Y7 c9 j7 M4 T3 U9 I8 {/ H: k4 y4 Q+ o
: z- @( ?. V' ?$ K12.3 软件设计9 D7 K5 b# f+ m$ C/ h: n) _
1.3.1 软件设计思路
2 |0 [, B' V& ~  S实验目的:本实验通过轮询读方式取GPIO的输入电平判断按键是否按下,并操作LED。
. Y8 Q' c* Z8 A' s7 f; Q& o0 ^1 `, x" z) W) K
1) 按键初始化:GPIO端口时钟使能、GPIO引脚设置为输入(PA0);7 Z0 X  c+ ]  B! b2 c" u

/ U9 r# g9 O0 c, v2) 封装每个按键处理函数:读取按键GPIO状态,操作LED灯亮灭;  ?" W) x# o) i$ o  l* S0 R

% W$ H' z7 k+ w4 \3) 主函数轮询按键状态:一直检测是否有按键被按下;
3 ?& q( v. S) y' j  h: t6 e
3 g1 S8 P5 a) S+ I0 u本实验配套代码位于“5_程序源码\5_GPIO—按键轮询\”。) m; x* R( l# v3 K
' D( {6 u  v! `

& [/ c, ]' Y" u; T' Y4 A' B$ v, W1.3.2 软件设计讲解" }4 I3 d* U( e: z2 O
1) GPIO宏定义与接口宏定义
7 a( ?. @: b: Z6 Q) [: s' U7 N0 b
* [- F8 H$ Z* c代码段 12.3.1 引脚宏定义(driver_key.h)
! @  i% K5 l' e& g! H# N5 ?% Q1 W: g: L: r! E7 c4 ^
  1. /*********************
    ; w+ m+ W/ c$ J

  2. ' d% i( R1 w9 r5 }
  3. * 按键引脚状态定义# E- a: ^2 O+ g

  4. / L# G) {0 A5 e+ W$ @8 `% U
  5. **********************/
    0 m4 L) g2 d8 o; X7 W( T

  6. % ]1 g0 J2 F7 E0 [% y
  7. #define PUSH_DOWN                       GPIO_PIN_RESET' x5 w5 u! P) A! ^3 k: ?2 f
  8. / S  \+ `; }, U
  9. #define SPRING_UP                       GPIO_PIN_SET
    & J# o2 U& c3 J; N& x

  10. # c9 r8 e6 E3 M2 c

  11. / G' i+ g% F9 }" |3 ~
  12. + w  r/ T2 B* y: ~
  13. /*********************4 e' A  g& s; U) I) E! v  L7 K% T
  14. ' O2 c$ I# n9 F( S# o- g; u: z
  15. * 引脚宏定义/ u0 r. P6 ?5 B4 a+ ]0 t2 L9 T" p7 ?/ v

  16. & \7 p8 B: ]2 [6 P, u+ P8 {
  17. **********************/
    0 i. n% x& t/ n4 ?" W
  18. 3 q6 f. x6 o7 }& m6 L9 G$ a
  19. #define KEY_GPIO_PIN                 GPIO_PIN_09 ^& h9 s4 ?& `% k1 B4 k
  20. 1 q, O( [# L& X0 s! w8 U
  21. #define KEY_GPIO_PORT                GPIOA
      T$ X4 H- K9 b( Q& K1 I: P, B- \

  22. 5 B* `4 P- |; a; V
  23. #define KEY_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()
    ' T  a* e! q7 j9 j, J7 \: d

  24. # ^# }3 @* P+ ?0 J1 E

  25. 9 c6 U9 }( s6 Z: }& S
  26. 0 A8 H# D1 c+ w, t
  27. /*********************
    % C- Q/ `. ]' X0 f7 |& M
  28. 9 X! p* ~$ u) N) o( U
  29. * 函数宏定义7 W- V+ T. Y! ?9 v* {, {3 V
  30. 1 j% q2 j8 ?- ~5 _
  31. **********************/
    ! w. C- N1 {. c. Y) P2 ]8 U- `3 {! M

  32. 0 N5 L5 R6 V% z  l
  33. /*
    / {2 ~5 `3 p" g* x7 Z6 y- G

  34. ! Q6 {' }+ i: m; M( ?
  35. * 按键状态读取函数宏定义# [2 ]: j. \% U% I

  36. ) n" ^2 N) X8 ~- e1 W
  37. */  y( e* Y# N" i& U5 Y
  38. 5 p4 n& d& |; \
  39. #define KEY                  HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN)
复制代码

) t' X% u3 a$ j8 k8 I8 a/ r7 I1 y* V$ b0 i1 r" M# t
根据硬件设计选定的对应按键的引脚,将其宏定义命名为KEY,且对他们的读取函数进行重命名。其中“HAL_GPIO_ReadPin()”原型“GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)”,参数依次是:引脚组,引脚号,返回的是0(低电平)或1(高电平)。
8 R4 U8 r) Q% N9 c$ k& ]! C5 h7 G) J6 q3 X% b: W& [
  N9 t# s7 J' }, F( g% M' F# I  Y
2) GPIO初始化  }, M- H8 L1 Q' L: K# C

$ W8 w0 l1 ^; ^$ Q代码段 12.3.2 按键初始化(driver_key.c)9 y$ c; g# u0 u0 r% Y8 g# m7 D- |

# j* h  W9 m7 j( v0 L8 h) L
  1. /*
    9 M' h% l1 I& u7 R4 m( U
  2. 9 f/ G9 {) I* y$ X
  3. *  函数名:void KeyInit(void)
    7 T+ [, N- U7 x/ l. Q# x; u  O

  4. , F3 C0 F% O! P+ z% c0 U5 ^
  5. *  输入参数:无1 O: v. G. t& Z% x: {
  6. ' |4 Y+ ]! u2 H' E% n- S; _1 K
  7. *  输出参数:无
    ( G4 u# n% a$ t3 `9 |! {

  8. + w/ F* Q7 v' F' |4 ^5 G2 u/ {! v
  9. *  返回值:无' y* a' w- X5 Y! f5 i5 K+ X

  10. 8 l) V; g$ z! ~: x0 E
  11. *  函数作用:初始化按键的引脚,配置为输入, l% J( T; j! Y( _
  12. 3 G0 P* w) X0 R! W# f2 M/ G8 H
  13. */
    & `, u3 L! K- F8 u" v

  14. : z) X3 `  }& d% c
  15. void KeyInit(void)
    + R& y+ v. [( A/ p$ C
  16. , t, d# a3 K9 a5 P
  17. {' R! e" G- \, d/ I$ s: J( q
  18. ' k2 w* F9 z  O$ U! I* P
  19.     // 定义GPIO的结构体变量
    0 G. C  [" |; ^3 A7 r! i* f& _1 n. P7 y
  20. ) j- s6 v" D( H) m. g
  21.     GPIO_InitTypeDef GPIO_InitStruct = {0};
    6 i) T5 ]9 k  t6 K+ s2 g: E
  22. 2 q8 b7 b- l# t- F8 |# Q
  23.     // 使能按键的GPIO对应的时钟
    % ]: K4 I/ ^+ p1 F

  24. 9 G/ {5 L) y  x+ e
  25.     KEY_GPIO_CLK_EN();. l6 x  f) G- o9 k# m3 x
  26. ! T" ]3 f# |/ ?# z$ a
  27. ( r& u6 \# g/ r$ x& p* _) h6 c1 `

  28. 0 p5 Y- Y+ m9 T" h
  29.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;             // 设置为输入模式/ W0 u, \9 _; c/ Y% {5 X
  30. ( n  T+ Q4 I3 v" f6 h3 _
  31.     GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉
    - Z: _/ x7 Q5 Q. W2 s3 d8 q: Z, u
  32. , z& c8 Y1 I: J0 T( g. x3 m
  33.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快# Q; A5 Z4 r& }& i. |
  34. 6 V0 y+ W6 A  R0 ^
  35. & J: E/ h! K+ x. G5 V
  36.   b0 B3 D( l2 @% ^9 a* b( ~7 K
  37.     // 初始化按键引脚配置
    9 f, p* \; z4 C" J# K9 k' v
  38. 7 X: g, z- ]7 R' e
  39.     GPIO_InitStruct.Pin = KEY_GPIO_PIN;              // 选择按键的引脚
    7 y. Q- e# ~# q  f7 D% C* m
  40. 0 j8 Y8 D: m& o- v
  41.     HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);
    + A6 U" K9 M& i  M8 i9 T1 C. m8 S
  42. # H* m8 G/ A2 Q7 w4 [
  43. }
复制代码

' |8 Q( X: V  H7 @) {8 L* d5 U' R. v7 y使能时钟,将引脚初始化为上拉输入。$ F; ^4 A9 o5 I0 B2 }% w. ^, v

! [  q- |; E. J5 o9 N
- V7 T7 U/ o3 R' [7 G3) 按键读取函数0 Z$ a) [5 H6 M" N( M

: `9 I1 D$ `1 m0 Y4 d; [1 x; M$ O为了展示效果,编写代码查询按键是否按下,按下后切换LED灯亮灭状态。9 {, N6 P+ T" R" N7 P
: |4 N. ?* A7 U/ }* y
代码段 12.3.3 按键读取函数(driver_key.c)" f2 Z" t/ b* r! ]- w9 I

& Q" R% W/ u0 W  r
  1. /*- G4 @" m$ Z, F. S% x
  2. & r" Q5 J8 b" R' O" v
  3. *  函数名:void KeyPolling(void)0 \3 C" `+ K; v/ a+ |) O

  4. 5 u9 v# a* f" F6 c* E5 j3 b
  5. *  输入参数:无  `/ e! ?' U7 L4 M3 D
  6. ( h% K" t0 Z4 I1 O" R
  7. *  输出参数:无' [1 m2 f" y! ]1 T  m, B9 J
  8. $ z1 ]5 L0 v' z+ G
  9. *  返回值:无
    . [$ n, x, r: q- ^8 _, y
  10. 0 y0 w# |$ u% d. Z3 E. N
  11. *  函数作用:使用轮询方式查询按键是否按下,通过按下控制LED灯亮灭
    1 U8 d$ Y# |  S0 X. ?

  12. - g3 v& L/ C6 G, D6 h. y* U6 O! r
  13. */0 k4 h9 t# f0 V3 _" z  U
  14. ! ]$ p6 A  p% y; b
  15. static bool key_flag = false;/ v7 p% U# }: q# N

  16. + Q" {" ?$ W& \* Y2 R
  17. void KeyPolling(void)- {# m$ s* l; E# S* n
  18. + m4 s3 R0 y, i4 H$ K
  19. {3 @% y; l6 z' a2 y3 ?0 t3 g; n

  20. 1 f, A: @! q% h, l. K7 T  r4 B! }% K
  21.     if(KEY == PUSH_DOWN)          // 如果检测到按键被按下7 T) U* h$ G  p  I- Q
  22. " O* f& i/ _% s- j' q
  23.     {1 ?- F4 r- [2 @0 `
  24. ! D. R# }  c+ K; P# ~( `
  25.         HAL_Delay(8);             // 延时8ms防按键抖动) Z; [$ d: p" n- [3 x
  26. / [/ _" y: ^7 {3 {1 k( p
  27.         if(KEY == PUSH_DOWN)      // 如果防抖动后按键依然是处于被按下的状态,就认为按键被按下过
    ! R" H/ ~' Q/ W) a

  28. 5 i* {; @( w) O  N% [* p( p
  29.         {7 _% e8 f/ H, v$ A  S8 P
  30. / e6 z5 X5 S0 ?  K* b
  31.             key_flag = !key_flag; // 用一个标志位来判断按键被按下次数,按下一次灯亮,再按一次灯灭,如此反复4 x9 G" Q9 n  n1 H8 \* E, c( G
  32. ( ^, h# i1 L8 c/ A0 b6 u9 D$ q4 V
  33. BLED(key_flag?OFF:ON);
    7 p; u* [% ?- H" f' w  j7 Q
  34. . I5 f3 J5 H+ z- U$ x" r
  35.         }8 f7 Q* M* z! R" o) D5 R
  36. . f* q' V$ ]: \7 e6 K4 D2 Y* \0 t
  37.     }& W. A6 |6 d, K; q

  38. 1 P$ \/ y/ @$ a7 p& C9 q
  39. }
复制代码
* Q: C6 U& H, B# G
8行:定义了一个全局变量标志位“key_flag”,作为按键被按下的标志;
9 U; W/ d" Z" _4 _0 k1 d
& c0 L( {# Y, c8 a11行:获取该按键状态;) @. m! Z6 d/ I% |, d; O/ p2 d
9 h. W- O$ i1 P- a: O& T
13行:延时5-10ms,软件去抖;
8 }; J) k( U/ r. s8 r& U- U( y- v2 E5 Q& ]* q# B0 i' d
14行:再次获取该按键状态,此时依旧按下,说明是正常按键操作,非抖动;
1 \1 y1 o# D/ \# O6 J0 k" i( x/ i9 [3 d' [3 X+ y  ]% O$ ^/ ~; F
16行:将标志位置反,按键按一次置反一次(即0->1->0->1这样循环);
( p: T+ S: ?5 d/ }
0 N: T) y' y: s; ?17行:根据标志位“key_flag”的值,让LED灯亮或灭;3 T, Q0 Z0 q# B6 F

. F# o3 \( S' G% X, ?此时每按下一次按键键,蓝色LED灯将亮灭交替。
1 I  ~/ m7 }" k( y
+ h7 G& Q4 x1 r  t( h$ [* p
1 Q9 j* L/ A: ^' G3 L4) 主函数测试; Q- ^. W# Y$ }4 @3 p$ Y7 y% i
3 {3 Z. ^- }; K+ a( i+ i
代码段 12.3.4 主函数控制逻辑(main.c)
" p. q/ r' ?; E; Q8 {
' I0 P' E3 E& q
  1. // 初始化LED7 V9 u# Z7 o- l; p

  2. % Q2 Z7 f9 X+ |7 m0 }4 G
  3. LedGpioInit();
    : X) ~! \' C1 J  o
  4. # W+ s; m( u+ t* l% _1 S) s1 h
  5. // 初始化按键
    + T. b+ H  h5 y5 X3 S

  6. 3 `, r$ h! |+ i/ e$ z
  7. KeyInit();
    / ]( k" [+ X! [+ C: e
  8. " ~& V# i: M% @$ _
  9. " M: L. ?6 r& t& {' \: |* z

  10. 7 J* \% X& L; h1 @" n  B( d
  11. while(1)) N4 X! v3 {! U7 J

  12. ' t$ j' G( Z0 k5 M
  13. {5 n6 m0 U& z) X3 r2 V0 C" K6 R2 k

  14. + u6 l: \/ \  d% z
  15.     // 轮询按键键
      f, i- P# `) n4 K- z' ]+ F" u
  16. 4 \9 c: N8 y* l
  17.     KeyPolling();0 W2 I/ \" n9 _

  18. 9 M& _+ _/ P8 j4 ~
  19. }
复制代码
( b2 q) r% d* @8 I: m; y/ v3 n
1~4行:初始化LED灯和按键;" ?  i  P, O  _, W8 t% I& H

9 w# n+ z0 j1 ~2 |  B6~10行:一直循环查询每个按键当前状态,从而判断对应按键是否按下;4 I  i! i7 y- }$ S
% p0 i& _; X: b, I9 S
12.4 实验效果
0 ^9 y$ l) M* v" H4 h本实验对应配套资料的“5_程序源码\5_GPIO—按键轮询\”。打开工程后,编译,下载。按下按键,用户LED灯亮/灭。
+ d; w' f! g% H7 J# [% k/ L" p$ Q( j% F3 T

8 k- C* @8 ]- W$ e7 \
赞 收藏 评论0 发布时间:2022-4-28 20:53

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版