2026年9月3日 星期四

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),其中 VM 必須大寫。
館員提醒:為每組設備貼上相同顏色與編號,並將搖桿與小車的 pairId 預先設好。這是讓多位兒童同時操作時仍保持安全、清楚且可管理的關鍵。

沒有留言:

張貼留言