2026年9月6日 星期日

樹藝AI故事機:讓自然素材作品自己說故事

樹藝AI故事機:讓自然素材作品自己說故事

Raspberry Pi Pico W × JQ6500 × HC-SR04 × MicroPython × Thonny

樹藝作品不只是靜態展示品。透過 Raspberry Pi Pico W、JQ6500 語音播放模組、超音波感測器與三個按鈕,我們可以製作一臺會主動迎賓、能讓參觀者自行選擇導覽內容、並在離場時說謝謝的「樹藝AI故事機」。

完成後的互動:作品開機會播放系統訊息;參觀者靠近時播放歡迎詞;按下按鈕可聆聽作品故事、使用材料或設計作者;真正離開後,故事機會播放「謝謝參觀」。

一、樹藝AI故事機的互動流程

1開機
播放系統訊息
2靠近
自動迎賓
3選擇
按下導覽按鈕
4聆聽
播放介紹內容
5離開
播放謝謝參觀

二、需要準備的硬體

元件用途說明
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 WJQ6500用途
VBUS 5V(實體 Pin 40)VCCJQ6500 供電
GND(實體 Pin 38 或任一 GND)GND共地,必接
GP0/UART0 TX(實體 Pin 1)RXPico W 傳送播放指令
不接TX第一版不讀取 JQ6500 狀態,避免 5V 訊號直接進入 Pico W。
SPK+、SPK-喇叭正、負極喇叭兩端直接接 JQ6500,不可接到 GND

2. HC-SR04 超音波感測器

Pico WHC-SR04用途
VBUS 5VVCC感測器供電
GNDGND共地
GP14(實體 Pin 19)TRIG發出超音波觸發訊號
GP15(實體 Pin 20)ECHO(經分壓)接收距離回傳訊號
重要安全提醒:HC-SR04 的 ECHO 是約 5V 輸出,不能直接接 Pico W 的 GP15。請用 1kΩ 與 2kΩ 電阻做分壓,保護 Pico W 的 3.3V GPIO。
HC-SR04 ECHO ── 1kΩ ──┬── Pico W GP15 │ 2kΩ │ GND

3. 三個導覽按鈕

功能Pico W GPIO播放音檔
作品故事GP10001.mp3
使用材料GP11003.mp3
設計作者GP12002.mp3

每一個按鈕的一端接對應 GPIO,另一端接 GND。程式使用 Pico W 的內建上拉電阻,因此不需再外接上拉電阻。

四、下載與使用 Thonny 開發 MicroPython

Thonny 是適合初學者的 Python 開發工具,可直接把程式寫入 Pico W,也能在 Shell 視窗查看感測器距離與除錯訊息。

  1. Thonny 官方網站下載並安裝 Windows 版本。
  2. 第一次使用 Pico W 時,到 Raspberry Pi MicroPython 官方文件下載 Pico W 專用的 UF2 韌體。
  3. 按住 Pico W 的 BOOTSEL 按鈕,再插入 USB 資料線;電腦會出現 RPI-RP2 磁碟。
  4. 將下載的 UF2 檔拖曳到 RPI-RP2,Pico W 會自動重新啟動。
  5. 開啟 Thonny,從右下角選擇「MicroPython (Raspberry Pi Pico)」,並選擇 Pico W 對應的 COM Port。
  6. 貼上本文程式,按 Ctrl + S,選擇儲存到「Raspberry Pi Pico」,檔名務必為 main.py
為什麼要存成 main.py?因為 Pico W 每次通電開機時,會自動執行內部儲存空間中的 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 WThonny 能連線、程式能儲存到 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)
若沒有聲音,依序檢查:JQ6500 的 VCC 與 GND、喇叭是否接在 SPK+ 與 SPK-、Pico W GP0 是否接到 JQ6500 RX、microSD 是否為 FAT32,以及音檔是否為 MP3 格式與正確編號。

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)
超音波測試提醒:HC-SR04 對柔軟衣物、斜面、細小物件與遠方牆面,可能出現不同反射距離。正式程式使用七筆中位數濾波與五秒離場確認,就是為了避免單次量測跳動而重複播放語音。

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□ 通過 □ 待調整

八、測試與展示前檢查

  1. 確認 Pico W、JQ6500、HC-SR04 的 GND 全部共地。
  2. 確認 HC-SR04 的 ECHO 已通過分壓電阻才連到 GP15。
  3. 確認 microSD 卡使用 FAT32,音檔以 001.mp3006.mp3 連續命名並依序放入。
  4. 通電後確認播放 004.mp3;靠近後確認只播放一次 005.mp3。
  5. 測試 GP10、GP11、GP12,確認分別播放作品故事、使用材料、設計作者。
  6. 離開感測範圍後,確認約五秒才播放 006.mp3,避免感測值偶爾跳動造成重複播放。
音檔管理建議:JQ6500 對 microSD 的檔案順序較敏感。建議先將記憶卡格式化為 FAT32,再依序放入 001.mp3006.mp3,中間不要缺號。日後若新增內容,可接續使用 007.mp3008.mp3,並同步修改程式中的播放編號。

沒有留言:

張貼留言