パソコンからシリアル通信でARDUINO制御

パソコンから制御

 今回はRS232C(USB変換)を使って、  Arduino UNOにコマンドを送り、パソコンから位置決めスライダーを動かします。次の動画の位置決めスライダーの後方にあるのは、パソコンモニターに表示されている制御画面です。先ず、パソコンから連続的に自動で移動コマンドを送信する連続運転の動画をご確認下さい。
40 → 25 → 50 → 10 → 70 (mm)の順に移動します。


  連続運転は、制御画面(下図)の右下側にある枠線内の①~⑤テキストボックスに0~200(mm)の値を入力して、最下の“連続運転”ボタンをクリックすると運転を開始し、①~⑤の順番に位置決めを行います。

 

 

 

 

 

 

 

他にも「原点復帰」「原点移動(0mmに戻る)」「LED点灯」「位置指定移動」のコマンドがあります。

ARDUINO プログラム

 ARDUINOプログラムを以下に掲載します。メインループ部(70~110行)が、通信でコマンドを受け取って処理を実行する箇所です。
 行番103の処理は、L6470 ドライブセットBUSY信号によりスライダーが移動中かどうかを判定します。移動中の場合は次の処理に進むことが出来ず、行番104のパソコンに対し完了報告(注1)を返信しません。
パソコン側は送信コマンドに対し、完了報告が戻ってきて、処理完了と判断し、次のコマンドを送信する様にプログラムを構成します。
この様にすることで、誤操作により連続して同じコマンドを送信してしまうなどのリスクを抑制します。
注1:完了報告は、コマンドと同じ内容

#include <SPI.h>

// ピン定義。
#define PIN_SPI_MOSI 11
#define PIN_SPI_MISO 12
#define PIN_SPI_SCK 13
#define PIN_SPI_SS 10
#define PIN_BUSY 9

#define PIN_3 3                 // 入力予備
#define PIN_4 4                 // 入力予備
#define PIN_5 5                 // リミットSW(原点側(左端))
#define PIN_6 6                 // リミットSW(右端)
#define PIN_7 7                 // LED出力
#define PIN_8 8                 // LED出力

// 入力値初期化
int sw_sts_3 = LOW;
int sw_sts_4 = LOW;
int sw_sts_5 = LOW;
int sw_sts_6 = LOW;

// 初回セットアップ
void setup()
{
  Serial.begin(9600);           // シリアル通信
  Serial.setTimeout(15000UL);   // タイムアウト設定(15秒)
  
  delay(1000);
  pinMode(PIN_SPI_MOSI, OUTPUT);
  pinMode(PIN_SPI_MISO, INPUT);
  pinMode(PIN_SPI_SCK, OUTPUT);
  pinMode(PIN_SPI_SS, OUTPUT);
  pinMode(PIN_BUSY, INPUT);

  pinMode(PIN_3, INPUT );
  pinMode(PIN_4, INPUT );
  pinMode(PIN_5, INPUT );
  pinMode(PIN_6, INPUT );
  pinMode(PIN_7, OUTPUT );
  pinMode(PIN_8, OUTPUT );
  
  SPI.begin();
  SPI.setDataMode(SPI_MODE3);
  SPI.setBitOrder(MSBFIRST);
  
  digitalWrite(PIN_SPI_SS, HIGH);
 
  L6470_rst_device();                             // L6470リセット
  L6470_set_parameter();                          // L6470パラメータ設定
  delay(500);

  get_origin();                                   // 原点復帰

  // 16000step=20.8cm → 16000 X 5 / 20.8 = 3846
  L6470_data_transfer(0x60,3,(long)3846);         // 絶対値指定移動(5cm) 
  L6470_wait_not_busy(800); 
  L6470_data_transfer(0x70,0,0);                  // 原点位置移動
  L6470_wait_not_busy(100); 
}

// シリアルデータ送信
void snd_str(String snd_dat){
  String snd_msg = "          " + snd_dat;
  Serial.println(snd_msg);
  return;
}

// メイン処理
void loop(){

  if ( Serial.available() > 0 ) {
    // シリアル受信
    // \r(CR), \r\n(CR+LF) ,\n(LF)
    String str = Serial.readStringUntil('\r'); 

    Serial.println();
    
    if (digitalRead(PIN_BUSY)){
      if ( str == "NEST") {
        get_origin();                             // 原点復帰
        
      } else if ( str=="MOVE_HOME" ) {
        L6470_data_transfer(0x60,3,(long)0);      // 絶対値指定移動(0cm) 
        
      } else if ( str.indexOf("MV")==0 ) {
        String pos_abs_str = str.substring(2);      
        long pos_abs_lng = pos_abs_str.toInt();
        L6470_data_transfer(0x60,3,pos_abs_lng);  // 絶対値指定移動(PCからの指定値)

      } else if ( str=="LED1_ON" ) {              // LED1 ON
        digitalWrite( PIN_7 , HIGH );
        digitalWrite( PIN_8 , LOW );
        
      } else if ( str=="LED2_ON" ) {              // LED2 ON
        digitalWrite( PIN_7 , LOW );
        digitalWrite( PIN_8 , HIGH );
    
      } else {
        str = "[" + str + "]";                    // 対象外コメント受信
      }
      
      L6470_wait_not_busy(100) ;
      snd_str(str);

    }else{
      snd_str("BUSY");                            // BUSY 返信
    }
  }
}

// LED点滅
void LED_FLICK(){
  for(int i=0;i<=10;i++){
    digitalWrite( PIN_7 , LOW );
    digitalWrite( PIN_8 , HIGH );
    delay(200);
    digitalWrite( PIN_7 , HIGH );
    digitalWrite( PIN_8 , LOW );
    delay(200);
  }
  digitalWrite( PIN_7 , LOW );
  digitalWrite( PIN_8 , LOW );
}

// 原点復帰
void get_origin(){
  digitalWrite( PIN_7 , LOW );
  digitalWrite( PIN_8 , LOW );

  // 反-原点方向(右)移動
  if(digitalRead( PIN_5 )){
    L6470_data_transfer(0x51,3,8000);             // 速度指定移動(中速)
    delay(50);
    while(digitalRead( PIN_5 )){ delay(500); }
    L6470_data_transfer(0xb0,0,0);                // 回転停止、保持トルク有
    L6470_wait_not_busy(500);
  }
  
  // 原点方向(左)移動
  L6470_data_transfer(0x50,3,8000);               // 速度指定移動(中速)
  while(!digitalRead( PIN_5 )){} 
  L6470_data_transfer(0xb0,0,0);                  // 回転停止、保持トルク有
  L6470_wait_not_busy(500);

  // 反-原点方向(右)移動
  L6470_data_transfer(0x51,3,500);                // 速度指定移動(低速)
  delay(50);
  while(digitalRead( PIN_5 )){}
  L6470_data_transfer(0xb0,0,0);                  // 回転停止、保持トルク有(機械原点)
  L6470_wait_not_busy(500);

  // 反-原点方向(右)移動(移動量指定)          // 600X20.8/16000=0.78(cm)
  L6470_data_transfer(0x41,3,600);                // 移動量・方向指定移動
  L6470_wait_not_busy(100);
  
  L6470_data_transfer(0xd8,0,0);                  // 原点情報初期化
  L6470_wait_not_busy(800);
  
  digitalWrite( PIN_7 , HIGH );
  digitalWrite( PIN_8 , HIGH );
}


// 初期設定
void L6470_set_parameter(){
  L6470_data_transfer(0x05,2,0x0e);               // [R, WS] 加速度default 0x08A (12bit) (14.55*val+14.55[step/s^2])
  L6470_data_transfer(0x06,2,0x0e);               // [R, WS] 減速度default 0x08A (12bit) (14.55*val+14.55[step/s^2]) 
  L6470_data_transfer(0x07,2,0x0e);               // [R, WR] 最大速度default 0x041 (10bit) (15.25*val+15.25[step/s])
  L6470_data_transfer(0x08,2,0x01);               // [R, WS] 最小速度default 0x000 (1+12bit) (0.238*val[step/s])
  L6470_data_transfer(0x15,2,0x3ff);              // [R, WR] μステップからフルステップへの切替点速度default 0x027 (10bit) (15.25*val+7.63[step/s])
  L6470_data_transfer(0x09,1,0x50);               // [R, WR] 停止時励磁電圧default 0x29 (8bit) (Vs[V]*val/256)
  L6470_data_transfer(0x0a,1,0x50);               // [R, WR] 定速回転時励磁電圧default 0x29 (8bit) (Vs[V]*val/256)
  L6470_data_transfer(0x0b,1,0x50);               // [R, WR] 加速時励磁電圧default 0x29 (8bit) (Vs[V]*val/256)
  L6470_data_transfer(0x0c,1,0x50);               // [R, WR] 減速時励磁電圧default 0x29 (8bit) (Vs[V]*val/256)

  // 0x00 : 400 step/rot , 0x01 : 800 step/rot , 0x02 : 1600 step/rot ,  0x03 : 3200 step/rot ,
  // 0x04 : 6400 step/rot , 0x05 : 12800 step/rot , 0x06 : 25600 step/rot ,  0x07 : 51200 step/rot
  //ステップモードdefault 0x07 (1+3+1+3bit)
  L6470_data_transfer(0x16,1,0x03);
}

// デバイスリセット
void L6470_rst_device(){
  L6470_data_send_u(0x00);                        //nop命令
  L6470_data_send_u(0x00);
  L6470_data_send_u(0x00);
  L6470_data_send_u(0x00);
  L6470_data_send_u(0xc0);
}

// 送信データ加工
void L6470_data_transfer(int add,int bytes,long val){
  int data[3];
  L6470_data_send(add);
  for(int i=0;i<=bytes-1;i++){
    data[i] = val & 0xff;  
    val = val >> 8;
  }
  if(bytes==3){
    L6470_data_send(data[2]);
  }
  if(bytes>=2){
    L6470_data_send(data[1]);
  }
  if(bytes>=1){
    L6470_data_send(data[0]);
  }  
}

// データ送信(BUSY待機)
void L6470_data_send(unsigned char add_or_val){
  while(!digitalRead(PIN_BUSY)){}     // BUSY解除待機
  digitalWrite(PIN_SPI_SS, LOW);      // ~SSイネーブル。
  SPI.transfer(add_or_val);           // アドレスもしくはデータ送信。
  digitalWrite(PIN_SPI_SS, HIGH);     // ~SSディスエーブル。
}

// データ送信(直ぐ送信)
void L6470_data_send_u(unsigned char add_or_val){
  digitalWrite(PIN_SPI_SS, LOW);      // ~SSイネーブル。
  SPI.transfer(add_or_val);           // アドレスもしくはデータ送信。
  digitalWrite(PIN_SPI_SS, HIGH);     // ~SSディスエーブル。
}

// BUSY解除後、指定時間待機
void L6470_wait_not_busy(long time){
  while(!digitalRead(PIN_BUSY)){}
  delay(time);
}

 

パソコン側のプログラム(C#)

 パソコン側プログラムは、Microsoft Visual Studio Community 2019をインストールし、C#フォームアプリケーションで作成しました。
ARDUINOプログラムにも記載した通り、パソコンからARDUINOにコマンド送信後、ARDUINOからの完了報告受信により、対応処理が完了したと判断します。処理が未完了の状態で、次のコマンド送信が出来ない様にしているつもりです。(?)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.ServiceProcess;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        string receivedData;
        string chk_rcv_Data;
        int ser_cnt;
        string[] mv_pos = new string[5];

        public Form1()
        {
            InitializeComponent();
        }

        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            // 受信文字列の取得
            string tmpRcvDat="";
            try
            {
                tmpRcvDat = this.serialPort1.ReadExisting();
                receivedData = receivedData + tmpRcvDat;

                int pos_nln = receivedData.IndexOf(this.serialPort1.NewLine);
                int num_chr = receivedData.Length;
                if (pos_nln>=0 && num_chr>3) {
                    receivedData = receivedData.Substring(0, pos_nln - 1);
                    AddRecievedDataDelegate add = new AddRecievedDataDelegate(AddRecievedData);
                    this.richTextBox1.Invoke(add, receivedData);

                    if (receivedData.IndexOf(chk_rcv_Data)>=0 && chk_rcv_Data != "") {
                        chk_rcv_Data = "";
                    }
                    
                    return;
                }                   
            }
            catch (Exception ex)
            {
                string msg_tmp = "データ受信エラーが発生しました。"+Environment.NewLine+Environment.NewLine;
                msg_tmp = msg_tmp + ex.Message;
                MessageBox.Show(msg_tmp, "データ受信エラー" ,MessageBoxButtons.OK,MessageBoxIcon.Error);
            }
        }

        private delegate void AddRecievedDataDelegate(string data);
        private void AddRecievedData(string data_rcv)
        {
            if (data_rcv == "") { return; }

            DateTime dt = DateTime.Now;
            string result = "["+dt.ToString("HHmmss")+"]";
            result = "";

            string tmp = data_rcv.Trim();

            this.richTextBox1.Text += tmp + result + Environment.NewLine;
            this.richTextBox1.SelectionStart = this.richTextBox1.Text.Length;
            this.richTextBox1.Focus();
            this.richTextBox1.ScrollToCaret();

            this.textBox3.Text = tmp;
        }

        // 立ち上がり時
        private void Form1_Load(object sender, EventArgs e)
        {
            chk_rcv_Data = "" ;

            string[] ports = System.IO.Ports.SerialPort.GetPortNames();
            for (int i = 0; i < ports.Length; ++i)
            {
                string port_name = ports[i];
                this.comboBox1.Items.Add(port_name);
                this.comboBox1.Text = port_name;
            }
        }


        private void From1_Closed(object sender, FormClosedEventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                serialPort1.Close();
            }
        }

        // コムポートを開く
        private void com_open ()
        {
            if (serialPort1.IsOpen != true)
            {
                string com_port = comboBox1.Text;
                this.serialPort1.PortName = com_port;
                this.serialPort1.BaudRate = 9600;
                
                this.serialPort1.Open();
            }
        }


        // シリアルデータ送信
        private void SendStrInf(string text , int kbn) {
            if(chk_rcv_Data != ""){ return ; }

            if (kbn == 1) { chk_rcv_Data = text ; }

            this.textBox3.Text = "";
            receivedData = "";
            
            com_open();
            this.serialPort1.Write(text+"\r");
        }


        // ポート切断
        private void Button1_Click(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                serialPort1.Close();
            }
        }

        // LED点滅
        private void Button5_Click(object sender, EventArgs e)
        {
            SendStrInf("LED1_ON" , 1 );
        }

        private void Button6_Click(object sender, EventArgs e)
        {
            SendStrInf("LED2_ON" , 1 );
        }


        // 原点復帰動作
        private void Button2_Click(object sender, EventArgs e)
        {
            SendStrInf("NEST" , 1 );
        }

        // 0mm位置移動
        private void Button3_Click(object sender, EventArgs e)
        {
            SendStrInf("MOVE_HOME" , 1 );
        }

        // 指定座標移動
        private void Button4_Click(object sender, EventArgs e)
        {
            string m_distance = this.textBox1.Text.Trim();
            float m_dist = float.Parse(m_distance);

            //max_step = 15385;
            if (m_dist >= 0 && m_dist <= 200)
            {

                double move_step = Math.Floor(15385 * m_dist / 200);
                string send_step = move_step.ToString();


                SendStrInf("MV" + send_step , 1 );
            }
            else
            {
                MessageBox.Show("移動位置の入力値が異常です。", "設定値異常", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void Button7_Click(object sender, EventArgs e)
        {
            mv_pos[0] = textBox2.Text.Trim() ;
            mv_pos[1] = textBox4.Text.Trim();
            mv_pos[2] = textBox5.Text.Trim();
            mv_pos[3] = textBox6.Text.Trim();
            mv_pos[4] = textBox7.Text.Trim();

            ser_cnt = 0;
            timer1.Enabled = true;
        }

        private void Timer1_Tick(object sender, EventArgs e)
        {
            if(chk_rcv_Data == "")
            {
                if (ser_cnt > 0) { Thread.Sleep(1500); }
                
                string m_dst = mv_pos[ser_cnt];
                float m_ds = float.Parse(m_dst);

                if (m_ds < 0 || m_ds > 200){ m_ds = 0; }

                double move_step = Math.Floor(15385 * m_ds / 200);
                string send_step = move_step.ToString();

                SendStrInf("MV" + send_step, 1);

                ser_cnt = ser_cnt + 1;
                if (ser_cnt >= 5) { timer1.Enabled = false; }

            }
        }
    }
}

 

まとめ

 なにかこの位置決めスライダーを使って、面白そうなものを作れないか考えています。ちょっとした仕掛を追加する程度で出来ると良いのですが、なかなか難しそうです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です