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

STM32经验分享 第12章 GPIO—按键轮询

[复制链接]
STMCU小助手 发布时间:2022-8-30 18:38
12.1 关于按键
* A8 L7 B$ W( A1 \1 o前面控制LED灯是让GPIO输出高低电平,而获取按键则是读取GPIO电平,从而获知用户是否按下按键。# Z* g* c1 N4 y/ ?0 l. O/ a! L  H
/ P0 A' U; s  i" p$ X. y# B
按键监测一般有两种:按键扫描和按键中断。按键扫描是间隔很短时间反复查询GPIO状态,从而得知是否有按键动作,这种方式代码简单,但比较耗资源。按键中断而是通过按键产生中断信号,从而实现按键的检测,这种方式需要使用到中断机制,需要对MCU了解深入一点,效果是最好的。8 ~) |! U9 m3 o/ k

! q# s) I4 A9 ?; u+ `$ w# N2 k本节先介绍按键扫描,理解按键的基本原理,下一章再介绍按键中断,同时了解STM32F103的中断使用方法。
6 b, o/ Z0 F4 m; s% }9 B4 `: \5 t: v: ~" @* V: s- W
按键一般占用一个GPIO口,通过监测该GPIO的电平变化得知按键操作,典型的电路如图 12.1.1 所示。当所需按键比较多时,则可以采用矩阵按键减少GPIO的占用。矩阵按键需要通过编程扫描等方式实现对多个按键的监控,这里以最简单的独立按键为基础进行介绍。# w; H9 \; u5 v+ Y6 H
I@{MSO}8CO7W%9)4I)_BM7G.png
9 ^5 h3 T6 h1 Y2 U5 L

7 z: {. b9 X5 Z' z/ X  ~0 u: Y
图 12.1.1 按键与MCU的连接方式示意图

  z/ l/ U+ Z* Z  |可以看到,在没有按下按键时,电源3.3V通过电阻连接到MCU的PA0脚上,此时MCU读取PA0的电平就是3.3V的高电平。在按键按下时,电源3.3V经过电阻,再经过按键连接到了地,此时PA0连接到接地的一端,读到的电平就是0V的低电平。由此,MCU就可用过读取对应引脚的电平值,得知按键的变化。
) |' [# Y7 U: v, C5 b" U8 A" C- e; i5 M5 O# n2 z, |
常用的按键都是机械触点式按键,机械式按键在按下或释放的过程中,由于机械弹性作用的影响,会伴随机械抖动,如图 12.1.2 所示。# t8 l3 \2 U2 B. @) j8 L" S& J

+ x$ i' q! @7 T( o% V
2FP62$]}XCNRID~FD)JRJ7V.png

0 m+ ]. `: Z' w# C7 K% _, d8 E! n) F5 @( N' y6 t0 m, F
图 12.1.2 机械按键抖动示意图

" L1 z1 v0 @7 a( d! A" h3 S4 G, l9 M: F7 {  x8 O
  S) v/ i% ?% S; U% ^1 s
抖动的时长与机械开关特性相关,一般为5-10ms。在这抖动过程中,会产生多次高低电平,导致被识别为多次按键操作。为了避免机械触点按键检测误判,必须消抖处理。按键消抖可以硬件上处理,即在按键旁并联电容,吸收抖动的电平。也可以软件处理,即通过延时,避开抖动。
8 ?5 T7 \9 o  Y% G; a% L$ B
$ d. N: [! C0 A3 G  e6 Z' \4 _由此,首先获取对应引脚的电平得知按键状态,再硬件或软件消除抖动。8 m: T5 H+ U% i0 L$ o# U- \
- ~+ l: c7 P3 q' b) K; M, N

: R5 `% J, I4 L1 l; f) A0 u& o# O/ j0 i, d7 ~- V1 o0 z
12.2 硬件设计
( U8 X" p! e. I) M- e" m* L
开发板上有两个按键,一个是复位按键(K1,红色),另一个是用户按键(K2,白色),这里只有用户按键能够编程控制。
3 @" l& q. z/ N7 Q/ K4 e( D
9 O3 x4 ?* n3 ~3 u- L如图 12.2.1 为开发板用户按键部分的原理图,电容C15用于硬件去抖,可以看作不存在。按键松开时,3V3经过上拉电阻R20到网络标号KEY,网络标号KEY另一端连接MCU的PA0,此时读取PA0电平为高电平;按键按下时,3V3经过上拉电阻R20,再通过按键接地,此时读取PA0电平为低电平。
" A) t; @1 o( G- E0 C
& s  e; m! C3 y5 z由此可知,按键按下,GPIO引脚电平变低,反之为高,按键所接GPIO为PA0。6 A# L: L8 Y3 o. v
4 i# X7 n$ I) ?, L8 D
`AAI{R(S$W{RV@1C(0_M[2V.png

( H! t) a& ~+ e! `( _9 _
" o$ a: e7 L8 a. [5 v, l4 [
图 12.2.1 用户按键原理图

8 s' B, G0 I* w
+ q" F- W; r. H. g1 C# X. q! P4 p* G- \  Y# j
12.3 软件设计

8 V1 S4 i& c3 J( T+ v4 Y) F1.3.1 软件设计思路, x! b2 a  @& \6 r
实验目的:本实验通过轮询读方式取GPIO的输入电平判断按键是否按下,并操作LED。- l6 q/ k2 u! ?
8 C  U9 I' I. o2 W. ]/ P( Z7 ?
1) 按键初始化:GPIO端口时钟使能、GPIO引脚设置为输入(PA0);
4 E& s0 A8 i+ B. X9 g0 p; M4 U+ w! F( P
2) 封装每个按键处理函数:读取按键GPIO状态,操作LED灯亮灭;
# H" r4 [' K$ L" I/ ^/ n3 D$ N8 \! h! ~
3) 主函数轮询按键状态:一直检测是否有按键被按下;/ n' {5 w# i5 {9 r/ i7 O- R+ n% a
) E; H# p1 Z/ u; r
本实验配套代码位于“5_程序源码\5_GPIO—按键轮询\”。. X1 W2 U  Q! {6 N
. \  l/ D* G% f8 Z2 P6 C

5 T. [/ G8 H' f( _7 n* U2 Q' @3 ~! A$ n5 {& P
1.3.2 软件设计讲解
4 w$ ~6 M+ F, _5 g' R( x/ s1) GPIO宏定义与接口宏定义
' A% v: |2 L, J" r' N" j
6 Y7 j: W% ~; n) |% ^; V) g- O代码段 12.3.1 引脚宏定义(driver_key.h)
6 {5 P5 p% [( J% v( H8 X
' q2 ], O& {6 U
  1. /*********************
    6 T' |" o6 e) n7 ]2 n

  2. 6 Z6 \2 J2 J$ w  ^+ z  o4 _
  3. * 按键引脚状态定义
      q% f% n. g# ?
  4. 1 h# x4 L& U8 @% ~6 U' ?( _. f* m
  5. **********************/
    7 O0 D; N# e. j( L% ^
  6. 9 p" M' G" a/ }  A, o
  7. #define PUSH_DOWN                       GPIO_PIN_RESET4 l  h( ?5 j- b

  8. . _# _& m; x8 c) h# L% ^9 L
  9. #define SPRING_UP                       GPIO_PIN_SET
    7 J4 }6 S9 n  f) p  Z& @
  10. 7 V% Z/ A) Y" e/ z, ]0 C
  11. 3 M+ s  T6 ^$ ]$ a: W8 B. N  v+ O
  12. / w2 C& l" E9 j% P0 m2 n: @
  13. /*********************
    1 m# T: q5 L+ A3 B/ ?1 D. C3 [) o
  14. 8 K/ n, N% @4 R# S! f- _- W
  15. * 引脚宏定义
    9 Y4 t6 ~! y9 m8 ~% G3 [1 _
  16. ' q) ]+ e+ b0 Z( C) t- V
  17. **********************/- b2 X  K8 ]5 Q+ }) ~
  18. ) k3 O" k- h3 I$ d
  19. #define KEY_GPIO_PIN                 GPIO_PIN_0
    & d6 \" e+ e7 e2 S0 i& H; w

  20. 4 P5 Y  `; T9 f2 r1 u9 ~+ b
  21. #define KEY_GPIO_PORT                GPIOA) j% K6 y3 P- M
  22. - a$ q1 Q% Z! g6 b. R( [
  23. #define KEY_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()$ f" {7 m( {" ?& J- ]

  24. 6 g% a! B& E6 f1 J7 Q. _+ }8 k2 a' f
  25. 7 N$ _1 V; v$ ^" n: L0 {
  26. 8 @( J( j; c- J. k! _
  27. /********************** X; m, V- p! T9 C; [% [" J

  28. # \9 Y) Y9 f6 Q% i: T5 R; J/ Q& ?
  29. * 函数宏定义
    7 o6 D" W0 x0 j' H3 d2 C: {# q
  30. 4 n+ m$ [5 k1 O6 W
  31. **********************/
      u' ?" d+ _$ W8 ]4 K3 _7 P) j3 L6 `
  32. 0 M$ A+ C  L0 O6 d* K
  33. /*
    . s! V. V7 s/ j2 z8 X7 D& X$ ]

  34. 1 S/ P7 z" P$ k& T( m$ O2 j
  35. * 按键状态读取函数宏定义3 W1 r) Q$ U# ^! d7 I" T# \

  36. % f6 g) {; ^" G+ z$ I( }
  37. */0 J! f# W/ r' R3 C7 q
  38. 0 o4 u- E$ z# n* y& w) a. u
  39. #define KEY                  HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN)
    , n9 _: e7 g- b2 ]2 J3 K, U
复制代码
& d# [8 L$ P' O$ ^
( I6 w: z% v( b; \
根据硬件设计选定的对应按键的引脚,将其宏定义命名为KEY,且对他们的读取函数进行重命名。其中“HAL_GPIO_ReadPin()”原型“GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)”,参数依次是:引脚组,引脚号,返回的是0(低电平)或1(高电平)。
! D2 ~" O" B% k
' W) Z6 R& z+ E8 ?% R) c- w! r% |! |2 E, I2 Y2 ]( I
" j9 F2 e3 d) t- y
2) GPIO初始化
& ]! Q, [1 J/ j0 I' g: d/ u
/ H' F& b) c0 n# I1 O代码段 12.3.2 按键初始化(driver_key.c)
7 t( d& A7 R* ~$ b7 H
- R# B% o# D  w2 a
  1. /*; c/ J0 S( |$ r& E5 _8 S" }
  2. $ Y5 l2 P2 O1 K% W2 s
  3. *  函数名:void KeyInit(void)
    & r# P. B5 j- v. F
  4. 5 K) v% n7 _* f1 I
  5. *  输入参数:无& t: P; o& M* ]: x$ p1 }0 Q* r4 A
  6. 9 S. @/ C8 f* }5 }; K/ J4 N. ]
  7. *  输出参数:无
    # Q& D  g( h8 m$ G* }* y, `3 N

  8. 2 j/ A* I4 n& I% l3 P
  9. *  返回值:无$ u* r5 E& V) E: S) W* r- f

  10. # q3 P6 x, l' A2 T
  11. *  函数作用:初始化按键的引脚,配置为输入+ Z# Q& p: a$ y1 q

  12. - v  R8 X! X# a5 A: b$ _, Z$ |/ K
  13. */
    $ \7 ^+ [8 t5 {; R( n. Y

  14. & y, D0 u- s9 {0 J# x5 X6 J% ]0 D
  15. void KeyInit(void)) @2 z/ l* P8 @" q  m0 h# c) {- F

  16. 9 `! P+ g! c# s* r& _
  17. {
    / W+ K5 t/ w- J
  18. : C' q5 r1 c; k* z: T, E: t
  19.     // 定义GPIO的结构体变量) H" Y- P: }7 Z8 b; R( m$ Q

  20. ; L/ l" X8 m1 ?) k
  21.     GPIO_InitTypeDef GPIO_InitStruct = {0};
    $ d4 z3 \1 H1 V  @# l$ v$ \. P7 F

  22. & M  W$ y, w: [, n0 B9 p
  23.     // 使能按键的GPIO对应的时钟0 _# J1 Y! M& W9 e- n) Y& B+ M% y! Z
  24. : u8 E/ D  I+ v0 q9 D# [& {
  25.     KEY_GPIO_CLK_EN();
    , L9 J' Y2 ]1 x# B2 b$ |# q5 n

  26. 7 w5 H% d) c* l7 O  M+ ?
  27. 6 s1 h+ R4 j! z5 X9 |1 ~6 J
  28. / i9 r9 R5 ]6 U( F1 O( _
  29.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;             // 设置为输入模式
      n6 Y  z7 C9 C% p, L+ ?6 [
  30. 0 y0 D) ^6 |8 S# T( K5 a
  31.     GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉
    1 J' F& A' @6 M. v! s
  32. 9 _, G" ?' [2 p
  33.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快* u2 K5 L5 a: c6 k& [4 @
  34. ( {0 X0 N4 c+ b& |" c; @

  35. ; a& G1 m+ T( E4 a3 M# B* j
  36. : C4 h; x! V- K
  37.     // 初始化按键引脚配置8 `' g) s0 S5 K8 d/ U  a
  38. 7 r: q6 h0 Q: N, p8 X- r
  39.     GPIO_InitStruct.Pin = KEY_GPIO_PIN;              // 选择按键的引脚
    ' V' j5 G+ K6 A6 R; E

  40. 3 e$ t; {/ I5 u) f1 h  _7 _
  41.     HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);
    ( [' z5 K4 w. l% z+ \
  42. ; O3 n- }  n% z: N3 @  i2 O! L- e
  43. }
复制代码
; U! z  H# \; x
使能时钟,将引脚初始化为上拉输入。
8 L  Q# K) A/ {; p( U3 ^6 g! J# I9 k: A0 x* `; E1 G% I
% {2 J: \' s( r6 j& \5 N$ @
# z* x- A/ Y+ a+ t+ H1 O3 C! D
3) 按键读取函数( c4 s9 E( n7 q, s- D, S( R* ]0 X6 n
; t7 B3 Q, ?, H0 x" S  L
为了展示效果,编写代码查询按键是否按下,按下后切换LED灯亮灭状态。) \$ c7 A5 K: A" M: B' s- y3 k
8 x+ a* {- t4 L  z6 T" Q) t
代码段 12.3.3 按键读取函数(driver_key.c)
( h3 P: U: Z% K
* M1 ~2 ?+ M/ t! ~
  1. /*
    , I0 Q0 c6 a# ?
  2. 3 V( S  s. ~* b) {
  3. *  函数名:void KeyPolling(void)7 v3 ?: i" e. M- j  L0 b

  4. $ W* B' Q0 m% Q7 h! \4 @- A9 D) `) @
  5. *  输入参数:无' `* s- z0 u- r
  6. & Z( u0 P( F0 V# F! {! {+ A8 p
  7. *  输出参数:无3 Q$ h4 }  D. h& s
  8. ' i: n, {2 ~0 g& E- i- @0 g8 n
  9. *  返回值:无4 ]+ m5 C, V$ Y) ?( o
  10. 6 \, j6 F4 U- A2 J6 H: E4 z8 E
  11. *  函数作用:使用轮询方式查询按键是否按下,通过按下控制LED灯亮灭
    / |; t3 ^3 V( ]! }( x+ h- V3 c

  12. 6 N5 R3 s9 h- O4 q
  13. */6 a6 ]+ A0 H! x

  14. / |- ~  p: R1 N1 W# \3 U9 i
  15. static bool key_flag = false;
    . X9 Z: n# G' U" H) L

  16. / i: d' d4 l0 T. D" B+ I$ n0 O) f$ t
  17. void KeyPolling(void): ^: y( w( m9 E3 S* p

  18. + [1 u! A. P$ T% X5 x- B
  19. {4 S, W7 B4 i; e4 L; c

  20. 0 f+ H' _, c# I6 b6 r
  21.     if(KEY == PUSH_DOWN)          // 如果检测到按键被按下
    # Z+ g, f/ t' [. ~6 ?

  22. / H6 |2 c2 _3 X( C6 h
  23.     {0 I& ~6 S7 n5 e, J# D6 f# N- j

  24. 7 M( G: K0 Y* t% H; M+ z
  25.         HAL_Delay(8);             // 延时8ms防按键抖动, x, X. }& m* l  q3 W$ h

  26. 1 O) X) U( i& P5 ?2 p
  27.         if(KEY == PUSH_DOWN)      // 如果防抖动后按键依然是处于被按下的状态,就认为按键被按下过9 h5 B5 K# \: Y; X5 z
  28. 3 y1 E7 e8 }5 j
  29.         {
    $ l  J! Y9 @. I' B0 g4 f
  30. 8 F  N0 U3 l: V0 |) b
  31.             key_flag = !key_flag; // 用一个标志位来判断按键被按下次数,按下一次灯亮,再按一次灯灭,如此反复; l6 ]/ @- i) b: ?4 e4 t
  32. 2 P$ s* b: b+ w0 v5 T
  33. BLED(key_flag?OFF:ON);
    ' Q+ U$ \" D% I% f/ W' F

  34. ) N( l) i$ K) y7 i& d
  35.         }* o+ p/ r, A7 x! H9 r

  36. . Y; s* ]+ ~3 h. X% V
  37.     }2 F3 g' R$ z" `2 |6 A3 p+ h

  38. / _, B; \% }2 i8 p2 C' P+ M1 C
  39. }
复制代码

- K; l  x6 ^! {% ]- |/ ~! V8行:定义了一个全局变量标志位“key_flag”,作为按键被按下的标志;" z: t6 y4 m+ g6 I
! O, O  o" h) Q$ n+ r. w& a8 S
11行:获取该按键状态;
0 Y; o# \. T% X2 z! L! b- d8 P. N. q0 X! N7 {- ]
13行:延时5-10ms,软件去抖;  {$ D. M4 G! @7 |
% U1 {4 ~: }9 ~
14行:再次获取该按键状态,此时依旧按下,说明是正常按键操作,非抖动;" W: Z, ], ~, R

$ @0 E: q" W! t) B16行:将标志位置反,按键按一次置反一次(即0->1->0->1这样循环);5 G" f- Q7 l0 l- e
6 K+ l# a' q5 o8 ]3 u1 `
17行:根据标志位“key_flag”的值,让LED灯亮或灭;
6 a0 @5 t. J* l, j1 w2 W+ ]
8 V& k3 O& ]1 e7 D此时每按下一次按键键,蓝色LED灯将亮灭交替。5 p+ \% [0 x" E: N( y

3 z$ k$ q1 O$ f7 }# U' B: B- g9 E6 G1 v: J: a" O

" u# a) p2 Z, U2 v4) 主函数测试
7 R6 s7 G/ d4 I
1 W+ a& u0 Z1 c/ ~- @' S代码段 12.3.4 主函数控制逻辑(main.c)
3 w; m* Q% O3 n3 U/ S; k8 @+ L5 q# K8 ]. v
  1. // 初始化LED0 M" I, l- `9 N: m
  2. - P& i% i& n2 X
  3. LedGpioInit();
    * B" K, g) R- {/ j+ f! \
  4. ) W5 p, M# w+ R1 a7 l) H" ]
  5. // 初始化按键
    ' H( t# d- Q9 a/ D8 T, D
  6. 6 f9 b# U: O2 y1 h2 k
  7. KeyInit();
    2 M. T0 G; w+ v8 U. P

  8. 1 S# ?( ?1 @' J9 `
  9. 6 l$ `& ^6 S# D9 A$ o) F% j& I
  10.   O0 Z1 c; \( P
  11. while(1)
    1 l/ [0 a; Q( p2 O/ G8 r5 g

  12. 4 c- C9 i/ P/ X& G
  13. {
    1 T, K* `1 N  ]/ q* S7 \5 M

  14. " w6 ~' S" Q3 O; ]* `& |
  15.     // 轮询按键键! h+ ]. B" f( R; v4 y

  16. ( O. V9 I, }3 N3 y% L# U
  17.     KeyPolling();* ~2 U" y) ^8 h0 G7 z) }( B
  18. 6 `% W7 |! i$ z. B
  19. }
复制代码

# Q' f* X9 _2 x1~4行:初始化LED灯和按键;' Q  l) K) G- j8 _" i+ _; z

0 g9 D, v/ N! D( o, v6~10行:一直循环查询每个按键当前状态,从而判断对应按键是否按下;% Q. v- V! [: h$ }5 j

+ B8 I+ o* q. U7 o12.4 实验效果) ~  _$ ~1 R3 t8 C6 x4 i4 L; v! N$ s
本实验对应配套资料的“5_程序源码\5_GPIO—按键轮询\”。打开工程后,编译,下载。按下按键,用户LED灯亮/灭。
$ N; y0 U) v) ]( P! W9 W
" C  g9 e: p" d3 W9 U作者:攻城狮子黄
0 e7 k  m5 N" M; l( W( N, b
( M. T4 Z/ B. u  g  o0 S# t* N  d3 f& T( n7 T( ]. w
收藏 评论0 发布时间:2022-8-30 18:38

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版