Raspberry Pi Pico Wを使用してステッピングモータ3軸のヘリカル補間を行う実験をしました。以前の記事で2軸の円弧補間について書きましたが、CNC装置では円弧補間に同期して他の1軸を指令するヘリカル補間の機能を持つものが多いので、ヘリカル補間も実現したいと思いプログラムを組みました。円弧補間の部分は以前作成したものをそのまま使用して、それに同期する軸の部分のみ円弧補間のプログラムに追加しました。(2軸の円弧補間に関しての記事はこちら)
尚、プログラミングツールとしてはArduino IDEを使用し、ステッピングモータの駆動にArduino言語のStepperライブラリを使用しています。
ヘリカル補間とは
ヘリカル補間とは円弧補間に同期して他の1軸を指令することによって螺旋状に工具を動作させることです。図1にヘリカル補間の軌跡を示します。

ヘリカル補間のアルゴリズム
ここでは、X軸、Y軸が円弧補間を行い、Z軸がそれに同期して動く場合を考えます。
X-Y軸の円弧補間ですが、円弧の軌跡を得るのにSIN関数とCOS関数を使用して適宜軌跡を計算する方法もあると思いますが、ここではコンピュータの能力が低かった時代のアルゴリズムでCPU負荷が少ない代数演算方式を使用しました。代数演算方式を使用した円弧補間に関しては以前の記事を参考にして下さい。(代数演算方式を使用した円弧補間の記事はこちら)
図2はX-Y軸の円弧補間に同期するZ軸の移動量を計算する原理図です。
Z軸の位置はX-Y軸の円弧の移動中の中心角に基づいて計算します。図2で始点から終点まで円弧補間で移動するとします。途中点は円弧補間で移動中の位置です。円弧の全移動時の中心角Θ0として、途中点での中心角をΘとしします。Z軸動作の始点から終点までの移動量を「Z軸全移動量」とした時に、途中点でのZ軸移動量は、
Z軸移動量 = Z軸全移動量 × Θ / Θ0
で表せます。
X-Y軸を代数演算方式を使用して円弧補間を行い、X軸の位置とY軸の位置が計算される毎に arc tan 演算を行って中心角(Θ)を計算します。そらから得られたZ軸の移動量に基づいてZ軸を動作させます。

実験装置
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軸とY軸は円弧補間、それに同期してZ軸が動く動作をするヘリカル補間のプログラムを作成して、
X軸とY軸が(X:0, Y:400)を中心とした半径400の円を描き、それに同期してZ軸が動作して、円が1周する間に400上昇する動作を5回行うプログラムを実行した様子です。
パソコンの画面は、Raspberry Pi Pico WからUSBシリアル通信を使用して送られた位置データをX-Y-Zプロットした画面です。3次元的なモータ動作が直観的に分からないので、このパソコンソフトを新たに作成しました。(X-Y-Zのプロットソフトに関してはこちらを参照して下さい)
作成したプログラム
Raspberry Pi Pico Wのプログラム開発ツールとして、Arduino IDEを使用しました。
ここでのヘリカル補間の動作は、X軸とY軸は円弧補間を行い、それに同期してZ軸が動く動作としました。
GP15に接続されたスイッチをONすると、X軸とY軸が(X:0, Y:400)を中心とした半径400の円を描き、それに同期してZ軸が動作して、円が1周する間に400上昇する動作を5回行うプログラムを以下に示します。(X軸とY軸の円弧補間プログラムの詳細はこちらを参照して下さい)
移動中はArduino IDEのシリアルプロッタ等を使用して、X軸、Y軸、Z軸の各位置をグラフ表示出来るように、パソコンへUSBシリアルポートを使用して、現在位置を以下の形式で送信しています。
X:「X軸パルス位置」,Y:「Y軸パルス位置」,Z「Z軸パルス位置」
例
X:45,Y:22,Z:100
以下に全ソースコードを8個の部分に分割して説明します。
1.初期処理 setup()関数
初期処理を行うsetup()関数のソースコードを以下に示します。
ステッピングモータを起動するのにArduino言語のStepperライブラリーを使用しています。
ステッピングモータの回転速度をsetSpeed()関数でX軸とY軸に関しては500rpmに設定しています。円弧補間を行うX軸とY軸に関しては、実際には1ステップづつの送りしかしないので、step()関数からは出来るだけ速く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめにセットしています。
X軸とY軸の円弧補間に同期するZ軸に関しては、step()関数で一度に複数ステップ送る可能性があるので、複数ステップ送った時にステッピングモータが脱調しないように低めのrpm値を設定しています。
現在位置を都度パソコンへUSBシリアル通信を使用して送るので、USBシリアル通信の設定をしています。
//------Raspberry Pi Pico W ヘリカル補間実験ソフト------------------
#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回転2048ステップ)
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(14); //Z軸ステッピングモータの回転速14rpm
}
2.メイン処理 loop()関数
メインの処理を行うloop()関数のソースコードを以下に示します。
loop()関数内で実際のステッピングモータの動作を行います。
HericalInterporation()関数がヘリカル補間を行う関数です。X軸とY軸が円弧補間を行いZ軸が円弧補間の位置に同期して動作します。
最初にGP15に接続されたスイッチが押されるとX軸とY軸が(X=0, Y=400)の位置を中心として、半径400で半円の円弧補間をします。Z軸はその間に200移動します。その後残りの半円を同じく(X=0, Y=400)の位置を中心として、半径400で円弧補間します。Z軸はその間に200移動します。以上の動作を5回繰り返します。
void loop() {
while (true){
//スイッチ読込(GP15 LOW判断)
if (digitalRead(15) == LOW){
break;
}
}
for (int i = 0; i < 5; i++){
long x_pos = 0; //X軸終点位置(パルス)
long y_pos = 800; //Y軸終点位置(パルス)
long x_cent_leng = 0; //X軸始点から中心までの距離(パルス)
long y_cent_leng = 400; //Y軸始点から中心までの距離(パルス)
long z_pos = 200 + i * 400; //Z軸終点位置(パルス)
long vel = 250; //送り速度(パルス/sec)
bool flag_cw = true; //CW送り
//ヘリカル補間実施
HelicalInterporation(x_pos, y_pos, x_cent_leng, y_cent_leng, z_pos, vel, flag_cw);
x_pos = 0; //X軸終点位置(パルス)
y_pos = 0; //Y軸終点位置(パルス)
x_cent_leng = 0; //X軸始点から中心までの距離(パルス)
y_cent_leng = -400; //Y軸始点から中心までの距離(パルス)
z_pos = 400 + i * 400; //Z軸終点位置(パルス)
vel = 250; //送り速度(パルス/sec)
flag_cw = true; //CW送り
//ヘリカル補間実施
HelicalInterporation(x_pos, y_pos, x_cent_leng, y_cent_leng, z_pos, vel, flag_cw);
}
}
3. ヘリカル補間実施 HericalInterporation()関数
ヘリカル補間を行うHericalInterporation()関数のソースコードを以下に示します。
この関数はX軸とY軸は円弧補間を行い、Z軸はその円弧補間に同期して動作します。
X軸とY軸は代数演算方式のパルス分配を使用して円弧補間を行います。この部分のソースコードは以前に紹介しましたものと同様です。詳細はそちらを参照して下さい。(円弧補間の記事はこちら)
都度、円弧補間を行うX軸とY軸の座標が決まる毎に arc tan 計算を行って円弧の中心角を求めます。
円弧の全移動時の中心角Θ0として、移動途中の中心角をΘとし、Z軸動作の始点から終点までの移動量を「Z軸の全移動量」とした時に、移動途中のZ軸の移動量を以下の様に計算します。
Z軸の移動量 = Z軸の全移動量 × Θ / Θ0
実際は「Z軸の移動量-Z軸の1つ前の移動量」が1パルスを超えたときにZ軸を超えたパルス分移動させます。
また、逐次現在位置をUSBシリアル通信でパソコンに送信しています。
//-----------ヘリカル補間実施処理--------------------------
//引数
//x_pos: 円弧補間終点X軸位置(パルス)
//y_pos: 円弧補間終点Y軸位置(パルス)
//x_dis_cent: 始点から中心までのX軸距離(パルス)
//y_dis_cent: 始点から中心までのY軸距離(パルス)
//z_pos; 同期Z軸終点位置(パルス)
//vel: 送り速度(パルス/sec)
//cw_flag: true: CW方向移動、 false: CCW方向移動
//----------------------------------------------------
void HelicalInterporation(long x_pos, long y_pos, long x_dis_cent, long y_dis_cent, long z_pos, long vel, bool cw_flag){
bool end_flag = false; //補間終了フラグ
long x_cent = x_cur_pos + x_dis_cent; //中心のX軸座標計算
long y_cent = y_cur_pos + y_dis_cent; //中心のY軸座標計算
long x_sta_pos_0_0 = x_cur_pos - x_cent; //中心(0,0)とした時のX軸始点設定
long y_sta_pos_0_0 = y_cur_pos - y_cent; //中心(0,0)とした時のY軸始点設定
long x_pos_0_0 = x_sta_pos_0_0; //中心(0,0)とした時のX軸現在位置初期設定
long y_pos_0_0 = y_sta_pos_0_0; //中心(0,0)とした時のY軸現在位置初期設定
long x_end_pos_0_0 = x_pos - x_cent; //中心(0,0)とした時のX軸の終点位置設定
long y_end_pos_0_0 = y_pos - y_cent; //中心(0,0)とした時のY軸の終点位置設定
long z_all_pulse = abs(z_pos - z_cur_pos);//Z軸全移動パルス量計算
long z_cur_pulse = 0; //Z軸現在パルス量クリア
long z_sta_pos = z_cur_pos; //Z軸開始位置セット
long z_end_pos = z_pos; //Z軸終了位置セット
//円の直径の二乗計算
long radius_squar = x_sta_pos_0_0 * x_sta_pos_0_0 + y_sta_pos_0_0 * y_sta_pos_0_0;
long sleepTime = 1000000 / vel; //次のパルス出力までのスリープ時間
//始点の象限検出
int cur_quadrant = quadrantJudge(x_sta_pos_0_0, y_sta_pos_0_0, cw_flag);
//終点の象限検出
int end_quadrant = quadrantJudge(x_end_pos_0_0, y_end_pos_0_0, cw_flag);
//始点の角度検出
double sta_ang = detect_ang(x_sta_pos_0_0, y_sta_pos_0_0, cw_flag);
//終点の角度検出
double end_ang = detect_ang(x_end_pos_0_0, y_end_pos_0_0, cw_flag);
//移動時の全角度値計算
double all_ang;
if (cw_flag == false){ //反時計回り?
if (end_ang >= sta_ang){ //終点角度>=始点角度?
all_ang = end_ang - sta_ang;
}else{ //終点角度<始点角度
all_ang = end_ang + M_PI * 2 - sta_ang;
}
}else{ //時計回り
if (sta_ang >= end_ang){ //始点角度>=終点角度?
all_ang = sta_ang - end_ang;
}else{ //始点角度<終点角度
all_ang = sta_ang + M_PI * 2 - end_ang;
}
}
//円弧補間パルス発生処理
while(true){
//判別式判断処理
long JD = x_pos_0_0 * x_pos_0_0 + y_pos_0_0 * y_pos_0_0 - radius_squar; //判別式
if (JD >= 0){ //判別式>=0?
if (cw_flag == false){ //CCW方向移動?
switch (cur_quadrant){ //象限判断
case 1: //第1象限
x_1_pulse_mov(-1); //X軸負方向に1パルス移動
x_pos_0_0--; //中心を(0,0)とした時のX軸現在位置マイナス
x_cur_pos--; //現在位置マイナス
break;
case 2: //第2象限
y_1_pulse_mov(-1); //Y軸負方向に1パルス移動
y_pos_0_0--; //中心を(0,0)とした時のy軸現在位置マイナス
y_cur_pos--; //現在位置マイナス
break;
case 3: //第3象限
x_1_pulse_mov(+1); //X軸正方向に1パルス移動
x_pos_0_0++; //中心を(0,0)とした時のX軸現在位置プラス
x_cur_pos++; //現在位置プラス
break;
case 4: //第4象限
y_1_pulse_mov(+1); //Y軸正方向に1パルス移動
y_pos_0_0++; //中心を(0,0)とした時のy軸現在位置プラス
y_cur_pos++; //現在位置プラス
break;
default:
break;
}
}else{ //CW方向移動
switch (cur_quadrant){ //象限判断
case 1: //第1象限
y_1_pulse_mov(-1); //Y軸負方向に1パルス移動
y_pos_0_0--; //中心を(0,0)とした時のy軸現在位置マイナス
y_cur_pos--; //現在位置マイナス
break;
case 2: //第2象限
x_1_pulse_mov(+1); //X軸正方向に1パルス移動
x_pos_0_0++; //中心を(0,0)とした時のX軸現在位置プラス
x_cur_pos++; //現在位置プラス
break;
case 3: //第3象限
y_1_pulse_mov(+1); //Y軸正方向に1パルス移動
y_pos_0_0++; //中心を(0,0)とした時のy軸現在位置プラス
y_cur_pos++; //現在位置プラス
break;
case 4: //第4象限
x_1_pulse_mov(-1); //X軸負方向に1パルス移動
x_pos_0_0--; //中心を(0,0)とした時のX軸現在位置マイナス
x_cur_pos--; //現在位置マイナス
break;
default:
break;
}
}
}else if (JD < 0){ //判別式<0
if (cw_flag == false){ //CCW方向移動?
switch (cur_quadrant){ //象限判断
case 1: //第1象限
y_1_pulse_mov(+1); //Y軸正方向に1パルス移動
y_pos_0_0++; //中心を(0,0)とした時のy軸現在位置プラス
y_cur_pos++; //現在位置プラス
break;
case 2: //第2象限
x_1_pulse_mov(-1); //X軸負方向に1パルス移動
x_pos_0_0--; //中心を(0,0)とした時のX軸現在位置マイナス
x_cur_pos--; //現在位置マイナス
break;
case 3: //第3象限
y_1_pulse_mov(-1); //Y軸負方向に1パルス移動
y_pos_0_0--; //中心を(0,0)とした時のy軸現在位置マイナス
y_cur_pos--; //現在位置マイナス
break;
case 4: //第4象限
x_1_pulse_mov(+1); //X軸正方向に1パルス移動
x_pos_0_0++; //中心を(0,0)とした時のX軸現在位置プラス
x_cur_pos++; //現在位置プラス
break;
default:
break;
}
}else{ //CW方向移動
switch (cur_quadrant){ //象限判断
case 1: //第1象限
x_1_pulse_mov(+1); //X軸負方向に1パルス移動
x_pos_0_0++; //中心を(0,0)とした時のX軸現在位置プラス
x_cur_pos++; //現在位置プラス
break;
case 2: //第2象限
y_1_pulse_mov(+1); //Y軸正方向に1パルス移動
y_pos_0_0++; //中心を(0,0)とした時のy軸現在位置プラス
y_cur_pos++; //現在位置プラス
break;
case 3: //第3象限
x_1_pulse_mov(-1); //X軸正方向に1パルス移動
x_pos_0_0--; //中心を(0,0)とした時のX軸現在位置マイナス
x_cur_pos--; //現在位置マイナス
break;
case 4: //第4象限
y_1_pulse_mov(-1); //Y軸負方向に1パルス移動
y_pos_0_0--; //中心を(0,0)とした時のy軸現在位置マイナス
y_cur_pos--; //現在位置マイナス
break;
default:
break;
}
}
}
//移動後の象限判断
cur_quadrant = quadrantJudge(x_pos_0_0, y_pos_0_0, cw_flag);
//Z軸同期移動処理
double ang;
double inc_ang;
if (z_all_pulse != 0){ //Z軸の移量は0ではない?
//現在の中心角度計算
ang = detect_ang(x_pos_0_0, y_pos_0_0, cw_flag); //現在中心角度計算
if (cw_flag == false){ //反時計方向移動?
if (ang >= sta_ang){ //現在角度>=スタート角度?
inc_ang = ang - sta_ang; //スタート点から現在点までの角度計算
}else{ //現在角度<スタート角度
inc_ang = ang + M_PI * 2 - sta_ang; //現在角度+2π-スタート角度
}
}else{ //時計方向移動
if (sta_ang >= ang){ //スタート角度>=現在角度?
inc_ang = sta_ang - ang;
}else{ //スタート角度<現在角度
inc_ang = sta_ang + M_PI * 2 - ang; //スタート角度+2π-現在角度
}
}
//Z軸位置計算
int z_inc_pulse = (int)(((double)z_all_pulse * inc_ang / all_ang) - (double)z_cur_pulse); //Z軸増分パルス計算
if (z_inc_pulse >= 1)
{
if (z_end_pos >= z_sta_pos) //Z軸は正方向移動?
{
z_pulse_mov(z_inc_pulse); //Z軸移動
z_cur_pulse = z_cur_pulse + z_inc_pulse;
z_cur_pos = z_cur_pos + z_inc_pulse;
}
else
{ //z軸は負方向移動
z_pulse_mov(0 - z_inc_pulse);//Z軸移動
z_cur_pulse = z_cur_pulse + z_inc_pulse;
z_cur_pos = z_cur_pos - z_inc_pulse;
}
}
}
delayMicroseconds(sleepTime); //次のパルス出力待ち
//円弧部補間終了判断処理
bool res = Positioning_end_judge(x_pos_0_0, y_pos_0_0, x_end_pos_0_0, y_end_pos_0_0, cur_quadrant, end_quadrant, cw_flag);
if (res == true){ //終了?
if (z_cur_pos != z_end_pos){ //Z軸が最終位置まで到達してない?
z_pulse_mov(z_end_pos - z_cur_pos); //Z軸残移動量送り
z_cur_pos = z_end_pos;
}
end_flag = true; //補間終了フラグセット
}
//現在位置を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へ送信
if (end_flag == true){ //全補間終了
break;
}
}
}
4.象限判断 quadrantJudge()関数
円弧補間するX軸とY軸の位置から象限を判断するquadrandJudge()関数のソースコードを以下に示します。
X=0またはY=0で象限の境界線上にいる場合は移動方向(CW,CCW)を加味して、これから移動する象限を現象限としています。
//-----------象限判断検出処理---------------------
//引数
//x_pos: 中心を(0,0)とした時のX軸位置
//y_pos: 中心を(0,0)とした時のY軸位置
//flag_cw: 移動方向 true: CW, false: CCW
//リターン値 象限(1~4)
//-----------------------------------------------
int quadrantJudge(long x_pos, long y_pos, bool flag_cw){
int quadrant = 1; //象限
if ((x_pos > 0) && (y_pos > 0)){ //第1象限?
quadrant = 1; //第1象限
}else if ((x_pos < 0) && (y_pos > 0)){ //第2象限?
quadrant = 2; //第2象限
}else if ((x_pos < 0) && (y_pos < 0)){ //第3象限?
quadrant = 3; //第3象限
}else if ((x_pos > 0) && (y_pos < 0)){ //第4象限?
quadrant = 4; //第4象限
}else if (x_pos == 0){ //X=0の境界線での象限判断
if (y_pos > 0){
if (flag_cw == true){ //CW方向移動?
quadrant = 1; //第1象限
}else{ //CCW方向移動
quadrant = 2; //第2象限
}
}else if (y_pos < 0){
if (flag_cw == true){ //CW方向移動?
quadrant = 3; //第3象限
}else{ //CCW方向移動
quadrant = 4; //第4象限
}
}
}else if (y_pos == 0){ //y=0の境界線での象限判断
if (x_pos > 0){
if (flag_cw == true){ //CW方向移動?
quadrant = 4; //第4象限
}else{ //CCW方向移動
quadrant = 1; //第1象限
}
}else if (x_pos < 0){
if (flag_cw == true){ //CW方向移動?
quadrant = 2; //第2象限
}else{ //CCW方向移動
quadrant = 3; //第3象限
}
}
}
return quadrant;
}
5.円弧補間の角度検出 detect_ang()関数
X軸とY軸の円弧補間の角度を検出する処理を行うdetect_ang()関数のソースコードを以下に示します。
円弧中心を(0,0)とした時のX軸とY軸の位置から arc tan を計算して現在の角度を出しています。arc tan の値を第1象限の値で計算して、その後現在居る象限によって処理を分けています。
また、X軸位置が0の場合は arc tan は計算出来ないので別途計算しています。
//-----------円弧補間の角度検出処理------------------
//引数
//x_pos: 中心を(0,0)とした時のX軸位置
//y_pos: 中心を(0,0)とした時のY軸位置
//リターン値
//現在角度(deg)
//--------------------------------------------------
double detect_ang(int x_pos, int y_pos, bool flag_cw){
double ang = 0;
double x_pos_abs = (double)abs(x_pos); //X軸位置絶対値
double y_pos_abs = (double)abs(y_pos); //Y軸位置絶対値
//象限検出
int quad = quadrantJudge(x_pos, y_pos, flag_cw);
if (x_pos != 0){
switch (quad){ //象限分岐
case 1: //第1象限
ang = atan(y_pos_abs / x_pos_abs);
break;
case 2: //第2象限
ang = M_PI - atan(y_pos_abs / x_pos_abs);//π-atan
break;
case 3: //第3象限
ang = M_PI + atan(y_pos_abs / x_pos_abs); //π+atan
break;
case 4: //第4象限
ang = M_PI * 2 - atan(y_pos_abs / x_pos_abs); //2π-atan
break;
}
}else{ //x_pos = 0の時
switch (quad){ //象限分岐
case 1: //第1象限
ang = M_PI / 2; // π/2
break;
case 2: //第2象限
ang = M_PI / 2; // π/2
break;
case 3: //第3象限
ang = M_PI * 3 / 2; // π/2 × 3
break;
case 4: //第4象限
ang = M_PI * 3 / 2; // π/2 × 3
break;
}
}
return ang;
}
6.X軸とY軸の円弧補間終了判断 Positioning_end_judge()関数
X軸とY軸の円弧補間の終了を判断するPositioning_end_judge()関数のソースコードを以下に示します。
現在居る象限と、現在居る座標値と、移動方向(CW,CCW)から終点に到着したかどうかの判断をしています。
//------------円弧補間終了判断処理---------------------
//引数
//x_pos: 中心を(0,0)とした場合のX軸現在位置
//y_pos: 中心を(0,0)とした場合のY軸現在位置
//x_e_pos: 中心を(0,0)とした場合のX軸終点位置
//y_e_pos: 中心を(0,0)とした場合のY軸終点位置
//quadrant: 現在位置の象限
//e_quadrant: 終了位置の象限
//flag_cw: 移動方向フラグ true: CW, false: CCW
//リターン値
//true: 円弧補間終了、 false: 円弧補間終了
//----------------------------------------------------
bool Positioning_end_judge(long x_pos, long y_pos, long x_e_pos, long y_e_pos, int quadrant, int e_quadrant, bool flag_cw){
bool res = false;
if (quadrant == e_quadrant){ //現在の象限=終了象限?
switch (quadrant){ //象限判断
case 1: //第1象限
if (flag_cw == false){ //CCW方向移動?
if ((x_pos <= x_e_pos) && (y_pos >= y_e_pos)){
res = true;
}
}else{ //CW方向移動
if ((x_pos >= x_e_pos) && (y_pos <= y_e_pos)){
res = true;
}
}
break;
case 2: //第2象限
if (flag_cw == false){ //CCW方向移動?
if ((x_pos <= x_e_pos) && (y_pos <= y_e_pos)){
res = true;
}
}else{ //CW方向移動
if ((x_pos >= x_e_pos) && (y_pos >= y_e_pos)){
res = true;
}
}
break;
case 3: //第3象限
if (flag_cw == false){ //CCW方向移動?
if ((x_pos >= x_e_pos) && (y_pos <= y_e_pos)){
res = true;
}
}else{ //CW方向移動
if ((x_pos <= x_e_pos) && (y_pos >= y_e_pos)){
res = true;
}
}
break;
case 4: //第4象限
if (flag_cw == false){ //CCW方向移動?
if ((x_pos >= x_pos) && (y_pos >= y_e_pos)){
res = true;
}
}else{ //CW方向移動
if ((x_pos <= x_pos) && (y_pos <= y_e_pos)){
res = true;
}
}
break;
default:
break;
}
}
return res;
}
7.X軸、Y軸1パルス移動処理 x_1_pulse_mov(), y_1_pulse_mov()関数
X軸とY軸をそれぞれ1パルス移動させるx_1_pulse_mov()関数とy_1_pusle_mov()関数のソースコードを以下に示します。
ステッピングモータを動作させる関数として、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ステップ移動
}
}
8.Z軸パルス移動処理 z_pulse_mov()
Z軸を指定パルス移動させるz_pulse_mov()関数のソースコードを以下に示します。
ステッピングモータを動作させる関数として、Arduino言語のStepperライブラリーのstep()関数を使用しています。
//-----------Z軸パルス移動処理----------------------
//引数
//pulse: 移動パルス
//---------------------------------------------------
void z_pulse_mov(int pulse){
stepper_z.step(pulse); //Z軸ステップ移動
}
最後に
Raspberry Pi Pico Wに3個のステッピングモータを接続してヘリカル補間を行うことが出来ました。
尚、送り速度がdelayMicroseconds()関数による待ち時間とstep()関数の処理時間等で決まり正確でないので、その点に関しては何らかの工夫が必要であると思います。