找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

巢课
电巢直播8月计划
查看: 1|回复: 0
打印 上一主题 下一主题

[硬件] 吴坚鸿单片机程序风格赏析——(三)AD按键扫描与蜂鸣器

[复制链接]

551

主题

1470

帖子

3万

积分

EDA365管理团队

Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9

积分
39487
跳转到指定楼层
1#
发表于 2019-9-27 15:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您!

您需要 登录 才可以下载或查看,没有帐号?注册

x
(1)        开场白:& q6 C. B5 v1 j) J! @% n2 N
AD按键可以实现一个IO口驱动几个按键的目的,以鸿哥的经验,一个IO口控制的按键数量最好不要超过3个。从实际应用来考虑,独立按键的方式最好,其次是行列按键,最次是AD按键。: S- u5 w) v  U! C8 Y
(2)功能需求:每按一个按键,蜂鸣器就响一次。
1 p' e+ [* U2 m* A+ Q$ P (3)硬件原理:
4 ]2 _- p* ~& r: U4 ?& F (a)用4个电阻竖着串联起来,最上端接5V,最下端接地。从上往下,最上端接5V的算第一个节点,最下端接地算最后一个节点,共5个节点。用一个带AD 的IO口连接到第2个节点上,此节点上连接第一个按键,按键的另一端接地。以此方式,第二个按键连接到第3个节点,第三个按键连接到第4个节点。这4个电 阻的目的主要是用来分压,靠不同的电压来识别不同的按键。7 [- E9 p% _# E3 m2 h
(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。
+ B$ h1 o! P% G8 o8 q# o" n (4)源码适合的单片机IC16f73,晶振为4MHz4 q; `1 [# V' E% }
(5)源代码讲解如下:
, c( n( ^( u: H" G" q% }- g #include<pic.h>         //包含芯片相关头文件0 D& n2 R" b8 ]1 e( w

0 C1 g% A/ V( L# j4 P //补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr  q- q! Y1 M. x1 C  x
9 n7 u% @( a& C" ?) j+ O. t
#define  beep_dr  RB0  //蜂鸣器输出
& ]/ ~6 p7 |4 L5 y7 g1 z; ]; A8 L! Z2 I, e' X8 {
//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量# T, o5 K6 v& q: d, z' K0 x
//前缀都用cnt_表示。凡是延时常量,都应该根据上机实际情况来调整出最佳的数值
" J, N3 i: a& f) s3 ~/ n( @ #define cnt_delay_cnt1   40  //按键去抖动延时阀值
2 d& F" |# y5 U2 N/ a #define cnt_voice_time   75 //蜂鸣器响的声音长短的延时阀值
! ]& h4 r/ ^2 M% Z #define cnt_key_nc  185   //没有按键按下时电压对应的AD数值
* N+ W/ [% j: ~0 X8 a #define cnt_key1_up      185   //1号按键按下时电压对应的AD数值上限
$ E: b, r5 C' F6 b% f #define cnt_key2_down  117  //2号按键按下时电压对应的AD数值下限( W, J5 J3 Z, V7 G# k+ C- F$ h
#define cnt_key2_up      137  //2号按键按下时电压对应的AD数值上限
- D7 R. u; B" D* v: _ #define cnt_key3_down  160  //3号按键按下时电压对应的AD数值下限
0 {9 a6 T. f+ H" ]6 I0 Z  ?* Q9 L #define cnt_key3_up      175  //3号按键按下时电压对应的AD数值上限
+ i% v6 {# B3 m% [: i2 B2 t- k4 q0 V& J! L. d/ g
" g8 G* p1 R$ Z# u3 h, ?* B9 b

% [; u( [& U, P3 \( G2 H- Y6 l, t, n8 X$ f9 N
//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中2 o& |/ `2 w/ G7 N8 G) ~3 h& s
//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。4 i1 T, Z/ B" w$ S/ E. i% b
void key_scan();                         //按键扫描函数,放在定时中断里
0 m1 e. G, C2 ~- ?5 k void key_service();                            //按键服务函数,放在main函数循环里
( C1 _; n6 q# Z( P5 K* ` void ad_samping();                         //AD采样, 放在main函数循环里+ a2 n- \/ U( a& @5 \$ C
void delay100();    //小延时9 @: y' C+ K+ O

' X7 V+ x9 T, N2 ` //补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名
1 M6 \  s0 Y( J( E4 H //后缀都用_step表示。
% ~6 F) R& A! y2 [; w; X& r( a: y/ f
unsigned char ad_step=0;     //AD扫描步骤变量,
+ e% {, b' S' m( d4 _$ n1 K! ? //补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名
9 T, ]7 w2 n9 p/ _  w //后缀都用_lock表示。
( ^; e4 J1 F' X# j unsigned char key_lock1=0;   //按键自锁标志. i, }' h" R, b* Y+ q

4 Q& e6 }; @" W2 @& }, z' z! } //补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量5 w# U- x0 z* Z* I4 s
//后缀都用_cnt表示。
, c( M3 n! }2 m$ G unsigned int  delay_cnt1=0;     //延时计数器的变量
/ N" M0 ?7 U6 E+ k unsigned int voice_time_cnt;        //蜂鸣器响的声音长短的计数延时
9 M) c0 H* l8 B; v3 _4 [( f
6 G0 @. a: u, I' x) F //补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类
1 n2 C$ y+ R1 ^# ~! f; v/ d2 w2 | //后缀都用_sec表示。
) j/ o. S  q5 v1 b Unsigned char key_sec=0;  //哪个按键被触发
$ ]5 R1 Y' |- g2 o* `
' Z; D1 Z3 T  U2 P# j //补充说明:吴坚鸿程序风格是这样的,凡是只有两种状态(0或者1)的变量,
9 a' D1 H7 Y  P' Y0 F //后缀都用_flag表示。
0 u* E. w# c4 D# ~. C9 ~ Unsigned char AD_Flag=0;   //用来指示单片机内部硬件AD处理完成的标志: c. @# G+ k8 I

3 N3 G- R, U" [5 S# |7 @9 w //跟AD有关的变量
0 v- w1 P4 y$ b7 w Unsigned char key_value=0;     //跟电压成比例关系的AD数值
2 c0 w$ I  m7 i6 m) Z) l" S7 ^. I0 ~; n5 U) }6 T
//主程序
6 ]: W* a/ O7 |, R  \& ~ main()
7 t% g* ^7 B8 }7 |7 j {" Z/ v8 U" y6 |+ {! J# d
# D" {% P0 F4 i' B- Q8 F9 B
             ADCON0=0x40;  //设置AD模式
7 I- `* ~, n4 k$ S" b         ADCON1=0x04;  // AD输入通道: b# T/ r* ^' @- h% B7 x: ^
         TRISA0=1;
; d7 m8 b1 M. ]* d8 x% o" z8 x1 O         TRISA1=1;/ q" s  W7 I/ {! H  x- L
         TRISA3=1;
' G. e* _- ~+ l$ j8 |' S. Y1 `. c/ Q1 @
         TRISB0=0;   //配置蜂鸣器输出0 D  y( w/ W4 e6 n3 Q0 D7 ?$ l& B/ z
1 k- P. D$ j- ?! q
         
/ a# R% ^/ u, F' P, u5 W1 i
& M4 p: T- ?  J+ _  v     ) U+ ]9 ~5 D/ m, @+ G  B

8 ]6 V. P3 l6 S. g2 I         T1CON=0x24;//定时中断! L' L- _& u% l1 P8 I, @- I6 E! L% X
           TMR1H=0xFE;9 k7 Q, n) B3 W8 ~; x* O3 ~
           TMR1L=0xEF;
- P& F# L$ Q1 {" r* O3 A' k         INTCON=0xC0;
. R% `; |& ?; Y' \4 O8 G         TMR1IF=0;9 m5 S# l% m. P( z
         TMR1IE=1;
* D' b( b& H* V5 B3 t- b* B& D: Y$ e4 u# C: t; M
               PIE1=0X00;7 _- t% p3 q& R- b4 U4 f. t
                PIE2=0X00;
  m: ~$ ]3 U/ F* r: p) `4 y. @0 e             ADIF=0;                                //A/D转换中断允许
3 I" l5 M5 ^+ \! q& Y3 p) O* U             ADIE=1;                                //A/D转换中断允许  Q, O( |1 T4 H/ X5 e6 u; [
               PEIE=1;                                //外围中断允许
* ^( p5 |) S. m           GIE=1;
# a0 E7 I- {; R  a4 o/ c& {           TMR1ON=1;   //开定时中断. U, W4 p+ |. h0 [
//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,
" [6 d3 P- Y4 C8 Y& K" C //大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可# Y6 T8 Q) _- y" A. z$ X
! m& ?3 D! h2 d' u$ p; S  A
     beep_dr=0;                               //关蜂鸣器,上电初始化IO
) s5 g+ ?! n, o2 Z3 P9 n
2 W; d* v/ s% p3 Z( e    while(1)    & j. y0 d$ N  R* A
    {
' k7 b, D3 N( H) }8 M- ^7 v% [                      CLRWDT(); //喂看门狗,大家不用过度关注此行/ H' n5 X- j" x* [
ad_samping();   //AD采样
1 y) x2 ?' ?, n" X                 key_service();        //按键服务6 H+ a" `5 A3 X! y3 o- D' t! |
}/ E4 n$ S4 R) Z) d( z

" P) k4 ^3 v/ G: d }
& H- V/ I" K; e        / |$ W. v$ u+ i( E2 I- I) S1 ~

  R4 G- v- }" ^: N% w& q; D void key_scan()                                //按键扫描函数
! }$ d( h; a& B0 i( k; X {  
% H/ m2 ^1 r, [; P0 x, Y   if(key_value>cnt_key_nc) //空闲,没有按下
+ I# F; N: v; g8 }   {; h6 U9 O' }2 m1 o! }
key_lock1=0;  //按键自锁标志清零
/ y% e* B( Z+ H# s! `9 _ delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙        
* |9 X/ ~3 z; { }
7 u8 @+ o* A. V   else if(key_lock1==0) //有按键按下,且是第一次被按下
6 j8 X: Q1 c) ^/ b  ?   {
/ {- m5 r, _( N& n        if(key_value<cnt_key1_up)   //K1按下
" t; k& _5 Z! b3 {) K* R        {8 s  F6 `0 D+ H/ q" Q. ~
++delay_cnt1;  //延时计数器! o7 a6 E7 I$ B: X" Y1 l
     If(delay_cnt1>cnt_delay_cnt1)8 N, x1 J$ y9 v( d: z1 ]
                {2 p+ i8 _  }4 U7 [$ a1 j
delay_cnt1=0;
( h) z0 |; o2 j" e0 h# J8 ^ key_lock1=1;  //自锁按键置位,避免一直触发
/ c2 j" o7 b- A                         key_sec=1;         //触发1号键# d0 w' S" J. i4 E
                 }
; P* ^. Y! @9 q( A         }            8 c% D. N$ F1 Y
         else if(key_value> cnt_key2_down&&key_value< cnt_key2_up)   //K2按下  x* Y2 N$ \3 ~. d' p; _. d
        {. I/ [. u" N2 Q4 i7 o
++delay_cnt1;  //延时计数器
0 j% i9 N3 v# W& [1 n0 }( a     If(delay_cnt1>cnt_delay_cnt1)
9 m; g2 y! R$ J: ]% `                {
+ T; y% k' `, y, L0 d" y) ]! @ delay_cnt1=0;! z5 Z/ y% W- ?
key_lock1=1;  //自锁按键置位,避免一直触发
* X. i7 Q2 I9 R4 f; k2 a. M  @. u                         key_sec=2;         //触发2号键+ m5 S/ }  x* P& m: \  k4 |6 y
                 }9 z, y  j/ C' n1 Y# v& D
          }            # h& F" i' S5 Q* ]
       else if(key_value> cnt_key3_down&&key_value< cnt_key3_up)   //K3按下                              {1 m, r6 V4 y& l" P
++delay_cnt1;  //延时计数器5 q( b' o& n# d: i4 r4 M
     If(delay_cnt1>cnt_delay_cnt1)
; g: ?; E* }& V! n                {. F. r. I4 y) f" q' X3 k6 e
delay_cnt1=0;
2 c1 x6 {$ q7 W! I key_lock1=1;  //自锁按键置位,避免一直触发
6 u# J2 l3 G; C& J7 ?! X( e                         key_sec=3;         //触发3号键
/ ^3 j+ z) J$ j0 c) z/ z8 Q& ]/ ^                 }
9 O6 S. o- i4 d# E6 Y- C        }: N2 C" O' I% d2 i
}     
+ I( S9 N) Z& ^1 I0 J! D$ m. d5 W void key_service()                //按键服务函数9 S& X  Q: g/ b; Y" s4 i9 f
{  k) e- k+ e, L- M' B' x6 {/ O: \
         switch(key_sec)                //按键服务状态切换
; i! R8 Q0 ]; b6 K- T( C3 x: T         {
  p' ~3 a+ ^1 L5 r% J                 case 1:// 1号键
" q2 D/ ^# o# L; K- m$ y7 |" @1 ~: u* d! b; X( M

4 a; X, V: ^9 @7 Y9 R6 Z // 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0
5 ^" M+ @% y2 B" { //时,会不断自减,一直到它为0时,自动把蜂鸣器关闭3 V! S( z  C+ J( \  N) B+ c3 b9 j7 q
                                   voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停
5 v* |3 h& T6 Q$ L5 ^) K                                                                                          
* y" D1 Z* t. V# L, l                         key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,4 f. _3 o' a4 ~) ]
//避免一直触发
  A  C2 F1 r1 S) E                         break;        
% H/ S6 |7 u9 @- N& s- f                 case 2:// 2号键
3 S  O! |  T3 v2 k* P' k4 ]- p$ i7 P                                  voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停1 I6 T6 N! c$ c4 C/ [1 u! \
                         key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
. G7 ^) B3 N& |# c2 ]. C //避免一直触发
9 x8 I  r; d- W  p" w                         break;        
4 Q9 V$ M* K" z2 \) u9 F                 case 3://3号键  e7 u$ W. z$ p4 R

+ Q0 ]2 U% J$ w- h! a# b0 i                                  voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停8 Y3 B' r0 Y! e' d, w" N8 V
                         key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
7 _$ K. X9 q# j# t8 D0 Z) i //避免一直触发; e4 `4 ^" w. O- P: L& R; Z& P# V( S
                         break;        ! s+ G/ |) d' ]( n7 g7 Y
                         * V( ~' w# a+ K5 t9 O9 f3 v: t
         }               
" P1 \5 t; S5 g+ } }2 y  K" l% c+ p* I; {! k# [7 _

/ i) H6 {9 n. r! Q7 R4 O6 s( z void ad_samping()    //AD采样函数,放在main循环里' e  K) O- A( g7 l3 q- e' M
{4 A# Z! V. d: b: \/ a9 H

9 n; u, s1 Q* D5 I switch(ad_step)    5 j. C# \7 N, s: T( e
{     ( I8 l- W, {1 q7 d/ q7 F2 S, F
case 0:+ h( S8 {* m+ H/ v
       ADCON0=0x59;        //切换直流输入通道AD采样AN3
* C  r7 P! U' n3 o1 ]6 P) c       delay100();      //此处为无厘头一个。如果有两个以上的AD通道进行切换,必须把这//个延时加上,否则不好使。这个完全是我的实战经验,这样的事情我经常会遇到,这种
# p( |( O9 K! Z. \7 V# Q+ w; w$ P* r //事情完全靠经验,我当年第一次遇到的时候也被折腾了好久才发现。当然这个项目只有+ s1 `/ P& Y- V% R6 z$ _
//1个AD通道,所以也许可以不用。* `5 W7 M5 ~3 d, h
       ADIF=0;/ O& Y& y, q: X( n7 ~
       CLRWDT();
4 D/ s" C$ C$ z/ @4 z- \, _8 z       ADGO=1;                //启动AD
, ^, q# b2 f! v- p; q$ O( c1 }       ad_step=1;            //下一次循环进入下一个步骤,用switch来切换流程,一直是我
# f  i1 u9 f% R& I: B. y //的最爱,尤其是通道多的时候,它的优越性更加能发挥得淋漓尽致。7 y* B; n& I7 R$ X6 c& f' m
       break;, U6 q7 |+ m5 s
case 1:$ Z: g3 h( [, V6 p0 A. [8 C# K
         if(AD_Flag==1)                        // AD采样完成
0 R; r% n% m9 D$ @! q! c) @6 ^          {! J1 ?  B1 T1 I, T# F; G
              AD_Flag=0;
& q' A# u2 V5 a: R3 A3 I- P6 g             key_value=ADRES;       //采集按键电压的AD数值,; s# R$ w, O5 O4 {* M* D$ t
             ADIF=0;
' D" W+ v  c( k2 R& Z/ I             CLRWDT();( p* R2 n  [4 T% R* _
             ADGO=1;                //启动AD
# U8 U2 N1 x5 @! A$ y7 B; E/ t/ N9 y1 D: t
             ad_step=0;     //下次循环切换回最前面那个步骤,这种控制方式我最喜欢2 t- v! @. f& b

( M; L3 M! I* v! `5 h4 |8 _
+ z1 u1 o7 J& W1 N, D          }
+ C" B% X9 ]# p+ [' o         break;9 t  K& {3 O/ U+ _* H9 Q) s
}" e5 q2 ?- Q, e
}
' F# O2 h, S% W% L# N& a0 ]' y
7 G  x0 V) F- f" v //小延时
! W+ w# p( T  h! Z  L void delay100()
3 `/ }2 L- j- w% T$ D/ J {1 K: R. j* ^! q% f  L) u& c1 Q
   unsigned char k;
$ E1 \) V! g0 L+ F1 `+ d# Z     for(k=0;k<100;k++);
: q/ c: r5 w% w: D2 b }
" v3 u  s9 I: I5 Z$ j
$ V/ W( [- p+ ]9 B7 j  s
+ u! J7 x1 H+ c+ X1 C //中断! h. a" D5 v+ j. a6 q7 p+ H
void interrupt timer1rbint(void)
; \" p6 b7 G+ s. p6 O5 ? {! Y+ h2 B- R( w$ J  @7 G

# n  o' p3 b1 i if(ADIF==1&&ADIE==1)                //AD转换完成中断, y8 i: J4 P1 R+ b; {* Q6 ~% l& E
{
7 L  l6 `' D0 s3 ]5 z               TMR1IE=0;   //禁止定时中断,避免两个中断相互扯淡/ z0 I& {  Q5 ^0 ?" ?& ^
            ADIF=0;                                //清除中断标志
: B, P" n+ e& S2 ^& S            AD_Flag=1;                        //置AD转换完成标志
0 j7 u) [% m/ M* u               TMR1IE=1;  //允许定时中断( k6 S# I  x4 q& I
}8 T$ Z7 r( Q- _# x! Y2 E: Z, d0 W; n
: x6 M7 a; K, T" Q
     if(TMR1IE==1&&TMR1IF==1)    //定时中断6 d/ R' h9 d. T! v
         {8 S: u, w. f9 J* k' ~- {
         
5 \& Y# e$ s) q0 b, n5 D6 i                    ADIE=0;        
$ h! \( ^# t0 Q5 V6 l6 d         
& x8 k+ }1 U9 z, ?: g                TMR1IF=0;+ V9 A0 Z" C5 M6 V' L1 k; q
                 TMR1ON=0;
5 D" P" A5 g- k1 `$ A$ `5 [! y9 p$ |) s
                  key_scan();                    //按键扫描函数. `! W7 {5 H2 f9 U) P0 S  u
       if(voice_time_cnt)                       //控制蜂鸣器声音的长短
1 U2 e' ?  T5 ?/ P& U2 Y                  {
2 m1 H/ K4 R. Z5 D                         beep_dr=1;         //蜂鸣器响
* Q6 g# e: E. d3 H$ y3 ^6 ^* x, N5 g                       --voice_time_cnt;        //蜂鸣器响的声音长短的计数延时
% r( `' Z7 B8 i6 [2 P                  }  J9 s' i" n' N9 P/ Y
                 else $ C: s& m& e# H$ {
                 {
1 u4 f0 F* u' F" N1 e beep_dr=0;      //蜂鸣器停止
8 g# p8 W+ x. [- J( o                 }
) i* d" w# L* m) v$ A+ O$ g! F; h& ~# T+ `9 K
            TMR1H=0xFF;/ \. F* r4 {; y- e) w
            TMR1L=0xC8;
* _* v( U* {# r; y- z            TMR1ON=1;" b# `$ P1 b4 v$ t  `# N. E
             ADIE=1;        
# h3 t. e$ ^5 d' {+ p2 g3 K; z     }) q& S, K, Z8 p. y0 X5 a
}8 `) p; L$ I/ N  ^6 j4 o
9 X! U9 p: Z& T6 n5 I

. b! U5 \1 d6 q  O4 ]1 ?9 e (6)小结:; h3 n( O2 L1 X" k5 N- D
有两路AD通道进行切换时,必须加一个小延时delay100(),否则会出现无厘头现象。“无厘头现象“是鸿哥发明的一个新词,专门用来表示那些莫名其妙的,用理论不好解释的现象。 (未完待续下一节)
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 支持!支持! 反对!反对!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

巢课

技术风云榜

关于我们|手机版|EDA365 ( 粤ICP备18020198号 )

GMT+8, 2025-4-12 02:37 , Processed in 0.056556 second(s), 32 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表