ロボットアームとArduino (GR-ROSE)で機構学のお勉強
tmanda.hatenablog.com
で紹介したロボットアームを制御するための機構学のお勉強です。
↑これの話です。
さっそくはじめていきましょう。
まずはロボットアームの関節角度を定義します。今回はロボットアームが垂直に立った状態を0度とします。
(サーボモータの可動範囲は0度±135度)

ロボットアームの関節の位置(サーボモータの回転軸の位置)を計算します。

今回はロボットアームの先端が水平に同じ高さになるように制御します。
「ロボットアームの先端が水平に同じ高さ」を数式で表すと次のようになります。

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

あとはプログラムにモータ角度をセットするコードを書くということになります。
※注意点
1. モータの可動範囲は実物のアームが干渉しない範囲になります。アーム同士がぶつからないようにしましょう。
2. 今回は高さを一定に保つ制御をしていますが、リンクが届かない範囲があります。以下の絵を参照。

今回使用したArduino互換ボード「GR-ROSE」には近藤科学のサーボモータを接続できるコネクタとライブラリが用意されているので、すぐに試すことができました。
ソースコードは以下です。
#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);
}