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

【经验分享】STM32学习笔记之按键扫描

[复制链接]
STMCU小助手 发布时间:2022-7-5 17:13
按键扫描的两种模式3 n1 y2 b$ M( D3 `
1.支持连续扫描模式
9 ^3 q8 `) _' E% V5 A& E# m连续按下时,会认为有多个数值,比如我们的遥控板,一直按下则频道会一直增加。
+ q# q6 |0 Z. d$ ]# i* l1 t2.不支持连续模式
- E, S: y  r+ U* K. B连续按下时,仅认为只有一个数值,比如我们的电源按键,长时间的按下并不会一直有效,从而频繁的开关机,就算按的时间很长,也只会进行一次。' f3 a/ q+ y! b! R4 W! r( U# C
只有在前一时刻的状态与这个时候的状态不想同时,会记一个数值。比如之前是高电平,现在是低电平就记一次,一直是低电平,则不继续计数进行。- c1 ^4 c# y( Z
# A& P9 A3 c( t, k8 j7 F6 ^5 M2 B
BU~$K(I]UUGP9@{W)0$X[)X.png 5 k2 ~5 K  T* l' {% O' ]: c3 p+ m! c

( K! N) g, b. F* d9 m如何用C语言处理是否是连续模式?# ~( T6 Y3 x0 `
关键字Static. a5 f+ E7 }( b  W1 [8 h6 Y
Static声明的局部变量是具有记忆功能的。+ C( r1 ^! w9 n% e
) D7 d( f' L5 q' j' X
例1:3 j  l7 A! O2 L, |; _# |: p
20200731122637790.png . t& D) H; e. J

6 M- t  [5 Z" Y0 f  Z* ]# F结果:1,1,1…6 X7 j/ T. B2 _3 K3 [. t
% _, q0 P  a, l5 B7 S7 O9 A
例2
9 t: M; T/ ~5 U$ T/ l. sstatic关键字具有存储记忆功能,能存入返回的上一时刻flag的值。
; t4 d' Y, S- J. x此时的flag++是在上一时刻的flag的基础上进行加1。
# z4 X/ ^( C5 v* @$ j; F/ Z4 f- M5 X# G$ P) j: x/ ~6 y
20200731122833667.png ( b( M$ @' r2 m
' [- ^. I4 v6 Z5 E1 ?4 \
结果:1,2,3…(具有记忆功能)
" s& E: y- H) e* R' d
" }& |" J( u/ c( j0 j+ N按键扫描,(不支持连续按下)的一般思路: s# X: @6 W5 \( W9 X$ v, Z
  1. u8 KEY_Scan(void)( \& ?; U: ?: w
  2.     {
    5 ~. X1 O5 P8 j% U  x. x9 `
  3.      static u8 key_up=1;
      L. S& a# ^8 X
  4.       if(key_up &&  KEY按下)//若key_up=1时,1&&KEY=KEY;若key_up=0时,0&&KEY=0;
    9 x; l+ D& @! N8 D% [
  5.        {( p1 }1 X$ ^( {, X
  6.         delay_ms(10);//延时,防抖
    : S6 o# V5 x! L+ R, p
  7.         key_up=0;//标记这次key已经按下) X+ J! `9 a) @4 A! r( W
  8.         if(KEY确实按下)
    & ~& \; E. g# k" _& }& v
  9.            {
      |/ O4 y6 \7 u9 |
  10.            return KEY_VALUE;. C2 Q  b% j: x6 q& C1 w
  11.             }" j; x, J# [/ y) e) G  G
  12.         }
    3 w9 N4 w' O* _! @
  13.         else if(KEY没有按下)  key_up=1;
    $ `/ J& E; w8 @/ l2 i+ ?, s1 g
  14.     }
复制代码

6 I1 W; k8 J8 J' T% U' X  V按键扫描(两种模式合二为一)的一般思路: Q$ M- c+ l& P5 l" B; u8 W
多了一个mode模式,mode=1,支持连续按下;mode=0,不支持连续按下。: b6 i& @$ [& {" ^
% w& F6 X, C8 W
  1. u8 KEY_Scan(u8 mode): r* @5 c% k  i3 t- p* }& q8 c6 y
  2.     {- {$ ~( l5 \( p# ]" |; ~
  3.      static u8 key_up=1;//默认刚开始为高电平
    8 J% L$ S+ A5 d
  4.      if(mode==1) key_up=1;//mode=1,支持连续按,key-up永远等于1;mode=0,则此行代码无用,不支持连续按;
    6 Q( W/ Y& g/ m2 L6 C2 g# S( G' P( P
  5.       if(key_up &&  KEY按下)/ S2 i) H6 S, l) |# X$ G2 v
  6.       {
    - f% J/ i! O) q1 z1 F0 @
  7.         delay_ms(10);//延时,防抖9 ~* l& C" H6 B0 j: Z9 u( k7 |
  8.         key_up=0;//标记这次key已经按下4 o: d4 q+ H1 {' q+ [0 F& U
  9.         if(KEY确实按下)
    2 J2 T4 N# ^! I5 C# Z
  10.           {
    ( ?. H- @- s9 ~7 Z; Z0 o
  11.            return KEY_VALUE;//识别到按下,返回KEY的真实值
    / g) w2 i5 g% L& N8 K3 r! o
  12.           }
    6 u  m) `( z6 v# i+ w; A5 n
  13.         }5 f- `  \! v/ C& v7 p9 \! i3 `% v
  14.         else if(KEY没有按下)  key_up=1;//表示没有扫描到按下,则返回 key_up=1
    . V5 A$ x$ j* v% y8 N
  15.        return 没有按下
    ! \# N9 z; g3 ^6 u3 ^! s
  16.     }
复制代码
9 g+ m8 p- @5 X2 [9 E! z6 }
打开我们的按键实验工程可以看到,我们引入了
, g4 u+ m$ f6 t# M3 ?1 i6 v8 [/ Dkey.c文件以及头文件 key.h。下面我们首
, m' `8 W4 H3 c: k; W  \先打开 key.c文件, 关键 代码如下:5 m6 \" y0 K: j* E
  1. #include "key.h" #include "delay.h" //按键初始化函数( O$ u5 [' ]  L( K) k9 f9 F( O
  2. void KEY_Init(void)6 M" A4 q/ H8 R4 u% Y, ?/ g
  3. {
      p3 D! ]% Y, P) F. h
  4. GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE, ENABLE);//使能 GPIOA,GPIOE时钟
    / H$ E! ^. {2 b0 {6 u: K( b
  5. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //KEY0 KEY1 KEY2对应引脚
    + P' M4 J3 \- d2 Q5 `
  6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
    & B( j8 o- c9 m2 W- A9 ]
  7. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
    9 ?' x% L8 z# a8 t* }
  8. GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化 GPIOE2,3,4 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//WK_UP对应引脚 PA0 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;//下拉
    $ S6 ~; [+ L& ]
  9. GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA0 } //按键处理函数: q$ V0 B' ]' J( F4 I% J# F
  10. //返回按键值& W) f! C$ _7 y/ b9 g
  11. //mode:0,不支持连续按 ;1,支持连续按 ; //0,没有任何按键按下
    & J5 E/ B! l- y* w/ u
  12. //1 KEY0按下 2 KEY1按下 3 KEY2按下 4 WKUP按下 WK_UP //注意此函数有响应优先级 ,KEY0>KEY1>KEY2>WK_UP!!
    " l  ]& y: s5 Z, @6 z
  13. & ^$ u. ~& w: \# Z' Y2 q
  14. u8 KEY_Scan(u8 mode)
    0 F/ Z# d% p2 b/ L: S9 ?- g
  15.    {$ d% P- M# @2 T: a# Q1 x
  16. static u8 key_up=1;//按键按松开标志
    $ k& r# `# L# |+ C7 }9 R
  17. if(mode)key_up=1; //支持连按% V/ Z& z( U2 @7 O) H9 \
  18. if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1)) { delay_ms(10);//去抖动6 U8 c4 m. o; d3 a
  19. key_up=0;
    % t1 m  M% g7 L8 N" ~
  20. if(KEY0==0)return 1;1 d) H4 g# S. m
  21. else if(KEY1==0)return 2;
    ; ~( Q+ L' E% M5 ~9 w1 L+ l" c5 Q# B
  22. else if(KEY2==0)return 3;" J7 m) ]: ^- f' B
  23. else if(WK_UP==1)return 4;
    - }- f% m( ^, \' W/ o
  24.     }
    ( D( T$ ~/ l2 R& p
  25. else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;' p4 c, m' O$ Z$ o
  26. return 0;// 无按键按下3 c9 b' e# Q$ B
  27. }
复制代码

( k1 o: l# t3 M, `  u这段代码包含 2个函数, void KEY_Init(void)和 u8 KEY_Scan(u8 KEY_Init是用来初始化按键输入的 IO口的。实现 PA0、 PE2~4的输入设置,这里和第六章 的输出配置 差不多只是这里用来设置成的是输入而 第六章 是输出 。, V! c4 P( q$ J$ P$ ?- X3 J
KEY_Scan函数,则是用来扫描这 4个 IO口是否有按键按下。 KEY_Scan函数, 支持两种扫描方式,通过 mode参数来设置。当mode为 0的时候, KEY_Scan函数将不支持连续按, 扫描某个按键,该按键按下之后必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多次触发,而坏处就是在需要长按的时候比较不合适。0 v: c* n: ~2 C4 g$ i
当mode为 1的时候, KEY_Scan函数将支持连续按,如果某个按键一直按下,则会 一直返回这个按键的键值,这样可以方便的实现长按检测。6 e3 v) g. d% H+ C
有了mode这个参数,大家就可以根据自己的需要,选择不同的方式。这里要提醒大家,因为该函数里面有 static变量,所以该函数不是一个可重入函数,在有 OS的情况下,这个大家要留意下。 同时还有一点要注意的就是,该函数的按键扫描是有优先级的,最优先的是 KEY0,第二优先的是 KEY1 接着 KEY2 最后是 KEY3 KEY3对应 KEY_UP按键) 。该函数有返回值,如果有按键按下,则返回非 0值,如果没有或者按键不正确,则返回 0。接下来我们看看头文件key.h里面的代码:
1 f" K. y( f8 G- D' X
. {4 C( U8 i' i  m9 U5 A
  1. #ifndef __KEY_H, i% }+ }% f8 P2 p* c
  2. #define __KEY_H+ K6 m0 c- V) i/ ~  y- P/ e  L
  3. #include "sys.h" /*下面的方式是通过直接操作库函数方式读取 IO*/
    8 R! G+ c3 i6 k" h0 W+ a
  4. #define KEY0
    9 {& X2 Y9 s1 H# `4 F5 q, M
  5. GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4" a- S; T- ]# I0 @; x
  6. #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) //PE3
    ! p1 S( r" r7 F! n5 d/ I$ e
  7. #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) //PE21 ^/ L" x9 x+ a" ]% g6 D
  8. #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) //PA0
    # T8 C6 |) e& ~  ~, F2 a
  9. #define KEY0_PRES 1
    , W; x3 T6 I8 f$ x( P8 X6 t
  10. #define KEY1_PRES 2* g, J: e& F$ E- N: W! }8 }  f
  11. #define KEY2_PRES 3# z6 w. i$ ]; g; `5 v- v( r3 C
  12. #define WKUP_PRES 4
    $ K/ a' d! i$ R# Z
  13. void KEY_Init(void); //IO初始化
    ( L& p6 {1 A  M
  14. u8 KEY_Scan(u8); //按键扫描函数
    6 o" [, v( _+ G  i
  15. #endif
复制代码
4 y4 V, @6 r( w( u; ]3 Y8 c" j
这段代码里面最关键就是4个宏定义
) {; X. v( R' o& |( B6 X# L# M
* {. q% a2 M' @6 X, ~2 u+ {
  1. #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4
    * g) o2 V  P: K/ J0 k9 a- @
  2. #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) //PE3 1 P- W, G# R  \) ~+ D: @
  3. #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) //PE2
    & f3 R0 v. n+ g3 h( Z2 n
  4. #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) //PA0
复制代码
( G/ y2 ^3 f+ l+ h) O# |5 M
这里使用的是调用库函数 来实现读取某个 IO口的 1个位的。同输出一样, 上面的功能也同样可以通过位带操作来简单的实现:
0 |. Y5 S. D1 e. y/ _* Z- V) g" _# r# _+ o6 Z
  1. #define KEY0 PEin(4) //PE4: U" G: D6 X/ T9 j2 v$ g
  2. #define KEY1 PEin(3) //PE3
    + f" J& I! k- @3 X3 [/ I4 P
  3. #define KEY2 PEin(2) //P32 0 ^7 c* y9 d1 @1 i5 v; N* t
  4. #define WK_UP PAin(0) //PA0
复制代码

8 U. }4 F# _1 y- r) Q2 h用库函数实现的好处是在各个STM32芯片上面的移植性 非常好,不需要修改任何代码。3 k  g6 N6 ]" q+ ^, U6 h8 A) n
用位带操作的好处是简洁,至于使用哪种方法,看各位的爱好了。
3 q* n7 h# C# ?3 C& b在key.h中,我们还定义了 KEY0_PRES / KEY1_PRES/ KEY2_PRES/WKUP_PRESS等 4个宏定义,分别对应开发板四个按键 KEY0/KEY1/KEY2/ KEY_UP)按键按下时 KEY_Scan返回的值。 通过宏定义的方式判断返回值,方便大家记忆和使用 。最后,我们看看main.c里面编写的主函数代码如下:. x& p' [$ x  K" M
- X) c. P  Z: G
  1. #include "sys.h"8 v* {: x$ x' d( |8 z
  2. #include "delay.h"
    + r$ c$ J# \9 o+ F. v" g9 W/ i
  3. #include "usart.h"9 }) V8 P! i1 S% n
  4. #include "led.h"
    1 x0 u$ @" I7 U1 D# F2 R
  5. #include "beep.h"1 ~1 z' ^7 u- n& t: n! H( h
  6. #include "key.h" : q  d: R# y5 M5 f$ P8 \2 @. L% a; ]
  7. int main(void)
    ' }. }! s) Z. N
  8. {
    5 C. H' ^' R* {: B
  9. u8 key; //保存键值( O7 [' _. c2 Z# U; L
  10. delay_init(168); //初始化延时函数
    5 J9 K' Q/ M1 l
  11. LED_Init(); //初始化 LED端口
    & h! Z; a8 j2 j: A2 ^% D3 J6 M
  12. BEEP_Init(); //初始化蜂鸣器端口* |$ }* [$ ]$ ?0 c; m7 m3 C
  13. KEY_Init(); //初始化与按键连接的硬件接口
    " D6 w. @6 Z/ V/ Y' H
  14. LED0=0; //先点亮红灯
    0 R* Z* \7 J- _& W! q
  15. while(1)
    ' X$ P5 `6 ^5 |" s1 H
  16.   { key=KEY_Scan(0); //得到键值
    : y, u9 N5 {; E+ t9 a4 U; H* J
  17. if(key)# Q* W2 l) z1 Z6 J- H1 g
  18.      { switch(key)
    & F! b. q2 u8 z, }
  19.               { case WKUP_PRES: //控制蜂鸣器8 C% U! O) K9 e, u# K) R
  20.                 BEEP=!BEEP; % Q' W9 R( N: ]' H" n' z
  21.                 break; ) [/ R+ A. f4 ~/ L' B3 R3 m
  22.            case KEY0_PRES: //控制 LED0翻转8 G! |( ~0 `  _
  23.                 LED0=!LED0;
    4 s( l0 a' a# T) X/ i
  24.                 break;+ x3 F# _1 j0 J  u2 t
  25.            case KEY1_PRES: //控制 LED1翻转7 h7 i; I: u+ ]0 m9 z
  26.                 LED1=!LED1; : w! Y- {3 O0 w# q# `7 d
  27.                 break;
    / E/ U' m- p: |, T
  28.            case KEY2_PRES://同时控制 LED0,LED1翻转: ]/ r7 @; I1 j" f& m9 h2 l
  29.                 LED0=!LED0;
    $ T0 V6 D" Y2 a
  30.                  LED1=!LED1;
    , ?8 O' e7 a/ U
  31.                  break; - N* g2 o1 T. E- I' r2 K6 R* o
  32.            }
    # z3 X8 P' f' S* ?) p$ ?
  33.       }
    " x  z1 T/ Z, t9 q
  34.       else delay_ms(10);, c' E# n0 k9 F  W; a. X
  35.    }
    / I1 p4 z& \( Q* ?
  36. }
复制代码

, d6 T8 F' o' T% ~) a; w) U主函数代码比较简单,先进行一系列的初始化操作,然后在死循环中调用按键扫描函数KEY_Scan()扫描按键值,最后根据按键值控制 LED和蜂鸣器的翻转。0 `! x/ M" {. R% G3 P0 B  ^
3 i8 S. u8 r. h
4 U4 a6 F. W3 T: M/ Q' B0 N3 @

  n6 T* P, w& m# ^
赞 收藏 评论0 发布时间:2022-7-5 17:13

举报

0个回答

所属标签

相似技术帖

官网相关资源

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