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

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



モーターは黒い固定部品で留めます。まず基板に印刷された白い線に合わせてモーターを置き、その上から黒い部品をかぶせます。きちんとはまると、黒い部品が前後に動かなくなります。
穴の位置を合わせ、基板の裏からネジを通して固定します。締めすぎるとネジ穴がバカになるので、程よい力で締めてください。
配線をつなぎます。モーター先端の白いコネクターを基板側の白いコネクターに、赤い線が右側に来る向きで差し込みます。
もう一方のモーターも同じ手順で取り付けます。こちらの配線も、赤い線が右側に来る向きで差し込みます(左右とも赤が右)。配線が長いので、邪魔にならないよう畳んでおきましょう。



前輪のボールキャスターには、はじめからネジが2本入っています。まずこのネジを外します。
ネジを外すと蓋が取れて、中のボールが自由に動く状態になります。ボールが転がって紛失しないよう注意してください。
蓋を閉じた状態で、ベース裏側の穴に位置を合わせ、外したネジで固定します。右の写真のように取り付けられれば完了です。


モーターの軸は横から見るとD形(片側が平ら)になっています。タイヤ穴の平らな面と向きを合わせ、グッと差し込みます(両輪とも)。
最後に超音波センサーをロボットの前方向に向け、4本のピンを差し込みます。

Pico W カードの取り付けに使う部品です。写真の番号と下のリストが対応しています。
※ 5・6 は別売りです(別途ご用意ください)。写真左は組み立て済みの UME Base 本体です。

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


カード裏面には40ピンの針が出ています。これを梅ベースの40個の穴にまっすぐ差し込みます。ずれずにぴったり入るはずです。
差し込んだら、表面から4箇所ネジで留めます。



Raspberry Pi Pico W を、USB 端子が左に来る向きでカードに差し込みます。
電池を入れます。向きがありますので、電池ボックスに書かれた+−のマークをよく見て入れてください。
最後に LCD モジュールを4つの穴に差し込んで完成です。
電源スイッチを左に倒して ON にします。上下の LED が両方とも光れば正常です。右に倒すと OFF になり、LED が消えます。
基板を使いこなすための、ピンアサイン・テスト用プログラム・回路図です。Raspberry Pi Pico(W)+MicroPython を想定。
| 機能 | GPIO | 40ピン# | 備考 |
|---|---|---|---|
| モーター 左 (IN1/IN2) | GP0 / GP1 | #25 / #26 | SS8837T・PWM |
| モーター 右 (IN1/IN2) | GP2 / GP3 | #27 / #28 | GP2駆動=前進(V3) |
| ラインセンサ L / C / R | GP26 / GP27 / GP28 | #29 / #30 / #31 | TCRT5000・ADC0-2・3.3V |
| 超音波 ECHO / TRIG | GP6 / GP7 | #32 / #33 | ECHOはV3で分圧3.0V |
| サーボ ×3 (J5/J6/J7) | GP8 / GP9 / GP10 | #34 / #35 / #36 | 電源=Vin |
| LED ×2 | GP11 / GP12 | #37 / #38 | 1kΩ直列・H点灯 |
| プッシュSW ×2 | GP13 / GP14 | #39 / #40 | V3で3.3V化・押下=H |
| I2C (OLED) | GP4=SDA / GP5=SCL | #20 / #19 | 0x3C・V3でプルアップ4.7k |
| 電源 | Vin / +5V / +3.3V / GND | 両列ミラー | 電池→Vin→MT3608→5V→XC6206→3.3V |
※ ピン定義は pins.py(唯一の真実)に準拠。GPIO は Pico のピン番号。
動作確認用の自己完結スクリプト(pins.py 不要・メニュー式)。Thonny で開いて実行、または
mpremote connect auto run 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()
クリックで拡大(別タブ)。