按键扫描的两种模式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
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
. 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
( 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
- u8 KEY_Scan(void)( \& ?; U: ?: w
- {
5 ~. X1 O5 P8 j% U x. x9 ` - static u8 key_up=1;
L. S& a# ^8 X - if(key_up && KEY按下)//若key_up=1时,1&&KEY=KEY;若key_up=0时,0&&KEY=0;
9 x; l+ D& @! N8 D% [ - {( p1 }1 X$ ^( {, X
- delay_ms(10);//延时,防抖
: S6 o# V5 x! L+ R, p - key_up=0;//标记这次key已经按下) X+ J! `9 a) @4 A! r( W
- if(KEY确实按下)
& ~& \; E. g# k" _& }& v - {
|/ O4 y6 \7 u9 | - return KEY_VALUE;. C2 Q b% j: x6 q& C1 w
- }" j; x, J# [/ y) e) G G
- }
3 w9 N4 w' O* _! @ - else if(KEY没有按下) key_up=1;
$ `/ J& E; w8 @/ l2 i+ ?, s1 g - }
复制代码
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
- u8 KEY_Scan(u8 mode): r* @5 c% k i3 t- p* }& q8 c6 y
- {- {$ ~( l5 \( p# ]" |; ~
- static u8 key_up=1;//默认刚开始为高电平
8 J% L$ S+ A5 d - 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 - if(key_up && KEY按下)/ S2 i) H6 S, l) |# X$ G2 v
- {
- f% J/ i! O) q1 z1 F0 @ - delay_ms(10);//延时,防抖9 ~* l& C" H6 B0 j: Z9 u( k7 |
- key_up=0;//标记这次key已经按下4 o: d4 q+ H1 {' q+ [0 F& U
- if(KEY确实按下)
2 J2 T4 N# ^! I5 C# Z - {
( ?. H- @- s9 ~7 Z; Z0 o - return KEY_VALUE;//识别到按下,返回KEY的真实值
/ g) w2 i5 g% L& N8 K3 r! o - }
6 u m) `( z6 v# i+ w; A5 n - }5 f- ` \! v/ C& v7 p9 \! i3 `% v
- else if(KEY没有按下) key_up=1;//表示没有扫描到按下,则返回 key_up=1
. V5 A$ x$ j* v% y8 N - return 没有按下
! \# N9 z; g3 ^6 u3 ^! s - }
复制代码 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
- #include "key.h" #include "delay.h" //按键初始化函数( O$ u5 [' ] L( K) k9 f9 F( O
- void KEY_Init(void)6 M" A4 q/ H8 R4 u% Y, ?/ g
- {
p3 D! ]% Y, P) F. h - GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE, ENABLE);//使能 GPIOA,GPIOE时钟
/ H$ E! ^. {2 b0 {6 u: K( b - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //KEY0 KEY1 KEY2对应引脚
+ P' M4 J3 \- d2 Q5 ` - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
& B( j8 o- c9 m2 W- A9 ] - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
9 ?' x% L8 z# a8 t* } - 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& ] - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA0 } //按键处理函数: q$ V0 B' ]' J( F4 I% J# F
- //返回按键值& W) f! C$ _7 y/ b9 g
- //mode:0,不支持连续按 ;1,支持连续按 ; //0,没有任何按键按下
& J5 E/ B! l- y* w/ u - //1 KEY0按下 2 KEY1按下 3 KEY2按下 4 WKUP按下 WK_UP //注意此函数有响应优先级 ,KEY0>KEY1>KEY2>WK_UP!!
" l ]& y: s5 Z, @6 z - & ^$ u. ~& w: \# Z' Y2 q
- u8 KEY_Scan(u8 mode)
0 F/ Z# d% p2 b/ L: S9 ?- g - {$ d% P- M# @2 T: a# Q1 x
- static u8 key_up=1;//按键按松开标志
$ k& r# `# L# |+ C7 }9 R - if(mode)key_up=1; //支持连按% V/ Z& z( U2 @7 O) H9 \
- if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1)) { delay_ms(10);//去抖动6 U8 c4 m. o; d3 a
- key_up=0;
% t1 m M% g7 L8 N" ~ - if(KEY0==0)return 1;1 d) H4 g# S. m
- else if(KEY1==0)return 2;
; ~( Q+ L' E% M5 ~9 w1 L+ l" c5 Q# B - else if(KEY2==0)return 3;" J7 m) ]: ^- f' B
- else if(WK_UP==1)return 4;
- }- f% m( ^, \' W/ o - }
( D( T$ ~/ l2 R& p - else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;' p4 c, m' O$ Z$ o
- return 0;// 无按键按下3 c9 b' e# Q$ B
- }
复制代码
( 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- #ifndef __KEY_H, i% }+ }% f8 P2 p* c
- #define __KEY_H+ K6 m0 c- V) i/ ~ y- P/ e L
- #include "sys.h" /*下面的方式是通过直接操作库函数方式读取 IO*/
8 R! G+ c3 i6 k" h0 W+ a - #define KEY0
9 {& X2 Y9 s1 H# `4 F5 q, M - GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4" a- S; T- ]# I0 @; x
- #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) //PE3
! p1 S( r" r7 F! n5 d/ I$ e - #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) //PE21 ^/ L" x9 x+ a" ]% g6 D
- #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) //PA0
# T8 C6 |) e& ~ ~, F2 a - #define KEY0_PRES 1
, W; x3 T6 I8 f$ x( P8 X6 t - #define KEY1_PRES 2* g, J: e& F$ E- N: W! }8 } f
- #define KEY2_PRES 3# z6 w. i$ ]; g; `5 v- v( r3 C
- #define WKUP_PRES 4
$ K/ a' d! i$ R# Z - void KEY_Init(void); //IO初始化
( L& p6 {1 A M - u8 KEY_Scan(u8); //按键扫描函数
6 o" [, v( _+ G i - #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+ {- #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4
* g) o2 V P: K/ J0 k9 a- @ - #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) //PE3 1 P- W, G# R \) ~+ D: @
- #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) //PE2
& f3 R0 v. n+ g3 h( Z2 n - #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
- #define KEY0 PEin(4) //PE4: U" G: D6 X/ T9 j2 v$ g
- #define KEY1 PEin(3) //PE3
+ f" J& I! k- @3 X3 [/ I4 P - #define KEY2 PEin(2) //P32 0 ^7 c* y9 d1 @1 i5 v; N* t
- #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
- #include "sys.h"8 v* {: x$ x' d( |8 z
- #include "delay.h"
+ r$ c$ J# \9 o+ F. v" g9 W/ i - #include "usart.h"9 }) V8 P! i1 S% n
- #include "led.h"
1 x0 u$ @" I7 U1 D# F2 R - #include "beep.h"1 ~1 z' ^7 u- n& t: n! H( h
- #include "key.h" : q d: R# y5 M5 f$ P8 \2 @. L% a; ]
- int main(void)
' }. }! s) Z. N - {
5 C. H' ^' R* {: B - u8 key; //保存键值( O7 [' _. c2 Z# U; L
- delay_init(168); //初始化延时函数
5 J9 K' Q/ M1 l - LED_Init(); //初始化 LED端口
& h! Z; a8 j2 j: A2 ^% D3 J6 M - BEEP_Init(); //初始化蜂鸣器端口* |$ }* [$ ]$ ?0 c; m7 m3 C
- KEY_Init(); //初始化与按键连接的硬件接口
" D6 w. @6 Z/ V/ Y' H - LED0=0; //先点亮红灯
0 R* Z* \7 J- _& W! q - while(1)
' X$ P5 `6 ^5 |" s1 H - { key=KEY_Scan(0); //得到键值
: y, u9 N5 {; E+ t9 a4 U; H* J - if(key)# Q* W2 l) z1 Z6 J- H1 g
- { switch(key)
& F! b. q2 u8 z, } - { case WKUP_PRES: //控制蜂鸣器8 C% U! O) K9 e, u# K) R
- BEEP=!BEEP; % Q' W9 R( N: ]' H" n' z
- break; ) [/ R+ A. f4 ~/ L' B3 R3 m
- case KEY0_PRES: //控制 LED0翻转8 G! |( ~0 ` _
- LED0=!LED0;
4 s( l0 a' a# T) X/ i - break;+ x3 F# _1 j0 J u2 t
- case KEY1_PRES: //控制 LED1翻转7 h7 i; I: u+ ]0 m9 z
- LED1=!LED1; : w! Y- {3 O0 w# q# `7 d
- break;
/ E/ U' m- p: |, T - case KEY2_PRES://同时控制 LED0,LED1翻转: ]/ r7 @; I1 j" f& m9 h2 l
- LED0=!LED0;
$ T0 V6 D" Y2 a - LED1=!LED1;
, ?8 O' e7 a/ U - break; - N* g2 o1 T. E- I' r2 K6 R* o
- }
# z3 X8 P' f' S* ?) p$ ? - }
" x z1 T/ Z, t9 q - else delay_ms(10);, c' E# n0 k9 F W; a. X
- }
/ I1 p4 z& \( Q* ? - }
复制代码
, 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# ^ |