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

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

[复制链接]
STMCU小助手 发布时间:2022-4-28 20:53
12.1 关于按键
5 j1 B$ V* I2 f9 W$ _% p6 Y前面控制LED灯是让GPIO输出高低电平,而获取按键则是读取GPIO电平,从而获知用户是否按下按键。6 H+ i- m; u  e* x

" I$ o6 |3 }3 J& v4 w按键监测一般有两种:按键扫描和按键中断。按键扫描是间隔很短时间反复查询GPIO状态,从而得知是否有按键动作,这种方式代码简单,但比较耗资源。按键中断而是通过按键产生中断信号,从而实现按键的检测,这种方式需要使用到中断机制,需要对MCU了解深入一点,效果是最好的。
5 |" c+ Q: w; U& ~8 p! g3 a' J) h0 U; Z" T8 q* \0 `+ s/ s
本节先介绍按键扫描,理解按键的基本原理,下一章再介绍按键中断,同时了解STM32F103的中断使用方法。5 K; v5 v  k6 U$ ^

0 u6 ]0 F' m' O0 D, M. B3 e: t* X* O按键一般占用一个GPIO口,通过监测该GPIO的电平变化得知按键操作,典型的电路如图 12.1.1 所示。当所需按键比较多时,则可以采用矩阵按键减少GPIO的占用。矩阵按键需要通过编程扫描等方式实现对多个按键的监控,这里以最简单的独立按键为基础进行介绍。
+ G0 F" ?% l- b! e& Y7 S9 K4 q
) ~/ w* P7 s0 h HCXQS2@B6Z%8(G)O$}H`{}A.png
) F8 x9 O+ C$ @" s$ h' u9 B6 k$ h! Y: V' n+ U; c( O
图 12.1.1 按键与MCU的连接方式示意图
" \9 n4 I- c9 b( w' A# a可以看到,在没有按下按键时,电源3.3V通过电阻连接到MCU的PA0脚上,此时MCU读取PA0的电平就是3.3V的高电平。在按键按下时,电源3.3V经过电阻,再经过按键连接到了地,此时PA0连接到接地的一端,读到的电平就是0V的低电平。由此,MCU就可用过读取对应引脚的电平值,得知按键的变化。
3 u* i  ]2 z/ M  a4 ?8 Z3 I/ Q( p* g4 Z
常用的按键都是机械触点式按键,机械式按键在按下或释放的过程中,由于机械弹性作用的影响,会伴随机械抖动,如图 12.1.2 所示。5 q, k2 v# h0 Z* y4 b/ s
, t. w) Z# q# k! v
5 ?9 L0 w$ y! p) _7 o0 U) C
J}%W8`E]}Q(CJPAEPFG8FA2.png
- g. T$ f( |5 [; P: R& e. C- g) d; P5 b
图 12.1.2 机械按键抖动示意图* |, N$ K: [1 |3 V! S, |, R
# j% a3 J+ k, V

- i2 D' k6 x$ H$ L" A8 s抖动的时长与机械开关特性相关,一般为5-10ms。在这抖动过程中,会产生多次高低电平,导致被识别为多次按键操作。为了避免机械触点按键检测误判,必须消抖处理。按键消抖可以硬件上处理,即在按键旁并联电容,吸收抖动的电平。也可以软件处理,即通过延时,避开抖动。
3 ^0 j4 x( M/ _; ~% i) u/ u% q" V
由此,首先获取对应引脚的电平得知按键状态,再硬件或软件消除抖动。
' v* e  i$ P. k4 F9 V0 U# r- H: A% A! _" a; B4 q5 v5 G
! Q1 p( g% N) [/ W
12.2 硬件设计0 j- G' H/ F* T% [% G8 T
开发板上有两个按键,一个是复位按键(K1,红色),另一个是用户按键(K2,白色),这里只有用户按键能够编程控制。
' J/ M% z3 @' O7 ?- Z* O% j5 Y+ L0 T1 N1 c2 _) X: S1 B; w" ]+ h+ r
如图 12.2.1 为开发板用户按键部分的原理图,电容C15用于硬件去抖,可以看作不存在。按键松开时,3V3经过上拉电阻R20到网络标号KEY,网络标号KEY另一端连接MCU的PA0,此时读取PA0电平为高电平;按键按下时,3V3经过上拉电阻R20,再通过按键接地,此时读取PA0电平为低电平。  a1 v& u2 `7 [5 X/ [! @+ n
) B; e) h: G: E# G* n
由此可知,按键按下,GPIO引脚电平变低,反之为高,按键所接GPIO为PA0。" u' g" T/ P4 F- ]; u
( v0 F: N+ g3 v  h
FU[36R~I8ZR%]K%{3L@E2~E.png
" Z6 S; g6 c% a! X/ P3 l1 z  V8 e/ z$ o' s7 o  ?
图 12.2.1 用户按键原理图/ T/ D% F/ J0 b/ v+ S
7 J$ w) B" o2 R  s& C

# I9 g' y) {+ h5 W! X12.3 软件设计
0 f6 C# `$ j/ e) V6 d& s2 c1.3.1 软件设计思路: i4 h1 ?6 S! e0 I8 ^( L+ w
实验目的:本实验通过轮询读方式取GPIO的输入电平判断按键是否按下,并操作LED。' W7 G1 |: K, i8 A

$ A  [1 Y3 m2 v7 [$ C; u1) 按键初始化:GPIO端口时钟使能、GPIO引脚设置为输入(PA0);
+ D0 O5 F& [* M. b/ ?
7 @* v; r+ i/ s) }+ @" p! C2) 封装每个按键处理函数:读取按键GPIO状态,操作LED灯亮灭;
* K" }% c( J! n- V, `
% w/ ]0 ]9 b0 U( U& Z, Y& k3) 主函数轮询按键状态:一直检测是否有按键被按下;: n; T) v9 Y# x0 w, _3 g) W

& h) U! D0 x" |  P9 B本实验配套代码位于“5_程序源码\5_GPIO—按键轮询\”。
; l& n$ A6 v% O0 d
9 G+ D; ]2 ~& J7 Q
9 q4 g8 |' [2 f# [# d  {7 I1.3.2 软件设计讲解
- |- u" x. U3 [+ V' K* S7 d1) GPIO宏定义与接口宏定义
9 z( v3 J) Q' \4 F, u: P' j; K; L+ z2 A: ~& [( ]& F( y& F
代码段 12.3.1 引脚宏定义(driver_key.h)
/ a  H/ @8 C# j& }0 w# z# B) K0 H, D' F# \! q- b
  1. /*********************3 B* Q0 D/ F9 S6 ]/ E  I; O9 f. r# w

  2. ' X) O, U3 K3 `. }% q# F
  3. * 按键引脚状态定义
    9 q3 j% B& J2 @1 Y9 w

  4. 2 s; Q" \4 W; \/ h
  5. **********************/& l+ y! z+ b" T- g) D

  6. * g4 e! z! J$ e6 J. w/ E/ j2 n) a
  7. #define PUSH_DOWN                       GPIO_PIN_RESET1 a; h$ t" p7 x  I4 i
  8. 1 N: F$ A5 x: r7 q" G1 s4 G" w
  9. #define SPRING_UP                       GPIO_PIN_SET
    $ `3 X! _9 _2 ?$ H: |! _7 ^

  10. ; }& j8 @( j( I: n0 I, Q" A) O
  11. % J2 T" t+ v; w0 {  w5 X

  12. ( `4 @% l  t  ~; [8 w7 q
  13. /*********************
    . E% D3 h5 Z' v7 o; A5 O

  14. 4 a6 H, `2 m: v, ^! V1 N8 Z! l
  15. * 引脚宏定义( v8 U+ O* q/ J6 E2 V* Q

  16. / M6 X* n# g& b  Z: Q& h
  17. **********************/" B0 G2 F' |! G* u/ ]% H1 `
  18. $ `0 h- K2 _- Y$ ?4 u: a3 N* W8 B% T
  19. #define KEY_GPIO_PIN                 GPIO_PIN_0
    8 n  L6 o% ]& y6 Y+ m
  20. : K! U+ ^' N" U- D
  21. #define KEY_GPIO_PORT                GPIOA5 U& U0 t) }+ r4 J" P% C

  22. , G2 t5 a0 W. U6 V, H* V
  23. #define KEY_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()3 L; Q% S" {) @0 g7 ~' z. }

  24. ) f5 k9 H& L: P5 Y' ?
  25. 9 d4 e/ X& I1 P) N% F5 T. h, M' H
  26. 0 z' s9 l0 r- ?# k6 p. @+ Y9 ^
  27. /*********************
    $ X% t. g5 ]5 [9 \
  28. ! d- d8 B/ u; N2 J: d. _4 q/ `8 r, M
  29. * 函数宏定义
    ' r5 }( G& F! ^, [. |9 _
  30. 6 \' M; t! [! H0 f% t
  31. **********************/
    8 ?: x/ y: v/ a

  32. 5 u! g5 G5 J6 Z5 B( x" V
  33. /*
    ; R) ]$ m9 }) J+ Y) r6 i( V
  34. : |# Y; M* E$ q& k! Y( J4 [
  35. * 按键状态读取函数宏定义
    + _! z* d% k" A. I
  36. - u: y* _" z# a$ i! o# o7 x
  37. */2 }1 C' O( \- q" n8 Z- T7 Y
  38. 0 G5 E0 h# o" U1 o( o! L
  39. #define KEY                  HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN)
复制代码
: X2 w  s" h0 u, ~2 ^5 l: k( D
' F2 C  W- a% }8 q( O) a
根据硬件设计选定的对应按键的引脚,将其宏定义命名为KEY,且对他们的读取函数进行重命名。其中“HAL_GPIO_ReadPin()”原型“GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)”,参数依次是:引脚组,引脚号,返回的是0(低电平)或1(高电平)。
/ }6 H) L: J4 I! V/ e9 W! ^7 r+ i" v0 l3 Q" B, q: n
$ ^1 L6 Y  K1 N4 t4 I5 y) x1 t
2) GPIO初始化
& B" u5 t7 T. @1 h! o' `0 E& F" ^, [9 ^* ~% c
代码段 12.3.2 按键初始化(driver_key.c)
8 N2 L/ c2 B/ Z) B# v, c& G3 a! }5 L! ^) i# e6 F
  1. /*5 S' P2 l, q3 \7 w3 o3 p
  2. + q3 }7 ^' o6 d: r* `
  3. *  函数名:void KeyInit(void)
    / b1 U) L' C% E& u

  4. # L+ [6 }: t7 S. l( J' C# n% {
  5. *  输入参数:无
    ' n6 Y4 F5 p3 b2 U6 I5 {
  6. 9 n. L* N' w! I- R1 {& N2 f( ^
  7. *  输出参数:无: i# l# W- x2 R4 ?' k/ M
  8. ( k  |( r* X2 A" u( v0 V
  9. *  返回值:无
    $ A+ w( V( S- j  A8 V' J
  10. . x  M6 g% Z( `( a
  11. *  函数作用:初始化按键的引脚,配置为输入
    ! C! I7 K+ R$ Y  g! Q( a% Y1 ]1 }
  12.   w& E$ c% w9 q8 o$ o! E7 u7 Z
  13. */
    3 ]- f( Z1 i2 m- a0 t/ N
  14. % u; \! k. t. A+ ]. B
  15. void KeyInit(void)
    , k# V  Y/ u$ }7 |2 q, M3 E2 @1 A
  16. 0 K$ u- |6 h- X% K) v, X4 O
  17. {
    + U* i3 n, l4 Y4 s  b) X  f  s

  18. 7 Y" E. X7 N1 f- p6 n
  19.     // 定义GPIO的结构体变量
    0 S! U% |8 B, S
  20. 7 Y$ _& N0 ~% J4 A, a2 T; q
  21.     GPIO_InitTypeDef GPIO_InitStruct = {0};1 c# r& ?9 ]  e! d5 u3 L) U  ]1 {0 o
  22. + |. X# Q4 _) l) G5 o& T1 x0 [
  23.     // 使能按键的GPIO对应的时钟/ u2 X1 e5 P! M, E4 z
  24. % r5 ?* T* N/ D0 @8 t
  25.     KEY_GPIO_CLK_EN();
      h; b, H0 _$ P" A9 H

  26. , r3 Q9 ?2 R% {

  27. ) o& f) k, E8 }7 ^; a6 o
  28. - p0 ^  ?* M) Z8 Z
  29.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;             // 设置为输入模式
    . E/ E! m- h6 \0 [6 j9 C

  30. 6 {. L3 Q6 p$ B, K) A+ D6 E
  31.     GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉
    ! G8 T* N1 M! ^' N* b; n

  32. ( d0 i$ ], }, ]/ R0 Y
  33.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快
    1 v/ R; q0 |8 b& }9 E, ]5 n% u" d) S
  34. 1 m# M! N* ]0 n9 m, c' j

  35. + c$ c2 [( P! B" v& Y

  36. 5 ]# F. {# t7 H" W! D. a
  37.     // 初始化按键引脚配置. h' ^  \  f1 X. F' p' v

  38. 2 k+ n. a+ B" `$ [
  39.     GPIO_InitStruct.Pin = KEY_GPIO_PIN;              // 选择按键的引脚
    ' I% j' A% B) W% i3 f

  40. ' j& D  b0 @, T! Z" K
  41.     HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);
    , T1 @; a. N+ }/ i
  42. 4 s: L3 i/ }0 O1 q: t9 P2 H
  43. }
复制代码

2 m0 |9 p; d& J& d% U0 A% y) x" W& n  x使能时钟,将引脚初始化为上拉输入。5 J( v% E" R8 k

7 \) ^/ C* d! E- C& x6 j: ~6 {
3 @$ z. p- n0 y6 P8 z; K# f3) 按键读取函数  ^( ?& D0 g& r" }& U5 P) Z4 a
2 }( F* ~3 G! l9 q+ G
为了展示效果,编写代码查询按键是否按下,按下后切换LED灯亮灭状态。. q- M! \6 q) b! E

- e* E' Q* S& C- Z/ w 代码段 12.3.3 按键读取函数(driver_key.c)6 a# C9 t2 a# ~6 i
; C( `5 N6 x3 O
  1. /*! R& O" M7 G$ T& ~# Y# h
  2. ' M8 b3 q+ B- H5 a# f0 C& ?+ o
  3. *  函数名:void KeyPolling(void)7 m! I  m* [% |! ?$ {; X" d: f9 _7 m
  4. 4 Z% W6 f$ z/ t/ E$ k! P
  5. *  输入参数:无2 F8 _0 M! ~& F( e; b, X
  6. " U% j+ P: P3 S2 |+ \
  7. *  输出参数:无
    6 _$ T# Z. @, M% \
  8. ; w2 i. G4 S9 }4 W
  9. *  返回值:无
      u. V( C& o& i' w

  10. : z) T$ D5 Y$ p' p3 k
  11. *  函数作用:使用轮询方式查询按键是否按下,通过按下控制LED灯亮灭
    0 L$ S+ y/ ~- z6 p! N3 H

  12. . J2 Z+ O/ A1 ~- Y5 Y
  13. */
    ( l3 s& e9 q* T* G1 X" C, M5 q
  14. 7 P1 X/ n$ X6 P  Y( W1 w' J
  15. static bool key_flag = false;
    - j6 @" b& C+ H  o5 s

  16. 1 `. R/ T( E# F4 \
  17. void KeyPolling(void)3 b0 p6 `: S$ I# K& _) e3 T
  18. 6 M# _$ r$ o$ _0 }- y6 N
  19. {2 F3 r. X- u" y3 p( i0 V5 L
  20. 8 H- Y8 `* W3 n3 Z9 |* `, \
  21.     if(KEY == PUSH_DOWN)          // 如果检测到按键被按下, V3 M  |4 `4 Q# a
  22. : x- z  h2 T$ E/ {% \
  23.     {: W4 `0 @, L, q  f4 S
  24. " A1 g2 b/ d0 Z, M5 B4 j5 [5 e: W
  25.         HAL_Delay(8);             // 延时8ms防按键抖动
    " }4 \1 z3 H* s4 ]( v8 P

  26. " L& ]8 k% w2 {
  27.         if(KEY == PUSH_DOWN)      // 如果防抖动后按键依然是处于被按下的状态,就认为按键被按下过# G  d  n% F- Q& r
  28. # ]) W- w1 M! J- I2 U0 [
  29.         {6 t4 J. w- W2 e) W6 D) l. ?" ^# P! C, V
  30. * i' }3 b. |! `) I: e
  31.             key_flag = !key_flag; // 用一个标志位来判断按键被按下次数,按下一次灯亮,再按一次灯灭,如此反复
    ! W! w/ x5 J7 Z# M1 U  z7 K' M; H, I
  32. / a2 r" s; V: W; ~- M1 Y
  33. BLED(key_flag?OFF:ON);
    , _$ {  ?. ?, [* z6 {8 L

  34. ! h' S8 x0 r" e
  35.         }. h  o! c5 s3 J: s" F* h1 l

  36. / y8 n7 A3 }& j! L- G' a
  37.     }$ d# i' n) {) Q1 @
  38. & B( G. r/ e' f6 ?: ^
  39. }
复制代码
9 E6 Z- T1 u8 T- I
8行:定义了一个全局变量标志位“key_flag”,作为按键被按下的标志;7 W, v( N2 U: s' n8 q7 W/ B! c
+ k/ }# d3 r  t7 q0 o6 T0 U
11行:获取该按键状态;* Q! U. Y* T' D& h

4 Z; g; _2 ?" g+ N) Z4 S- R1 N13行:延时5-10ms,软件去抖;+ }. ~0 L5 m% h$ w# }; d

' w( V% ]- m5 ~6 q14行:再次获取该按键状态,此时依旧按下,说明是正常按键操作,非抖动;
% B% B+ \4 }4 Z) y& ?2 q' d  O6 V' C
! }. _7 X0 Q! H1 C& }16行:将标志位置反,按键按一次置反一次(即0->1->0->1这样循环);
8 P, C; m6 ~. a* c5 r) Q
# v& H6 P# D( F' k& T7 h17行:根据标志位“key_flag”的值,让LED灯亮或灭;
9 c: R- |$ w' b3 {/ o0 f
; l$ z1 ]% X: \3 n此时每按下一次按键键,蓝色LED灯将亮灭交替。0 B5 ~7 b* i7 z1 O$ O
; Z/ c$ R; f+ N
$ d4 U. R0 Y" {- Z4 d
4) 主函数测试
1 |. A/ E# w/ ^- F6 E
3 w* `0 {0 @3 G8 T代码段 12.3.4 主函数控制逻辑(main.c), o6 [  |6 p; s; j" |* z; G* B
  [$ M# s3 v4 n& M
  1. // 初始化LED
    + H$ d& g6 k7 d
  2. # L/ n& M5 J; c1 ~% w; o5 P
  3. LedGpioInit();: b& L6 x% {: P& x2 b6 W

  4. : ]7 k, }: U! M- @
  5. // 初始化按键( n9 @/ H+ |" r

  6. 2 {6 J. [5 |- f/ V; [' E
  7. KeyInit();& J0 \1 g$ ~5 j  T! f% F

  8. ' H  W9 D8 Z# ]! v

  9. 4 M0 x7 ^+ @, p4 B3 w2 P9 ]

  10. % d4 t6 F7 W% h  H( \, F, N
  11. while(1)
    6 Q1 m& U4 v) q) I7 |4 ~; `% W

  12.   Q6 _: H( Y/ R
  13. {+ C. D! E4 y5 y( D& F

  14. ! N7 e. [& F  ]# `1 Y3 ]
  15.     // 轮询按键键
    % G& l9 y' t+ H
  16. 0 Y  z' V3 B7 K$ L1 e2 z) B
  17.     KeyPolling();4 N$ ~6 ^+ Z  R& s

  18. ( z4 C% Q9 t2 j$ e1 H* }8 U6 M
  19. }
复制代码

8 C: X) k) Y) I6 F' v. V5 h1~4行:初始化LED灯和按键;
5 l- H: l1 `' I% I" y" e4 h8 Q1 I; |& A8 A/ g- Y
6~10行:一直循环查询每个按键当前状态,从而判断对应按键是否按下;% p/ d- ^5 W: z8 R; b8 Z" E, u2 }
* X/ |1 g$ \- c" Y3 n# O) X& n
12.4 实验效果  I- v6 w" f% b, b9 T
本实验对应配套资料的“5_程序源码\5_GPIO—按键轮询\”。打开工程后,编译,下载。按下按键,用户LED灯亮/灭。 4 B9 B% `! Y3 y4 n8 c# H# {
& d: x% R" c. t' u% W1 L. e

0 n5 C8 }) v/ H6 Q$ C, N2 {
赞 收藏 评论0 发布时间:2022-4-28 20:53

举报

0个回答

所属标签

相似技术帖

官网相关资源

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