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

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

[复制链接]
STMCU小助手 发布时间:2022-7-1 11:57
本章所要实现的功能是:通过开发板上的 4 个按键控制 LED。
按键介绍
    按键是一种电子开关, 使用时轻轻按开关按钮就可使开关接通, 当松开手时,开关断开。我使用的按键及内部简易图通常如下图所示。
微信图片_20220701115639.jpg
  s# Y5 g, W' ^# K) B% c) M
    按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态,如果按键按下,初始导通状态变为断开,初始断开状态变为导通。通常的按键所用开关为机械弹性开关,当机械触点断开 、闭合时,电压信号如下图所示。

$ z! @3 Z0 N9 g+ C; \0 N, _
微信图片_20220701115824.png
6 O. s; o1 M% A( G5 i4 q  a8 ~# Y
    由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖。
    按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时 10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。其中延时10ms 就是软件消抖处理,至于硬件消抖,大家可以百度了解下,网上都有非常详细的介绍。

& X! c: o6 o# ^. i) B4 e- I0 C, w
硬件设计
    我们STM32F1开发板上有4 个控制按键,其硬件电路如图所示。

6 e" P& r' K( L# o8 w; @! C" Y7 Z
微信图片_20220701115829.png

3 m2 ~: H; H9 w
微信图片_20220701115832.png

0 z! C- u: ^, ^) m& u: e
微信图片_20220701115838.jpg

: _$ x* j- K+ @% P: o
    从原理图我们可以知道,按键 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,主要是为后面章节实验使用,到时候我们再具体介绍。
4 s  s: ?) z' V4 V) Z$ W! m# Y
软件设计
    整个程序实现的流程步骤如下:
(1)初始化按键使用的端口及时钟
(2)按键检测处理
(3)按键控制处理
, R. [+ F3 h% N, N
按键初始化函数( ~7 C$ @" q& s# ^& h6 F3 C
    我们打开工程中 key.c 文件,按键初始化代码如下:
  1. /****************************************************************
    . Y4 r7 z" l1 t7 q
  2. * 函 数 名 : KEY_Init" ^" L0 _2 U- N; r# h
  3. * 函数功能 : 按键初始化. a3 |* Q3 V  Z. w$ I$ b# K
  4. * 输 入 : 无" r* r. u# r; Y; h6 M
  5. * 输 出 : 无
    . H7 q+ N4 D  d9 |1 G
  6. ****************************************************************/
    " B7 k) m+ E  d0 n6 l* o4 @
  7. void KEY_Init(void)
    " a$ X1 m- ?2 f7 z( `& ?
  8. {
    1 e) g! w2 T; h! p* @% x
  9.   GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量% J8 ^( h6 E7 X4 \
  10.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);, `3 h% V/ m/ b( _! _
  11.   GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin; //选择你要设置的IO 口
    4 I/ y( L6 h0 v. |0 y. C
  12.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入
    ' Q$ l9 Y. z1 C. }& b8 i5 t# H. z
  13.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率6 V8 u0 u& ~6 Z
  14.   GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); /* 初始化GPIO */4 x* J) O* s* c' q: e
  15.   GPIO_InitStructure.GPIO_Pin=KEY_DOWN_Pin|KEY_LEFT_Pin|KEY_RIGHT_Pin;
    ; [  p  ]5 ^8 @3 G* Y
  16.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入  j0 s/ w1 N' g) s9 Z% ~6 @
  17.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    0 v: x2 ?0 [6 ~( i( |( `
  18.   GPIO_Init(KEY_Port,&GPIO_InitStructure);
    % V- F* e$ B. n; N
  19. }
复制代码

6 y' F8 k3 `% F: T' p6 k
    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
    4 P$ A3 G( j5 I
  2. #define _key_H3 v7 A% D& m8 e' ?
  3. #include "system.h"
    4 _' `+ Y; t( X! y  N: F* _' z4 y
  4. #define KEY_LEFT_Pin GPIO_Pin_2 //定义 K_LEFT 管脚- s- W1 g6 O* i
  5. #define KEY_DOWN_Pin GPIO_Pin_3 //定义 K_DOWN 管脚" M' R) M" N2 c
  6. #define KEY_RIGHT_Pin GPIO_Pin_4 //定义 K_RIGHT 管脚
    2 K9 x1 q3 W4 b' f0 D7 W; l
  7. #define KEY_UP_Pin GPIO_Pin_0 //定义 KEY_UP 管脚
    # N( }. e# \) z2 o8 Z9 _
  8. #define KEY_Port (GPIOE) //定义端口
    4 O+ x( Z+ A: W# T& ?4 {" Y
  9. #define KEY_UP_Port (GPIOA) //定义端口( m  f- d5 g& V7 c: h
  10. //使用位操作定义, G/ o+ z  W0 k- u- c! L
  11. #define K_UP PAin(0)6 M3 ?: a0 i/ a4 a1 P2 L# v1 a9 v
  12. #define K_DOWN PEin(3)
    % Y* A% U0 G, _, [6 r: b
  13. #define K_LEFT PEin(4)
    $ [: |4 U$ b9 _  B5 z
  14. #define K_RIGHT PEin(2)$ P* t8 R' q# ?& ]7 j
  15. //使用读取管脚状态库函数定义+ c9 L3 P) W. P+ {" u/ J5 K
  16. //#define K_UP GPIO_ReadInputDataBit(KEY_UP_Port,KEY_UP_Pin)
    . T2 S/ ?" N2 ^1 }- {
  17. //#define K_DOWN GPIO_ReadInputDataBit(KEY_Port,KEY_DOWN_Pin)
    8 C( t/ W. I" [1 ^7 m
  18. //#define K_LEFT GPIO_ReadInputDataBit(KEY_Port,KEY_LEFT_Pin)' |* X0 d4 G, c" W, _) o# s4 g
  19. //#define K_RIGHT GPIO_ReadInputDataBit(KEY_Port,KEY_RIGHT_Pin)( S1 [2 u8 {/ I' p0 ]  A
  20. //定义各个按键值' c$ y5 w, O) A/ |, o: v6 }  o
  21. #define KEY_UP 1! o7 t8 U# u0 u. I
  22. #define KEY_DOWN 2$ {" }9 d. y1 G! W" q
  23. #define KEY_LEFT 36 `% Q5 t: F' F  ~! q
  24. #define KEY_RIGHT 4
    : X# B& ]" Z3 B3 s3 ?! h
  25. void KEY_Init(void);
    4 e5 b2 c3 ^4 Z4 W
  26. u8 KEY_Scan(u8 mode);
    4 K( H# Q( W% J2 A5 M
  27. #endif
复制代码

( A* }, ~4 R. r' e( @6 k8 k+ i
    key.h 文件内定义了按键的端口、管脚、读取管脚的电平状态、函数声明等信息。里面使用了两种方式定义读取管脚电平宏,一种是通过位操作,另一种是通过读取管脚电平的库函数。由于位操作比较方便,因此我们就使用位操作的方式,库函数定义的宏全部注释掉。

& _0 n8 F/ J3 O1 t( ?) I8 j6 O
按键检测函数
    要知道哪个按键被按下,就需要编写按键检测函数,具体代码如下:
  1. /***************************************************************
    1 A2 r! }& J9 p7 f
  2. * 函 数 名 : KEY_Scan
    5 u- i3 ~" D% S
  3. * 函数功能 : 按键扫描检测# G& m5 r# m6 y7 H; ?) v8 y
  4. * 输 入 : mode=0:单次按下按键* u; Q1 a$ K0 }9 v
  5. mode=1:连续按下按键
    ( Y* h7 ^2 k! A0 \- |% O+ @. o
  6. * 输 出 : 0:未有按键按下7 [0 A* [2 }9 k5 f5 j. }
  7. KEY_UP:K_UP 键按下
    , n+ o8 B# p0 t7 M
  8. KEY_DOWN:K_DOWN 键按下) t7 t! W8 V5 Q
  9. KEY_LEFT:K_LEFT 键按下8 Z" H1 I7 ?8 g
  10. KEY_RIGHT:K_RIGHT 键按下
    & B) F( x+ }0 T: f+ f
  11. ****************************************************************/" ~% O) @$ a* F# d2 N3 k+ }
  12. u8 KEY_Scan(u8 mode)
    ' U9 S; }- f9 `( K6 t* J
  13. {
    : Y4 n3 K3 U; a* A* D; S
  14.   static u8 key=1;3 p7 o7 q+ a/ T- Q
  15.   if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
    + }$ U) c( n/ e; t& {- P: {
  16.   {
    7 z" o9 l( Y) K# ^' n$ J% F+ \/ g9 S
  17.     delay_ms(10); //消抖) |+ l8 x: o3 G+ X, p  L' ?
  18.     key=0;
    9 V( ^0 A, m* w, K4 n* q: }3 [
  19.     if(K_UP==1)1 I5 a( x; a, s6 `, x1 e
  20.     {
    ' e" k: e; d8 ~. [7 V. z: R! N7 f
  21.       return KEY_UP;# S) ?7 W5 _7 u0 T: [4 f+ G
  22.     }
    9 N4 q2 c5 ~! Y# r9 \$ f& j
  23.     else if(K_DOWN==0)
    9 b% C' ~7 x" p2 F7 K
  24.     {
    ! ]% l' r# \5 v2 E
  25.       return KEY_DOWN;, Q/ ?2 F% \0 h
  26.     }
    8 R5 h) F8 m! k6 u/ m4 j
  27.     else if(K_LEFT==0)5 |4 [! u" c/ p
  28.     {, x5 P* a$ V' k7 r7 A  s9 V( F
  29.       return KEY_LEFT;, d( p, D/ X# k) h. `( d
  30.     }
    7 {, f$ q% c9 N# R2 `" N. C1 ^( H/ z
  31.     else
    * h: i& Q4 }$ D/ e5 K/ t' a
  32.     {
    ) [5 `  [, _" ]4 ~  d0 h
  33.       return KEY_RIGHT;" U: U# ]' a/ Z2 D3 M
  34.     }1 h, i) x  O. F) E: V) N6 U7 r2 z- g
  35.   }
    3 [8 d/ P, u8 Q" F# Q- N
  36.   else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下2 [9 {* w8 ^6 R9 P1 q
  37.   {
    , @  e# k7 w/ {9 l& r
  38.     key=1;9 B; _' z  `/ g. s. F2 |
  39.   }+ C3 ?7 I! M% S$ }
  40.   if(mode==1) //连续按键按下
    ( @5 y5 H6 A* P" ~5 S4 G
  41.   {0 y0 E4 @7 V: ^! O; q
  42.     key=1;
    & h: M& J1 g6 d7 C8 h) h
  43.   }
      G9 q" N) b  V: g
  44.   return 0;
    4 {& S+ ?; i+ n: M- M. i
  45. }
复制代码

0 _, c1 u0 }6 g1 t! l! R
    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 语句。

% p1 f) {' d, s* [, a7 P
主函数
    最后我们看下主函数,代码如下:
  1. /***************************************************************" N# F) N3 c- q1 @3 U
  2. * 函 数 名 : main
    5 o4 ]4 P  K+ Y  D6 U/ `0 e
  3. * 函数功能 : 主函数+ h3 F. k* M$ Y5 A: j
  4. * 输 入 : 无
    . q7 y" k3 S% f7 A  f
  5. * 输 出 : 无/ {8 d  x6 {  e' |+ D( r8 m7 c
  6. ****************************************************************/3 N0 V  e: U  O* b% f
  7. int main(): N, v2 I. A* n7 ^7 }) q
  8. {
    % P9 k4 G2 w, p
  9.   u8 key,i;3 {8 z- l" J# Z0 U: r, K
  10.   SysTick_Init(72);
    7 H  ^  _! f* ^! S- W
  11.   LED_Init();; `6 i  Q# n7 `: u: U4 \" T9 |
  12.   KEY_Init();* O0 A& ~& |5 p' r3 G# X9 C
  13.   while(1)
    0 q# Q! {- b2 f+ A3 Y0 L1 K- Z. g
  14.   {
    5 Z7 R0 N! |" v& y0 ^- I9 S
  15.     key=KEY_Scan(0); //扫描按键
    0 z- R, B5 s7 E, Q
  16.     switch(key)
    3 L. y- w! B$ b8 ^3 ]
  17.     {
    - s; G. `7 R% [7 O  u6 G+ i
  18.       case KEY_UP: led2=0;break; //按下 K_UP 按键 点亮D2 指示灯/ \5 ~+ ]7 N5 r* ]' Q! G
  19.       case KEY_DOWN: led2=1;break; //按下 K_DOWN 按键 熄灭D2 指示灯" K! z: V, Z% |& m/ x2 y
  20.       case KEY_LEFT: led3=1;break; //按下 K_LEFT 按键 点亮D3 指示灯0 v! _6 d1 n& D. f% I
  21.       case KEY_RIGHT: led3=0;break; //按下 K_RIGHT 按键 熄灭D3 指示灯" O) H2 @6 R1 m0 k4 ~: F0 y: h
  22.      }. N' D, @8 K- E0 R5 ]1 f3 K
  23.     i++;3 d" t" \. ^1 |  K: ?3 M4 e! |
  24.     if(i%20==0)
    ) \4 p6 w; q# \* i- g+ R" j
  25.     {  T7 E2 r9 d- F* @+ ?5 d
  26.       led1=!led1; //LED1 状态取反
    7 ~0 C0 k# ]" [: m. M1 ]3 g
  27.     }& j' {# f6 c6 v5 M, J
  28.     delay_ms(10);) y- z: V; O2 ?' X$ Y! e
  29.   }
    8 |* y4 ?. g8 Y) N4 \# h+ @
  30. }
复制代码

+ W' @9 e& U9 z9 g. L# o. D
    主函数实现的功能比较简单,首先将使用到的硬件初始化(这里说的初始化表示端口和时钟全部初始化,后面就不再强调),比如 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 指示灯点亮。
' s/ x0 S0 N/ _# @. g
收藏 评论0 发布时间:2022-7-1 11:57

举报

0个回答

所属标签

相似分享

官网相关资源

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