【C#】com0comを使ったシリアル通信テスト


この記事では

C#のアプリで、シリアル通信テストを行うための方法を書き残す

使用したツールの情報

  • com0com 3.0.0.0
  • TeraTerm 4.105
  • .NET Core 3.1
    • System.IO.Ports 5.0.1

com0com の設定

PC単体でCOMポートの接続テストをするために、com0comをインストールする

インストールからデバイスドライバの更新まで

「com0com」でGoogle検索(.jp)すると、この記事が1番目にヒットする
記事の説明通りに進めれば、問題なくインストールできる

インストール時のWindows10バージョン

Windows10 21H1 にインストールした

com0com をセットアップする

ツールの場所: "C:\Program Files (x86)\com0com\setupg.exe"

今回は{COM20, COM21}{COM22, COM23} の2セットペアを生成した

デバイスマネージャーで確認する

ドライバの更新が正常ではない場合、黄色三角のエラーマークが表示される

解消するためには、Windows Update -> "オプション更新プログラム" から所定のプログラムを更新する
(ドライバを更新する方法について、先述の記事に記載あり)

TeraTermの設定

COM21COM23をコンソールで監視する

シリアルポートの設定はデフォルトのままとした

C#アプリからシリアル通信コマンドを送信する

コードの詳細は記事の最下部の付録に掲載する

ウィンドウにはSendボタンのみが配置される
ボタンを押すと、COM20->COM21数字18桁COM22->COM23英字18桁がそれぞれ送信される

TeraTermでコマンドを受け取る

TeraTermをCOM21, COM23に接続する
アプリのSendボタンを押すたびに、受け取ったコマンドがそれぞれのコンソールに表示される

  • COM21COM20から受け取った数字を表示
  • COM23COM22から受け取った英字を表示

MySerialPortクラスでコマンドを受け取る

送信につかったMySerialPortクラス同様に
受信側もMySerialPortを使う

全体のコードは付録に記載
    public MainWindow()
    {
        InitializeComponent();

        _com20 = new MySerialPort("COM20");
        _com21 = new MySerialPort("COM21"); // <-- TeraTermを使うときはコメントアウト
        _com22 = new MySerialPort("COM22");
        _com23 = new MySerialPort("COM23"); //  <-- TeraTermを使うときはコメントアウト
    }

TeraTermのときと違い、受け取ったByte配列は16進数で表示される

例) "1"は16進数31、 "A"は16進数41

おわりに

参考にさせていただいた記事にもある通り

「com0com」はとても便利ですが、所詮は仮想ですので最終的な確認は本物のシリアルポートがあるPCでやりましょう。

付録

検証に使ったコードを掲載する

シリアルポート操作用モデル

コマンドを送信するSendメソッドだけを公開している

データを受け取った際にイベントが発火し、Receiveメソッドにより受け取ったデータをbyte[]に格納する

MySerialPort.cs
using System;
using System.Diagnostics;
using System.IO.Ports;

public class MySerialPort
{
    private readonly SerialPort _sp;

    public MySerialPort(string portName)
    {
        _sp = new SerialPort()
        {
            PortName = portName,
            BaudRate = 9600,
            DataBits = 8,
            Parity = Parity.None,
            StopBits = StopBits.One,
            Handshake = Handshake.None
        };

        _sp.Open();
        _sp.DataReceived += OnDataReceived;
    }

    private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        Debug.Write($"{_sp.PortName} Data Received");

        byte[] receivedBytes= Receive();
        int receivedBytesLength = Buffer.ByteLength(receivedBytes);

        for(int i = 0; i < receivedBytesLength; i++)
        {
            Debug.Write($"{receivedBytes[i]:X},");
        }
        Debug.Write("\n");
    }

    public void Send(string message = "DefaultMessage")
    {
        _sp.Write(message);
        Debug.WriteLine($"{_sp.PortName} send {message}");
    }

    private byte[] Receive()
    {
        byte[] buf = new byte[128];
        int totalReadBytes = 0;

        while(true)
        {
            int bytesToRead = _sp.BytesToRead;

            if (bytesToRead == 0)
                break;

            int readBytes = _sp.Read(buf, totalReadBytes, bytesToRead);

            totalReadBytes += readBytes;
            Debug.WriteLine($"{_sp.PortName} toRead: {bytesToRead}, readBytes:{readBytes}, total: {totalReadBytes}");
        }

        byte[] receivedBytes = new byte[totalReadBytes];
        Array.Copy(buf, receivedBytes, totalReadBytes);

        return receivedBytes;
    }
}

画面のコードビハインド

MySerialPortクラスのインスタンスを4つ生成する
com0comで設定した4つのポート名をコンストラクタの引数に与えている

ボタンにはイベントハンドラSend(object sender, RoutedEventArgs e)が紐づいており
COM20からは数字18桁、COM22からは英字18桁が送信される

com0comで20と21はペアになっているので、20から送信した内容は21のポートで受信される
(COM22->COM23も同様)

MainWindow.xaml.cs
using System.Threading.Tasks;
using System.Windows;

public partial class MainWindow : Window
{
    private readonly MySerialPort _com20;
    private readonly MySerialPort _com21;
    private readonly MySerialPort _com22;
    private readonly MySerialPort _com23;

    public MainWindow()
    {
        InitializeComponent();

        _com20 = new MySerialPort("COM20");
        //_com21 = new MySerialPort("COM21"); // <-- TeraTermを使うときはコメントアウト
        _com22 = new MySerialPort("COM22");
        //_com23 = new MySerialPort("COM23"); //  <-- TeraTermを使うときはコメントアウト
    }

    private void Send(object sender, RoutedEventArgs e)
    {
        _ = Task.Run(() => _com20.Send("123456789123456789"));
        _ = Task.Run(() => _com22.Send("ABCDEFGHIABCDEFGHI"));
    }
}