micro:bitを使って、メカナムホイールのラジコンカーを作ってみた

スポンサーリンク

今ある機材にメカナムホイールを足せば、以前から気になってた全方位移動ラジコンカーが作れるので、思い切ってホイールを買って作ってみた。

材料

車体側

micro:bit
Yahboom Super:bit(micro:bit拡張ボード)
geek moter?(geek servoと書かれたレゴに接続できる赤いモーター) 4つ
メカナムホイール 4つ
レゴブロック

メカナムホイールはスイッチサイエンスで試験的に取り扱っているLEGOに接続できるプラスチックメカナムホイールを選択。

リモコン側

micro:bit
ジョイスティック付きコントローラーキット

完成した結果

こんな感じ。

f:id:sato_susumu:20200830215009j:plain

コントローラーにアナログスティックがあるので、全方向に緩急をつけて移動できるようにしてみた。
メカナムホイール固有の車っぽくないSFチックな動きが面白い。
灰色のギアがたまーに外れるのが玉にキズだけど、十分満足な出来栄え。(自画自賛)

基板は子供が直接に触れにくいように簡単にブロックで囲ってる。

下から見た写真。

f:id:sato_susumu:20200830215012j:plain

モータとギア1セットの詳細。
モータはレゴブロックに直接つながるのは大変ありがたいけど、0.5ポッチ単位の調整がいるので正直少し扱いづらい。

f:id:sato_susumu:20200830215015j:plain

失敗した点

その1

この形にたどり着くまでに2台作ってボツにした。
1台目はモーターにメカナムホイールを直付けしたけど、スピードが遅すぎて物足りなかったのでボツ。
2台目は複数のギアを組み込み、タイヤの回転を早くしたけど、パワーが弱すぎてほとんど横移動ができなかったのでボツ。
3台目でやっと納得のいくものが作れた。
レゴをベースにしているので、気楽に作ったり壊したりできるのがいいところ。

その2

前進/後退はできる。左右の旋回もできる。けど、横移動は左右逆向きに移動する。
原因はホイールの取り付けミスだった。 メカナムホイールには取り付けの向きがあるが、裏表を勘違いして全部逆に取り付けてしまった。
前後方向は問題がなかったので、最初、何を間違えたのかわからなかった。

その3

後日、次は前進/後退はできる。左右の旋回もできる。けど、横移動しようとすると移動せずにガタガタ震えるという現象に遭遇した。
ホイールの取り付けは問題ない。
原因は左側前後のホイールと基板との配線ミス。
あとからだったらホイールの動きを見たらわかるけど、最初、何を間違えたのか、またわからなかった。

プログラムの作成

次のページを参考に適当に作成。

toioでメカナムホイール制御を楽しむ
How a Mecanum Drive Works

コントローラー側のMakeCode用プログラム(python)

無線機能で左右スティックのXYとABボタンの押下状況を送信するプログラム。
XとYの範囲は-100〜100で、0が中央、-100が左の端(もしくは上の端)
ニュートラルでもキレイに0にはならないが、10以下くらいにはなる。

joy_b = 0
joy_a = 0
joy_ry = 0
joy_rx = 0
joy_ly = 0
joy_lx = 0
ch = 9
radio.set_group(ch)
basic.show_number(ch)

def on_forever():
    global joy_lx, joy_ly, joy_rx, joy_ry, joy_a, joy_b
    # micro:bit用ジョイスティック付きコントローラーキット の仕様
    # https://www.switch-science.com/catalog/5308/
    # P2に0書き込み:左ジョイスティック選択
    # P2に1書き込み:右ジョイスティック選択
    # P0読み込み:ジョイスティックのアナログ値X方向(0〜1023) 0:左端 1023:右端
    # P1読み込み:ジョイスティックのアナログ値Y方向(0〜1023) 0:下端 1023:上端
    pins.digital_write_pin(DigitalPin.P2, 0)
    joy_lx = Math.map(pins.analog_read_pin(AnalogPin.P0), 0, 1023, -100, 100)
    joy_ly = Math.map(pins.analog_read_pin(AnalogPin.P1), 0, 1023, -100, 100)
    pins.digital_write_pin(DigitalPin.P2, 1)
    joy_rx = Math.map(pins.analog_read_pin(AnalogPin.P0), 0, 1023, -100, 100)
    joy_ry = Math.map(pins.analog_read_pin(AnalogPin.P1), 0, 1023, -100, 100)
    if input.button_is_pressed(Button.A):
        joy_a = 1
    else:
        joy_a = 0
    if input.button_is_pressed(Button.B):
        joy_b = 1
    else:
        joy_b = 0
    radio.send_value("LX", joy_lx)
    radio.send_value("LY", joy_ly)
    radio.send_value("RX", joy_rx)
    radio.send_value("RY", joy_ry)
    radio.send_value("A", joy_a)
    radio.send_value("B", joy_b)
basic.forever(on_forever)

def on_forever2():
    basic.pause(1000)
    serial.write_value("LX", joy_lx)
    serial.write_value("LY", joy_ly)
    serial.write_value("RX", joy_rx)
    serial.write_value("RY", joy_ry)
    serial.write_value("A", joy_a)
    serial.write_value("B", joy_b)
basic.forever(on_forever2)

車体側のMakeCode用プログラム(python) 通常版

基本的には無線機能で受け取った左スティックの方向に合わせて、モータを制御するだけ。

Super:bitを扱うので、予め拡張機能 https://github.com/lzty634158/SuperBit の追加が必要。

def update_servo_value():
    magnitude = Math.sqrt((joy_lx/100.0)**2 + (joy_ly/100.0)**2)
    magnitude = Math.min(magnitude, 1.0)
    base_angle = Math.atan2(joy_ly, joy_lx)
    angle = base_angle - Math.PI / 4
    rightX = joy_rx / 100.0
    v1 = magnitude * Math.cos(angle) + rightX
    v2 = magnitude * Math.sin(angle) - rightX
    v3 = magnitude * Math.sin(angle) + rightX
    v4 = magnitude * Math.cos(angle) - rightX

    # 計算確認用のため日頃はコメントアウト
    # serial.write_value("joy_lx", joy_lx)
    # serial.write_value("joy_ly", joy_ly)
    # serial.write_value("joy_rx", joy_rx)
    # serial.write_value("magnitude", magnitude)
    # serial.write_value("base_angle", base_angle)
    # serial.write_value("angle", angle)
    # serial.write_value("v1", v1)
    # serial.write_value("v2", v2)
    # serial.write_value("v3", v3)
    # serial.write_value("v4", v4)

    # left front
    SuperBit.motor_run(SuperBit.enMotors.M1, v1 * 255)
    # right front
    SuperBit.motor_run(SuperBit.enMotors.M3, v2 * 255)
    # left rear
    SuperBit.motor_run(SuperBit.enMotors.M2, v3 * 255)
    # right rear
    SuperBit.motor_run(SuperBit.enMotors.M4, v4 * 255)

def on_received_value(name, value):
    global joy_lx, joy_ly, joy_rx, joy_ry, joy_a, joy_b
    if name == "LX":
        joy_lx = value
    if name == "LY":
        joy_ly = value
    if name == "RX":
        joy_rx = value
    if name == "RY":
        joy_ry = value
    if name == "A":
        joy_a = value
    if name == "B":
        joy_b = value
    update_servo_value()
radio.on_received_value(on_received_value)

joy_ry = 0
joy_rx = 0
joy_ly = 0
joy_lx = 0
joy_a = 0
joy_b = 0

radio_ch = 9
radio.set_group(radio_ch)
basic.show_string(str(radio_ch))

def on_forever():
    basic.pause(1000)
    serial.write_value("LX", joy_lx)
    serial.write_value("LY", joy_ly)
    serial.write_value("RX", joy_rx)
    serial.write_value("RY", joy_ry)
    serial.write_value("A", joy_a)
    serial.write_value("B", joy_b)
basic.forever(on_forever)

number = 0

def test():
    basic.pause(1000)
    dummy_lx = 0
    dummy_ly = 0
    dummy_rx = 0
    dummy_ry = 0
    dummy_a = 0
    dummy_b = 0

    global number
    if number % 5 == 0:
        dummy_lx = 100
    elif number % 5 == 1:
        dummy_lx = -100

    if number % 4 == 0:
        dummy_ly = 100
    elif number % 4 == 1:
        dummy_ly = -100
    number = number + 1

    on_received_value("LX", dummy_lx)
    on_received_value("LY", dummy_ly)
    on_received_value("RX", dummy_rx)
    on_received_value("RY", dummy_ry)
    on_received_value("A", dummy_a)
    on_received_value("B", dummy_b)

# 疑似受信テスト用のため日頃はコメントアウト
# basic.forever(test)

車体側のMakeCode用プログラム(python) メンテナンス版

2つのアナログスティックでメカナムホイールを直接操作したかったので作成した誰得プログラム。自分用。

Super:bitを扱うので、予め拡張機能 https://github.com/lzty634158/SuperBit の追加が必要。

左スティック
上:左前輪(正回転)  下:左前輪(逆回転)
右:左後輪(正回転)  左:左後輪(逆回転)

右スティック
上:右前輪(正回転)  下:右前輪(逆回転)
右:右後輪(正回転)  左:右後輪(逆回転)

def update_servo_value():
    # left front
    SuperBit.motor_run(SuperBit.enMotors.M1, joy_ly / 100.0 * 255)
    # right front
    SuperBit.motor_run(SuperBit.enMotors.M3, joy_ry / 100.0 * 255)
    # left rear
    SuperBit.motor_run(SuperBit.enMotors.M2, joy_lx / 100.0 * 255)
    # right rear
    SuperBit.motor_run(SuperBit.enMotors.M4, joy_rx / 100.0 * 255)

def on_received_value(name, value):
    global joy_lx, joy_ly, joy_rx, joy_ry, joy_a, joy_b
    if name == "LX":
        joy_lx = value
    if name == "LY":
        joy_ly = value
    if name == "RX":
        joy_rx = value
    if name == "RY":
        joy_ry = value
    if name == "A":
        joy_a = value
    if name == "B":
        joy_b = value
    update_servo_value()
radio.on_received_value(on_received_value)

joy_ry = 0
joy_rx = 0
joy_ly = 0
joy_lx = 0
joy_a = 0
joy_b = 0

radio_ch = 9
radio.set_group(radio_ch)
basic.show_string(str(radio_ch))