樹藝AI故事機:讓自然素材作品自己說故事
Raspberry Pi Pico W × JQ6500 × HC-SR04 × MicroPython × Thonny
樹藝作品不只是靜態展示品。透過 Raspberry Pi Pico W、JQ6500 語音播放模組、超音波感測器與三個按鈕,我們可以製作一臺會主動迎賓、能讓參觀者自行選擇導覽內容、並在離場時說謝謝的「樹藝AI故事機」。
一、樹藝AI故事機的互動流程
播放系統訊息
自動迎賓
按下導覽按鈕
播放介紹內容
播放謝謝參觀
二、需要準備的硬體
| 元件 | 用途 | 說明 |
|---|---|---|
| Raspberry Pi Pico W | 故事機控制核心 | 執行 MicroPython 程式,讀取感測器與按鈕,發送播放命令。 |
| JQ6500 語音播放模組 | 播放 MP3 | 插入 microSD 卡,連接喇叭後播放故事、材料與作者介紹。 |
| HC-SR04 超音波感測器 | 偵測參觀者靠近或離開 | 量測距離,作為迎賓與致謝的觸發條件。 |
| 按鈕 × 3 | 選擇導覽內容 | 建議加上大字標籤:作品故事、使用材料、設計作者。 |
| 喇叭與 microSD 卡 | 輸出語音與保存音檔 | 建議使用 4Ω、3W 左右的小喇叭;microSD 卡格式化為 FAT32。 |
| 1kΩ 與 2kΩ 電阻 | 保護 Pico W | 將 HC-SR04 的 ECHO 5V 訊號降壓為約 3.3V。 |
三、Pico W、JQ6500、超音波與按鈕接線
1. JQ6500 語音模組
| Pico W | JQ6500 | 用途 |
|---|---|---|
| VBUS 5V(實體 Pin 40) | VCC | JQ6500 供電 |
| GND(實體 Pin 38 或任一 GND) | GND | 共地,必接 |
| GP0/UART0 TX(實體 Pin 1) | RX | Pico W 傳送播放指令 |
| 不接 | TX | 第一版不讀取 JQ6500 狀態,避免 5V 訊號直接進入 Pico W。 |
| SPK+、SPK- | 喇叭正、負極 | 喇叭兩端直接接 JQ6500,不可接到 GND。 |
2. HC-SR04 超音波感測器
| Pico W | HC-SR04 | 用途 |
|---|---|---|
| VBUS 5V | VCC | 感測器供電 |
| GND | GND | 共地 |
| GP14(實體 Pin 19) | TRIG | 發出超音波觸發訊號 |
| GP15(實體 Pin 20) | ECHO(經分壓) | 接收距離回傳訊號 |
3. 三個導覽按鈕
| 功能 | Pico W GPIO | 播放音檔 |
|---|---|---|
| 作品故事 | GP10 | 001.mp3 |
| 使用材料 | GP11 | 003.mp3 |
| 設計作者 | GP12 | 002.mp3 |
每一個按鈕的一端接對應 GPIO,另一端接 GND。程式使用 Pico W 的內建上拉電阻,因此不需再外接上拉電阻。
四、下載與使用 Thonny 開發 MicroPython
Thonny 是適合初學者的 Python 開發工具,可直接把程式寫入 Pico W,也能在 Shell 視窗查看感測器距離與除錯訊息。
- 到 Thonny 官方網站下載並安裝 Windows 版本。
- 第一次使用 Pico W 時,到 Raspberry Pi MicroPython 官方文件下載 Pico W 專用的 UF2 韌體。
- 按住 Pico W 的
BOOTSEL按鈕,再插入 USB 資料線;電腦會出現RPI-RP2磁碟。 - 將下載的 UF2 檔拖曳到
RPI-RP2,Pico W 會自動重新啟動。 - 開啟 Thonny,從右下角選擇「MicroPython (Raspberry Pi Pico)」,並選擇 Pico W 對應的 COM Port。
- 貼上本文程式,按
Ctrl + S,選擇儲存到「Raspberry Pi Pico」,檔名務必為main.py。
main.py,故事機就不必再接著電腦才能運作。五、完整 MicroPython 程式
本程式加入「七筆距離中位數濾波」與「連續五秒離場確認」,可減少 HC-SR04 因衣物、角度或背景反射造成的重複迎賓與致謝。
from machine import Pin, UART, time_pulse_us
import time
# 樹藝AI故事機:Pico W + JQ6500 + HC-SR04 + 三個按鈕
# JQ6500:Pico W GP0 接 JQ6500 RX
# 第一版只傳送播放命令,JQ6500 TX 可不接
jq6500 = UART(0, baudrate=9600, tx=Pin(0))
# HC-SR04 超音波感測器
trig = Pin(14, Pin.OUT)
echo = Pin(15, Pin.IN)
# 三個按鈕:另一端皆接 GND
button_story = Pin(10, Pin.IN, Pin.PULL_UP) # 作品故事
button_material = Pin(11, Pin.IN, Pin.PULL_UP) # 使用材料
button_author = Pin(12, Pin.IN, Pin.PULL_UP) # 設計作者
# 距離與時間設定
ENTER_DISTANCE = 80 # 小於 80 cm:有人靠近
LEAVE_DISTANCE = 120 # 大於 120 cm:可能離開
WELCOME_WAIT_MS = 6000 # 歡迎詞播放後,等待 6 秒再開放按鈕
LEAVE_CONFIRM_MS = 5000 # 連續 5 秒在離場距離外,才判定離開
BUTTON_DEBOUNCE_MS = 500 # 按鍵防彈跳時間
# 最近 7 筆距離使用中位數濾波,降低錯誤反射的影響
FILTER_SIZE = 7
distance_buffer = []
# 系統狀態變數
person_is_here = False
last_button_time = 0
button_ready_time = 0
far_since = None
def play_file(number):
"""播放 JQ6500 microSD 卡內指定編號的 MP3。"""
command = bytes([0x7E, 0x04, 0x03, 0x00, number, 0xEF])
jq6500.write(command)
print("播放音檔:", number)
def get_distance():
"""讀取 HC-SR04 距離;量測失敗時回傳 999。"""
trig.value(0)
time.sleep_us(2)
trig.value(1)
time.sleep_us(10)
trig.value(0)
try:
duration = time_pulse_us(echo, 1, 30000)
if duration < 0:
return 999
return duration * 0.0343 / 2
except OSError:
return 999
def get_filtered_distance():
"""保留最近 7 筆讀值,取中位數作為穩定距離。"""
raw_distance = get_distance()
distance_buffer.append(raw_distance)
if len(distance_buffer) > FILTER_SIZE:
distance_buffer.pop(0)
sorted_distances = sorted(distance_buffer)
filtered_distance = sorted_distances[len(sorted_distances) // 2]
return raw_distance, filtered_distance
def check_buttons():
"""參觀者在場時,按鈕可選擇播放內容。"""
global last_button_time
now = time.ticks_ms()
if time.ticks_diff(now, last_button_time) < BUTTON_DEBOUNCE_MS:
return
if button_story.value() == 0:
print("選擇:作品故事")
play_file(1) # 001.mp3
last_button_time = now
elif button_material.value() == 0:
print("選擇:使用材料")
play_file(3) # 003.mp3
last_button_time = now
elif button_author.value() == 0:
print("選擇:設計作者")
play_file(2) # 002.mp3
last_button_time = now
# 開機程序:004.mp3
print("樹藝AI故事機啟動中……")
time.sleep(2)
play_file(4)
print("系統已就緒,等待參觀者靠近……")
while True:
raw_distance, distance = get_filtered_distance()
now = time.ticks_ms()
# 開發測試時可取消下一行註解,觀察距離變化
# print("原始:", raw_distance, "cm;濾波後:", distance, "cm")
# 第一次偵測到有人靠近:播放 005.mp3 歡迎詞
if distance < ENTER_DISTANCE and not person_is_here:
print("參觀者靠近:", distance, "cm")
play_file(5)
person_is_here = True
far_since = None
button_ready_time = time.ticks_add(now, WELCOME_WAIT_MS)
if person_is_here:
# 歡迎詞播放一段時間後,開放三個按鈕
if time.ticks_diff(now, button_ready_time) >= 0:
check_buttons()
# 在範圍內:取消離場判斷
if distance < LEAVE_DISTANCE:
far_since = None
# 在範圍外:開始連續離場確認
else:
if far_since is None:
far_since = now
elif time.ticks_diff(now, far_since) >= LEAVE_CONFIRM_MS:
print("參觀者已離開:", distance, "cm")
play_file(6) # 006.mp3:謝謝參觀
person_is_here = False
far_since = None
time.sleep(1)
time.sleep_ms(150)
六、microSD 音檔與語音文字稿
| 音檔 | 功能 | 觸發方式 |
|---|---|---|
| 001.mp3 | 作品故事 | 按下 GP10 按鈕 |
| 002.mp3 | 設計作者 | 按下 GP12 按鈕 |
| 003.mp3 | 使用材料 | 按下 GP11 按鈕 |
| 004.mp3 | 系統開機訊息 | 故事機通電啟動 |
| 005.mp3 | 歡迎參觀樹藝工坊 | 參觀者靠近 |
| 006.mp3 | 謝謝參觀 | 參觀者連續離開感測範圍五秒後 |
001.mp3作品故事|《貓頭鷹博士的月光工坊》
很久以前,森林邊有一位愛思考的貓頭鷹博士。他白天站在老樹上,仔細觀察落葉、果實和被風吹落的小樹枝;夜晚,便飛回月光照亮的小工坊,把自然送來的禮物做成有生命的作品。
有一天,森林裡的小動物問博士:「這些不起眼的木頭,真的能變成好看的東西嗎?」博士笑著說:「每一片木頭,都記得陽光、雨水和風吹過的故事。只要用心傾聽,就能找到它的新模樣。」
於是,博士選了一段圓圓的樹枝當身體,用深色木片做成翅膀,再加上兩顆明亮的大眼睛。慢慢地,一隻精神十足的貓頭鷹出現在大家面前。牠不是普通的貓頭鷹,而是守護森林故事的小博士。
從那天起,貓頭鷹博士提醒每一位來訪的人:不要輕易丟棄自然材料,也不要忘記身邊的老樹與土地。只要願意觀察、珍惜與創作,平凡的材料,也能長出新的故事。
002.mp3設計作者|樹藝工坊/李永謨工藝師
本作品由樹藝工坊的李永謨工藝師設計創作。李永謨老師自一九八九年起投入樹藝自然創作,經常從樹皮、樹葉、葉脈、果實與種子之中,細心尋找最適合創作的自然材料;有些植物甚至只能運用果實來呈現特色。
在李老師眼中,樹皮、葉子與果實雖然不完美,卻都有自己獨特的形狀、色彩、紋理與生命價值。透過巧妙的雙手與細膩的觀察,他將原本容易被忽略的自然素材,創造成栩栩如生的昆蟲、動物與花卉。
樹藝創作不只是手作藝術,更是一種尊重自然的方式。李老師讓看似無用的素材重獲新生,也帶領我們看見樹的另一面生命價值,並從創作中體悟珍惜、感恩與生命的真諦。
003.mp3使用材料
這件貓頭鷹博士作品,主要使用森林與校園中常見的自然素材創作。圓圓的身體是一顆海檬果;頭頂和翅膀使用三片玉米雞羽毛,展現貓頭鷹蓬鬆的羽毛感。
大大的眼睛與臉部輪廓,搭配麻棟、蓮蕉、羊蹄甲和青剛櫟等種子與果實,讓作品有了靈活的表情。作品底座使用一片大的樟木片,小樟木片做為裝飾;兩段樟木枝則像是貓頭鷹停棲的樹枝。最後,以一圈鋁線固定與連接各個部件。
這些材料原本都是自然掉落的果實、種子、羽毛與枝條。透過觀察、整理與創作,它們重新變成守護森林故事的貓頭鷹博士。提醒大家:海檬果不可食用,製作時請由老師或大人協助處理。
004.mp3系統開機訊息
您好,樹藝AI故事機已經啟動。歡迎來到樹藝工坊,邀請您一起認識自然素材所創造的美麗作品。
005.mp3歡迎參觀樹藝工坊
歡迎參觀樹藝工坊。您現在看到的是由自然果實、種子、樹皮、樹葉與羽毛創作而成的作品。請按下按鈕,選擇聆聽作品故事、使用材料或設計作者。讓我們一起發現大自然的另一種生命價值。
006.mp3謝謝參觀
謝謝您的參觀。願您帶著一雙善於觀察的眼睛,重新看見身邊的樹木、葉片、果實與種子。期待您再次來到樹藝工坊,聆聽更多大自然的故事。
七、程式測試技巧:先分開,再整合
故事機包含語音、感測與按鈕三種功能。開發時最有效的原則是:一次只測試一個元件。若一開始就執行完整程式,聲音、距離與按鈕同時出問題時,往往很難快速找出原因。
1. 建議的測試順序
| 階段 | 要確認的事情 | 成功判斷 |
|---|---|---|
| 第一階段:Pico W | Thonny 能連線、程式能儲存到 Pico W。 | Shell 出現 MPY: soft reboot 後,能看到 print() 訊息。 |
| 第二階段:JQ6500 | 供電、喇叭、UART 接線與 microSD 音檔編號正確。 | 可個別播放 001 至 006 等音檔。 |
| 第三階段:按鈕 | 三個 GPIO 都能讀到按下與放開。 | 按下不同按鈕,Shell 顯示正確的功能名稱。 |
| 第四階段:超音波 | 量測距離與實際距離大致相符,且不會頻繁跳動。 | 靠近時讀值變小,離開時讀值變大。 |
| 第五階段:整合 | 迎賓、按鈕導覽、離場致謝能依序運作。 | 每次靠近只迎賓一次;真正離開後才播放致謝詞。 |
2. JQ6500 單獨播放測試
先確認 JQ6500、喇叭與 microSD 音檔完全正常,再與感測器整合。將下列短程式存到 Pico W 執行,故事機會每四秒輪流播放開機、歡迎與致謝音檔。
from machine import Pin, UART
import time
jq6500 = UART(0, baudrate=9600, tx=Pin(0))
def play_file(number):
command = bytes([0x7E, 0x04, 0x03, 0x00, number, 0xEF])
jq6500.write(command)
print("播放音檔:", number)
while True:
play_file(4) # 系統開機訊息
time.sleep(4)
play_file(5) # 歡迎參觀
time.sleep(4)
play_file(6) # 謝謝參觀
time.sleep(4)
3. 三個按鈕的讀值測試
在還沒有接 JQ6500 前,也可先用 Shell 訊息確認按鈕接線。按鈕另一端必須接 GND;按下時,內建上拉的 GPIO 讀值會變為 0。
from machine import Pin
import time
button_story = Pin(10, Pin.IN, Pin.PULL_UP)
button_material = Pin(11, Pin.IN, Pin.PULL_UP)
button_author = Pin(12, Pin.IN, Pin.PULL_UP)
while True:
if button_story.value() == 0:
print("按下:作品故事(GP10)")
time.sleep_ms(300)
elif button_material.value() == 0:
print("按下:使用材料(GP11)")
time.sleep_ms(300)
elif button_author.value() == 0:
print("按下:設計作者(GP12)")
time.sleep_ms(300)
若按下按鈕沒有訊息,先不要修改主程式;請檢查按鈕兩腳是否接在正確的兩側、是否真的接到 GND,以及 GPIO 編號是否與實際接線一致。
4. 超音波感測器單獨測試
先讓 HC-SR04 只負責顯示距離。手持物品靠近感測器,距離數字應逐漸變小;移開後數字應逐漸變大。這一步可協助確認 TRIG、ECHO、分壓電阻與共地是否正確。
from machine import Pin, time_pulse_us
import time
trig = Pin(14, Pin.OUT)
echo = Pin(15, Pin.IN)
def get_distance():
trig.value(0)
time.sleep_us(2)
trig.value(1)
time.sleep_us(10)
trig.value(0)
try:
duration = time_pulse_us(echo, 1, 30000)
if duration < 0:
return 999
return duration * 0.0343 / 2
except OSError:
return 999
while True:
print("距離:", round(get_distance(), 1), "cm")
time.sleep_ms(500)
5. 善用 Thonny Shell 找問題
- 開發時保留
print(),例如印出「播放音檔」、「按鈕名稱」與「原始/濾波後距離」。 - 測試成功後,再將頻繁執行的距離列印註解掉,避免 Shell 訊息太多、影響觀察。
- 每次只修改一小段程式,儲存、執行、確認結果,再進行下一步;不要一次改接線與程式邏輯。
- 出現錯誤時,先看 Shell 最後一行的錯誤類型與行號;例如
OSError常與感測器量測或接線有關。 - 看到
MPY: soft reboot是 Thonny 重新執行 Pico W 程式的正常訊息,不是硬體故障。
6. 整合測試紀錄表
| 測試項目 | 預期結果 | 測試結果 |
|---|---|---|
| 通電開機 | 播放 004.mp3 系統開機訊息 | □ 通過 □ 待調整 |
| 有人靠近 | 只播放一次 005.mp3 歡迎詞 | □ 通過 □ 待調整 |
| GP10 按鈕 | 播放 001.mp3 作品故事 | □ 通過 □ 待調整 |
| GP11 按鈕 | 播放 003.mp3 使用材料 | □ 通過 □ 待調整 |
| GP12 按鈕 | 播放 002.mp3 設計作者 | □ 通過 □ 待調整 |
| 參觀者離開 | 連續約五秒離場後播放 006.mp3 | □ 通過 □ 待調整 |
八、測試與展示前檢查
- 確認 Pico W、JQ6500、HC-SR04 的 GND 全部共地。
- 確認 HC-SR04 的 ECHO 已通過分壓電阻才連到 GP15。
- 確認 microSD 卡使用 FAT32,音檔以
001.mp3到006.mp3連續命名並依序放入。 - 通電後確認播放 004.mp3;靠近後確認只播放一次 005.mp3。
- 測試 GP10、GP11、GP12,確認分別播放作品故事、使用材料、設計作者。
- 離開感測範圍後,確認約五秒才播放 006.mp3,避免感測值偶爾跳動造成重複播放。
001.mp3 至 006.mp3,中間不要缺號。日後若新增內容,可接續使用 007.mp3、008.mp3,並同步修改程式中的播放編號。
沒有留言:
張貼留言