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


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

2軸の直線補間とは

CNC装置で直線補間とは、両端点の数値情報を与えて、それによりきまる直線に沿って工具の運動を直線状に制御することです。
通常工作機械の主軸テーブルはモータで駆動されますが、X軸方向の駆動モータとY軸方向の駆動モータを同時に動作をさせて主軸テーブルを直線上に動かします。今回の実験では2個のステッピングモータを同時に動作させて直線補間を実現します。

ここでは、それぞれのステッピングモータをX軸ステッピングモータ、Y軸ステッピングモータと呼びます。
2個のステッピングモータそれぞれを一定の回転速度で同時に回転開始させ、同時に回転停止させれば直線状に主軸テーブルを動作させることが出来ると思われます。ただし、完全に同時スタート、同時停止はマイコンの処理の遅れ等を考えると意外と難しいです。
特にArduino言語Stepperライブラリのstep(steps)関数でstepsを大きな値に設定した場合、指定したステップ数モータが回転完了するまで抜け出ないので、同時に2個のステッピングモータを動かすことは出来ません。

そこで、2個のステッピングモータを逐次細かく回転させて直線状になるように制御します。
2個のステッピングモータの動きが直線状になるように、それぞれのモータ駆動回路に与えるパルスを分配することをパルス分配と言います。

パルス分配に関して

ステッピングモータは、ステッピングモータのコントローラに1パルス信号を与えると1ステップ回転すると仮定します。
ステッピングモータで駆動されるテーブルを直線状に動作させるためには、2個のステッピングモータに適宜にパルスを与える必要があります。
このパルスの与え方をパルス分配と呼びます。
パルス分配方式には、DDA方式、代数演算方式、最小偏差補間方式等が有ります。今回は代数演算方式を使用してみることにしました。

代数演算方式のパルス分配

代数演算方式の直線補間時のパルス分配の概念を図1に示します。
代数演算方式のパルス分配とは直線や曲線の代数方程式がその線上にない座標軸に対して、正または負になる性質を利用します。
図1はX-Y座標の第1象限の場合ですが、始点を(0,0)、終点を(X1,Y1)とする直線を考えた場合、この直線上の点を(X2,Y2)とした時、
  X1 × Y2 – X2 × Y1 = 0
が成立します。
次に直線の上側にある(X3,Y3)をとると
  X1 × Y3 – X3 × Y1 > 0
が成立します。
次に直線の下側にある(X4,Y4)をとると
  X1 × Y4 – X4 × Y1 < 0
が成立します。
よって任意の点を(X,Y)とした時
  X1 × Y – X × Y1 = D
なる判別式Dを調べて、D≧0ならばX軸に1パルス与え、そこでまたDを調べ、D<0となればY軸に1パルス与え、D>0になるまでパルスを与え続ければ直線を1パルスの精度で階段状に補間することが出来ます。
指令直線が第1象限にないときでも、座標軸の変換を行い、第1象限と同じ方法で演算をすることが出来ます。

図1.代数演算方式による直線補間

象限による区別に関して

代数演算方式のパルス分配に関して第1象限の図を使用して説明しましたが、図2に示すように直線補間のスタート点をX-Y座標軸の中心とした場合に終点の象限は、終点1が第1象限、終点2が第2象限、終点3が第3象限、終点4が第4象限ですが、全て第1象限の形に変換して計算して、実際にステッピングモータを回転させる時に象限に応じて回転方向を変えることで全ての象限での処理が可能となります。

第2象限移動の終点2の場合、X = -5, Y = 5の位置に移動しますが、X = 5, Y = 5の位置に移動すると考えて移動位置を計算して、ステッピングモータを回転する時にX軸は負方向にY軸は正方向に回転させます。

第3象限移動の終点3の場合、X = -4, Y = -5の位置に移動しますが、X = 4, Y = 5の位置に移動すると考えて移動位置を計算して、ステッピングモータを回転する時にX軸、Y軸共に負方向に回転させます。

第4象限移動の終点4の場合、X = 6, Y = -2の位置に移動しますが、X = 6, Y = 2の位置に移動すると考えて移動位置を計算して、ステッピングモータを回転する時にX軸は正方向にY軸共は負方向に回転させます。

図2.終点位置の象限

実験装置

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に接続しました。
図3に実験装置の回路図を示します。

図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ステップ
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がひっくり返っていることに注意が必要です。

動作動画

以下動画は、直線補間のプログラムを作成して、順々に(1000,1000)の位置へ移動→(-700,500)の位置へ移動→(-300,-400)の位置へ移動→(800,-600)の位置へ移動→(0,0)の位置へ移動する動作を実行した時の状況です。

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

プログラム

Raspberry Pi Pico Wのプログラム開発ツールとして、Arduino IDEを使用しました。
GP15に接続されたスイッチをONすると、直線補間で順々に(1000,1000)の位置へ移動→(-700,500)の位置へ移動→(-300,-400)の位置へ移動→(800,-600)の位置へ移動→(0,0)の位置へ移動するプログラムを以下に示します。
各軸の移動ではStepperライブラリを使用していますが、X,Y軸それぞれ1パルスづつほぼ交互に送る方式なので、送り速度はsetSpeed()関数では決まりません。1パルス送る毎にdelayMicroseconds()関数による待ちを入れて、このディレイ時間で送り速度が決まります。step()関数からは出来るだけ速く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめにセットします。
移動中はArduino IDEのシリアルプロッタ等を使用して、X軸、Y軸の各位置をグラフ表示出来るように、パソコンへUSBシリアルポートを使用して、現在位置を以下の形式で送信しています。
X:「X軸パルス位置」,Y:「Y軸パルス位置」

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

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_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軸ステッピングモータ(橙線)

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

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

メインの処理を行うloop()関数のソースコードを以下に示します。
loop()関数内で実際のステッピングモータの動作を行います。
直線補間を行う関数がlinearInterporation()関数です。
GP15に接続されたスイッチが押されるとlinearInterporation()関数で直線補間を以下5回行います。
1.(X:1000, Y:1000)の位置まで直線補間で移動。
2.(X:1000, Y:1000)の位置から(X:-700, Y:500)の位置まで直線補間で移動。
3.(X:-700, Y:500)の位置から(X:-300,Y: -400)の位置まで直線補間で移動。
4.(X:-300, Y:-400)の位置から(X:800, Y:-600)の位置まで直線補間で移動。
5.(X800, Y:-600)の 位置から(X:0, Y:0)の位置まで直線補間で移動。

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

  long x_pos = 1000;  //X軸目的位置(パルス)
  long y_pos = 1000;  //Y軸目的位置(パルス)
  long vel = 250;     //送り速度(パルス/sec)
  linearInterporation(x_pos, y_pos, vel); //直線補間移動
 
  x_pos = -700;       //X軸目的位置(パルス)
  y_pos = 500;        //Y軸目的位置(パルス)
  vel = 250;          //送り速度(パルス/sec)
  linearInterporation(x_pos, y_pos, vel); //直線補間移動
 
  x_pos = -300;       //X軸目的位置(パルス)
  y_pos = -400;       //Y軸目的位置(パルス)
  vel = 250;          //送り速度(パルス/sec)
  linearInterporation(x_pos, y_pos, vel); //直線補間移動
 
  x_pos = 800;        //X軸目的位置(パルス)
  y_pos = -600;       //Y軸目的位置(パルス)
  vel = 250;          //送り速度(パルス/sec)
  linearInterporation(x_pos, y_pos, vel); //直線補間移動
 
  x_pos = 0;          //X軸目的位置(パルス)
  y_pos = 0;          //Y軸目的位置(パルス)
  vel = 250;          //送り速度(パルス/sec)
  linearInterporation(x_pos, y_pos, vel); //直線補間移動
}

3. 直線補間実施 linearInterporation()関数

直線補間を行うlinearInterporation()関数のソースコードを以下に示します。
X軸の目標位置とY軸の目標位置と送り速度とを設定することによって直線補間を実施します。
都度判別式を用いてパルスの分配を決めています。判別式が正のときはX軸を1パルス移動、負の時はY軸を1パルス移動しています。
パルス出力後にdelayMicroseconds()関数で休みを取って送り速度を調整します。
逐次現在位置をUSBシリアル通信でパソコンに送信しています。

//---------直線補間実行処理------------
//パラメーラ
//x_pos:  X軸目標位置(パルス)
//y_pos:  Y軸目標位置(パルス)
//vel:    送り速度(パルス/sec)
//------------------------------------        
void linearInterporation(long x_pos, long y_pos, long vel){
  long XincPulse = x_pos - x_cur_pos; //X軸増分パルス
  long YincPulse = y_pos - y_cur_pos; //Y軸増分パルス
  long x_all_pulse = abs(XincPulse);  //X軸移動パルス数絶対値
  long y_all_pulse = abs(YincPulse);  //Y軸移動パルス数絶対値

  long sleepTime = 1000000 / vel; //次のパルス出力までのスリープ時間(μsec);

  long x_cur_pulse = 0;       //x軸現在出力パルス数
  long y_cur_pulse = 0;       //y軸現在出力パルス数

  bool x_end_flag = false;    //X軸目的位置到着フラグ
  bool y_end_flag = false;    //Y軸目的位置到着フラグ

  if (x_all_pulse == 0){      //X軸移動量ゼロ?
     x_end_flag = true;       //X軸移動量ゼロの時は移動完了とする
  }
  if (y_all_pulse == 0){      //Y軸移動量ゼロ?
      y_end_flag = true;      //Y軸移動量ゼロの時は移動完了とする
  }
  
  //直線補間パルス発生処理
  while (true)
  {
    //判別式判断
    long JD = x_all_pulse * y_cur_pulse - y_all_pulse * x_cur_pulse;
    if (JD == 0){
      if (x_end_flag == false){ //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軸現在位置マイナス
        }
      } 
      else if (y_end_flag == false){  //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軸現在位置マイナス
        }
      }
    }
    else if (JD > 0){
      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軸現在位置マイナス
      }
    }
    else if (JD < 0){
      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軸現在位置マイナス
      }
    }

    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軸位置到達フラグセット
    }

    //現在位置を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へ送信

    delayMicroseconds(sleepTime);     //次のパルス出力待ち

    if ((x_end_flag == true) && (y_end_flag == true)){  //移動終了?
      break;    //終了
    }
  }
}

4.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ステップ移動
  }
}

最後に

2個のステッピングモータに逐次パルスを分配することによって直線補間を行うことが出来ました。
次は2軸の円弧補間の実験を行いたいと思います。


PAGE TOP