team M and Aの部屋

ゆるーい電子工作好きな夫婦のブログです。

ロボットアームとArduino (GR-ROSE)で機構学のお勉強

tmanda.hatenablog.com で紹介したロボットアームを制御するための機構学のお勉強です。 f:id:tmanda:20200506103951p:plain ↑これの話です。

さっそくはじめていきましょう。

まずはロボットアームの関節角度を定義します。今回はロボットアームが垂直に立った状態を0度とします。
サーボモータの可動範囲は0度±135度) f:id:tmanda:20200506130807p:plain

ロボットアームの関節の位置(サーボモータの回転軸の位置)を計算します。 f:id:tmanda:20200509141301p:plain

今回はロボットアームの先端が水平に同じ高さになるように制御します。 f:id:tmanda:20200506131038p:plain 

「ロボットアームの先端が水平に同じ高さ」を数式で表すと次のようになります。 f:id:tmanda:20200509141308p:plain

①②の式を代入すると、 f:id:tmanda:20200509141354p:plain ここで黄色マークした式をこの後で使います。根本のモータを好きなように制御したときの他の2つのモータ角度を計算します。 f:id:tmanda:20200506131411p:plain

あとはプログラムにモータ角度をセットするコードを書くということになります。

※注意点
1. モータの可動範囲は実物のアームが干渉しない範囲になります。アーム同士がぶつからないようにしましょう。
2. 今回は高さを一定に保つ制御をしていますが、リンクが届かない範囲があります。以下の絵を参照。
f:id:tmanda:20200506135721p:plain

今回使用したArduino互換ボード「GR-ROSE」には近藤科学サーボモータを接続できるコネクタとライブラリが用意されているので、すぐに試すことができました。

www.renesas.com

ソースコードは以下です。

#include <ICS.h>

IcsController ICS(Serial1);
IcsServo servo0;
IcsServo servo1;
IcsServo servo2;
IcsServo servo3;

int theta0_pulse = 0;  //3500-11500
int theta1_pulse = 0;
int theta2_pulse = 0;
int theta3_pulse = 0;

bool motion_enable = false;
bool dir0 = false;
bool dir1 = false;

#define TH0_ZERO 7500
#define TH0_LIMIT_MIN 3500
#define TH0_LIMIT_MAX 11500

#define TH1_ZERO 7500
#define TH1_LIMIT_MIN 4500
#define TH1_LIMIT_MAX 10500

#define TH2_ZERO 7500
#define TH2_LIMIT_MIN 4500
#define TH2_LIMIT_MAX 10500

#define TH3_ZERO 7500
#define TH3_LIMIT_MIN 4500
#define TH3_LIMIT_MAX 10500


void setup() {

  Serial.begin(115200);
  pinMode(PIN_LED1, OUTPUT);

  ICS.begin();
  servo0.attach(ICS, 0x01);
  servo1.attach(ICS, 0x02);
  servo2.attach(ICS, 0x03);
  servo3.attach(ICS, 0x04);
  
  theta0_pulse = TH0_ZERO;
  theta1_pulse = servo1.getPosition();
  theta2_pulse = servo2.getPosition();
  theta3_pulse = servo3.getPosition();

  setPosition4axis(theta0_pulse, theta1_pulse, theta2_pulse, theta3_pulse);
  delay(1000);
  digitalWrite(PIN_LED1, HIGH);
}

void loop() {
  if(Serial.available()){
    switch(Serial.read()){
      case 's':
        motion_enable = !motion_enable;
        break;
      case '0':
        motion_enable = false;
        theta0_pulse = TH0_ZERO;
        theta1_pulse = TH1_ZERO;
        theta2_pulse = TH2_ZERO;
        theta3_pulse = TH3_ZERO;
        break;
      default:
        break;
    }
  }
  
  if(motion_enable){
    int tmp;
    int dth = 50;

    if(theta0_pulse > (TH0_LIMIT_MAX - 400)){
      dth = 50 - (TH0_LIMIT_MAX - 400 - theta0_pulse)/8;
      if(dth < 5) dth = 5;
    } else if(theta0_pulse < (TH0_LIMIT_MIN + 400)) {
      dth = 50 - (TH0_LIMIT_MIN + 400 - theta0_pulse)/8;
      if(dth < 5) dth = 5;
    } else {
      dth = 50;
    }
    if(dir0){
      theta0_pulse += dth;
      if(theta0_pulse > TH0_LIMIT_MAX){
        theta0_pulse = TH0_LIMIT_MAX;
        dir0 = !dir0;
      }
    } else {
      theta0_pulse -= dth;
      if(theta0_pulse < TH0_LIMIT_MIN){
        theta0_pulse = TH0_LIMIT_MIN;
        dir0 = !dir0;
      }
    }//*/
    if(theta1_pulse > 8620){
      dth = 100 - (theta1_pulse-8620)/4;
      if(dth < 5) dth = 5;
    } else if(theta1_pulse < 6000) {
      dth = 100 - (6000 - theta1_pulse)/4;
      if(dth < 5) dth = 5;
    } else {
      dth = 100;
    }
    if(dir1){
      theta1_pulse += dth;
      if(theta1_pulse > 9000){
        theta1_pulse = 9000;
        dir1 = !dir1;
      }
    } else {
      theta1_pulse -= dth;
      if(theta1_pulse < 5620){
        theta1_pulse = 5620;
        dir1 = !dir1;
      }
    }//*/
    tmp = calc_theta2_pulse(theta1_pulse);
    if(tmp != 0) theta2_pulse = tmp;
    theta3_pulse = TH3_ZERO - ((theta1_pulse - TH1_ZERO) + (theta2_pulse - TH2_ZERO));
  }

  setPosition4axis(theta0_pulse, theta1_pulse, theta2_pulse, theta3_pulse);
  delay(25);
}

void setPosition4axis(int pos0, int pos1, int pos2, int pos3){
  if(pos0 < TH0_LIMIT_MIN){
    pos0 = TH0_LIMIT_MIN;
  } else if(pos0 > TH0_LIMIT_MAX){
    pos0 = TH0_LIMIT_MAX;
  }
  if(pos1 < TH1_LIMIT_MIN){
    pos1 = TH1_LIMIT_MIN;
  } else if(pos1 > TH1_LIMIT_MAX){
    pos1 = TH1_LIMIT_MAX;
  }
  if(pos2 < TH2_LIMIT_MIN){
    pos2 = TH2_LIMIT_MIN;
  } else if(pos2 > TH2_LIMIT_MAX){
    pos2 = TH2_LIMIT_MAX;
  }
  if(pos3 < TH3_LIMIT_MIN){
    pos3 = TH3_LIMIT_MIN;
  } else if(pos3 > TH3_LIMIT_MAX){
    pos3 = TH3_LIMIT_MAX;
  }
  Serial.print(servo0.setPosition(pos0)); Serial.write('\t');
  Serial.print(servo1.setPosition(pos1)); Serial.write('\t');
  Serial.print(servo2.setPosition(pos2)); Serial.write('\t');
  Serial.print(servo3.setPosition(pos3)); Serial.println();//*/
}


int calc_theta2_pulse(int theta1){
  const float l1 = 68.0f;
  const float l2 = 60.0f;
  const float l2_ref = 89.8f;
  const float pulse_to_rad = 135.0f*M_PI/(4000.0f*180.0f);
  const float rad_to_pulse = (4000.0f*180.0f)/(135.0f*M_PI);
  
  const float theta1_rad = (float)(theta1 - TH1_ZERO) * pulse_to_rad;
  const float p2_y = l1 * cos(theta1_rad);
  
  float theta2_rad;
  float cos_theta2 = (l2_ref - p2_y)/l2;
  
  if(cos_theta2 > 1.0f){
    return 0; //Error
  }
  theta2_rad = acos(cos_theta2) - theta1_rad;
  
  return ((theta2_rad * rad_to_pulse) + TH2_ZERO);
}