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

【经验分享】STM32实例-按键控制实验

[复制链接]
STMCU小助手 发布时间:2022-7-1 11:57
本章所要实现的功能是:通过开发板上的 4 个按键控制 LED。
按键介绍
    按键是一种电子开关, 使用时轻轻按开关按钮就可使开关接通, 当松开手时,开关断开。我使用的按键及内部简易图通常如下图所示。
微信图片_20220701115639.jpg
. Z$ i; v: w( c4 v2 l
    按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态,如果按键按下,初始导通状态变为断开,初始断开状态变为导通。通常的按键所用开关为机械弹性开关,当机械触点断开 、闭合时,电压信号如下图所示。
- }5 J) ]8 C# u+ `1 \; \
微信图片_20220701115824.png

* o& \0 w- b4 D, V0 {6 t9 C
    由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖。
    按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时 10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。其中延时10ms 就是软件消抖处理,至于硬件消抖,大家可以百度了解下,网上都有非常详细的介绍。
% a. ?5 k7 H) o) X. }, E
硬件设计
    我们STM32F1开发板上有4 个控制按键,其硬件电路如图所示。
8 I" M/ g% n& Y* F4 f
微信图片_20220701115829.png

9 ]. w( D$ b  Q- g' L$ n
微信图片_20220701115832.png
3 {) j) h6 ?, D* \+ c. k5 F
微信图片_20220701115838.jpg
0 P/ d3 w% h. X0 V
    从原理图我们可以知道,按键 K_UP 连接在 STM32F1 芯片的 PA0 引脚上,按键 K_LEFT、K_DOWN、K_RIGHT 分别连接在 STM32F1 芯片的 PE2、PE3、PE4 引脚上。
    需要注意的是:K_UP 按键另一端是接在 3.3V 上,按下时输入到芯片管脚即为高电平;K_LEFT、K_DOWN、K_RIGHT 按键另一端是全部接在 GND 上,这个和我们学习 51 单片机是一样的,采用独立式按键接法,按下时输入到芯片管脚即为低电平。为什么在 K_UP 上接一个 3.3V,主要是为后面章节实验使用,到时候我们再具体介绍。
3 t0 N' O5 E$ v7 `1 k, D
软件设计
    整个程序实现的流程步骤如下:
(1)初始化按键使用的端口及时钟
(2)按键检测处理
(3)按键控制处理
" g" ^) F& w5 v
按键初始化函数
- D; q  h' y+ L) H! {4 N5 h
    我们打开工程中 key.c 文件,按键初始化代码如下:
  1. /****************************************************************5 }+ m: V" ^  N" ~$ L' b
  2. * 函 数 名 : KEY_Init- x+ m5 i2 i( c
  3. * 函数功能 : 按键初始化
    : U) b2 `7 t8 W* v: k% o( Z
  4. * 输 入 : 无
    0 B) @% U: y; {8 u! p/ s/ E# c
  5. * 输 出 : 无3 ~9 S. N$ A7 q) L9 \
  6. ****************************************************************/' @; r& N) a: Y" G0 V) Q) w
  7. void KEY_Init(void)
    : K- p2 m0 s) b: E4 O7 r) @
  8. {+ p. G% H) `6 \( y' K; o
  9.   GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
      ?8 d8 m& R/ D8 u# h6 x0 s
  10.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);" |8 i! Y2 U0 c) U! Y% N
  11.   GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin; //选择你要设置的IO 口& q& g. g1 S7 B: V$ a) }8 p9 f$ q
  12.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入6 z" {+ c2 I# |4 ]1 U. M/ v& }
  13.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率; b' n7 X, ?9 R7 {: p; _
  14.   GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); /* 初始化GPIO */
    . P' U4 j  R: C9 U
  15.   GPIO_InitStructure.GPIO_Pin=KEY_DOWN_Pin|KEY_LEFT_Pin|KEY_RIGHT_Pin;9 ]# k( q: }5 Q& o
  16.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
    4 }7 q# m! f& K" r; Z0 q3 N
  17.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;- Z- G" R1 a5 K
  18.   GPIO_Init(KEY_Port,&GPIO_InitStructure);2 k) l4 ?$ f& T
  19. }
复制代码

9 }( U0 }. M/ t1 r9 o& }
    KEY_Init()函数用来初始化按键的端口及时钟。要知道按键是否按下,就需
要读取按键所对应的 IO 口电平状态,因此我们需要把 GPIO 配置为输入模式(不清楚输入模式对应的选项值,可以参考“使用库函数点亮 LED”内容,里面介绍了怎么快速查找代码),因为 K_UP 按键一端是接 3.3V 的,当按下后 PA0管脚即为高电平,所以需要将 PA0 管脚配置为下拉输入模式 GPIO_Mode_IPD,这样 PA0 管脚的默认电平就为低电平,如果读取到 PA0 管脚的电平为高电平时,就说明 K_UP 按键按下。其他几个按键是共地,所以需要配置为上拉输入模式GPIO_Mode_IPU,分析方法和 K_UP 类似。
    在函数内我们看到有几个参数不是库函数内的,比如KEY_LEFT_Pin、KEY_Port,这种情况一般是我们自己定义的宏,通常放在对应的头文件内,我们打开 key.h,可以看到如下代码:
  1. #ifndef _key_H
      S# F5 b( X/ h! T( I
  2. #define _key_H6 Y0 t: ^( U. \; e$ ^9 m
  3. #include "system.h"# X7 K- \  M, o7 z6 ^
  4. #define KEY_LEFT_Pin GPIO_Pin_2 //定义 K_LEFT 管脚+ s1 S( @4 U" }2 j" a: T1 X, m
  5. #define KEY_DOWN_Pin GPIO_Pin_3 //定义 K_DOWN 管脚, u6 {0 e. \2 Z) ]0 V# S8 G
  6. #define KEY_RIGHT_Pin GPIO_Pin_4 //定义 K_RIGHT 管脚7 A' e' E8 m  c% r- b2 S3 ?1 h" A8 ?# f
  7. #define KEY_UP_Pin GPIO_Pin_0 //定义 KEY_UP 管脚; _$ t$ o, g2 _/ [- \& J0 p
  8. #define KEY_Port (GPIOE) //定义端口6 c3 u& Z0 {7 J3 e- I6 S& f& |
  9. #define KEY_UP_Port (GPIOA) //定义端口+ f  ~& W6 F" T4 {. v8 H/ B& A
  10. //使用位操作定义
    6 b2 Y5 s3 H2 L& ]0 d& F5 T
  11. #define K_UP PAin(0): n5 e9 t3 y: {) b; s4 I
  12. #define K_DOWN PEin(3)
      F# o; J( f  Y- g" `$ G9 v, x4 f
  13. #define K_LEFT PEin(4)6 ]9 K. @, t7 Y3 a2 R; }5 B
  14. #define K_RIGHT PEin(2)( O: U' o. b1 @$ N7 u
  15. //使用读取管脚状态库函数定义
    5 M9 P1 t( m9 Y7 |( e; o
  16. //#define K_UP GPIO_ReadInputDataBit(KEY_UP_Port,KEY_UP_Pin)5 M. I! o( z! z) M% V, ]
  17. //#define K_DOWN GPIO_ReadInputDataBit(KEY_Port,KEY_DOWN_Pin)) a- P4 y( j: g1 p3 K7 H8 Z
  18. //#define K_LEFT GPIO_ReadInputDataBit(KEY_Port,KEY_LEFT_Pin)0 d1 R: T! K$ W0 s
  19. //#define K_RIGHT GPIO_ReadInputDataBit(KEY_Port,KEY_RIGHT_Pin)/ F1 w, e1 W. s: d0 c9 |
  20. //定义各个按键值
    $ O5 w4 a# t$ T
  21. #define KEY_UP 1/ i' c  x4 x* u
  22. #define KEY_DOWN 2
    + M. Z9 c, R* f! B7 i
  23. #define KEY_LEFT 3
    5 }0 O! W  `$ V# [# m
  24. #define KEY_RIGHT 4
    3 d: ?: [5 B$ F% a8 A
  25. void KEY_Init(void);
    ; M: W' e- S0 b" M
  26. u8 KEY_Scan(u8 mode);
    * e, r. q% x( a
  27. #endif
复制代码
( U( G9 ~6 s) {6 s" X. l% e
    key.h 文件内定义了按键的端口、管脚、读取管脚的电平状态、函数声明等信息。里面使用了两种方式定义读取管脚电平宏,一种是通过位操作,另一种是通过读取管脚电平的库函数。由于位操作比较方便,因此我们就使用位操作的方式,库函数定义的宏全部注释掉。
" }- y! g' l1 R
按键检测函数
    要知道哪个按键被按下,就需要编写按键检测函数,具体代码如下:
  1. /***************************************************************
    ! P: Z4 Q1 J9 |# g2 R
  2. * 函 数 名 : KEY_Scan
    7 \7 F2 S/ X( Y
  3. * 函数功能 : 按键扫描检测
    & W. t2 ]% a5 K, b
  4. * 输 入 : mode=0:单次按下按键4 }3 w/ o1 d1 ^7 t# a  A
  5. mode=1:连续按下按键1 z1 J( x2 f  _3 L# v( f; @' q$ o
  6. * 输 出 : 0:未有按键按下
    0 _  H9 w+ s/ Y9 _7 {6 m
  7. KEY_UP:K_UP 键按下+ U) P1 y. F+ q8 I
  8. KEY_DOWN:K_DOWN 键按下' V" C7 j% @; j* `8 d: d% O6 {$ G
  9. KEY_LEFT:K_LEFT 键按下
    6 P, F/ C1 [+ O3 A0 S# [, K3 ^
  10. KEY_RIGHT:K_RIGHT 键按下
    ( j2 p8 z+ l2 C& X: t
  11. ****************************************************************/! G5 w7 D: x9 p" j9 F9 L: e
  12. u8 KEY_Scan(u8 mode)
    4 A' {$ J' p. I9 |
  13. {. G3 L0 G5 V2 q  B0 x# `& a
  14.   static u8 key=1;2 I" S9 `, b) Q0 o9 b+ Q/ ]1 }
  15.   if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
    3 @% [0 j' z8 |0 S
  16.   {
    ' T. o% L% m4 p! ~' w7 Q8 C
  17.     delay_ms(10); //消抖
    ; q8 G: M$ z: b' Y' q( c
  18.     key=0;
    7 S9 ]$ c, l  J8 O; }
  19.     if(K_UP==1), O( f. N( X* V* ]: P6 S
  20.     {
    , c/ S+ K! {- a* K
  21.       return KEY_UP;
    # r  t) y. W! L. I1 L6 Q; Z0 P7 f( W# Q
  22.     }) {% ?# ?; D8 ?% X! J( E% d
  23.     else if(K_DOWN==0)
    ) i( L  r& |3 ]" e( r
  24.     {! ?# B" |) @- G2 S9 J) i+ j
  25.       return KEY_DOWN;
    / l( L* ?1 c8 m+ Z- K
  26.     }
    5 ^8 q" }2 K- q4 J) Z8 e
  27.     else if(K_LEFT==0)
    8 Y5 c+ Y9 E8 x# j. ?& V, d4 ^
  28.     {
    & p; s! D* I0 _# a+ @9 I
  29.       return KEY_LEFT;
    ) q+ h3 s4 l; e* e  j% T
  30.     }2 F. L. G2 p5 c
  31.     else9 z. @1 K4 H% i
  32.     {$ o0 E5 Y. V: P  O! c! A- W/ k
  33.       return KEY_RIGHT;
    8 [. K# G( |. \* S6 N8 [
  34.     }
      B5 A. y& x/ N5 C0 C/ x) |$ U
  35.   }
    ; L  v5 t7 c! F' J; ^
  36.   else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下$ {! \8 T' t3 J$ U% o! Z0 y2 N
  37.   {! b2 f) m7 s, t; a3 }5 Y  P
  38.     key=1;1 N+ m$ K5 Z2 O  i9 `6 }) a
  39.   }% m) N" Q" u$ D5 b" `1 r
  40.   if(mode==1) //连续按键按下
    7 \: N+ f$ J- g( S$ ^' }, W$ [
  41.   {9 q" D; K9 F: `' Y
  42.     key=1;4 i) E3 m4 ^; S' `
  43.   }( a4 p: [3 O& [, Q+ t/ s4 ?" m
  44.   return 0;( z2 W. o/ |/ Y- Q, i5 l* O! y
  45. }
复制代码

: R9 G* W& E; `0 c% E
    KEY_Scan 函数带一个形参 mode,该参数用来设定是否连续扫描按键,如果mode 为 0,只能操作一次按键,只有当按键松开后才能触发下次的扫描,这样做的好处是可以防止按下一次出现多次触发的情况。如果 mode 为 1,函数是支持连续扫描的,即使按键未松开,在函数内部有 if(mode==1)这条判断语句,因此key 始终是等于 1 的,所以可以连续扫描按键,当按下某个按键,会一直返回这个按键的键值,这样做的好处是可以很方便实现连按操作。函数内的 delay_ms(10)即为软件消抖处理,通常延时 10ms 即可。
    KEY_Scan 函数还带有一个返回值,如果未有按键按下,返回值即为 0,否则返回值即为对应按键的键值,如 KEY_UP、KEY_DOWN、KEY_LEFT、KEY_RIGHT,这都是头文件内定义好的宏,方便大家记忆和使用。函数内定义了一个 static 变量,所以该函数不是一个可重入函数。还有一点要注意的就是该函数按键的扫描是有优先级的,因为函数内用了 if...else if...else 格式,所以最先扫描处理的按键是 K_UP,其次是 K_DOWN,然后是 K_LEFT,最后是 K_RIGHT。如果需要将其优先级设置一样,那么可以全部用 if 语句。
8 H4 G6 q. p! i9 e4 M
主函数
    最后我们看下主函数,代码如下:
  1. /***************************************************************
    9 r% e$ r$ D& I4 x. s# B
  2. * 函 数 名 : main( R  _' K2 G$ \( _- B4 V! ~9 M
  3. * 函数功能 : 主函数; `" o0 T1 F! `" m1 f( K
  4. * 输 入 : 无4 h6 v. D* p1 ]7 D
  5. * 输 出 : 无  c& p+ x( T/ @/ K: A
  6. ****************************************************************/
    # J9 N6 M1 P) j0 u) k% S' V& \
  7. int main()
    ( H9 g: B* V) B7 r$ K0 T& |
  8. {
    , |  g! T2 g1 `
  9.   u8 key,i;: N! Z, a4 _- Q  _; u& N
  10.   SysTick_Init(72);
    : v* i! P7 p4 g, |! A* n
  11.   LED_Init();9 c8 P; O! O' D; P, c1 W% w
  12.   KEY_Init();8 I4 M" x# i3 e' `9 M
  13.   while(1): p' h+ K+ [/ r) u& I5 f+ u5 @
  14.   {0 h% ]0 C6 c5 v" [3 H) R
  15.     key=KEY_Scan(0); //扫描按键1 i5 Y) W# H" r$ t. K) a; i: b  D
  16.     switch(key)
    , m- j/ D) N' W6 t4 _# g
  17.     {3 G) o- {7 i9 N0 p4 q/ ^
  18.       case KEY_UP: led2=0;break; //按下 K_UP 按键 点亮D2 指示灯" B1 U; {0 Y, ~9 E, B  `; T8 S
  19.       case KEY_DOWN: led2=1;break; //按下 K_DOWN 按键 熄灭D2 指示灯
    ! @* _5 i* t: o. }) m: G
  20.       case KEY_LEFT: led3=1;break; //按下 K_LEFT 按键 点亮D3 指示灯
    7 e  s) S, B- u% d
  21.       case KEY_RIGHT: led3=0;break; //按下 K_RIGHT 按键 熄灭D3 指示灯" o7 \# C/ b# S# W" D" ^- T& \
  22.      }7 l& U0 ~; J# {+ s5 F$ I
  23.     i++;
    ( I$ z! N2 P+ M. p: w$ ], [- D7 q
  24.     if(i%20==0)$ S" S4 q) G  x
  25.     {0 [0 |' l. ~5 l2 g( o1 b
  26.       led1=!led1; //LED1 状态取反
    7 ]( @4 Z; k: {& c! Z+ k& P; T
  27.     }0 c7 I9 p: n5 K2 t
  28.     delay_ms(10);+ |" e# s2 M: [+ c6 D. {
  29.   }
    3 W: o7 ?) O5 J$ v0 p: T! e
  30. }
复制代码
0 H! @( X& a& ~5 Q
    主函数实现的功能比较简单,首先将使用到的硬件初始化(这里说的初始化表示端口和时钟全部初始化,后面就不再强调),比如 LED、蜂鸣器和按键。然后在 while 循环内调用按键扫描函数,扫描函数传入的参数值为 0,即 mode=0,所以这里只对它单次按键操作,将扫描函数返回后的值保存在变量 key 内,通过switch 语句进行比较,从而控制 LED。
    程序后面的 if(i%20==0)判断语句, 表示当 i 能够整除 20 的时候就进入 LED状态翻转,后面有一个 delay_ms(10)延时;也就是间隔 200ms,led1 就会翻转一次状态。将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁。当按下 K_UP 键,D2 指示灯点亮;当按下 K_DOWN 键,D2 指示灯熄灭;当按下 K_LEFT 键,D3 指示灯熄灭,当按下 K_RIGHT 键,D3 指示灯点亮。

+ i5 _1 m7 w  t) [  P& S; G+ ]
收藏 评论0 发布时间:2022-7-1 11:57

举报

0个回答

所属标签

相似技术帖

官网相关资源

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