今天繼續測試 ESP32-WROVER-DEV 開發板應用, 本篇要利用一個 PIR 紅外線感測器來觸發 OV2640 來拍照, 這樣當 PIR 感測到人或動物的體溫發射的紅外線時就會觸發影像擷取動作.
本系列之前的文章參考 :
關於 PIR 模組特性參考好久之前玩 Arduino 時整理的筆記 :
摘要如下 :
- PIR=Passive InRared Sensor (被動人體紅外線感測器)
- 紅外線波長 780nm (近紅外線, 無輻射熱) ~ 100um (遠紅外線, 有輻射熱)
- 人或動物常溫下釋放的紅外線波長約為 10um
- PIR 偵測四周紅外線能量分布, 將其分成兩部分來比較能量分布是否平衡, 偵測到能量失衡即表示有在移動而發出觸發信號, 但 PIR 只能偵測熱輻射源是否有在移動, 無法偵測移動的距離, 方向與位置
- PIR 模組感應距離最遠 7 公尺, 範圍可達 110 度角
PIR 模組內主要是利用焦電型 (pyroelectric) 紅外線感測器 D203S 將溫度變化轉成電子訊號, 外面有一個聚乙烯平凸透鏡外罩, 用來過濾與聚焦波長 8um~14um 的紅外線 :
背面是電路板與接腳 :
PIR 模組有三隻接腳 :
- VCC : +5V
- GND : 接地
- OUT : 3.3V (偵測到人體紅外線) 與 0V (未偵測到人體紅外線)
PIR 模組運作電源為 5V (不要接 3.3V 電源, 會無法偵測紅外線), 但輸出腳 OUT 偵測到人體紅外線時卻是輸出 3.3V, 因此可直接與 3.3V 系統的 ESP32 相連而無須做位準轉換. 在此次實驗中, 我將此 OUT 腳接到 GPIO13, 將其設定為輸入腳以接收 PIR 的偵測觸發信號.
1. PIR 模組輸出信號檢視 :
接下來我將上面 "Arduino 測試 : PIR 紅外線移動偵測 (二)" 這篇 Arduino 程式改寫為 MicroPython 版本, 用來觀察 PIR 模組是否正確輸出信號 :
from machine import Pin
import time
pir_pin=Pin(13, Pin.IN) # 初始化 GPIO13 為輸入
counter=0 # 用來計算已顯示的標記數量
while True:
val=pir_pin.value() # 讀取 PIR 模組的輸出
mark='-' # 預設標記 '-' 表示未偵測到
if val == 1: # 偵測到人體標記改為 '*'
mark='*'
print(mark, end='') # 列印標記不換行
counter += 1 # 標記計數增量 1
if counter >= 50: # 超過 50 個標記換行
print()
counter=0 # 重設計數器
time.sleep(0.1) # 等待 100 毫秒避免過多輸出
執行結果如下 :
-----------------------------------***************
************************************************--
--------------------------------------------------
------------------****************************----
------------*******************************-------
--------------------*******************-----******
*******-----*************-------------------------
其中 ---- 表示未偵測到人體紅外線, 而 **** 表示有偵測到人體紅外線, 確認 PIR 模組有正常輸出. 接下來就可以利用 PIR 輸出來觸發 IRQ, 讓中斷處理函式呼叫拍照函式擷取影像並存檔.
2. 用 PIR 觸發中斷拍照並存檔 :
此例使用 GPIO13 作為中斷輸入腳, 上面範例的接線不用改, PIR 輸出仍然是接到 GPIO13, 但程式改為將 GPIO13 設為偵測 PIR 送來的上升緣觸發中斷信號. 關於中斷用法參考 :
透過 ChatGPT 協作, 經過多次測試修改後得到下面可正常運作的程式 :
# pir_camera_capture.py
import camera
import time
from machine import Pin
from time import localtime
import gc
def init_camera(): # 初始化鏡頭
camera.init(
0, d0=4, d1=5, d2=18, d3=19, d4=36, d5=39, d6=34, d7=35,
href=23, vsync=25, reset=-1, sioc=27, siod=26, xclk=21,
pclk=22, fb_location=camera.PSRAM, format=camera.JPEG,
xclk_freq=camera.XCLK_10MHz, framesize=camera.FRAME_QVGA)
print("Camera initialized.")
def capture_image(): # 擷取影像 & 存檔
now=localtime()
file_name='cap_{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}.jpg'.format(
now[0], now[1], now[2], now[3], now[4], now[5])
buf=camera.capture()
if buf:
with open(file_name, 'wb') as f:
f.write(buf)
print(f'Image has been saved as {file_name}')
else:
print('Failed to capture image')
del buf
gc.collect() # 強制垃圾回收
def pir_trigger(pin): # 有防抖動的 IRQ 中斷處理函式
global motion_detected, last_trigger_time # 取得全域變數
current_time=time.ticks_ms() # 紀錄現在毫秒數時戳
if time.ticks_diff(current_time, last_trigger_time) > 2000: # 防抖
print('Motion detected!') # 超過 2 秒表示已穩定(非信號抖動)
motion_detected=True # 設定拍照旗標
last_trigger_time=current_time # 更新防抖動時戳
# 初始化全域變數
motion_detected=False # 觸發中斷旗標 : 用來通知主程式執行拍照
last_trigger_time=0 # PIR 輸出防抖動時間戳
# 初始化攝像頭
init_camera()
# 設定 PIR 引腳及中斷
pir_pin=Pin(13, Pin.IN, Pin.PULL_DOWN)
pir_pin.irq(trigger=Pin.IRQ_RISING, handler=pir_trigger)
# 主迴圈負責監視拍照旗標, 若被設定就拍照存檔
try:
while True:
if motion_detected:
capture_image()
motion_detected=False # 拍照旗標
time.sleep(0.1)
except KeyboardInterrupt:
print("Exiting...")
camera.deinit()
此程式與之前的範例有一個重大差別, 那就是此處攝像頭只初始化一次, 之前的作法是每拍一次就呼叫 camera.deinit() 釋放記憶體資源, 下次拍照前重新呼叫 init_camera() 初始化, 這樣做是多此一舉, 因為初始化只要一次即可, 就能多次呼叫 capture() 進行多次拍照, 也可以避免在中段處理中減少了頻繁的初始化與資源釋放.
此程式使用全域變數來記錄拍照旗標狀態, 在中斷處理函式中只是簡單判斷一下是否為穩定之處發信號而非抖動以過濾雜訊, 若為穩定之觸發信號就設定拍照旗標讓主程式的無窮迴圈去處理拍照事宜, 因為中斷處理函式不應占用太長的處理時間, 否則可能運作會不如預期.
測試結果如下 :
True
Camera initialized.
<IRQ>
Motion detected!
5214
Image has been saved as capture_20241117_194500.jpg
Motion detected!
cam_hal: EV-EOF-OVF
cam_hal: EV-VSYNC-OVF
cam_hal: EV-EOF-OVF
8688
Image has been saved as capture_20241117_194507.jpg
Motion detected!
7787
Image has been saved as capture_20241117_194514.jpg
Motion detected!
cam_hal: EV-EOF-OVF
7787
Image has been saved as capture_20241117_194520.jpg
Motion detected!
cam_hal: EV-EOF-OVF
cam_hal: EV-VSYNC-OVF
cam_hal: EV-EOF-OVF
8742
Image has been saved as capture_20241117_194523.jpg
Motion detected!
cam_hal: EV-EOF-OVF
8272
Image has been saved as capture_20241117_194526.jpg
Motion detected!
7016
Image has been saved as capture_20241117_194533.jpg
Motion detected!
cam_hal: EV-EOF-OVF
cam_hal: EV-VSYNC-OVF
cam_hal: EV-EOF-OVF
7314
Image has been saved as capture_20241117_194537.jpg
Motion detected!
8005
Image has been saved as capture_20241117_194540.jpg
Motion detected!
cam_hal: EV-EOF-OVF
8005
Image has been saved as capture_20241117_194545.jpg
Motion detected!
cam_hal: EV-EOF-OVF
7881
Image has been saved as capture_20241117_194547.jpg
Motion detected!
cam_hal: EV-EOF-OVF
8102
Image has been saved as capture_20241117_194554.jpg
Motion detected!
cam_hal: EV-EOF-OVF
7914
Image has been saved as capture_20241117_194600.jpg
Motion detected!
cam_hal: EV-EOF-OVF
7023
Image has been saved as capture_20241117_194606.jpg
Motion detected!
cam_hal: EV-EOF-OVF
cam_hal: EV-VSYNC-OVF
cam_hal: EV-EOF-OVF
8062
Image has been saved as capture_20241117_194613.jpg
Motion detected!
8543
Image has been saved as capture_20241117_194616.jpg
Motion detected!
cam_hal: EV-EOF-OVF
cam_hal: EV-VSYNC-OVF
cam_hal: EV-EOF-OVF
.... (略) ....
Motion detected!
7794
Image has been saved as capture_20241117_195246.jpg
Motion detected!
7928
Image has been saved as capture_20241117_195248.jpg
Motion detected!
8078
Image has been saved as capture_20241117_195311.jpg
Motion detected!
cam_hal: EV-EOF-OVF
8084
Image has been saved as capture_20241117_195316.jpg
Motion detected!
7924
Image has been saved as capture_20241117_195403.jpg
Motion detected!
7924
Image has been saved as capture_20241117_195407.jpg
Exiting...
True
檢視開發板根目錄果然建立了許多 jpeg 圖檔 :
下面是擷取影像中的一張圖 capture_20241117_195240.jpg :
這是我鄉下家的書房兼臥室哈哈.
沒有留言 :
張貼留言