UME Base 組み立て説明書

マイコンを差し替えて学ぶ、入門ロボット
完成した PoliviaBot UME

PREPARE用意するもの

写真の番号と下のリストが対応しています。組み立てを始める前に、部品がそろっているか確認しましょう。

  1. ベース基板(UME Base 本体)
  2. ギアモーター × 2
  3. モーター固定用の黒い部品 × 2
  4. タイヤ × 2
  5. ボールキャスター × 1
  6. 超音波センサー × 1
  7. ネジ × 4

※ Raspberry Pi Pico W カードの部品は PART 2 の最初で確認します。

PART 1UME Base 本体を組み立てる

1

モーターの取り付け

モーターは黒い固定部品で留めます。まず基板に印刷された白い線に合わせてモーターを置き、その上から黒い部品をかぶせます。きちんとはまると、黒い部品が前後に動かなくなります。

穴の位置を合わせ、基板の裏からネジを通して固定します。締めすぎるとネジ穴がバカになるので、程よい力で締めてください。

配線をつなぎます。モーター先端の白いコネクターを基板側の白いコネクターに、赤い線が右側に来る向きで差し込みます。

もう一方のモーターも同じ手順で取り付けます。こちらの配線も、赤い線が右側に来る向きで差し込みます(左右とも赤が右)。配線が長いので、邪魔にならないよう畳んでおきましょう。

💡 コツ黒い部品をかぶせたとき前後にガタつかないか確認してから、裏のネジを締めます。ネジは締めすぎ注意です。
2

ボールキャスターの取り付け

前輪のボールキャスターには、はじめからネジが2本入っています。まずこのネジを外します

ネジを外すと蓋が取れて、中のボールが自由に動く状態になります。ボールが転がって紛失しないよう注意してください。

蓋を閉じた状態で、ベース裏側の穴に位置を合わせ、外したネジで固定します。右の写真のように取り付けられれば完了です。

💡 コツキャスターのネジも締めすぎるとネジ穴がバカになります。慎重に締めてください。
3

タイヤと超音波センサーの取り付け

モーターの軸は横から見るとD形(片側が平ら)になっています。タイヤ穴の平らな面と向きを合わせ、グッと差し込みます(両輪とも)。

最後に超音波センサーをロボットの前方向に向け、4本のピンを差し込みます。

✅ これで UME Base 本体は完成です。

PART 2Raspberry Pi Pico W カードを取り付ける

4

部品の確認

Pico W カードの取り付けに使う部品です。写真の番号と下のリストが対応しています。

  1. カード本体
  2. スペーサー × 4
  3. ネジ × 8
  4. LCD モジュール
  5. 単4電池 × 3(別売り)
  6. Raspberry Pi Pico W 本体(別売り)

5・6 は別売りです(別途ご用意ください)。写真左は組み立て済みの UME Base 本体です。

5

スペーサーの固定

梅ベースの4つの穴に金色のスペーサーを取り付けます。穴の位置を合わせ、裏面からネジで4箇所固定します。

💡 コツ先にネジを通して手で軽く回し、位置が合ったところでドライバーで締めると安定して作業できます。
6

カードの取り付け

カード裏面には40ピンの針が出ています。これを梅ベースの40個の穴にまっすぐ差し込みます。ずれずにぴったり入るはずです。

差し込んだら、表面から4箇所ネジで留めます。

💡 コツピンが曲がっていると入りにくくなります。すべての穴に入っているか確認してからグッと差し込みます。
7

Pico W・電池・LCD の取り付け

Raspberry Pi Pico W を、USB 端子が左に来る向きでカードに差し込みます。

電池を入れます。向きがありますので、電池ボックスに書かれた+−のマークをよく見て入れてください。

最後に LCD モジュールを4つの穴に差し込んで完成です。

💡 コツ電池の向きを間違えると動作しません。ボックスの+−表示を必ず確認してください。

完成!

完成

🔌 動作確認

電源スイッチを左に倒して ON にします。上下の LED が両方とも光れば正常です。右に倒すと OFF になり、LED が消えます。

APPENDIX技術資料(開発者向け)

基板を使いこなすための、ピンアサイン・テスト用プログラム・回路図です。Raspberry Pi Pico(W)+MicroPython を想定。

A. ピンアサイン

機能GPIO40ピン#備考
モーター 左 (IN1/IN2)GP0 / GP1#25 / #26SS8837T・PWM
モーター 右 (IN1/IN2)GP2 / GP3#27 / #28GP2駆動=前進(V3)
ラインセンサ L / C / RGP26 / GP27 / GP28#29 / #30 / #31TCRT5000・ADC0-2・3.3V
超音波 ECHO / TRIGGP6 / GP7#32 / #33ECHOはV3で分圧3.0V
サーボ ×3 (J5/J6/J7)GP8 / GP9 / GP10#34 / #35 / #36電源=Vin
LED ×2GP11 / GP12#37 / #381kΩ直列・H点灯
プッシュSW ×2GP13 / GP14#39 / #40V3で3.3V化・押下=H
I2C (OLED)GP4=SDA / GP5=SCL#20 / #190x3C・V3でプルアップ4.7k
電源Vin / +5V / +3.3V / GND両列ミラー電池→Vin→MT3608→5V→XC6206→3.3V

※ ピン定義は pins.py(唯一の真実)に準拠。GPIO は Pico のピン番号。

B. テスト用プログラム

動作確認用の自己完結スクリプト(pins.py 不要・メニュー式)。Thonny で開いて実行、または mpremote connect auto run ume_bringup.py で実行します。  ⬇ ダウンロード

ume_bringup.py(全文・クリックで開く)
# -*- coding: utf-8 -*-
# PoliviaBot UME V3 + RaspyPicoW V3 ブリングアップ(自己完結・1ファイル)
#
#   mpremote connect auto run ume_bringup.py
#
# pins.py も lib も投入不要(ピン定義・OLEDドライバを内蔵)。メニューで各テストを選ぶ。
# V3 で是正済み(実機検証は本スクリプトで):
#   ① U5(HC-SR04)配置反転是正 → 正挿しで逆接なし   ② 右モータOUT入替 → GP2駆動で前進
#   ③ D2 SS14→SS34 → 突入電流で発熱しない          ④ SW高電位 3.3V化 → 押下でGPIO定格内
#   ⑤ ECHO分圧 2.2k/3.3k=3.0V → GP6定格内          ⑥ I2Cプルアップ4.7k → 400kHz一括OLED可
# ピン定義は pins.py(唯一の真実)と一致させること(変更時は両方直す)。
import time
from machine import Pin, PWM, ADC, I2C, time_pulse_us
import framebuf

# ===== ピン定義(pins.py と一致)=====
MOTOR_L_IN1, MOTOR_L_IN2 = 0, 1
MOTOR_R_IN1, MOTOR_R_IN2 = 2, 3
LINE_L, LINE_C, LINE_R = 26, 27, 28
SONAR_ECHO, SONAR_TRIG = 6, 7
SERVO_0, SERVO_1, SERVO_2 = 8, 9, 10
LED_1, LED_2 = 11, 12
SW_1, SW_2 = 13, 14
PRESSED = 1                       # プルダウン・押下=High
I2C_SDA, I2C_SCL = 4, 5
OLED_ADDR = 0x3C


# ===== SSD1306 最小ドライバ(内蔵・ページ分割show)=====
class SSD1306_I2C(framebuf.FrameBuffer):
    def __init__(self, width, height, i2c, addr=0x3C):
        self.width, self.height, self.i2c, self.addr = width, height, i2c, addr
        self.pages = height // 8
        self.buffer = bytearray(self.pages * width)
        super().__init__(self.buffer, width, height, framebuf.MONO_VLSB)
        for cmd in (0xAE, 0x20, 0x00, 0x40, 0xA1, 0xA8, height - 1, 0xC8, 0xD3, 0x00,
                    0xDA, 0x12 if height == 64 else 0x02, 0xD5, 0x80, 0xD9, 0xF1,
                    0xDB, 0x30, 0x81, 0xFF, 0xA4, 0xA6, 0x8D, 0x14, 0xAF):
            self.i2c.writeto(self.addr, bytes([0x80, cmd]))
        self.fill(0); self.show()

    def _win(self):
        for c in (0x21, 0, self.width - 1, 0x22, 0, self.pages - 1):
            self.i2c.writeto(self.addr, bytes([0x80, c]))

    def show(self):                       # ページ分割(どの速度でも安全)
        self._win()
        w = self.width
        for p in range(self.pages):
            self.i2c.writeto(self.addr, b"\x40" + self.buffer[p * w:(p + 1) * w])

    def show_bulk(self):                  # 一括転送(V3 プルアップ検証用・400kHz想定)
        self._win()
        self.i2c.writeto(self.addr, b"\x40" + self.buffer)


# ===== 各テスト =====
def test_led():
    print("[01] LED1(GP11)/LED2(GP12)+本体LED を交互5回点滅")
    l1 = Pin(LED_1, Pin.OUT, 0); l2 = Pin(LED_2, Pin.OUT, 0)
    try:
        ob = Pin("LED", Pin.OUT)
    except Exception:
        try:
            ob = Pin(25, Pin.OUT)
        except Exception:
            ob = None
    for _ in range(5):
        l1.value(1); l2.value(0); ob and ob.value(1); time.sleep(0.3)
        l1.value(0); l2.value(1); ob and ob.value(0); time.sleep(0.3)
    l1.value(0); l2.value(0); ob and ob.value(0)
    print("  → 両方点滅で合格")


def test_switch(sec=15):
    print("[02] SW1(GP13)/SW2(GP14) 状態表示(V3:3.3V側・押下=1)/ %d秒" % sec)
    sw1 = Pin(SW_1, Pin.IN); sw2 = Pin(SW_2, Pin.IN)
    t0 = time.ticks_ms(); prev = None
    while time.ticks_diff(time.ticks_ms(), t0) < sec * 1000:
        cur = (sw1.value(), sw2.value())
        if cur != prev:
            print("  SW1=%d SW2=%d" % cur); prev = cur
        time.sleep(0.02)
    print("  → 押すと該当が1になれば合格(V3はGPIO定格内)")


def test_line(sec=15):
    print("[03] ラインセンサ L/C/R 生値 / %d秒(白で下降・黒/浮かしで上昇)" % sec)
    a = (ADC(LINE_L), ADC(LINE_C), ADC(LINE_R))
    t0 = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), t0) < sec * 1000:
        v = [x.read_u16() for x in a]
        print("  L=%5d C=%5d R=%5d  %s" % (v[0], v[1], v[2], "#" * (sum(v) // 24000)))
        time.sleep(0.25)


def test_oled():
    print("[04] I2C0スキャン+OLED(V3プルアップを400kHzで検証)")
    i2c = I2C(0, sda=Pin(I2C_SDA), scl=Pin(I2C_SCL), freq=400_000)
    found = i2c.scan()
    print("  scan:", [hex(x) for x in found])
    if OLED_ADDR not in found:
        print("  0x3C なし → 電池ON・OLED装着/向き(J4:1GND 2VCC 3SCL 4SDA)を確認")
        return
    o = SSD1306_I2C(128, 64, i2c, OLED_ADDR)
    o.fill(0); o.rect(0, 0, 128, 64, 1)
    o.text("PoliviaBot UME", 4, 8); o.text("V3 bring-up", 4, 22)
    o.text("I2C 400kHz", 4, 36); o.text(repr(time.localtime())[:13], 4, 50)
    try:
        o.show_bulk()      # 一括転送が通れば V3 プルアップが効いている
        print("  → 表示OK+400kHz一括転送OK(⑥I2Cプルアップ有効)")
    except OSError as e:
        o.show()           # ダメなら分割(V2相当)
        print("  → 一括転送NG(%s)→分割で表示。プルアップ未実装/不良の疑い" % e)


def test_motor():
    print("[05] モータ L/R(スローディケイ・要・車輪を浮かす/V3:両輪とも前進方向か確認)")
    if input("  ★車輪を浮かせたか? [y/N]: ").strip().lower() != "y":
        print("  中止"); return
    FREQ, WEAK, MID, FULL = 20000, 30000, 45000, 65535
    def mk(p): q = PWM(Pin(p)); q.freq(FREQ); q.duty_u16(0); return q
    L1, L2, R1, R2 = mk(MOTOR_L_IN1), mk(MOTOR_L_IN2), mk(MOTOR_R_IN1), mk(MOTOR_R_IN2)
    fwd = lambda a, b, d: (a.duty_u16(FULL), b.duty_u16(FULL - d))
    rev = lambda a, b, d: (b.duty_u16(FULL), a.duty_u16(FULL - d))
    off = lambda a, b: (a.duty_u16(0), b.duty_u16(0))
    try:
        for nm, a, b in (("左L", L1, L2), ("右R", R1, R2)):
            print("  --- %s:前進 弱→中 → 停止 → 後進 中 → 停止" % nm)
            fwd(a, b, WEAK); time.sleep(1.3); fwd(a, b, MID); time.sleep(1.3)
            off(a, b); time.sleep(0.8); rev(a, b, MID); time.sleep(1.3); off(a, b); time.sleep(0.6)
        print("  → V3は左右とも『前進』で同じ向きに進めば合格(GP2駆動=右前進)")
        print("  ※発熱したら即電源OFF(D2=SS34化で突入電流対策済のはず)")
    finally:
        for p in (L1, L2, R1, R2):
            p.duty_u16(0); p.deinit()


def test_servo():
    print("[06] サーボ J5/J6/J7(GP8/9/10)中央→左→右→中央")
    d = lambda us: int(us * 65535 // 20000)
    for nm, gp in (("S0 J5", SERVO_0), ("S1 J6", SERVO_1), ("S2 J7", SERVO_2)):
        print("  ---", nm)
        s = PWM(Pin(gp)); s.freq(50)
        for us in (1500, 1000, 2000, 1500):
            s.duty_u16(d(us)); time.sleep(0.8)
        s.duty_u16(0); s.deinit()


def test_sonar(sec=15):
    print("[07] 超音波 HC-SR04 距離(V3:ECHO分圧3.0V・正挿し)/ %d秒" % sec)
    trig = Pin(SONAR_TRIG, Pin.OUT, 0); echo = Pin(SONAR_ECHO, Pin.IN)
    t0 = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), t0) < sec * 1000:
        trig.value(0); time.sleep_us(5); trig.value(1); time.sleep_us(10); trig.value(0)
        dur = time_pulse_us(echo, 1, 30000)
        if dur < 0:
            print("  応答なし(給電5V・向き・分圧を確認)")
        else:
            print("  %.1f cm (echo %d us)" % (dur * 0.0343 / 2, dur))
        time.sleep(0.4)


def info():
    print("ピン: MOTOR L=GP0/1 R=GP2/3 / LINE=GP26/27/28 / SONAR TRIG=GP7 ECHO=GP6")
    print("      SERVO=GP8/9/10 / LED=GP11/12 / SW=GP13/14 / I2C=GP4(SDA)/GP5(SCL) 0x3C")
    print("電源: 電池→SW3→Vin→MT3608→5V→XC6206→3V3 / USB単独はベース無給電(センサ等は電池ON)")


TESTS = {"1": test_led, "2": test_switch, "3": test_line, "4": test_oled,
         "5": test_motor, "6": test_servo, "7": test_sonar, "i": info}


def menu():
    print("\n==== PoliviaBot UME V3 ブリングアップ ====")
    while True:
        print("\n 1:LED 2:SW 3:ライン 4:OLED 5:モータ 6:サーボ 7:超音波  i:ピン情報  q:終了")
        sel = input(" 番号> ").strip().lower()
        if sel == "q":
            print("終了"); return
        fn = TESTS.get(sel)
        if not fn:
            print("  不明な入力"); continue
        try:
            fn()
        except KeyboardInterrupt:
            print("  (中断)")
        except Exception as e:
            print("  エラー:", e)


menu()

C. 回路図

クリックで拡大(別タブ)。

UME ベース基板
UMEベース回路図
Pico W カード(キャリア)
Pico Wカード回路図