C#フォームプログラムでのシリアル通信の仕方

# はじめに
　arduinoとのUSBシリアルを使った通信で、C#でプログラムを書く機会があったので、概要を記載します。
# 接続
　フォームにSerialPortを配置します。通信条件は目的に応じでプロパティを設定するか、接続をする際に初期化します。
　ボタンクリックで接続するには、次のようにします。

```connect.cs
using System.IO.Ports;

private void button1_Click(object sender, EventArgs e)
{
    serialPort1.BaudRate = 115200;
    serialPort1.Parity = Parity.None;
    serialPort1.DataBits = 8;
    serialPort1.StopBits = StopBits.One;
    serialPort1.Handshake = Handshake.None;
    serialPort1.PortName = "COM4";
    serialPort1.Open();
}
```

# USBシリアルでの接続ポートは？
　USBシリアルポートを使うときには、接続のたびにCOM番号が変わったりします。
　その時には、コンボボックスにCOMポートを列挙しておいて、接続時にコンボボックスを見に行くようにすると便利ですね。サンプルでは、portComboBoxにポート番号を設定します。設定するタイミングは、フォームが読み込まれるタイミング（Form1_Load)がいいでしょう。

```potrs.cs
private void Form1_Load(object sender, EventArgs e)
{
    string[] ports = SerialPort.GetPortNames();
    foreach (string port in ports)
    {
        portComboBox.Items.Add(port);
    }
    if (portComboBox.Items.Count > 0)
        portComboBox.SelectedIndex = 0;
}
```

# データの送信
　データを送信するのは簡単です。Button2のクリックでtextBox2の内容を送信する例です。textBox2の送信の最後に"\n"を追加しています。これは、相手側に合わせてください。

```send.cs
private void button2_Click(object sender, EventArgs e)
{
    if (serialPort1.IsOpen)
    {
        serialPort1.Write(textBox2.Text + "\n");
    }
}
```

# データの受信
　データ受信イベントを使った方法を説明します。
　SerialPortのDataReceivedイベントをダブルクリックで設定します。

```read1.cs
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    string str = serialPort1.ReadLine();
}
```

　これで改行までの1行を読み込むのですが、受信内容をテキストボックス(複数行)に追加しようとして次のようにすると、受信時に例外が発生します。

```read2.cs
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    string str = serialPort1.ReadLine();
    this.textBox1.AppendText(str + "\n");
}
```

　これは、データ受信スレッドと、メインスレッドが異なるためです。じぁあどうするかというと、

```read3.cs
delegate void SetTextCallback(string text);
private void Response(string text)
{
    if (textBox1.InvokeRequired)
    {
        SetTextCallback d = new SetTextCallback(Response);
        Invoke(d, new object[] { text });
    }
    else
    {
        textBox1.AppendText(text + "\n");
    }
}

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    string str = serialPort1.ReadLine();
    Response(str);
}
```

このようにすればよいです。
　応用するなら、Response関数内のelse以下のところにコードを追加すると大概のことができます。

# 閉じる
　これは簡単！

```close.cs

private void button2_Click(object sender, EventArgs e)
{
    serialPort1.Close();
}
```

　ところが！閉じるときにハングアップすることがあります。
　これの原因は読込の時に使ったInvoke関数がブロッキングするからです。そこで、次のコードにすると問題ありません。

```read4.cs
delegate void SetTextCallback(string text);
private void Response(string text)
{
    if (textBox1.InvokeRequired)
    {
        SetTextCallback d = new SetTextCallback(Response);
        BeginInvoke(d, new object[] { text });      // <--ここを変える
    }
    else
    {
        textBox1.AppendText(text + "\n");
    }
}

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    string str = serialPort1.ReadLine();
    Response(str);
}
```

　Invokeではなく、BeginInvokeを使うということです。


# Arduino R4対策
　arduino R4でUSBシリアルを使った通信行ったところ何も受信できないことがわかりました。色々調べた所、SerialPortの DtrEnable を true にすると何事もなく動き出しました。


プロジェクト全体はリンクからダウンロードしてください。
[VisualStudio2017でのサンプルプロジェクト](https://github.com/mag02243/SerialTest)
VisualStudio2022でも動作します。