可以控制12軸的MGR996,單邊的測試腳部已經準備好了,就該來準備產生步態的部分了,產生步態的部分,直接用DrGuero2001公開的core.cpp內容。
因為之前練習控制MGR996的範例都是python,這些範例也會用到。所以決定把core.cpp內部產生步態的部分,用python重寫,應該只是格式不同,現在也不用顧慮效率。
不過因為python和C++其實都還不熟,就是持續兩邊查教學資料,邊寫python版本。說難倒是不難,單是無法理解C++的意思,與不知道python哪裡寫錯了,就要卡上不少時間
重寫後的python code如下,基本上把core.cpp逐行改寫,原本的日文註解也移過來了,外加我自己的註解,用 " #XD " 開頭做區分。
#!/usr/bin/env python # -*- coding: utf-8 -*- #Libraries import time #https://docs.python.org/fr/3/library/time.html from adafruit_servokit import ServoKit #https://circuitpython.readthedocs.io/projects/servokit/en/latest/ #/#把math加進來 import math #Constants nbPCAServo=16 LEG = 180.0 #Parameters MIN_IMP =[500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500] MAX_IMP =[2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500] MIN_ANG =[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] MAX_ANG =[180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180]以上會設定控制MGR996的基本參數
K0W = [0,0] K1W = [0,0] K2W = [0,0] HW = [0,0] A0W = [0,0] A1W = [0,0] mode = 0 exit = 0 detail = 0 loop = 0 ii = 1 steptime = 0.1 adjFR = 2.04 autoH = 170 autoHs = 180 walkF = 1 pitch = 0 roll = 0 jikuasi = 0 asiPress_r = 0 asiPress_l = 0 uvcOff = 0 dyi = 0 swf = 12 fwr0 = 0 fwr1 = 0 fwct = 1 fwctEnd = 48 dx = [0,0] dxi = 0 dyi = 0 fhMax = 20 landRate = 0.2 fh = 0 fw = 20 LEG = 180.0以上變數和core.cpp相同
#Objects
pca = ServoKit(channels=16)
# function init
def init():
for i in range(nbPCAServo):
pca.servo[i].set_pulse_width_range(MIN_IMP[i] , MAX_IMP[i])
對MGR996的角度和PWM範圍進行設定
# function main
def main():
pca.servo[0].angle=None #disable channel
pca.servo[1].angle=None #disable channel
pca.servo[2].angle=None #disable channel
pca.servo[3].angle=None #disable channel
pca.servo[4].angle=None #disable channel
pca.servo[5].angle=None #disable channel
pca.servo[8].angle=None #disable channel
pca.servo[9].angle=None #disable channel
pca.servo[10].angle=None #disable channel
pca.servo[11].angle=None #disable channel
pca.servo[12].angle=None #disable channel
pca.servo[13].angle=None #disable channel
pca.servo[14].angle=None #disable channel
pca.servo[15].angle=None #disable channel
time.sleep(0.5)
print("stretch leg")
L1 = 90
L2 = 90
L3 = 90
L4 = 90
L5 = 90
L6 = 90
R1 = 90
R2 = 90
R3 = 90
R4 = 90
R5 = 90
R6 = 90
pca.servo[11].angle = R6
time.sleep(0.2)
pca.servo[10].angle = L6
time.sleep(0.2)
pca.servo[9].angle = R5
time.sleep(0.2)
pca.servo[8].angle = L5
time.sleep(0.2)
pca.servo[7].angle = R4
time.sleep(0.2)
pca.servo[6].angle = L4
time.sleep(0.2)
pca.servo[5].angle = R3
time.sleep(0.2)
pca.servo[4].angle = L3
time.sleep(0.2)
pca.servo[3].angle = R2
time.sleep(0.2)
pca.servo[2].angle = L2
time.sleep(0.2)
pca.servo[1].angle = R1
time.sleep(0.2)
pca.servo[0].angle = L1
time.sleep(0.2)
pca.servo[0].angle=None #disable channel
pca.servo[1].angle=None #disable channel
pca.servo[2].angle=None #disable channel
pca.servo[3].angle=None #disable channel
pca.servo[4].angle=None #disable channel
pca.servo[5].angle=None #disable channel
pca.servo[8].angle=None #disable channel
pca.servo[9].angle=None #disable channel
pca.servo[10].angle=None #disable channel
pca.servo[11].angle=None #disable channel
pca.servo[12].angle=None #disable channel
pca.servo[13].angle=None #disable channel
pca.servo[14].angle=None #disable channel
pca.servo[15].angle=None #disable channel
time.sleep(1)
開始跑步態前,先把腳部伸直,不過缺少速度控制,前次測試停止的角度太大時,動作會很激烈。
global detail
global ii
global steptime
while True:
try:
print(" ")
print("input loop")
loop = int(input("number of loop : "))
break;
except:
print("Invalid input")
while True:
try:
print(" ")
print("input step time")
steptime = float(input("step time : "))
break;
except:
print("Invalid input")
while True:
try:
print(" ")
print("show detail ?")
detail = int(input("input 1 for YES : "))
break;
except:
detail = 0
break;
輸入想測試的loop(總step次數),每個step間隔時間,以及是否顯示詳細資料(檢查中間的step狀態)。為了測試所以加一些額外的輸出,用來確認python是否正常執行。
print(" ")
localtime = time.asctime( time.localtime(time.time()) )
print ("Start time :", localtime)
print(" ")
用來觀察執行開始與結束的時間。
ii = 1
while ii < loop :
#print("loop ",ii)
ii += 1
walk();
#print("QWERTYUIOP")
重複執行產生步態的walk()函數,直到指定的loop上限。
print("walk done , Release servo")
pca.servo[0].angle=None #disable channel
pca.servo[1].angle=None #disable channel
pca.servo[2].angle=None #disable channel
pca.servo[3].angle=None #disable channel
pca.servo[4].angle=None #disable channel
pca.servo[5].angle=None #disable channel
pca.servo[8].angle=None #disable channel
pca.servo[9].angle=None #disable channel
pca.servo[10].angle=None #disable channel
pca.servo[11].angle=None #disable channel
pca.servo[12].angle=None #disable channel
pca.servo[13].angle=None #disable channel
pca.servo[14].angle=None #disable channel
pca.servo[15].angle=None #disable channel
time.sleep(0.1)
localtime = time.asctime( time.localtime(time.time()) )
print(" ")
print ("End time :", localtime)
print(" ")
以上會以上會在完成loop後,停止發送PWM訊號到MGR996,等於放鬆腳部。
以下開始基本上就是原本core.cpp的python改寫,沒有變動產生部態的計算方式,可以參考另一篇core.cpp的產生步態解析。
# ******************
# * * 脚位置制御 **
# ******************
# function footcont
###footcont to generate local motion###
def footCont(x,y,h,s):
#x:中点を0とする足前後方向距離(前+)
#XD //http://ai2001.ifdef.jp/uvc/qa.jpg
#XD //猜是腳底前後與中心距離,起始是距離,後面計算應該是角度ThetaX
#y:中点を0とする足左右方向距離(右+)
#XD //猜是腳底左右與中心距離
#h:足首ロール軸から股関節ロール軸までの距離
#XD //猜是腳踝與股關節的距離
#s:支持脚(0)遊脚(1)を指定
#XD //支持腳猜是落地或是懸空
#XD //還沒找到起始值如何決定,這是函數,使用時才輸入數值
#XD //但是雙冒號有查到但還是看不懂。
k = float(0)
#XD //計算用的,應該是角度ThetaK
k = math.sqrt(x*x+(y*y+h*h))
#A0からK0までの距離
#XD //A0腳踝?,K0股關節?
if k > LEG:
k = LEG
#計算エラー回避
#XD //避開計算錯誤,大概是要避免腳反摺
x = math.asin(x/k)
#K0脚振り角度 Update in June 1,2021 :Before revision( x=asin(x/LEG) )
#XD //重複x,這看不懂,把x(距離)拿去當輸入,再把arcsin計算結果寫入x(角度),單位是弧度
k = math.acos(k/LEG)
#K0膝曲げ角度
#XD //重複k,這看不懂,把k(距離)拿去當輸入,再把arccos計算結果寫入k(角度),單位是弧度
fbAV=0
#UVC評価の為、ジャイロは無効にする
#XD //ジャイロGyro陀螺儀
lrAV=0
#XD //我猜也是gyro相關
K0W[s] = k+x
#XD //單位應該是角度
#XD //http://ai2001.ifdef.jp/uvc/qa.jpg圖上沒有
HW[s] = k*2
#XD //單位應該是角度
#XD //http://ai2001.ifdef.jp/uvc/qa.jpg對圖好理解
A0W[s] = k-x-0.003*fbAV
#XD //單位應該是角度
#XD //http://ai2001.ifdef.jp/uvc/qa.jpg圖上沒有
k = math.atan(y/h)
#K1角度
#XD //單位應該是角度
#XD //看起來重複使用變數用來計算
K1W[s] = k
#XD //看起來重複使用變數用來計算,再寫入目標變數
if(s==0) :
A1W[s] = -k-0.002*lrAV
#XD //腳懸空時的計算
else :
A1W[s] = -k+0.002*lrAV
#print("footcont done")
#XD //腳著地時的計算,計算式看起來就是反向
# **********************
# * * 歩行制御メイン **メインmain
# **********************
def walk():
i = int(0)
j = int(0)
k = float(0)
global autoH
global autoHs
global mode
global fwct
global ii
jikuasi = 0
asiPress_r = 0
asiPress_l = 0
uvcOff = 0
dyi = 0
swf = 12
fwr0 = 0
fwr1 = 0
fwctEnd = 48
#dx = [0,0]
dxi = 0
dyi = 0
fhMax = 20
landRate = 0.2
fh = 0
fw = 20
#LEG = 180.0
#//////////////////////
#// 初期姿勢に移行 ////
#//////////////////////
if mode == 0:
#//louis 姿勢初始化
if autoHs > autoH :
autoHs -= 1
#XD //autoHs初始值180,autoH初始值170
else:
mode=10
#XD //chenge mode to idle
#XD //mode initial value 0
if detail == 1 :
print("姿勢初始化 ",mode)
#print("mode ",mode," J0")
footCont( -adjFR, 0, autoHs, 0 );
#XD //(軸足) adjFR initial 2.04
#print("mode ",mode," J1")
footCont( -adjFR, 0, autoHs, 1 );
#XD //(遊脚) adjFR initial 2.04
#////////////////////
#// アイドル状態 ////
#////////////////////
#XD // idle status
elif mode == 10 :
K0W[0] = 0
#XD //股関節0(軸足) value 0
K0W[1] = 0
#XD //股関節1(遊脚) value 0
#// パラメータ初期化 ////parameter initial
dx[0] =0
#前後方向脚位置
dx[1] =0
#前後方向脚位置
fwr0 =0
#中心からの戻り股幅(軸足)
fwr1 =0
#中心からの戻り股幅(遊脚)
fwct =0
#一周期カウンタ
# louis //計算到步態週期的第幾個count
dxi =0
#縦距離積分値
dyi =0
#横距離積分値
dy =0
#横振り距離
jikuasi =0
#XD //jikuasi軸足
fwctEnd =48
#一周期最大カウント数
swf =12
#横振り最大距離
fhMax =20
#足上げ最大高さ
landRate=0.2
#0.1一歩の内、両足接地期間の割合
fh =0
#足上げ高さ指示
#XD //推測是抬腳高度
if detail == 1 :
print("アイドル状態 ",mode)
#print("mode ",mode," J0")
footCont( -adjFR, 0, autoH, 0 );
#XD //(軸足)
#print("mode ",mode," J1")
footCont( -adjFR, 0, autoH, 1 );
#XD //(遊脚)
if walkF & 0x01 :
#歩行フラグ (b0:歩行 b1:未 b2:未)
#XD // # 是位元運算and
fw=20
#中心からの振り出し股幅
mode=20
#XD // switch to mode 20
#///////////////////////////////////////////////////////////////
#////////////////////// 歩行制御 ///////////////////////////
#///////////////////////////////////////////////////////////////
elif mode == 20 or mode == 30:
#XD //後面有差,就是20和30會共用大部分的計算,但是根據20或30調整部分計算
#elif mode == 30 :
############################################################
#################### UVC(上体垂直制御) ####################
############################################################
if ( ( jikuasi == 0 and asiPress_r < -0.1 and asiPress_l > -0.1 ) or ( jikuasi == 1 and asiPress_r > -0.1 and asiPress_l < -0.1 ) ) :
#XD //看起來是分左右腳檢查,只需要左腳或右腳接地壓力數值有一邊成立
k = 1.5 * 193 * sin(lrRad)
#// 左右方向変位 ////
#XD //lrRad頭左右角度 右が-,放在biped.h
#XD //看來是用頭判斷身體偏左或偏右
#XD //k計算用,不用在意起始值
if jikuasi == 0 :
dyi += k
#XD //軸腳時,dyi增加k
else :
dyi -= k
#XD //游腳時,dyi減少k
if dyi > 0 :
dyi = 0
#横距離積分値
if dyi < -30 :
dyi = -30
#XD //把dyi數值限制在-30與0之間
k = 1.5 * 130 * math.sin(fbRad)
#// 前後方向変位 ////
#XD //fbRad頭前後角度 前が-
#XD //也是用頭判斷身體偏前或偏後
#XD //k計算用,不用在意起始值
dxi += k
#XD //dxi縦距離積分値,但是看不到什麼時候積分的
dyi *= 0.90
#減衰
if uvcOff == 1 :
#XD //關閉UVC,把dxi和dyi歸零
dxi = 0
dyi = 0
############################################################
######################### 基本歩容 #######################
############################################################
#XD //步態只看腳軸尖
#// 横振り ////
k = swf * math.sin(math.pi*(fwct)/fwctEnd)
#sinカーブ //louis カーブcurve
#XD //swf,不起始值12
#XD //math.pi圓周率,sinf就是sin,只是傳回f浮點數,輸入是弧度
#XD //fwct會由零逐步增加到最大值fwctEnd
#XD //算式看懂了,但是意義不明
if jikuasi == 0 :
dy = k
#右振り
#XD //軸腳時,dy等於k
else :
dy = -k
#左振り
#XD //游腳時,dy等於負k
#// 軸足側前振り制御 ////
if fwct < fwctEnd/2 :
dx[jikuasi] = fwr0*( 1 - 2.0*fwct/fwctEnd )
#立脚中期まで
#XD //半週期內,2.0*fwct/fwctEnd一定小於1,所以fwr0會隨跨出逐步減少
else :
dx[jikuasi] = -(fw-dxi)*( 2.0*fwct/fwctEnd-1 )
#立脚中期移行UVC適用
#XD //超過半週期時,2.0*fwct/fwctEnd一定大於1
#// 遊脚側前振り制御 ////
if mode == 20 :
if detail == 1 :
print("両脚シフト期間",mode)
#両脚シフト期間
#XD //シフトshift應該是兩隻腳交錯的時候。
if fwct < (landRate*fwctEnd) :
#XD //檢查fwct是否超過範圍,在範圍內就
dx[jikuasi^1] = fwr1-(fwr0-dx[jikuasi])
#XD //由jikuasi的dx資料,對產生另一jikuasi的dx數據
else :
#XD //fwct超過範圍就
fwr1 = dx[jikuasi^1]
mode=30;
#XD //這裡切換成case 30
if mode == 30 :
if detail == 1 :
print("前振出",mode)
#前振出
#XD //k計算用,不用在意起始值,只有量值沒有方向性
k=(
-math.cos(
math.pi*( fwct-landRate*fwctEnd )/
#XD //超出landrate比率的步態count
( (1-landRate)*fwctEnd )
#前振り頂点までの残りクロック数
#XD //跨步到頂點前,剩餘的步態count
)+1
)/2
#0-1の∫的カーブ
#XD //∫微分,カーブcurve,似乎直接用/2簡單達成
dx[jikuasi^1] = fwr1+k*( fw-dxi-fwr1 )
#XD //算式看得懂,但是原理就不知道了
if dx[jikuasi] > 100 :
#振り出し幅リミット
#XD //跨步距離有檢查正方向上限
dxi -= dx[jikuasi] - 100
#XD //超過100的部分,就減少dxi積分
dx[jikuasi] = 100
#XD // 然後回歸100
if dx[jikuasi] < -100 :
#XD //跨步距離也檢查負方向下限
dxi -= dx[jikuasi] + 100
#XD //低過-100的部分,就減少dxi積分
dx[jikuasi] = -100
#XD // 然後回歸-100
if dx[jikuasi^1] > 100 :
dx[jikuasi^1] = 100
#振り出し幅リミット抑制
#XD //換腳,跨步距離有檢查正方向上限,然後回歸100
if dx[jikuasi^1] < -100 :
dx[jikuasi^1] = -100
#XD //換腳,跨步距離也檢查負方向下限,然後回歸-100
#// 足上制御 ////
#XD //控制抬腳高度
i = landRate * fwctEnd
#XD //算出兩腳都落地的步態count
if fwct > i :
fh = fhMax * math.sin( math.pi*(fwct-i)/(fwctEnd-i) )
#XD //超出落地的步態count後,才計算抬腳高度,一樣用步態比率計算
else :
fh = 0
#// 脚制御関数呼び出し ////
#XD //関数function
if jikuasi == 0 :
#是算完腳尖位置,才呼叫footCont來計算,轉軸角度
#print("mode ",mode," J0")
footCont( dx[0]-adjFR , -dy-dyi+1, autoH, 0 )
#print("mode ",mode," J1")
footCont( dx[1]-adjFR , dy-dyi+1, autoH-fh, 1 )
else :
#print("mode ",mode," J0")
footCont( dx[0]-adjFR , -dy-dyi+1, autoH-fh, 0 )
#print("left ",K2W[0]," ",K1W[0]," ",K0W[0]," ",HW[0]," ",A0W[0]," ",A1W[0]," ","right ",K2W[1]," ",K1W[1]," ",K0W[1]," ",HW[1]," ",A0W[1]," ",A1W[1])
#print("mode ",mode," J1")
footCont( dx[1]-adjFR , dy-dyi+1, autoH, 1 )
#print("left ",K2W[0]," ",K1W[0]," ",K0W[0]," ",HW[0]," ",A0W[0]," ",A1W[0]," ","right ",K2W[1]," ",K1W[1]," ",K0W[1]," ",HW[1]," ",A0W[1]," ",A1W[1])
############################################################
############ CPG(歩周期生成、今回は簡易仕様) ############
############################################################
if fwct == fwctEnd :
#簡易仕様(固定周期)
#XD //步態做滿週期時,
jikuasi ^= 1
#是換腳動作,^=1是XOR 1。0101切換。
fwct = 1
#XD //回歸起始值1
dxi = 0
#XD //歸零
fwr0 = dx[jikuasi]
#XD //軸足
fwr1 = dx[jikuasi^1]
#XD //軸足,^=1是指數1。結果不會變阿。
fh=0
#XD //歸零
mode = 20
#XD //case切到20
if fw == 20 :
#歩幅制御
landRate = 0.1
#XD //回到初始值
fw = 40
#XD //把跨步距離加到40
else :
fwct += 1
#XD //週期加1
else :
print("else")
exit = 1
print("exit",exit)
這個else,我已經忘記用途了 -_-||
前段產生步態後,就要煮換成送到MGR996的角度,這邊要順便根據實際機構,處理旋轉方向和0度位置。
####servo move###
if detail == 1 :
print("left ",K2W[0]," ",K1W[0]," ",K0W[0]," ",HW[0]," ",A0W[0]," ",A1W[0])
print("right ",K2W[1]," ",K1W[1]," ",K0W[1]," ",HW[1]," ",A0W[1]," ",A1W[1])
L1 = int(K2W[0]*180/math.pi+90)
L2 = int(-K1W[0]*180/math.pi+90)
L3 = int(K0W[0]*180/math.pi+90)
L4 = int(HW[0]*180/math.pi+90)
L5 = int(-A0W[0]*180/math.pi+90)
L6 = int(A1W[0]*180/math.pi+90)
R1 = int(K2W[1]*180/math.pi+90)
R2 = int(-K1W[1]*180/math.pi+90)
R3 = int(-K0W[1]*180/math.pi+90)
R4 = int(-HW[1]*180/math.pi+90)
R5 = int(A0W[1]*180/math.pi+90)
R6 = int(-A1W[1]*180/math.pi+90)
#print("send to servo---------------------------------------------------------------------------------")
#print("left ",L1," ",L2," ",L3," ",L4," ",L5," ",L6)
#print("right ",R1," ",R2," ",R3," ",R4," ",R5," ",R6)
#print("fwct ",fwct)
print("loop,",ii,",mode,",mode,",fwct,",fwct,",left,",L1,",",L2,",",L3,",",L4,",",L5,",",L6,",right,",R1,",",R2,",",R3,",",R4,",",R5,",",R6)
#print("detail ",detail)
以下就把轉換後的角度,輸出給MGR996完成動作。
#left leg
pca.servo[0].angle = L1
pca.servo[2].angle = L2
pca.servo[4].angle = L3
pca.servo[6].angle = L4
pca.servo[8].angle = L5
pca.servo[10].angle = L6
#right leg
pca.servo[1].angle = R1
pca.servo[3].angle = R2
pca.servo[5].angle = R3
pca.servo[7].angle = R4
pca.servo[9].angle = R5
pca.servo[11].angle = R6
time.sleep(steptime)
if __name__ == '__main__':
init()
main()
為了測試所以加一些額外的輸出,用來確認python是否正常執行,MG996R不供電,空跑測試
同樣MG996R不供電,空跑測試,把輸出的結果,用excel畫出曲線。
右腳看起來大致正確,但是左腳還有問題需要處理。
送給單邊腳步跑跑看,很順利6+6軸都會動,但是各軸的基準方向和原點都還不正確。


