顯示具有 Q霸小車 標籤的文章。 顯示所有文章
顯示具有 Q霸小車 標籤的文章。 顯示所有文章

2026年9月3日 星期四

網際網路應用:我的搖桿,只開我的小車!

網際網路應用:我的搖桿,只開我的小車!

今天我們用搖桿控制 Q霸小車,也用它認識網路。網路的工作就是:送訊息、收訊息、確認是不是傳給對的人。

今天完成後,你可以做到:
① 用搖桿開車 ② 讓小車不會撞到東西 ③ 讓你的搖桿不會開到別人的車

一、網路像傳話遊戲

生活中的角色小車系統裡是誰?
說話的人搖桿
傳話的路無線電波
聽話的人Q霸小車
回覆「收到」嗶一聲、震一下、顯示勾勾

二、開車四步驟

  1. 看同色號:搖桿和小車要有一樣的顏色與號碼。
  2. 等一下:看到勾勾、聽到嗶聲、感覺震動,表示配對成功。
  3. 推搖桿:往前推前進;往左、右推就轉彎。
  4. 遇到危險:按紅色 F,立刻停!

三、四個按鍵

C慢慢開
D正常開
E快快開
F立刻停

四、小車的三個安全規則

  1. 太久沒聽到搖桿:小車會自己停下來。
  2. 前面太近:小車會先慢慢走;再靠近就停下來。
  3. 按 F:小車一定先停,按 C、D 或 E 才能再開。

五、我們也會看懂一點程式

下面這段意思是:「如果太久沒收到訊息,小車停止。」

if control.millis() - lastControlTime > 500:
    cuteBot.motors(0, 0)

下面這段意思是:「第 2 組只聽第 2 組的搖桿。」

pairId = 2
radio.set_group(100 + pairId)

六、闖關任務

  1. 和同學同時開兩組小車,看看會不會互相影響。
  2. 讓小車靠近障礙物,觀察它什麼時候會慢下來、什麼時候會停。
  3. 行駛中按 F,確認小車能不能立刻停止。
  4. 用自己的話說出:為什麼搖桿和小車要同色同號?
記住:網路不是只有手機和電腦。搖桿、小車、感測器也可以互相傳訊息,這就是物聯網!

延伸閱讀:V1、V2、V3、V4、V5

網際網路應用:一支搖桿如何可靠地控制一台 Q霸小車?

網際網路應用:一支搖桿如何可靠地控制一台 Q霸小車?

「遙控車」看似只是硬體活動,其實是一個縮小版的網際網路系統:裝置收集輸入、封裝成訊息、透過無線網路傳送、接收端解讀資料並做出行動。本教材從 Joystick:bit × Q霸小車 V1~V5,認識網際網路應用最重要的可靠性、安全性與多人協作問題。

一、學習目標

  • 說明感測端、傳輸端、服務端與致動端的角色。
  • 理解廣播群組、鍵值訊息、回覆確認與逾時處理。
  • 以「安全優先」思維分析物聯網應用。
  • 設計多人共用場域的一對一裝置配對方案。

二、一台遙控車就是一個網路應用

系統角色本案例網路概念
輸入端Joystick:bit x、y 軸與四鍵資料產生者
傳輸端micro:bit Radio無線通道與群組
處理端小車端 Python 程式訊息解析、狀態機、規則
輸出端馬達、燈號、蜂鳴器服務回應與人機介面

三、V1~V5 對應的網路議題

  1. V1|訊息傳遞:搖桿將 x、y 值以鍵值訊息送出,小車依名稱接收。
  2. V2|可靠性:死區避免雜訊;超過 0.5 秒未收到訊息便停車,這就是逾時偵測。
  3. V3|邊緣運算:小車本地讀取超音波距離,快速做出降速或停止決策。
  4. V4|事件驅動:C、D、E、F 鍵不是持續輪詢,而是在按下事件發生時更新系統狀態。
  5. V5|識別與回覆:以 pairId 選擇專屬群組,並用 pair / ack 做握手確認。

四、資料封包設計:不要只傳數字

有意義的訊息必須包含「名稱」與「值」。例如 ("x", 35) 表示向右 35,("stop", 1) 表示緊急停止。這種設計比只傳一串數字更容易除錯與擴充。

# 搖桿端
radio.send_value("x", x)
radio.send_value("y", y)
radio.send_value("stop", 1)

# 小車端
if name == "x":
    xValue = value
elif name == "stop":
    emergencyStop = value == 1

討論:如果未來要加入「車燈」、「任務開始」與「低電量」,應如何命名訊息?請提出一份小組通訊協定表。

五、可靠性與安全性:先判斷不能做什麼

真正的網路應用不能假設每個封包都會到達。小車端的決策順序應為:

未配對/緊急停止 → 斷線停車 → 避障 → 一般控制
if emergencyStop:
    cuteBot.motors(0, 0)
elif control.millis() - lastControlTime > 500:
    cuteBot.motors(0, 0)
elif yValue > 0 and distance <= 15:
    cuteBot.motors(0, 0)
else:
    cuteBot.motors(leftSpeed, rightSpeed)

六、多人同時使用:廣播群組就像班級頻道

若所有設備都使用群組 51,一位學生的搖桿可能控制全班的小車。V5 將每一組設備設為專屬頻道:

pairId = 3
radio.set_group(100 + pairId)  # 第 3 組使用群組 103

radio.send_value("pair", pairId)
# 小車確認後:radio.send_value("ack", pairId)

這相當於網路上的「位址+連線確認」。雖然不是完整的 TCP/IP,卻已經具備辨識對象、建立關係與回覆確認的核心精神。

七、3 小時課堂任務

  1. 30 分鐘:畫出 V5 的資料流,標記每一種訊息與方向。
  2. 50 分鐘:完成兩組小車同時運作,驗證不互控。
  3. 40 分鐘:刻意關閉搖桿或遮住超音波,記錄安全機制的反應。
  4. 40 分鐘:設計一項新服務,例如車位預約、任務通知或低電量警示。
  5. 20 分鐘:用「問題、資料、規則、結果」分享設計。

八、延伸閱讀

V1 基礎遙控|V2 可靠控制|V3 安全避障|V4 事件互動|V5 配對與回饋

Python 程式實習:從搖桿遙控車學會事件、通訊與安全控制

Python 程式實習:從搖桿遙控車學會事件、通訊與安全控制

寫程式不只是讓硬體動起來,而是把使用者操作、資料傳輸、狀態判斷與安全處置串成可靠的系統。本教材以 Joystick:bit 遙控 Q霸小車的五個版本為案例,帶領大三學生由「可動」走向「可維運」。

適用課程:Python 程式實習、物聯網與智慧生活、嵌入式系統入門
建議時間:3 小時實作課+1 週小組延伸挑戰

一、學習目標

  1. 能以 Python 讀取搖桿資料,並透過無線廣播控制雙輪車。
  2. 能用變數、函式、條件判斷、迴圈與事件處理程式互動。
  3. 理解 x、y 軸混合為左右馬達速度的差速控制概念。
  4. 能加入死區、逾時停車、避障與緊急停止等安全機制。
  5. 能為多人共用場域設計一對一裝置配對與可維護的編號策略。

二、從 V1 到 V5:程式能力如何成長?

版本核心功能Python 觀念
V1搖桿讀值、廣播遙控變數、對應換算、無限迴圈
V2死區、三段速度、斷線停車條件判斷、狀態變數、時間差
V3超音波避障感測資料、優先順序、限制輸出
V4C、D、E、F 四鍵操作事件驅動、函式、介面設計
V5一對一配對、蜂鳴器、震動回饋通訊協定、握手、雙向狀態回傳

三、第一個關鍵:把搖桿數值變成可用的控制值

Joystick:bit 的 x、y 軸原始值範圍為 0~1023;馬達速度較適合使用 -100~100。先進行範圍對應,再做死區處理,才能避免搖桿已回正、小車卻慢慢滑動的問題。

x = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.X),
    0, 1023, -100, 100)
y = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.Y),
    0, 1023, -100, 100)

if x >= -10 and x <= 10:
    x = 0
if y >= -10 and y <= 10:
    y = 0

思考題:為什麼死區不能設得太小?又為什麼不能設得太大?請分別從感測器誤差與操控精準度說明。

四、第二個關鍵:差速控制不是左右各寫一個方向

以 y 作為前後速度、x 作為轉向量,左右輪可以用下面兩行程式同時完成前進、後退與轉彎:

leftSpeed = Math.constrain(yValue + xValue, -100, 100)
rightSpeed = Math.constrain(yValue - xValue, -100, 100)
cuteBot.motors(leftSpeed, rightSpeed)

其中 Math.constrain() 是防呆設計:斜向推搖桿時,加總可能超過 100;若不限制,行為會因硬體函式處理方式不同而不穩定。

五、第三個關鍵:安全程式要有優先順序

安全不是在最後補一個 if。小車端應明確定義決策順序:

未配對或緊急停止 → 斷線停車 → 超音波避障 → 一般搖桿控制
if emergencyStop:
    cuteBot.motors(0, 0)
elif control.millis() - lastControlTime > 500:
    cuteBot.motors(0, 0)
elif yValue > 0 and distance > 0 and distance <= 15:
    cuteBot.motors(0, 0)
elif yValue > 0 and distance <= 30:
    cuteBot.motors(leftSpeed * 0.4, rightSpeed * 0.4)
else:
    cuteBot.motors(leftSpeed, rightSpeed)

這是嵌入式系統常見的「安全優先」寫法。程式可讀性比把所有條件塞進一行更重要,因為日後需要維護、測試與除錯。

六、第四個關鍵:多人場域不能共用同一個廣播群組

若兒童館內十支搖桿都使用群組 51,任一孩子推動搖桿都可能讓十台車一起動。V5 的改善方式是以 pairId 建立專屬群組:

pairId = 1                 # 第 1 組設備
radio.set_group(100 + pairId)  # 使用群組 101

同組搖桿與小車的 pairId 必須一致;第 2 組改為 2,便使用群組 102。再以 pair 與 ack 訊息建立握手確認:

# 搖桿端:提出配對要求
radio.send_value("pair", pairId)

# 小車端:確認同一組後回覆
if name == "pair" and value == pairId:
    paired = True
    radio.send_value("ack", pairId)

七、第五個關鍵:回饋讓系統真正可用

無線系統不能只「送出去」。V5 讓小車將狀態回傳給搖桿,並以蜂鳴器與震動告訴使用者目前情況:

  • 配對成功:短音+震動一次。
  • 慢、中、快:分別以 1、2、3 次提示確認。
  • 前方接近障礙物:提示減速;距離過近:連續提示並停止。
  • F 緊急停止:低音與震動,直到按 C、D、E 之一才解除。
def feedback(times, tone):
    for index in range(times):
        music.play_tone(tone, music.beat(BeatFraction.SIXTEENTH))
        joystickbit.Vibration_Motor(80)
        basic.pause(60)

除錯提醒:Joystick:bit 的震動函式名稱為 Vibration_Motor,其中 V 與 M 必須大寫。

八、實作任務:把 V5 變成可展示的智慧遙控車

  1. 完成基本搖桿遙控與差速轉彎。
  2. 加入死區、三段速度與 0.5 秒斷線停車。
  3. 加入超音波避障,設定停止距離與減速距離。
  4. 使用 C、D、E、F 建立速度與緊急停止事件。
  5. 完成一對一配對;兩組設備同時開機,驗證不會互相控制。
  6. 設計一項自己的狀態回饋,例如配對失敗、低電量或任務完成提示。

九、評量規準

面向檢核重點
功能正確可前後左右控制,速度切換與四鍵功能正常。
安全設計斷線、障礙物與緊急停止都能安全停車。
程式品質變數命名清楚、重複程式整理為函式、條件順序合理。
場域思考兩組以上設備可同時使用且不互相干擾。

十、延伸挑戰

將小車設計成兒童館任務車:小朋友先配對自己的車,依顏色指示完成巡遊、避障與停靠任務;系統記錄完成時間與安全停車次數。這會把 Python 從單純語法練習,轉化為可被真實使用者操作、觀察與改進的智慧系統。

延伸閱讀:本教材的五篇實作基礎

Joystick:bit 遙控 Q霸小車 V5:一對一配對、蜂鳴器與震動回饋

Joystick:bit 遙控 Q霸小車 V5:一對一配對、蜂鳴器與震動回饋

在兒童館同時使用多台 Q霸小車時,最需要避免的是「一支搖桿控制到別組小車」。本作品以每組設備專屬的 pairId 建立無線群組,讓每位小朋友只控制自己手上的搖桿所配對的小車;同時加入蜂鳴器、震動、超音波避障、斷線停車與緊急停止功能。

學習目標
學習 micro:bit 無線群組、一對一裝置配對、雙向訊息回傳、感測安全控制與觸覺回饋設計。

一、材料準備

  • Q霸小車(CuteBot)套件 × 1
  • Joystick:bit2 擴充搖桿 × 1
  • micro:bit × 2、電池盒與電池 × 2
  • Q霸小車超音波模組 × 1

二、兒童館多組設備的配對原則

每一組設備的搖桿與小車,都設定相同的 pairId。程式會將廣播群組設為 100 + pairId,因此各組不會互相干擾。

設備組別pairId廣播群組
紅色 011101
藍色 022102
綠色 033103

部署方式:將同色、同編號貼紙貼在搖桿與小車上,並在兩端程式都改成相同的 pairId 後再下載。建議館員事先完成燒錄,兒童不需要自行設定。

三、右側四鍵功能

按鍵功能回饋
C(P12)慢速 40%1 聲短音+震動 1 次
D(P13)一般速度 70%2 聲短音+震動 2 次
E(P14)快速 100%3 聲短音+震動 3 次
F(P15)緊急停止低音+長震動;按 C/D/E 才能解除

四、程式設計重點

  1. 搖桿每秒送出一次配對訊號,小車回傳 ack 確認;搖桿收到後以勾勾、短音和震動提示。
  2. 只有完成配對的小車才會接受 x、y 搖桿控制訊號。
  3. 小車前方 15~30 cm 時自動降速;15 cm 內自動停止。
  4. 若 0.5 秒沒有收到控制訊號,小車自動停止。

五、MakeCode Python 程式

請建立兩個專案。搖桿端加入 Joystick:bit 擴充功能,小車端加入 CuteBot 擴充功能。兩端皆使用 Radio。

(一)Joystick:bit 搖桿端

pairId = 1  # 同一組的小車也必須設為相同數字
x = 0
y = 0
speed = 70
paired = False
emergencyStop = False
lastPairRequest = -1000
lastStatus = -1

def feedback(times, tone):
    for index in range(times):
        music.play_tone(tone, music.beat(BeatFraction.SIXTEENTH))
        joystickbit.Vibration_Motor(80)
        basic.pause(60)

joystickbit.init_joystick_bit()
radio.set_group(100 + pairId)
basic.show_number(pairId)

def on_button_c():
    global speed, emergencyStop
    speed = 40
    emergencyStop = False
    radio.send_value("stop", 0)
    basic.show_number(1)
    feedback(1, 523)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P12,
    joystickbit.ButtonType.down, on_button_c)

def on_button_d():
    global speed, emergencyStop
    speed = 70
    emergencyStop = False
    radio.send_value("stop", 0)
    basic.show_number(2)
    feedback(2, 659)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P13,
    joystickbit.ButtonType.down, on_button_d)

def on_button_e():
    global speed, emergencyStop
    speed = 100
    emergencyStop = False
    radio.send_value("stop", 0)
    basic.show_number(3)
    feedback(3, 784)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P14,
    joystickbit.ButtonType.down, on_button_e)

def on_button_f():
    global emergencyStop
    emergencyStop = True
    radio.send_value("stop", 1)
    basic.show_icon(IconNames.NO)
    feedback(1, 220)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P15,
    joystickbit.ButtonType.down, on_button_f)

def on_received_value(name, value):
    global paired, lastStatus
    if name == "ack" and value == pairId:
        if not paired:
            paired = True
            basic.show_icon(IconNames.YES)
            feedback(1, 988)
    if name == "status" and value != lastStatus:
        lastStatus = value
        if value == 1:
            feedback(1, 440)
        elif value == 2:
            feedback(2, 330)
        elif value == 3:
            feedback(1, 220)

radio.on_received_value(on_received_value)

def on_forever():
    global x, y, lastPairRequest
    if control.millis() - lastPairRequest > 1000:
        radio.send_value("pair", pairId)
        lastPairRequest = control.millis()
    x = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.X),
        0, 1023, -100, 100)
    y = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.Y),
        0, 1023, -100, 100)
    if x >= -10 and x <= 10:
        x = 0
    if y >= -10 and y <= 10:
        y = 0
    if emergencyStop:
        radio.send_value("stop", 1)
    elif paired:
        radio.send_value("x", x * speed / 100)
        radio.send_value("y", y * speed / 100)
    basic.pause(50)

basic.forever(on_forever)

(二)Q霸小車端

pairId = 1  # 必須和配對搖桿相同
xValue = 0
yValue = 0
distance = 0
paired = False
emergencyStop = False
lastControlTime = -1000
lastStatus = -1

def send_status(newStatus):
    global lastStatus
    if newStatus != lastStatus:
        lastStatus = newStatus
        radio.send_value("status", newStatus)

radio.set_group(100 + pairId)
basic.show_number(pairId)
cuteBot.motors(0, 0)

def on_received_value(name, value):
    global xValue, yValue, paired, emergencyStop, lastControlTime
    if name == "pair" and value == pairId:
        paired = True
        radio.send_value("ack", pairId)
    elif paired and name == "x":
        xValue = value
        lastControlTime = control.millis()
    elif paired and name == "y":
        yValue = value
        lastControlTime = control.millis()
    elif paired and name == "stop":
        emergencyStop = value == 1
        lastControlTime = control.millis()

radio.on_received_value(on_received_value)

def on_forever():
    global distance
    if not paired:
        cuteBot.motors(0, 0)
    elif emergencyStop:
        cuteBot.motors(0, 0)
        basic.show_icon(IconNames.NO)
        send_status(3)
    elif control.millis() - lastControlTime > 500:
        cuteBot.motors(0, 0)
        basic.show_icon(IconNames.SAD)
        send_status(4)
    else:
        distance = cuteBot.ultrasonic(cuteBot.SonarUnit.Centimeters)
        leftSpeed = Math.constrain(yValue + xValue, -100, 100)
        rightSpeed = Math.constrain(yValue - xValue, -100, 100)
        if yValue > 0 and distance > 0 and distance <= 15:
            cuteBot.motors(0, 0)
            basic.show_icon(IconNames.NO)
            send_status(2)
        elif yValue > 0 and distance > 15 and distance <= 30:
            cuteBot.motors(leftSpeed * 0.4, rightSpeed * 0.4)
            basic.show_icon(IconNames.SMALL_DIAMOND)
            send_status(1)
        else:
            cuteBot.motors(leftSpeed, rightSpeed)
            basic.clear_screen()
            send_status(0)
    basic.pause(30)

basic.forever(on_forever)

六、測試步驟與除錯

  1. 先將兩端的 pairId 都設為 1,下載程式並開機。
  2. 搖桿顯示勾勾、短響並震動一次,表示已與同組小車完成配對。
  3. 按 C、D、E 測試三段速度;按 F 確認小車立即停止,並按 C、D 或 E 解除停止。
  4. 在前方放置障礙物:15~30 cm 會降速,15 cm 內會停止。
  5. 若小車前進方向相反,將小車端兩個 yValue > 0 改為 yValue < 0。
  6. 若震動沒有反應,確認程式使用的是 joystickbit.Vibration_Motor(80),其中 V、M 必須大寫。
館員提醒:為每組設備貼上相同顏色與編號,並將搖桿與小車的 pairId 預先設好。這是讓多位兒童同時操作時仍保持安全、清楚且可管理的關鍵。

使用 Joystick:bit 擴充搖桿遙控 Q霸小車 V4

使用 Joystick:bit 擴充搖桿遙控 Q霸小車 V4

本實作使用兩片 micro:bit 進行無線通訊:一片裝在 Joystick:bit2 擴充搖桿,負責讀取搖桿方向;另一片安裝在 Q霸小車(CuteBot) 上,接收訊號後控制左右輪馬達。V4 保留搖桿死區、斷線自動停車與超音波安全避障,並啟用右側四鍵,讓速度切換與緊急停止都可單手完成。

學習目標
了解 micro:bit 廣播通訊、類比搖桿讀值、超音波感測與雙輪差速控制,完成具備安全保護與速度切換功能的 Q霸小車。

一、材料準備

  • Q霸小車套件(CuteBot)× 1
  • Joystick:bit2 擴充搖桿 × 1
  • micro:bit × 2
  • 電池盒與電池 × 2

二、操作前準備

  1. 開啟 Microsoft MakeCode for micro:bit,分別建立「搖桿端」與「小車端」兩個專案。
  2. 在兩個專案中加入 Radio(廣播) 功能;搖桿端另加入 Joystick:bit 擴充功能,小車端加入 CuteBot 擴充功能。
  3. 兩端的廣播群組都設定為 51。群組號碼必須相同,才能互相通訊。
  4. 分別下載程式到兩片 micro:bit,再接上電池並開機。

三、搖桿端編程:死區、三段速度與訊號發送

搖桿的原始讀值介於 0~1023。本程式利用「對應」積木將數值換算為 -100~100,並先判斷搖桿是否落在中央的死區範圍,再依速度模式縮放輸出值,最後以廣播鍵值送出 x 與 y。

  • x 軸:控制左右轉向。
  • y 軸:控制前進與後退。
  • 死區 -10~10:數值位於此區間時歸零,避免小車自己緩慢移動。
  • 三段速度:按右側 C 鍵為慢速 40%、D 鍵為一般 70%、E 鍵為快速 100%。F 鍵為緊急停止。
  • 廣播群組 51:作為搖桿與小車共用的無線頻道。
圖1 Joystick:bit 搖桿端積木程式(可依下列 V2 邏輯補強)

搖桿端 V2 積木邏輯

  1. 建立變數:x、y、speed。
  2. 啟動時設定 speed = 70,廣播群組設為 51。
  3. 按下 A:設定 speed = 40 並顯示 1;按下 B:設定 speed = 70 並顯示 2;按下 A+B:設定 speed = 100 並顯示 3。
  4. 重複無限次:讀取並對應 x、y 軸;若 x 或 y 介於 -10~10,將該值設為 0;最後分別將 x、y 乘以 speed ÷ 100 後廣播。

四、Q霸小車端編程:超音波安全避障、斷線停車與馬達控制

小車端收到廣播鍵值後,依名稱將數值分別存入 xValue、yValue,同時記錄最後一次收到訊號的時間。若超過 500 毫秒沒有收到新訊號,便立刻停止兩輪馬達;向前行駛時,超音波感測器會依前方距離自動降速或停車。

左輪速度 = yValue + xValue
右輪速度 = yValue − xValue

當搖桿向前推時,兩輪同向前轉;向右推時,左輪速度增加、右輪速度降低,小車便向右轉。向左、向後的原理相同。斷線停車能避免電池沒電、搖桿關機或超出廣播距離時,小車仍維持原方向行駛。

圖2 Q霸小車端積木程式

小車端 V3 積木邏輯

  1. 建立變數:xValue、yValue、lastReceiveTime。
  2. 啟動時將廣播群組設為 51,並讓兩輪馬達停止。
  3. 當收到廣播鍵值:名稱為 x 時更新 xValue;名稱為 y 時更新 yValue;每次收到訊號都將 lastReceiveTime 設為「運行時間(毫秒)」。
  4. 重複無限次:若「運行時間 − lastReceiveTime」大於 500,設定左右輪速度都為 0;否則讀取超音波距離。
  5. 前方距離為 15~30 cm 時,向前行駛自動降為 40% 速度;距離小於等於 15 cm 時,向前行駛立即停止。

五、MakeCode Python 程式碼

若要使用 Python 模式,請在 MakeCode 先加入 Joystick:bit 與 CuteBot 擴充功能,再分別建立兩個專案並貼上以下程式。

(一)Joystick:bit 搖桿端

x = 0
y = 0
speed = 70

radio.set_group(51)
basic.show_number(2)  # 開機預設為一般速度

def on_button_pressed_a():
    global speed
    speed = 40
    basic.show_number(1)  # 慢速
input.on_button_pressed(Button.A, on_button_pressed_a)

def on_button_pressed_b():
    global speed
    speed = 70
    basic.show_number(2)  # 一般速度
input.on_button_pressed(Button.B, on_button_pressed_b)

def on_button_pressed_ab():
    global speed
    speed = 100
    basic.show_number(3)  # 快速
input.on_button_pressed(Button.AB, on_button_pressed_ab)

def on_forever():
    global x, y

    x = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.X),
        0, 1023, -100, 100)
    y = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.Y),
        0, 1023, -100, 100)

    # 搖桿死區:回到中央時停止
    if x >= -10 and x <= 10:
        x = 0
    if y >= -10 and y <= 10:
        y = 0

    # 依速度模式縮放輸出值
    x = x * speed / 100
    y = y * speed / 100

    radio.send_value("x", x)
    radio.send_value("y", y)
    basic.pause(50)

basic.forever(on_forever)

(二)Q霸小車端:V3 超音波安全避障

xValue = 0
yValue = 0
lastReceiveTime = -1000
distance = 0

radio.set_group(51)
basic.show_number(51)
cuteBot.motors(0, 0)

def on_received_value(name, value):
    global xValue, yValue, lastReceiveTime
    if name == "x":
        xValue = value
    elif name == "y":
        yValue = value
    lastReceiveTime = control.millis()

radio.on_received_value(on_received_value)

def on_forever():
    global distance
    # 優先保護 1:逾 0.5 秒未收到訊號,立即停車
    if control.millis() - lastReceiveTime > 500:
        cuteBot.motors(0, 0)
    else:
        distance = cuteBot.ultrasonic(cuteBot.SonarUnit.Centimeters)
        leftSpeed = Math.constrain(yValue + xValue, -100, 100)
        rightSpeed = Math.constrain(yValue - xValue, -100, 100)

        # 優先保護 2:僅在向前時啟用前方避障
        if yValue > 0 and distance > 0 and distance <= 15:
            cuteBot.motors(0, 0)
            basic.show_icon(IconNames.NO)
        elif yValue > 0 and distance > 15 and distance <= 30:
            cuteBot.motors(leftSpeed * 0.4, rightSpeed * 0.4)
            basic.show_icon(IconNames.SMALL_DIAMOND)
        else:
            cuteBot.motors(leftSpeed, rightSpeed)
            basic.clear_screen()
    basic.pause(30)

basic.forever(on_forever)

測試提示:本程式假設 yValue > 0 是前進。如果實測發現前進方向相反,請把小車端的兩個 yValue > 0 改為 yValue < 0。

六、V4 四鍵駕駛面板 Python 程式

以下程式為 V4 正式測試版。右側按鍵 C、D、E 用於速度選擇,F 為緊急停止;按下 F 後,必須按 C、D 或 E 才能重新啟動。

按鍵功能
C(P12)慢速 40%
D(P13)一般 70%
E(P14)快速 100%
F(P15)緊急停止

(一)Joystick:bit 搖桿端 V4

x = 0
y = 0
speed = 70
emergencyStop = False

joystickbit.init_joystick_bit()
radio.set_group(51)
basic.show_number(2)

def on_button_c():
    global speed, emergencyStop
    speed = 40
    emergencyStop = False
    radio.send_value("stop", 0)
    basic.show_number(1)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P12,
    joystickbit.ButtonType.down, on_button_c)

def on_button_d():
    global speed, emergencyStop
    speed = 70
    emergencyStop = False
    radio.send_value("stop", 0)
    basic.show_number(2)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P13,
    joystickbit.ButtonType.down, on_button_d)

def on_button_e():
    global speed, emergencyStop
    speed = 100
    emergencyStop = False
    radio.send_value("stop", 0)
    basic.show_number(3)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P14,
    joystickbit.ButtonType.down, on_button_e)

def on_button_f():
    global emergencyStop
    emergencyStop = True
    radio.send_value("stop", 1)
    basic.show_icon(IconNames.NO)
joystickbit.on_button_event(joystickbit.JoystickBitPin.P15,
    joystickbit.ButtonType.down, on_button_f)

def on_forever():
    global x, y
    x = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.X),
        0, 1023, -100, 100)
    y = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.Y),
        0, 1023, -100, 100)
    if x >= -10 and x <= 10:
        x = 0
    if y >= -10 and y <= 10:
        y = 0
    if emergencyStop:
        radio.send_value("stop", 1)
    else:
        radio.send_value("x", x * speed / 100)
        radio.send_value("y", y * speed / 100)
    basic.pause(50)

basic.forever(on_forever)

(二)Q霸小車端 V4

xValue = 0
yValue = 0
lastReceiveTime = -1000
distance = 0
emergencyStop = False

radio.set_group(51)
basic.show_number(51)
cuteBot.motors(0, 0)

def on_received_value(name, value):
    global xValue, yValue, lastReceiveTime, emergencyStop
    lastReceiveTime = control.millis()
    if name == "x":
        xValue = value
    elif name == "y":
        yValue = value
    elif name == "stop":
        emergencyStop = value == 1

radio.on_received_value(on_received_value)

def on_forever():
    global distance
    # 緊急停止 → 斷線停車 → 超音波避障 → 搖桿控制
    if emergencyStop:
        cuteBot.motors(0, 0)
        basic.show_icon(IconNames.NO)
    elif control.millis() - lastReceiveTime > 500:
        cuteBot.motors(0, 0)
        basic.show_icon(IconNames.SAD)
    else:
        distance = cuteBot.ultrasonic(cuteBot.SonarUnit.Centimeters)
        leftSpeed = Math.constrain(yValue + xValue, -100, 100)
        rightSpeed = Math.constrain(yValue - xValue, -100, 100)
        if yValue > 0 and distance > 0 and distance <= 15:
            cuteBot.motors(0, 0)
            basic.show_icon(IconNames.NO)
        elif yValue > 0 and distance > 15 and distance <= 30:
            cuteBot.motors(leftSpeed * 0.4, rightSpeed * 0.4)
            basic.show_icon(IconNames.SMALL_DIAMOND)
        else:
            cuteBot.motors(leftSpeed, rightSpeed)
            basic.clear_screen()
    basic.pause(30)

basic.forever(on_forever)

測試提示:如果實測前進方向是 yValue < 0,請把小車端兩個 yValue > 0 改為 yValue < 0。

七、完成後的操作方式

圖3 Q霸小車與 Joystick:bit 擴充搖桿實體外觀
搖桿操作小車動作
向前推前進
向後拉後退
向左推左轉
向右推右轉
回到中央停止(死區保護)
按 A/B/A+B切換慢速/一般/快速模式
搖桿關機或超出距離0.5 秒後自動停車

八、除錯小提醒

  1. 小車沒有反應:先確認兩端廣播群組是否都為 51,以及兩片 micro:bit 是否都有供電。
  2. 前後方向相反:將 y 軸對應範圍由 -100~100 改為 100~-100,或將馬達速度乘以 -1。
  3. 左右方向相反:將 x 軸對應範圍反向,或在小車端交換加、減的運算方向。
  4. 停下仍會緩慢移動:先確認死區已設定為 -10~10;若仍有誤差,可加大為 -15~15。
  5. 小車經常自動停車:確認兩端群組號相同、電池電力充足,並將斷線判斷時間由 500 毫秒調整為 800 毫秒。

九、延伸挑戰

  • 按下搖桿按鍵時,讓小車發出聲音或亮起車燈。
  • 加入超音波避障:前方太近時,即使搖桿要求前進也自動停車。
  • 將小車改造成校園導覽車、農場巡檢車或智慧物流車,設計任務關卡。
教學重點:這個作品不是只讓小車「動起來」,而是把感測輸入、無線傳輸與馬達輸出串成完整的智慧控制系統。學生可從中理解物聯網裝置如何把人的操作,轉換成真實世界的行動。

使用 Joystick:bit 擴充搖桿遙控 Q霸小車 V3

使用 Joystick:bit 擴充搖桿遙控 Q霸小車 V3

本實作使用兩片 micro:bit 進行無線通訊:一片裝在 Joystick:bit2 擴充搖桿,負責讀取搖桿方向;另一片安裝在 Q霸小車(CuteBot) 上,接收訊號後控制左右輪馬達。V3 保留搖桿死區、斷線自動停車與三段速度,再加入超音波安全避障,讓小車接近前方障礙物時能自動降速或停止。

學習目標
了解 micro:bit 廣播通訊、類比搖桿讀值、超音波感測與雙輪差速控制,完成具備安全保護與速度切換功能的 Q霸小車。

一、材料準備

  • Q霸小車套件(CuteBot)× 1
  • Joystick:bit2 擴充搖桿 × 1
  • micro:bit × 2
  • 電池盒與電池 × 2

二、操作前準備

  1. 開啟 Microsoft MakeCode for micro:bit,分別建立「搖桿端」與「小車端」兩個專案。
  2. 在兩個專案中加入 Radio(廣播) 功能;搖桿端另加入 Joystick:bit 擴充功能,小車端加入 CuteBot 擴充功能。
  3. 兩端的廣播群組都設定為 51。群組號碼必須相同,才能互相通訊。
  4. 分別下載程式到兩片 micro:bit,再接上電池並開機。

三、搖桿端編程:死區、三段速度與訊號發送

搖桿的原始讀值介於 0~1023。本程式利用「對應」積木將數值換算為 -100~100,並先判斷搖桿是否落在中央的死區範圍,再依速度模式縮放輸出值,最後以廣播鍵值送出 x 與 y。

  • x 軸:控制左右轉向。
  • y 軸:控制前進與後退。
  • 死區 -10~10:數值位於此區間時歸零,避免小車自己緩慢移動。
  • 三段速度:按 A 鍵為慢速 40%、按 B 鍵為一般 70%、同時按 A+B 為快速 100%。
  • 廣播群組 51:作為搖桿與小車共用的無線頻道。
圖1 Joystick:bit 搖桿端積木程式(可依下列 V2 邏輯補強)

搖桿端 V2 積木邏輯

  1. 建立變數:x、y、speed。
  2. 啟動時設定 speed = 70,廣播群組設為 51。
  3. 按下 A:設定 speed = 40 並顯示 1;按下 B:設定 speed = 70 並顯示 2;按下 A+B:設定 speed = 100 並顯示 3。
  4. 重複無限次:讀取並對應 x、y 軸;若 x 或 y 介於 -10~10,將該值設為 0;最後分別將 x、y 乘以 speed ÷ 100 後廣播。

四、Q霸小車端編程:超音波安全避障、斷線停車與馬達控制

小車端收到廣播鍵值後,依名稱將數值分別存入 xValue、yValue,同時記錄最後一次收到訊號的時間。若超過 500 毫秒沒有收到新訊號,便立刻停止兩輪馬達;向前行駛時,超音波感測器會依前方距離自動降速或停車。

左輪速度 = yValue + xValue
右輪速度 = yValue − xValue

當搖桿向前推時,兩輪同向前轉;向右推時,左輪速度增加、右輪速度降低,小車便向右轉。向左、向後的原理相同。斷線停車能避免電池沒電、搖桿關機或超出廣播距離時,小車仍維持原方向行駛。

圖2 Q霸小車端積木程式

小車端 V3 積木邏輯

  1. 建立變數:xValue、yValue、lastReceiveTime。
  2. 啟動時將廣播群組設為 51,並讓兩輪馬達停止。
  3. 當收到廣播鍵值:名稱為 x 時更新 xValue;名稱為 y 時更新 yValue;每次收到訊號都將 lastReceiveTime 設為「運行時間(毫秒)」。
  4. 重複無限次:若「運行時間 − lastReceiveTime」大於 500,設定左右輪速度都為 0;否則讀取超音波距離。
  5. 前方距離為 15~30 cm 時,向前行駛自動降為 40% 速度;距離小於等於 15 cm 時,向前行駛立即停止。

五、MakeCode Python 程式碼

若要使用 Python 模式,請在 MakeCode 先加入 Joystick:bit 與 CuteBot 擴充功能,再分別建立兩個專案並貼上以下程式。

(一)Joystick:bit 搖桿端

x = 0
y = 0
speed = 70

radio.set_group(51)
basic.show_number(2)  # 開機預設為一般速度

def on_button_pressed_a():
    global speed
    speed = 40
    basic.show_number(1)  # 慢速
input.on_button_pressed(Button.A, on_button_pressed_a)

def on_button_pressed_b():
    global speed
    speed = 70
    basic.show_number(2)  # 一般速度
input.on_button_pressed(Button.B, on_button_pressed_b)

def on_button_pressed_ab():
    global speed
    speed = 100
    basic.show_number(3)  # 快速
input.on_button_pressed(Button.AB, on_button_pressed_ab)

def on_forever():
    global x, y

    x = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.X),
        0, 1023, -100, 100)
    y = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.Y),
        0, 1023, -100, 100)

    # 搖桿死區:回到中央時停止
    if x >= -10 and x <= 10:
        x = 0
    if y >= -10 and y <= 10:
        y = 0

    # 依速度模式縮放輸出值
    x = x * speed / 100
    y = y * speed / 100

    radio.send_value("x", x)
    radio.send_value("y", y)
    basic.pause(50)

basic.forever(on_forever)

(二)Q霸小車端:V3 超音波安全避障

xValue = 0
yValue = 0
lastReceiveTime = -1000
distance = 0

radio.set_group(51)
basic.show_number(51)
cuteBot.motors(0, 0)

def on_received_value(name, value):
    global xValue, yValue, lastReceiveTime
    if name == "x":
        xValue = value
    elif name == "y":
        yValue = value
    lastReceiveTime = control.millis()

radio.on_received_value(on_received_value)

def on_forever():
    global distance
    # 優先保護 1:逾 0.5 秒未收到訊號,立即停車
    if control.millis() - lastReceiveTime > 500:
        cuteBot.motors(0, 0)
    else:
        distance = cuteBot.ultrasonic(cuteBot.SonarUnit.Centimeters)
        leftSpeed = Math.constrain(yValue + xValue, -100, 100)
        rightSpeed = Math.constrain(yValue - xValue, -100, 100)

        # 優先保護 2:僅在向前時啟用前方避障
        if yValue > 0 and distance > 0 and distance <= 15:
            cuteBot.motors(0, 0)
            basic.show_icon(IconNames.NO)
        elif yValue > 0 and distance > 15 and distance <= 30:
            cuteBot.motors(leftSpeed * 0.4, rightSpeed * 0.4)
            basic.show_icon(IconNames.SMALL_DIAMOND)
        else:
            cuteBot.motors(leftSpeed, rightSpeed)
            basic.clear_screen()
    basic.pause(30)

basic.forever(on_forever)

測試提示:本程式假設 yValue > 0 是前進。如果實測發現前進方向相反,請把小車端的兩個 yValue > 0 改為 yValue < 0。

六、完成後的操作方式

圖3 Q霸小車與 Joystick:bit 擴充搖桿實體外觀
搖桿操作小車動作
向前推前進
向後拉後退
向左推左轉
向右推右轉
回到中央停止(死區保護)
按 A/B/A+B切換慢速/一般/快速模式
搖桿關機或超出距離0.5 秒後自動停車

七、除錯小提醒

  1. 小車沒有反應:先確認兩端廣播群組是否都為 51,以及兩片 micro:bit 是否都有供電。
  2. 前後方向相反:將 y 軸對應範圍由 -100~100 改為 100~-100,或將馬達速度乘以 -1。
  3. 左右方向相反:將 x 軸對應範圍反向,或在小車端交換加、減的運算方向。
  4. 停下仍會緩慢移動:先確認死區已設定為 -10~10;若仍有誤差,可加大為 -15~15。
  5. 小車經常自動停車:確認兩端群組號相同、電池電力充足,並將斷線判斷時間由 500 毫秒調整為 800 毫秒。

八、延伸挑戰

  • 按下搖桿按鍵時,讓小車發出聲音或亮起車燈。
  • 加入超音波避障:前方太近時,即使搖桿要求前進也自動停車。
  • 將小車改造成校園導覽車、農場巡檢車或智慧物流車,設計任務關卡。
教學重點:這個作品不是只讓小車「動起來」,而是把感測輸入、無線傳輸與馬達輸出串成完整的智慧控制系統。學生可從中理解物聯網裝置如何把人的操作,轉換成真實世界的行動。

使用 Joystick:bit 擴充搖桿遙控 Q霸小車 V2

使用 Joystick:bit 擴充搖桿遙控 Q霸小車 V2

本實作使用兩片 micro:bit 進行無線通訊:一片裝在 Joystick:bit2 擴充搖桿,負責讀取搖桿方向;另一片安裝在 Q霸小車(CuteBot) 上,接收訊號後控制左右輪馬達。V2 再加入搖桿死區、斷線自動停車與三段速度,讓小車操作更穩定也更安全。

學習目標
了解 micro:bit 廣播通訊、類比搖桿讀值與雙輪差速控制,完成具備安全保護與速度切換功能的 Q霸小車。

一、材料準備

  • Q霸小車套件(CuteBot)× 1
  • Joystick:bit2 擴充搖桿 × 1
  • micro:bit × 2
  • 電池盒與電池 × 2

二、操作前準備

  1. 開啟 Microsoft MakeCode for micro:bit,分別建立「搖桿端」與「小車端」兩個專案。
  2. 在兩個專案中加入 Radio(廣播) 功能;搖桿端另加入 Joystick:bit 擴充功能,小車端加入 CuteBot 擴充功能。
  3. 兩端的廣播群組都設定為 51。群組號碼必須相同,才能互相通訊。
  4. 分別下載程式到兩片 micro:bit,再接上電池並開機。

三、搖桿端編程:死區、三段速度與訊號發送

搖桿的原始讀值介於 0~1023。本程式利用「對應」積木將數值換算為 -100~100,並先判斷搖桿是否落在中央的死區範圍,再依速度模式縮放輸出值,最後以廣播鍵值送出 x 與 y。

  • x 軸:控制左右轉向。
  • y 軸:控制前進與後退。
  • 死區 -10~10:數值位於此區間時歸零,避免小車自己緩慢移動。
  • 三段速度:按 A 鍵為慢速 40%、按 B 鍵為一般 70%、同時按 A+B 為快速 100%。
  • 廣播群組 51:作為搖桿與小車共用的無線頻道。
圖1 Joystick:bit 搖桿端積木程式(可依下列 V2 邏輯補強)

搖桿端 V2 積木邏輯

  1. 建立變數:x、y、speed。
  2. 啟動時設定 speed = 70,廣播群組設為 51。
  3. 按下 A:設定 speed = 40 並顯示 1;按下 B:設定 speed = 70 並顯示 2;按下 A+B:設定 speed = 100 並顯示 3。
  4. 重複無限次:讀取並對應 x、y 軸;若 x 或 y 介於 -10~10,將該值設為 0;最後分別將 x、y 乘以 speed ÷ 100 後廣播。

四、Q霸小車端編程:斷線自動停車與馬達控制

小車端收到廣播鍵值後,依名稱將數值分別存入 xValue、yValue,同時記錄最後一次收到訊號的時間。若超過 500 毫秒沒有收到新訊號,便立刻停止兩輪馬達。

左輪速度 = yValue + xValue
右輪速度 = yValue − xValue

當搖桿向前推時,兩輪同向前轉;向右推時,左輪速度增加、右輪速度降低,小車便向右轉。向左、向後的原理相同。斷線停車能避免電池沒電、搖桿關機或超出廣播距離時,小車仍維持原方向行駛。

圖2 Q霸小車端積木程式

小車端 V2 積木邏輯

  1. 建立變數:xValue、yValue、lastReceiveTime。
  2. 啟動時將廣播群組設為 51,並讓兩輪馬達停止。
  3. 當收到廣播鍵值:名稱為 x 時更新 xValue;名稱為 y 時更新 yValue;每次收到訊號都將 lastReceiveTime 設為「運行時間(毫秒)」。
  4. 重複無限次:若「運行時間 − lastReceiveTime」大於 500,設定左右輪速度都為 0;否則以 yValue + xValue、yValue − xValue 控制左右輪。

五、MakeCode Python 程式碼

若要使用 Python 模式,請在 MakeCode 先加入 Joystick:bit 與 CuteBot 擴充功能,再分別建立兩個專案並貼上以下程式。

(一)Joystick:bit 搖桿端

x = 0
y = 0
speed = 70

radio.set_group(51)
basic.show_number(2)  # 開機預設為一般速度

def on_button_pressed_a():
    global speed
    speed = 40
    basic.show_number(1)  # 慢速
input.on_button_pressed(Button.A, on_button_pressed_a)

def on_button_pressed_b():
    global speed
    speed = 70
    basic.show_number(2)  # 一般速度
input.on_button_pressed(Button.B, on_button_pressed_b)

def on_button_pressed_ab():
    global speed
    speed = 100
    basic.show_number(3)  # 快速
input.on_button_pressed(Button.AB, on_button_pressed_ab)

def on_forever():
    global x, y

    x = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.X),
        0, 1023, -100, 100)
    y = Math.map(joystickbit.get_rocker_value(joystickbit.rockerType.Y),
        0, 1023, -100, 100)

    # 搖桿死區:回到中央時停止
    if x >= -10 and x <= 10:
        x = 0
    if y >= -10 and y <= 10:
        y = 0

    # 依速度模式縮放輸出值
    x = x * speed / 100
    y = y * speed / 100

    radio.send_value("x", x)
    radio.send_value("y", y)
    basic.pause(50)

basic.forever(on_forever)

(二)Q霸小車端

xValue = 0
yValue = 0
lastReceiveTime = -1000

radio.set_group(51)
basic.show_number(51)
cuteBot.motors(0, 0)

def on_received_value(name, value):
    global xValue, yValue, lastReceiveTime

    if name == "x":
        xValue = value
    elif name == "y":
        yValue = value

    # 每收到一筆資料就刷新最後接收時間
    lastReceiveTime = control.millis()

radio.on_received_value(on_received_value)

def on_forever():
    # 逾 0.5 秒未收到訊號,立即停車
    if control.millis() - lastReceiveTime > 500:
        cuteBot.motors(0, 0)
    else:
        leftSpeed = Math.constrain(yValue + xValue, -100, 100)
        rightSpeed = Math.constrain(yValue - xValue, -100, 100)
        cuteBot.motors(leftSpeed, rightSpeed)

basic.forever(on_forever)

測試提示:如果前後方向相反,將搖桿端 y 軸的對應範圍由 -100, 100 改為 100, -100;若左右相反,則反轉 x 軸範圍。

六、完成後的操作方式

圖3 Q霸小車與 Joystick:bit 擴充搖桿實體外觀
搖桿操作小車動作
向前推前進
向後拉後退
向左推左轉
向右推右轉
回到中央停止(死區保護)
按 A/B/A+B切換慢速/一般/快速模式
搖桿關機或超出距離0.5 秒後自動停車

七、除錯小提醒

  1. 小車沒有反應:先確認兩端廣播群組是否都為 51,以及兩片 micro:bit 是否都有供電。
  2. 前後方向相反:將 y 軸對應範圍由 -100~100 改為 100~-100,或將馬達速度乘以 -1。
  3. 左右方向相反:將 x 軸對應範圍反向,或在小車端交換加、減的運算方向。
  4. 停下仍會緩慢移動:先確認死區已設定為 -10~10;若仍有誤差,可加大為 -15~15。
  5. 小車經常自動停車:確認兩端群組號相同、電池電力充足,並將斷線判斷時間由 500 毫秒調整為 800 毫秒。

八、延伸挑戰

  • 按下搖桿按鍵時,讓小車發出聲音或亮起車燈。
  • 加入超音波避障:前方太近時,即使搖桿要求前進也自動停車。
  • 將小車改造成校園導覽車、農場巡檢車或智慧物流車,設計任務關卡。
教學重點:這個作品不是只讓小車「動起來」,而是把感測輸入、無線傳輸與馬達輸出串成完整的智慧控制系統。學生可從中理解物聯網裝置如何把人的操作,轉換成真實世界的行動。

使用 Joystick:bit 擴充搖桿遙控 Q霸小車

使用 Joystick:bit 擴充搖桿遙控 Q霸小車

本實作使用兩片 micro:bit 進行無線通訊:一片裝在 Joystick:bit2 擴充搖桿,負責讀取搖桿方向;另一片安裝在 Q霸小車(CuteBot) 上,接收訊號後控制左右輪馬達。透過 x、y 兩軸數值混合,即可讓小車前進、後退與左右轉彎。

學習目標
了解 micro:bit 廣播通訊、類比搖桿讀值與雙輪差速控制,完成一台可用手持搖桿遙控的 Q霸小車。

一、材料準備

  • Q霸小車套件(CuteBot)× 1
  • Joystick:bit2 擴充搖桿 × 1
  • micro:bit × 2
  • 電池盒與電池 × 2

二、操作前準備

  1. 開啟 Microsoft MakeCode for micro:bit,分別建立「搖桿端」與「小車端」兩個專案。
  2. 在兩個專案中加入 Radio(廣播) 功能;搖桿端另加入 Joystick:bit 擴充功能,小車端加入 CuteBot 擴充功能。
  3. 兩端的廣播群組都設定為 51。群組號碼必須相同,才能互相通訊。
  4. 分別下載程式到兩片 micro:bit,再接上電池並開機。

三、搖桿端編程:讀取方向並發送 x、y 值

搖桿的原始讀值介於 0~1023。本程式利用「對應」積木將數值換算為 -100~100,讓它能直接作為馬達速度使用;接著以廣播鍵值分別送出 x 與 y。

  • x 軸:控制左右轉向。
  • y 軸:控制前進與後退。
  • 廣播群組 51:作為搖桿與小車共用的無線頻道。

圖1 Joystick:bit 搖桿端積木程式

四、Q霸小車端編程:接收數值並控制馬達

小車端收到廣播鍵值後,依名稱將數值分別存入 xValue、yValue。在「重複無限次」中,利用下列差速控制公式設定馬達速度:

左輪速度 = yValue + xValue
右輪速度 = yValue − xValue

當搖桿向前推時,兩輪同向前轉;向右推時,左輪速度增加、右輪速度降低,小車便向右轉。向左、向後的原理相同。

圖2 Q霸小車端積木程式

五、完成後的操作方式



圖3 Q霸小車與 Joystick:bit 擴充搖桿實體外觀
搖桿操作小車動作
向前推前進
向後拉後退
向左推左轉
向右推右轉
回到中央停止或接近停止

六、除錯小提醒

  1. 小車沒有反應:先確認兩端廣播群組是否都為 51,以及兩片 micro:bit 是否都有供電。
  2. 前後方向相反:將 y 軸對應範圍由 -100~100 改為 100~-100,或將馬達速度乘以 -1。
  3. 左右方向相反:將 x 軸對應範圍反向,或在小車端交換加、減的運算方向。
  4. 停下仍會緩慢移動:這是搖桿中心值的誤差。可設定小範圍死區,例如 x、y 介於 -10~10 時改為 0。

七、延伸挑戰

  • 按下搖桿按鍵時,讓小車發出聲音或亮起車燈。
  • 加入超音波避障:前方太近時,即使搖桿要求前進也自動停車。
  • 將小車改造成校園導覽車、農場巡檢車或智慧物流車,設計任務關卡。
教學重點:這個作品不是只讓小車「動起來」,而是把感測輸入、無線傳輸與馬達輸出串成完整的智慧控制系統。學生可從中理解物聯網裝置如何把人的操作,轉換成真實世界的行動。

2026年1月22日 星期四

[Q霸小車] 第三堂AI鏡頭初體驗



範例一、辦識前進、後退、左轉、右轉、停止


Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
PlanetX_AILens.init_module()
PlanetX_AILens.switchfunc(PlanetX_AILens.FuncList.CARD)

def on_forever():
    PlanetX_AILens.camera_image()
    if PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.FORWARD):
        basic.show_leds("""
            . . # . .
            . # # # .
            # . # . #
            . . # . .
            . . # . .
            """)
    elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.BACK):
        basic.show_leds("""
            . . # . .
            . . # . .
            # . # . #
            . # # # .
            . . # . .
            """)
    elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.STOP):
        basic.show_leds("""
            . . . . .
            . # # # .
            . # # # .
            . # # # .
            . . . . .
            """)
    elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.TURNLEFT):
        basic.show_leds("""
            . . # . .
            . # . . .
            # # # # #
            . # . . .
            . . # . .
            """)
    elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.TURNRIGHT):
        basic.show_leds("""
            . . # . .
            . . . # .
            # # # # #
            . . . # .
            . . # . .
            """)
basic.forever(on_forever)

範例二、第三堂完整程式

Python程式:


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def on_button_pressed_a():
    global mode
    mode = (mode + 1) % 5
    basic.show_number(mode)
input.on_button_pressed(Button.A, on_button_pressed_a)

def on_button_pressed_b():
    global mode
    if mode > 0:
        mode = mode - 1
    else:
        mode = 4
    basic.show_number(mode)
input.on_button_pressed(Button.B, on_button_pressed_b)

mode = 0
PlanetX_AILens.init_module()
PlanetX_AILens.switchfunc(PlanetX_AILens.FuncList.CARD)
mode = 0
basic.show_number(mode)
car = 0

def on_forever():
    global car
    PlanetX_AILens.camera_image()
    if mode == 0:
        if PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.FORWARD):
            basic.show_leds("""
                . . # . .
                . # # # .
                # . # . #
                . . # . .
                . . # . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.BACK):
            basic.show_leds("""
                . . # . .
                . . # . .
                # . # . #
                . # # # .
                . . # . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.STOP):
            basic.show_leds("""
                . . . . .
                . # # # .
                . # # # .
                . # # # .
                . . . . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.TURNLEFT):
            basic.show_leds("""
                . . # . .
                . # . . .
                # # # # #
                . # . . .
                . . # . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.TURNRIGHT):
            basic.show_leds("""
                . . # . .
                . . . # .
                # # # # #
                . . . # .
                . . # . .
                """)
    elif mode == 1:
        if PlanetX_AILens.other_card(PlanetX_AILens.otherCards.MOUSE):
            basic.show_string("Mouse")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.MICROBIT):
            basic.show_string("micro\"bit")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.RULER):
            basic.show_string("Ruler")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.CAT):
            basic.show_string("Cat")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.PEAR):
            basic.show_string("Pear")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.SHIP):
            basic.show_string("Ship")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.APPLE):
            basic.show_string("Apple")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.CAR):
            basic.show_string("Car")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.PEN):
            basic.show_string("Pen")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.DOG):
            basic.show_string("Dog")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.UMBRELLA):
            basic.show_string("Umbrella")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.AIRPLANE):
            basic.show_string("Airplane")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.CLOCK):
            basic.show_string("clock")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.GRAPE):
            basic.show_string("Grape")
        elif PlanetX_AILens.other_card(PlanetX_AILens.otherCards.CUP):
            basic.show_string("Cup")
    elif mode == 2:
        if PlanetX_AILens.number_card(PlanetX_AILens.numberCards.ZERO):
            basic.show_number(0)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.ONE):
            basic.show_number(1)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.TWO):
            basic.show_number(2)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.THREE):
            basic.show_number(3)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.FOUR):
            basic.show_number(4)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.FIVE):
            basic.show_number(5)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.SIX):
            basic.show_number(6)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.SEVEN):
            basic.show_number(7)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.EIGHT):
            basic.show_number(8)
        elif PlanetX_AILens.number_card(PlanetX_AILens.numberCards.NINE):
            basic.show_number(9)
    elif mode == 3:
        if PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.A):
            basic.show_string("A")
        elif PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.B):
            basic.show_string("B")
        elif PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.C):
            basic.show_string("C")
        elif PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.D):
            basic.show_string("D")
        elif PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.E):
            basic.show_string("E")
    elif mode == 4:
        if PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.FORWARD):
            car = 1
            basic.show_leds("""
                . . # . .
                . # # # .
                # . # . #
                . . # . .
                . . # . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.BACK):
            car = 2
            basic.show_leds("""
                . . # . .
                . . # . .
                # . # . #
                . # # # .
                . . # . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.STOP):
            car = 0
            basic.show_leds("""
                . . . . .
                . # # # .
                . # # # .
                . # # # .
                . . . . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.TURNLEFT):
            car = 3
            basic.show_leds("""
                . . # . .
                . # . . .
                # # # # #
                . # . . .
                . . # . .
                """)
        elif PlanetX_AILens.traffic_card(PlanetX_AILens.trafficCards.TURNRIGHT):
            car = 4
            basic.show_leds("""
                . . # . .
                . . . # .
                # # # # #
                . . . # .
                . . # . .
                """)
    basic.pause(100)
basic.forever(on_forever)

def on_forever2():
    global car
    if car == 0:
        cuteBot.stopcar()
    elif car == 1:
        cuteBot.motors(30, 30)
    elif car == 2:
        cuteBot.motors(-30, -30)
    elif car == 3:
        cuteBot.motors(-30, 30)
        basic.pause(250)
        car = 0
    elif car == 4:
        cuteBot.motors(30, -30)
        basic.pause(250)
        car = 0
basic.forever(on_forever2)