TES Blog

株式会社テクニカルエンジニアリングサポートに所属する社員が、自身が携わるテクノロジーやイベントに関する情報を発信しています。

C# .NET Framework によるRS232C通信

はじめに

普段案件としてWebアプリケーション開発に携わることが多いですが、最近関わった案件にてRS232C通信を用いた開発を行いましたので、備忘の意味合いも込めて記事にしました。

開発環境

  • Windows 10 Pro
  • Visual Studio 2017
  • C# .NET Framework 4.5

開発準備

コントロールの要素として SerialPort を用意して、マシンに接続されているCOMを参照することになりますが、開発時に通信状況を確認するために仮想シリアルポートを用意すると、効率よく開発ができると思います。
今回の作業時には、com0comを利用しました。
こちらを用いることで、マシン内に仮想シリアルポートを用意して、WindowsFormのアプリケーションにてCOMを認識することができます。

データの受信

機器から送信されるコマンドを受信する場合については、下記のコードで実装可能です。

ポートのオープン

通信を行う前に、対象のシリアルポートのオープンを行います。

try
{
    this.serialPort.PortName = "COM4";
    this.serialPort.Open();
}
catch (Exception ex)
{
     Console.WriteLine(ex.ToString());
}

受信処理

RS232Cのポートに対するデータの受信処理は SerialPortDataReceived イベントハンドラを使用します。

private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // シリアルポートをオープンしていない場合、処理を行わない.
    if (!this.serialPort.IsOpen) return;

    try
    {
        // 受信データを読み込む.
        string receivedData = this.serialPort.ReadExisting();
        Console.WriteLine(receivedData);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

フォームの要素(LabelTextBox 等)に受信した文字列を表示する場合ですが、フォームを管理するスレッドとDataReceivedによるデータ受信を管理するスレッドは異なるため、delegate を使用して実装します。

private delegate void ShowDataDelegate();

private void SetTextBox(string data)
{
    this.textBox.Text = data;
}

private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // シリアルポートをオープンしていない場合、処理を行わない.
    if (!this.serialPort.IsOpen) return;

    try
    {
        // 受信データを読み込む.
        string receivedData = this.serialPort.ReadExisting();
        Console.WriteLine(receivedData);
        
        ShowDataDelegate showData = new ShowDataDelegate(SetTextBox);
        this.textBox.Invoke(showData, receivedData);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

データの送信

機器に対するコマンドを送信する場合については、下記のコードで実装可能です。

ポートのオープン

送信時も受信時と同様にポートのオープンを行います。

送信処理

送信を行う場合、SerialPortWriteメソッドを使用します。
ポイントとなるのは、送信対象の機器において改行コードの扱いが異なりますので、適宜調整が必要です。

string command = "sample code";
this.serialPort.Write(command + "\r\n");
// this.serialPort.Write(command + "\r");

単体のコマンド発行であれば上記で問題ありませんが、複数同時にコマンドを発行する場合には、書き込みに失敗するケースを考えてリトライ処理を実装すると、精度向上が見込めます。

private async void WriteSerialPort(string command)
{
    int retry = 0;

    try
    {
        this.serialPort.Write(command + "\r\n");
    }
    catch (IOException ex)
    {
        await Task.Delay(300);
        retry += 1;

        if (retry > 5) throw ex;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

選択可能なポートの取得

接続されているポート一覧を取得するには、SerialPortGetPortNamesメソッドを使用します。
コンボボックスから選択できるように要素に一覧を設定する場合は、以下のコードで実装可能です。

private void InitializeSerialPort()
{
    DataTable ports = new DataTable();
    ports.Columns.Add("key", typeof(string));
    ports.Columns.Add("value", typeof(string));

    foreach (string name in SerialPort.GetPortNames())
    {
        DataRow row = ports.NewRow();
        row["key"] = name;
        row["value"] = name;
        ports.Rows.Add(row);
    }

    ports.AcceptChanges();

    this.cmbSerialPort.ValueMember = "key";
    this.cmbSerialPort.DisplayMember = "value";
    this.cmbSerialPort.DataSource = ports;
}