|
EDA365欢迎您!
您需要 登录 才可以下载或查看,没有帐号?注册
x
(1)技术体会:在行列式扫描结构的薄膜按键里,干扰很大,按键扫描程序非常讲究,尤其是去抖动的处理。
6 y j5 g; [6 Z& O$ q8 U- ^ (2)功能需求:每按一个按键,蜂鸣器就响一次。
" t. Y0 ^; l* N( j$ X (3)硬件原理:4 o0 B" G0 h: ~% b% \
(a)用4个IO来做2X2按键行列扫描,其中作为输入的2个IO口必须接上拉电阻20K左右。
# }6 t t2 B- Z (b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。而无源蜂鸣器是要靠断断续续的开关信号来驱动才能响,就是要频率来驱动。; I) E' a" R/ ?, v0 Q# I+ `
(4)源码适合的单片机 IC18F4620,晶振为22.1184MHz# k5 e9 N3 X- q8 {) ?1 L
(5)源代码讲解如下:
' I% R$ M+ Q' A, Z9 e- A% ^* ]/ @ #include<pic18.h> //包含芯片相关头文件9 P9 k) b" o1 J0 E
: I: r G) u6 t" A+ _3 c! ^
//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr9 s5 O8 V% Y- J0 D
8 I2 v4 T5 I) {
#define beep_dr LATA1 //蜂鸣器输出, j, A0 _. M- e7 ?& g
3 p( V/ k ^% ^; {( b: h #define key_dr1 LATB3 //2X2按键行输出. B' j% x, U1 E: M/ n2 b3 x( P
#define key_dr2 LATB4 //2X2按键行输出0 A6 m5 ^# J! B6 r9 [! w
2 r( f3 _# }' T, G5 l( y! o #define key_sr1 RB6 //2X2按键行输入! j3 ]% r- `$ ^. l) [* \. l
#define key_sr2 RB7 //2X2按键行输入: n3 Z) F. c' w) m- V1 {) w2 D
2 H" w' D! F) Q //补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量# B' M& n0 r2 E) i) N5 v$ R) V+ ^
//前缀都用cnt_表示。0 ?3 W# G! Q9 b& S
#define cnt_delay_cnt1 25 //按键去抖动延时阀值
# {. m# V( Q& Y0 h! ~" N$ h& [2 V #define cnt_delay_cnt2 5 //按键行输出信号稳定的小延时阀值, x5 J& N: l- |0 x3 o6 C
6 g$ t$ E1 m% _" j #define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值
. ^$ ^; f \8 i: i void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗
2 J* S, ~/ ^: ]7 b8 i0 |0 ~! G/ A1 I, S( |
//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中
; V% s2 f7 ]' A& e" c L# s //断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。! U3 H5 a) I4 X6 c
void key_scan(); //按键扫描函数,放在定时中断里
' s$ N, p% g& h2 E: R0 {4 [8 S void key_service(); //按键服务函数,放在main函数循环里# {: V. t" L" l( p
/ j ^0 y! f, }# w* l3 s* ~, o5 x- o1 B5 s7 A
//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名
k7 W6 F& |" [ [( [2 ?7 f* P* M //后缀都用_step表示。) ]2 T" f6 `6 [! D
) l+ _( @2 y, |
unsigned char key_step=1; //按键扫描步骤变量,在switch()语句的括号里
2 _) K5 Q& I1 ]7 U& H //补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名4 X* @& N; n7 S z( Z5 N
//后缀都用_lock表示。
+ h! M/ U( w z/ K2 j7 t. g# l9 V unsigned char key_lock1=0; //按键自锁标志8 F) J, p7 C2 c2 Q3 G& L, Y
//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量) r. v) N4 Z3 M8 k3 X7 v( }
//后缀都用_cnt表示。
! H0 q& e' C3 I [+ R+ o3 z. R unsigned int delay_cnt1=0; //延时计数器的变量% v# ^0 b" V2 i
unsigned int delay_cnt2=0; //延时计数器的变量1 q7 Q* |5 J Z
unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时
+ Q" G4 ^" k. }/ E1 Z2 o; B1 V
//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类4 K/ o } r) f# B1 i/ m
//后缀都用_sec表示。
- S# ^* Y# o8 e5 I. R! F1 ? Unsigned char key_sec=0; //哪个按键被触发
1 U1 h, p/ z$ R! F- D6 i2 }. e% v$ n9 v- h
//主程序' l, i r' c+ G: l g3 c
main()
0 j2 T; a' e8 n! {; [' v0 K {
# }0 i9 Y5 B3 M J/ P( S6 J; L ADCON0=0x00;
* A0 ?4 j+ v& D ADCON1=0x0f; //全部为数字信号
/ T$ B* ]0 I' Y- U$ R2 d5 l5 J: Q t ADCON2=0xa1; //右对齐
" X/ _$ O0 w& H% v1 J RBPU=0; //上拉电阻% i Z' c" `1 N7 u6 K* }( |1 f( u
SSPEN=0; //决定RA5不作为串口' ^0 U* E/ _) x E& i- {
9 w9 B) W4 e' L# t- @! \& n
TRISB3=0; //配置按键行扫描IO为输出
6 a9 \- y: I/ q. s TRISB4=0; //配置按键行扫描IO为输出% ^: F y/ x- d3 e+ u
TRISB6=1; //配置按键列扫描IO为输入0 Q: h* h7 _" z+ B& u$ L8 P
TRISB7=1; //配置按键列扫描IO为输入% l: c" x5 l- g$ t3 F! Q
8 T( \0 b- d" i
* j# a" L6 U3 t3 j. z4 ] T1CON=0x24; //定时器中断配置
+ S; {5 H w9 ~ n8 b TMR1H=0xF5;
9 ^5 L; g& W: _! D! ]8 s: Q TMR1L=0x5F;
" G4 U# v" e2 l5 V3 D3 O0 x TMR1IF=0;! W# c" S, K( Z+ U& u9 v2 s9 Z, F1 a
TMR1IE=1;
8 X8 {1 `# B6 e# L$ z9 F9 ` TMR1ON=1;
: S2 D8 i' [8 S+ t+ | TMR1IE=1;9 P8 H* H" e* ]; S0 D+ A
//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,, h M! \* u5 L, y1 G
//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可. G/ `( V- u: [1 w
+ b7 [4 m8 p7 c" w- O, t+ v7 c
beep_dr=0; //关蜂鸣器,上电初始化IO r, h7 ]8 E9 t+ i+ Y1 L
; ]- g" U* \ \1 V' g( ?: {
while(1) / k7 k/ |; R3 g
{* A; O" Z5 ?- P: o
CLRWDT(); //喂看门狗,大家不用过度关注此行$ W; P. @/ U% B: k' m1 L$ h8 m
key_service(); //按键服务
8 Q! B1 e. b8 c" X3 v( y0 d3 B }$ a' A* I6 |2 h6 C7 w' I5 M
' g* c" @8 K; x8 u: X
}
4 }" i$ K" _+ g1 F. f8 J. B ( @- j' L5 `$ M i; y# m
* N- T% m3 `0 @0 P1 G
void key_scan() //按键扫描函数$ l2 X) u$ f2 l, O
{
8 A1 X# T& Y2 }/ ^6 A7 Y1 t //补充说明:如果中断一次就把所有的按键都扫描完,中断占用的时间片就会太多,势//必会影响main函数里其他子程序的运行,为了避免一口气把所//的按键都扫描完,此' N+ E. J! E: L/ y; W
//处用switch语句把4个按键分成2等分,一次中断只扫描2个按键
6 m8 U% r1 ^" F" [1 ] switch(key_step) //按键扫描步骤,
/ s- L1 q- O1 W, p/ H {
9 ]. O; O) W( _" }# s; { case 1: //扫描1号键,2号键$ z- C' v! k4 t: _8 q+ y7 w
key_dr1=0; //按键行扫描输出第一行低电平' ^. V; o: B* y) m* o" }
key_dr2=1;2 s- k" j2 z, p" z4 _ r
delay_cnt2=0; //延时计数器清零
) ^( F' s3 d! I key_step++; //切换到下一个运行步骤8 U( [, X" Q3 ~! y @
break;: G: |8 {3 X( l3 G
case 2:0 L' }" [' Z. f# G4 ]9 Z3 p( e) N
delay_cnt2++;
7 _) W/ N1 U1 j if(delay_cnt2>cnt_delay2) //小延时,但不是去抖动延时,替代一直受网友争议的delay1(40)
7 Z- ?& Q, `. o! s {
" r7 y- o* [6 q$ {% E; ~: t8 {- N6 {" h delay_cnt2=0;
3 L' [- D7 d+ u/ u key_step++; //切换到下一个运行步骤5 D* ]+ C2 `$ Q4 A( z
}" ?& [, D0 S$ l- h5 |' A/ j* v
break;! z9 }9 _. x( u% O7 C) }
case 3:
) o) r, o( o0 ] } if(key_sr1==1&&key_sr2==1), U1 D& c; b+ b" U2 N) y: c
{ //如果没有按键按下,则2个IO输入都是高电平
' {% \4 F: O( m$ A0 i key_step++; //如果没有按键按下,下一个中断扫描下2个9 `/ o$ N, {) E0 E
//按键
3 j+ l- I& w$ K key_lock1=0; //按键自锁标志清零
4 d. n3 `8 F! ~ delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙 0 S# W+ M$ L$ Z+ v7 z. l
) R$ ~( j9 x0 T. k6 ?: q; O
}8 q: [$ Y9 }. S v: `3 Q" c
Else if(key_sr1==0&&key_sr2==1&&key_lock1==0)
7 H" {. d8 N( c# o. d { // key_lock1按键自锁,避免按键一直触发,下降沿有效- Y; R' F8 F5 N2 s2 R W# y
++delay_cnt1; //延时计数器
6 A7 M$ L/ t8 t //补充说明:有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常* n7 e2 \/ i( |* L& u7 m/ }& Y( [
//巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,' Z1 U) [4 g7 M6 G
//在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某//种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零, u2 c8 Z' l& ?" A
//我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上9 x4 m1 D5 B& D$ u# f: t
//给我加薪1000元。
) L* ~: ]# ^8 x) r' X5 l% h6 c' W. o if(delay_cnt1>cnt_delay_cnt1) //延时计数器超过一定的数值
. k# u- s8 ?! P* _7 Q {
% Z9 H+ f. O0 n- W. x/ `9 c6 N delay_cnt1=0;
b' f* ~6 m; l- [( T- U$ m5 b key_lock1=1; //自锁按键置位,避免一直触发,只有松开按键,, o. X/ K/ x. Q: [
//此标志位才会被清零$ {: I; D2 r6 D% u' G
key_sec=1; //触发1号键1 V) v3 x8 T( `/ z8 Z1 e& N- }: V! O+ l; v
}
/ N4 Y! U, P7 P0 l, ^' t . k+ s0 k8 d- f2 Q' u6 H2 z
}/ |' a7 Y; S9 [( e
else if(key_sr1==1&&key_sr2==0&&key_lock1==0)
* z% H$ u& t9 s/ _& m9 ] {6 Y. Q& y. m& Q7 k, ?& Y7 R
++delay_cnt1;
6 [: X0 J! U3 @ \ if(delay_cnt1>cnt_delay_cnt1)
- J' o. J8 F+ F7 Y/ p5 B5 L6 R p {2 V) R- o; b: R" R
delay_cnt1=0;
* Z6 C. w8 ?5 _6 K3 ` key_lock1=1; //自锁按键置位,避免一直触发
! V) F( x( ?6 M1 i0 x1 y key_sec=2; //触发2号键
9 V5 ?; F5 n6 [4 j1 O; L
* N: c6 J/ G5 x, }' N+ s; E }
" \- Y4 }& t2 z }
- s+ _* p' ?: P6 I0 J/ G break;, F: U8 @1 s9 z: `! ?8 A& b( a9 k
9 m. G7 _; v ~- y
case 4: //扫描//扫描3号键,4号键* ]$ A$ J( b' i \& k- Q
key_dr1=1;
( \# |, b. g& [) `& Q/ l5 r$ ~ key_dr2=0; //按键行扫描输出第二行低电平3 a4 `- I6 F% L! L9 _) Q
delay_cnt2=0; //延时计数器清零
, U+ a+ W, V5 d: R" s key_step++; //切换到下一个运行步骤
: o4 r% K4 }; F! f5 U/ }& d break;4 y1 W7 `! P4 P, ?. j. F4 H
case 5:4 K3 g7 f; }" z$ M2 C4 a, V
delay_cnt2++;4 g" x" l$ G3 l2 S# q
if(delay_cnt2>cnt_delay2) //小延时,但不是去抖动延时,替代一直受网友争议的delay1(40) $ x" O2 k) r* r! b
{
/ F% E9 x( H4 \1 l( U6 j5 {: W% O delay_cnt2=0;: X$ e2 r8 a- l" `$ _: r+ [
key_step++; //切换到下一个运行步骤" B5 \0 P' k: c, s+ ^' b
}0 {9 p; D- p: F5 A7 P1 L
break;5 S- S* i3 U* M1 _" V
case 6:% K3 q% x2 c8 _+ A; ]
if(key_sr1==1&&key_sr2==1)
2 J# M. q. X! P+ U" ?. r8 u {
1 _* d( f; g% ?9 E key_step++;
2 H; ^2 r" I* q# u key_lock1=0; 2 e. c0 I Z2 _0 y; i! ?5 {3 Y
delay_cnt1=0;
9 J$ j% j$ j3 `+ Y2 R
: N' g3 E9 X4 b* c }
% b$ j% p: d; {' h3 y, E Else if(key_sr1==0&&key_sr2==1&&key_lock1==0)
2 |. i+ \- q+ j! d+ |3 \1 Y! i. y { , f% e( r; D2 v) ]& N6 F7 d
++delay_cnt1;
! ^ }# n+ u4 R8 A2 P: P if(delay_cnt1>cnt_delay_cnt1) & C+ x' a1 g/ k+ P: f) {7 \: V3 j
{
9 y r, v- `2 Q/ Z) R delay_cnt1=0;1 I, Y( k) {2 r# p3 O r. I4 \
key_lock1=1;
]9 J# b2 p( [+ i& v key_sec=3; //触发3号键
3 Y5 M% E6 ~, N+ h/ u7 F# S; t3 Q" i, r }
& ?, R7 j0 Y1 i5 `( H 2 w# l+ J' K: x/ ~ {5 u5 u
}
, K5 U- s% p! d* u; g1 X else if(key_sr1==1&&key_sr2==0&&key_lock1==0)- s# y, F& v7 w9 W# z7 X
{( b4 w% F, z6 B, j% m
++delay_cnt1;( G# ] L" q4 p& {
if(delay_cnt1>cnt_delay_cnt1)/ {5 C/ k/ p" \3 Q. n
{/ H8 f8 d3 L/ f% ]$ p2 L
delay_cnt1=0;
& h% Y3 F; ? N2 {9 Y3 E0 r* `" } key_lock1=1; //自锁按键置位,避免一直触发
: | M+ _6 T9 m/ H: ~. x' A' W+ | key_sec=4; //触发4号键6 p% B1 y9 \- W
8 @3 |8 }9 h9 f6 w4 g
}
% W2 `1 p& @1 E2 A0 t }
1 `; ~4 T2 A: x: ]1 t* O1 V break;
8 C, U( k0 @3 E+ h1 D }. l/ K$ k+ |$ v( V+ Q
if(key_step>6) //第1组按键与第2组按键反复轮流扫描
, D* \. c j! I8 P* l {% C! Y' ^) }4 M0 {5 q
key_step=1;
& I5 a" B1 R4 d/ z }' N. n9 Z a* U' m7 [) l: R
}) H* X& V" I$ E. d- r% h# d- W. D5 i
! e6 I; y1 g- Z8 r void key_service() //按键服务函数
9 T9 _7 ~+ Q" \( \3 } {
" Y3 u1 |- p' u+ E2 }+ s" k/ T switch(key_sec) //按键服务状态切换: N: f. u9 Q* e; D7 ]3 A, D
{
1 k, @0 {8 e& Q" d- u- w! s A( u case 1:// 1号键
/ l# o$ X3 j! h$ {3 |; [0 \0 q; i# s! h! D H1 O3 f
. T/ D' b! I2 q& a( } V6 S
// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0% |- D$ e u6 F( b: t; @
//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭/ {$ ]5 B" h- h8 |! L) f8 c
voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停; x) s2 r# B. w% K6 d
# _& s7 D, K8 u key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,0 C8 T* u* ]- i( h
//避免一直触发 R+ [/ T; }' W
break;
! J% b# f' y0 |* o case 2:// 2号键
4 u* z5 m1 @1 v5 y! I voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 i Z0 ], H9 U: k2 c4 h
key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,
" M; [5 }7 d5 Y9 q) l& @ //避免一直触发1 }9 [7 r9 r" n6 u# v( R
break; 4 D% ~# X+ `3 n3 q2 i
case 3://3号键
7 {' X: }5 \, r7 Z7 L( y1 R- l6 Q
voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停
/ j7 X9 E: a. E8 H+ T key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,# L6 Q* {7 M8 D
//避免一直触发" Z4 N% [& R" I7 q1 y5 M
break; : X/ K( ]7 ^2 ]7 y
case 4://4号键4 q+ K. q0 e7 y! @) g1 _
$ m# F: A C' f: `+ N+ X
voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停' G0 I+ i$ q0 j5 |$ V
key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,. t' Y6 \- `# G, h0 [9 e& n! g
//避免一直触发
( m* O: \! a" L0 ~6 P break;
r3 A0 g& [* Q P# I/ G5 ]) G8 |) |$ J! K% ^5 A
) }# L3 f! Z" Z6 X2 C5 a6 K0 ^0 U
4 \- p& Z) S7 x1 H
5 A) Y( N1 |8 J# D1 d# B
}
' R) O# Q# G/ l$ b$ ? }
& K& p# B. v8 }6 Z
1 o% i+ r% y( q) f% T# h: C S. w! G6 s& Q
6 s2 ~9 b! h Y6 r4 l- q4 D; {* W3 F3 j+ L3 A0 F3 l3 c/ y
//中断
1 S" v, m, O4 ?3 a+ r void interrupt timer1rbint(void)
* v6 [, w% g# D {3 J$ H$ W) ]4 p. l7 O4 ?
if(TMR1IE==1&&TMR1IF==1) //定时中断
" x& P$ U' b7 R Q9 I" w* i7 } {
! Y; e; a; O" O: V/ t
7 I# [; e# f* @3 u. k# v TMR1IF=0; //定时中断标志位关闭9 S0 _0 S3 x; i: f: p! {& |
TMR1ON=0; //定时中断开关关闭3 f) F( P/ i9 F2 A3 L, \8 m1 V
: Y7 P* B3 Q- M key_scan(); //按键扫描函数2 B ?: A" Q8 y ~7 o9 F' z
if(voice_time_cnt) //控制蜂鸣器声音的长短0 C6 T4 y& g. z
{! D, z$ Z- }6 Q, E
beep_dr=1; //蜂鸣器响
/ I H; \! d9 G' a --voice_time_cnt; //蜂鸣器响的声音长短的计数延时
2 K% \$ S/ `9 k- d8 T! h& I5 Q }
- v# y" b3 l- J8 ^ else # ] \, r/ d7 o8 @, N
{' Z3 J% }! J2 M& W: V
beep_dr=0; //蜂鸣器停止; V3 W. v, B; c% x+ |) q
}. ?; e2 r& c6 T0 a; c5 ~
TMR1H=0xF5; //重新设置定时时间间隔
6 y2 ~1 g0 a. M; v# @ TMR1L=0x5F;+ ^. W z4 h/ M$ U% V
TMR1ON=1; //定时中断开关打开
/ v& I& y% x( h0 q* } }
7 G7 r- _% J2 V6 q0 r& w4 t/ n }+ y; `" A, z& v" i- X# q
) p( O/ G$ d9 A! C! s" t$ C: d
# R n3 H# V1 R1 a
Z( Z' q s0 n& ~4 H
5 c6 ~( d5 K. M0 I+ g, w9 n void delay1(unsigned int de)! Y% J% |4 B+ Y
{
# f; s1 y9 k" x/ w7 a' [( V5 H unsigned int t;& O* H& T9 r5 K
for(t=0;t<de;t++);
/ U/ Y$ v2 j6 p }2 }3 z1 ]8 m+ d/ p2 v3 ? j k
4 _, \! D+ `3 l' u' _: m# i0 f; e3 W' ]9 E. y
(6)小结:
" I5 b% x7 q }2 S- y i3 K3 O3 b 以上是我常用的编程结构。后续我做的所有项目基本上是这样一种编程结构。这一节技术上要特别重视按键扫描。有按键触发之后,不要马上响应,要延时一段 时间去抖动,此处本人设计非常巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,在延时去抖动的时候,还必须要监控延时这段 时间里,按键IO输入口是否会由于受到某种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零,我当年就是因为这样处 理,把卖给富士康100台受干扰死机的设备修好了,老板马上给我加薪1000元。(未完待续下一节) |
|