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

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

[复制链接]
STMCU小助手 发布时间:2022-8-30 18:38
12.1 关于按键! H8 v" V. p! y' |
前面控制LED灯是让GPIO输出高低电平,而获取按键则是读取GPIO电平,从而获知用户是否按下按键。
0 N: \- \/ |1 L, U; v  K, T2 a7 ]- a& p
按键监测一般有两种:按键扫描和按键中断。按键扫描是间隔很短时间反复查询GPIO状态,从而得知是否有按键动作,这种方式代码简单,但比较耗资源。按键中断而是通过按键产生中断信号,从而实现按键的检测,这种方式需要使用到中断机制,需要对MCU了解深入一点,效果是最好的。
& c" c1 I# I- @% o& L+ t- N2 n* `9 Q9 t, f: s2 N6 ~' _
本节先介绍按键扫描,理解按键的基本原理,下一章再介绍按键中断,同时了解STM32F103的中断使用方法。/ l; x+ ]: A: u0 [* K! T
  {, p, T+ F: T/ |: L: E2 D' E2 s
按键一般占用一个GPIO口,通过监测该GPIO的电平变化得知按键操作,典型的电路如图 12.1.1 所示。当所需按键比较多时,则可以采用矩阵按键减少GPIO的占用。矩阵按键需要通过编程扫描等方式实现对多个按键的监控,这里以最简单的独立按键为基础进行介绍。7 j! u9 u. _" Y* t7 m
I@{MSO}8CO7W%9)4I)_BM7G.png

6 H3 x  v2 G, u( t8 I) J+ h" ]9 D( z  I9 I
图 12.1.1 按键与MCU的连接方式示意图

* B  n. b. e- e/ Y8 a7 ~可以看到,在没有按下按键时,电源3.3V通过电阻连接到MCU的PA0脚上,此时MCU读取PA0的电平就是3.3V的高电平。在按键按下时,电源3.3V经过电阻,再经过按键连接到了地,此时PA0连接到接地的一端,读到的电平就是0V的低电平。由此,MCU就可用过读取对应引脚的电平值,得知按键的变化。9 \" y, e/ ~+ B" O* S8 @, E2 G
% e6 I9 N3 ?( S9 m, ?
常用的按键都是机械触点式按键,机械式按键在按下或释放的过程中,由于机械弹性作用的影响,会伴随机械抖动,如图 12.1.2 所示。
8 d: f7 h3 T: b6 g* a7 f. ~/ E" h1 |5 [! N0 i
2FP62$]}XCNRID~FD)JRJ7V.png
6 V6 L( j* V9 W0 a4 U3 Q

; |" M% v8 T. Y& [: _/ g% k
图 12.1.2 机械按键抖动示意图

5 W2 x( Y9 `+ g% G$ g- ^9 W, M( B. D( `: M1 }7 b& d
  e, b% u' d9 J, B4 d
抖动的时长与机械开关特性相关,一般为5-10ms。在这抖动过程中,会产生多次高低电平,导致被识别为多次按键操作。为了避免机械触点按键检测误判,必须消抖处理。按键消抖可以硬件上处理,即在按键旁并联电容,吸收抖动的电平。也可以软件处理,即通过延时,避开抖动。
# a+ M. ]! p3 u; [5 G/ O" A" H4 P
: q' _( I4 X3 B由此,首先获取对应引脚的电平得知按键状态,再硬件或软件消除抖动。
) t( B5 D+ V, M( Z. ~8 [) j
# i4 T2 \; L. k* f) O5 O; A+ U, c- G& F) }
( T3 e1 ~$ `+ T4 D1 m( I  ~- d
12.2 硬件设计
5 s4 @/ `2 s. @+ n0 r* {; i2 }
开发板上有两个按键,一个是复位按键(K1,红色),另一个是用户按键(K2,白色),这里只有用户按键能够编程控制。
; b) b2 l6 H( e  d7 d
7 s0 P6 h* ~6 [  b! {2 y; n' M. l如图 12.2.1 为开发板用户按键部分的原理图,电容C15用于硬件去抖,可以看作不存在。按键松开时,3V3经过上拉电阻R20到网络标号KEY,网络标号KEY另一端连接MCU的PA0,此时读取PA0电平为高电平;按键按下时,3V3经过上拉电阻R20,再通过按键接地,此时读取PA0电平为低电平。! F# d& R, [4 z
0 ?: H1 @( S: [. k7 S% V
由此可知,按键按下,GPIO引脚电平变低,反之为高,按键所接GPIO为PA0。
- J1 g: x; P+ O) z# D0 D; K
4 r( u/ K; }2 T0 R: h" C. ^
`AAI{R(S$W{RV@1C(0_M[2V.png

2 Y$ p, m  c. U9 p2 j2 ]; ^1 O7 ^( J. q, ~* }+ u
图 12.2.1 用户按键原理图
- L5 f2 q% E, ^1 G2 x

+ d! b! G; l" ~/ s" D" s
1 \% h, S/ P8 o6 R5 I; j0 H12.3 软件设计
3 p5 u% V  e4 Y2 N4 {9 Y6 B9 {
1.3.1 软件设计思路
( ~1 s3 I4 C$ m" O& Z实验目的:本实验通过轮询读方式取GPIO的输入电平判断按键是否按下,并操作LED。$ O4 s9 [$ g& J+ N# _, N; l

4 r3 _7 Z/ J& K) x1) 按键初始化:GPIO端口时钟使能、GPIO引脚设置为输入(PA0);8 F4 N/ G4 T" k! l) F

; b) v# K/ k3 L( R' j' y; t9 W2) 封装每个按键处理函数:读取按键GPIO状态,操作LED灯亮灭;
& B. q4 ^5 c, `. ~) U( N2 \# b
+ K0 u: }$ a# ]/ [5 ?2 H3) 主函数轮询按键状态:一直检测是否有按键被按下;
% B- m9 m2 o3 i  E$ H3 J/ T& q, p' V( \  l  ^; u6 q- V
本实验配套代码位于“5_程序源码\5_GPIO—按键轮询\”。2 c) y4 t& v. h1 m1 t# F/ v

1 |: b; `, ?8 t; ^# u
$ I3 j# _3 H; ~8 B- {. h8 c2 J9 H& ~, h) m
1.3.2 软件设计讲解
1 g7 @( ]: L- X1) GPIO宏定义与接口宏定义3 F* d4 |6 G" i. F3 a1 e$ {. w
( O# h  m* ~4 r$ d
代码段 12.3.1 引脚宏定义(driver_key.h)8 @' _8 y# B( q& J; Y9 m% d

$ j3 o' Q8 p4 C1 O/ {7 o
  1. /*********************
    % L- S4 e+ z  T# a8 K# Q

  2. / c& |& ^# i! w( n3 B8 _& a
  3. * 按键引脚状态定义
    ! k0 a7 ?& V! r2 i/ A* Z& H

  4. + B% K6 G# P6 R
  5. **********************/
    % j$ l; H0 Z5 l: B! m, Y2 `
  6. / p( |) k  C) ~5 N% i  F
  7. #define PUSH_DOWN                       GPIO_PIN_RESET* g0 ]. O8 M0 y& L& G; Q: i% T

  8. 8 G. H  \& f4 K: J* V  h3 M+ i3 k
  9. #define SPRING_UP                       GPIO_PIN_SET7 g# C" C$ u, A! L5 l! i5 o% {
  10. 8 p* P; m+ l: t& R. {6 o
  11. 5 W) |4 l, J' g8 n
  12. 3 ^9 F5 y" F, \: |0 r* S
  13. /*********************! H0 W! X5 u: V) t

  14. & o0 ~* ^1 D" V. P& q. f# u) l8 D
  15. * 引脚宏定义
    5 k/ z4 b* U0 S# J& ]) _" y
  16. 9 H9 x; P# |+ _1 d$ Y, e
  17. **********************/& [2 U0 a3 b9 b; P& ~

  18. 5 [9 {% v# V+ g- E$ Y1 v
  19. #define KEY_GPIO_PIN                 GPIO_PIN_0
    : K6 G; o  R# g; {- b  D4 [, W
  20.   K7 l* O. A- x/ L" w* Q
  21. #define KEY_GPIO_PORT                GPIOA
    . k: a& n/ u, t1 U$ _

  22. . L9 U2 F) M" K
  23. #define KEY_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()1 D9 k, G3 _+ g" r: e

  24. 7 X8 o/ ~5 Y, s6 f5 }! s

  25. + k- C6 Y1 u- |$ b. N$ v5 f. A8 e
  26. ; n% q" f; z; X9 F& b2 @- n
  27. /*********************5 {' b/ H( e; \* z( }

  28. , J" [9 c% s9 e" @' D
  29. * 函数宏定义5 Y- W; X' m  D! E0 {
  30. ' m1 a& x! ?8 P* f
  31. **********************/
      R. m) |. G0 y) k3 B' G
  32. 2 x1 ?/ w5 `6 v5 p& E$ q
  33. /*
    0 T  j) s3 D) c, d
  34. % M% o9 S# A! C6 c9 I
  35. * 按键状态读取函数宏定义
    * L/ K! X; D1 _6 ]" |. g* T

  36. " L' M2 i" z) O* Q+ |% L3 G
  37. */
    - ~: n" `4 l5 }$ s# _, w+ K
  38. , _  k& ]( N+ N4 K# ~  Z' A
  39. #define KEY                  HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN)
    + m2 I, L: O3 @' }( m
复制代码

6 R, r; g* q( O6 ?- y4 _+ k1 m- |
( m& V2 q4 ?* ~5 R% }# i, X" n根据硬件设计选定的对应按键的引脚,将其宏定义命名为KEY,且对他们的读取函数进行重命名。其中“HAL_GPIO_ReadPin()”原型“GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)”,参数依次是:引脚组,引脚号,返回的是0(低电平)或1(高电平)。9 [$ Y0 S6 j3 j& t& [" H
, H  y6 a) J' A7 E) i) v

. G7 b3 S4 E* f7 T" R3 w2 \; m+ _2 H6 s0 Q7 ?" ^
2) GPIO初始化/ S  y; K4 k5 S: y! O+ F. H

4 q: Y  |& D/ ~, d. E+ @6 J! c代码段 12.3.2 按键初始化(driver_key.c)
5 Y) S  M' L/ Q8 R
" _; [0 n8 E  e8 p9 V
  1. /*
    1 N( f) D1 L5 d1 ~; o

  2. % y$ D" J4 z, y+ M( H
  3. *  函数名:void KeyInit(void)
    + E) z2 m/ ]9 ^( ~6 z7 j1 k' V
  4. ( |9 {: T& `8 n0 A# B; p
  5. *  输入参数:无
    & L- m) S  c7 R. R; h, o

  6. 5 z: b# p& \  B' F6 u2 x8 A9 r1 ?- v
  7. *  输出参数:无3 d( J; B; T$ w( k! ]; n

  8. ; c0 M3 T1 a6 k3 M% L8 l/ c7 x
  9. *  返回值:无
    ( L- |7 E& Z  Y2 v' ~0 J
  10. 3 U: a1 Q% w) v+ W! {' Z% g
  11. *  函数作用:初始化按键的引脚,配置为输入) M. o$ Y3 b  |

  12. % g3 R+ p6 [; F! C
  13. */, }; v8 @6 Y" s
  14. . H' t4 d) T: l& ~1 W2 ~
  15. void KeyInit(void)
    ) ?3 i6 l$ G! ^7 R4 [% j& S4 d

  16. 5 k# U# ^; }" W  w( C9 Q
  17. {
    " z- q' B+ d# i1 D2 k+ t3 l3 I

  18. 8 I: U% @; y; a$ j
  19.     // 定义GPIO的结构体变量7 g9 _' E1 z" S! h3 f
  20. % f4 Z! y: I, X6 ]
  21.     GPIO_InitTypeDef GPIO_InitStruct = {0};; `3 z1 ?. v- ~7 C7 W! L
  22. ' B* b+ ^/ G3 C. |/ ^! S0 y
  23.     // 使能按键的GPIO对应的时钟
    8 N+ c' }' Q# I) J0 j- c
  24. : f. C7 S& C$ g$ o2 w3 i$ V
  25.     KEY_GPIO_CLK_EN();5 h+ ]& I$ }" \: i; z

  26. . w0 i' o! k3 n: A3 F
  27. - }% A6 H- f2 ?' V! i# a

  28. 1 i% Y& ~7 N; M* I' Y! b5 ~# J- d
  29.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;             // 设置为输入模式3 @) u0 [9 F/ y" v% |9 ]8 I: g6 R  o

  30.   w+ |& A. X0 t' U
  31.     GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉
    8 j2 P/ m! M: J' j8 W
  32. 5 B  k+ x1 |8 U1 E2 Y: k! \" r6 Y" G( f
  33.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快
    2 S) G! l: I: O$ `5 c/ N( p
  34. 0 \/ Y/ ?8 U: i- z2 A' f% x

  35. ; K* W" ^3 A# L
  36. 4 X! L8 g! N. [) q
  37.     // 初始化按键引脚配置
    ' w0 r6 k/ F; t3 y; O) @

  38. 3 Z8 u# _, m5 ]" j( _
  39.     GPIO_InitStruct.Pin = KEY_GPIO_PIN;              // 选择按键的引脚
    3 M) s0 e9 h3 p. V( X7 t3 p

  40. / [- n0 P2 s. K! O/ b
  41.     HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);" [( t8 t) _6 B! |

  42. 0 T7 @" p4 U% J
  43. }
复制代码

# y* f; y, Q% H% I- M使能时钟,将引脚初始化为上拉输入。2 t! B7 e- e; g- T

. q* Y6 S; p& S7 W3 u% ~/ l  N+ b! G
) [1 y5 ]* @$ b6 h# v8 N# m: n. z' X
3) 按键读取函数: J# Z! D* C3 V: N. P1 M

& [8 g. R3 E2 E; J8 q9 G5 I! t为了展示效果,编写代码查询按键是否按下,按下后切换LED灯亮灭状态。+ V1 `. s5 c2 d, k0 W6 p
5 P; \: l/ s) g. Y
代码段 12.3.3 按键读取函数(driver_key.c)) a& P; B4 g' _; N
, v( t- {: Y% P) C1 n
  1. /*
    $ w9 K2 L' C& `/ r0 l2 M
  2. 3 o0 V+ E3 k' i; ]" M
  3. *  函数名:void KeyPolling(void)/ O8 c  v4 x, i$ v7 D8 c

  4. - q# \( p' z1 k$ K
  5. *  输入参数:无0 O7 j& w. w, o  X3 E$ t3 }& B0 S9 B

  6. 8 G8 P( c0 V# R9 ^) x* I; o
  7. *  输出参数:无5 c; E5 h( L/ K7 c! P, T' c. [$ D
  8. $ o0 i. p2 ?; a- t- g
  9. *  返回值:无/ T1 I  Z0 X+ I" Q: H4 u$ }" e

  10. ! F  s* J9 g& S, {1 k' D) h
  11. *  函数作用:使用轮询方式查询按键是否按下,通过按下控制LED灯亮灭$ N8 G/ d( s8 C1 `/ o; w; P+ v; E" q
  12. 8 X+ n3 u, ?: W
  13. */
    - h# w4 T5 K5 f) n: M4 J
  14. 6 H3 C* y0 O. k- Q2 o
  15. static bool key_flag = false;: z3 P. z4 L: O' L/ Q. Z

  16. 1 b+ e( l( W0 R
  17. void KeyPolling(void)
    2 t  ~5 Y' K7 |: Z( e  r

  18. 7 a+ K! F+ c0 E0 e( f* {6 a7 V
  19. {- j0 d- |' }6 }: [8 E/ V

  20. * C# J2 H/ p1 j
  21.     if(KEY == PUSH_DOWN)          // 如果检测到按键被按下
    5 ?; L) e; s% J1 z& \
  22. : _+ T2 J9 c: W
  23.     {
    % i9 O/ K# r+ S
  24. $ A; l( ^! E3 n: X8 B% K% [
  25.         HAL_Delay(8);             // 延时8ms防按键抖动% q$ Y+ t5 u8 O  j
  26. % J7 F' }  T( V, k  C
  27.         if(KEY == PUSH_DOWN)      // 如果防抖动后按键依然是处于被按下的状态,就认为按键被按下过
    9 m; C8 {( g* Z: A3 X

  28. 4 x8 V9 M6 ^: f$ n/ }) G9 y4 a
  29.         {
    9 O# a$ z  f$ n: `+ j
  30.   N/ l6 K( I! o! u! W0 \  v' E  D
  31.             key_flag = !key_flag; // 用一个标志位来判断按键被按下次数,按下一次灯亮,再按一次灯灭,如此反复7 A9 R2 t* C5 f, i2 m0 B0 z
  32. ; B: q  }# Z" ^
  33. BLED(key_flag?OFF:ON);. g% Y! _8 g4 H

  34. 6 @8 E! S$ o3 {5 U* n3 w6 N
  35.         }
    " {$ v% Q9 H- e1 L9 p

  36. 8 m( X6 {( ?  d' Z$ q
  37.     }- K1 T) B1 K: U4 E

  38. " G$ f9 ~) t; w6 w1 @& i$ M
  39. }
复制代码

2 M' {, \6 S% |: q8行:定义了一个全局变量标志位“key_flag”,作为按键被按下的标志;
5 w  z8 n, P1 q& A. Z0 X+ M8 d+ k/ a3 T+ o+ o5 ^" W6 @5 o- c$ n- h
11行:获取该按键状态;3 m* j, p: l0 d! F3 }" I# S- D( t
( g" R3 b- _; ~( w+ u
13行:延时5-10ms,软件去抖;+ J* N) K3 M1 }" j# P: H  W

1 }0 }5 u6 K) p14行:再次获取该按键状态,此时依旧按下,说明是正常按键操作,非抖动;& x( B- K% A) P  t6 @# B% X

% d% l( k# y& @/ x" f) J16行:将标志位置反,按键按一次置反一次(即0->1->0->1这样循环);
- f9 B! m3 H1 `. y# n( x: O
/ u9 |: F: G% J5 E) ^& X0 a17行:根据标志位“key_flag”的值,让LED灯亮或灭;
) B5 R2 a) _' t8 S8 B: w4 I8 w  R) ~! Y' b
此时每按下一次按键键,蓝色LED灯将亮灭交替。
; a3 m2 f, x% p9 p* J4 ^6 U
- w4 M- A( K' v
$ M0 t1 m! M2 g  ]4 u& o6 S$ j, O+ L# |8 T
4) 主函数测试% y1 ^2 H1 e6 C8 x) q

+ r" h" Q* u& Y: k" N! C( [0 @代码段 12.3.4 主函数控制逻辑(main.c); c3 P: n$ E+ w& ]
/ `7 T# |* @, z! t8 v
  1. // 初始化LED
    4 n& [( J2 Z# }$ G
  2. 0 S. j& L8 y* d( _" R2 N
  3. LedGpioInit();
    . H7 Z" q; a' r
  4. / Z6 n# b. M+ V" v8 a+ R4 {/ `
  5. // 初始化按键+ F, {, p* ?; g) g0 u

  6. 9 l1 h" w% e6 O# |7 }  F& ]9 X2 b
  7. KeyInit();3 J' l. w) h6 i3 j3 B% E& |9 h
  8. # F2 q1 H* }4 _. Y/ ]4 W- Z

  9. : j8 Z6 s; w$ E! V6 R# E

  10. 1 y5 x: j. w7 l% U
  11. while(1)
    ) h) S. }) M6 R; l" S: o

  12. % l6 a9 D+ a9 G
  13. {
    3 k/ [2 y4 H. T& D1 U" ^7 l

  14. ! r$ c( t/ h2 d8 c8 O  P
  15.     // 轮询按键键
    ! e, H& M2 C: r. z! R

  16. - E: T: A3 f( j/ m# I
  17.     KeyPolling();
    ; r. X% v* C/ O, h; \
  18. 0 d% U* ]* Y& U+ P- `
  19. }
复制代码

# R- B6 U" V5 N* Z$ W/ S  T6 z- p1~4行:初始化LED灯和按键;
& G- G; U1 r; @- v  A; x0 t
' `7 K7 }* b& Q' L6~10行:一直循环查询每个按键当前状态,从而判断对应按键是否按下;
7 F+ B' Z* y1 H* O# a: c2 K& v' y+ Z. Y- U! _+ y% F
12.4 实验效果
; C- ~8 R1 E/ B本实验对应配套资料的“5_程序源码\5_GPIO—按键轮询\”。打开工程后,编译,下载。按下按键,用户LED灯亮/灭。   {9 t$ s# s! S" G+ B- O2 _3 B+ _% D

* y8 v" {( }4 I. y作者:攻城狮子黄
4 g8 O! N+ r( A, {5 d1 l1 y0 G+ N) R, k  X1 w
1 c8 D. b$ B; ]% R" I0 |! q
赞 收藏 评论0 发布时间:2022-8-30 18:38

举报

0个回答

所属标签

相似技术帖

官网相关资源

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