STM32筆記(28):HC-SR04 超音波測距

過去在研究 Arduino 時,曾使用 HC-SR04 超音波測距進行距離實測:Arduino筆記(19):HC-SR04超音波測距模組,這篇在我的部落格中瀏覽量一直排名很前面,表示多數學習自動控制的朋友都想瞭解如何使用 HC-SR04 進行測距,現在改用 STM32F1 的開發板來實做測距功能。

[超音波測距 HC-SR04]

HC-SR04 技術規格:
  • 電源:DC5V/2mA
  • 輸出電位( 1/ 0):5V/ 0V
  • 精度:3mm
  • 距離範圍:2 ~ 450cm
  • 有效的角度:<15℃
  • 觸發輸入信號:10uS TTL pulse
  • ECHO輸出信號: Input TTL lever signal and the range in proportion
  • 接線方式:VCC、trig (控制端)、echo (接收端)、GND

模組工作原理:
  • 採用I/O觸發測距,給至少為 10us 的高電位信號
  • 模組自動發送 8 個 40KHZ 的方波,自動檢測是否有信號返回
  • 有信號返回,通過 I/O 輸出一高電位,高電位持續的時間就是超音波從發射到返回的時間
  • 測試距離 = (高電位時間 x 聲速 (343.2 米/秒)) / 2


TRIG 引腳負責「觸發」送出超音波,一開始將這個引腳設定為高電位 10 us,此時 HC-SR04 發送 8 個 40 kHz 週期的音波;在發送了一個音波後,ECHO 引腳會變為高電位,這時 STM32 開始定時器計時,ECHO 引腳用於距離測量,在檢測到回傳的超音波後,ECHO 引腳的變為低電位,這時再讀取定時器的時間,兩個時間差就是音波來回的時間。

距離=(ECHO 高電位時間 x 超音波速度(空氣中的聲速 340 m/sec)/2

以下先不呼叫函式,直接以音波在空氣中傳遞的大約速度,算出來回測得的時間,乘以空氣中傳遞聲音的速度,算出距離。程式中有一行 lengthTemp = ((float)t/58.2); 各位會不會覺得奇怪,為什麼要除以 58.2 ? 那是因為在攝氏零度之海平面音速約為 331.5公尺/秒,每升高 1 攝氏度,音速就增加 0.607 公尺/秒,可以列出一個公式:

• 音速 c = 331.5 + 0.607 * t (其中 t 為攝氏溫度)。

以攝氏 20 度為例:音速約為 331.5 + 0.607 * 20 = 343.64 公尺/秒
- 音速公尺/秒 換算成 公分/微秒:343.64 * 100 / 1000000 = 0.034364 公分/微秒,亦即
- 音速每公分需要 29.1 微秒: 1 / 0.034364 = 29.1 微秒/公分
- 來回的距離,單程距離需再將測得的距離 / 2 ,得到 / 29.1 在除以 2 = / 58.2

[材料]

  • STM32F103C8T6 主板 x 1
  • OLED SSD1306 顯示器 x 1
  • STLINK V2 模擬下載器 x 1
  • HC-SR04 超音波測距 x 1
  • 麵包板 x 1
  • 連接線 x N 條

[接線與電路圖]

HC-SR04 分別接 3.3v 及 GND,中間的接腳 TRIG 和 ECHO 分別接在 STM32F103x 的 PA0、PA1,與 SSD1306 連接的方式如下:
STM32F103xSSD1306 OLEDHC-SR04
3.3vVDDVCC
GNDGNDGND
PB8SCK/SCL-
PB9SDA-
PA0-TRIG
PA1-ECHO



[程式]

主程式 main.c 如下:
#include "stm32f10x.h"
#include "delay.h"
#include "oled.h"
#include "hcsr04.h"

uint16_t Second;

int main (void)
{
	u32 vtg;
	u32 distance;
	
	delay_init();	    	 //延時函數初始化	  
	OLED_Init();			 //初始化OLED  
	HCSR04_Init();

	OLED_Clear(0);           //清除螢幕
	OLED_ShowString(2, 2, "Dist.:",16);	
	while (1)
	{
		distance = HCSR04GetLength();
		OLED_ShowNum(52, 2, distance/100, 4, 16);	  //整數位
	    OLED_ShowString(85, 2, ".",16); 

		vtg = distance % 100;  	//小數點後兩位
		if (vtg < 10) {
			OLED_ShowNum(93, 2, vtg, 2, 16);
			OLED_ShowString(93, 2,"0",16); 			//個位數時前面補0	
		} else
		{
			OLED_ShowNum(93, 2, vtg, 2, 16);			
		}
		delay_ms(200);
	}
}


HC-SR04.c 的程式內容如下:
#include "stm32f10x.h"
#include "hcsr04.h"
#include "delay.h"

u16 msHcCount = 0;	//ms計數

void HCSR04_Init(void)
{  
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;     //定義計時器結構體
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
     
    //IO 初始化
    GPIO_InitStructure.GPIO_Pin = HCSR04_TRIG;       	//發送電位引腳 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;    	 //返回電位引腳 Echo
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;	
    GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);  
    GPIO_ResetBits(HCSR04_PORT,HCSR04_ECHO);	
	 
	//計時器初始化 使用基本計時器TIM3
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);   //啟動TIM3 RCC時鐘

	TIM_DeInit(TIM3);
	TIM_TimeBaseStructure.TIM_Period = 65535; //(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_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位		 
		
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);   	//清除更新中斷,避免立即產生中斷
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);    //啟用計時器更新中斷

	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;            	//設定中斷服務
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  	//主要優先順序
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         	//次要優先順序
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        		//啟用中斷
	NVIC_Init(&NVIC_InitStructure);
	
    TIM_Cmd(TIM3,DISABLE);     
}

//計時器中斷服務程式
void TIM3_IRQHandler(void)   //TIM3中斷
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //檢查TIM更新中斷是否發生
    {
         TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  //清除TIM更新中斷標誌 
         msHcCount++;
    }
}
 
 
//開始計數
static void StartTimer()        //打開計時器
{
    TIM_SetCounter(TIM3,0);	//清除計數
    msHcCount = 0;
    TIM_Cmd(TIM3, ENABLE);  //啟用TIM3外設	
}
 
static void StopTimer() 	//關閉計時器
{
    TIM_Cmd(TIM3, DISABLE); //啟用TIMx外設
}
 
//獲取計時器時間
float GetEchoTimer(void)
{
    u32 t = 0;
    t = msHcCount*1000;			//得到MS
    t += TIM_GetCounter(TIM3);	//得到US
	TIM3->CNT = 0;  			//將TIM3計數寄存器的計數值清零
	delay_ms(50);
    return t;
}
 
//取五次數據平均值
float HCSR04GetLength(void)
{
	u32 t = 0;
	int i = 0;
	float lengthTemp = 0;  
	float sum = 0; 	
	float distance;

	for(i=0;i<5;i++){		
		
		TRIG_SEND = 1;      //發送口高電平輸出
		delay_us(20);
		TRIG_SEND = 0;
			
		while(ECHO_RECEIVE == 0); 	//等待接收口高電平輸出
		StartTimer();        	//打開計時器

		while(ECHO_RECEIVE == 1);
		StopTimer();        	//關閉計時器
		
		t = GetEchoTimer();     //獲取時間,解析度為1US

		lengthTemp = ((float)t/58.2);	//cm
		sum = lengthTemp + sum ;
	}
	distance = 100*sum/5.0;
	return distance;
}
 

HC-SR04.h 程式內容如下:
#ifndef __HCSR04_H
#define __HCSR04_H

#define HCSR04_TRIG     GPIO_Pin_0
#define HCSR04_ECHO     GPIO_Pin_1
#define HCSR04_PORT     GPIOA

#define TRIG_SEND		PAout(0) 
#define ECHO_RECEIVE  	PAin(1)

void 	HCSR04_Init(void);
static void OpenTimerForHc(void);
void 	TIM3_IRQHandler(void);
float 	GetEchoTimer(void);
float 	HCSR04GetLength(void);

#endif

完整的程式請參考 Github:13. HC-SR04 measure distance

[結果]

在測距的過程中,有時會因為周圍的環境,反射物體的表面等差異,讓測試的結果有些許誤差,不過大致都還算準確。HC-SR04 的測距功能可以用來作為小車的防撞或避開障礙,小小的誤差是可以接受的。


[參考資料]


Post a Comment

較新的 較舊