Raspberry Pi Pico Wによるステッピングモータ2軸の円弧補間の実験(Arduino IDE使用)


Raspberry Pi Pico Wを使用してステッピングモータ2軸の円弧補間を行う実験をしました。円弧の軌跡を得るのにSIN関数とCOS関数を使用して適宜軌跡を計算する方法もあると思いますが、ここではコンピュータの能力が低かった時代のアルゴリズムで整数演算のみで可能な代数演算方式を使用しました。

プログラミングツールとしてはArduino IDEを使用し、各軸の移動ではArduino言語のStepperライブラリを使用しています。X,Y軸それぞれ1パルスづつほぼ交互に送る方式なので、送り速度はステッピングモータの回転数を設定するsetSpeed()関数では決まりません。ステッピングモータを回すstep()関数から出来るだけ早く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめにセットしています。

円弧補間とは

CNC装置で円弧補間とは、終点の位置と半径と移動方向(CW or CCW)の数値情報を与えて、それによりきまる円弧に沿って工具の運動を円弧状に制御することです。
今回の実験では2個のステッピングモータを使用しました。2個のステッピングモータを同時に動作させて円弧補間を実現しました。
ここでは、それぞれのステッピングモータをX軸ステッピングモータ、Y軸ステッピングモータと呼びます。
2個のステッピングモータのそれぞれの駆動回路に与えるパルスを円弧状になるように分配することをパルス分配と言います。

パルス分配に関して

ステッピングモータは、ステッピングモータのコントローラに1パルス信号を与えると1ステップ回転すると仮定します。
ステッピングモータで駆動されるテーブルを円弧状に動作させるためには、2個のステッピングモータに適宜にパルスを与える必要があります。
このパルスの与え方をパルス分配と呼びます。

円弧の軌跡を得るのにSIN関数とCOS関数を使用して適宜軌跡を計算する方法もあると思いますが、ここではコンピュータの能力が低かった時代のアルゴリズムでCPUへの負荷が少ないものを使用してみることにしました。
昔からあるアルゴリズムとしてDDA方式、代数演算方式、最小偏差補間方式等がありますが、今回は整数演算のみで可能な代数演算方式を使用してみました。

代数演算方式の円弧補間パルス分配

代数演算方式の円弧補間時のパルス分配の概念を図1に示します。
代数演算方式のパルス分配とは直線や曲線の代数方程式がその線上にない座標軸に対して、正または負になる性質を利用します。
図1はX-Y座標の第1象限の場合ですが、中心を(0,0)とする円を考えた場合、この円弧上の任意の点をP(XP, YP)、始点A(Xa, Ya)とした場合、
  Xp2 + Yp2 – (Xa2 + Ya2 )= 0
が成立します。
次に円弧の外側にあるB(Xb,Yb)をとると
  Xb2 + Yb2 – (Xa2 + Ya2 ) > 0
が成立します。
次に円弧の内側にあるC(Xc,Yc)をとると
  Xc2 + Yc2 – (Xa2 + Ya2) < 0
が成立します。
よって任意の点を(X,Y)とした時
  X2 + Y2 – (Xa2 + Ya2) =D
なる判別式Dを調べて、D≧0ならばX軸に1パルス与え、そこでまたDを調べ、D<0となればY軸に1パルス与え、D≧0になるまでパルスを与え続ければ円弧を1パルスの精度で階段状に補間することが出来ます。
円弧が第1象限にないときでも、座標軸の変換を行い、第1象限と同じ方法で演算をすることが出来ます。

図1.代数演算方式による円弧補間

象限と移動方向(CW, CCW)による区別に関して

代数演算方式の円弧補間のパルス分配に関して図1で第1象限の場合を使用して説明しましたが、パルスの分配方法は移動方向(CW, CCW)と象限によって図2、図3のように異なります。

CCW方向(反時計回り)の移動の場合は、以下の様に処理します。(図2)
【第1象限】
判別式D≧0の時 X←X -1, 判別式D<0の時 Y←Y+1
【第2象限】
判別式D≧0の時 Y←Y -1, 判別式D<0の時 X←X -1
【第3象限】
判別式D≧0の時 X←X+1, 判別式D<0の時 Y←Y -1
【第4象限】
判別式D≧0の時 Y←Y+1, 判別式D<0の時 X←X+1

図2.円弧補間の象限による処理(CCW方向移動)

CW方向(時計回り)の移動の場合は、以下の様に処理します。(図3)
【第1象限】
判別式D≧0の時 Y←Y -1, 判別式D<0の時 X←X+1
【第2象限】
判別式D≧0の時 X←X+1, 判別式D<0の時 Y←Y+1
【第3象限】
判別式D≧0の時 Y←Y+1, 判別式D<0の時 X←X -1
【第4象限】
判別式D≧0の時 X←X -1, 判別式D<0の時 Y←Y -1

図3.円弧補間の象限による処理(CW方向移動)

実験装置

X軸とY軸のステッピングモータは28BYJ-48とそのドライバーボードのセットを使用しました。このステッピングモータとドライバーボードは複数のネット通販から購入することが出来ます。
コントローラとしてはRaspberry Pi Pico Wを使用して実験装置を組みました。

28BYJ-48ステッピングモータとドライバ
28BYJ-48ステッピングモータ裏面
実験装置

今回使用したステッピングモータの主な仕様は表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
表1.28BYJ-48ステッピングモータの主な仕様

実験装置回路図

28BYJ-48ステッピングモータのドライバーボードはテキサスインスツルメンツのULN2003ANと言うダーリントントランジスタアレイが使用されています。
ドライバーボード自体の資料が無かったので、テスタと基板のパターンを見て回路を調べました。
X軸用のステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP0~GP3に接続しました。
Y軸用のステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP4~GP7に接続しました。
起動用のスイッチをRaspberry Pi Pico WのGP15に接続しました。
図4に実験装置の回路図を示します。

図4.実験装置の回路図

ステッピングモータの駆動に関して

今回使用したステッピングモータ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ステップ
pin1HIGHLOWLOWHIGH
pin2LOWHIGHHIGHLOW
pin3HIGHHIGHLOWLOW
pin4LOWLOWHIGHHIGH
表2.step()関数実行の場合の励磁順序

また、ステッピングモータ28BYJ-48の仕様書では1-2相励磁方式の励磁順序の表が載っていますが、それから推測した2相励磁の場合の励磁順序は表3の通りです。(時計方向回り時)

ドライバボード入力モータリード線1ステップ2ステップ3ステップ4ステップ
+ (5V)リード線色 赤5Vに接続5Vに接続5Vに接続5Vに接続
IN1リード線色 青HIGHLOWLOWHIGH
IN2リード線色 桃HIGHHIGHLOWLOW
IN3リード線色 黄LOWHIGHHIGHLOW
IN4リード線色 橙LOWLOWHIGHHIGH
表3.ステッピングモータの28BYJ-48の2相励磁順序

以上からpin1~pin4とドライバボードIN1~IN4の関係は次の様になります。
pin1: リード線色 青 (ドライバボード入力 IN1)
pin2: リード線色 桃 (ドライバボード入力 IN3)
pin3: リード線色 黄 (ドライバボード入力 IN2)
pin4: リード線色 橙 (ドライバボード入力 IN4)
ドライバボード入力にIN2とIN3がひっくり返っていることに注意が必要です。

動作動画

以下動画は、円弧補間のプログラムを作成して、
(0,0)の位置から中心(0, 400)、半径400のCCW方向の円弧補間で(-400, 400)の位置まで移動。
(-400,400)の位置から中心(-400, 0)、半径400のCCW方向の円弧補間で(0,0)の位置まで移動。
(0,0)の位置から中心(400, 0)、半径400のCW方向の円弧補間で(400, -400)の位置まで移動。
(400, -400)の位置から中心(0, -400)、半径400のCW方向の円弧補間で(0,0)の位置まで移動。
の動作を実行したときの状況です。

パソコンの画面は、Raspberry Pi Pico WからUSBシリアル通信を使用して送られた位置データをX-Yプロットした画面です。2次元的なモータ動作が直観的に分からないので、このパソコンソフトを新たに作成しました。(X-Yのプロットソフトに関してはこちらを参照して下さい)

作成したプログラム

Raspberry Pi Pico Wのプログラム開発ツールとして、Arduino IDEを使用しました。

GP15に接続されたスイッチをONすると順次以下の様に移動します。
1.(0,0)の位置から中心(0, 400)、半径400のCCW方向の円弧補間で(-400, 400)の位置まで移動。
2.(-400,400)の位置から中心(-400, 0)、半径400のCCW方向の円弧補間で(0,0)の位置まで移動。
3.(0,0)の位置から中心(400, 0)、半径400のCW方向の円弧補間で(400, -400)の位置まで移動。
4.(400, -400)の位置から中心(0, -400)、半径400のCW方向の円弧補間で(0,0)の位置まで移動。

移動中はAnduino IDEのシリアルプロッタ等を使用して、X軸、Y軸の各位置をグラフ表示出来るように、パソコンへUSBシリアルポートを使用して、現在位置を以下の形式で送信しています。
X:「X軸パルス位置」,Y:「Y軸パルス位置」

X:45,Y:22
以下に全ソースコードを6個の部分に分割して説明します。

1.初期処理 setup()関数

初期処理を行うsetup()関数のソースコードを以下に示します。
ステッピングモータを起動するのにArduino言語のStepperライブラリーを使用しています。ステッピングモータの回転速度をsetSpeed()関数で500rpmに設定していますが、実際には1ステップづつの送りしかしないので、setSpeed()関数では回転速度は決まりません。1パルス送る毎にdelayMicroseconds()関数による待ちを入れて、このディレイ時間で送り速度を決めています。step()関数からは出来るだけ速く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめにセットします。
現在位置を都度パソコンへUSBシリアル通信を使用して送るので、USBシリアル通信の設定をしています。

//------Raspberry Pi Pico W 同時2軸円弧補間実験ソフト------------------
#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ステップ)

long x_cur_pos = 0;    //X軸現在位置(符号付き機械座標のパルス位置)
long y_cur_pos = 0;    //Y軸現在位置(符号付き機械座標のパルス位置)

void setup() {
  Serial.begin(9600);       //USBシリアル通信初期化

  pinMode(15, INPUT);       //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軸ステッピングモータ(橙線)

  //ステッピングモータの初期設定
  stepper_x.setSpeed(500);  //X軸ステッピングモータの回転速度500rpm
  stepper_y.setSpeed(500);  //Y軸ステッピングモータの回転速度500rpm
}

2.メイン処理 loop()関数

メインの処理を行うloop()関数のソースコードを以下に示します。
loop()関数で実際のステッピングモータの動作を行います。
円弧補間を行う関数がCircularInterporation()関数です。

GP15に接続されたスイッチが押されるとCircularInterporation()関数で円弧補間を以下4回行います。
1.(0,0)の位置から中心(0, 400)、半径400のCCW方向の円弧補間で(-400, 400)の位置まで移動。
2.(-400,400)の位置から中心(-400, 0)、半径400のCCW方向の円弧補間で(0,0)の位置まで移動。
3.(0,0)の位置から中心(400, 0)、半径400のCW方向の円弧補間で(400, -400)の位置まで移動。
4.(400, -400)の位置から中心(0, -400)、半径400のCW方向の円弧補間で(0,0)の位置まで移動。
円弧補間を行うCircularInterporation()関数は、引数として終点位置、始点から中心までの距離、送り速度、移動方向(CW,CCW)を指定します。

void loop() {
  while (true){
    //スイッチ読込(LOW判断)
    if (digitalRead(15) == LOW){
      break;
    }
  }

  long x_pos = -400;        //X軸終点位置(パルス)
  long y_pos = 400;         //Y軸終点位置(パルス)
  long x_cent_leng = 0;     //X軸始点から中心までの距離(パルス)
  long y_cent_leng = 400;   //Y軸始点から中心までの距離(パルス)
  long vel = 250;           //送り速度(パルス/sec)
  bool flag_cw = false;     //CCW送り

  //円弧補間実施
  CircularInterporation(x_pos, y_pos, x_cent_leng, y_cent_leng, vel, flag_cw);

  x_pos = 0;                //X軸終点位置(パルス)
  y_pos = 0;                //Y軸終点位置(パルス)
  x_cent_leng = 0;          //X軸始点から中心までの距離(パルス)
  y_cent_leng = -400;       //Y軸始点から中心までの距離(パルス)
  vel = 250;                //送り速度(パルス/sec)
  flag_cw = false;          //CCW送り

  //円弧補間実施
  CircularInterporation(x_pos, y_pos, x_cent_leng, y_cent_leng, vel, flag_cw);

  x_pos = 400;              //X軸終点位置(パルス)
  y_pos = -400;             //Y軸終点位置(パルス)
  x_cent_leng = 400;        //X軸始点から中心までの距離(パルス)
  y_cent_leng = 0;          //Y軸始点から中心までの距離(パルス)
  vel = 250;                //送り速度(パルス/sec)
  flag_cw = true;           //CW送り

  //円弧補間実施
  CircularInterporation(x_pos, y_pos, x_cent_leng, y_cent_leng, vel, flag_cw);

  x_pos = 0;                //X軸終点位置(パルス)
  y_pos = 0;                //Y軸終点位置(パルス)
  x_cent_leng = -400;       //X軸始点から中心までの距離(パルス)
  y_cent_leng = 0;          //Y軸始点から中心までの距離(パルス)
  vel = 250;                //送り速度(パルス/sec)
  flag_cw = true;           //CW送り

  //円弧補間実施
  CircularInterporation(x_pos, y_pos, x_cent_leng, y_cent_leng, vel, flag_cw);
}

3. 円弧補間実施 CircularInterporation()関数

円弧補間を行うCircularInterporation()関数のソースコードを以下に示します。
終点位置と、始点から中心までの距離と、送り速度と、移動方向(cw,ccw)とを設定することによって円弧補間を実施します。
中心を(0, 0)とする円弧に変換して各処理を行っています。
都度判別式を用いてパルスの分配を決めています。
パルス出力後にdelayMicroseconds()関数で休みを取って送り速度を調整します。
現在居る位置の象限によって処理が変わるので、象限を検出して処理を分けています。
逐次現在位置をUSBシリアル通信でパソコンに送信しています。

//-----------円弧補間実施処理--------------------------
//引数
//x_pos:  円弧補間終点X軸位置(パルス)
//y_pos:  円弧補間終点Y軸位置(パルス)
//x_dis_cent: 始点から中心までのX軸距離(パルス)
//y_dis_cent: 始点から中心までのY軸距離(パルス)
//vel:        送り速度(パルス/sec)
//cw_flag:    true: CW方向移動、 false: CCW方向移動
//----------------------------------------------------
void CircularInterporation(long x_pos, long y_pos, long x_dis_cent, long y_dis_cent, long vel, bool cw_flag){
  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 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);


  //円弧補間パルス発生処理
  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);
  
    delayMicroseconds(sleepTime);     //次のパルス出力待ち

    //現在位置をPCへシリアル通信
    String text_x = String(x_cur_pos);
    String text_y = String(y_cur_pos);
    String text = String("X:" + text_x +"," + "Y:" + text_y);
    Serial.println(text);           //現在位置をPCへ送信

    //円弧補間終了判断処理
    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){ //終了?
      break;
    }
  }
}

4.円弧補間時の象限判断 quadrantJudge()関数

円弧補間時のX軸、Y軸位置から象限を検出するquadrantJudge()関数のソースコードを以下に示します。
X軸位置の正、負とY軸座標の正、負の状態から象限を判断しています。
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.円弧補間終了判断 Positioning_end_judge()関数

円弧補間の終点まで到達したのを判断するPositioning_end_judge()関数のソースコードを以下に示します。
X軸とY軸の座標値が終点位置と一致した時に終点に到達したと判断するだけであると、終点位置の指定が円弧から多少ずれている場合に終了出来なくなってしまうので、以下2点から判断しています。
・現在居る象限が終点の象限と一致するか。
・移動方向(CW,CCW)を加味してX軸とY軸がそれぞれが終点位置を超えたか。

//------------円弧補間終了判断処理---------------------
//引数
//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;
}

6.1パルス移動処理 x_1_pulse_mov(), y_1_pulse_mov()関数

X軸とY軸をそれぞれ1パルス移動させるx_1_pulse_mov()関数とy_1_pulse_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ステップ移動
  }
}

最後に

SIN関数とCOS関数を使用しないCPU負荷が小さい代数演算方式を使用して、2個のステッピングモータを円弧補間制御することが出来ました。さらに円弧補間に同期する1軸を追加してヘリカル補間のプログラムも今後作ってみたいと思っています。


PAGE TOP