Raspberry Pi Pico Wを使用してステッピングモータ3軸の直線補間を行う実験をしました。以前2軸の直線補間の記事を掲載しましたが、3軸のCNCを作りたかったので3軸の直線補間を行うプログラムを作成しました。
前回の2軸の直線補間ではパルス分配方式として代数演算方式を使用しました。(2軸の直線補間に関しての記事はこちら)
ところが、3軸の直線補間の場合、代数演算方式のパルス分配方式は原理的に使用出来ないことが分かりました。そこで全く別のアルゴリズムを考えました。3軸の内、最も移動量の多い軸を基本軸として、その軸を等間隔に1パルスづつ送り、他の2軸はその基本軸との関係を見ながら適宜送る方式としました。
尚、プログラミングツールとしてはArduino IDEを使用し、ステッピングモータの駆動にArduino言語のStepperライブラリを使用しています。
3軸直線補間の原理
直線補間を行う3軸をX軸、Y軸、Z軸とした時に、現在位置から目的位置まで移動するときに、最も移動量の多い軸を基本軸とします。
例えば現在位置を(X=0, Y=0, Z=0)とした場合に、直線補間で移動する位置が(X=300, Y=200, Z=100)とした場合に、X軸の移動量が300、Y軸の移動量が200、Z軸の移動量が100で最も多く移動するのはX軸なのでX軸を基本軸とします。
ここでX軸の移動量300を「X軸の全移動パルス数」と呼ぶことにします。
また、Y軸の移動量200を「Y軸の全移動パルス数」と呼ぶことにします。
また、Z軸の移動量100を「Z軸の全移動パルス数」と呼ぶことにします。
また、移動中の各軸のスタートからその時点までに移動したパルス数を、それぞれ「X軸の現在のパルス数」、「Y軸の現在のパルス数」、「Z軸の現在のパルス数」と呼ぶことにします。
基本軸X軸は、一定間隔で1パルスづつ送ります。
X軸を1パルス送る毎にY軸に対して、
(Y軸の全移動パルス数 × X軸の現在パルス数/X軸の全移動パルス数) – Y軸の現在パルス数
を計算します。この値が1以上になった時にY軸を1パルス送ります。
同様に、Z軸に対して
(Z軸の全移動パルス数 × X軸の現在パルス数/X軸の全移動パルス数) – Z軸の現在パルス数
を計算します。この値が1以上になった時にZ軸を1パルス送ります。
以上を繰り返すことによってX軸、Y軸、Z軸の3軸の直線補間を実現します。
実験装置
X軸、Y軸、Z軸のステッピングモータは28BYJ-48とそのドライバーボードのセットを使用しました。このステッピングモータとドライバーボードは複数のネット通販から購入することが出来ます。
コントローラとしてはRaspberry Pi Pico Wを使用して実験装置を組みました。
今回使用したステッピングモータの主な仕様は表1の通りです。このモータは1/64のギアで減速されていて、1-2相励磁の場合にステップ角は5.625°/64なので4096ステップでモータ軸が1回転します。最大自起動周波数が500ppsなので、500パルス/sec以下で起動する必要があります。起動後は徐々に加速を行った場合に、最大で1000パルス/secまで仕様上は可能と思われます。
ただし、Stepperライブラリーのstep()関数を使用した場合の励磁方式は2相励磁のようです。そのため2048ステップでモータ1回転となります。
項目 | 仕様 |
相数 | 4相 |
励磁方式 | 1-2相励磁方式 |
ステップ角 | 5.625°/64 (減速比1/64) |
電圧 | 5VDC |
相抵抗 | 22Ω ± 7% 25°C |
最大応答周波数 | 1000pps |
最大自起動周波数 | 500pps |
引き込みトルク | 800gf.cm / 5VDC 400pps |
実験装置回路図
28BYJ-48ステッピングモータのドライバーボードはテキサスインスツルメンツのULN2003ANと言うダーリントントランジスタアレイが使用されています。
ドライバーボード自体の資料が無かったので、テスタと基板のパターンを見て回路を調べました。
X軸用のステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP0~GP3に接続しました。
Y軸用のステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP4~GP7に接続しました。
Z軸用のステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP8~GP11に接続しました。
起動用のスイッチをRaspberry Pi Pico WのGP15に接続しました。
図3に実験装置の回路図を示します。
ステッピングモータの駆動に関して
今回使用したステッピングモータ28BYJ-48は1-2相励磁の場合、1回転4096ステップとなります。ただStepperライブラリのstep()関数は2相励磁方式です。2相励磁の場合1回転2048ステップとなります。
Stepper(step2, pin1, pin2, pin3, pin4)
関数でI/Oピンを割り振った場合の励磁順序は表2の様になっています。
引数 | 1ステップ | 2ステップ | 3ステップ | 4ステップ |
pin1 | HIGH | LOW | LOW | HIGH |
pin2 | LOW | HIGH | HIGH | LOW |
pin3 | HIGH | HIGH | LOW | LOW |
pin4 | LOW | LOW | HIGH | HIGH |
また、ステッピングモータ28BYJ-48の仕様書では1-2相励磁方式の励磁順序の表が載っていますが、それから推測した2相励磁の場合の励磁順序は表3の通りです。(時計方向回り時)
ドライバボード入力 | モータリード線 | 1ステップ | 2ステップ | 3ステップ | 4ステップ |
+ (5V) | リード線色 赤 | 5Vに接続 | 5Vに接続 | 5Vに接続 | 5Vに接続 |
IN1 | リード線色 青 | HIGH | LOW | LOW | HIGH |
IN2 | リード線色 桃 | HIGH | HIGH | LOW | LOW |
IN3 | リード線色 黄 | LOW | HIGH | HIGH | LOW |
IN4 | リード線色 橙 | LOW | LOW | HIGH | HIGH |
以上からpin1~pin4とドライバボードIN1~IN4の関係は次の様になります。
pin1: リード線色 青 (ドライバボード入力 IN1)
pin2: リード線色 桃 (ドライバボード入力 IN3)
pin3: リード線色 黄 (ドライバボード入力 IN2)
pin4: リード線色 橙 (ドライバボード入力 IN4)
ドライバボード入力にIN2とIN3がひっくり返っていることに注意が必要です。
動作動画
以下動画は、直線補間で順々に(X=1000,Y=1000,Z=0)の位置へ移動→(X=500,Y=500,Z=2000)の位置へ移動→(X=0,Y=0,Z=1000)の位置へ移動→(X=-2000,Y=800,Z=1500)の位置へ移動→(X=-1000,Y=-1000,Z=-400)の位置へ移動→(X=0,Y=0,Z=0)の位置へ移動するプログラムを作成して実行したときの状況です。
パソコンの画面は、Raspberry Pi Pico WからUSBシリアル通信を使用して送られた位置データをX-Y-Zプロットした画面です。3次元的なモータ動作が直観的に分からないので、このパソコンソフトを新たに作成しました。このソフトに関しては別途紹介します。
作成したプログラム
Raspberry Pi Pico Wのプログラム開発ツールとして、Arduino IDEを使用しました。
GP15に接続されたスイッチをONすると、直線補間で順々に(X=1000,Y=1000,Z=0)の位置へ移動→(X=500,Y=500,Z=2000)の位置へ移動→(X=0,Y=0,Z=1000)の位置へ移動→(X=-2000,Y=800,Z=1500)の位置へ移動→(X=-1000,Y=-1000,Z=-400)の位置へ移動→(X=0,Y=0,Z=0)の位置へ移動するプログラムを以下に示します。
各軸の移動ではStepperライブラリを使用していますが、X,Y,Z軸それぞれ1パルスづつ送る方式なので、送り速度はsetSpeed()関数では決まりません。1パルス送る毎にdelayMicroseconds()関数による待ちを入れて、このディレイ時間で送り速度がきまります。step()関数からは出来るだけ速く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめにセットします。
移動中はArduino IDEのシリアルプロッタ等を使用して、X軸、Y軸、Z軸の各位置をグラフ表示出来るように、パソコンへUSBシリアルポートを使用して、現在位置を以下の形式で送信しています。
X:「X軸パルス位置」,Y:「Y軸パルス位置」,Z「Z軸パルス位置」
例
X:45,Y:22,Z:100
以下に全ソースコードを4個の部分に分割して説明します。
1.初期処理 setup()関数
初期処理を行うsetup()関数のソースコードを以下に示します。
ステッピングモータを駆動するのにArduino言語のStepperライブラリーを使用しています。ステッピングモータの回転速度をsetSpeed()関数で500rpmに設定していますが、実際には1ステップづつの送りしかしないので、setSpeed()関数では回転速度は決まりません。1パルス送る毎にdelayMicroseconds()関数による待ちを入れて、このディレイ時間で送り速度を決めています。step()関数からは出来るだけ速く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめにセットします。
現在位置を都度パソコンへUSBシリアル通信を使用して送るので、USBシリアル通信の設定をしています。
//------Raspberry Pi Pico W 同時3軸直線補間実験ソフト------------------
#include <Stepper.h>
Stepper stepper_x(2048, 0, 2, 1, 3); //X軸モータ設定(1回転2048ステップ)
Stepper stepper_y(2048, 4, 6, 5, 7); //Y軸モータ設定(1回転2048ステップ)
Stepper stepper_z(2048, 8, 10, 9, 11); //Z軸モータ設定(1回転2038ステップ)
long x_cur_pos = 0; //x軸現在位置(符号付き絶対座標のパルス位置)
long y_cur_pos = 0; //y軸現在位置(符号付き絶対座標のパルス位置)
long z_cur_pos = 0; //z軸現在位置(符号付き絶対座用のパルス位置)
void setup() {
Serial.begin(9600); //USBシリアル通信初期化
pinMode(15, INPUT_PULLUP); //GP15を入力端子に設定(スイッチ接続)
pinMode(0, OUTPUT); //X軸ステッピングモータ(青線)
pinMode(1, OUTPUT); //x軸ステッピングモータ(桃線)
pinMode(2, OUTPUT); //x軸ステッピングモータ(黄線)
pinMode(3, OUTPUT); //x軸ステッピングモータ(橙線)
pinMode(4, OUTPUT); //Y軸ステッピングモータ(青線)
pinMode(5, OUTPUT); //Y軸ステッピングモータ(桃線)
pinMode(6, OUTPUT); //Y軸ステッピングモータ(黄線)
pinMode(7, OUTPUT); //Y軸ステッピングモータ(橙線)
pinMode(8, OUTPUT); //Z軸ステッピングモータ(青線)
pinMode(9, OUTPUT); //Z軸ステッピングモータ(桃線)
pinMode(10, OUTPUT); //Z軸ステッピングモータ(黄線)
pinMode(11, OUTPUT); //Z軸ステッピングモータ(橙線)
//ステッピングモータの初期設定
stepper_x.setSpeed(500); //X軸ステッピングモータの回転速度500rpm
stepper_y.setSpeed(500); //Y軸ステッピングモータの回転速度500rpm
stepper_z.setSpeed(500); //Z軸ステッピングモータの回転速度500rpm
}
2.メイン処理 loop()関数
メインの処理を行うloop()関数のソースコードを以下に示します。
loop()関数で実際のステッピングモータの動作を行います。
3軸の直線補間を行う関数がlinearInterporation3()関数です。
GP15に接続されたスイッチが押されるとlinearInterporation3()関数で3軸の直線補間を以下6回行います。
1.(X=0,Y=0,Z=0)の位置から(X=1000,Y=1000,Z=0)の位置まで直線補間で移動。
2.(X=1000,Y=1000,Z=0)の位置から(X=500,Y=500,Z=2000)の位置まで直線補間で移動。
3.(X=500,Y=500,Z=2000)の位置から(X=0,Y=0,Z=1000)の位置まで直線補間で移動。
4.(X=0,Y=0,Z=1000)の位置から(X=-2000,Y=800,Z=1500)の位置まで直線補間で移動。
5.(X=-2000,Y=800,Z=1500)の位置から(X=-1000,Y=-1000,Z=-400)の位置まで直線補間で移動。
6.(x=-1000,Y=-1000,Z=-400)の位置から(X=0,Y=0,Z=0)の位置まで直線補間で移動。
void loop() {
while (true){
//スイッチ読込(GP15 LOW判断)
if (digitalRead(15) == LOW){
break;
}
}
long x_pos = 1000; //X軸目的位置(パルス)
long y_pos = 1000; //Y軸目的位置(パルス)
long z_pos = 0; //Z軸目標位置(パルス)
long vel = 400; //送り速度(パルス/sec)
linearInterporation3(x_pos, y_pos, z_pos, vel); //直線補間移動
x_pos = 500; //X軸目的位置(パルス)
y_pos = 500; //Y軸目的位置(パルス)
z_pos = 2000; //Z軸目標位置(パルス)
vel = 400; //送り速度(パルス/sec)
linearInterporation3(x_pos, y_pos, z_pos, vel); //直線補間移動
x_pos = 0; //X軸目的位置(パルス)
y_pos = 0; //Y軸目的位置(パルス)
z_pos = 1000; //Z軸目標位置(パルス)
vel = 400; //送り速度(パルス/sec)
linearInterporation3(x_pos, y_pos, z_pos, vel); //直線補間移動
x_pos = -2000; //X軸目的位置(パルス)
y_pos = 800; //Y軸目的位置(パルス)
z_pos = 1500; //Z軸目標位置(パルス)
vel = 400; //送り速度(パルス/sec)
linearInterporation3(x_pos, y_pos, z_pos, vel); //直線補間移動
x_pos = -1000; //X軸目的位置(パルス)
y_pos = -1000; //Y軸目的位置(パルス)
z_pos = -400; //Z軸目標位置(パルス)
vel = 400; //送り速度(パルス/sec)
linearInterporation3(x_pos, y_pos, z_pos, vel); //直線補間移動
x_pos = 0; //X軸目的位置(パルス)
y_pos = 0; //Y軸目的位置(パルス)
z_pos = 0; //Z軸目標位置(パルス)
vel = 250; //送り速度(パルス/sec)
linearInterporation3(x_pos, y_pos, z_pos, vel); //直線補間移動
}
3. 直線補間(3軸)実施 linearInterporation3()関数
3軸の直線補間を行うlinearInterporation3()関数のソースコードを以下に示します。
最も移動量の多い軸を基本軸として、その軸を1パルスづつ送ります。
基本軸の位置を基に他の軸の位置を求めそれぞれの位置へ送ります。
逐次現在位置をUSBシリアル通信でパソコンに送信しています。
//---------3軸直線補間実行処理------------
//パラメーラ
//x_pos: X軸目標位置(パルス)
//y_pos: Y軸目標位置(パルス)
//z_pos; Z軸目標位置(パルス)
//vel: 送り速度(パルス/sec)
//----------------------------------------
void linearInterporation3(long x_pos, long y_pos, long z_pos, long vel){
long XincPulse = x_pos - x_cur_pos; //X軸増分パルス
long YincPulse = y_pos - y_cur_pos; //Y軸増分パルス
long ZincPulse = z_pos - z_cur_pos; //Z軸増分パルス
long x_all_pulse = abs(XincPulse); //X軸移動パルス数絶対値
long y_all_pulse = abs(YincPulse); //Y軸移動パルス数絶対値
long z_all_pulse = abs(ZincPulse); //Z軸移動パルス数絶対値
int basicAxisNo; //基準軸(1:X, 2:Y, 3:Z)
//基準軸の選定(最も移動量の多い軸を基準軸とする)
if ((x_all_pulse >= y_all_pulse) && (x_all_pulse >= z_all_pulse)){
basicAxisNo = 1; //基準軸をX軸に設定
}
else if ((y_all_pulse >= x_all_pulse) && (y_all_pulse >= z_all_pulse)){
basicAxisNo = 2; //基準軸をY軸に設定
}
else if ((z_all_pulse >= x_all_pulse) && (z_all_pulse >= y_all_pulse)){
basicAxisNo = 3; //基準軸をZ軸に設定
}
long sleepTime = 1000000 / vel; //次のパルス出力までのスリープ時間(μsec);
long x_cur_pulse = 0; //X軸現在出力パルス数
long y_cur_pulse = 0; //Y軸現在出力パルス数
long z_cur_pulse = 0; //Z軸現在出力パルス数
bool x_end_flag = false; //X軸目的位置到着フラグ
bool y_end_flag = false; //Y軸目的位置到着フラグ
bool z_end_flag = false; //Z軸目標位置到達フラグ
if (x_all_pulse == 0){ //X軸移動量ゼロ?
x_end_flag = true; //X軸移動量ゼロの時は移動完了とする
}
if (y_all_pulse == 0){ //Y軸移動量ゼロ?
y_end_flag = true; //Y軸移動量ゼロの時は移動完了とする
}
if (z_all_pulse == 0){ //Z軸移動量ゼロ?
z_end_flag = true; //Z軸移動量ゼロの時は移動完了とする
}
//直線補間パルス発生処理
while (true){
//基準軸1パルス移動
switch(basicAxisNo){
case 1: //基準軸Xの時
if (XincPulse > 0){ //移動方向正?
x_1_pulse_mov(+1); //X軸正方向に1パルス移動
x_cur_pulse++; //X軸現在出力パルス数プラス
x_cur_pos++; //X軸現在位置プラス
}
else if (XincPulse < 0){ //移動方向負?
x_1_pulse_mov(-1); //X軸負方向に1パルス移動
x_cur_pulse++; //X軸現在出力パルス数プラス
x_cur_pos--; //X軸現在位置マイナス
}
break;
case 2: //基準軸Yの時
if (YincPulse > 0){ //移動方向正?
y_1_pulse_mov(+1); //Y軸正方向に1パルス移動
y_cur_pulse++; //Y軸現在出力パルス数プラス
y_cur_pos++; //Y軸現在位置プラス
}
else if (YincPulse < 0){ //移動方向負?
y_1_pulse_mov(-1); //Y軸負方向に1パルス移動
y_cur_pulse++; //Y軸現在出力パルス数プラス
y_cur_pos--; //Y軸現在位置マイナス
}
break;
case 3: //基準軸Zの時
if (ZincPulse > 0){ //移動方向正?
z_1_pulse_mov(+1); //Z軸正方向に1パルス移動
z_cur_pulse++; //Z軸現在出力パルス数プラス
z_cur_pos++; //Z軸現在位置プラス
}
else if (ZincPulse < 0){ //移動方向負?
z_1_pulse_mov(-1); //Z軸負方向に1パルス移動
z_cur_pulse++; //Z軸現在出力パルス数プラス
z_cur_pos--; //Z軸現在位置マイナス
}
break;
}
int pulse;
//基準軸に同期してX軸移動
switch(basicAxisNo){
case 1: //基準軸はX軸の時
pulse = 0;
break;
case 2: //基準軸はY軸の時
pulse = (x_all_pulse * y_cur_pulse / y_all_pulse) - x_cur_pulse;
break;
case 3: //基準軸はZ軸の時
pulse = (x_all_pulse * z_cur_pulse / z_all_pulse) - x_cur_pulse;
break;
}
if (pulse >= 1){
for (int i = 0; i < pulse; i++){
if (XincPulse > 0){ //移動方向正?
x_1_pulse_mov(+1); //X軸正方向に1パルス移動
x_cur_pulse++; //X軸現在出力パルス数プラス
x_cur_pos++; //X軸現在位置プラス
}
else if (XincPulse < 0){ //移動方向負?
x_1_pulse_mov(-1); //X軸負方向に1パルス移動
x_cur_pulse++; //X軸現在出力パルス数プラス
x_cur_pos--; //X軸現在位置マイナス
}
}
}
//基準軸に同期してY軸移動
switch(basicAxisNo){
case 1: //基準軸X軸の時
pulse = (y_all_pulse * x_cur_pulse / x_all_pulse) - y_cur_pulse;
break;
case 2: //基準軸Y軸の時
pulse = 0;
break;
case 3: //基準軸Z軸の時
pulse = (y_all_pulse * z_cur_pulse / z_all_pulse) - y_cur_pulse;
break;
}
if (pulse >= 1){
for (int i = 0; i < pulse; i++){
if (YincPulse > 0){ //移動方向正?
y_1_pulse_mov(+1); //Y軸正方向に1パルス移動
y_cur_pulse++; //Y軸現在出力パルス数プラス
y_cur_pos++; //Y軸現在位置プラス
}else if (YincPulse < 0){ //移動方向負?
y_1_pulse_mov(-1); //Y軸負方向に1パルス移動
y_cur_pulse++; //Y軸現在出力パルス数プラス
y_cur_pos--; //Y軸現在位置マイナス
}
}
}
//基準軸に同期してZ軸移動
switch(basicAxisNo){
case 1: //基準軸X軸の時
pulse = (z_all_pulse * x_cur_pulse / x_all_pulse) - z_cur_pulse;
break;
case 2: //基準軸Y軸の時
pulse = (z_all_pulse * y_cur_pulse / y_all_pulse) - z_cur_pulse;
break;
case 3: //基準軸Z軸の時
pulse = 0;
break;
}
if (pulse >= 1){
for (int i = 0; i < pulse; i++){
if (ZincPulse > 0){ //移動方向正?
z_1_pulse_mov(+1); //Z軸正方向に1パルス移動
z_cur_pulse++; //Z軸現在出力パルス数プラス
z_cur_pos++; //Z軸現在位置プラス
}
else if (ZincPulse < 0){ //Z移動方向負?
z_1_pulse_mov(-1); //Z軸負方向に1パルス移動
z_cur_pulse++; //Z軸現在出力パルス数プラス
z_cur_pos--; //Z軸現在位置マイナス
}
}
}
if (x_cur_pulse >= x_all_pulse){ //X軸位置到達?
x_end_flag = true; //X軸位置到達フラグセット
}
if (y_cur_pulse >= y_all_pulse){ //Y軸位置到達?
y_end_flag = true; //Y軸位置到達フラグセット
}
if (z_cur_pulse >= z_all_pulse){ //Z軸位置到達?
z_end_flag = true; //Z軸位置到達フラグセット
}
//現在位置をPCへシリアル通信
String text_x = String(x_cur_pos);
String text_y = String(y_cur_pos);
String text_z = String(z_cur_pos);
String text = String("X:" + text_x +",Y:" + text_y + ",Z:" + text_z);
Serial.println(text); //現在位置をPCへ送信
delayMicroseconds(sleepTime); //次のパルス出力待ち
if ((x_end_flag == true) && (y_end_flag == true) && (z_end_flag == true)){ //移動終了?
break; //終了
}
}
}
4.1パルス移動処理 x_1_pulse_move(), y_1_pulse_move(),z_1_pulse_move()関数
X軸とY軸とZ軸をそれぞれ1パルス移動させる関数を以下に示します。
ステッピングモータを動作させる関数として、Arduino言語のStepperライブラリーのstep()関数を使用しています。
//---------X軸1パルス移動処理-------------
//パラメーラ
//direction: +1: +方向移動、-1: -方向移動
//----------------------------------------
void x_1_pulse_mov(int direction){
//X軸方向に1パルス移動
if (direction == 1){ //+方向移動?
stepper_x.step(+1); //+方向1ステップ移動
}else if (direction == -1){ //-方向移動?
stepper_x.step(-1); //-方向1ステップ移動
}
}
//---------Y軸1パルス移動処理-------------
//パラメーラ
//direction: +1: +方向移動、-1: -方向移動
//----------------------------------------
void y_1_pulse_mov(int direction){
//Y軸方向に1パルス移動
if (direction == 1){ //+方向移動?
stepper_y.step(+1); //+方向1ステップ移動
}else if (direction == -1){ //-方向移動?
stepper_y.step(-1); //-方向1ステップ移動
}
}
//---------Z軸1パルス移動処理-------------
//パラメーラ
//direction: +1: +方向移動、-1: -方向移動
//----------------------------------------
void z_1_pulse_mov(int direction){
//Z軸方向に1パルス移動
if (direction == 1){ //+方向移動?
stepper_z.step(+1); //+方向1ステップ移動
}else if (direction == -1){ //-方向移動?
stepper_z.step(-1); //-方向1ステップ移動
}
}
最後に
2軸の直線補間とは別のアルゴリズムになってしまいましたが、3個のステッピングモータを直線補間制御することが出来ました。次は3軸のCNCの機能として一般的なヘリカル補間のプログラムを考えたいと思います。