見出し画像

ロボット工学専攻の大学院生がスマブラspのロボット作ってみた!!

こんにちは,anaです.僕は現在,広域での意味でのロボット工学専攻の大学院生です(ロボット工学を含む機械工学全般好きです).今回は少しロボット工学に触れながら,スマブラspのロボットの横強のモーションを実機の製作を通して再現しました.

今回使用する学問(機械工学)
・ロボット工学
・機構学
・材料力学

Abstract

In this handout, I create robotic arm of the SSBU. I construct relationship between each link using homogeneous transformation matrix. I create technical drawings and circuits for the robot and program it to reproduce side tilt without shifts. In addition, I succeed in lifting that arm by improving gearbox. Moreover, I confirm its reproduction by experimentation.

1. 問題設定(製作上考慮する制約)

問題設定を先に書いておきます.ロボットを製作するうえで,どのような条件で製作するかを下記にまとめておきます.
・再現するロボットのモーションは横強(無シフト)であり,製作はロボットの右アームとする.
・ロボットアームの自由度は7軸である(なお,機構上,アクチュエータはスマブラspのロボットと必ずしも同じとは限らない).
・ロボットアームの制御は順運動学ベースで行う.

2. 同次変換行列(各関節の関係性の表現)

今回は少し理論的に議論できたらと考えているため,7軸のロボットアームの同次変換行列を導出します.
同次変換行列は回転行列と位置ベクトルからなり,ある座標を別の座標系から見たときの座標を求めるときに使用する行列です.
実際に各関節の座標系同士の同次変換行列が下図のとおりです.

同次変換行列の導出

慣習的にsinはs,cosはc,θBのような軸周りの回転を+,その逆の回転を-で表しています.世界座標系とは基準となる絶対座標系のΣAです.関節は7関節を想定しており,ΣB~ΣHです.各関節の関係性を同次変換行列で表しています.同次変換行列とは,回転行列と位置ベクトルからなる異なる2つの座標系の関係を表すことができます.最終的に世界座標系ΣAからロボットの手先の座標系ΣIまでの全ての同時変換行列を掛けることで,世界座標系からみたロボットの手先位置を表すことができます.ΣA(世界座標系)をそのままAZ軸方向にlABだけ移動させたものがΣBです.ΣBをBZ軸方向にlBC移動させ,その後BX軸周りにθBC回転し,回転後のBZ軸方向にlBC2移動させたものがΣCです.ΣCをCZ方向にlCD1移動させ,その後CX軸周りに-θCD1回転し,回転後のXY平面において平行移動させたものがΣDです.ΣDをDXDY平面において平行移動させたものがΣEです.ΣEをEXEY平面において平行移動させたものがΣFです.ΣFをFXFY平面において平行移動させ,その後FX軸周りに90度回転させたものがΣGです.ΣGをGXGZ平面において平行移動させ,その後GX軸周りに-90度回転させたものがΣHです.ΣHをHXHY平面において平行移動させたものがΣI(ロボットの手先位置)です.DH記法(Denavit-Hartenberg記法)を用いて書いた方が分かりやすかったかもしれません.
少し余談ですが,関連分野としてマルチボディダイナミクス(機械工学の中の機械力学の中の動力学関連)という分野があるそうです(研究室の後輩が読んでいました).マルチボディダイナミクスを使えば,より動力学的に考えることができるとお思いますが,割愛します.

3. シミュレーション(python)

pythonを用いてΣD~ΣFが同じXY平面上にあると仮定し,2次元平面で順運動学ベースでプログラミングをしました.
下記のコードをVisual Studio Code等で実行すれば,動画のような実行結果が得られると思います.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from numpy import sin,cos



# 同次変換行列(x軸方向に並進,z軸周りに回転)
def Tm(l,th):
    Tia = np.array([[cos(th), -sin(th), l*cos(th)],
                    [sin(th), cos(th), l*sin(th)],
                    [0., 0., 1.]])

    return Tia

# 順運動学の計算
def fk(l1, l2, th1, th2):
    # 原点座標(縦ベクトル)
    vec = np.array([[0.],[0.],[1.]] )

    # 順運動学の計算
    Tia1=Tm(l1, th1)
    Tia2=Tm(l2, th2)

    Xa1 = np.dot(Tia1,vec)
    Xa = np.dot(Tia1,Tia2)
    Xa2 = np.dot(Xa,vec)

    return np.array([[0, 0], [Xa1[0], Xa1[1]], [Xa2[0], Xa2[1]]])

# リンク1, 2の長さの初期値
L = [0.5, 0.5]

Lc = L

#Lの長さの最大値
valMax = 1

# 第1, 2の関節角度の初期値
th =[30, 60]

th_rad = np.radians(th)

# 順運動学の計算
p = fk(L[0],L[1], th_rad[0],th_rad[1])


# グラフ描画位置の設定
fig = plt.figure(figsize=(8.0, 8.0))
plt.axis('equal')
#plt.subplots_adjust(left=0.1, bottom=0.15)
plt.xlim(-1.5, 1.5)
plt.ylim(-2, 2)
# グラフ描画
plt.grid()
graph, = plt.plot(p.T[0], p.T[1])

def update_th1(slider_val):
    # 関節1の角度を更新
    th[0] = np.radians([slider_val])

    global th_rad
    th_rad = [th[0],th_rad[1]]

    p = fk(L[0],L[1], th[0],th_rad[1])

    # 手先位置を更新
    graph.set_data(p.T[0], p.T[1])
    graph.set_linestyle('-')
    graph.set_linewidth(5)
    graph.set_color('r')
    graph.set_marker('o')
    graph.set_markerfacecolor('k')
    graph.set_markeredgecolor('k')
    graph.set_markersize(15)

    # グラフ再描画
    fig.canvas.draw_idle()

def update_th2(slider_val):
    # 関節2の角度を更新
    th[1] = np.radians([slider_val])

    global th_rad
    th_rad = [th_rad[0],th[1]]

    p = fk(L[0],L[1], th_rad[0],th[1])

    # 手先位置更新
    graph.set_data(p.T[0], p.T[1])
    graph.set_linestyle('-')
    graph.set_linewidth(5)
    graph.set_color('r')
    graph.set_marker('o')
    graph.set_markerfacecolor('k')
    graph.set_markeredgecolor('k')
    graph.set_markersize(15)

    # グラフ描画
    fig.canvas.draw_idle()

def update_L1(slider_val):
    # 関節1の角度を更新
    
    Lc[0] = slider_val

    global L
    
    L = [Lc[0],L[1]]

    p = fk(Lc[0],L[1], th_rad[0],th_rad[1])

    # 手先位置を更新
    graph.set_data(p.T[0], p.T[1])
    graph.set_linestyle('-')
    graph.set_linewidth(5)
    graph.set_color('r')
    graph.set_marker('o')
    graph.set_markerfacecolor('k')
    graph.set_markeredgecolor('k')
    graph.set_markersize(15)

    # グラフ再描画
    fig.canvas.draw_idle()

def update_L2(slider_val):
    # 関節1の角度を更新
    
    Lc[1] = slider_val

    global L
    
    L = [L[0],Lc[1]]

    p = fk(L[0],Lc[1], th_rad[0],th_rad[1])

    # 手先位置を更新
    graph.set_data(p.T[0], p.T[1])
    graph.set_linestyle('-')
    graph.set_linewidth(5)
    graph.set_color('r')
    graph.set_marker('o')
    graph.set_markerfacecolor('k')
    graph.set_markeredgecolor('k')
    graph.set_markersize(15)
    # グラフ再描画
    fig.canvas.draw_idle()
    
# スライダー表示位置
slider1_pos = plt.axes([0.1, 0.05, 0.8, 0.03])
slider2_pos = plt.axes([0.1, 0.01, 0.8, 0.03])
slider3_pos = plt.axes([0.1, 0.95, 0.8, 0.03])
slider4_pos = plt.axes([0.1, 0.90, 0.8, 0.03])

threshold_slider1 = Slider(slider1_pos, 'θD', 0, 360, valinit=th[0])
threshold_slider2 = Slider(slider2_pos, 'θE', 0, 360, valinit=th[1])
threshold_slider3 = Slider(slider3_pos, 'l_DE', 0, valMax, valinit=L[0])
threshold_slider4 = Slider(slider4_pos, 'l_EF', 0, valMax, valinit=L[1])


# スライダー値変更の処理
threshold_slider1.on_changed(update_th1)
threshold_slider2.on_changed(update_th2)
threshold_slider3.on_changed(update_L1)
threshold_slider4.on_changed(update_L2)

graph.set_linestyle('-')
graph.set_linewidth(5)
graph.set_color('r')
graph.set_marker('o')
graph.set_markerfacecolor('k')
graph.set_markeredgecolor('k')
graph.set_markersize(15)
plt.grid()
plt.show()

実行動画は上記のツイートの通りです.
l_DEとl_EFは上のバーで,θDとθEは下のバーで調整可能で,任意の長さ,角度に指定できます.これは,上記の同次変換行列(各関節の関係性を行列で表したもの)を使用することで,指定した角度分他の関節も計算して動くことができます.このように各関節の角度を指定して手先の位置・姿勢を求めることを順運動学といいます.反対に手先の軌道から各関節の角度を求めることを逆運動学といいます.通常,自由度が多い場合(冗長性がある場合)は逆運動学の方が難しくなります(手先の軌道から各関節の角度が一意に定まるとは限らない).今回のロボットアームは7軸であるため,冗長性があり,逆運動学を解析的に解くこと(式変形を利用して代数的に解くこと)はかなり難しいです(後々触れますが指数座標を使用すると比較的簡単になります).実際,今回の場合は数値解法(逐一計算によって,閾値を利用して解くこと)によって解くことはできると考えられますが,私の専門分野と少し異なるため今回は省略させてください.なお,通常は6軸のマニピュレータがあれば,3次元座標であればどの位置・姿勢にも移動できます.これは,x,y,z軸方向とx,y,z軸周りの回転の合計が6だからです.しかし今回のロボットアームは7軸であるため,人間のように複雑に3次元空間に軌道をえがくことができます(実際,人間も腕から手先までの自由度は7).
今回は学術的視点からロボットの横強を再現することが主なテーマですが,ここで7軸のロボットアームの逆運動学を理論的に考察すると,時間がとても足らないので,しません.
制御工学を専門にしている同期に聞くと,指数座標を使用することで順運動学および逆運動学を解くことができるそうですが,すぐに習得できそうになかったので,今回は順運動学ベースで考えたことで勝手に良しとします(良くない).
ちなみに指数座標の勉学におすすめなのは京都大学の吉川先生が執筆された本らしいです(参考文献に入れています).

4. 筐体設計・製作(fusion 360・3Dプリンタ)

実際に製作するために,ロボットの右アームを設計しました.以前設計したものがあるため,そのモデルをモータ等が取り付けられるように修正しました.全体像は下記のとおりです.

全体像
正面図


平面図
右側面図
背面図


左側面図


下面図

図面は下記の通りです.重要なものには,コメントしてます.なお,ギアボックスは別の章で詳しく書くため,省略してます.パーツは形状を把握しやすいようにあえて,斜め方向からの視点も追加しています.先に組立図を示します.

全体の組立図

全体の組立図は,全てのパーツを組み込んだ全体像です.こちらの組立図は他の組立図も入っています.

台の組立図

アームを支える台は7つのパーツを組み合わせています.台の組立てには,M3のネジを16本使用しています.

台1
台2
台3
台4

長方形の穴があるのは,モータの配線を通すためです.

台5

アナログスティックをつけるための穴をあけています.手持ちのアナログスティックの型番を調べたのですが,寸法が分からなかったのでノギスで測りました.

台6
台7
ガード凹

ガード凸と組み合わせて,肩を再現してます.

ガード凸
ハンド

fusion 360はネジをモデル化すると,図面にしたときに,ネジ部がそのまま図面におこされます.本来であれば,下図のような書き方をします.

ネジの製図
ハンドカバー
リンク1
リンク2_2
リンク2_ver1
リンク2_ver2
リンク2_ver3
リンク3_上
リンク3_下

リンクの名前適当につけすぎですね.今後気をつけます.

軸_上
軸_下

少し,今回の設計について書きます.今回は7軸のロボットアームを製作するにあたって,モータ7個を自然に組み込めるようにしました.本来であれば,各リンクの内部にモータを配置したかったですが,内部に配置するには,現在のサイズの4倍の大きさが最低必要で,その場合トルクが足りなくなるため,今回の設計にしました.ここで,普段ものを作る上での優先順位は,その対象が正常に動作するか(機能的再現性)>>その対象の外観に違和感が生じないか(見て目的再現性)>>3Dプリンタで製作しやすいか>>>>組立てが難しくないか,という順番なので,大体自分が作った回路等が入った複雑なものは組み立てるのがとても難しいです.今回は,機能を優先したため,モータがむき出しになっています(個人的にモータむき出しはメカメカしくて好きですが,見た目の再現性は良くないですね).見た目的に一番良くないのは,ΣF付近のリンクがモータを優先して本来の見た目から逸脱しすぎてしまったところです.細かく設計すると,図面におこしたときに部分拡大図を多用しないといけなくなって,見づらいかもしれません.もし,寸法の抜けがあったらすみません.

5. モーションスタディ(モーションデザイン)

再現するモーションは横強(シフトなし)であり,1Fずつコマ送りにして各関節の角度を画面から算出し,順運動学ベースで考えました.
fusion 360にはモーションスタディというコマンドがあり,各時刻において関節の角度を指定することで,モーションを再現することができます.
ここで,下図のモーション,同時変換行列およびモータの定義をしておきます.ΣBが土台のステッピングモータ(モータ0)で下図の回転19です.ΣCが肩のステッピングモータ(モータ1)で下図の回転15です.ΣDがロボットの白いガードのサーボモータ(モータ2)で下図の回転23です.ΣEがロボットの白いガードのサーボモータ(モータ2)で下図の回転23です.ΣFがロボットの白いガードの次のリンクのサーボモータ(モータ3)で下図の回転17です.ΣGが次のリンクのサーボモータ(モータ4)で下図の回転13です.ΣHが次のリンクのサーボモータ(モータ5)で下図の回転16です.ΣHがハンドのサーボモータ(モータ6)で下図の回転7です.

モーションスタディ画面(ギアボックスを考慮しているもの)

基本的には当初のモーションスタディのまま作っています.

ギアボックスを追加したモーションです.

ここで初めて気づいたのですが,従来のモーションのままでは,ハンドとギアボックスが干渉していますね.実機実験では,恐らくΣD,E(モータ2,3)がトルク不足で干渉しなかったと考えています.

各時刻における回転角 [deg]

各時刻における回転角は上表のとおりで,モータ1がギアボックスをつけているため,減速比から逆算してモータ1の回転数は計算しました.実際にプログラムにするときは,回転角と角速度を指定しています.どちらも一番遅いところを基準にして導出しました.

6. 回路設計(Arduino)

今回,回路とプログラムが本当に良くないので,さらっと見るだけにしてください.ある諸事情により,Arduino UnoとArduino nanoを一つずつ使用しています(ステッピングモータ二つを上手く一つのArduinoで制御できなかった).
ピン配置は下記のとおりです.

ピン配置

ステッピングモータは28BYJ-48を使用し,ULN2003A(ドライバモジュール)に接続して制御しています.サーボモータはSG90を使用しています.9V形電池は4個使用しており,ステッピングモータにそれぞれ一つずつ,Arduino Unoに一つ,Arduino nanoとサーボモータ5つに一つ使用しています.また,ブレッドボードを使用し,ジョイスティックの信号を分岐させています.

実際の回路

配線は台に開けている四角形の箇所から通しています.Arduinoやブレッドボードは台を被せたときに,外に出ないようにしています.ピンを増やすモジュールを組み込めば,もっとよくできたかもしれません.

7. プログラム(Arduino)

ここもあまり良くないので,さらっと見てください.Arduino Uno用とArduino nano用のプログラムは下記のとおりです.

//Arduino Uno用
#include <Stepper.h>

const int MOTOR_STEPS = 2048; 

Stepper myStepper0(MOTOR_STEPS, 3, 5, 4, 6);

const int sw_pin = 7; 
const int x_pin = A0; //X軸の出力をアナログA0へ接続.0でもOK
const int y_pin = A1; //Y軸の出力をアナログA1へ接続.1でもOK

int sw_state = 0; //初期状態設定
int x_pos = 0; //初期状態設定
int y_pos = 0;//初期状態設定

int a=0; //判定用変数

float x_convert = 0.0f; //計算値の変数設定,オーバーフローしないようにflaot設定
float y_convert = 0.0f; //計算値の変数設定,オーバーフローしないようにflaot設定

void setup() { 
  myStepper0.setSpeed(10); // 10回転/1分に設定 rpm(1分あたりの回転数)
  pinMode(sw_pin, INPUT_PULLUP); //デジタルIOの7をinput指定とプルアップ
 Serial.begin(9600); //シリアル通信のデータ転送レートを9600bpsで指定.bpsはビット/秒.
  }
  
void loop() { 
sw_state = digitalRead(sw_pin); //ジョイスティックのセンタークリックの状態の読み出し
x_pos = analogRead(x_pin); //X軸の読み出し
y_pos = analogRead(y_pin); //Y軸の読み出し
x_convert = (float)x_pos / 1023.0f * 5.0f; //読み取った値を電圧に変換,floatで計算
y_convert = (float)y_pos / 1023.0f * 5.0f; //読み取った値を電圧に変換,floatで計算

//ジョイスティックの判定
  if(a==0||a==1)
  {
    if(y_pos>850||a==1)
    {
      if(x_pos>255&&x_pos<767)
      {
        
        a=1;
      }
      else if(a==1)
      {
        a=1;
      }
    }
  }

  if(a==1)
  {
  //10
  myStepper0.setSpeed(14);
  myStepper0.step(-1024);
  Serial.print("10");
   
  //20
  myStepper0.setSpeed(5);
  myStepper0.step(342);
  Serial.print("20");   
  
  //30
  myStepper0.setSpeed(4);
  myStepper0.step(285);
  Serial.print("30");   

  //40
  myStepper0.setSpeed(4);
  myStepper0.step(256);
  Serial.print("40");  

  //50
  myStepper0.setSpeed(2);
  myStepper0.step(313);
  Serial.print("50");   

  //60
  myStepper0.setSpeed(1);
  myStepper0.step(57);
  Serial.print("60");  

  //70
  myStepper0.setSpeed(1);
  myStepper0.step(29);
  Serial.print("70");   

  //80
  myStepper0.setSpeed(1);
  myStepper0.step(29);
  Serial.print("80"); 

  //90
  myStepper0.setSpeed(1);
  myStepper0.step(114);
  Serial.print("90");   

  //100
  myStepper0.setSpeed(1);
  myStepper0.step(57);
  Serial.print("100");   

  //初期位置に戻る
  myStepper0.setSpeed(10);
  myStepper0.step(-456);
  Serial.print("110");   
        
  // 静止時には電流を遮断 
  digitalWrite(3,  LOW); 
  digitalWrite(4,  LOW); 
  digitalWrite(5, LOW); 
  digitalWrite(6, LOW); 
  
  a=0; 
  }
}
//Arduino nano用
#include <Stepper.h>

const int MOTOR_STEPS = 2048; 

Stepper myStepper1(MOTOR_STEPS, 4, 8, 7, 12);

const int sw_pin = 7; //SWピンをデジタルIOの7へ接続
const int x_pin = A0; //X軸の出力をアナログA0へ接続.0でもOK
const int y_pin = A1; //Y軸の出力をアナログA1へ接続.1でもOK

int sw_state = 0; //初期状態設定
int x_pos = 0; //初期状態設定
int y_pos = 0;//初期状態設定

int a=0; //判定用変数

float x_convert = 0.0f; //計算値の変数設定,オーバーフローしないようにflaot設定
float y_convert = 0.0f; //計算値の変数設定,オーバーフローしないようにflaot設定

 

#include <VarSpeedServo.h>       // ライブラリのインクルード     
VarSpeedServo myservo2;      // サーボオブジェクトの作成
VarSpeedServo myservo3;
VarSpeedServo myservo4;
VarSpeedServo myservo5;
VarSpeedServo myservo6;
 
const int Servo_Pin2 = 5;        // サーボ接続ピン
const int Servo_Pin3 = 6;
const int Servo_Pin4 = 9;
const int Servo_Pin5 = 10;
const int Servo_Pin6 = 11;

int SPEED1 = 10;                 // スピードの設定
int SPEED2 = 10;
int SPEED3 = 10;                 
int SPEED4 = 10;
int SPEED5 = 10;                 
int SPEED6 = 10;
 
void setup() {
  myservo2.attach(Servo_Pin2);
  myservo3.attach(Servo_Pin3);
  myservo4.attach(Servo_Pin4);
  myservo5.attach(Servo_Pin5);
  myservo6.attach(Servo_Pin6);
  myStepper1.setSpeed(10); // 10回転/1分に設定 rpm(1分あたりの回転数)
  pinMode(sw_pin, INPUT_PULLUP); //デジタルIOの7をinput指定とプルアップ
 Serial.begin(9600); //シリアル通信のデータ転送レートを9600bpsで指定.bpsはビット/秒.
} 
 
void loop() {
  x_pos = analogRead(x_pin); //X軸の読み出し
  y_pos = analogRead(y_pin); //Y軸の読み出し
  x_convert = (float)x_pos / 1023.0f * 5.0f; //読み取った値を電圧に変換,floatで計算
  y_convert = (float)y_pos / 1023.0f * 5.0f; //読み取った値を電圧に変換,floatで計算

  if(a==0||a==1)
  {
    if(y_pos>850||a==1)
    {
      if(x_pos>255&&x_pos<767)
      {
        
        a=1;
      }
      else if(a==1)
      {
        a=1;
      }
    }
  }
  if(a==1)
  {
  //0 初期位置
  myservo2.write(70, SPEED2);   
  myservo3.write(70, SPEED3);     
  myservo4.write(70, SPEED4);
  myservo5.write(70, SPEED5);     
  myservo6.write(70, SPEED6);
  
  Serial.print("0");
  
  myservo2.wait();               // サーボ②の動作完了を待つ
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait();   

  //10
  myservo2.write(100, 6);   
  myservo3.write(100, 6);     
  myservo4.write(74, 1);
  myservo5.write(100, 6);     
  myservo6.write(50, 4);
  myStepper1.setSpeed(12);
  myStepper1.step(854);
  Serial.print("10");
         
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait();   

  //20   
  myservo2.write(130, 6);   
  myservo3.write(130, 6);     
  myservo4.write(78, 1);
  myservo5.write(70, 6);     
  myservo6.write(48, 1);
  myStepper1.setSpeed(12);
  myStepper1.step(854);
  Serial.print("20");
              
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait();   

  //30  
  myservo2.write(170, 8);   
  myservo3.write(120, 2);     
  myservo4.write(82, 1);
  myservo5.write(60, 2);     
  myservo6.write(45, 1);
  myStepper1.setSpeed(12);
  myStepper1.step(-854);
  Serial.print("30");
              
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  //40  
  myservo2.write(130, 8);   
  myservo3.write(110, 2);     
  myservo4.write(86, 1);
  myservo5.write(70, 2);     
  myservo6.write(43, 1);
  myStepper1.setSpeed(12);
  myStepper1.step(-854);  
  Serial.print("40");
               
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 
  
  //50  
  myservo2.write(110, 4);   
  myservo3.write(100, 2);     
  myservo4.write(90, 1);
  myservo5.write(80, 2);     
  myservo6.write(40, 1);
  myStepper1.setSpeed(8);
  myStepper1.step(-569);
  Serial.print("50");
          
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 
 
  //60
  myservo2.write(95, 3);   
  myservo3.write(160, 12);     
  myservo4.write(90, 10);
  myservo5.write(90, 2);     
  myservo6.write(45, 1);
  myStepper1.setSpeed(3);
  myStepper1.step(-171);
  Serial.print("60");
        
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  //70
  myservo2.write(86, 2);   
  myservo3.write(165, 1);     
  myservo4.write(70, 4);
  myservo5.write(100, 2);     
  myservo6.write(50, 1);
  myStepper1.setSpeed(3);
  myStepper1.step(-171); 
  Serial.print("70");
             
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  //80
  myservo2.write(78, 2);   
  myservo3.write(170, 1);     
  myservo4.write(60, 2);
  myservo5.write(110, 2);     
  myservo6.write(55, 1);
  myStepper1.setSpeed(3);
  myStepper1.step(-228);
  Serial.print("80");
      
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  //90
  myservo2.write(70, 2);   
  myservo3.write(140, 6);     
  myservo4.write(50, 2);
  myservo5.write(90, 4);     
  myservo6.write(60, 1);
  myStepper1.setSpeed(8);
  myStepper1.step(-567);
  Serial.print("90");
            
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  //100  
  myservo2.write(50, 4);   
  myservo3.write(150, 2);     
  myservo4.write(50, 0);
  myservo5.write(90, 0);     
  myservo6.write(60, 0);
  myStepper1.setSpeed(15);
  myStepper1.step(-1138); 
  Serial.print("100");
              
  myservo2.wait();               
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  //初期位置に戻る
  myservo2.write(70, SPEED2);   
  myservo3.write(70, SPEED3);     
  myservo4.write(70, SPEED4);
  myservo5.write(70, SPEED5);     
  myservo6.write(70, SPEED6);
  myStepper1.setSpeed(10);
  myStepper1.step(2845); 
  Serial.print("110");
  
  myservo2.wait();              
  myservo3.wait(); 
  myservo4.wait(); 
  myservo5.wait(); 
  myservo6.wait(); 

  digitalWrite(4,  LOW); 
  digitalWrite(7,  LOW); 
  digitalWrite(8, LOW); 
  digitalWrite(12, LOW); 
  
  
  a=0; 
  }  
}

情報系を専攻している方からすると,スマートなプログラムではないとは思いますが,一応動作します(自分は一応基本情報技術者を持っていますが,がっつり専攻していないです).
複数の便利なライブラリを使用し,各モータは回転角と角速度を指定します.都合上,両パラメータは整数型にする必要があるため適宜切り上げ/切り下げをしています.ステッピングモータは相対的な角度を指定するのに対し,サーボモータは絶対的な角度を指定しています.サーボモータは±90 deg(0~180 deg)まで制御できますが,一部の可動域を考慮して,70 degを原点として再度,回転角を計算し直して指定しています.

8. ギアボックスの設計・製作(fusion360・3Dプリンタ)

当初は,ΣCにサーボモータを直接つなぐことを考えていましたが,トルク不足が発生しました(そもそもモータの直つなぎは良くないかも).その後,サーボモータをステッピングモータに変更しましたが,トルク不足が発生しました.

そこで,ギアボックスを製作することにしました.
ギアボックスを製作するにあたって,少し機構学を復習しました.

復習1
復習2
復習3
復習4
復習5
復習6
復習7

そして,ギアボックスを製作しました.
計算過程を載せていますが,各係数においてPLAのものが調べても分からなかったので,適当な物性値にしています(良くない).
計算して得られた歯幅よりも実物は大きく設計しました.
トルクと角速度はトレードオフなので,トルクを何倍にしたいか決めて,歯数を決定します.

計算過程1
計算過程2
計算過程3

結果は上手く動作しませんでした.

理由はモーメントの腕の長さをアームの長さの半分に設定したことによるトルク不足だと考えています.重心をアームの長さの半分に設定しましたが,明らかにハンドが重いため,重心の位置が異なります.そこで,トルクを10倍から20倍に設定しなおし,追加でギアボックスを製作しました.

追加ギアボックスの計算

計算結果を用いて設計し直しました.

図面は下図のとおりです.

ギアボックス組立図
ギアボックス出力側
ギアボックスの蓋
ギアボックス第二入力側
歯車台
被動歯車
被動歯車2
駆動歯車
駆動歯車2

歯車はアドインから「SpurGear」を実行しています.各歯車のパラメータは図面右上の部品欄の説明に書いています.このギアボックスを使用することで,トルクが20倍となり,アームを持ち上げることができるようになりました.しかし,トルクを上げるとその分速度は落ちるので,速度は1/20になっています.

9. 構造解析(材料力学的考察)

製作したロボットのアームを材料力学的な考察をします.例として,ロボットのハンド部分に集中荷重(外力)がかかったとして考えます.この外力によって部材(今回はロボットのアーム)がどのように変形するかを考えるのが材料力学です.各パーツの断面二次モーメントを計算しても良いのですが,fusion 360にはシミュレーション(構造解析)があるので,そちらを利用します.青い矢印が荷重を示しており,10 Nに設定しました.

構造解析画面
メッシュ作成

各パーツは接触しており,解析時にメッシュを切る(三角形に分割する)必要があるため,単純化を行っています(有限要素法等が関連しています).4節で,ネジ部をモデル化してしまっているため,ネジ部は単純化し,ただの円柱になっています.単純化を行うことで,計算しやすい形状に近似し,計算が簡単になります.また,円は四角形に単純化しました.なお,厳密には違いますが,材質は全てABSプラスチックに設定しました.拘束面は,台1~7の上面を固定しました.結果を示します.

結果(斜め視点)
結果の拡大(斜め視点)
結果(正面)
結果(上面)
結果(ハンド拡大)

青い箇所は安全率(safety factor)が高く,壊れにくいです.安全率は設計における強度計算式の信頼度,使用材料のばらつき,予期しえない負荷応力,など不確実さを表す数値です.安全率が高い箇所も多くありますが,一部の断面積が小さい箇所では安全率が小さくなってしまいました.最小値となった箇所は,10 Nの荷重によるモーメントが原因だと考えています.しかし,その箇所はステッピングモータの回転軸(金属製)であり,恐らく支障はないと考えています.荷重として,10 Nを試してみましたが,約1 kgのものを置くと壊れそうということが分かりました.今回は簡単に計算するために,形状を単純化し,荷重は10 Nとしましたが,実際には動作をしながらであるため,各関節の動作によって発生する外レンチ(外力+外モーメント)を考慮する必要があるかもしれません(少し機械力学的な考え方かも).また,発展させて振動工学的に考えるのも良いかもしれません(今回の場合は,多自由度系の振動になると思います).

10. 実機実験

各パーツを3Dプリンタで印刷し,回路と組み合わせて出来上がったロボットアームが下図です.

ロボットのアーム
正面図
右側面図
平面図

実験動画は下記のとおりです.

概ね再現できたのではないでしょうか.今回は右腕だけですが,完成度はそこそこと考えています.何より動作してくれたのが本当に嬉しいです.モータ2,3が角度によってはトルク不足になっているので,改良が必要そうですね.

11. 謝辞

本製作を進めるにあたり,活発な議論にお付き合いいただきました本研究室の先輩方,同回生,下級生の皆様に対し,この場を借りて御礼申し上げます.

12. 参考文献

[1] 早川恭弘,櫟弘明,矢野順彦:ロボット工学,コロナ社,2007.
[2] 細田耕:実践ロボット制御 基礎から動力学まで,オーム社,2019.
[3] 吉川恒夫:ロボット制御基礎論,コロナ社,1988.
[4] 林洋次ら:機械製図,実教出版,2012.
[5] 塚田忠夫,舟橋宏明,野口昭治:機械設計入門,実教出版,2014.
[6] 西村尚:ポイントを学ぶ材料力学,丸善出版,1988.
[7] 藤田勝久:振動工学 新装版 振動の基礎から実用解析入門まで,森北出版,2016.

あとがき

個人的に作った感想としては,楽しかったぐらいしかないのですが,せっかくなので書きます.
普段ものを作るときは割と適当な感覚で作るので,今回みたいに趣味で理論とかをいろいろ考えながら作ったのは久々でした.
今まで研究や授業で勉強してきて役に立ったものもあれば,勉強し直さないといけなかったりして,高専時代もっとやっていればと思いました.

一年前は統計やAIを使用して,スマブラ分析的なことをしていましたが,今はスマブラ関連のものづくりをしていて,それぞれ別のものに捉えられているかもしれませんが,その背景には工学(ここでの工学とは工学全般を指します)をスマブラにフィードバックしたいというところから派生しています(スマブラと工学の融合).一年前は,スマブラ×工学の先駆けとして,需要がありそうなスマブラ分析に関する統計やAI(yoloを用いた物体検出)をしていました.スマブラの各パラメータの分析や対戦動画から得られる情報を分析(情報を得ることや方法自体にも価値があると考えています)することで何かスマブラ界隈に寄与できないかと考えていました(今も考えています).その次に,高専時代からの専攻分野である機械工学で何かできないかを考えていました.ものづくりは元から好きなので,スマブラ関連のものづくりを始めました.機械工学より統計を先に優先したのは,需要が統計やAIの方がありそうと考えたからです.今のスマブラ関連のものづくりは正直自己満によるものが大部分を占めていると考えています.ただ,もし自分のようにものづくり(機械工学)に興味がある人がいて,参考になれば…と思い,作ったものを公開しています.できれば,機械工学全般をスマブラに応用したいと考えています.今の大学院では,ロボット工学が主な専攻分野ですが,実は,高専専攻科時代の専攻分野は流体力学(機械工学の4力のうちの一つで,今まで特に言及したことないかも)なので,流体力学を使って何かしたいと考えています.でも,パッと思いつくのは,周期境界条件を利用した数値流体シミュレーションでルカリオの波動弾を作ることぐらいで,あんまり思いつかないです(そもそも流体力学は難しすぎるので,作りたいってなっても運動方程式がたてれるか怪しい).他にもいろいろしてみたいことはたくさんあります.ただ,自分自身初となる展示会への出展が決まったため,7月くらいまではそちらの準備をしたいと考えています(ある大型オフ大会の展示会).もし,展示会に興味があれば楽しみにしてください~!!
今回のロボットも持っていく予定です.

ここまで読んでいただき,ありがとうございました!!


この記事が気に入ったらサポートをしてみませんか?