二足歩行ロボット「OTTO」その1

二足歩行ロボット「OTTO」その1

サーボモータ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()