Learning Objectives
- 說明超音波測距如何由回音時間算出距離,並把 Echo 腳的 5V 安全地接到樹莓派
- 用 gpiozero 的 DistanceSensor 讀距離,依距離分段控制警示燈與蜂鳴器
- 說明 PIR 人體感測器偵測什麼、偵測不到什麼,以及延遲與暖機
- 用 when_motion、when_no_motion 寫事件驅動的程式
讓作品知道「前面有東西」「有人來了」
倒車雷達會在快撞到時嗶嗶叫,走廊的燈在有人經過時自己亮起來。這兩件事用的是兩種常見又便宜的感測器:
- 超音波測距模組(HC-SR04):發出人耳聽不到的聲音,聽回音,算出距離。
- PIR 人體感測器(例如 HC-SR501):感應人體發出的紅外線有沒有變化,知道「有人在動」。
這一課要回答:
- 回音的時間怎麼變成公分?
- 5V 的感測器怎麼安全地接到 3.3V 的樹莓派?
- 「有人經過時才做事」的程式,為什麼不必一直寫迴圈去檢查?
超音波測距:聽回音算距離
HC-SR04 有四支腳:VCC(5V)、Trig、Echo、GND。量一次距離的流程:
- 樹莓派在 Trig 送出至少 10 μs 的高電位脈波。
- 模組發出 8 個 40 kHz 的超音波,同時把 Echo 拉高。
- 聲波碰到物體反彈回來,模組收到回音就把 Echo 拉低。
- Echo 維持高電位的時間,就是聲音來回一趟的時間。
距離 = 回音時間 × 聲速 ÷ 2 (除以 2:聲音走了來回兩倍的路)
聲速約 340 m/s = 0.034 cm/μs
例:回音時間 1160 μs → 1160 × 0.034 ÷ 2 ≈ 19.7 cm
規格書的簡便算法:μs ÷ 58 ≈ 公分
規格書寫的量測範圍約 2 cm~400 cm,兩次量測之間建議隔 60 ms 以上,免得上一次的回音干擾下一次。 軟的、斜的表面會讓回音變弱或彈到別處,讀值就不準。
Echo 是 5V:樹莓派的 GPIO 只能承受 3.3V,直接接 Echo 可能燒壞腳位。要用兩顆電阻分壓: 例如 Echo 串 330Ω 到 GPIO,GPIO 再經 470Ω 接地,GPIO 看到的大約是 3V。也有 3.3V 就能工作的 HC-SR04P 版本,不需要分壓。
gpiozero 的 DistanceSensor 負責送 Trig、量 Echo、換算距離:
from gpiozero import DistanceSensor
sensor = DistanceSensor(echo=24, trigger=23) # max_distance 預設 1 公尺
sensor.distance # 單位是公尺,最大就是 max_distance
逐步看:讀距離
模擬的物體在 2.5 秒內從 80 cm 靠近到 15 cm。注意讀 distance 那一步,時間多走了約 60 ms:那是送出超音波、等回音的時間。
逐步看:倒車雷達
物體從 120 cm 靠近到 10 cm。程式依距離分三段:50 cm 以上不警示、20~50 cm 亮燈、20 cm 以內燈亮加蜂鳴器。
這裡把 max_distance 設成 2 公尺,否則 1 公尺以外都會讀成 100 cm。
PIR:感應「有人在動」
PIR 是 Passive Infrared(被動紅外線)的縮寫。它不發出任何東西,只接收:人體會發出紅外線,有人在感測範圍內移動時, 感測器收到的紅外線發生變化,輸出腳就變成高電位。因此:
- 它偵測的是變化:一個人靜靜坐著不動,過一陣子就可能被當成「沒有人」。
- 它不知道距離、也不知道是誰,只回報「有動靜」或「沒有」。
常見的 HC-SR501 模組:
| 項目 | 說明 |
|---|---|
| 電源 | 接 5V(模組的工作電壓範圍比較寬) |
| 輸出 | 偵測到時輸出約 3.3V 的高電位,可以直接接 GPIO |
| 延遲旋鈕 | 偵測到之後輸出維持高電位多久,可調(約數秒到數分鐘) |
| 靈敏度旋鈕 | 調整偵測距離 |
| 觸發模式跳線 | 可重複觸發:有人一直動就一直延長;不可重複:時間到就先拉低 |
| 暖機 | 剛通電時要等一段時間(數十秒左右)才穩定,這段時間的讀值不要採信 |
gpiozero 的 MotionSensor 讀它:motion_detected 是目前有沒有偵測到,when_motion、when_no_motion 是事件。
事件驅動:登記好,等它發生
第 06 課的按鈕用過事件。寫法的重點是「登記」而不是「檢查」:
pir.when_motion = on_motion # 有動靜時,請呼叫 on_motion(注意沒有括號)
pir.when_no_motion = on_still
pause() # 程式停在這裡等,事件發生時 gpiozero 會呼叫你登記的函式
和自己寫 while True 一直讀 motion_detected 比起來,事件寫法不會錯過短暫的變化,也不必決定「多久檢查一次」。
from signal import pause 的 pause() 讓主程式不要結束;如果主程式結束了,登記的事件也就沒有機會執行。
登記時寫
on_motion,不是on_motion():加了括號會立刻執行一次,再把它的回傳值(None)登記上去。
需要「等到有人才繼續往下做」的流程時,也可以用 pir.wait_for_motion():程式會停在這一行,直到偵測到移動。
逐步看:有人經過就開燈
模擬有人在 0.8~1.8 秒、2.6~3.3 秒經過。主程式執行到 pause() 就停住了,
之後的每一步都是 gpiozero 在事件發生時呼叫你的函式:注意左上角的時間直接跳到事件發生的那一刻。
小測驗:超音波
下面的程式會印出什麼?(聲速 0.034 cm/μs)
echo_us = 2000
cm = echo_us * 0.034 / 2
print(round(cm))
DistanceSensor(echo=24, trigger=23) 沒有設定 max_distance,物體在 1.5 公尺外,distance 大約會是多少?
為什麼算距離時要除以 2?
小測驗:PIR 與事件
寫成 pir.when_motion = on_motion()(多了括號),會發生什麼事?
下面的程式模擬三次事件,會印出什麼?
count = 0
def on_motion():
global count
count = count + 1
handler = on_motion
for _ in range(3):
handler()
print(count)
HC-SR501 剛接上電源的前幾十秒,讀值忽有忽無,最合理的處理是?
重點整理
- 超音波測距:Trig 送 10 μs 脈波,Echo 高電位的時間是聲音來回的時間;距離=時間 × 聲速 ÷ 2(μs ÷ 58 ≈ cm)。
- HC-SR04 的 Echo 是 5V,要分壓(例如 330Ω/470Ω)才能接樹莓派;或改用 3.3V 版本。
DistanceSensor(echo=, trigger=, max_distance=):distance單位是公尺,超過 max_distance 會停在最大值;讀一次存進變數再判斷。- PIR 偵測的是紅外線的變化(有人在動),人不動就可能偵測不到;HC-SR501 有延遲與靈敏度旋鈕,通電後要暖機。
MotionSensor的when_motion、when_no_motion登記事件(不加括號),再用pause()讓程式等事件發生。
下一課換成攝影機:讓樹莓派「看見」,拍照並做簡單的影像處理。