前言
上一節我們已經研究了超聲波接收模塊並自己設計了一個超聲波接收模塊,在此基礎上又嘗試用單片機加反相器構成生成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 } code
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 } View Code
上面的代碼將數據發送給上位機的過程放大中斷中去處理不是太好,於是就將串口數據發送改到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 } View Code但是僅僅有一個接收模塊肯定不是我們的最終目標,於是再向前邁一步,這次加入一個接收模塊,於是把單片機剩下的那個外部中斷也給用上了,程序大致和上面的很像~但是這裡出現個問題:因為定時器就一個,如果用這一個定時器去計算兩個接收模塊接收時間的話,看似可以(你可能會想到用一個秒表給多人計時),但是超聲波不是人!一方面,當其中一個接收模塊觸發中斷時並進行相應的處理會影響計時器;另一方面,如果強制想實現這個計時過程要添加很多標志和判斷處理,這樣很不明智!下面是初步嘗試時的程序,他只能測出首先接受到超聲波信號的接收頭的數據,另一個會被自動放棄掉~
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 } View Code本來我以為那種掐表模式的多接收模塊計時模型肯定能實現,於是浪費了很長時間,把代碼改的很亂,最後還是不能完美的完成目標~就在我吃飯的路上突然受分時操作系統的影響產生了一個新的靈感:由於測量頻率很快,我可以把一個測量周期分為兩部分,一部分用於只用接收模塊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
單片機的超聲波測距,至少要三個超聲波模塊,前、左、右各個方向同時檢測,得出三個方向的最小距離並報警,這有可能造成相互干擾,要分時進行,一個一上順序測量。
可以,但比較難。
3個模塊需要工作在不同的頻率上(無論是發射還是接收),最好用DSP進行FFT處理,才能准確識別。
或者采用樓上兩位朋友的建議,讓3個模塊依次工作,由於輪詢的頻率可以很快,這樣也可以看似‘同時工作’。
崔冬
[權威專家]