# 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()