前言
上一节我们已经研究了超声波接收模块并自己设计了一个超声波接收模块,在此基础上又尝试用单片机加反相器构成生成40KHz的超声波发射电路,可是发现采用这种设计的发射电路存在严重的发射功率太低问题,对齐的情况下最多只有10CM。本节主要介绍并制造一种大功率超声波发射装置~
目录
一、浪里淘金,寻找最简超声波功率提高方案
1.1、优化波形发生程序
1.2、尝试各种其他超声模块方案
1.3、用三极管放大信号
1.4、MAX232放大信号方案
二、步步为营,打造高效准确超声测距算法
2.1、接收MCU区分接收头信号并统计时差算法初试
2.2、折衷——单MCU上集成收发模块实现测距
2.3、命中注定——分手的时候到了
三、阶段小结
四、相关链接
一、浪里淘金,寻找最简超声波功率提高方案
1.1、优化波形发生程序
>_<" 上节讲到的利用反相器加单片机生成40KHz的超声波发射装置存在严重的功率问题,然后在上次之后的研究中我发现通过调节定时器的定时,功率会有稍微的提高,但还是比较弱~(因为条件限制,根本买不起示波器这种神器,所有只有酷比的调试代码啦!)下面的代码即改进后的51单片机代码,这次定时器采用的是定时器2,16位重装模式~
1 /*----------------------------------------------- 2 名称:定时器2 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 6 sbit P10=P1^0; 7 sbit P11=P1^1; 8 /*------------------------------------------------ 9 定时器初始化子程序 10 ------------------------------------------------*/ 11 void TIM2Inital(void) 12 { 13 RCAP2H = (65536-12)/256;//晶振11.0592M 12us 16bit 自动重载(最好是用12M但是要实现时间同步,那边需要串口,所以这里就勉强采用11.0952) 14 RCAP2L = (65536-12)%256; 15 ET2=1; //打开定时器中断 16 EA=1; //打开总中断 17 TR2=1; //打开定时器开关 18 } 19 /*------------------------------------------------ 20 主程序 21 ------------------------------------------------*/ 22 main() 23 { 24 P10=0; 25 P11=0; 26 TIM2Inital(); 27 while(1){} 28 } 29 /*------------------------------------------------ 30 定时器中断子程序 31 ------------------------------------------------*/ 32 void TIM2(void) interrupt 5 using 1//定时器2中断 33 { 34 TF2=0; 35 P10=~P10; 36 P11=~P11; 37 }
1.2、尝试各种其他超声模块方案
>_<" 发现上面的方法不能大幅度改变发射功率,于是还是继续网上找资料,于是看上一节最后留下的第二个链接中的文章,找个简单的实验了下。结果在protues中仿真发现怎么也不是想要的效果,另一方面考虑到要提供9V的电压,于是就放弃了在面包板上连接实物实验。然后又在protues里实验了用555做超声波发送模块的方案,结果不知道为什么,protues里似乎不能给555加9V电压(一直报错)。最后大致浏览下这个文档中的方案,发现基本上都需要9V电压,而且模电较多(我这里电子元件不是太多,最坑的是没有示波器!)
1.3、用三极管放大信号
>_<" 一个偶然的发现某同学的毕业设计中的方案:他介绍在脉冲发生电路和脉冲发射电路中加一个三极管来放大信号,觉得这个简单易行,我在面包板上简单的用一个2N3904三极管,照着模拟电路书本上简单放大电路连接好,将上述产生40KHz的单片机脉冲发生电路的引脚和三极管的基极相连,集电极加载一个12V的电压,测试结果发现可以很有效地提高发射功率,但是只成功了一小会,然后再怎么实验都无法再收到超声波了(我怀疑是把三极管弄坏了),所以该方案又失败了~
1.4、MAX232放大信号方案
>_<" 通过上面的各种尝试,我发现上面发射部分设计方案有一个共同的特点:都需要较高的驱动电压。但是我购买的HC-SR04超过声波测距模块却只需要用5V就能发射功率很强的成声波,这点引起了我的思考。于是直接找来HC-SR04的设计图:
通过研究发现:其发射部分采用STC系列单片机作为40KHz的脉冲发生器,然后把13、14两路(他一定是让这两路提供反向电平作为输出)链接到 MAX232的两个输入端!一看到MAX232瞬间就明白了:MAX232是经常用在串口通信中用于将串口信号放大来传播更远距离的芯片,他这里采用 MAX232这个特点用于将信号放大,然后在输出端直接驱动发射头!非常机智!于是我利用手头上的串口转TTL模块做一个简单的实验,结果令人振奋,果然能够对信号进行很强的放大:
于是一鼓作气,重新设计一个信号更强劲的方案,并把电路焊接成发射模块:这次采用MAX232的2个输入和输出通道,将两个方波同时放大,将产生更加强劲的效果!
二、步步为营,打造高效准确超声测距算法
>_<" 到上面为止我们已经完美地把超声波发射与接收模块都做好了,那么现在就要研究下如何利用他们进行测距了~(PS:嘻嘻这里俺可不是简单的用一个发送模块一个接收模块进行直线空间上的测距,这种东西早都比较成熟了,网上一搜一把,而且非常便宜!我要做的是利用2个接收模块及一个接收模块在二维平面上对物体进行定位!)
2.1、接收MCU区分接收头信号并统计时差算法初试
>_<" 还记得我们上节用到的最最简单的信号检测算法吗?该程序是放到接收部分MCU中运行的,因为超声波接收部分一旦接收到超声波就会产生一个1-0-1信号,所以我们上一节只是简单的将接收模块输出链接到单片机的一个引脚,在单片机程序中对该引脚电平进行轮询输出。但是这里我们用了2个接收头,如果处理不当,会很难分辨是哪一个接收头产生的信号,如果采用顺序输出又不满足两个信号到达的先后顺序不同且会变的事实,综上,这里先初步用一个ok作为标记:为0表示没收到一个信号;为1表示只收到1好接收模块的信号;为2表示只收到2号接收模块的信号;为3表示两个都收到了,具体如下:[图中连续ab之间的+号的个数表示两个信号之间的时间差]
1 /*------------------------------------------- 2 简单的串口通信{接收} 3 -------------------------------------------*/ 4 #include<reg51.h> 5 6 #define uint unsigned int 7 #define uchar unsigned char 8 9 10 sbit IN1 = P1^0; 11 sbit IN2 = P1^1; 12 /*-------------------------------------------- 13 USAR初始函数 14 ---------------------------------------------*/ 15 void USRT_init() 16 { 17 TMOD=0x20; //设置T1定时器工作方式2 18 TH1=0xfd; //T1定时器装初值 19 TL1=0xfd; 20 TR1=1; //启动T1定时器 21 SM0=0; //设定串口工作方式 22 SM1=1; 23 EA=1; //开总中断 24 } 25 /*-------------------------------------------- 26 主函数 27 ---------------------------------------------*/ 28 void main() 29 { 30 int i=0,ok=0; 31 USRT_init(); 32 while(1) 33 { 34 SBUF='+'; 35 if(IN1==0 && ok!=1 && ok!=3){ 36 SBUF='a'; 37 if(ok==2)ok=3; 38 else ok=1; 39 } 40 if(IN2==0 && ok!=2 && ok!=3){ 41 SBUF='b'; 42 if(ok==1)ok=3; 43 else ok=2; 44 } 45 if(ok==3){ 46 i++; 47 if(i==100){ 48 ok=0; 49 i=0; 50 } 51 } 52 while(!TI); //每次等待发送完毕,再执行下一条 53 TI=0; //手动清0 54 } 55 }
2.2、折衷——单MCU上集成收发模块实现测距
>_<" 为了体现咱们是步步为营的,所以俺刚开始并没有直接去挑战发送和接收模块分开或者直接两个接收模块去测距,而是先尝试一下在一个MCU上连接一个发射模块和一个接收模块,自制一个简单的超声波测距仪(市场上卖的那种直线型的)。这个程序不难理解,但是麻雀虽小,五脏俱全,这里用到了51单片机的几乎所有中断:1)负责接收模块监听的外部中断2)负责计时的T0计数器3)负责串口的T1定时器4)负责产生方波的T2定时器~然后测距的思路很简单:首先发送模块在短时间内发送100周期的40KHz超声波,然后计时器开始计数,等到接收模块输出引脚产生1-0-1下降沿触发外部中断时停止计数,然后根据转换公式将超声波传播计数转换为距离并把数据通过串口发送给上位机。
1 /*----------------------------------------------- 2 名称:定时器2 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 #include<intrins.h> 6 7 sbit P10=P1^0; 8 sbit P11=P1^1; 9 10 unsigned char times;//一次发射方波数 11 char bwei,shwei,gwei;//数据 12 13 /*------------------------------------------------ 14 定时器初始化子程序[定时器2,16位自动装值,用于产生40KHz方波] 15 ------------------------------------------------*/ 16 void TIM2Inital(void) 17 { 18 RCAP2H = (65536-12)/256;//晶振11.0592M 12us 16bit 自动重载(最好是用12M但是要实现时间同步,那边需要串口,所以这里就勉强采用11.0952) 19 RCAP2L = (65536-12)%256; 20 ET2=1; //打开定时器中断 21 EA=1; //打开总中断 22 TR2=1; //打开定时器开关 23 } 24 /*-------------------------------------------- 25 USAR初始函数及发送一个字符 26 ---------------------------------------------*/ 27 void USRT_init() 28 { 29 TMOD|=0x20; //设置T1定时器工作方式2 30 TH1=0xfd; //T1定时器装初值 31 TL1=0xfd; 32 TR1=1; //启动T1定时器 33 SM0=0; //设定串口工作方式 34 SM1=1; 35 EA=1; //开总中断 36 } 37 void send(char a) 38 { 39 SBUF=a; 40 while(!TI); //每次等待发送完毕,再执行下一条 41 TI=0; //手动清0 42 } 43 /*------------------------------------------------ 44 定时器初始化子程序[用于计算超声波发送到收到的时间间隔] 45 外部中断P32用于接收超声波接收低电平 46 ------------------------------------------------*/ 47 void InterruptInit(void) 48 { 49 TMOD|=0x01;//T0计数,方式1 50 TH0=0;//计数初值 51 TL0=0; 52 IT0=1;//INT0负脉冲触发 53 EA=1;//开总中断 54 EX0=1;//开外部中断INT0 55 } 56 /*------------------------------------------------ 57 主程序 58 ------------------------------------------------*/ 59 main() 60 { 61 USRT_init();//初始化串口 62 InterruptInit();//初始化 63 while(1) 64 { 65 P10=P11=0; 66 TR0=1;//T0开始计数 67 times=0; 68 TIM2Inital(); 69 while(times<200); 70 ET2=0;//关闭定时器中断 71 } 72 } 73 /*------------------------------------------------ 74 INTO中断服务程序 75 ------------------------------------------------*/ 76 void intersvro(void) interrupt 0 using 1 77 { 78 unsigned long COUNT; 79 unsigned long num; 80 TR0=0; //停止计数 81 COUNT=TH0*256+TL0; 82 num=(344*COUNT)/10000; 83 if(num==0)goto A; 84 bwei=(char)('0'+num%1000/100);//取百位 85 shwei=(char)('0'+num%100/10);//取十位 86 gwei=(char)('0'+num%10);//取个位 87 send(bwei); 88 send(shwei); 89 send(gwei); 90 send(0x0d); 91 send(0x0a); 92 A: TH0=0; 93 TL0=0; 94 times=1000; 95 } 96 /*------------------------------------------------ 97 定时器中断子程序 98 ------------------------------------------------*/ 99 void TIM2(void) interrupt 5 using 1//定时器2中断 100 { 101 TF2=0; 102 P10=~P10; 103 P11=~P11; 104 times++; 105 }
上面的代码将数据发送给上位机的过程放大中断中去处理不是太好,于是就将串口数据发送改到main函数中了,优化后的代码如下:
1 /*----------------------------------------------- 2 名称:定时器2 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 #include<intrins.h> 6 7 sbit P10=P1^0; 8 sbit P11=P1^1; 9 10 unsigned char times,flag;//一次发射方波数 11 char bwei,shwei,gwei;//数据 12 unsigned long num1,num2; 13 14 /*------------------------------------------------ 15 定时器初始化子程序[定时器2,16位自动装值,用于产生40KHz方波] 16 ------------------------------------------------*/ 17 void TIM2Inital(void) 18 { 19 RCAP2H = (65536-12)/256;//晶振11.0592M 12us 16bit 自动重载(最好是用12M但是要实现时间同步,那边需要串口,所以这里就勉强采用11.0952) 20 RCAP2L = (65536-12)%256; 21 ET2=1; //打开定时器中断 22 EA=1; //打开总中断 23 TR2=1; //打开定时器开关 24 } 25 /*-------------------------------------------- 26 USAR初始函数及发送一个字符 27 ---------------------------------------------*/ 28 void USRT_init() 29 { 30 TMOD|=0x20; //设置T1定时器工作方式2 31 TH1=0xfd; //T1定时器装初值 32 TL1=0xfd; 33 TR1=1; //启动T1定时器 34 SM0=0; //设定串口工作方式 35 SM1=1; 36 EA=1; //开总中断 37 } 38 void send(char a) 39 { 40 SBUF=a; 41 while(!TI); //每次等待发送完毕,再执行下一条 42 TI=0; //手动清0 43 } 44 /*------------------------------------------------ 45 定时器初始化子程序[用于计算超声波发送到收到的时间间隔] 46 外部中断P32用于接收超声波接收低电平 47 ------------------------------------------------*/ 48 void InterruptInit(void) 49 { 50 TMOD|=0x01;//T0计数,方式1 51 TH0=0;//计数初值 52 TL0=0; 53 IT0=1;//INT0负脉冲触发 54 EA=1;//开总中断 55 EX0=1;//开外部中断INT0 56 } 57 /*------------------------------------------------ 58 主程序 59 ------------------------------------------------*/ 60 main() 61 { 62 USRT_init();//初始化串口 63 InterruptInit();//初始化 64 while(1) 65 { 66 P10=P11=0; 67 flag=0; 68 TR0=1;//T0开始计数 69 times=0; 70 TIM2Inital(); 71 while(times<200); 72 ET2=0;//关闭定时器中断 73 if(flag){ 74 bwei=(char)('0'+num1%1000/100);//取百位 75 shwei=(char)('0'+num1%100/10);//取十位 76 gwei=(char)('0'+num1%10);//取个位 77 send(bwei); 78 send(shwei); 79 send(gwei); 80 send(0x0d); 81 send(0x0a); 82 } 83 EX0=1; 84 } 85 } 86 /*------------------------------------------------ 87 INTO中断服务程序 88 ------------------------------------------------*/ 89 void intersvro(void) interrupt 0 using 1 90 { 91 unsigned long COUNT; 92 TR0=0; //停止计数 93 COUNT=TH0*256+TL0; 94 num1=(344*COUNT)/10000; 95 if(num1==0)return; 96 flag=1; 97 TH0=0; 98 TL0=0; 99 times=1000; 100 EX0=0; 101 } 102 /*------------------------------------------------ 103 定时器中断子程序 104 ------------------------------------------------*/ 105 void TIM2(void) interrupt 5 using 1//定时器2中断 106 { 107 TF2=0; 108 P10=~P10; 109 P11=~P11; 110 times++; 111 }
但是仅仅有一个接收模块肯定不是我们的最终目标,于是再向前迈一步,这次加入一个接收模块,于是把单片机剩下的那个外部中断也给用上了,程序大致和上面的很像~但是这里出现个问题:因为定时器就一个,如果用这一个定时器去计算两个接收模块接收时间的话,看似可以(你可能会想到用一个秒表给多人计时),但是超声波不是人!一方面,当其中一个接收模块触发中断时并进行相应的处理会影响计时器;另一方面,如果强制想实现这个计时过程要添加很多标志和判断处理,这样很不明智!下面是初步尝试时的程序,他只能测出首先接受到超声波信号的接收头的数据,另一个会被自动放弃掉~
1 /*----------------------------------------------- 2 名称:定时器2 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 #include<intrins.h> 6 7 sbit P10=P1^0; 8 sbit P11=P1^1; 9 10 unsigned char times,flag;//一次发射方波数 11 char bwei,shwei,gwei;//数据 12 unsigned long num1,num2; 13 14 /*------------------------------------------------ 15 定时器初始化子程序[定时器2,16位自动装值,用于产生40KHz方波] 16 ------------------------------------------------*/ 17 void TIM2Inital(void) 18 { 19 RCAP2H = (65536-12)/256;//晶振11.0592M 12us 16bit 自动重载(最好是用12M但是要实现时间同步,那边需要串口,所以这里就勉强采用11.0952) 20 RCAP2L = (65536-12)%256; 21 ET2=1; //打开定时器中断 22 EA=1; //打开总中断 23 TR2=1; //打开定时器开关 24 } 25 /*-------------------------------------------- 26 USAR初始函数及发送一个字符 27 ---------------------------------------------*/ 28 void USRT_init() 29 { 30 TMOD|=0x20; //设置T1定时器工作方式2 31 TH1=0xfd; //T1定时器装初值 32 TL1=0xfd; 33 TR1=1; //启动T1定时器 34 SM0=0; //设定串口工作方式 35 SM1=1; 36 EA=1; //开总中断 37 } 38 void send(char a) 39 { 40 SBUF=a; 41 while(!TI); //每次等待发送完毕,再执行下一条 42 TI=0; //手动清0 43 } 44 /*------------------------------------------------ 45 定时器初始化子程序[用于计算超声波发送到收到的时间间隔] 46 外部中断P32用于接收超声波接收低电平 47 ------------------------------------------------*/ 48 void InterruptInit(void) 49 { 50 TMOD|=0x01;//T0计数,方式1 51 TH0=0;//计数初值 52 TL0=0; 53 IT0=1;//INT0负脉冲触发 54 IT1=1; 55 EA=1;//开总中断 56 EX0=1;//开外部中断INT0 57 EX1=1; 58 } 59 /*------------------------------------------------ 60 主程序 61 ------------------------------------------------*/ 62 main() 63 { 64 USRT_init();//初始化串口 65 InterruptInit();//初始化 66 while(1) 67 { 68 P10=P11=0; 69 flag=0; 70 TR0=1;//T0开始计数 71 times=0; 72 TIM2Inital(); 73 while(times<200); 74 ET2=0;//关闭定时器中断 75 if(flag==1){ 76 num1-=5;//发现所测结果比真实大5左右 77 bwei=(char)('0'+num1%1000/100);//取百位 78 shwei=(char)('0'+num1%100/10);//取十位 79 gwei=(char)('0'+num1%10);//取个位 80 send('x'); 81 send(':'); 82 send(bwei); 83 send(shwei); 84 send(gwei); 85 send(0x0d); 86 send(0x0a); 87 }else if(flag==2){ 88 num2-=5;//发现所测结果比真实大5左右 89 bwei=(char)('0'+num2%1000/100);//取百位 90 shwei=(char)('0'+num2%100/10);//取十位 91 gwei=(char)('0'+num2%10);//取个位 92 send('y'); 93 send(':'); 94 send(bwei); 95 send(shwei); 96 send(gwei); 97 send(0x0d); 98 send(0x0a); 99 } 100 EX0=1; 101 EX1=1; 102 } 103 } 104 /*------------------------------------------------ 105 INTO中断服务程序 106 ------------------------------------------------*/ 107 void intersvro0(void) interrupt 0 108 { 109 unsigned long COUNT; 110 TR0=0; //停止计数 111 COUNT=TH0*256+TL0; 112 num1=(344*COUNT)/10000; 113 if(num1==0)return; 114 flag=1; 115 TH0=0; 116 TL0=0; 117 times=1000; 118 EX0=0; 119 } 120 /*------------------------------------------------ 121 INT1中断服务程序 122 ------------------------------------------------*/ 123 void intersvro1(void) interrupt 2 124 { 125 unsigned long COUNT; 126 TR0=0; //停止计数 127 COUNT=TH0*256+TL0; 128 num2=(344*COUNT)/10000; 129 if(num2==0)return; 130 flag=2; 131 TH0=0; 132 TL0=0; 133 times=1000; 134 EX1=0; 135 } 136 /*------------------------------------------------ 137 定时器中断子程序 138 ------------------------------------------------*/ 139 void TIM2(void) interrupt 5 using 1//定时器2中断 140 { 141 TF2=0; 142 P10=~P10; 143 P11=~P11; 144 times++; 145 }
本来我以为那种掐表模式的多接收模块计时模型肯定能实现,于是浪费了很长时间,把代码改的很乱,最后还是不能完美的完成目标~就在我吃饭的路上突然受分时操作系统的影响产生了一个新的灵感:由于测量频率很快,我可以把一个测量周期分为两部分,一部分用于只用接收模块1进行测距,另一部分只用接收模块2进行测量,这样一个测量周期就能完美的测量出两个接收模块的测距数据!代码如下:
1 /*----------------------------------------------- 2 超声波接收一个接P32一个接P33中断INT0和INT1 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 #include<intrins.h> 6 7 sbit P10=P1^0; 8 sbit P11=P1^1; 9 10 unsigned char times,flag;//一次发射方波数 11 char bwei,shwei,gwei;//数据 12 unsigned long num1,num2; 13 14 /*------------------------------------------------ 15 定时器初始化子程序[定时器2,16位自动装值,用于产生40KHz方波] 16 ------------------------------------------------*/ 17 void TIM2Inital(void) 18 { 19 RCAP2H = (65536-12)/256;//晶振11.0592M 12us 16bit 自动重载(最好是用12M但是要实现时间同步,那边需要串口,所以这里就勉强采用11.0952) 20 RCAP2L = (65536-12)%256; 21 ET2=1; //打开定时器中断 22 EA=1; //打开总中断 23 TR2=1; //打开定时器开关 24 } 25 /*-------------------------------------------- 26 USAR初始函数及发送一个字符 27 ---------------------------------------------*/ 28 void USRT_init() 29 { 30 TMOD|=0x20; //设置T1定时器工作方式2 31 TH1=0xfd; //T1定时器装初值 32 TL1=0xfd; 33 TR1=1; //启动T1定时器 34 SM0=0; //设定串口工作方式 35 SM1=1; 36 EA=1; //开总中断 37 } 38 void send(char a) 39 { 40 SBUF=a; 41 while(!TI); //每次等待发送完毕,再执行下一条 42 TI=0; //手动清0 43 } 44 /*------------------------------------------------ 45 定时器初始化子程序[用于计算超声波发送到收到的时间间隔] 46 外部中断P32用于接收超声波接收低电平 47 ------------------------------------------------*/ 48 void InterruptInit(void) 49 { 50 TMOD|=0x01;//T0计数,方式1 51 TH0=0;//计数初值 52 TL0=0; 53 IT0=1;//INT0负脉冲触发 54 IT1=1; 55 EA=1;//开总中断 56 // EX0=1;//开外部中断INT0 57 // EX1=1; 58 } 59 /*------------------------------------------------ 60 主程序 61 ------------------------------------------------*/ 62 main() 63 { 64 bit ok=0; 65 USRT_init();//初始化串口 66 InterruptInit();//初始化 67 while(1) 68 { 69 if(ok==0){ 70 EX0=1; 71 72 P10=P11=0; 73 flag=0; 74 TR0=1;//T0开始计数 75 times=0; 76 TIM2Inital(); 77 while(times<200); 78 ET2=0;//关闭定时器中断 79 80 if(flag==1){ 81 num1-=5;//发现所测结果比真实大5左右 82 bwei=(char)('0'+num1%1000/100);//取百位 83 shwei=(char)('0'+num1%100/10);//取十位 84 gwei=(char)('0'+num1%10);//取个位 85 send('x'); 86 send(':'); 87 send(bwei); 88 send(shwei); 89 send(gwei); 90 send(0x0d); 91 send(0x0a); 92 ok=1; 93 } 94 }else{ 95 EX1=1; 96 97 P10=P11=0; 98 flag=0; 99 TR0=1;//T0开始计数 100 times=0; 101 TIM2Inital(); 102 while(times<200); 103 ET2=0;//关闭定时器中断 104 105 if(flag==2){ 106 num2-=5;//发现所测结果比真实大5左右 107 bwei=(char)('0'+num2%1000/100);//取百位 108 shwei=(char)('0'+num2%100/10);//取十位 109 gwei=(char)('0'+num2%10);//取个位 110 send('y'); 111 send(':'); 112 send(bwei); 113 send(shwei); 114 send(gwei); 115 send(0x0d); 116 send(0x0a); 117 ok=0; 118 } 119 } 120 } 121 } 122 /*------------------------------------------------ 123 INTO中断服务程序 124 ------------------------------------------------*/ 125 void intersvro0(void) interrupt 0 126 { 127 unsigned long COUNT; 128 TR0=0; //停止计数 129 COUNT=TH0*256+TL0; 130 num1=(344*COUNT)/10000; 131 if(num1==0)return; 132 flag=1; 133 TH0=0; 134 TL0=0; 135 times=1000; 136 EX0=0; 137 } 138 /*------------------------------------------------ 139 INT1中断服务程序 140 ------------------------------------------------*/ 141 void intersvro1(void) interrupt 2 142 { 143 unsigned long COUNT; 144 TR0=0; //停止计数 145 COUNT=TH0*256+TL0; 146 num2=(344*COUNT)/10000; 147 if(num2==0)return; 148 flag=2; 149 TH0=0; 150 TL0=0; 151 times=1000; 152 EX1=0; 153 } 154 /*------------------------------------------------ 155 定时器中断子程序 156 ------------------------------------------------*/ 157 void TIM2(void) interrupt 5 using 1//定时器2中断 158 { 159 TF2=0; 160 P10=~P10; 161 P11=~P11; 162 times++; 163 }
2.3、命中注定——分手的时候到了
>_<" 上面我们已经实现了在一个MCU上实现了一个发射模块两个接收模块分别测距,但是将发射和接收放在同一个模块上是很不理想的一种折衷,那么现在是时候来一个高超的手术了!
>_<" 通过分析上面集成在一个MCU上的代码可以知道:发送部分仅占用40KHz发送函数用于适时的发送一段连续的超声波;接收部分负责计时、等待接收模块的1-0-1中断、计算距离、将数据发送给上位机等功能。其中有一个核心而隐蔽的问题,即:时间同步问题!当在一个MCU上时,我们只要简单的启动计时器同时启动超声波发送,而在两个MCU上实现这个并不是件容易的事!最终我想到了用一根线来同步时间,即:当接收模块准备好时,把该线上的电平置1-0产生下降沿(P20引脚),然后把该线的另一端连接将发送模块的外部中断0的引脚(P32引脚),采用中断方式监听起跑命令。当发送模块收到起跑命令时便发送一串超声波,同时接收模块开始计数,剩下的部分就和在同一个MCU上的原理很相似了~具体请参考代码(注意接收部分在发送起跑命令后的延时技巧!这是一种可以被中断剪短的延时,用来等待超声波接收模块接收有效的数据)
1 /*----------------------------------------------- 2 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 #include<intrins.h> 6 7 sbit P10=P1^0; 8 sbit P11=P1^1; 9 10 unsigned char times,flag;//一次发射方波数 11 12 /*------------------------------------------------ 13 定时器初始化子程序[定时器2,16位自动装值,用于产生40KHz方波] 14 ------------------------------------------------*/ 15 void TIM2Inital(void) 16 { 17 RCAP2H = (65536-12)/256;//晶振11.0592M 12us 16bit 自动重载(最好是用12M但是要实现时间同步,那边需要串口,所以这里就勉强采用11.0952) 18 RCAP2L = (65536-12)%256; 19 ET2=1; //打开定时器中断 20 EA=1; //打开总中断 21 TR2=1; //打开定时器开关 22 } 23 /*------------------------------------------------ 24 用于接收接收端发送过来的发送超声波的命令 25 ------------------------------------------------*/ 26 void InterruptInit(void) 27 { 28 TMOD|=0x01;//T0计数,方式1 29 TH0=0;//计数初值 30 TL0=0; 31 IT0=1;//INT0负脉冲触发 32 EA=1;//开总中断 33 EX0=1;//开外部中断INT0 34 } 35 /*------------------------------------------------ 36 主程序 37 ------------------------------------------------*/ 38 main() 39 { 40 InterruptInit();//初始化 41 while(1) 42 { 43 flag=0; 44 while(!flag); 45 EX0=0;//关闭中断 46 times=0; 47 TIM2Inital(); 48 while(times<80); 49 ET2=0;//关闭定时器中断 50 EX0=1;//开中断 51 } 52 } 53 /*------------------------------------------------ 54 INTO中断服务程序 55 ------------------------------------------------*/ 56 void intersvro0(void) interrupt 0 57 { 58 flag=1; 59 } 60 /*------------------------------------------------ 61 定时器中断子程序 62 ------------------------------------------------*/ 63 void TIM2(void) interrupt 5 using 1//定时器2中断 64 { 65 TF2=0; 66 P10=~P10; 67 P11=~P11; 68 times++; 69 }
1 /*----------------------------------------------- 2 超声波接收一个接P32一个接P33中断INT0和INT1 3 ------------------------------------------------*/ 4 #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 5 #include<intrins.h> 6 7 8 sbit P20=P2^0;//通知发送的发送超声波(由高电平变低电平) 9 unsigned char flag; 10 char bwei,shwei,gwei;//数据 11 unsigned long num1,num2,times; 12 13 /*-------------------------------------------- 14 延时函数 15 ---------------------------------------------*/ 16 void delays() 17 { 18 times=0; 19 while(times<1000)times++; 20 } 21 22 /*-------------------------------------------- 23 USAR初始函数及发送一个字符 24 ---------------------------------------------*/ 25 void USRT_init() 26 { 27 TMOD|=0x20; //设置T1定时器工作方式2 28 TH1=0xfd; //T1定时器装初值 29 TL1=0xfd; 30 TR1=1; //启动T1定时器 31 SM0=0; //设定串口工作方式 32 SM1=1; 33 EA=1; //开总中断 34 } 35 void send(char a) 36 { 37 SBUF=a; 38 while(!TI); //每次等待发送完毕,再执行下一条 39 TI=0; //手动清0 40 } 41 /*------------------------------------------------ 42 定时器初始化子程序[用于计算超声波发送到收到的时间间隔] 43 外部中断P32用于接收超声波接收低电平 44 ------------------------------------------------*/ 45 void InterruptInit(void) 46 { 47 TMOD|=0x01;//T0计数,方式1 48 TH0=0;//计数初值 49 TL0=0; 50 IT0=1;//INT0负脉冲触发 51 IT1=1; 52 EA=1;//开总中断 53 // EX0=1;//开外部中断INT0 54 // EX1=1; 55 } 56 /*------------------------------------------------ 57 主程序 58 ------------------------------------------------*/ 59 main() 60 { 61 bit ok=0; 62 USRT_init();//初始化串口 63 InterruptInit();//初始化 64 while(1) 65 { 66 P20=1; 67 if(ok==0){ 68 EX0=1;//开外部INT0中断 69 flag=0; 70 P20=0;//P20由高变低产生一个下降沿来触发发送模块进行发送超声波 71 TR0=1;//T0开始计数 72 delays(); 73 if(flag==1){ 74 num1-=5;//发现所测结果比真实大5左右 75 bwei=(char)('0'+num1%1000/100);//取百位 76 shwei=(char)('0'+num1%100/10);//取十位 77 gwei=(char)('0'+num1%10);//取个位 78 send('x'); 79 send(':'); 80 send(bwei); 81 send(shwei); 82 send(gwei); 83 send(0x0d); 84 send(0x0a); 85 ok=1; 86 } 87 }else{ 88 EX1=1;//开外部中断INT1 89 flag=0; 90 P20=0;//P20由高变低产生一个下降沿来触发发送模块进行发送超声波 91 TR0=1;//T0开始计数 92 delays(); 93 if(flag==2){ 94 num2-=5;//发现所测结果比真实大5左右 95 bwei=(char)('0'+num2%1000/100);//取百位 96 shwei=(char)('0'+num2%100/10);//取十位 97 gwei=(char)('0'+num2%10);//取个位 98 send('y'); 99 send(':'); 100 send(bwei); 101 send(shwei); 102 send(gwei); 103 send(0x0d); 104 send(0x0a); 105 ok=0; 106 } 107 } 108 } 109 } 110 /*------------------------------------------------ 111 INTO中断服务程序 112 ------------------------------------------------*/ 113 void intersvro0(void) interrupt 0 114 { 115 unsigned long COUNT; 116 TR0=0; //停止计数 117 COUNT=TH0*256+TL0; 118 num1=(344*COUNT)/10000; 119 if(num1==0)return; 120 flag=1; 121 TH0=0; 122 TL0=0; 123 times=10000; 124 EX0=0; 125 } 126 /*------------------------------------------------ 127 INT1中断服务程序 128 ------------------------------------------------*/ 129 void intersvro1(void) interrupt 2 130 { 131 unsigned long COUNT; 132 TR0=0; //停止计数 133 COUNT=TH0*256+TL0; 134 num2=(344*COUNT)/10000; 135 if(num2==0)return; 136 flag=2; 137 TH0=0; 138 TL0=0; 139 times=10000; 140 EX1=0; 141 }
三、阶段小结
>_<" 经过这三个阶段,我们已经从分析需求->调研市场->研究资料->购买原料->动手实践->模数结合->软硬兼修->调试修改->优化....最终完成了我们的超声波测距的硬件模块(鼓掌)!然后下一阶段将进入PC上基于C#的测距客户端软件的开发,更多有趣小制作敬请关注~
相关链接
博主主页(希望在其他地方看到该文的朋友点击这里):http://www.cnblogs.com/zjutlitao/
[自娱自乐] 1、超声波测距模块DIY笔记(一)链接:http://www.cnblogs.com/zjutlitao/p/4014855.html
[自娱自乐] 2、超声波测距模块DIY笔记(二)链接:http://www.cnblogs.com/zjutlitao/p/4029937.html
超声波发射接收电路(1.2中提到的资料):文库连接 下载好的文档链接
用三极管放大信号的方案(1.3中提到的资料):http://wenku.baidu.com/view/1637c60676c66137ee0619c8.html