戻る

# 20250831-robotcar-receiver.py
# XIAO ESP32-C3 ロボットカー受信側(ESP-NOW)
# 送信側は "forward/backward/left_turn/right_turn/stop" のみでOK
# 速度はロボット側ローカル変数 SPEED_U16 で決定(比率は無視)

from machine import Pin, PWM
import network, espnow, time

# ====== ユーザー設定(ここを変えるだけで速度変更)======
# PWMデューティ(16bit: 0..65535)
SPEED_U16 = 40000     # ★この値を直接変える(例:30000 / 50000 など)
MIN_SPEED = 8000      # 最低限回る下限(モータ特性に合わせて)
MAX_SPEED = 65535     # 上限

# (任意)速度を段階的に切り替えたい場合のプリセット
SPEED_LEVELS = [15000, 22000, 35000, 45000, 55000, 65000]
_current_level = 3    # SPEED_LEVELS の初期インデックス(上の配列4番目=40000)

# (任意)本体ボタンで速度調整したい場合に使うピン(不要なら None のまま)
PIN_SPEED_UP   = None   # 例: 6
PIN_SPEED_DOWN = None   # 例: 7
DEBOUNCE_MS = 180

# ====== モータ配線(XIAO ESP32-C3)======
AIN1 = PWM(Pin(2))   # 左 正転
AIN2 = PWM(Pin(3))   # 左 逆転
BIN1 = PWM(Pin(4))   # 右 正転
BIN2 = PWM(Pin(5))   # 右 逆転
for m in (AIN1, AIN2, BIN1, BIN2):
    m.freq(1000)

def _set_duty(p, u16):
    if u16 < 0: u16 = 0
    if u16 > 65535: u16 = 65535
    if hasattr(p, "duty_u16"):
        p.duty_u16(u16)
    else:
        p.duty(int(u16/64))

def stop():
    _set_duty(AIN1,0); _set_duty(AIN2,0)
    _set_duty(BIN1,0); _set_duty(BIN2,0)

def forward(speed_u16):
    _set_duty(AIN1,speed_u16); _set_duty(AIN2,0)
    _set_duty(BIN1,speed_u16); _set_duty(BIN2,0)

def backward(speed_u16):
    _set_duty(AIN1,0); _set_duty(AIN2,speed_u16)
    _set_duty(BIN1,0); _set_duty(BIN2,speed_u16)

def left_turn(speed_u16):   # その場回転
    _set_duty(AIN1,0);            _set_duty(AIN2,speed_u16)  # 左 逆
    _set_duty(BIN1,speed_u16);    _set_duty(BIN2,0)          # 右 正

def right_turn(speed_u16):
    _set_duty(AIN1,speed_u16);    _set_duty(AIN2,0)          # 左 正
    _set_duty(BIN1,0);            _set_duty(BIN2,speed_u16)  # 右 逆

# ====== 速度ユーティリティ ======
def _clamp_speed(u16):
    if u16 < MIN_SPEED: return MIN_SPEED
    if u16 > MAX_SPEED: return MAX_SPEED
    return u16

def get_speed():
    # ボタンを使う場合は現在レベル、使わない場合はSPEED_U16
    global SPEED_U16, _current_level
    if (PIN_SPEED_UP is not None) and (PIN_SPEED_DOWN is not None):
        return _clamp_speed(SPEED_LEVELS[_current_level])
    else:
        return _clamp_speed(SPEED_U16)

def set_speed_from_level(idx):
    global _current_level
    if idx < 0: idx = 0
    if idx >= len(SPEED_LEVELS): idx = len(SPEED_LEVELS) - 1
    _current_level = idx
    print("Speed level:", _current_level, "=>", SPEED_LEVELS[_current_level])

# ====== (任意)本体ボタンで速度調整 ======
_last_btn_ms = 0
if (PIN_SPEED_UP is not None) and (PIN_SPEED_DOWN is not None):
    btn_up   = Pin(PIN_SPEED_UP,   Pin.IN, Pin.PULL_UP)
    btn_down = Pin(PIN_SPEED_DOWN, Pin.IN, Pin.PULL_UP)
else:
    btn_up = btn_down = None

def _poll_buttons():
    global _last_btn_ms, _current_level
    if (btn_up is None) or (btn_down is None): return
    now = time.ticks_ms()
    if time.ticks_diff(now, _last_btn_ms) < DEBOUNCE_MS: return
    # 押されたら LOW になる前提(PULL_UP)
    if btn_up.value() == 0:
        set_speed_from_level(_current_level + 1)
        _last_btn_ms = now
    elif btn_down.value() == 0:
        set_speed_from_level(_current_level - 1)
        _last_btn_ms = now

# ====== ESP-NOW 初期化 ======
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
try:
    wlan.config(channel=6)   # 送信側と合わせる
except:
    pass

esp = espnow.ESPNow()
esp.active(True)

FAILSAFE_MS = 700
last_rx = time.ticks_ms()
stop()
print("XIAO C3 robot: waiting commands (ESP-NOW)…")
if (PIN_SPEED_UP is not None) and (PIN_SPEED_DOWN is not None):
    print("Local speed control: buttons enabled")
else:
    print("Local speed control: set SPEED_U16 in code")

# ====== 受信ループ ======
while True:
    # (任意)速度ボタンのポーリング
    _poll_buttons()

    mac, msg = esp.recv(0)     # 非ブロッキング
    now = time.ticks_ms()

    if msg:
        try:
            cmd = msg.decode().strip()
        except:
            cmd = ""

        # 旧来の "action[:coef]" 形式が飛んできても coef は無視
        if ":" in cmd:
            action = cmd.split(":", 1)[0]
        else:
            action = cmd

        spd = get_speed()

        if   action == "forward":
            forward(spd)
        elif action == "backward":
            backward(spd)
        elif action == "left_turn":
            left_turn(spd)
        elif action == "right_turn":
            right_turn(spd)
        elif action == "stop":
            stop()

        last_rx = now

    # フェイルセーフ
    if time.ticks_diff(now, last_rx) > FAILSAFE_MS:
        stop()