1. 程式人生 > >STM32與HC-SR04超聲波測距

STM32與HC-SR04超聲波測距

首先,先來看一下這個模組的基本功能和原理。

HC-SR04超聲波測距模組可提供2cm-400cm的非接觸式距離感測功能,測距精度可達高到3mm;模組包括超聲波發射器、接收器與控制電路。像智慧小車的測距以及轉向,或是一些專案中,常常會用到。智慧小車測距可以及時發現前方的障礙物,使智慧小車可以及時轉向,避開障礙物。

注意是5v輸入,但是我用stm32 的3.3v輸入也是沒有問題的。

二.工作原理

      1.給超聲波模組接入電源和地。
      2.給脈衝觸發引腳(trig)輸入一個長為20us的高電平方波

      3.輸入方波後,模組會自動發射8個40KHz的聲波,與此同時回波引腳(echo)端的電平會由0變為1;(此時應該啟動定時器計時)
      4.當超聲波返回被模組接收到時,回波引 腳端的電平會由1變為0;(此時應該停止定時器計數),定時器記下的這個時間即為超聲波由發射到返回的總時長。
      5.根據聲音在空氣中的速度為344米/秒,即可計算出所測的距離。

      要學習和應用感測器,學會看懂感測器的時序圖是很關鍵的,所以我們來看一下HC-SR04的時序觸發圖。

    

    我們來分析一下這個時序圖,先由觸發訊號啟動HC-RS04測距模組,也就是說,主機要先發送至少10us的高電平,觸發HC-RS04,模組內部發出訊號是感測器自動迴應的,我們不用去管它。輸出迴響訊號是我們需要關注的。訊號輸出的高電平就是超聲波發出到重新返回接收所用的時間。用定時器,可以把這段時間記錄下來,算出距離,別忘了結果要除於2,因為總時間是傳送和接收的時間總和。

下面是親測可用的驅動程式。

晶片型號為stm32f103zet6,超聲波測距後通過串列埠列印到電腦上面。

驅動和測距;

//超聲波測距

#include "hcsr04.h"
 
#define HCSR04_PORT     GPIOB
#define HCSR04_CLK      RCC_APB2Periph_GPIOB
#define HCSR04_TRIG     GPIO_Pin_5
#define HCSR04_ECHO     GPIO_Pin_6

#define TRIG_Send  PBout(5) 
#define ECHO_Reci  PBin(6)

u16 msHcCount = 0;//ms計數

void Hcsr04Init()
{  
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;     //生成用於定時器設定的結構體
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(HCSR04_CLK, ENABLE);
     
        //IO初始化
    GPIO_InitStructure.GPIO_Pin =HCSR04_TRIG;       //傳送電平引腳
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推輓輸出
    GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
    GPIO_ResetBits(HCSR04_PORT,HCSR04_TRIG);
     
    GPIO_InitStructure.GPIO_Pin =   HCSR04_ECHO;     //返回電平引腳
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
    GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);  
		GPIO_ResetBits(HCSR04_PORT,HCSR04_ECHO);	
	 
			//定時器初始化 使用基本定時器TIM6
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);   //使能對應RCC時鐘
		//配置定時器基礎結構體
		TIM_DeInit(TIM2);
		TIM_TimeBaseStructure.TIM_Period = (1000-1); //設定在下一個更新事件裝入活動的自動重灌載暫存器週期的值         計數到1000為1ms
		TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //設定用來作為TIMx時鐘頻率除數的預分頻值  1M的計數頻率 1US計數
		TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;//不分頻
		TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上計數模式
		TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的引數初始化TIMx的時間基數單位		 
		
		TIM_ClearFlag(TIM6, TIM_FLAG_Update);   //清除更新中斷,免得一開啟中斷立即產生中斷
		TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);    //開啟定時器更新中斷
		hcsr04_NVIC();
    TIM_Cmd(TIM6,DISABLE);     
}


//tips:static函式的作用域僅限於定義它的原始檔內,所以不需要在標頭檔案裡宣告
static void OpenTimerForHc()        //開啟定時器
{
        TIM_SetCounter(TIM6,0);//清除計數
        msHcCount = 0;
        TIM_Cmd(TIM6, ENABLE);  //使能TIMx外設
}
 
static void CloseTimerForHc()        //關閉定時器
{
        TIM_Cmd(TIM6, DISABLE);  //使能TIMx外設
}
 
 
 //NVIC配置
void hcsr04_NVIC()
{
			NVIC_InitTypeDef NVIC_InitStructure;
			NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
			NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn;             //選擇串列埠1中斷
			NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //搶佔式中斷優先順序設定為1
			NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         //響應式中斷優先順序設定為1
			NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //使能中斷
			NVIC_Init(&NVIC_InitStructure);
}


//定時器6中斷服務程式
void TIM6_IRQHandler(void)   //TIM3中斷
{
        if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)  //檢查TIM3更新中斷髮生與否
        {
                TIM_ClearITPendingBit(TIM6, TIM_IT_Update  );  //清除TIMx更新中斷標誌 
                msHcCount++;
        }
}
 

//獲取定時器時間
u32 GetEchoTimer(void)
{
        u32 t = 0;
        t = msHcCount*1000;//得到MS
        t += TIM_GetCounter(TIM6);//得到US
	      TIM6->CNT = 0;  //將TIM2計數暫存器的計數值清零
				Delay_Ms(50);
        return t;
}
 

//一次獲取超聲波測距資料 兩次測距之間需要相隔一段時間,隔斷迴響訊號
//為了消除餘震的影響,取五次資料的平均值進行加權濾波。
float Hcsr04GetLength(void )
{
		u32 t = 0;
		int i = 0;
		float lengthTemp = 0;
		float sum = 0;
		while(i!=5)
		{
		TRIG_Send = 1;      //傳送口高電平輸出
		Delay_Us(20);
		TRIG_Send = 0;
		while(ECHO_Reci == 0);      //等待接收口高電平輸出
			OpenTimerForHc();        //開啟定時器
			i = i + 1;
			while(ECHO_Reci == 1);
			CloseTimerForHc();        //關閉定時器
			t = GetEchoTimer();        //獲取時間,解析度為1US
			lengthTemp = ((float)t/58.0);//cm
			sum = lengthTemp + sum ;
		
	}
		lengthTemp = sum/5.0;
		return lengthTemp;
}


/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函式名稱: Delay_Ms_Ms
** 功能描述: 延時1MS (可通過模擬來判斷他的準確度)			
** 引數描述:time (ms) 注意time<65535
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
void Delay_Ms(uint16_t time)  //延時函式
{ 
	uint16_t i,j;
	for(i=0;i<time;i++)
  		for(j=0;j<10260;j++);
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函式名稱: Delay_Ms_Us
** 功能描述: 延時1us (可通過模擬來判斷他的準確度)
** 引數描述:time (us) 注意time<65535				 
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
void Delay_Us(uint16_t time)  //延時函式
{ 
	uint16_t i,j;
	for(i=0;i<time;i++)
  		for(j=0;j<9;j++);
}



但是關於USART的函式我就不往上寫了,這個簡單的串列埠列印大家應該都會寫。下面簡單貼一下我的主函式吧。
/*
教訓:實驗前一定要檢查引腳連線是否正確,萬不可搞錯,不然又要燒壞晶片!!!!
2017.6.8
*/

#include "hcsr04.h"
#include "chao_usart.h"

int main()
{
	
		float length;
		
		GPIO_cfg();
	  NVIC_cfg();
		USART_cfg();	
		printf("串列埠初始化成功!\n");
	
		Hcsr04Init();	
		printf("超聲波初始化成功!\n");//測試程式是否卡在下面兩句上面

		length = Hcsr04GetLength();
		printf("距離為:%.3f\n",length);
	
	
}




實驗結果:

好了,其實這個模組很簡單,但是要是把他用的很好的話還是比較困難的,比如用超聲波做一個四軸定高的程式,還是有一定的挑戰性的。

寫這篇部落格的目的不僅僅是介紹這個模組的使用,其實這種使用介紹網上一搜一大把,我只是想紀錄一下,我在做這個模組的時候遇到的一些其他的問題。

其中有一個小插曲,就是當吧寫好的程式燒進去之後,執行時總是出現每次返回一個同樣的比正常值小的多的資料,比如說0.034cm,這明顯是一個錯誤的資料,但是剛開始的時候,不知道為什麼

總是這樣,多次復位從新上電總是這一個資料。讓我很是苦惱。但是幸運的是,在這樣的情況中間,他又會有時出現一兩個正常的的資料,讓你有點摸不著頭腦。

上網查了一下才慢慢明白,這種現象叫做“餘震”,網上關於餘震的解釋大致有三種:

  1、探頭的餘震。即使是分體式的,發射頭工作完後還會繼續震一會,這是物理效應,也就是餘震。這個餘震訊號也會向外傳播。如果你的設計是發射完畢後立刻切換為接收狀態(無盲區),那麼這個餘震波會通過殼體和周圍的空氣,直接到達接收頭、干擾了檢測(注:通常的測距設計裡,發射頭和接收頭的距離很近,在這麼短的距離裡超聲波的檢測角度是很大的,可達180度)。
  2、殼體的餘震。就像敲鐘一樣,能量仍來自發射頭。發射結束後,殼體的餘震會直接傳導到接收頭,當然這個時間很短,但已形成了干擾。另外,在不同的環境溫度下,殼體的硬度和外形會有所變化,其餘震有時長、有時短、有時干擾大、有時干擾小,這是設計工業級產品時必須要考慮的問題。
  3、電路串擾。超聲波發射時的瞬間電流很大,例如某種工業級連續測距產品瞬間電流會有15A,通常的產品也能達到1A,瞬間這麼大的電流會對電源有一定影響,並干擾接收電路。通過改善電源設計可以緩解這種情況,但在低成本設計中很難根除。所以每次發射完畢,接收電路還需要一段時間穩定工作狀態。在此期間,其輸出的訊號很難使用。

消除上述現象的方法之一就是在檢測的時候多次迴圈檢測,取平均值,也就是加權平均濾波,一個簡單的濾波處理。就是下面這一段:

                u32 t = 0;
		int i = 0;
		float lengthTemp = 0;
		float sum = 0;
		while(i!=5)
		{
		TRIG_Send = 1;      //傳送口高電平輸出
		Delay_Us(20);
		TRIG_Send = 0;
		while(ECHO_Reci == 0);      //等待接收口高電平輸出
			OpenTimerForHc();        //開啟定時器
			i = i + 1;
			while(ECHO_Reci == 1);
			CloseTimerForHc();        //關閉定時器
			t = GetEchoTimer();        //獲取時間,解析度為1US
			lengthTemp = ((float)t/58.0);//cm
			sum = lengthTemp + sum ;
		
	}
		lengthTemp = sum/5.0;
		return lengthTemp;

加了這個之後,基本上就沒有出現餘震現象了。

還有一點就是測試程式前一定要檢查引腳有沒有接錯,不管多有把握,也要看一遍,不然很容易出大事的,一個晶片也許就因為你的大意給GG了。切記,這個應該也算我們這個行業的基本素養吧。