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

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

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

2 n. B: p" ?4 H$ [
    按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态,如果按键按下,初始导通状态变为断开,初始断开状态变为导通。通常的按键所用开关为机械弹性开关,当机械触点断开 、闭合时,电压信号如下图所示。
5 z9 i( t8 B- S
微信图片_20220701115824.png

: C0 |7 A4 L6 c8 r
    由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖。
    按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时 10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。其中延时10ms 就是软件消抖处理,至于硬件消抖,大家可以百度了解下,网上都有非常详细的介绍。
) X' M! ~. C4 v6 j. b
硬件设计
    我们STM32F1开发板上有4 个控制按键,其硬件电路如图所示。

: [+ j/ r  |* i( T0 f/ ]
微信图片_20220701115829.png

: o( s( F6 O3 ]3 v* {
微信图片_20220701115832.png
& X( a  Y& y" l' d. _  s
微信图片_20220701115838.jpg

; U! m# ?1 b* o7 ^7 q0 _) K/ }# c
    从原理图我们可以知道,按键 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 g- C0 i* Z$ m& k5 W' v  L! h' P7 Z( p, }
软件设计
    整个程序实现的流程步骤如下:
(1)初始化按键使用的端口及时钟
(2)按键检测处理
(3)按键控制处理
, S4 G9 Z0 M4 q/ f/ F5 Z3 ]
按键初始化函数
( @" C6 X; K8 ]: w
    我们打开工程中 key.c 文件,按键初始化代码如下:
  1. /****************************************************************2 o% t/ n! O* b/ f4 z5 ]9 n. I
  2. * 函 数 名 : KEY_Init. J# E& t2 `3 P' `
  3. * 函数功能 : 按键初始化7 q7 F# X: T9 O
  4. * 输 入 : 无3 l' Z1 O1 O; H& ?, O8 d, V
  5. * 输 出 : 无. [1 j* x$ \7 T  j
  6. ****************************************************************/
    / G; f% p/ \5 E- A& H. K( T2 t# {; H
  7. void KEY_Init(void)( d* }$ Y7 e  a
  8. {7 F% i) S0 c. g
  9.   GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量$ w, I1 d! C6 \% t
  10.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);
    1 }: ?' ^. b  k# r. Q. R$ a
  11.   GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin; //选择你要设置的IO 口
      ?( p4 }& q2 B1 Z- V% p
  12.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入
    / g2 `% n( N! c, X: [3 p/ O
  13.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率1 s" f. y5 [) ^* x
  14.   GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); /* 初始化GPIO */6 j) G7 i# P1 L+ `; \
  15.   GPIO_InitStructure.GPIO_Pin=KEY_DOWN_Pin|KEY_LEFT_Pin|KEY_RIGHT_Pin;
    6 c% U, u# v  R
  16.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入  s1 k* ?$ W0 g! |. m; B
  17.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    $ `7 p% `6 q  W. [' t+ @$ R) _* N
  18.   GPIO_Init(KEY_Port,&GPIO_InitStructure);
    - n& A& N" @5 {5 m! y$ v
  19. }
复制代码

! i& e$ j- D7 ^) R
    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# m& v% E6 [8 _
  2. #define _key_H, s* T; H& m+ P& Z5 S$ W
  3. #include "system.h"
    ; ?' F: S% c8 P, _
  4. #define KEY_LEFT_Pin GPIO_Pin_2 //定义 K_LEFT 管脚
    2 I. G" T9 C. L3 M
  5. #define KEY_DOWN_Pin GPIO_Pin_3 //定义 K_DOWN 管脚4 l1 @$ @+ m  K! P1 _5 b" G4 M
  6. #define KEY_RIGHT_Pin GPIO_Pin_4 //定义 K_RIGHT 管脚7 d- K- T, P! A5 Z: O: t
  7. #define KEY_UP_Pin GPIO_Pin_0 //定义 KEY_UP 管脚/ d, Z9 v% `6 z, z
  8. #define KEY_Port (GPIOE) //定义端口: m! ]* R- |3 ]5 ^1 F! f
  9. #define KEY_UP_Port (GPIOA) //定义端口
    7 `+ a) E1 T7 M0 t, t$ G
  10. //使用位操作定义. B9 n5 _& @3 z) a2 j% ^2 y$ G, q
  11. #define K_UP PAin(0)/ o8 X" o5 _; S4 X! ~+ @
  12. #define K_DOWN PEin(3)6 C3 ^  {! ]- \+ X1 Q8 j0 \7 g
  13. #define K_LEFT PEin(4)8 Y. h+ z) C) d4 K
  14. #define K_RIGHT PEin(2)
    # X- S* C! R1 w, W
  15. //使用读取管脚状态库函数定义
    . e5 P& K1 v4 A. X
  16. //#define K_UP GPIO_ReadInputDataBit(KEY_UP_Port,KEY_UP_Pin)1 s. I8 y. K7 e5 y* ^( Q
  17. //#define K_DOWN GPIO_ReadInputDataBit(KEY_Port,KEY_DOWN_Pin)
    1 m9 H+ s2 w* y. n1 [5 }4 M
  18. //#define K_LEFT GPIO_ReadInputDataBit(KEY_Port,KEY_LEFT_Pin)
    2 J3 k  a, g4 A. B
  19. //#define K_RIGHT GPIO_ReadInputDataBit(KEY_Port,KEY_RIGHT_Pin)* g1 h4 b3 V7 v" O
  20. //定义各个按键值
    + ]5 b& D; V5 f/ N' a
  21. #define KEY_UP 17 F; e9 d5 N, E: l6 p& x8 G0 b
  22. #define KEY_DOWN 2
    8 M2 M' {: f1 T6 u+ S* U8 d
  23. #define KEY_LEFT 3$ ^% c* i3 c2 ?* E3 B" N/ e) D7 L, b
  24. #define KEY_RIGHT 4
    * W2 R4 v* u" n7 a
  25. void KEY_Init(void);
    ; Z1 g; g' E8 i/ v! {
  26. u8 KEY_Scan(u8 mode);$ E$ {/ p* k% ^' U6 e% \
  27. #endif
复制代码
* M, _. ?3 I" A3 j2 z6 F
    key.h 文件内定义了按键的端口、管脚、读取管脚的电平状态、函数声明等信息。里面使用了两种方式定义读取管脚电平宏,一种是通过位操作,另一种是通过读取管脚电平的库函数。由于位操作比较方便,因此我们就使用位操作的方式,库函数定义的宏全部注释掉。
* c$ O$ v( B) \+ Z! B1 @) a
按键检测函数
    要知道哪个按键被按下,就需要编写按键检测函数,具体代码如下:
  1. /***************************************************************
    - [% x$ O1 B. [! Q
  2. * 函 数 名 : KEY_Scan
    . N% [# d. z" Y4 J) G
  3. * 函数功能 : 按键扫描检测- ^. C, b7 ~9 Z$ x+ V# Y1 B
  4. * 输 入 : mode=0:单次按下按键2 s6 F, x5 ]2 o2 s' Q4 W
  5. mode=1:连续按下按键1 u9 F6 z4 [3 z" E3 {0 d5 g. M
  6. * 输 出 : 0:未有按键按下& M5 F2 o' m7 ?) ~5 I
  7. KEY_UP:K_UP 键按下3 U5 V# |( D& m) a
  8. KEY_DOWN:K_DOWN 键按下( d: n. E2 d$ Z% @. I) `" _% r
  9. KEY_LEFT:K_LEFT 键按下" ^7 L3 G& G" _9 r2 q
  10. KEY_RIGHT:K_RIGHT 键按下2 C; H  I/ I# ]3 F7 }- H. W
  11. ****************************************************************/$ j) a, C) Q6 j6 o  |: {
  12. u8 KEY_Scan(u8 mode)
    + a! l; C0 j; {' I5 K- o. D1 [
  13. {
    5 C5 U/ t# ^# y/ ^9 s: q2 e
  14.   static u8 key=1;
    & t! Z7 }7 _; ~
  15.   if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下8 j% d, S! g' h! Q; q; T
  16.   {
    ; }8 E: W$ u  k# }
  17.     delay_ms(10); //消抖7 O2 m+ G7 }5 i! w! b! \9 b
  18.     key=0;
    : J7 }1 U& x+ ~
  19.     if(K_UP==1)0 E; |' J' v" i1 _2 s$ P; f
  20.     {
    # g! P: A) P  ~" }, P
  21.       return KEY_UP;, _/ q3 r5 P4 _4 ]
  22.     }+ f+ ^6 P7 @6 A. S* P' y7 ]3 W
  23.     else if(K_DOWN==0)9 M) D5 E# I' n/ c" _0 c* ]
  24.     {- U! d) O; K( _4 M' m$ E
  25.       return KEY_DOWN;
    0 Y  e* J7 `0 h# P1 O$ ]: a7 x
  26.     }
    ) L* [! c3 t, g! ^# d
  27.     else if(K_LEFT==0)( m; h0 Y5 m& @" }3 \
  28.     {
    , ]1 O8 ^  p0 O" H4 C& _9 ^7 D& v
  29.       return KEY_LEFT;
    ) j, }+ H: |: j9 V
  30.     }* k7 g/ W+ F1 Y9 ^, n
  31.     else
    : X' `0 k$ D* u9 Q
  32.     {
    * J3 B" @2 y9 H- O9 v$ i
  33.       return KEY_RIGHT;4 F4 R- H* S3 y% o8 [
  34.     }
    & {9 b; h4 B& }; t! n' c) V; Y/ |* m, R
  35.   }
    # p/ o- _- B  T- M9 L& Q
  36.   else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下; I0 ]  j! [8 l1 z! c0 C
  37.   {' K1 }/ P/ S3 o# b3 M3 @
  38.     key=1;
    9 w1 ?7 w& V' `  E/ r
  39.   }4 S- b' `$ }2 n$ a
  40.   if(mode==1) //连续按键按下0 V, {6 \! v& ]0 ^5 Y
  41.   {
    # i3 ?1 E; U0 F
  42.     key=1;  U/ J' J- x6 Q. h. P; j
  43.   }: g5 ^$ f! W0 y
  44.   return 0;
    8 ^: m+ t; C" P6 Y' y: c
  45. }
复制代码

; {" L0 ~3 d, ]+ J) @8 n
    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 语句。
/ D: A: D7 N. t) {8 z
主函数
    最后我们看下主函数,代码如下:
  1. /***************************************************************7 M! r/ N- A7 x/ w( k" u
  2. * 函 数 名 : main# x9 K) T+ y$ O9 P/ f; ]* g
  3. * 函数功能 : 主函数
    / c8 W, I% e) Z( O- y
  4. * 输 入 : 无6 L; J" h$ _9 V+ r+ _& x1 H
  5. * 输 出 : 无2 s  z, W; h  n
  6. ****************************************************************/1 u7 o) P; p3 \! Y! w
  7. int main()
    9 N, _( E2 @$ s* b( V5 @4 C4 C
  8. {
    . T4 y1 X( g$ ~! I, h% A) L$ v0 E
  9.   u8 key,i;. u+ D; u/ g7 x4 L4 P
  10.   SysTick_Init(72);8 G4 Y( F! v- i; \* j- ^
  11.   LED_Init();
    ' {2 f; c' i+ M
  12.   KEY_Init();- M9 D: y; g9 q# u* a! K" j* q
  13.   while(1)' |: I8 }8 o9 g6 }0 r3 ~% J
  14.   {* v, Q% W6 j0 d3 H/ t0 ]
  15.     key=KEY_Scan(0); //扫描按键2 t+ f+ r0 I# S& q3 ~
  16.     switch(key)- x0 z6 n3 |: ?6 t( ~
  17.     {
    * o2 [2 V6 P* B& L
  18.       case KEY_UP: led2=0;break; //按下 K_UP 按键 点亮D2 指示灯. }& w. `, s5 }' I# `6 G
  19.       case KEY_DOWN: led2=1;break; //按下 K_DOWN 按键 熄灭D2 指示灯5 u* ^% d+ f- l/ ]/ v
  20.       case KEY_LEFT: led3=1;break; //按下 K_LEFT 按键 点亮D3 指示灯
    % ?( |# N# a% y5 f9 l0 g
  21.       case KEY_RIGHT: led3=0;break; //按下 K_RIGHT 按键 熄灭D3 指示灯+ i+ V/ X- g) r9 b& _0 X5 u
  22.      }
    6 u6 t& [! ?6 c  k
  23.     i++;6 m# O5 p$ Y5 ?6 V* s
  24.     if(i%20==0)1 k$ `6 A' }' T9 m
  25.     {
    9 T& X4 [2 e# v; C1 e
  26.       led1=!led1; //LED1 状态取反8 \; y! U% }4 Y+ I1 B. O/ T
  27.     }0 @/ Q9 c1 W: ~8 K
  28.     delay_ms(10);4 T% h  C# x4 H2 W) s. }
  29.   }
    % m! n: [6 K/ F8 P) D" q
  30. }
复制代码
, W6 U0 l3 k$ q/ Q6 q2 p9 W5 U
    主函数实现的功能比较简单,首先将使用到的硬件初始化(这里说的初始化表示端口和时钟全部初始化,后面就不再强调),比如 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 指示灯点亮。

  I) t& P3 u( V0 j4 `) Q4 f; B
赞 收藏 评论0 发布时间:2022-7-1 11:57

举报

0个回答

所属标签

相似技术帖

官网相关资源

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