サーボモータ4つだけで簡単な二足歩行するロボット「OTTO」2025年度は、これを様々なマイコンで動作させてみたのでその記録。

OTTOはキットも5000円程度で売ってはいるが、オープンソースで筐体の3Dデータも公式サイトからダウンロードできる。

これは「Ninjya」バージョン バンブーラボでGcode化して3Dプリント

筐体と部品一式。目の部分は距離センサではなく大型のLEDを使用して価格を抑えた。


回路図 マイコンはオリジナルのAruduino Nanoから RP-2040-Zero に換装。

頭部に収めるように55mmサイズの基板を加工

製作マニュアル

製作の様子(体験入学)









電源スイッチをオンオフするたびに、歩行パターンが変化するようにプログラミングした
from machine import Pin, PWM
from time import sleep
import sys,os
LL = PWM(Pin(2)) # レフトレッグ
RL = PWM(Pin(3)) # ライトレッグ
LF = PWM(Pin(4)) # レフトフット
RF = PWM(Pin(5)) # ライトフット
LED_RIGHT = Pin(26,Pin.OUT)
LED_LEFT = Pin(15,Pin.OUT)
SP = PWM(Pin(14)) # GP14 に PWM 出力
LL.freq(50) # サーボモーターは50Hzで制御
RL.freq(50)
LF.freq(50)
RF.freq(50)
#V1
#LLS = 96 # レフトレッグ 直立時の角度
#RLS = 92 # ライトレッグ
#LFS = 97 # レフトフット
#RFS = 80 # ライトフット
#V2
LLS = 102 # レフトレッグ 直立時の角度
RLS = 92 # ライトレッグ
LFS = 97 # レフトフット
RFS = 80 # ライトフット
LLN = LLS # レフトレッグ 現在の角度
RLN = RLS # ライトレッグ
LFN = LFS # レフトフット
RFN = RFS # ライトフット
MIN_DUTY = 500 * 65535 // 20000 # 500μsを16ビットスケールで
MAX_DUTY = 2400 * 65535 // 20000 # 2400μsを16ビットスケールで
DUTY_RANGE = MAX_DUTY - MIN_DUTY # 差分を定数として計算
def angle_to_duty(angle): # 角度をデューティー比に変換する関数
#angle = angle + 90
return int(MIN_DUTY + DUTY_RANGE * angle // 180)
def angle_to_angle(servo1,s_angle1,e_angle1,servo2,s_angle2,e_angle2,ms):
if s_angle1 < e_angle1 :
as1 = 1
else:
as1 = -1
if s_angle2 < e_angle2 :
as2 = 1
else:
as2 = -1
angle1 = s_angle1
angle2 = s_angle2
f1=0
f2=0
while True:
if angle1 != e_angle1 :
angle1 += as1
servo1.duty_u16(angle_to_duty(angle1))
else:
f1=1
if angle2 != e_angle2 :
angle2 += as2
servo2.duty_u16(angle_to_duty(angle2))
else:
f2=1
sleep(ms)
if f1==1 and f2==1 :
break;
def sw():
sleep(0.01)
def upright():
LL.duty_u16(angle_to_duty(LLS))
RL.duty_u16(angle_to_duty(RLS))
LF.duty_u16(angle_to_duty(LFS))
RF.duty_u16(angle_to_duty(RFS))
def forward_walk(step_time,ms):
RF.duty_u16(angle_to_duty(RFS -30))
LF.duty_u16(angle_to_duty(LFS -15))
sleep(step_time)
LL.duty_u16(angle_to_duty(LLS +25))
RL.duty_u16(angle_to_duty(RLS +25))
sleep(step_time)
LF.duty_u16(angle_to_duty(LFS +30))
RF.duty_u16(angle_to_duty(RFS +15))
sleep(step_time)
LL.duty_u16(angle_to_duty(LLS -25))
RL.duty_u16(angle_to_duty(RLS -25))
sleep(step_time)
def forward_walk2(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFS-30,LF,LFN,LFS-15,ms)
RFN = RFS-30
LFN = LFS-15
sleep(step_time)
angle_to_angle(LL,LLN,LLS+25,RL,RLN,RLS+25,ms)
LLN = LLS+25
RLN = RLS+25
sleep(step_time)
angle_to_angle(LF,LFN,LFS+30,RF,RFN,RFS+15,ms)
LFN = LFS+30
RFN = RFS+15
sleep(step_time)
angle_to_angle(LL,LLN,LLS-25,RL,RLN,RLS-25,ms)
LLN = LLS-25
RLN = RLS-25
sleep(step_time)
def turn_left_walk(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFS-30,LF,LFN,LFS-15,ms)
RFN = RFS-30
LFN = LFS-15
sleep(step_time)
angle_to_angle(LL,LLN,LLS+25,RL,RLN,RLS+5,ms)
LLN = LLS+25
RLN = RLS+5
sleep(step_time)
angle_to_angle(LF,LFN,LFS+30,RF,RFN,RFS+15,ms)
LFN = LFS+30
RFN = RFS+15
sleep(step_time)
angle_to_angle(LL,LLN,LLS-25,RL,RLN,RLS-5,ms)
LLN = LLS-25
RLN = RLS-5
sleep(step_time)
def turn_right_walk(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFS-30,LF,LFN,LFS-15,ms)
RFN = RFS-30
LFN = LFS-15
sleep(step_time)
angle_to_angle(LL,LLN,LLS+5,RL,RLN,RLS+25,ms)
LLN = LLS+5
RLN = RLS+25
sleep(step_time)
angle_to_angle(LF,LFN,LFS+30,RF,RFN,RFS+15,ms)
LFN = LFS+30
RFN = RFS+15
sleep(step_time)
angle_to_angle(LL,LLN,LLS-5,RL,RLN,RLS-25,ms)
LLN = LLS-5
RLN = RLS-25
sleep(step_time)
def backward_walk2(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFS-30,LF,LFN,LFS-15,ms)
RFN = RFS-30
LFN = LFS-15
sleep(step_time)
angle_to_angle(LL,LLN,LLS-25,RL,RLN,RLS-25,ms)
LLN = LLS-25
RLN = RLS-25
sleep(step_time)
angle_to_angle(LF,LFN,LFS+30,RF,RFN,RFS+15,ms)
LFN = LFS+30
RFN = RFS+15
sleep(step_time)
angle_to_angle(LL,LLN,LLS+25,RL,RLN,RLS+25,ms)
LLN = LLS+25
RLN = RLS+25
sleep(step_time)
def kani_walk_right(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFN-35,LF,LFN,LFN,ms)
RFN = RFN-35
LFN = LFN
sleep(step_time)
angle_to_angle(RF,RFN,RFN,LF,LFN,LFN-35,ms)
RFN = RFN
LFN = LFS-35
sleep(step_time)
angle_to_angle(RF,RFN,RFS,LF,LFN,LFN,ms)
RFN = RFS
LFN = LFN
sleep(step_time)
angle_to_angle(RF,RFN,RFS,LF,LFN,LFS,ms)
RFN = RFS
LFN = LFS
sleep(step_time)
def kani_walk_left(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFN,LF,LFN,LFN+35,ms)
RFN = RFN
LFN = LFN+35
sleep(step_time)
angle_to_angle(RF,RFN,RFN+35,LF,LFN,LFN,ms)
RFN = RFN+35
LFN = LFN
sleep(step_time)
angle_to_angle(RF,RFN,RFN,LF,LFN,LFS,ms)
RFN = RFN
LFN = LFS
sleep(step_time)
angle_to_angle(RF,RFN,RFS,LF,LFN,LFN,ms)
RFN = RFS
LFN = LFN
sleep(step_time)
def dance1(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFS+30,LF,LFN,LFS+30,ms)
RFN = RFS+30
LFN = LFS+30
sleep(step_time)
angle_to_angle(RF,RFN,RFS-30,LF,LFN,LFS-30,ms)
RFN = RFS-30
LFN = LFS-30
sleep(step_time)
def moon_walk_left(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFN-50,LF,LFN,LFN,ms)
RFN = RFN-50
LFN = LFN
sleep(step_time)
angle_to_angle(RF,RFN,RFS,LF,LFN,LFN+50,ms)
RFN = RFS
LFN = LFN+50
sleep(step_time)
angle_to_angle(RF,RFN,RFN,LF,LFN,LFS,ms)
RFN = RFN
LFN = LFS
sleep(step_time)
def moon_walk_right(step_time,ms):
global LLN,RLN,LFN,RFN
angle_to_angle(RF,RFN,RFN,LF,LFN,LFN+50,ms)
RFN = RFN
LFN = LFN+50
sleep(step_time)
angle_to_angle(RF,RFN,RFN-50,LF,LFN,LFS,ms)
RFN = RFN-50
LFN = LFS
sleep(step_time)
angle_to_angle(RF,RFN,RFS,LF,LFN,LFN,ms)
RFN = RFS
LFN = LFN
sleep(step_time)
def save_mode(n):
with open("mode.txt", "w") as f:
f.write(str(n)) # 数字を文字として保存
def load_mode():
try:
with open("mode.txt", "r") as f:
mode = f.read().strip() # 改行などを除去
return int(mode)
except:
return 0 # エラー時は 0 を返す(初期値)
def beep(f,t,c):
SP.freq(f) # PWM 周波数を f Hzに設定
for i in range(0,c):
SP.duty_u16(32767) # duty比 50%
LED_RIGHT.value(1)
LED_LEFT.value(1)
sleep(t)
SP.duty_u16(0) # duty比 0%
LED_RIGHT.value(0)
LED_LEFT.value(0)
sleep(t)
try:
#save_mode(0)
#sleep(2)
#sys.exit()
upright()
mode = load_mode() # 読み込み
print("保存された値:", mode)
sleep(1)
if mode>=7 :
save_mode(0) # 保存
else:
save_mode(mode+1) # 保存
sleep(1)
beep(440,0.5/(mode+1),mode+1)
LED_RIGHT.value(1)
LED_LEFT.value(1)
#mode = 7
while True:
if mode == 0 :
forward_walk2(0.1,0.005) # 前歩き
if mode == 1 :
backward_walk2(0.1,0.005) # 後ろ歩き
if mode == 2 :
for i in range(0,5,1): # 前へ5歩
forward_walk2(0.1,0.005)
for i in range(0,5,1) : # 後ろへ5歩
backward_walk2(0.1,0.005)
if mode == 3 :
for i in range(0,6,1):
kani_walk_left(0.01,0.006)
for i in range(0,6,1) :
kani_walk_right(0.01,0.006)
if mode == 4 :
for i in range(0,4,1):
moon_walk_left(0.01, 0.005)
for i in range(0,4,1) :
moon_walk_right(0.01,0.005)
if mode == 5 :
for i in range(0,5,1):
dance1(0.01, 0.005)
sleep(1)
if mode == 6 :
turn_left_walk(0.1,0.005) # 前歩き
if mode == 7 :
turn_right_walk(0.1,0.005) # 前歩き
except KeyboardInterrupt: # 終了前にサーボを初期位置に戻す
print("サーボを初期位置(0度)に戻しています...")
upright()
LED_RIGHT.value(0)
LED_LEFT.value(0)
sleep(2) # 少し待機
LL.deinit() # プログラムを停止する際にPWMを無効化
RL.deinit() # プログラムを停止する際にPWMを無効化
LF.deinit() # プログラムを停止する際にPWMを無効化
RF.deinit() # プログラムを停止する際にPWMを無効化
print("サーボ制御を終了しました。")
#sys.exit()