一切開始之前,我們需要先來溫習一個中學就學習過的物理原理:
常溫常壓下空氣中的聲速: 340m/s
德罕姆沖你微笑
聲波雷達原理
超聲波也是聲波,它在1個標準大氣壓下,15℃的空氣中的傳播速度為340m/s。而聲波在傳播的過程中遇到障礙物時,會反射也會衍射,所以當我們測量出發出聲波和聽到回聲的時間差,就能估算出聲音傳播的距離。同時因為超聲波頻率高波長短,所以衍射低,拘束性好,因此能量衰減也更少,傳播的距離也更長,更比耳朵能聽到的低頻率聲音更適合做長距離測量。
超聲波測距原理
如上圖所示,測量步驟為:
- 發出n個mHz的超聲波脈沖,超聲波脈沖發射結束時開始計時,記作t0
- 開始監聽mHz的超聲波脈沖的回聲,監聽到n個后結束計時,記作t1
- 計算時間差t = t1 - t0,為超聲波來回兩程所花費的時間
- 計算超聲波走過的距離s = v * (t1 - t0) / 2(v為聲速)
聲波速度與空氣溫度的關系
聲波傳播速度與傳播介質的溫度成正比,與傳輸介質的密度成正比。聲波在1個標準大氣壓下的傳播速度與空氣溫度的關系為:
v = 331.5 + 0.607t
v為聲波的速度,單位m/s
t為空氣的溫度,單位℃
超聲波測距模塊
現在市面上的超聲波測距模塊很多,比較常見的就算HC-SRxxx系列和US-100系列了,它們甚至可以用于汽車的倒車雷達。基本上都長這個樣子,一定會有一個或者兩個圓筒筒的超聲波收發器。
超聲波模塊
目前這個超聲波模塊都是使用40kHz的聲波,人耳可辨識的聲音頻率范圍在20 ~ 20kHz,模塊使用的聲音頻率超過人耳可識頻率上限的2倍,完全對人類的正常生活產生影響。
它們的測距方式都是大同小異的,通常有TTL方式和GPIO方式兩種,GPIO是最通用的一種控制方式,我們就用這個方式為例說明超聲波測距模塊的用法。
接線方式如下:
- Trigger,觸發輸入端,默認低電平,輸入一個時常超過10μs的高電平脈沖即可觸發模塊發射一組超聲波脈沖
- Echo,回聲輸出端,默認低電平,模塊發射超聲波脈沖結束后,回聲端就會輸出高電平,直到監聽到所有超聲波脈沖的回聲(或監聽超時)后才會重新輸出低電平
超聲波測距時序
使用步驟如下:
- 在拉高Trigger端10μs以上后拉低,以觸發模塊發射8個40kHz的超聲波脈沖
- 開始監聽Echo端,Echo輸出高電平時開始計時,Echo輸出低電平時結束計時
- 計算出計時的時長,即為聲波一來一回花費的時間
- 計算聲波走過的路程
如果想要測量得更精確,可以測量環境溫度,然后如聲波速度與空氣溫度的關系所述,利用環境溫度來修正聲波的速度后,再計算聲波的距離。溫度測量的實現方法詳見[Ardunio] DS18B20溫度傳感器
編碼實現
#define ULTRASONIC_ECHO_PIN 2
#define ULTRASONIC_TRIGGER_PIN 4
#define ULTRASONIC_DISCOVERING_STATE_PIN 13
#define ULTRASONIC_DISCOVERING_INTERVAL 500000
volatile bool mReadyToDiscovering = true;
unsigned long mEchoStartTime = 0;
unsigned long mEchoEndTime = 0;
float mDistance = 0.0f;
void trigger();
void calcDistance();
void onEcho();
bool isReadyToDiscovering();
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Ultrasonic ranging ready");
// UltraSonic pins setup
pinMode(ULTRASONIC_ECHO_PIN, INPUT);
pinMode(ULTRASONIC_TRIGGER_PIN, OUTPUT);
pinMode(ULTRASONIC_DISCOVERING_STATE_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(ULTRASONIC_ECHO_PIN), onEcho, CHANGE);
// UltraSonic pins initialization
digitalWrite(ULTRASONIC_TRIGGER_PIN, LOW);
digitalWrite(ULTRASONIC_DISCOVERING_STATE_PIN, LOW);
}
void loop() {
if (isReadyToDiscovering()) {
calcDistance();
trigger();
}
}
bool isReadyToDiscovering() {
if (mReadyToDiscovering) {
unsigned long now = micros();
if (now - mEchoEndTime > ULTRASONIC_DISCOVERING_INTERVAL) {
mReadyToDiscovering = false;
return true;
}
}
return false;
}
void trigger() {
digitalWrite(ULTRASONIC_TRIGGER_PIN, HIGH);
delayMicroseconds(50);
digitalWrite(ULTRASONIC_TRIGGER_PIN, LOW);
}
void onEcho() {
uint8_t echo = digitalRead(ULTRASONIC_ECHO_PIN);
unsigned long now = micros();
if (echo == HIGH) {
mEchoStartTime = now;
digitalWrite(ULTRASONIC_DISCOVERING_STATE_PIN, HIGH);
} else {
mEchoEndTime = now;
digitalWrite(ULTRASONIC_DISCOVERING_STATE_PIN, LOW);
mReadyToDiscovering = true;
}
}
void calcDistance() {
long deltaTime = mEchoEndTime - mEchoStartTime;
mDistance = 340L * deltaTime / 2000000.0; // s = vt = 340 m/s * time / 2
Serial.print("Distance = ");Serial.print(mDistance * 100.0);Serial.print("cm, Time = ");Serial.print(deltaTime);Serial.println("us");
}