Raspberry Pi Pico Wを使用してステッピングモータをUSBシリアル通信で制御する実験(通信処理とモータ制御の両立)


Raspberry Pi Pico Wを使用してステッピングモータをUSBシリアル通信で制御する実験を行いました。

以前の記事でCNCの要素技術としてJOG送り動作を行うプログラムを紹介しました。そのプログラムではスイッチを使用してステッピングモータの回転、停止を行っていました。今回そのJOG送り動作のプログラムを改造して、パソコンからのUSBシリアル通信指令によってステッピングモータの回転、停止を行うプログラムを作成して実験しました。(JOG送りに関する記事はこちら)

プログラミングツールとしてはArduino IDEを使用し、ステッピングモータの駆動にArduino言語のStepperライブラリを使用しています。
今回のプログラムではステッピングモータの制御とシリアル通信処理を同時並列的に行わなければならずマルチスレッドの機能が使用できるOSならば得に問題無く出来ますが、loop()関数しかないarduino言語では工夫が必要です。

以前紹介したステッピングモータのJOG送り動作プログラムでは、1ステップずつモータを回転させて、モータの送り速度を決めるのにdelay()関数を使用して待ち時間を取って行っていましたが、delay()関数で待っている時間はシリアル通信処理等の他の処理が出来ません。そこで今回のプログラムではloop()関数内でdelayを使用しないで一定間隔毎の処理を実現します。

今回のプログラムではmicros()関数を使用して現在時間を取得して、前回のステップ送り処理からの経過時間を見て次のステップ送りを行うことによってモータの回転速度を決めています。loop()関数内に待ち時間がないので、USBシリアル通信の処理も同時に行っています。

loop()関数内で1定時間間隔毎に処理を行う方法

今回のプログラムでは、loop()関数内で一定時間間隔毎に処理を行うために、micros()関数を使用します。
micros()関数はプログラム実行を開始した時から現在までの時間をマイクロ秒単位で返す関数です。この関数で現在の時間を得て、前回処理した時の時間との差が予定した一定時間になった時に次の処理を行うことで一定時間間隔毎に処理を行うことが可能です。

但し、注意が必要なのは、micros()のリターン値はunsigned longの32bitでオーバーフローすると零に戻ります。実際には0xffffffff (4294967295)を超えると零に戻ります。これは71.6分に相当します。
よって、micros()関数で得た現在時間から、前回の処理時にmicros()関数で得た時間を引いて経過時間を計算すると、前回処理した時の時間が71.6分近くの場合に、次の時間読込が0付近の値となってしまい経過時間が正しく計算出来ません。

今回作成したプログラムでは、現在時間と前回の時間を比較して、
現在時間 < 前回の時間
の場合は途中でオーバフローが発生したと判断して、次の様に経過時間を計算しています。
経過時間 = 現在時間 – (0xffffffff – 前回の時間 + 1)

経過時間を計算する部分のプログラムコードを以下に示します。(ここでlast_timeは前回の処理実行時にmicros()関数で得た時間です)

 //前回処理からの経過時間計算
  unsigned long pass_time;
  unsigned long now_time = micros();    //現在時刻読出し(usec単位)
  if (now_time >= last_time){
    pass_time = now_time - last_time;   //経過時間計算
  }else{
    //オーバフローを加味して経過時間計算
    pass_time = now_time - (0xffffffffUL - last_time + 1);
  }

実験装置

ステッピングモータは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ステッピングモータの主な仕様

実験装置回路図

図1に実験装置の回路図を示します。1個のステッピングモータをRaspberry Pi Pico Wと接続しています。

28BYJ-48ステッピングモータのドライバーボードはテキサスインスツルメンツのULN2003ANと言うダーリントントランジスタアレイが使用されています。ドライバーボード自体の資料が無かったので、テスタと基板のパターンを見て回路を調べました。

ステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP0~GP3に接続しました。

USB通信を使用してパソコンと接続しています。

図1.実験装置の回路図

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

今回使用したステッピングモータ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.ステッピングモータの28BY48の2相励磁順序

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

動作動画

以下動画は、今回作成したプログラムの動作時の様子です。パソコンからのコマンド通信でJOG送り動作を行っています。
パソコンからUSBシリアル通信で次のコマンド(文字列)を送信するとステッピングモータが動作します。
 ”CW\n”  CW方向に回転
 ”CCW\n”  CCW方向に回転
 ”STOP\n”  回転停止

パソコンの画面は、Arduino IDEのシリアルモニタの画面です。
コマンドの文字列を入力してリターンキーを押すとRaspberry Pi Pico Wにコマンドが送信されステッピングモータが動作します。ステッピングモータの現在位置がRaspberry Pi Pico Wからパソコンに送信されて表示されています。

プログラム

Raspberry Pi Pico Wのプログラム開発ツールとして、Arduino IDEを使用しました。
ステッピングモータを動作させるための関数としてStepperライブラリを使用しました。表4にStepperライブラリの関数を示します。

関数説明
Stepper(steps, pin1, pin2, pin3, pin4)steps: 1回転あたりのステップ数(int)
pin1,pin2: モータに接続されているピンの番号
pin3,pin4: 4ピンのモータの場合のピン番号
戻り値: 作成されたインスタンス
Stepper:setSpeed(rpms)rpms:モータの回転速度。1分間あたり何回転するかを正の数で設定。(long)
step()をコールしたときのスピードをセットする。
Stepper:step(steps)steps:モータが回転する量(ステップ数)。逆回転させる場合は負の値を設定。(int)
回転が終了するまで関数を抜け出ない。
表4.Sttepperライブラリーの関数概略

パソコンからUSBシリアル通信で送られた以下コマンドデータに従ってステッピングモータが動作します。
 ”CW\n”  CW方向に回転
 ”CCW\n”  CCW方向に回転
 ”STOP\n”  回転停止
パソコンから送られたコマンドに従ってStepperライブラリのstep(steps)関数を使用してステッピングモータを回転させます。
step(steps)関数はstepsで指定したステップ数だけステッピングモータを回転させます。stepsに大きな値を設定してしまうとそのステップ数回転するまでstep()関数から抜け出ませんので、通信等その他の処理を行うことが出来ません。
よってstepsには小さな値を設定して直ちにstep()関数から抜け出るようにします。モータの回転速度の調整は、前述した様にmicros()関数を使用して現在時間を取得して、前回のステップ送り処理からの経過時間を見て次のステップ送りを行うことによって行っています。

移動中はAnduino IDEのシリアルモニタ機能等を使用して、ステッピングモータの回転位置を表示できるように、パソコンへUSBシリアルポートを使用して、現在位置を以下の形式で送信しています。
X:「パルス位置」

X:45

以下に全ソースコードを3個の部分に分割して説明します。

1.初期処理 setup()関数

初期処理を行うsetup()関数のソースコードを以下に示します。

ステッピングモータを起動するのにArduino言語のStepperライブラリーを使用しています。ステッピングモータの回転速度をsetSpeed()関数で500rpmに設定していますが、実際には1ステップずつの送りしかしないので、setSpeed()関数では回転速度は決まりません。他の処理を行うためにstep()関数からは出来るだけ速く抜け出るようにsetSpeed(rpms)のrpmsの値は大きめに設定しています。

USBシリアル通信を使用して、パソコンからコマンドを受け取るのと、現在位置を都度パソコンへ送るので、USBシリアル通信の設定をしています。

一定時間間隔毎に処理を行うための間隔時間を
delay_time = 60 * 1000 * 1000 / STEPS / RPM_SPEED;
でモータの回転速度(rpm)と1回転のステップ数から計算しています。時間の単位はμsecです。

//--Raspberry Pi Pico W シリアル通信JOG運転テストソフト-------------
#include <Stepper.h>

#define RPM_SPEED 10 //モータ回転速度(rpm)
#define STEPS 2048  //モータの1回転のステップ数

//軸の現在位置記憶用データ(符号付きステップ位置)
long current_pos = 0;

//経過時間判断用データ
unsigned long last_time = 0;  //前回の時間記憶
unsigned long delay_time = 0; //処理待ち時間(次の処理をするまでの時間)

//シリアルデータ受信処理用データ
char rdBuf[100];   //受信データバッファ
int rdNo = 0;      //受信データ数
String s_rcvData;  //受信文字列

//回転指令フラグ
bool cw_mov_flag = false;  //CW回転指令フラグ
bool ccw_mov_flag = false; //CCW回転指令フラグ

//ステッピングモータ初期設定
Stepper stepper(STEPS, 0, 2, 1, 3);//ステッピングモータ設定(1回転2048ステップ)

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

 pinMode(0, OUTPUT); //ステッピングモータ(青線)
 pinMode(1, OUTPUT); //ステッピングモータ(桃線)
 pinMode(2, OUTPUT); //ステッピングモータ(黄線)
 pinMode(3, OUTPUT); //ステッピングモータ(橙線)

 //ステッピングモータ回転速度設定
 stepper.setSpeed(500); //ステッピングモータの回転速度500rpm

 //処理待ち時間の設定
 delay_time = 60 * 1000 * 1000 / STEPS / RPM_SPEED;
}

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

メインの処理を行うloop()関数のソースコードを以下に示します。

readSerial()関数でパソコンからのシリアル通信データを受信しています。
パソコンからシリアル通信データを受信するとcom_interpreter()関数で受信データの内容を確認しています。com_interpreter()関数内では受信データを調べて、CW方向回転フラグ(cw_mov_flag)とCCW方向回転フラグ(ccw_mov_flag)を設定しています。

前述した前回の処理からの経過時間を計算して、経過時間が予定した時間を過ぎたときに、cw_mov_flagとccw_mov_flagの状況に応じてステッピングモータの1ステップ回転処理を実行します。

void loop() {
  //シリアルデータ受信処理
  bool res = readSerial();

  if (res == true){        //受信完了
    com_interpreter();     //受信コマンド解釈処理
  }

  //前回処理からの経過時間計算
  unsigned long pass_time;
  unsigned long now_time = micros();    //現在時刻読出し(usec単位)
  if (now_time >= last_time){
    pass_time = now_time - last_time;   //経過時間計算
  }else{
    //オーバフローを加味して経過時間計算
    pass_time = now_time - (0xffffffffUL - last_time + 1);
  }
  
  //モータ回転処理
  if (pass_time >= delay_time){     //時間到達?
    last_time = now_time;
    if (cw_mov_flag == true){       //CW回転?
      stepper.step(-1);             //-1ステップ回転
      current_pos--;                //現在位置マイナス

      //現在位置をPCへシリアル通信
      serial_pos();
    }
    else if (ccw_mov_flag == true){ //CCW回転?
      stepper.step(1);              //1ステップ回転
      current_pos++;                //現在位置プラス

      //現在位置をPCへシリアル通信
      serial_pos();
    }
  }
}

3.シリアル通信処理と通信コマンド解釈処理関係関数

シリアル通信の送信処理を行うserial_pos()関数と、受信処理を行うreadSerial()関数と、シリアル通信で受信したコマンドを解釈するcom_interpreter()関数のソースコードを以下に示します。

serial_pos()関数は、ステッピングモータの現在位置(変数名 current_pos)の値をパソコンに送信しています。

readSerial()関数は、パソコンから送信されたコマンドデータを受信します。コマンドデータは最終文字が”\n”となっています。”\n”を受信した時に一連のコマンドデータを受信したと判断して受信データをString型に変換しています。(変数名 s_rcvData)

com_interpreter()関数は、パソコンから受信したコマンドの文字列を解釈します。
“CW”を受け取った場合は、ステッピングモータをCW方向に回転させるので、cw_mov_flagをtrueにします。
“CCW”を受け取った場合は、ステッピングモータをCCW方向に回転させるので、ccw_mov_flagをtrueにします。
“STOP”を受けっとった場合は、ステッピングモータを停止させるので、cw_mov_flagとccw_mov_flagを共にfalseにします。

//現在位置をパソコンにシリアル通信
void serial_pos(){
  String text_x = String(current_pos);
  String text = String("X:" + text_x);
  Serial.println(text);     //現在位置をPCへ送信
}

//パソコンからの受信処理
//戻り値: ture 受信完了、false 受信未完了
bool readSerial()
{
  bool rdEndFlag = false; //受信完了フラグ

  int rNo = Serial.available();
  if (rNo > 0)          //受信データ有り
  {
    //受信データ読込
    for (int i =0; i < rNo; i++){
      rdBuf[rdNo] = Serial.read();  //1バイト読込
      rdNo++;
    }

    if (rdBuf[rdNo - 1] == '\n'){   //最終文字"\n"?
      rdEndFlag = true;   //受信完了フラグセット
      rdNo = 0;           //受信データ数クリア
      s_rcvData = rdBuf;  //受信データをストリングへ
    }
  }

  return rdEndFlag;
} 

//パソコンからの受信データコマンド解釈
void com_interpreter()
{
  if (s_rcvData.startsWith("CW") == true){        //"CW"?
    ccw_mov_flag = false;                         //ccw回転フラグリセット
    cw_mov_flag = true;                           //CW回転フラグセット
  }else if (s_rcvData.startsWith("CCW") == true){ //"CCW"?
    cw_mov_flag = false;                          //cw回転フラグリセット
    ccw_mov_flag = true;                          //CCW回転フラグセット
  }else if (s_rcvData.startsWith("STOP") == true){//"STOP"?
    cw_mov_flag = false;                          //cw回転フラグリセット
    ccw_mov_flag = false;                         //ccw回転フラグリセット
  }
}

最後に

Raspberry Pi Pico Wを使用してパソコンからのシリアル通信データに従ってステッピングモータを駆動するJOG送りプログラムを作りました。micros()関数で得た時間を基に一定間隔毎に処理を行うことによって、ステッピングモータの制御とシリアル通信管理をloop()関数内で共存させることでマルチスレッドを使用しないで並列処理的に動作を実現することが出来ました。
さらに処理間隔を複数設けることによって、loop()関数内で様々な時間に同期した多くの処理を並列的に行うプログラムも組めると思います。


PAGE TOP