2026年9月6日 星期日

從「燈號優先序」到「訊號並列」:樹藝AI故事機 V1.2 的人機協作測試紀錄

從「燈號優先序」到「訊號並列」:樹藝AI故事機 V1.2 的人機協作測試紀錄

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

樹藝AI故事機的目標,是讓自然素材作品能主動迎接參觀者,並以語音介紹作品故事、使用材料與設計作者。這次開發最值得記錄的,並不是「AI 一次產生了正確程式」,而是透過實體接線、量測與反覆測試,修正了 AI 最初對燈號邏輯的假設,完成可穩定運作的 V1.2。

本次核心收穫:程式設計不能只看「看起來合理」的控制邏輯。當硬體中的訊號屬於不同層次時,不能強迫它們排成單一優先序;必須由人透過實測,辨識哪些狀態應該互斥、哪些訊號應該並列呈現。

一、樹藝AI故事機的功能

互動情境系統行為音檔
作品通電開機播放系統開機訊息004.mp3
參觀者靠近播放歡迎詞,引導選擇故事、材料或作者005.mp3
按下作品故事按鈕播放貓頭鷹博士的作品故事001.mp3
按下設計作者按鈕播放樹藝工坊李永謨工藝師介紹002.mp3
按下使用材料按鈕播放自然素材與創作材料說明003.mp3
參觀者真正離開連續五秒未偵測到近距離物體後,播放致謝詞006.mp3

二、從「推測」到「實測」:BUSY 訊號帶來的轉折

JQ6500 模組可由 Pico W 的 UART 送出播放命令,但如果沒有播放狀態回授,程式只能以「預估秒數」判斷音檔是否播完。為了讓燈號真正反映播放情況,本次將 JQ6500 的 BUSY 腳位接到 Pico W 的 GP20。

實測項目量測結果程式判斷
JQ6500 播放中BUSY 約 2.4V1,代表正在播放
JQ6500 未播放BUSY 約 0V0,代表未播放
BUSY 原始讀值偶爾在 0、1 間跳動需連續穩定 300 ms 才更新狀態

因此程式加入兩道保護:

  1. 播放啟動保護時間:送出播放命令後,等待 500 ms,避免 BUSY 尚未切換就被誤判為播放結束。
  2. BUSY 去彈跳:原始讀值必須連續穩定 300 ms,程式才接受新的播放狀態。

三、AI 初版邏輯的問題:所有燈號被迫只能選一個

AI 初版建議以「紅燈異常 > 黃燈系統語音 > 藍燈作品內容 > 綠燈待機」的優先序處理燈號。這種寫法適合每一種狀態都完全互斥的系統,但在實際硬體中,紅燈代表的是感測器診斷訊號,黃燈與藍燈代表的是語音互動狀態;兩者並不屬於同一個維度。

初版:單一優先序

程式一旦判定紅燈優先,就可能關閉黃燈、藍燈或綠燈。這雖然讓「某一刻只有一顆燈亮」,但會把不同來源的資訊混在一起,現場不容易理解真正狀態。

實測修正:訊號並列

紅燈獨立表示感測器異常;播放狀態再決定黃燈、藍燈或綠燈。如此可同時看見「系統正在播音」與「感測器出現異常」,不會互相遮蔽。

修正後的核心概念是:

# 紅燈:獨立的診斷訊號
if sensor_error:
    led_error.value(1)
else:
    led_error.value(0)

# 黃、藍、綠燈:依播放狀態顯示
if is_audio_playing():
    ...
else:
    ...
人機協作的關鍵:AI 可以快速提出架構、程式與可能的修正方向;但「紅燈是否應與其他燈號並列」並不是純粹語法問題,而是現場展示者對使用情境、可讀性與硬體行為的判斷。這個判斷必須回到實際作品前完成。

四、實測流程:從單元測試走向完整互動

測試階段做法得到的結果
JQ6500 語音測試依序播放 001 至 006 音檔。確認 microSD 必須連續命名,避免缺少音檔造成指定播放異常。
BUSY 訊號量測播放與停止時,以電表觀察 BUSY 電壓。確認播放為 2.4V、停止為 0V,建立播放狀態判斷基礎。
超音波測試觀察原始值與七筆中位數濾波後距離。可排除偶發的遠方反射值,減少重複迎賓與離場誤判。
近距離桌上測試設定靠近 10 cm、離開 25 cm。可在桌上快速重複觸發,不必每次走到正式展示距離。
LED 整合測試觀察綠、黃、藍、紅燈與 BUSY 的對應。發現單一優先序容易誤判,改為診斷燈與互動燈並列處理。

五、V1.2 燈號定義

LED接腳意義
綠燈GP16待機中,可等待參觀者靠近或選擇內容。
黃燈GP17播放開機訊息、歡迎詞或謝謝參觀。
藍燈GP18播放作品故事、使用材料或設計作者。
紅燈GP19超音波連續五次量測失敗的診斷提示,可與播放狀態並列。

六、測試成功的 V1.2 程式

以下程式使用 Pico W 的 GP20 讀取 JQ6500 BUSY,並加入 BUSY 穩定判斷、超音波中位數濾波、近距離測試模式,以及修正後的 LED 並列邏輯。

from machine import Pin, UART, time_pulse_us
import time

jq6500 = UART(0, baudrate=9600, tx=Pin(0))
busy = Pin(20, Pin.IN)

trig = Pin(14, Pin.OUT)
echo = Pin(15, Pin.IN)

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)

led_ready = Pin(16, Pin.OUT)
led_welcome = Pin(17, Pin.OUT)
led_playing = Pin(18, Pin.OUT)
led_error = Pin(19, Pin.OUT)

FILE_STORY = 1
FILE_AUTHOR = 2
FILE_MATERIAL = 3
FILE_BOOT = 4
FILE_WELCOME = 5
FILE_GOODBYE = 6

TEST_MODE = True

if TEST_MODE:
    ENTER_DISTANCE = 10
    LEAVE_DISTANCE = 25
else:
    ENTER_DISTANCE = 80
    LEAVE_DISTANCE = 120

LEAVE_CONFIRM_MS = 5000
BUTTON_DEBOUNCE_MS = 500
PLAY_START_LOCK_MS = 500
BUSY_DEBOUNCE_MS = 300
FILTER_SIZE = 7
DEBUG = False

distance_buffer = []
person_is_here = False
far_since = None
last_button_time = 0

current_audio_type = "idle"
audio_start_lock_until = 0

busy_raw = busy.value()
busy_stable = busy_raw
busy_changed_at = time.ticks_ms()

sensor_fail_count = 0
sensor_error = False


def time_is_active(end_time):
    return time.ticks_diff(time.ticks_ms(), end_time) < 0


def update_busy_state():
    global busy_raw, busy_stable, busy_changed_at

    now = time.ticks_ms()
    reading = busy.value()

    if reading != busy_raw:
        busy_raw = reading
        busy_changed_at = now

    if (
        busy_raw != busy_stable
        and time.ticks_diff(now, busy_changed_at) >= BUSY_DEBOUNCE_MS
    ):
        busy_stable = busy_raw

    return busy_stable


def is_audio_playing():
    if time_is_active(audio_start_lock_until):
        return True

    return update_busy_state() == 1


def is_managed_audio_playing():
    if current_audio_type == "idle":
        return False

    return is_audio_playing()


def update_leds():
    global current_audio_type

    # 紅燈是獨立診斷訊號,不以優先序排除其他燈號
    if sensor_error:
        led_error.value(1)
    else:
        led_error.value(0)

    # 黃、藍、綠燈表示目前互動狀態
    if is_audio_playing():
        led_ready.value(0)

        if current_audio_type in (
            "boot",
            "welcome",
            "goodbye"
        ):
            led_welcome.value(1)
        else:
            led_playing.value(1)

    else:
        led_playing.value(0)
        led_welcome.value(0)
        led_ready.value(1)


def play_file(number, audio_type):
    global current_audio_type, audio_start_lock_until

    command = bytes([0x7E, 0x04, 0x03, 0x00, number, 0xEF])
    jq6500.write(command)

    current_audio_type = audio_type
    audio_start_lock_until = time.ticks_add(
        time.ticks_ms(),
        PLAY_START_LOCK_MS
    )

    print("播放音檔:", number)


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


def get_filtered_distance():
    global sensor_fail_count, sensor_error

    raw_distance = get_distance()

    if raw_distance >= 999:
        sensor_fail_count += 1
    else:
        sensor_fail_count = 0

    sensor_error = sensor_fail_count >= 5

    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 is_managed_audio_playing():
        return

    if time.ticks_diff(now, last_button_time) < BUTTON_DEBOUNCE_MS:
        return

    if button_story.value() == 0:
        print("選擇:作品故事")
        play_file(FILE_STORY, "content")
        last_button_time = now

    elif button_material.value() == 0:
        print("選擇:使用材料")
        play_file(FILE_MATERIAL, "content")
        last_button_time = now

    elif button_author.value() == 0:
        print("選擇:設計作者")
        play_file(FILE_AUTHOR, "content")
        last_button_time = now


print("樹藝AI故事機 V1.2 啟動中……")
play_file(FILE_BOOT, "boot")

while is_managed_audio_playing():
    update_leds()
    time.sleep_ms(100)

update_leds()
print("系統已就緒,等待參觀者靠近……")


while True:
    raw_distance, distance = get_filtered_distance()
    now = time.ticks_ms()

    update_leds()

    if DEBUG:
        print(
            "原始:", raw_distance,
            "cm;濾波後:", distance,
            "cm;BUSY原始:", busy.value(),
            ";BUSY穩定:", busy_stable
        )

    if sensor_error:
        time.sleep_ms(150)
        continue

    if (
        distance < ENTER_DISTANCE
        and not person_is_here
        and not is_managed_audio_playing()
    ):
        print("參觀者靠近:", distance, "cm")
        person_is_here = True
        far_since = None
        play_file(FILE_WELCOME, "welcome")

    if person_is_here:
        if not is_managed_audio_playing():
            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")
                    person_is_here = False
                    far_since = None
                    play_file(FILE_GOODBYE, "goodbye")

    time.sleep_ms(150)

七、可複製的協作方法

  1. 先讓 AI 建立初版:快速完成接線表、程式架構、可能的除錯方向。
  2. 分段測試硬體:先測音檔、再測按鈕、再測超音波,最後才整合燈號。
  3. 量測而不猜測:以電表確認 BUSY 在播放時為 2.4V、停止時為 0V,再寫程式判斷。
  4. 記錄異常現象:例如 BUSY 讀值跳動、綠燈閃爍、超音波量到背景反射。
  5. 修正模型,而非只修補結果:這次不是單純改亮燈順序,而是重新定義「診斷訊號」與「互動狀態」應採並列處理。
  6. 以可運作版本收斂:將測試成功的程式、音檔編號、接線與測試設定完整保存。
結語:樹藝AI故事機讓自然素材作品可以主動說故事;而這次 V1.2 的測試也說明,AI 的價值不在於取代人的判斷,而在於加速提出可測試的方案。真正讓作品變得可靠的,是 AI 的快速生成、人的現場觀察,以及一次次回到硬體與使用情境的驗證。

樹藝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,並同步修改程式中的播放編號。

2026年9月4日 星期五

雲端網路的核心概念:從智慧養殖到 AI 互動導覽

雲端網路的核心概念:從智慧養殖到 AI 互動導覽

當我們使用手機看影片、上傳照片、網路購物或遠端控制家電時,背後都離不開「雲端網路」。 雲端網路不只是把資料放到遠端伺服器,更是透過網路把人、設備、資料、服務與人工智慧連結起來, 讓系統能夠即時互動、彈性擴充與集中管理。

一、什麼是雲端網路?

雲端網路是指透過網際網路,使用遠端資料中心提供的運算、儲存、資料庫、應用程式及網路服務。 使用者不必自己購買大量伺服器或網路設備,只要透過電腦、手機、感測器或機器人連上網路, 就能使用雲端上的資源。

一句話理解:
雲端網路就是把「運算、儲存、網路與智慧服務」放到雲端,讓各地的使用者與設備可以隨時連線使用。

二、雲端網路的核心概念

  1. 虛擬化(Virtualization)
    將一台實體伺服器、硬碟或網路設備,切分成多個可獨立使用的虛擬資源,例如虛擬機器、虛擬硬碟與虛擬網路。
  2. 軟體定義網路(SDN, Software Defined Networking)
    將網路設備的管理與決策集中到軟體控制器。管理者可以用軟體調整資料傳輸路徑、頻寬與安全規則, 讓網路管理更有彈性。
  3. 網路功能虛擬化(NFV, Network Function Virtualization)
    原本要使用專用硬體才能完成的路由器、防火牆、負載平衡器等功能,改以軟體方式在一般伺服器上執行, 可降低成本,也更容易擴充。
  4. 基礎設施即代碼(IaC, Infrastructure as Code)
    用程式或設定檔建立伺服器、資料庫、網路與防火牆規則。這樣可以重複部署、保留版本紀錄, 並減少人工設定造成的錯誤。
  5. 彈性擴充與負載平衡
    當使用者增加時,雲端可以自動增加伺服器資源;當流量下降時,再減少資源以節省成本。 負載平衡則把大量使用者的請求分配到多台伺服器。
  6. 資安、身分驗證與資料保護
    雲端系統會利用帳號權限、防火牆、加密、VPN、網路分區與備份機制, 保護資料與設備,避免未授權使用者存取。

三、雲端網路在生活與產業上的應用

領域 應用例子 雲端網路的功能
智慧家庭 遠端控制冷氣、攝影機、門鎖與掃地機器人 讓家電與手機透過雲端交換資料與控制指令。
串流影音 YouTube、Netflix、線上直播與雲端遊戲 利用雲端伺服器、CDN 與負載平衡,提供大量使用者穩定服務。
線上學習 Google Classroom、Moodle、視訊課程 集中提供教材、作業、測驗、影片與學習紀錄。
電子商務 網路商店、行動支付、物流追蹤 整合商品、訂單、庫存、金流與配送資訊。
智慧製造 機台監控、品質檢測、預測維護 收集設備資料,透過雲端 AI 分析異常與預估故障。
智慧農漁業 水質、土壤、氣象、影像與設備控制 讓感測器即時上傳資料,並由手機或電腦遠端監控。

四、三個智慧生活與地方創生應用案例

案例一:水井村智慧養殖

在養殖現場,可利用溶氧、水溫、pH 值等感測器蒐集魚塭資料,再透過 HUB8735、 Wi-Fi、4G/5G 或 LoRa 網路傳送到雲端平台。養殖戶只要打開手機,就能即時查看水質狀況。

當溶氧過低或水溫異常時,雲端平台可以自動發出告警通知,並進一步連動打氣機或抽水設備。 若累積足夠資料,還能運用 AI 分析水質變化,協助養殖戶及早處理問題。

核心價值:讓養殖管理從「靠經驗巡池」走向「即時監測、遠端管理與智慧決策」。

案例二:水井三寶智慧互動導覽平台

水井村可將烏龜、白馬與姻緣花等地方文化故事,建置成雲端智慧導覽平台。 遊客掃描 QR Code、使用手機網頁或操作互動螢幕後,即可取得故事、照片、語音、影片與地圖資訊。

平台也可結合 AI 導覽 Agent。當遊客詢問「姻緣樹有多久歷史?」、 「下一個景點怎麼走?」或「水井三寶代表什麼意義?」時, AI Agent 可根據地方知識庫即時回答,成為不受時間限制的數位導覽員。

核心價值:讓地方文化能被數位保存、持續更新,並以互動方式傳遞給更多遊客與下一代。

案例三:Q-Robot AI 結合 AI Agent

Q-Robot 可結合麥克風、喇叭、螢幕、按鈕、燈光與感測器,成為能與人互動的智慧機器人。 當孩童、長者或遊客向 Q-Robot 說話或按下按鈕時,Q-Robot 可透過雲端網路連接 AI Agent 與知識庫, 理解問題後,再以語音、文字、燈光或動作回應。

例如,Q-Robot 可以扮演下列角色:

  • 地方文化小導遊:介紹水井三寶與村庄故事。
  • 兒童學習夥伴:帶領孩子完成程式設計、環保、食農與 AI 體驗任務。
  • 長者陪伴助手:以較慢的語速聊天、播放懷舊故事,提醒社區活動。
  • 活動任務引導員:依照參與者的回答與活動進度,安排下一個互動關卡。
核心價值:讓機器人不只是播放指令,而是能連接地方知識、理解問題並提供個人化互動的 AI 夥伴。

五、三個案例的比較

案例 主要對象 雲端網路的角色 AI 的角色
水井村智慧養殖 養殖戶 傳送感測資料、遠端控制設備 分析水質與異常預警
水井三寶智慧互動導覽 遊客、社區居民 提供故事、地圖、影音與使用紀錄 回答地方文化與導覽問題
Q-Robot AI Agent 兒童、長者、學習者 連接機器人、知識庫與後台服務 理解提問、規劃任務、產生個人化回應

六、結語

雲端網路的價值,不只是把資料存放在遠端,而是把現場設備、使用者、資料平台與 AI 智慧服務連接起來。 從水井村智慧養殖的水質監測,到水井三寶的文化導覽,再到 Q-Robot 的 AI 互動陪伴, 都說明了雲端網路可以成為地方創生、智慧教育與永續生活的重要基礎。

善用雲端網路,讓地方的故事、產業與學習,連結成更智慧、更永續的未來。

2026年9月3日 星期四

不是AI寫完才測: 用 Red、Green、Refactor 學會可靠的 Python 程式設計

TEST-DRIVEN DEVELOPMENT

不是AI寫完才測:
用 Red、Green、Refactor 學會可靠的 Python 程式設計

先定義成功,再讓程式與AI一起追上來。

生成式AI可以快速寫出Python程式,但「看起來能執行」不等於「真的符合需求」。在大三的Python程式實習中,我們要學習一種更可靠的工作方式:先寫測試,再寫程式,最後整理程式。這就是測試驅動開發(Test-Driven Development,簡稱TDD)。

RED 先寫會失敗的測試
GREEN 用最小修改通過
REFACTOR 整理而不改行為
一句話理解TDD: 不是AI寫完才測,而是測試牽著AI走。先說清楚「成功是什麼」,再要求程式做到。

一、什麼是 Red、Green、Refactor?

1. Red:先寫一個會失敗的測試

Red代表「紅燈」。在還沒有功能程式之前,先把我們期待的結果寫成測試。因為功能尚未完成,測試理應失敗;這正好證明測試真的有在檢查需求。

例如,我們要撰寫一個函式,計算一組成績的平均值。先定義成功條件:輸入[80, 90, 100]時,結果應該是90

RED|先定義成功
def test_average_of_three_scores(): assert average([80, 90, 100]) == 90

此時average()尚未建立,執行測試會失敗。這不是挫折,而是我們明確知道:下一步要完成的是什麼。

2. Green:用最少的程式碼讓測試通過

Green代表「綠燈」。現在才開始寫功能程式,目標是讓剛才的測試通過。此階段不必急著追求最漂亮、最完整的寫法,而是先讓需求正確實現。

GREEN|讓測試通過
def average(scores): return sum(scores) / len(scores)

再執行一次測試,若結果通過,就代表程式至少符合目前定義的基本需求。

3. Refactor:整理程式,但不能改變原本行為

Refactor代表「重構」。當測試已經通過,我們可以安心改善程式的可讀性與可維護性,例如取清楚的名稱、抽出重複程式、補上輸入檢查或撰寫註解。

REFACTOR|程式更清楚、更可靠
def average(scores): """計算成績平均;空串列不允許計算。""" if not scores: raise ValueError("成績不可為空") total_score = sum(scores) student_count = len(scores) return total_score / student_count

重構後,原本的測試仍必須通過。也就是說,程式的內部結構可以變好,但對使用者而言,正確行為不能被改壞。

二、為什麼要先測試,再請AI寫程式?

如果直接對AI說「幫我寫一個平均成績的程式」,AI可能產生看似正確的答案;但它不一定知道您如何處理空白資料、文字輸入、缺考成績或小數點。若先把需求寫成測試,就能把模糊的想法變成可驗證的規格。

直接請AI寫程式 先寫測試,再請AI協作
容易只看程式能不能執行。 先確認程式是否符合明確需求。
需求改變時,不容易知道哪裡受影響。 新增測試即可描述新需求,再修改程式。
AI產生錯誤時,常難以察覺。 測試失敗會立即指出程式尚未達標。
修改後可能不小心破壞舊功能。 所有舊測試都能協助確認原有功能仍正確。

三、用AI進行TDD的實作方式

在Python程式實習中,可以依照下列流程與AI協作:

  1. 先用自己的話寫下需求與成功條件。
  2. 先完成一至兩個測試案例,例如正常資料、空白資料或錯誤輸入。
  3. 把測試與需求交給AI,請它只修改功能程式,直到測試通過。
  4. 請AI解釋每一行程式,而不是只複製答案。
  5. 要求AI協助重構後,再次執行所有測試。
  6. 由自己檢查輸出結果是否符合常識與真實情境。

四、從「平均成績」到真實專題

TDD不只適合成績計算。未來同學處理YouBike資料、感測器資料、社區問卷、智慧農業資料或網站API時,都可以先寫下「資料正確時應得到什麼結果」,再開始撰寫程式。

  • 資料缺少某個欄位時,程式是否能顯示清楚錯誤?
  • 感測器數值超出合理範圍時,是否會產生警示?
  • API沒有回傳資料時,系統是否仍能安全處理?
  • 修改功能後,原本已經成功的功能是否仍能正常運作?
AI讓我們更快寫出程式;
Red、Green、Refactor讓我們更有能力寫出可信任的程式。

國立虎尾科技大學|Python程式實習

AI十秒就能交出一段Python,為什麼大三還要學Python程式實習?

AI十秒就能交出一段Python,為什麼大三還要學Python程式實習?

適用課程:大三「Python程式實習」第一堂課

生成式AI出現後,只要輸入一句話,例如:「輸入5位學生成績,計算平均、最高、最低」,AI往往在十秒內就能產生一段看起來完整的Python程式。

因此,同學很自然會問:既然AI會寫程式,我們為什麼還要花一學期學Python?

答案是:AI可以很快交出程式碼初稿,但真正困難的工作,是從拿到程式碼後才開始。Python程式實習要培養的,不只是「打出程式」,而是能夠判斷、驗證、除錯、改進與應用程式的能力。


一、AI十秒能做什麼?——快速產生程式初稿

面對一個簡單需求,AI可以依照自然語言描述產生程式。例如下列程式碼表達的是:「請AI依照題目,產生一段解題程式」的概念:

prompt = "輸入 5 位學生成績,計算平均、最高、最低"
answer = AI.generate(prompt)
print(answer)

不過,這段不是可直接執行的標準Python程式,而是用來說明AI協助寫程式的流程。真正可以執行的Python版本,可能像下面這樣:

scores = []

for i in range(5):
    score = float(input(f"請輸入第 {i + 1} 位學生的成績:"))
    scores.append(score)

print("平均:", sum(scores) / len(scores))
print("最高:", max(scores))
print("最低:", min(scores))

AI確實能很快給出這樣的答案。但是,當程式出現在你面前時,真正的問題才剛開始。

重要觀念:
AI可以在十秒內產生程式初稿;但程式是否正確、安全、可維護,仍需要懂程式的人來判斷與負責。

二、真正難的問題,從程式碼出現後才開始

問題1:這段程式一定對嗎?

不一定。AI產生的是根據大量資料推測出的「合理答案」,不是保證完全正確的答案。程式可能有語法錯誤、變數名稱不一致、計算邏輯錯誤,甚至看起來可以執行,結果卻不符合題目要求。

例如,若題目要求「計算五位學生的平均」,AI卻只讀到四筆資料,或誤把總分除以6,程式仍可能執行,但答案已經錯了。

因此,學習Python的第一個目的,就是能看懂程式碼在做什麼,並能自己設計測試資料來驗證結果。

問題2:使用者輸入 abc 會怎樣?

上面的範例預期使用者輸入數字,但真實世界中的使用者不一定完全配合。若輸入abc、空白或「九十分」等文字,下面這行程式就會發生錯誤:

score = float(input("請輸入成績:"))

Python會出現ValueError,然後程式中斷。這時,我們必須加入例外處理,讓程式能提醒使用者重新輸入:

while True:
    try:
        score = float(input("請輸入成績:"))
        break
    except ValueError:
        print("輸入錯誤,請輸入數字,例如:85 或 92.5。")

這就是程式實習的重要價值:不只讓程式「跑得動」,更要讓程式面對錯誤輸入時,仍能穩定而友善地運作。

問題3:如果資料有10,000筆呢?

五筆成績可以一筆一筆輸入;但如果面對全校一萬名學生、社區問卷資料、感測器每分鐘上傳的資料,人工輸入已不可能完成。

此時,程式需要從CSV檔、Excel檔、資料庫或網路API讀取資料,並進一步進行清理、分析與視覺化。例如:

  • 讀取一萬筆學生資料。
  • 找出缺漏值、錯誤值與重複資料。
  • 計算各班平均、及格率與成績分布。
  • 將結果畫成圖表或輸出成報表。

這正是大三Python程式實習會逐步學到的能力:從基本語法,走向真實資料與實務問題的處理。

問題4:需求突然改變呢?

真實專題中,需求幾乎一定會改變。原本只要計算平均、最高與最低,後來老師或使用單位可能提出新的要求:

  • 缺考學生不納入平均。
  • 列出低於60分的學生名單。
  • 計算全班及格率。
  • 依照班級、性別或科系分組分析。
  • 自動輸出Excel成績報表。
  • 將資料放上網頁,供教師或學生查詢。

如果只會複製AI產生的程式,一旦需求改變,就很容易不知道該從哪裡修改;但如果理解變數、串列、條件判斷、迴圈、函式與模組,就能和AI協作,快速調整程式。

問題5:結果算錯,你看得出來嗎?

最危險的錯誤,不是程式直接當掉,而是程式看似正常執行,卻產生錯誤結果。

例如,缺考資料以0表示時,若直接納入平均,班級平均就會被拉低;或者讀取Excel欄位時,誤把「學號」當成「成績」,程式仍會算出一個數字,但那個數字毫無意義。

因此,程式設計者必須具備資料判讀與驗證能力,包括:

  • 用少量資料手算,和程式結果比對。
  • 測試正常、極端與錯誤輸入。
  • 確認資料欄位與資料型態是否正確。
  • 檢查結果是否符合常識與題目要求。
  • 保留程式、資料來源與處理過程,讓結果可追溯。

三、AI時代,我們為什麼還要學Python?

在AI時代,學Python不是為了和AI比誰打字快,而是為了成為能夠善用AI、檢查AI、改進AI成果的人。

AI可以協助 學生必須具備
快速產生程式初稿 看懂程式的邏輯與結構
提供可能的解法 判斷解法是否符合需求
協助修改與補充程式 描述問題、定位錯誤與驗證修改
協助撰寫文件與註解 確認資料、結果與責任歸屬

未來需要的人才,不只是會使用AI的人,而是能將AI產生的內容轉化為可靠系統、解決真實問題的人。

本課程的學習目標:
讓每位同學都能從「請AI寫程式」,進一步成長為「能與AI一起完成可靠程式與實務專題的人」。

四、第一堂課給同學的提醒

從今天開始,AI可以是你的學習夥伴,但不能取代你的思考。你可以請AI解釋程式、產生範例、協助找錯誤,也可以和AI一起完成專題;但是你要逐步學會提出清楚問題、閱讀程式、測試結果與做出判斷。

AI讓每個人都能很快得到程式碼;
學Python,讓你有能力判斷程式碼是否正確,
並把它真正用在真實問題上。

五、課後思考

  1. 如果AI產生的程式可以執行,你如何確認它的結果正確?
  2. 如果使用者輸入錯誤資料,你希望程式如何回應?
  3. 若資料從5筆增加到10,000筆,你會如何改寫程式?
  4. 你希望在本學期用Python與AI,解決哪一個生活、地方或專業領域的問題?

國立虎尾科技大學|Python程式實習

用 micro:bit Python Editor 學習真正的 MicroPython:從 LED、按鍵到無線互動

用 micro:bit Python Editor 學習真正的 MicroPython:從 LED、按鍵到無線互動

當你已經用 MakeCode 讓 micro:bit 動起來,下一步可以改用 micro:bit Python Editor。它採用 MicroPython,程式結構更接近一般 Python;學生能保留硬體實作的即時回饋,同時學習 importwhile True、函式、序列輸出與模組化程式。

編輯器:micro:bit Python Editor v3
建議對象:具備 MakeCode 基礎、準備進入文字型 Python 程式設計的學生。

一、為什麼要學 micro:bit Python Editor?

學習階段你會學到什麼?
MakeCode 積木程式邏輯、事件、硬體輸入與輸出
MakeCode PythonPython 風格的硬體控制與程式結構
micro:bit Python EditorMicroPython 的 importwhile True、除錯主控台與模組
IDLE Python檔案、資料分析、API、AI 與完整應用程式

micro:bit Python Editor 有模擬器、語法提示、錯誤標示與序列主控台。先在模擬器測試,再傳送到實體 micro:bit,可以降低初次接觸文字程式的挫折感。

二、第一行程式:匯入 micro:bit 工具箱

from microbit import *

這行程式把 micro:bit 的 LED、按鍵、溫度、加速度、腳位與時間功能載入。目前的程式可以使用 displaybutton_asleep() 等名稱。

三、範例 1:讓 LED 顯示愛心

from microbit import *

display.show(Image.HEART)
sleep(1000)
display.clear()

練習:Image.HEART 改成 Image.HAPPYImage.SAD 或自己建立 5×5 圖案。

四、範例 2:按 A 顯示 A,按 B 顯示 B

from microbit import *

while True:
    if button_a.was_pressed():
        display.show('A')
    if button_b.was_pressed():
        display.show('B')

while True: 表示「永遠重複」。程式不是一直盯著按鍵不放,而是持續檢查是否發生「按過一次」的事件。

五、範例 3:搖一搖,顯示隨機點數

from microbit import *
from random import randint

while True:
    if accelerometer.was_gesture('shake'):
        number = randint(1, 6)
        display.show(str(number))
        sleep(800)

這個例子同時練習:感測器、條件判斷、變數、隨機數與字串轉換。

六、範例 4:溫度計與序列主控台

from microbit import *

while True:
    celsius = temperature()
    display.scroll(str(celsius) + 'C')
    print(celsius)
    sleep(2000)

display.scroll() 是給板子上的人看;print() 則會輸出到 Python Editor 的序列主控台,適合觀察資料是否合理、協助除錯。

七、範例 5:兩片 micro:bit 無線傳話

請用兩片 micro:bit,兩端都下載下列程式。按下 A 就送出訊息;接收端會顯示收到的內容。

from microbit import *
import radio

radio.on()
radio.config(group=23)

while True:
    if button_a.was_pressed():
        radio.send('Hello')
        display.show(Image.HAPPY)

    message = radio.receive()
    if message:
        display.scroll(message)

重點:兩片 micro:bit 的 group 必須相同,才像在同一個無線頻道。請改成不同組別測試,觀察訊息是否還能收到。

八、從 MakeCode Python 搬家時,不能直接複製的地方

MakeCode Pythonmicro:bit MicroPython
basic.show_number(51)display.show('5')display.scroll('51')
basic.forever(...)while True:
radio.send_value()radio.send()radio.receive()

兩者都是 Python,但 API 不同。若要控制 CuteBot、Joystick:bit 等擴充硬體,必須使用廠商提供、可在 MicroPython 使用的模組;MakeCode 的擴充功能不能直接複製過來。

九、課堂任務:微型智慧環境站

  1. 顯示目前溫度;當溫度高於自訂門檻時顯示警示圖案。
  2. 按 A 顯示目前溫度,按 B 顯示光線值。
  3. 每 5 秒用 Radio 傳送一行格式化資料,例如 T=28,L=120
  4. 另一片 micro:bit 收到資料後顯示圖示,並在序列主控台列印收到的原始訊息。

十、學習檢核

  • 我能說明 from microbit import * 的用途。
  • 我能用 while True 撰寫持續運作的程式。
  • 我能用序列主控台檢查 print() 輸出的資料。
  • 我能設定兩片 micro:bit 使用相同的 Radio 群組並互相傳訊息。
  • 我知道 MakeCode Python 與 MicroPython 的語法相似,但 API 不可直接混用。

參考資源:micro:bit Python Editor v3官方 Python Editor 指南

Python 程式實習:MakeCode Python 如何指揮 micro:bit 硬體?

Python 程式實習:MakeCode Python 如何指揮 micro:bit 硬體?

當我們寫下 radio.send_value("x", 35)cuteBot.motors(60, 60) 時,真正發生了什麼?本教材帶你理解:程式碼不是直接碰觸硬體,而是經過編譯、執行環境與硬體介面,最後才變成 LED、無線訊號、感測器讀值與馬達動作。

一、學習目標

  • 分辨 MakeCode Python、MicroPython 與電腦 IDLE Python 的差異。
  • 說明 MakeCode Python 從程式碼到 micro:bit 硬體動作的流程。
  • 理解事件驅動程式與一般由上而下執行程式的差別。
  • 能從 API 名稱推論其操作的硬體資源與通訊方式。

二、先釐清:它們不在同一個 Python 世界

環境主要用途程式在哪裡執行?
MakeCode Python以 Python 風格控制 micro:bit、Radio、馬達與感測器編譯後,在 micro:bit 上執行
IDLE Python檔案、資料分析、網頁、AI 與一般軟體開發電腦上的 CPython
MicroPython在微控制器上執行 Python 直譯器支援 MicroPython 韌體的微控制器

三者都有 ifforwhiledef 等共通語法;但可呼叫的函式庫、執行方式與目標硬體不同。MakeCode Python 的重點是「用接近 Python 的語法,使用 MakeCode 的硬體 API」。

三、從程式碼到硬體:中間經過了什麼?

MakeCode
Python
PXT 轉換
與編譯
.hex
機器碼
micro:bit
執行環境
LED、Radio、
腳位、I2C、馬達

你按下「下載」時,MakeCode 會將程式編譯成 micro:bit 的 ARM 處理器可執行的 .hex 檔。下載到開發板後,micro:bit 的執行環境才依序處理按鍵、時間、無線接收與硬體控制。

四、API 是硬體的翻譯字典

MakeCode Python它要做什麼?底層硬體資源
basic.show_number(51)在板子上顯示 515×5 LED 矩陣
radio.send_value("x", 35)送出 x 軸控制資料2.4 GHz 無線電
pins.digital_write_pin(...)輸出高、低電位GPIO 腳位
cuteBot.motors(60, 60)設定左右輪速度腳位或 I2C 與馬達驅動板

擴充功能的價值,是將複雜的電路與通訊細節包裝成可讀的 API。學生仍要理解:函式不是魔法,它是在請底層系統操作某項硬體資源。

五、MakeCode 為什麼常用事件?

在 IDLE 中,我們常把程式想成由上而下執行;但硬體系統必須隨時等候按鍵、無線資料或感測器變化,因此 MakeCode 常採用事件驅動。

def on_received_value(name, value):
    if name == "x":
        xValue = value

radio.on_received_value(on_received_value)

這段程式的意思不是「立刻執行一次後結束」,而是向執行環境登記:以後只要收到廣播鍵值,就呼叫這個函式。

相對地,basic.forever() 則適合放需要持續檢查的工作,例如馬達輸出、逾時判斷或感測距離。

六、程式實驗:觀察同一個概念在不同環境的差異

實驗 A:顯示與輸出

# MakeCode Python:對硬體說話
basic.show_number(51)

# IDLE Python:對終端機說話
print(51)

實驗 B:重複執行與逾時安全

def on_forever():
    if control.millis() - lastReceiveTime > 500:
        cuteBot.motors(0, 0)

basic.forever(on_forever)

請學生回答:這段程式為何要持續執行?若只檢查一次,斷線安全還有效嗎?

七、常見錯誤與除錯方法

現象可能原因檢查方式
IDLE 找不到 basic在錯誤環境執行 MakeCode API改回 MakeCode Python;或重寫為電腦版程式。
程式不能轉回積木某些 Python 寫法無法雙向轉換保留 Python 模式並檢查是否可編譯下載。
小車不動群組不同、未收到資料或安全條件成立先顯示群組、資料值與逾時計時。

八、挑戰任務:從硬體控制走向系統設計

  1. 為 Q霸小車新增一個「狀態回報」訊息,讓搖桿知道小車是否避障或斷線。
  2. 把遙控系統畫成資料流圖,標示程式、執行環境、無線傳輸與硬體輸出。
  3. 比較 MakeCode Python 與 IDLE Python:若要做「兒童館小車使用紀錄」應在哪一端執行?為什麼?
本課結論:MakeCode Python 讓我們用接近 Python 的方式撰寫硬體邏輯;編譯器與 micro:bit 執行環境則負責把這些指令變成真正的電氣訊號與裝置行為。理解這層關係,才能從「會寫積木」進階到「能設計可靠的智慧系統」。