当前位置:文档之家› [PIC单片机] 吴坚鸿单片机程序风格赏析【1】

[PIC单片机] 吴坚鸿单片机程序风格赏析【1】

[PIC单片机] 吴坚鸿单片机程序风格赏析【1】
[PIC单片机] 吴坚鸿单片机程序风格赏析【1】

(一)按键行列扫描与蜂鸣器

(1)技术体会:在行列式扫描结构的薄膜按键里,干扰很大,按键扫描程序非常讲究,尤其是去抖动的处理。(2)功能需求:每按一个按键,蜂鸣器就响一次。

(3)硬件原理:

(a)用4个IO来做2X2按键行列扫描,其中作为输入的2个IO口必须接上拉电阻20K左右。

(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。而无源蜂鸣器是要靠断断续续的开关信号来驱动才能响,就是要频率来驱动。

(4)源码适合的单片机:PIC18F4620,晶振为22.1184MHz

(5)源代码讲解如下:

#include //包含芯片相关头文件

//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr

#define beep_dr LATA1 //蜂鸣器输出

#define key_dr1 LATB3 //2X2按键行输出

#define key_dr2 LATB4 //2X2按键行输出

#define key_sr1 RB6 //2X2按键行输入

#define key_sr2 RB7 //2X2按键行输入

//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量

//前缀都用cnt_表示。

#define cnt_delay_cnt1 25 //按键去抖动延时阀值

#define cnt_delay_cnt2 5 //按键行输出信号稳定的小延时阀值

#define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值

void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗

//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中

//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。

void key_scan(); //按键扫描函数,放在定时中断里

void key_service(); //按键服务函数,放在main函数循环里

//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名

//后缀都用_step表示。

unsigned char key_step=1; //按键扫描步骤变量,在switch()语句的括号里

//补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名

//后缀都用_lock表示。

unsigned char key_lock1=0; //按键自锁标志

//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量

//后缀都用_cnt表示。

unsigned int delay_cnt1=0; //延时计数器的变量

unsigned int delay_cnt2=0; //延时计数器的变量

unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时

//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类

//后缀都用_sec表示。

Unsigned char key_sec=0; //哪个按键被触发

//主程序

main()

{

ADCON0=0x00;

ADCON1=0x0f; //全部为数字信号

ADCON2=0xa1; //右对齐

RBPU=0; //上拉电阻

SSPEN=0; //决定RA5不作为串口

TRISB3=0; //配置按键行扫描IO为输出

TRISB4=0; //配置按键行扫描IO为输出

TRISB6=1; //配置按键列扫描IO为输入

TRISB7=1; //配置按键列扫描IO为输入

T1CON=0x24; //定时器中断配置

TMR1H=0xF5;

TMR1L=0x5F;

TMR1IF=0;

TMR1IE=1;

TMR1ON=1;

TMR1IE=1;

//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,

//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可

beep_dr=0; //关蜂鸣器,上电初始化IO

while(1)

{

CLRWDT(); //喂看门狗,大家不用过度关注此行

key_service(); //按键服务

}

}

void key_scan() //按键扫描函数

{

//补充说明:如果中断一次就把所有的按键都扫描完,中断占用的时间片就会太多,势//必会影响main函数里其他子程序的运行,为了避免一口气把所//的按键都扫描完,此

//处用switch语句把4个按键分成2等分,一次中断只扫描2个按键

switch(key_step) //按键扫描步骤,

{

case 1: //扫描1号键,2号键

key_dr1=0; //按键行扫描输出第一行低电平

key_dr2=1;

delay_cnt2=0; //延时计数器清零

key_step++; //切换到下一个运行步骤

break;

case 2:

delay_cnt2++;

if(delay_cnt2>cnt_delay2) //小延时,但不是去抖动延时,替代一直受网友争议的delay1(40)

{

delay_cnt2=0;

key_step++; //切换到下一个运行步骤

}

break;

case 3:

If (key_sr1==1&&key_sr2==1)

{ //如果没有按键按下,则2个IO输入都是高电平

key_step++; //如果没有按键按下,下一个中断扫描下2个按键

key_lock1=0; //按键自锁标志清零

delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙

}

Else if (key_sr1==0&&key_sr2==1&&key_lock1==0)

{ // key_lock1按键自锁,避免按键一直触发,下降沿有效

++delay_cnt1; //延时计数器

//补充说明:有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常

//巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,

//在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某//种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零

//我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上

//给我加薪1000元。

if(delay_cnt1>cnt_delay_cnt1) //延时计数器超过一定的数值

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发,只有松开按键,

//此标志位才会被清零

key_sec=1; //触发1号键

}

}

else if(key_sr1==1&&key_sr2==0&&key_lock1==0)

{

++delay_cnt1;

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=2; //触发2号键

}

}

break;

case 4: //扫描//扫描3号键,4号键

key_dr1=1;

key_dr2=0; //按键行扫描输出第二行低电平

delay_cnt2=0; //延时计数器清零

key_step++; //切换到下一个运行步骤

break;

case 5:

delay_cnt2++;

if(delay_cnt2>cnt_delay2) //小延时,但不是去抖动延时,替代一直受网友争议的delay1(40)

{

delay_cnt2=0;

key_step++; //切换到下一个运行步骤

}

break;

case 6:

if(key_sr1==1&&key_sr2==1)

{

key_step++;

key_lock1=0;

delay_cnt1=0;

}

Else if(key_sr1==0&&key_sr2==1&&key_lock1==0)

{

++delay_cnt1;

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1;

key_sec=3; //触发3号键

}

}

else if(key_sr1==1&&key_sr2==0&&key_lock1==0)

{

++delay_cnt1;

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=4; //触发4号键

}

}

break;

}

if(key_step>6) //第1组按键与第2组按键反复轮流扫描

{

key_step=1;

}

}

void key_service() //按键服务函数

{

switch(key_sec) //按键服务状态切换

{

case 1:// 1号键

// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0

//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 2:// 2号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 3://3号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 4://4号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

}

}

//中断

void interrupt timer1rbint(void)

{

if(TMR1IE==1&&TMR1IF==1) //定时中断

{

TMR1IF=0; //定时中断标志位关闭

TMR1ON=0; //定时中断开关关闭

key_scan(); //按键扫描函数

if(voice_time_cnt) //控制蜂鸣器声音的长短

{

beep_dr=1; //蜂鸣器响

--voice_time_cnt; //蜂鸣器响的声音长短的计数延时

}

else

{

beep_dr=0; //蜂鸣器停止

}

TMR1H=0xF5; //重新设置定时时间间隔

TMR1L=0x5F;

TMR1ON=1; //定时中断开关打开

}

}

void delay1(unsigned int de)

{

unsigned int t;

for(t=0;t

}

(6)小结:

以上是我常用的编程结构。后续我做的所有项目基本上是这样一种编程结构。这一节技术上要特别重视按键扫描。有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,在延时去抖动的时候,还必须要监控延时这段时间里,按键IO 输入口是否会由于受到某种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零,我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上给我加薪1000元。(未完待续下一节)

第二节:独立按键扫描与蜂鸣器

(1)学习目标:利用上一节的程序框架,把按键行列扫描方式改成独立按键扫描方式。通过这节的练习,加深熟悉吴坚鸿的程序框架,以及独立按键扫描的编程方式。

(2)功能需求:每按一个按键,蜂鸣器就响一次。

(3)硬件原理:

(a)用4个IO来做独立按键扫描,4个IO口都必须接上拉电阻20K左右。

(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。

(4)源码适合的单片机:PIC18F4620,晶振为22.1184MHz

(5)源代码讲解如下:

#include //包含芯片相关头文件

//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr

#define beep_dr LATA1 //蜂鸣器输出

#define key_sr1 RB6 //独立按键输入

#define key_sr2 RB7 //独立按键输入

#define key_sr3 RB3 //独立按键输入

#define key_sr4 RB4 //独立按键输入

//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量

//前缀都用cnt_表示。凡是延时常量,都应该根据上机实际情况来调整出最佳的数值

#define cnt_delay_cnt1 25 //按键去抖动延时阀值

#define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值

//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中

//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。

void key_scan(); //按键扫描函数,放在定时中断里

void key_service(); //按键服务函数,放在main函数循环里

//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名

//后缀都用_step表示。

unsigned char key_step=1; //按键扫描步骤变量,在switch()语句的括号里

//补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名

//后缀都用_lock表示。

unsigned char key_lock1=0; //按键自锁标志

unsigned char key_lock2=0; //按键自锁标志

unsigned char key_lock3=0; //按键自锁标志

unsigned char key_lock4=0; //按键自锁标志

//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量

//后缀都用_cnt表示。

unsigned int delay_cnt1=0; //延时计数器的变量

unsigned int delay_cnt2=0; //延时计数器的变量

unsigned int delay_cnt3=0; //延时计数器的变量

unsigned int delay_cnt4=0; //延时计数器的变量

unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时

//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类

//后缀都用_sec表示。

Unsigned char key_sec=0; //哪个按键被触发

//主程序

main()

{

ADCON0=0x00;

ADCON1=0x0f; //全部为数字信号

ADCON2=0xa1; //右对齐

RBPU=0; //上拉电阻

SSPEN=0; //决定RA5不作为串口

TRISB3=1; //配置独立按键IO为输入

TRISB4=1; //配置独立按键IO为输入

TRISB6=1; //配置独立按键IO为输入

TRISB7=1; //配置独立按键IO为输入

T1CON=0x24; //定时器中断配置

TMR1H=0xF5;

TMR1L=0x5F;

TMR1IF=0;

TMR1IE=1;

TMR1ON=1;

TMR1IE=1;

//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,

//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可

beep_dr=0; //关蜂鸣器,上电初始化IO

while(1)

{

CLRWDT(); //喂看门狗,大家不用过度关注此行

key_service(); //按键服务

}

}

void key_scan() //按键扫描函数

{

//补充说明:因为独立按键扫描没有了行列按键扫描的delay1(40)延时,所以速度很//快,可以一次中断扫描完所有的按键,不用switch(key_step)语句来分段

if(key_sr1==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位{

key_lock1=0; //按键自锁标志清零

delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock1==0) //有按键按下,且是第一次被按下

{

++delay_cnt1; //延时计数器

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=1; //触发1号键

}

}

if(key_sr2==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位{

key_lock2=0; //按键自锁标志清零

delay_cnt2=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock2==0) //有按键按下,且是第一次被按下

{

++delay_cnt2; //延时计数器

if(delay_cnt2>cnt_delay_cnt1)

{

delay_cnt2=0;

key_lock2=1; //自锁按键置位,避免一直触发

key_sec=2; //触发2号键

}

}

if(key_sr3==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位{

key_lock3=0; //按键自锁标志清零

delay_cnt3=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock3==0) //有按键按下,且是第一次被按下

{

++delay_cnt3; //延时计数器

if(delay_cnt3>cnt_delay_cnt1)

{

delay_cnt3=0;

key_lock3=1; //自锁按键置位,避免一直触发

key_sec=3; //触发3号键

}

}

if(key_sr4==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位{

key_lock4=0; //按键自锁标志清零

delay_cnt4=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock4==0) //有按键按下,且是第一次被按下

{

++delay_cnt4; //延时计数器

if(delay_cnt4>cnt_delay_cnt1)

{

delay_cnt4=0;

key_lock4=1; //自锁按键置位,避免一直触发

key_sec=4; //触发4号键

}

}

}

void key_service() //按键服务函数

{

switch(key_sec) //按键服务状态切换

{

case 1:// 1号键

// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0 //时,会不断自减,一直到它为0时,自动把蜂鸣器关闭

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 2:// 2号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 3://3号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 4://4号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

}

}

//中断

void interrupt timer1rbint(void)

{

if(TMR1IE==1&&TMR1IF==1) //定时中断

{

TMR1IF=0; //定时中断标志位关闭

TMR1ON=0; //定时中断开关关闭

key_scan(); //按键扫描函数

if(voice_time_cnt) //控制蜂鸣器声音的长短

{

beep_dr=1; //蜂鸣器响

--voice_time_cnt; //蜂鸣器响的声音长短的计数延时

}

else

{

beep_dr=0; //蜂鸣器停止

}

TMR1H=0xF5; //重新设置定时时间间隔

TMR1L=0x5F;

TMR1ON=1; //定时中断开关打开

}

}

(6)小结:

利用上一节的程序框架,把行列扫描改成独立按键扫描的速度非常快,体现了吴坚鸿程序框架的优越性。通过此节的学习,让大家加深熟悉吴坚鸿程序框架,同时也掌握了独立按键的编程方式。(未完待续下一节)

第三节:AD按键扫描与蜂鸣器

(1)开场白:

AD按键可以实现一个IO口驱动几个按键的目的,以鸿哥的经验,一个IO口控制的按键数量最好不要超过3个。从实际应用来考虑,独立按键的方式最好,其次是行列按键,最次是AD按键。

(2)功能需求:每按一个按键,蜂鸣器就响一次。

(3)硬件原理:

(a)用4个电阻竖着串联起来,最上端接5V,最下端接地。从上往下,最上端接5V的算第一个节点,最下端接地算最后一个节点,共5个节点。用一个带AD的IO口连接到第2个节点上,此节点上连接第一个按键,按键的另一端接地。以此方式,第二个按键连接到第3个节点,第三个按键连接到第4个节点。这4个电阻的目的主要是用来分压,靠不同的电压来识别不同的按键。

(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。

(4)源码适合的单片机:PIC16f73,晶振为4MHz

(5)源代码讲解如下:

#include //包含芯片相关头文件

//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr

#define beep_dr RB0 //蜂鸣器输出

//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量

//前缀都用cnt_表示。凡是延时常量,都应该根据上机实际情况来调整出最佳的数值

#define cnt_delay_cnt1 40 //按键去抖动延时阀值

#define cnt_voice_time 75 //蜂鸣器响的声音长短的延时阀值

#define cnt_key_nc 185 //没有按键按下时电压对应的AD数值

#define cnt_key1_up 185 //1号按键按下时电压对应的AD数值上限

#define cnt_key2_down 117 //2号按键按下时电压对应的AD数值下限

#define cnt_key2_up 137 //2号按键按下时电压对应的AD数值上限

#define cnt_key3_down 160 //3号按键按下时电压对应的AD数值下限

#define cnt_key3_up 175 //3号按键按下时电压对应的AD数值上限

//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中

//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。

void key_scan(); //按键扫描函数,放在定时中断里

void key_service(); //按键服务函数,放在main函数循环里void ad_samping(); //AD采样, 放在main函数循环里

void delay100(); //小延时

//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名

//后缀都用_step表示。

unsigned char ad_step=0; //AD扫描步骤变量,

//补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名

//后缀都用_lock表示。

unsigned char key_lock1=0; //按键自锁标志

//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量

//后缀都用_cnt表示。

unsigned int delay_cnt1=0; //延时计数器的变量

unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时

//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类

//后缀都用_sec表示。

Unsigned char key_sec=0; //哪个按键被触发

//补充说明:吴坚鸿程序风格是这样的,凡是只有两种状态(0或者1)的变量,

//后缀都用_flag表示。

Unsigned char AD_Flag=0; //用来指示单片机内部硬件AD处理完成的标志

//跟AD有关的变量

Unsigned char key_value=0; //跟电压成比例关系的AD数值

//主程序

main()

{

ADCON0=0x40; //设置AD模式

ADCON1=0x04; // AD输入通道

TRISA0=1;

TRISA1=1;

TRISA3=1;

TRISB0=0; //配置蜂鸣器输出

T1CON=0x24;//定时中断

TMR1H=0xFE;

TMR1L=0xEF;

INTCON=0xC0;

TMR1IF=0;

TMR1IE=1;

PIE1=0X00;

PIE2=0X00;

ADIF=0; //A/D转换中断允许

ADIE=1; //A/D转换中断允许

PEIE=1; //外围中断允许GIE=1;

TMR1ON=1; //开定时中断

//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,

//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可beep_dr=0; //关蜂鸣器,上电初始化IO

while(1)

{

CLRWDT(); //喂看门狗,大家不用过度关注此行

ad_samping(); //AD采样

key_service(); //按键服务

}

}

void key_scan() //按键扫描函数

{

if(key_value>cnt_key_nc) //空闲,没有按下

{

key_lock1=0; //按键自锁标志清零

delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock1==0) //有按键按下,且是第一次被按下

{

if(key_value

{

++delay_cnt1; //延时计数器

If(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=1; //触发1号键

}

}

else if(key_value> cnt_key2_down&&key_value< cnt_key2_up) //K2按下{

++delay_cnt1; //延时计数器

If(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=2; //触发2号键

}

}

else if(key_value> cnt_key3_down&&key_value< cnt_key3_up) //K3按下{

++delay_cnt1; //延时计数器

If(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=3; //触发3号键

}

}

}

void key_service() //按键服务函数

{

switch(key_sec) //按键服务状态切换

{

case 1:// 1号键

// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0

//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 2:// 2号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 3://3号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

}

}

void ad_samping() //AD采样函数,放在main循环里

{

switch(ad_step)

{

case 0:

ADCON0=0x59; //切换直流输入通道AD采样AN3

delay100(); //此处为无厘头一个。如果有两个以上的AD通道进行切换,必须把这//个延时加上,否则

不好使。这个完全是我的实战经验,这样的事情我经常会遇到,这种

//事情完全靠经验,我当年第一次遇到的时候也被折腾了好久才发现。当然这个项目只有

//1个AD通道,所以也许可以不用。

ADIF=0;

CLRWDT();

ADGO=1; //启动AD

ad_step=1; //下一次循环进入下一个步骤,用switch来切换流程,一直是我//的最爱,尤其是通道多的时候,它的优越性更加能发挥得淋漓尽致。

break;

case 1:

if(AD_Flag==1) // AD采样完成

{

AD_Flag=0;

key_value=ADRES; //采集按键电压的AD数值,

ADIF=0;

CLRWDT();

ADGO=1; //启动AD

ad_step=0; //下次循环切换回最前面那个步骤,这种控制方式我最喜欢

}

break;

}

}

//小延时

void delay100()

{

unsigned char k;

for(k=0;k<100;k++);

}

//中断

void interrupt timer1rbint(void)

{

if(ADIF==1&&ADIE==1) //AD转换完成中断

{

TMR1IE=0; //禁止定时中断,避免两个中断相互扯淡

ADIF=0; //清除中断标志

AD_Flag=1; //置AD转换完成标志

TMR1IE=1; //允许定时中断

}

if(TMR1IE==1&&TMR1IF==1) //定时中断

{

ADIE=0;

TMR1IF=0;

TMR1ON=0;

key_scan(); //按键扫描函数

if(voice_time_cnt) //控制蜂鸣器声音的长短

{

beep_dr=1; //蜂鸣器响

--voice_time_cnt; //蜂鸣器响的声音长短的计数延时

}

else

{

beep_dr=0; //蜂鸣器停止

}

TMR1H=0xFF;

TMR1L=0xC8;

TMR1ON=1;

ADIE=1;

}

}

(6)小结:

有两路AD通道进行切换时,必须加一个小延时delay100(),否则会出现无厘头现象。“无厘头现象“是鸿哥发明的一个新词,专门用来表示那些莫名其妙的,用理论不好解释的现象。(未完待续下一节)

第四节:“鸿哥三宝”之74HC165(按键扫描篇)

(1)开场白:

这节将要跟大家介绍一下鸿哥的“三宝”,它们分别是74HC165,74HC595,ULN2003A.之所以它们在我心中的地位那么高,是因为很多工控小项目经常用到它。74HC165使我们从此不再为单片机的输入口不足而烦恼,我们用3根IO口就可以检测100多路的输入信号。74HC595使我们从此不再为单片机的输出口不足而烦恼,我们用4根IO口就可以驱动100多个继电器或者LED。而ULN2003A则大大简化了我们的三极管驱动电路,一个芯片就集成了7个三极管,它有500mA的驱动能力,内部自带续流二极管,用来驱动继电器的时候,二极管也省了。74HC165对静电很敏感,很脆弱,在通电的情况,绝对不要用手摸到他的引脚,我曾经用非绝缘的镊子来短接其输入口,烧坏了很多个,因此对于74HC165我们要懂得怜香惜玉,小心呵护。总之,“鸿哥三宝”实乃电子工程师居家旅行之必备良药。

(2)功能需求:每按一个按键,蜂鸣器就响一次。

(3)硬件原理:

(a)把两个74HC165联级起来,就可以达到用3根IO口来检测16个按键的目的。此电路的本质是并入串出的原理。具体的电路读者只要下载芯片资料一看就明。还是那句话,按键那里记得接20K左右的上拉电阻。

(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。

(4)源码适合的单片机:PIC18f4520,晶振为22.1184MHz

(5)源代码讲解如下:

#include //包含芯片相关头文件

//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr

#define beep_dr LATA2 //蜂鸣器输出

# define hc165_cp_dr LATA0 //74hc165的3根驱动IO之一

# define hc165_pl_dr LATA1 //74hc165的3根驱动IO之一

# define hc165_q7_sr RE0 //74hc165的3根驱动IO之一

//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量

//前缀都用cnt_表示。

#define cnt_delay_cnt1 40 //按键去抖动延时阀值

#define cnt_voice_time 150 //蜂鸣器响的声音长短的延时阀值

//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中

//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。

void key_scan(); //按键扫描函数,放在定时中断里

void key_service(); //按键服务函数,放在main函数循环里

//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名

//后缀都用_step表示。

unsigned char key_step=1; //按键扫描步骤变量,在switch()语句的括号里

//补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名

//后缀都用_lock表示。

unsigned char key_lock1=0; //按键自锁标志

unsigned char key_lock2=0; //按键自锁标志

unsigned char key_lock3=0; //按键自锁标志

unsigned char key_lock4=0; //按键自锁标志

//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量

//后缀都用_cnt表示。

unsigned int delay_cnt1=0; //延时计数器的变量

unsigned int delay_cnt2=0; //延时计数器的变量

unsigned int delay_cnt3=0; //延时计数器的变量

unsigned int delay_cnt4=0; //延时计数器的变量

unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时

//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类

//后缀都用_sec表示。

Unsigned char key_sec=0; //哪个按键被触发

Unsigned int key_status=0; //一个字节8位,此处2个字节,共16位,每一位代表一////个按键的状态

//主程序

main()

{

ADCON0=0x00;

ADCON1=0x0f; //全部为数字信号

ADCON2=0xa1; //右对齐

RBPU=0; //上拉电阻

SSPEN=0; //决定RA5不作为串口

TRISA2=0; //蜂鸣器输出

TRISA0=0; //74hc165的3根驱动IO之一

TRISA1=0; //74hc165的3根驱动IO之一

TRISE0=1; //74hc165的3根驱动IO之一

T1CON=0x24; //定时器中断配置

TMR1H=0xFE;

TMR1L=0xEF;

TMR1IF=0;

TMR1IE=1;

TMR1ON=1;

TMR1IE=1;

//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,

//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可

beep_dr=0; //关蜂鸣器,上电初始化IO

while(1)

{

CLRWDT(); //喂看门狗,大家不用过度关注此行

key_service(); //按键服务

}

}

void key_scan() //按键扫描函数

{

unsigned char j; //中间循环变量

Key_status =0x0000; //每个按键的电平状态,共16个

hc165_pl_dr=0;

asm("nop");

asm("nop");

hc165_pl_dr=1;

asm("nop");

asm("nop");

for(j=0;j<16;j++)

{

hc165_cp_dr=0;

asm("nop");

asm("nop");

key_status=key_status<<1;

if(hc165_q7_sr==1)key_status=key_status+1;

hc165_cp_dr=1;

asm("nop");

asm("nop");

} //以上一小段代码是通过驱动2个74HC165来获取16个按键的电平状态//key_status

//以下代码通过解析每一位电平状态来确定哪个按键被触发

if((key_status &0x0001)==0x0001)

{

key_lock1=0; //按键自锁标志清零

delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock1==0)

{

++ delay_cnt1;

if(delay_cnt1> cnt_delay_cnt1) //延时计数去抖动

{

delay_cnt1=0;

key_lock1=1;

key_sec=1; //触发1号键

}

}

if((key_status &0x0002)==0x0002)

{

key_lock2=0; //按键自锁标志清零

delay_cnt2=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock2==0)

{

++ delay_cnt2;

if(delay_cnt2> cnt_delay_cnt1) //延时计数去抖动

{

delay_cnt2=0;

key_lock2=1;

key_sec=2; //触发2号键

}

}

if((key_status &0x0004)==0x0004)

{

key_lock3=0; //按键自锁标志清零

delay_cnt3=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock3==0)

{

++ delay_cnt3;

if(delay_cnt3> cnt_delay_cnt1) //延时计数去抖动

{

delay_cnt3=0;

key_lock3=1;

key_sec=3; //触发3号键

}

}

if((key_status &0x0008)==0x0008)

{

key_lock4=0; //按键自锁标志清零

delay_cnt4=0; //按键去抖动延时计数器清零,此行非常巧妙

}

else if(key_lock4==0)

{

++ delay_cnt4;

if(delay_cnt4> cnt_delay_cnt1) //延时计数去抖动

{

delay_cnt4=0;

key_lock4=1;

key_sec=4; //触发4号键

}

}

//如果要接16个按键,读者可以继续往下添加类似的代码,本例只触发4个按键作为演示

}

void key_service() //按键服务函数

{

switch(key_sec) //按键服务状态切换

{

case 1:// 1号键

// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0

//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 2:// 2号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

相关主题
相关文档 最新文档