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

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

[复制链接]
STMCU小助手 发布时间:2022-7-1 11:57
本章所要实现的功能是:通过开发板上的 4 个按键控制 LED。
按键介绍
    按键是一种电子开关, 使用时轻轻按开关按钮就可使开关接通, 当松开手时,开关断开。我使用的按键及内部简易图通常如下图所示。
微信图片_20220701115639.jpg

. C) A8 X* D( N- M# N2 m' s! u
    按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态,如果按键按下,初始导通状态变为断开,初始断开状态变为导通。通常的按键所用开关为机械弹性开关,当机械触点断开 、闭合时,电压信号如下图所示。
1 A3 f, O$ ~* G+ Y9 `: h% f
微信图片_20220701115824.png
2 C% z6 y% c6 y  Z
    由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖。
    按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时 10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。其中延时10ms 就是软件消抖处理,至于硬件消抖,大家可以百度了解下,网上都有非常详细的介绍。
' l2 x; @9 u% j$ r* `! [
硬件设计
    我们STM32F1开发板上有4 个控制按键,其硬件电路如图所示。

6 m* I' \& k( F" e
微信图片_20220701115829.png

6 Z4 Z8 J0 c" k0 {4 M6 h* w5 F! g
微信图片_20220701115832.png

& M+ i: |2 ~# }" q% K' W
微信图片_20220701115838.jpg
$ i9 S$ z) x& B" |) m
    从原理图我们可以知道,按键 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,主要是为后面章节实验使用,到时候我们再具体介绍。

& f& r) S9 n0 o# v- \! o0 h6 T
软件设计
    整个程序实现的流程步骤如下:
(1)初始化按键使用的端口及时钟
(2)按键检测处理
(3)按键控制处理

, e( ^: I8 s; p6 i, r
按键初始化函数8 K5 N& X3 Y6 R+ ]% N8 |! ~
    我们打开工程中 key.c 文件,按键初始化代码如下:
  1. /****************************************************************
    - S0 J# {) A5 n% r" \5 d
  2. * 函 数 名 : KEY_Init7 D4 a. ~3 z* o4 p
  3. * 函数功能 : 按键初始化3 P2 t+ D5 s: r* I7 u; a, D
  4. * 输 入 : 无
    $ d2 H) a$ h* {3 Q# _6 Q' J
  5. * 输 出 : 无
    + I5 k# d1 P7 W1 e  b
  6. ****************************************************************/
    : U0 B+ N" f% I7 p
  7. void KEY_Init(void)
    8 M! ^- k) z0 y
  8. {6 ~6 Z/ P3 f& w. I
  9.   GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量- M; a1 @6 ]7 [0 i3 K& v: l
  10.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);% h( u9 t9 e0 K3 U# B% d1 Y
  11.   GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin; //选择你要设置的IO 口$ R, s1 S# S  V5 {" F. V/ X* b7 W
  12.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入
    $ L3 M# P" G8 W" Q
  13.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率' a/ ]+ x5 O5 q9 K# D) [5 E2 _
  14.   GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); /* 初始化GPIO */
    ( d' b" m/ B7 V, T. Y5 X4 }4 ~& v( c3 Y  z
  15.   GPIO_InitStructure.GPIO_Pin=KEY_DOWN_Pin|KEY_LEFT_Pin|KEY_RIGHT_Pin;
    / I- `3 R4 h, H/ h
  16.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入) s3 n9 H9 V1 n# h- \
  17.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    & \2 U- x/ h+ j5 Z$ V
  18.   GPIO_Init(KEY_Port,&GPIO_InitStructure);
    ! m2 O$ }- u) b6 h4 P
  19. }
复制代码
1 A) \2 G, ~8 k* l8 E& Z
    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_H7 Q' E, `* q  G5 c4 ~; c, ?' i; V. ~
  2. #define _key_H
    5 g7 U( S; k5 U/ k1 y0 P
  3. #include "system.h"6 P7 C+ i7 a  C! l, i
  4. #define KEY_LEFT_Pin GPIO_Pin_2 //定义 K_LEFT 管脚7 s# g0 ~+ f0 F6 b' ]
  5. #define KEY_DOWN_Pin GPIO_Pin_3 //定义 K_DOWN 管脚/ Y2 L( b2 W; j9 @! _2 `9 |: k
  6. #define KEY_RIGHT_Pin GPIO_Pin_4 //定义 K_RIGHT 管脚5 R& h- f) Y6 F
  7. #define KEY_UP_Pin GPIO_Pin_0 //定义 KEY_UP 管脚
    * a- a: E, Y) J7 ~
  8. #define KEY_Port (GPIOE) //定义端口
    ! z5 V$ x; G. W4 I7 w) }
  9. #define KEY_UP_Port (GPIOA) //定义端口& h: E' k) _8 }$ O% V- u
  10. //使用位操作定义
    / C$ k  A2 l% K, J$ B
  11. #define K_UP PAin(0)
    6 `6 N) ~' E3 i+ z  _3 Y' [- U
  12. #define K_DOWN PEin(3)( v' v! S/ P8 P# B6 ?9 o* o
  13. #define K_LEFT PEin(4)5 h# Q# X9 h: Y0 V4 V, B
  14. #define K_RIGHT PEin(2)
    # j9 P1 T0 K* H/ J
  15. //使用读取管脚状态库函数定义
    $ m; _, z$ X- ^& M
  16. //#define K_UP GPIO_ReadInputDataBit(KEY_UP_Port,KEY_UP_Pin)
    ( A( E# j. R/ O0 {. I7 M- w
  17. //#define K_DOWN GPIO_ReadInputDataBit(KEY_Port,KEY_DOWN_Pin)
    4 |' k7 P+ R3 C* v
  18. //#define K_LEFT GPIO_ReadInputDataBit(KEY_Port,KEY_LEFT_Pin)
    1 I+ S( e5 s( {5 R4 Z
  19. //#define K_RIGHT GPIO_ReadInputDataBit(KEY_Port,KEY_RIGHT_Pin)
    5 _" A2 S4 r* ]: S/ T9 L' c
  20. //定义各个按键值
      Z2 D# a. E9 o+ o; r8 o6 A
  21. #define KEY_UP 1* q$ W  B* n4 E+ }6 {
  22. #define KEY_DOWN 2
    , h* b' H% l" |& X" _
  23. #define KEY_LEFT 3
    3 m& X% b* P1 U3 q9 G
  24. #define KEY_RIGHT 4
    ; F; q, r: i5 q7 c
  25. void KEY_Init(void);+ v$ v+ G0 x$ F# L1 g8 h) k& ?
  26. u8 KEY_Scan(u8 mode);1 }" N* s" O2 a! u+ r, I# K0 F8 c- |
  27. #endif
复制代码

! z7 o+ c2 e; `8 K" a% H* b
    key.h 文件内定义了按键的端口、管脚、读取管脚的电平状态、函数声明等信息。里面使用了两种方式定义读取管脚电平宏,一种是通过位操作,另一种是通过读取管脚电平的库函数。由于位操作比较方便,因此我们就使用位操作的方式,库函数定义的宏全部注释掉。

5 D0 l# Y- T& w3 J& w7 R7 A
按键检测函数
    要知道哪个按键被按下,就需要编写按键检测函数,具体代码如下:
  1. /***************************************************************
    % n; v5 E# y9 F0 f6 U( Q/ v. Z3 p
  2. * 函 数 名 : KEY_Scan
    ! ]8 p& @- W- ?9 D. n2 v$ Q
  3. * 函数功能 : 按键扫描检测8 s. e% r' s2 s2 T1 p
  4. * 输 入 : mode=0:单次按下按键9 J4 _- U3 N: R/ \- t& H1 k5 q: k" R$ [
  5. mode=1:连续按下按键. z/ X  o7 @/ |& j/ I1 \$ c( e
  6. * 输 出 : 0:未有按键按下
    , w1 r# X. T& ^- K' `, F+ X
  7. KEY_UP:K_UP 键按下5 ~- M1 j+ D3 a7 A/ P
  8. KEY_DOWN:K_DOWN 键按下9 S+ ^- q7 s: x
  9. KEY_LEFT:K_LEFT 键按下$ s7 {3 |. O9 Y6 q- s; j
  10. KEY_RIGHT:K_RIGHT 键按下1 d+ Z  |# X" g) L' ?7 [
  11. ****************************************************************/5 O9 X1 ]- X) s4 [6 w, a4 C- w; h5 w
  12. u8 KEY_Scan(u8 mode)- A& {; r5 M& R, _" Z2 R% j
  13. {9 H! f1 S" z0 M) P- T3 a# l( r
  14.   static u8 key=1;
    1 |' z* }8 ~% ~$ J. z+ D2 I
  15.   if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
    " @6 X1 U6 d, D5 X- G
  16.   {7 {/ Y. R9 G- `$ w9 M) D
  17.     delay_ms(10); //消抖- B+ {9 w# g: }# B4 E8 ~( M& B
  18.     key=0;* e$ q5 s5 M: T$ h0 i) N
  19.     if(K_UP==1)
    * @( ^& i/ D0 v. K
  20.     {
    ; w5 N. ]) j1 ]7 `. W, W) Q! X3 m
  21.       return KEY_UP;8 ?" b$ w$ q8 ^7 A& x7 ?0 O
  22.     }% H; t" k! v3 l# N
  23.     else if(K_DOWN==0)
    9 H3 j$ j% T/ N* }5 _$ p
  24.     {
    # D* Q+ V; T# Y+ w
  25.       return KEY_DOWN;! z3 ^- u6 M& A: l( h' A2 e+ X- a0 k
  26.     }0 C" {1 F# q  h+ v
  27.     else if(K_LEFT==0)
      D& G& j5 u0 Q) D) L4 j
  28.     {
    , t# q$ |) J& e0 y, l
  29.       return KEY_LEFT;
    * S7 Z5 g7 J& T: W
  30.     }* m, M5 x7 B9 P' z5 ~& c
  31.     else( @' H. ^, R0 G- i# Z: O
  32.     {- M3 _$ G. ~! Z) N4 d
  33.       return KEY_RIGHT;
    % V* [3 `6 L! V' `: |# G! _9 r9 n
  34.     }
    / k* D9 C5 W+ b6 U) d
  35.   }
    $ \& J1 P( i& w: D: X2 z
  36.   else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下( ?* _, O  q7 D8 r! f
  37.   {
    : z! k2 r/ W  E) ~# \
  38.     key=1;0 u5 I0 D+ I' B: c  M9 z
  39.   }$ C7 g% [" l. L6 ~/ r
  40.   if(mode==1) //连续按键按下
    * H# Y; g% H+ B$ e; [
  41.   {4 T3 f1 b/ S" A
  42.     key=1;
    * F, \8 D) P- G6 M8 e( }9 {
  43.   }
      x! x9 O4 u2 o' j. y
  44.   return 0;
      v0 |1 n" _% S3 c* ~5 w
  45. }
复制代码
. h- m+ D! y0 y& v# S. e" K0 w
    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 语句。

: F& q7 m2 ~2 z0 n; I, Z/ \
主函数
    最后我们看下主函数,代码如下:
  1. /***************************************************************! j' ~& H8 j" V3 `5 p4 o/ i- }0 s5 w
  2. * 函 数 名 : main
    - }- R$ @+ O( `* n! x
  3. * 函数功能 : 主函数
    & F* D/ q1 j$ C3 T' U5 j  g
  4. * 输 入 : 无
    + @6 `: n) o& E* ]. ^5 t
  5. * 输 出 : 无8 M- ~8 `  P% |$ x" \& c. z
  6. ****************************************************************/9 u; V+ l/ G$ @2 R5 \& o7 B
  7. int main()  K9 m% L) o0 r) O" G+ ?+ D
  8. {' o1 p. a1 C) u& c4 b
  9.   u8 key,i;( ?! E( i/ F, y2 M# ?" ]5 U* Y
  10.   SysTick_Init(72);
    7 M3 p2 O* l! {6 I2 g; M* n
  11.   LED_Init();: ~4 t8 \. k2 T+ g, W6 w9 a; d
  12.   KEY_Init();& D6 r  Y7 e1 G
  13.   while(1)
    ; ?. V* Q, \. y( o8 {0 q( {; ]. T* t! K
  14.   {
    + W' a8 g3 O5 \6 u2 z
  15.     key=KEY_Scan(0); //扫描按键
    5 C+ Z4 @9 W% o4 G9 Z
  16.     switch(key), K7 S& p  J* I
  17.     {1 Z6 p' ]: E) F* K# P- r7 Y
  18.       case KEY_UP: led2=0;break; //按下 K_UP 按键 点亮D2 指示灯
    0 W3 M- H3 L/ E$ d
  19.       case KEY_DOWN: led2=1;break; //按下 K_DOWN 按键 熄灭D2 指示灯
    8 P! v: Z0 \% t; p
  20.       case KEY_LEFT: led3=1;break; //按下 K_LEFT 按键 点亮D3 指示灯
    : [/ x) T  Y' e) M3 W
  21.       case KEY_RIGHT: led3=0;break; //按下 K_RIGHT 按键 熄灭D3 指示灯; D" L+ Q3 ]7 B" b0 H& q
  22.      }" i- {- U% r2 x8 L1 x
  23.     i++;
    9 i2 f1 Z: I0 W, q; Z3 |
  24.     if(i%20==0)
    + X9 c% m7 _) V3 V: A
  25.     {
    : O# Z* h8 g$ J1 |9 P2 v
  26.       led1=!led1; //LED1 状态取反
    ! W/ K0 E* q3 D3 ~2 X! K1 @
  27.     }, e3 n$ y0 a! i. _
  28.     delay_ms(10);) M! Q) {( o& i4 S+ m
  29.   }" G, `) G- I. F$ ]" F5 p& N
  30. }
复制代码

: ]( s# o: |, G3 [
    主函数实现的功能比较简单,首先将使用到的硬件初始化(这里说的初始化表示端口和时钟全部初始化,后面就不再强调),比如 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 指示灯点亮。

  W! [* Y1 \( r7 j
收藏 评论0 发布时间:2022-7-1 11:57

举报

0个回答

所属标签

相似分享

官网相关资源

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