2017年9月7日 星期四

MicroPython on ESP8266 (二十三) : 超音波模組 HC-SR04 測試

我兩個星期前就找出 HC-SR04 超音波模組準備進行測試, 但一時興起想確定 MicroPython on ESP8266 能否與 Arduino 介接就擱下了. 因此做完 UART 實驗後就回頭來研究 MicroPython 要如何使用超音波模組.

本系列 MicroPython on ESP8266 測試文章參考 :

MicroPython on ESP8266 (一) : 燒錄韌體
MicroPython on ESP8266 (二) : 數值型別測試
MicroPython on ESP8266 (三) : 序列型別測試
MicroPython on ESP8266 (四) : 字典與集合型別測試
MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試
MicroPython on ESP8266 (六) : 檔案系統測試
MicroPython on ESP8266 (七) : 時間日期測試
MicroPython on ESP8266 (八) : GPIO 測試
MicroPython on ESP8266 (九) : PIR 紅外線移動偵測
MicroPython v1.9.1 版韌體測試
MicroPython on ESP8266 (十) : socket 模組測試
MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
MicroPython on ESP8266 (十二) : urequests 模組測試
MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試
MicroPython 使用 ampy 突然無法上傳檔案問題
MicroPython on ESP8266 (十四) : 網頁伺服器測試
WeMOS D1 Mini 開發板測試
MicroPython on ESP8266 (十五) : 光敏電阻與 ADC 測試
MicroPython on ESP8266 (十六) : 蜂鳴器測試
MicroPython on ESP8266 (十七) : 液晶顯示器 1602A 測試
MicroPython on ESP8266 (十八) : SSD1306 液晶顯示器測試
# MicroPython v1.9.2 版釋出
MicroPython on ESP8266 (十九) : 太陽能測候站與移動偵測控制照明
MicroPython on ESP8266 (二十) : 從 ThingSpeak 讀取資料
MicroPython on ESP8266 (二十一) : 使用 ThingTweet 傳送推文
MicroPython on ESP8266 (二十二) : UART 串列埠測試

MicroPython 文件參考 :

MicroPython tutorial for ESP8266  (官方教學)
http://docs.micropython.org/en/latest/micropython-esp8266.pdf
http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket
https://gist.github.com/xyb/9a4c8d7fba92e6e3761a (驅動程式)
超音波模組主要用在測距, 例如應用在智慧小車防撞或水位偵測等. 之前在玩 Arduino 時曾做過 HC-SR04 的測試, 參考 :

Arduino 超音波模組測試
HC-SR04 超音波感測器介紹

HC-SR04 模組的特徵摘要如下 :

HC-SR04 模組使用 40KHz 超音波, 偵測距離 2~400 公分, 具有四個針腳 : VCC (+5V), GND, TRIG (輸入) 與 ECHO (輸出). TRIG 輸入為高位準觸發, 當控制器將 TRIG 拉到 High 位準超過 10 微秒就會觸發模組於發射端送出連續 8 個 40KHz 超音波脈衝, 接收端收到反射波後就會在 ECHO 腳送出一個 High 位準脈波, 其寬度即為超音波往返之時間. 參考 :




只要將此 High 脈波時長 (單位微秒) 除以 58 微秒即為模組與障礙物之距離 (cm), 這 58 微秒是音波在聲速 344m/s 下往返距離 1 cm 障礙物所花的時間.

另外要注意的是, HC-SR04 模組是 5V 系統, 因此 TRIG 與 ECHO 這兩隻腳與 ESP8266 介接時需要一個 5V/3.3V 位準轉換模組, 參考之前 UART 測試時的做法 :

MicroPython on ESP8266 (二十二) : UART 串列埠測試

或者可用 2N7000 MOSFET 與 4.7K 電阻製作雙向位準轉換器 :




以下測試我使用 D1 Mini + HC-SR04 + 雙向位準轉換模組, 完整電路圖如下 :





軟體部分主要就是利用 GPIO 埠向 HC-SR04 的 TRIG 腳發送 10 微秒以上寬度的 HIGH 脈波, 然後測量 ECHO 腳的 HIGH 脈波寬度 (微秒), 再除以 58 微秒即可得到與障礙物之距離了. 在 Arduino 的超音波測試中, Arduino 有 pulseIn() 函數來測量; 但在 MicroPython 卻沒有, 必須自己實作. 我主要是參考下面這篇的寫法加以改寫成 ping() 函數 :

Module for Ultrasonic sensor (HC-SR04)


測試 1 : 用超音波測量距離 (不考慮溫度)

import time
from machine import Pin

def ping(trigPin, echoPin):
    trig=Pin(trigPin, Pin.OUT)
    echo=Pin(echoPin, Pin.IN)
    trig.value(1)
    time.sleep_us(10)
    trig.value(0)
    count=0
    timeout=False
    start=time.ticks_us()
    while not echo.value(): #wait for HIGH
        time.sleep_us(10)
        count += 1
        if count > 100000: #over 1s timeout
            timeout=True
            break
    if timeout: #timeout return 0
        duration=0
    else: #got HIGH pulse:calculate duration
        count=0
        start=time.ticks_us()
        while echo.value(): #wait for LOW
            time.sleep_us(10)
            count += 1
            if count > 2320: #over 400cm range:quit
                break
        duration=time.ticks_diff(time.ticks_us(), start)
    return duration

while True:
    distance=round(ping(trigPin=13,echoPin=15)/58)
    print('%scm' % distance)
    time.sleep(1)

上面的 ping() 函數要傳入 trigPin 與 echoPing 的 GPIO 編號, 例如使用 D1 Mini 的 D7 (GPIO 13) 當 trigPin, D8 (GPIO 15) 當 echoPin 的話, 就要呼叫 ping(13, 15). 在 trigPin 送出 10 微秒 HIGH 脈波就會觸發 HC-SR04 送出超音波, 然後用一個無限迴圈, 以 10 微秒為單位偵測 echoPin 是否有收到 HIGH 脈波. 根據 Arduino 的 pulseIn() 函數規格, 若在 1 秒內沒有收到 echoPin 脈波, 就回傳 0, 所以跳出迴圈的條件是 1000000/10=100000.

"the function returns 0 if no complete pulse was received within the timeout. Default is one second (unsigned long)."

若在 1 秒時限內偵測到 HIGH 脈波, 就啟動新的計時器來計算脈波寬度, 同樣地, 這無限迴圈也必須設一個跳出條件, 根據 HC-SR04 規格, 其偵測範圍為 2 cm ~ 400 cm, 因此 echoPin 收到的最長 HIGH 脈波是 23.2 毫秒, 即 23200 微秒, 呼叫 time.ticks_us() 以 10 微秒為單位計時的話, 跳出條件是 2320 個迴圈 :

400cm*58us/cm=23200us=23.2ms

最後呼叫 time.ticks_diff() 函數來計算脈波寬度 (微秒). 注意, time.ticks_diff() 的第一參數是結束時間; 而第二參數是開始時間, 放反的話會得到負數, 測試結果確實可傳回準確之距離.

4cm
5cm
8cm
5cm
5cm
124cm
21cm
1368cm
316cm
13cm
4cm
8cm
4cm


下面測試 2 則是加上溫度調整的版本 :

測試 2 : 用超音波測量距離 (考慮溫度)

import time
from machine import Pin

def ping(trigPin, echoPin):
    trig=Pin(trigPin, Pin.OUT)
    echo=Pin(echoPin, Pin.IN)
    trig.value(1)
    time.sleep_us(10)
    trig.value(0)
    count=0
    timeout=False
    start=time.ticks_us()
    while not echo.value(): #wait for HIGH
        time.sleep_us(10)
        count += 1
        if count > 100000: #over 1s timeout
            timeout=True
            break
    if timeout: #timeout return 0
        duration=0
    else: #got HIGH pulse:calculate duration
        count=0
        start=time.ticks_us()
        while echo.value(): #wait for LOW
            time.sleep_us(10)
            count += 1
            if count > 2320: #over 400cm range:quit
                break
        duration=time.ticks_diff(time.ticks_us(), start)
    return duration

temperature=25  
velocity=331.5 + 0.6*temperature  

while True:
    duration=ping(trigPin=13,echoPin=15)/1000000/2   #unit=second, single way
    distance=round(duration*velocity*100)   #single way
    print('%scm' % distance)
    time.sleep(1)


上面範例是指定溫度為 25 度, 如果加上一個 DHT11 來取得溫度值的話就更完美了, 可惜現在手邊沒有 DHT11 了.


參考 :

Library for HC-SR04 and SRF04 ultrasonic sensors (for pyboard)
Pulse width in microseconds (like pulseIn in Arduino)


2017-09-10 補充 :

今天在找 WDT 資料時找到 machine.time_pulse_us() 函數, 這是 machine 物件的脈波計時器函數, 用來測量指定針腳輸入的脈波寬度 (單位微秒), 相當於 Arduino 的 pulseIn() 函數, 使用前須先匯入 machine 模組. 其 API 如下 :

machine.time_pulse_us(pin, pulse_level, timeout_us=1000000)

第一參數為 Pin 物件, 傳入要偵測之輸入針腳, 例如 machine.Pin(0) 便是偵測 GPIO 0 腳. 第二參數為要偵測之脈波位準, 傳入值為 0 或 1, 傳入 0 表示要偵測位準變成變成 LOW 之時間; 同理傳入 1 就是偵測位準變成 HIGH 的時間. 指令執行時若位準已經是指定的 pulse_level, 計時器會立即開始, 否則會等待變成  pulse_level 時才會開始計時, 但這個等待受到第三參數所設定之逾時計時器的限制, 此指令執行後逾時計時器開始計時, 若在指定時間 (預設 1 秒) 內沒有偵測到脈波變化, 傳回值為 -2; 若偵測到指定之脈波變化後, 沒有回到原來的位準, 就傳回 -1.

<<< import machine
<<< machine.time_pulse_us(machine.Pin(0),0)
-2
<<< machine.time_pulse_us(machine.Pin(0),1)
-1

上面是在 GPIO 0 福接情況下的測試, 由於 GPIO 0 有內建 10K 上拉電阻, 預設位準就是 HIGH, 因此第一個指令使用 time_pulse_us() 去偵測 LOW 位準會因為等不到 LOW 脈波而在預設的 1 秒等待時間截止時傳回 -2; 同理, 第二個指令要偵測其變 HIGH 之脈寬, 因其原本就是 HIGH, 指令執行後開始計時, 但卻等不到其變回 LOW, 因此在預設 1 秒等待時間後傳回 -1. 注意, 逾時計時器最大可設 2.56 秒 (2560000 微秒), 超過此長度時機器會重置. 參考 :

ESP8266 - Board auto-reset when using overlong delay machine.time_pulse_us() #2775

我們可以利用 machine.time_pulse_us() 來取代上面範例中自製的脈波計時程式碼, 改寫 ping() 函數如下 :

def ping(trigPin, echoPin):
    trig=Pin(trigPin, Pin.OUT)
    trig.value(1)
    time.sleep_us(10)
    trig.value(0)
    ret=time_pulse_us(Pin(echoPin),1)
    if ret == -2: #no pulse
        duration=0
    elif ret == -1: #pulse no ending
        duration=1000000
    else:
        duration=ret
    return duration

因 Python 無 switch case 語法, 因此用 if elif else 來判斷傳回值, 若為 -2 表示預期之脈波沒出現, 傳回值設為 0 秒 (計算距離為 0 cm); 若傳回 -1 表示位準變化後沒有結束, 傳回逾時計時器預設時長 1 秒 (計算距離約 17241 cm).

測試 3 : 用超音波測量距離 : 使用 machine.time_pulse_us()

import time
from machine import Pin,time_pulse_us

def ping(trigPin, echoPin):
    trig=Pin(trigPin, Pin.OUT)
    trig.value(1)
    time.sleep_us(10)
    trig.value(0)
    ret=time_pulse_us(Pin(echoPin),1)
    if ret == -2: #no pulse
        duration=0
    elif ret == -1: #pulse no ending
        duration=1000000
    else:
        duration=ret
    return duration

temperature=25
velocity=331.5 + 0.6*temperature

while True:
    duration=ping(trigPin=13,echoPin=15)/1000000/2   #unit=second, single way
    distance=round(duration*velocity*100)   #single way
    print('%scm' % distance)
    time.sleep(1)

測試結果與上面相同, 但程式較精簡了.

4 則留言 :

耗子也疯狂 提到...

我买了连个版本的HC-SR04 ,一个是HC-SR04+ 区别就是后者可以直接3.3V驱动,但是测量距离要比前者短将近一半,原版的SR04貌似不做电压转换也能在nodemcu上正确获取距离

小狐狸事務所 提到...

我手上的是 5V 版的 HC-SR04, 得加個位準調整器才行, 實際用尺去量誤差不大.

Unknown 提到...

請問超音波模組 HC-SR04 用 D1 mini Trig --D5腳位、Ehco --D6腳位 均顯示0cm,不曉得哪裡有問題?

小狐狸事務所 提到...

通常都是接觸不良, 更換其他 GPIO 腳試試看