找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

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

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

[复制链接]

551

主题

1470

帖子

3万

积分

EDA365管理团队

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

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

EDA365欢迎您!

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

x
(1)        开场白:
$ p- B" Q$ ~. l1 u" m AD按键可以实现一个IO口驱动几个按键的目的,以鸿哥的经验,一个IO口控制的按键数量最好不要超过3个。从实际应用来考虑,独立按键的方式最好,其次是行列按键,最次是AD按键。8 t5 @: @' ^4 W* V  \
(2)功能需求:每按一个按键,蜂鸣器就响一次。
4 t4 f/ i1 `) _ (3)硬件原理:$ [* n1 o4 s( Y  z0 c5 K4 ~
(a)用4个电阻竖着串联起来,最上端接5V,最下端接地。从上往下,最上端接5V的算第一个节点,最下端接地算最后一个节点,共5个节点。用一个带AD 的IO口连接到第2个节点上,此节点上连接第一个按键,按键的另一端接地。以此方式,第二个按键连接到第3个节点,第三个按键连接到第4个节点。这4个电 阻的目的主要是用来分压,靠不同的电压来识别不同的按键。
$ z1 Z: P$ h2 o. r9 p2 S (b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。4 b0 Y4 _8 T) q. q" k! E7 v+ {
(4)源码适合的单片机IC16f73,晶振为4MHz
4 u4 U1 l. I+ d0 l* P0 o (5)源代码讲解如下:
) M3 h% H* U# p  i* ] #include<pic.h>         //包含芯片相关头文件8 N, Z4 F! e- r$ U0 b3 E
9 _- }, W6 Y* f9 I5 T
//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr
9 W" t/ @; L  I( J
# D: t4 c) D0 A9 X8 [ #define  beep_dr  RB0  //蜂鸣器输出
3 L0 q7 j$ C) d: |! L
7 q) v0 V9 D  p9 W2 @ //补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量7 E/ {& H3 _2 z5 J; X. ^
//前缀都用cnt_表示。凡是延时常量,都应该根据上机实际情况来调整出最佳的数值
+ ?. O* ^( j* D" a7 v# I #define cnt_delay_cnt1   40  //按键去抖动延时阀值- P! I6 i, O/ @% B" @6 M
#define cnt_voice_time   75 //蜂鸣器响的声音长短的延时阀值6 g7 E' W- D8 }( P+ f7 c
#define cnt_key_nc  185   //没有按键按下时电压对应的AD数值
+ y- K- C8 P  @, M" d4 i #define cnt_key1_up      185   //1号按键按下时电压对应的AD数值上限
3 ^$ T, }1 e" [0 ] #define cnt_key2_down  117  //2号按键按下时电压对应的AD数值下限/ d4 W5 c4 @2 }' G; p
#define cnt_key2_up      137  //2号按键按下时电压对应的AD数值上限
- c( m6 c* |% _( g0 w #define cnt_key3_down  160  //3号按键按下时电压对应的AD数值下限0 k  R, Y; z' ?7 w+ u
#define cnt_key3_up      175  //3号按键按下时电压对应的AD数值上限& O# s; @3 r  c. {

% }3 q  Z7 }) c3 s! s( B# c
, C) g# u) q: J) h4 ~% t  s! V0 ^8 e
( t) q# Z* w8 `" \; V3 u
//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中
$ ]( t1 A- \6 t7 y6 V$ H- ~ //断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。& g" T" a8 h! j) U) G% c( E
void key_scan();                         //按键扫描函数,放在定时中断里( L+ l: ^( H: N! ~6 J$ L! S- d
void key_service();                            //按键服务函数,放在main函数循环里
! l1 b1 {' S( d" X& v% o. q void ad_samping();                         //AD采样, 放在main函数循环里
/ @% q; q7 U; H& g  T/ k void delay100();    //小延时" H: g; b) T9 [  m, M

" J# e* N9 S- m. p! p0 }( p5 V! k //补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名1 g( i6 `: [( E8 Z% C" X$ x
//后缀都用_step表示。. W: v( b% l0 d/ K+ y
! J/ C: {+ F/ C2 C5 m2 z
unsigned char ad_step=0;     //AD扫描步骤变量,
. X: ?" S5 X0 _/ |+ {0 y2 k: _ //补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名
( g- H! G# T5 r" O+ k: u //后缀都用_lock表示。
1 a" {1 h2 p+ _' F% T unsigned char key_lock1=0;   //按键自锁标志
0 z" t8 \9 k; }
0 \! K2 d0 Y$ W2 J( U$ B5 |  t //补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量
0 x/ ~+ C5 ]/ }  l" P //后缀都用_cnt表示。* E  F: U& w$ b1 A$ d
unsigned int  delay_cnt1=0;     //延时计数器的变量
5 ?; v) G, q( Y unsigned int voice_time_cnt;        //蜂鸣器响的声音长短的计数延时
) `# ?0 B/ n" Q& p7 d5 W5 |* C( m0 H' K$ p" J& {) U
//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类
' B  G. Z7 x1 P' Q* d //后缀都用_sec表示。& w7 K2 A" A9 x
Unsigned char key_sec=0;  //哪个按键被触发
- }" |1 d' Z! x0 }2 j
% x/ c1 c" _* a9 o //补充说明:吴坚鸿程序风格是这样的,凡是只有两种状态(0或者1)的变量,
$ ?+ f" w. @# d9 P+ K: m, w+ _ //后缀都用_flag表示。
1 u( u" ]" c+ N. N% S Unsigned char AD_Flag=0;   //用来指示单片机内部硬件AD处理完成的标志
, B3 B6 {8 Y) c( c/ b& U& z- R8 ], t
//跟AD有关的变量8 J; B; w1 p8 L
Unsigned char key_value=0;     //跟电压成比例关系的AD数值8 A& o' j* R$ G8 R4 n# C

4 Y7 p; L5 O% Y: A) R+ n& S% ? //主程序) |4 K  c  F- }( R( `
main()' u+ x4 c% \* [# X% }5 B1 X
{
' d! h. x; s3 p' j. |9 }6 V3 }5 B- F+ N
             ADCON0=0x40;  //设置AD模式0 h% n' u% X9 K
         ADCON1=0x04;  // AD输入通道
. ]% D# q$ O4 A         TRISA0=1;
& M% t! u1 Z3 e7 {         TRISA1=1;
2 a) S$ T6 R2 v2 E/ i         TRISA3=1;
/ [7 U/ T# w8 W% y7 u& m, m8 Z0 s8 T7 p* K% A9 Z
         TRISB0=0;   //配置蜂鸣器输出
% m, I. V5 g$ b0 C4 T3 A
$ Q0 j- J: z' h2 C( T3 {         ' M6 j! ]5 |  i

& C5 D" N+ F2 ?     
+ C5 d% W8 c& W6 G* ]" c( i  k( _  y# [+ N
         T1CON=0x24;//定时中断3 o3 z5 H* ]1 {' s! m/ u$ A
           TMR1H=0xFE;' }( _( c0 P1 u) L5 \6 K
           TMR1L=0xEF;" R8 v5 l$ R" t5 K
         INTCON=0xC0;
7 o6 z& u- X$ r9 F3 i         TMR1IF=0;/ ]0 h8 ?2 T4 B
         TMR1IE=1;! {8 o% c4 h, ^, F% H5 \
$ X+ e  o  n: A% ^9 O" v3 O0 @
               PIE1=0X00;
0 r! h9 Q& M% n" M; B: `                PIE2=0X00;
- A) C4 M& Z5 |; l5 j             ADIF=0;                                //A/D转换中断允许7 l/ @! Q7 k* \% g8 ^( ^
             ADIE=1;                                //A/D转换中断允许1 j# H; q+ n# E. ^# W
               PEIE=1;                                //外围中断允许
, q! E5 c7 b; Q, E3 t( X, t0 B+ J           GIE=1;- s$ d0 v3 o5 ]# x
           TMR1ON=1;   //开定时中断
) T. Q/ p' U2 v5 ?9 J$ l4 P4 y8 N //补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,1 U* B' w# V+ A. V
//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可' d6 v: b& `8 e% a% r: i
; u/ |' E2 s7 W1 D- _% `; ?0 v7 q
     beep_dr=0;                               //关蜂鸣器,上电初始化IO
, `3 K$ J" V% x  ~
! I- W4 T) Y7 Q& F* ~1 l( F    while(1)    8 [, v7 L4 B9 c) b. M, E
    {0 a  ~3 C7 f' i: |& p! A
                      CLRWDT(); //喂看门狗,大家不用过度关注此行; ~( {1 U. [, |. D1 Y
ad_samping();   //AD采样
7 b& D8 k1 i% F4 w0 V: U8 ^4 H                 key_service();        //按键服务
& N6 A: H/ Q0 D: O/ K }
1 V3 m" j" t1 h9 t$ p2 O$ A' N) H. ^8 \! ?4 O, K
}
& N$ j' @) ~  w; Q        
1 p" y+ s% L& ^6 k
8 e, V2 L- I' z6 d  K" `5 W! t; F void key_scan()                                //按键扫描函数9 o/ J3 y% C. E) S6 L' c/ d7 W
{  0 \/ n0 a0 L6 }/ D: }
   if(key_value>cnt_key_nc) //空闲,没有按下
( ^: g9 A$ x; Z5 p+ n   {' v, b4 c. O. t9 D# F
key_lock1=0;  //按键自锁标志清零4 X; O! [6 P4 l* g
delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙        
+ W3 ?- @- Y6 N# p; S }; p0 T* q! S) D9 e3 ?0 o8 E
   else if(key_lock1==0) //有按键按下,且是第一次被按下3 G; l  F/ F$ y$ L  q, v
   {
& y' o$ S% E+ Q# U( ^: j% B( H; Q        if(key_value<cnt_key1_up)   //K1按下
% v' u, T% e( w9 y' C7 q        {
( v3 u; d  [3 a! I ++delay_cnt1;  //延时计数器
7 _3 H3 ?1 {3 X) s+ C! T     If(delay_cnt1>cnt_delay_cnt1)
: Y+ c* e8 t" @/ I+ J- f& @                {$ ~% O6 A* A7 _/ q
delay_cnt1=0;
1 q2 v1 k: U1 g7 Q3 q key_lock1=1;  //自锁按键置位,避免一直触发
4 Y' b; j# |$ p+ H; r                         key_sec=1;         //触发1号键
7 B3 u& F6 k4 \' o                 }5 x" x  _- J3 c: V: _4 ?
         }            
7 ?7 F' b: |, \; T# `         else if(key_value> cnt_key2_down&&key_value< cnt_key2_up)   //K2按下, q" ~8 g! u1 ?: D/ ~' P$ ?
        {
( z, U  ^) ]" R( o3 i ++delay_cnt1;  //延时计数器
: a+ q) v2 c' D     If(delay_cnt1>cnt_delay_cnt1); s9 {; [( }8 [' G
                {
% H3 w4 ^% q0 Z delay_cnt1=0;* t. q/ F' C7 R$ x
key_lock1=1;  //自锁按键置位,避免一直触发
; h8 P  h7 G! Y/ K                         key_sec=2;         //触发2号键
% V0 P8 l) ]' s3 L                 }
1 f$ u" x' ]( `; Z# |. r          }            9 V3 X: r2 b- I
       else if(key_value> cnt_key3_down&&key_value< cnt_key3_up)   //K3按下                              {2 y7 o# c0 V* k6 }" X
++delay_cnt1;  //延时计数器* V0 ]+ f. i# U, l% s% |
     If(delay_cnt1>cnt_delay_cnt1)1 M# z) o) v& D/ o
                {! \: l* o( `1 j8 G0 G
delay_cnt1=0;, g( T+ b8 `; T- R) `3 \( ?6 S7 g" _
key_lock1=1;  //自锁按键置位,避免一直触发7 w# [: Y& y$ g0 z. c1 \
                         key_sec=3;         //触发3号键2 V; D( }. d4 B: d" p- @
                 }$ ]+ ~; n1 Z! }4 I! e9 \
        }. k( H0 e& v7 j; L
}     
- e: E) v5 G& a' i/ y void key_service()                //按键服务函数3 f/ d0 I$ A2 m" s4 f+ t3 q
{6 d3 X1 s$ Y1 \# A/ j3 v7 M4 M  z
         switch(key_sec)                //按键服务状态切换) e1 G- m1 ?. G0 d2 \1 r
         {+ N# {; s) G8 ~+ \- O! Y4 ]$ w
                 case 1:// 1号键! H/ H) |9 v, Z. e
  O% I, A( `* M6 l' E) l, u
: j& c' \6 m- s: F* q. p' S
// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0& g5 T: b- q: z/ C8 s
//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭
% i  |+ m2 Y7 x8 f3 H, F! e, q                                   voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停) q) X5 M- x3 n) |+ b
                                                                                          
9 G) U% f4 `+ U1 ?0 `3 q                         key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
) Y+ n6 r3 ?* |$ ?' M) t //避免一直触发2 `5 \0 h1 k: f2 o# e5 }2 k- E* n
                         break;        4 X8 z' y9 K* y* ~! U( O: P6 |
                 case 2:// 2号键7 o) q9 M' q: Z# Z5 o6 V/ r
                                  voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停1 a( f& l: b0 y* Y) }7 y2 I
                         key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,
( s! A3 u& k- X& }  _) f; | //避免一直触发
: C" O0 J! d: i                         break;        
/ U4 J$ G& K' u                 case 3://3号键! `0 J2 F5 j1 D% N
- ?" b' c7 v* j3 Z: b
                                  voice_time_cnt= cnt_voice_time;    //蜂鸣器响“滴”一声就停& M- M, I$ d3 G6 O  c' \
                         key_sec=0;   //相应完按键处理程序之后,把按键选择变量清零,) y0 A- x0 z; U2 L3 N0 k) f4 H! t
//避免一直触发/ W. @1 [7 G; L" R/ u
                         break;        , v8 y. u3 y8 T/ {, p4 q0 P8 k/ j
                        
- [2 `9 ?+ ?: ~- b5 c9 B0 S& [7 E         }               
+ l9 I; B$ _( Q1 }; Y }
* b2 F, X3 T. _  w& s. Z) a
3 Y1 W. u; v2 m! Y) R, D  G9 n void ad_samping()    //AD采样函数,放在main循环里9 D6 \1 ]. ?  b1 p* a5 U7 x
{
6 |  f6 S0 i, k! L' z1 k/ d+ e  }% `- ~
switch(ad_step)    ( W( Y4 Q- k  v% w$ B3 y4 E! ]* T
{     % A/ b4 ~, y8 Y2 s4 S
case 0:
8 Y- o" s/ ?+ f7 M6 P0 [       ADCON0=0x59;        //切换直流输入通道AD采样AN3( l; \# Z* P( b  ~, U% m/ \( C0 y
       delay100();      //此处为无厘头一个。如果有两个以上的AD通道进行切换,必须把这//个延时加上,否则不好使。这个完全是我的实战经验,这样的事情我经常会遇到,这种
1 Z7 V; e3 e) h2 y //事情完全靠经验,我当年第一次遇到的时候也被折腾了好久才发现。当然这个项目只有
) o, I9 W4 @5 U# H! [- X$ c //1个AD通道,所以也许可以不用。
' G9 F4 V3 U4 d       ADIF=0;' f0 F! D6 W& z. O! E' b
       CLRWDT();# S; R- f% I- s% p6 W- W
       ADGO=1;                //启动AD ' K& ]7 V0 k6 R
       ad_step=1;            //下一次循环进入下一个步骤,用switch来切换流程,一直是我& L1 _7 w$ h0 y
//的最爱,尤其是通道多的时候,它的优越性更加能发挥得淋漓尽致。- G1 Y1 `  h& g
       break;, q/ D# n5 C$ A9 `5 \3 J. y
case 1:. ^- K( h, N+ h; @1 i4 v) j
         if(AD_Flag==1)                        // AD采样完成
7 B' E+ n" P/ l2 m+ r          {
" L% d- b6 c+ ~9 D! w7 r2 ?. m; N              AD_Flag=0;
: }% d) \$ T8 ?) G$ r& `             key_value=ADRES;       //采集按键电压的AD数值,. `0 A$ \  L: x1 h. [5 J6 a7 ]
             ADIF=0;
9 \$ e2 E2 H7 f+ j8 ]             CLRWDT();) i, `% U! u- b8 L' K
             ADGO=1;                //启动AD
8 o+ G! |0 m3 J) i4 q* q1 u2 P
! o* S3 ^7 g5 f$ a! o, D9 C             ad_step=0;     //下次循环切换回最前面那个步骤,这种控制方式我最喜欢
2 N/ c. z( B/ e; y  _& F' @7 E% z5 ]9 _' T! t3 T4 F( C' D

0 w, e7 q, T8 `7 [1 f4 |2 D          }9 n: g" g# v- a- B1 i' y% l
         break;& Y; d1 L+ k' A. g/ i4 B
}
! b3 X( U7 j. l1 d/ d! Z9 i }
: q8 `' }1 x7 x- O7 T' V& j9 A& X$ J; P. d. O1 t4 r5 B  t( S2 f& g3 c% y
//小延时
* ?7 G; ?0 a/ j void delay100()
4 t/ i- G. @4 p" c& u* x" u. ` {
$ u- R( g7 Z0 M5 V2 m3 Y   unsigned char k;
1 R* B$ D9 \! m7 \0 J& X     for(k=0;k<100;k++);
. Y) V% k2 u& [4 |+ b) F2 W7 n }
2 g' _; D$ p/ s
! }; e" j' G/ g% n- ^4 x- a' i* [$ j% Q9 K- l. B9 [
//中断. E( r- ?- R$ u9 K, O& R$ U
void interrupt timer1rbint(void)
3 ^: \! o; e  V {, N2 `$ N1 ]. @, F6 }  z

9 Q# ^9 i- H% M6 i. a if(ADIF==1&&ADIE==1)                //AD转换完成中断# v$ G) b! m% g# s
{
) [8 L& U/ P7 C) `               TMR1IE=0;   //禁止定时中断,避免两个中断相互扯淡
0 w, n2 [* [- x+ i            ADIF=0;                                //清除中断标志
  O8 R1 J, z; y  K            AD_Flag=1;                        //置AD转换完成标志! i/ I) X& K  ]6 Q( ~; w
               TMR1IE=1;  //允许定时中断
9 Q( M1 h; k8 ]% Z; V0 L1 S/ M }# R2 U& X- A5 _' M& M" ]

; Y+ T! L- K4 G. d2 m5 k. O     if(TMR1IE==1&&TMR1IF==1)    //定时中断; n6 q. D7 n9 y2 B/ J3 v9 q( l+ j( H
         {+ l- f* _9 Z* g2 u; J# m6 ^* u/ h( @
          6 M7 I* g, V6 }+ ?
                    ADIE=0;        
8 K0 c% ~7 s5 H6 n! v9 Z; m          " q3 i# z$ p6 A+ v, c3 Q0 ?2 v
                TMR1IF=0;  P% i! l" q' G1 {% }" Y! m8 V
                 TMR1ON=0;
$ q- t: T  G7 i3 A* b4 e* D
6 w: t) f& M, X7 T5 j                  key_scan();                    //按键扫描函数  \* z1 F$ ?- L/ X
       if(voice_time_cnt)                       //控制蜂鸣器声音的长短
7 }" [9 F6 O3 m1 o8 W3 o9 p                  {
" i  k' A' h, }0 V                         beep_dr=1;         //蜂鸣器响9 G' S0 i. w# R4 [! ~, d
                       --voice_time_cnt;        //蜂鸣器响的声音长短的计数延时' D) ?, \1 h, }  |& s9 Z0 i0 r! H
                  }& [' E- _/ g3 E/ r; E# c7 k0 J- t
                 else $ q# F& d! z! _% B2 Q
                 {. |" E  f# Z/ w4 |' B
beep_dr=0;      //蜂鸣器停止
, R  j/ n, }% e  v: Q                 }( I; M' z) x6 n2 t0 Z5 b7 I" N( R# Q
. v( q; y# b( c% Z# @
            TMR1H=0xFF;' r' s% n" R) B7 G
            TMR1L=0xC8;7 l# s- Q: d4 |8 g, {
            TMR1ON=1;
' f6 P9 H9 w$ ]             ADIE=1;        ; ]7 o6 N! S2 ^' C1 z, ^
     }; w/ H& J# E8 L! r
}
# N. \" y* Z: A  Z  R: Y1 U: `. ]$ l4 v* H9 T, f( \' V3 n' S
! K: l. K2 b& M
(6)小结:
4 {- h% F0 \- V: Q: \ 有两路AD通道进行切换时,必须加一个小延时delay100(),否则会出现无厘头现象。“无厘头现象“是鸿哥发明的一个新词,专门用来表示那些莫名其妙的,用理论不好解释的现象。 (未完待续下一节)
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 支持!支持! 反对!反对!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

巢课

技术风云榜

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

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

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

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

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