Raspberry Pi Pico Wを使用してステッピングモータをUSBシリアル通信で制御するパソコンの操作パネルソフトの作成


Raspberry Pi Pico WとUSB接続してステッピングモータの動作を操作するパソコンの操作パネルソフトウェアをC#で作成したので紹介します。

前回紹介しましたRaspberry Pi Pico Wを使用してステッピングモータをUSBシリアル通信で制御する記事では、arduino IDEのシリアルモニタ機能を使用してRaspberry Pi Pico Wに動作コマンドを送ったり、現在位置データを表示したりしましたが不便なので、パソコンで動作する操作パネル的なソフトウェアを作成しました。Raspberry Pi Pico W側のプログラムは以前の記事と同じです。(ステッピングモータをUSBシリアル通信で制御する記事はこちら

ソフトウェアの開発ツールはVisual Studio 2022、使用言語はC#を使用しています。

動作動画

以下動画は、今回作成したソフトウェアの動作時の様子です。「接続ボタン」をクリックした後に「CWボタン」と「CCWボタン」をクリックしている間、対応する方向にモータが回転します。

ソフトウェア作成手順

以下に本ソフトウェアを作成する手順を示します。開発ツールはVisual Studio 2022、使用言語はC#を使用します。

1.Visual Stadio 2022を起動し、「新しいプロジェクトの作成(N)」をクリックします。

2.下記画面が表示されたら、「Windowsフォームアプリケーション(.NET Framework)」を選択して「次へ(N)」をクリックします。

3.下記画面が表示されたら「プロジェクト名(J)」と「場所(L)」を入力して「作成(C)」をクリックします。ここでは、プロジェクト名はデフォルトのWindowsFormsApp1を使用しています。

4.空のフォーム画面が表示されます。

5.ツールボックスのButtonをドラッグしてフォームに貼り付けます。

6.貼り付けたボタンのプロパティの(Name)をCwButtonに変更します。

7.貼り付けたボタンのプロパティのTextをCWに変更します。ボタンに表示される文字が「CW」になります。このボタンを以後「CWボタン」と呼びます。

8.このボタンのプロパティの雷マークをクリックするとイベント項目が表示されます。
イベント項目のMouseDownをダブルクリックするとMouseDownの横にCwButton_MouseDownと表示されて、Form1.csにCwButton_MouseDownメソッドが作成されます。これが「CWボタン」の上にあるマウスのマウスボタンが押された時に呼び出されるメソッドです。
また、イベント項目のMouseUpをダブルクリックするとMouseUpの横にCwButton_MouseUpと表示されて、Form1.csにCwButton_MouseUpメソッドが作成されます。これが「CWボタン」の上にあるマウスのマウスボタンが離されたされた時に呼び出されるメソッドです。

9.同様に「CWボタン」の横にボタンを貼り付けます。プロパティとイベントは表1の様に設定します。このボタンには「CCW」が表示されます。このボタンを以後「CCWボタン」と呼びます。
イベントに関しては、MouseDownをダブルクリックすると自動的にCcwButton_MouseDownメソッドが作成されます。また、MouseUpをダブルクリックするとCcwButton_MouseUpメソッドが作成されます。

種類名称設定値
プロパティ(Name)CcwButton
プロパティTextCCW
イベントMouseDownCcwButton_MouseDown
イベントMouseUpCcwButton_MouseUp
表1、CCWボタン設定内容

10.「CWボタン」の下にボタンを貼り付けます。プロパティとイベントは表2の様に設定します。このボタンは「接続」と表示されます。このボタンを以後「接続ボタン」と呼びます。
イベントはClickを使用します。Clickをダブルクリックすると「接続ボタン」をクリックした時、呼び出されるのメソッド ConnectButton_Clickが作成されます。

【接続ボタン】

種類名称設定値
プロパティ(Name)ConnectButton
プロパティText接続
イベントClickConnectButton_Click
表2.接続ボタン設定内容

11.「接続ボタン」の横にボタンを貼り付けます。プロパティとイベントは表3の様に設定します。このボタンには「切断」と表示されます。このボタンを以後「切断ボタン」と呼びます。
イベントはClickを使用します。Clickをダブルクリックすると「切断ボタン」をクリックした時、呼び出されるメソッド DisconnectButton_Clickが作成されます。

【切断ボタン】

種類名称設定値
プロパティ(Name)DisconnectButton
プロパティText切断
イベントClickDisconnectButton_Click
表3.切断ボタン設定内容

12.ツールボックスのLabelをドラッグして「CWボタン」の上側付近に貼り付けます。これはRaspberry Pi Pico Wから送信されてくるステッピングモータの現在位置を表示するラベルです。


13.このLabelのプロパティを表示して(Name)をPositionLabelに変更します。

14.ツールボックスのSerialPortをドラッグしてフォームに貼り付けます。

15.フォーム画面の下側にserialPort1と表示されます。

16.SerialPort1のプロパティ画面のイベントのDataReceivedをダブルクリックすると、データを受信した時に呼び出されるメソッドserialPort1_DataReceivedが作成されます。

17.それぞれのボタンとラベルの寸法と位置を調整します。PositionLabelのプロパティのTextは「X 0000」に変更しました。

18.以上でホーム画面の作成は完了です。以上の操作でForm1.csファイルに空のそれぞれのイベントに対応したメソッドのコーディングが出来上がっています。現時点でのForm1.csのコードを以下に示します。メソッド内部を書いていくことによってソフトウェアは完成します。

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void CwButton_MouseDown(object sender, MouseEventArgs e)
        {

        }

        private void CwButton_MouseUp(object sender, MouseEventArgs e)
        {

        }

        private void CcwButton_MouseDown(object sender, MouseEventArgs e)
        {

        }

        private void CcwButton_MouseUp(object sender, MouseEventArgs e)
        {

        }

        private void ConnectButton_Click(object sender, EventArgs e)
        {

        }

        private void DisconnectButton_Click(object sender, EventArgs e)
        {

        }

        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {

        }
    }
}

作成したプログラム説明

1.接続処理 ConnectButton_Click()メソッド

「接続ボタン」をクリックした時に呼び出されるメソッドがConnectButton_Click()メソッドです。本メソッドでシリアルポートの設定とオープンを行っています。以下にプログラムコードを示します。

既にシリアルポートがオープンしている状態で「接続ボタン」が押されて、さらにオープンしょうとすることを避けるためにif (serialPort1.IsOpen == false)でシリアルポートが既にオープンされていないか確認して、シリアルポートが既にオープンされている場合は何もしないようにしています。

本プログラムでは、シリアルポート名を”COM3″としています。これはパソコンの状態によって変わりますので、Paspberry Pi Pico Wと接続されるシリアルポート名を設定します。(USBシリアルポートのドライバはarduino IDEがRaspberry Pi Pico Wと接続する時に使用するのと同じものを使用します。)

//接続ボタンがクリックされた時のイベントメソッド
private void ConnectButton_Click(object sender, EventArgs e)
{
    if (serialPort1.IsOpen == false)    //シリアルポートOpenされてない?
    {
        serialPort1.PortName = "COM3";  //シリアルポート名設定
        serialPort1.BaudRate = 9600;    //ボーレート設定

        try
        {
            serialPort1.Open();         //シリアルポートオープン
        }
        catch (Exception ex)
        {
            MessageBox.Show("シリアルポートがオープン出来ません", "通知");
            return;
        }

        serialPort1.DiscardInBuffer();  //受信バッファクリア
        serialPort1.DtrEnable = true;   // DTR (端末準備可)をtrueに
        serialPort1.RtsEnable = true;   // RTS (送信要求)をtrueに
    }
}

2.切断処理 DisconnectButton_Click()メソッド

「切断ボタン」をクリックした時に呼び出されるメソッドがDisconnectButton_Click()メソッドです。本メソッドでシリアルポートのクローズを行っています。以下にプログラムコードを示します。

//切断ボタンがクリックされた時のイベントメソッド
private void DisconnectButton_Click(object sender, EventArgs e)
{
    try
    {
        serialPort1.Close();            //シリアルポートクローズ
    }
    catch (Exception ex)
    {
        return;
    }
}

3.CWボタンを押した時の処理 CwButton_MouseDown()メソッド 

「CWボタン」にマウスカーソルを持って行きボタンを押した時呼び出されるメソッドがCwButton_MouseDown()メソッドです。以下にプログラムコードを示します。

「CWボタン」を押すと、”CW\n”をシリアルポートからRaspberry Pi Pico Wに送信しています。
serialPort1.Write()で文字列が送信されます。C#の場合、文字コードはUTF-16ですが、デフォルトでASCIIにエンコードされて送信されます。

Raspberry Pi Pico Wには”CW\n”を受け取るとステッピングモータをCW方向に回転させるプログラムを入れてあります。(Raspberry Pi Pico WでステッピングモータをUSBシリアル通信で制御する記事はこちら

//CWボタン上のマウスボタンが押された時のイベントメソッド
private void CwButton_MouseDown(object sender, MouseEventArgs e)
{
    try
    {
        serialPort1.Write("CW\n");      //"CW"コマンド送信
    }
    catch (Exception ex)
    {
        MessageBox.Show("データ送信出来ません", "通知");
    }
}

4.CWボタンを離した時の処理 CwButton_MouseUp()メソッド 

「CWボタン」を離した時呼び出されるメソッドがCwButton_MouseUp()メソッドです。以下にプログラムコードを示します。

「CWボタン」を離すと、”STOP\n”がシリアルポートからRaspberry Pi Pico Wに送信されます。
serialPort1.Write()で文字列が送信されます。C#の場合、文字コードはUTF-16ですが、デフォルトでASCIIにエンコードされて送信されます。

Raspberry Pi Pico Wには”STOP\n”を受け取るとステッピングモータの回転を停止させるプログラムを入れてあります。(Raspberry Pi Pico WでステッピングモータをUSBシリアル通信で制御する記事はこちら

//CWボタン上のマウスボタンが離された時のイベントメソッド
private void CwButton_MouseUp(object sender, MouseEventArgs e)
{
    try
    {
        serialPort1.Write("STOP\n");    //"STOP"コマンド送信
    }
    catch (Exception ex)
    {
        MessageBox.Show("データ送信出来ません", "通知");
    }
}

5.CCWボタンを押した時の処理と離した時の処理

「CCWボタン」を押した時の処理と離した時の処理は、「CWボタン」を押した時の処理と離した時の処理とほぼ同じです。(3項、4項 参照)
「CCWボタン」を押した時は”CCW\n”をシリアルポートから送信しています。「CCWボタン」を離した時は”STOP\n”をシリアルポートから送信しています。

Raspberry Pi Pico Wには”CCW\n”を受け取るとステッピングモータをCCW方向に回転させ、”STOP\n”を受け取るとステッピングモータの回転を停止させるプログラムを入れてあります。(Raspberry Pi Pico WでステッピングモータをUSBシリアル通信で制御する記事はこちら

6.受信イベント処理 serialPort1_DataReceived()メソッド

シリアルポートがデータを受信した時に呼び出されるのがserialPort1_DataReceived()メソッドです。以下にプログラムコードを示します。

Raspberry Pi Pico Wからは、モータの現在位置が
“X:「現在位置」\r\n”
の文字列で送信されてきます。

Raspberry Pi Pico WからのデータはASCIIコードで送られますが、serialPort1.ReadExisting()ではUTF-16に変換されてString型のデータで呼び出されます。

受信した文字列から”X:”と”\n\r”の位置を検出することによって、現在位置部分を取り出しています。
現在位置はフォーム画面のPositionLabelに表示しています。

private string rcvData = null;          //受信データ

//シリアルポートがデータ受信した時のイベントメソッド
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    //シリアルポートオープンされてない場合は何もしない
    if (serialPort1.IsOpen == false)
    {
        return;
    }

    string rdata = serialPort1.ReadExisting();  //受信データ読込

    if (rcvData == null)
    {
        rcvData = rdata;
    }
    else
    {
        rcvData = rcvData + rdata;
    }

   
    //---------現在位置データを取り出す処理---------
    int pos_x = rcvData.IndexOf("X:");      //"X:"位置検索
    int pos_n = rcvData.IndexOf("\r\n");    //"\r\n"位置検索

    //受信データ内に"X:"と"\r\n"存在しない場合は終了
    if ((pos_x == -1) || (pos_n == -1))
    {
        return;
    }

    string pos_data = rcvData.Substring(pos_x, rcvData.Length - pos_x);  //"X:"より前のデータを削除
    pos_n = pos_data.IndexOf("\r\n");
    if (pos_n != -1)    //"\r\n"存在
    {
        pos_data = pos_data.Substring(2, pos_n - 2);

        rcvData = null;         //受信データクリア


        //現在位置をフォーム画面に表示
        this.Invoke((Action)(() =>
        {
            PositionLabel.Text = "X " + pos_data;
        }));
    }
}

Form1.csの全コード

以下にForm1.csの全コードを示します。

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private string rcvData = null;          //受信データ

        public Form1()
        {
            InitializeComponent();
        }

        //CWボタン上のマウスボタンが押された時のイベントメソッド
        private void CwButton_MouseDown(object sender, MouseEventArgs e)
        {
            try
            {
                serialPort1.Write("CW\n");      //"CW"コマンド送信
            }
            catch (Exception ex)
            {
                MessageBox.Show("データ送信出来ません", "通知");
            }
        }

        //CWボタン上のマウスボタンが離された時のイベントメソッド
        private void CwButton_MouseUp(object sender, MouseEventArgs e)
        {
            try
            {
                serialPort1.Write("STOP\n");    //"STOP"コマンド送信
            }
            catch (Exception ex)
            {
                MessageBox.Show("データ送信出来ません", "通知");
            }
        }

        //CCWボタン上のマウスボタンが押された時のイベントメソッド
        private void CcwButton_MouseDown(object sender, MouseEventArgs e)
        {
            try
            {
                serialPort1.Write("CCW\n");      //"CCW"コマンド送信
            }
            catch (Exception ex)
            {
                MessageBox.Show("データ送信出来ません", "通知");
            }
        }

        //CCWボタン上のマウスボタンが離された時のイベントメソッド
        private void CcwButton_MouseUp(object sender, MouseEventArgs e)
        {
            try
            {
                serialPort1.Write("STOP\n");      //"STOP"コマンド送信
            }
            catch (Exception ex)
            {
                MessageBox.Show("データ送信出来ません", "通知");
            }
        }

        //接続ボタンがクリックされた時のイベントメソッド
        private void ConnectButton_Click(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen == false)    //シリアルポートOpenされてない?
            {
                serialPort1.PortName = "COM3";  //シリアルポート名設定
                serialPort1.BaudRate = 9600;    //ボーレート設定

                try
                {
                    serialPort1.Open();         //シリアルポートオープン
                }
                catch (Exception ex)
                {
                    MessageBox.Show("シリアルポートがオープン出来ません", "通知");
                    return;
                }

                serialPort1.DiscardInBuffer();  //受信バッファクリア
                serialPort1.DtrEnable = true;   // DTR (端末準備可)をtrueに
                serialPort1.RtsEnable = true;   // RTS (送信要求)をtrueに
            }
        }

        //切断ボタンがクリックされた時のイベントメソッド
        private void DisconnectButton_Click(object sender, EventArgs e)
        {
            try
            {
                serialPort1.Close();            //シリアルポートクローズ
            }
            catch (Exception ex)
            {
                return;
            }
        }

        //シリアルポートがデータ受信した時のイベントメソッド
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            //シリアルポートオープンされてない場合は何もしない
            if (serialPort1.IsOpen == false)
            {
                return;
            }

            string rdata = serialPort1.ReadExisting();  //受信データ読込

            if (rcvData == null)
            {
                rcvData = rdata;
            }
            else
            {
                rcvData = rcvData + rdata;
            }

           
            //---------現在位置データを取り出す処理---------
            int pos_x = rcvData.IndexOf("X:");      //"X:"位置検索
            int pos_n = rcvData.IndexOf("\r\n");    //"\r\n"位置検索

            //受信データ内に"X:"と"\r\n"存在しない場合は終了
            if ((pos_x == -1) || (pos_n == -1))
            {
                return;
            }

            string pos_data = rcvData.Substring(pos_x, rcvData.Length - pos_x);  //"X:"より前のデータを削除
            pos_n = pos_data.IndexOf("\r\n");
            if (pos_n != -1)    //"\r\n"存在
            {
                pos_data = pos_data.Substring(2, pos_n - 2);

                rcvData = null;         //受信データクリア


                //現在位置をフォーム画面に表示
                this.Invoke((Action)(() =>
                {
                    PositionLabel.Text = "X " + pos_data;
                }));
            }
        }
    }
}

最後に

Raspberry Pi Pico WとUSBシリアル通信で接続して、ステッピングモータの動作を操作するパソコンの操作パネルソフトウェアをC#を使用して作成しました。簡単にするために厳密なエラー処理は省いてありますのでご容赦お願い致します。

例えば、本プログラムでは、「CWボタン」を押した瞬間にCW回転コマンドをRaspberry Pi Pico Wに送信してモータを回転させて、「CWボタン」を離した瞬間にSTOPコマンドを送信してモータを停止させています。
テスト装置としてはこれで良いのですが、実際の現場で使用する装置に適用するには危険と思われます。「CW」ボタンを押した後で何等かの原因で通信が途絶えてしまったり、パソコンが停止したりするとモータが回転したままとなってしまうからです。

対策として、パソコン側は「CW」ボタンを押している間、短い時間間隔で定期的にコマンドをRaspberry Pi Pico Wに送信し、Raspberry Pi Pico W側は、そのコマンド受信が途絶えた時にモータを停止させる等の処理が必要になると思います。

また、受信データが正常な形をしていなかった場合の処理等も必要と思われます。


PAGE TOP