C#(VS2013 Community, .NET4.5.1)でArduinoとシリアル通信する

# ちょっとハマった

http://www.mztm.jp/2013/03/17/serialcommnunication/

http://hananekolog.blogspot.jp/2014/05/c-arduino.html

などなど、先人の残した情報が豊富なチャレンジなのですが、VisualStudio+C#で
FormApplicationを作ったことがあんまりなかったせいか、それでもうまく行かなくてハマったので書き残します。

![無題クリップ_051515_111352_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/21596/c03d8cce-5979-03c9-2013-f967b38be4fe.jpeg)

こんなかんじのFormを適当に作ります。

![スクリーンショット_051515_111815_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/21596/349be0c9-e6b8-6f85-848b-0e06262114e2.jpeg)

フォームデザイナの下にserialPortオブジェクトが鎮座ましましてますが、こんなかんじでVSのフォームデザイナはUIオブジェクト以外のオブジェクトをフォームに埋め込むことができます。
これをどうやってやるか、というと、
![スクリーンショット_051515_112111_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/21596/c72833a2-1108-c429-e19a-67601236e40b.jpeg)
この様にツールボックスのコンポーネントの中にSerialPortオブジェクトがあるのでこれをドラッグ＆ドロップすればOKというわけです。
![スクリーンショット_051515_112605_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/21596/a27a003e-e124-ae5c-b0c4-3a14dedbda19.jpeg)
SerialPortオブジェクトのプロパティでポート名やボーレートを設定できます。
私のアプリではCOMポート名とボーレートをUIから選べるようにしてるんですが、テストのためだけならここで決め打ちしてしまってもよいでしょう。
![スクリーンショット_051515_112943_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/21596/07305f0d-d219-3a11-8641-4c76c14e0c92.jpeg)
そして実際にデータ受信するのに重要な操作が、このイベントハンドラの設定です。雷（？）アイコンのタブでイベントハンドラを設定できるので、ここで“DataReceived”に受信処理を担当するメンバメソッドを指定します。まだ作ってないなら空欄をダブルクリックすると“（シリアルポートオブジェクト名）_DataReceived”というメソッドを勝手に作ってくれるので楽できます。

```csharp
        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try {
                string data = serialPort.ReadExisting();
                if(!string.IsNullOrEmpty(data)) {
                    Invoke((MethodInvoker)(() => rcvTextBox.AppendText(data)));
                }
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }
```

connectボタンにもハンドラを設定して、ここでシリアルポートのオープン処理を仕込みます。

```csharp
        private void connectButton_Click(object sender, EventArgs e)
        {
            if(serialPort.IsOpen) {
                serialPort.Close();

                connectButton.Text = "connect";
                portNames.Enabled = true;
                baudRates.Enabled = true;
            }
            else {
                serialPort.PortName = portNames.SelectedItem.ToString();

                BuadRateItem baud = (BuadRateItem)baudRates.SelectedItem;
                serialPort.BaudRate = baud.BaudRate;

                try {
                    serialPort.Open();

                    connectButton.Text = "disconnet";
                    portNames.Enabled = false;
                    baudRates.Enabled = false;
                }
                catch(Exception ex) {
                    MessageBox.Show(ex.Message);
                }
            }
        }
```

ま、ここまでは冒頭に挙げた先人の知恵と全く一緒です。足りない情報はそこから拾ってください。

# なんか繋がらない！

Arduino IDEのシリアルモニタではちゃんとデータが来てるのを確認してるんでArduino側のプログラムに不備はないと思うんですが、先人の知恵に従った実装では待てど暮らせど*_DataRecievedが呼ばれる気配がありません。一体何が違うのか、といろいろオロオロした結果、RtsEnableをTrueに設定すれば良いことがわかりました。
![スクリーンショット_051515_115824_PM.jpg](https://qiita-image-store.s3.amazonaws.com/0/21596/a093bff6-81d7-f68c-faf0-91682814c45c.jpeg)
この点に触れている記事が見当たらなかったため右往左往したのですが、（自分が使ってる）Arduino Microだとこうする必要があって、Unoならいらない、とかなんですかね？

# こんどはX押してウィンドウを閉じようとするとハングする！

無事データも受信できてめでたしめでたし、シリアル通信のデータをダンプするだけじゃなく見栄え良く表示する実装に移ろうか、と思ったらウィンドウを閉じてアプリを落とそうとするとそのまま固まってしまうことに気づきました。ちゃんと切断してから閉じれば即終了するのですが、これはちょっと不便です。
ああー、ちゃんとウィンドウが閉じるときにシリアルをCloseしてやればいいのかな、とやってみたのですが症状変わらず。
connectButton_Click内でCloseする分にはちゃんと働くのに、なぜ？となるわけですが、解決方法はGoogle先生が見つけてくれました。

https://social.msdn.microsoft.com/Forums/en-US/ce8ce1a3-64ed-4f26-b9ad-e2ff1d3be0a5/serial-port-hangs-whilst-closing?forum=Vsexpressvcs

フォームのデストラクト時SerialPortは自動でクローズされて解体されるわけですが、RtsEnableをTrueにしてオープンしているSerialPortオブジェクトがある場合、そのままだとデッドロックするようです。FormClosingイベントハンドラ内で明示的にCloseする場合でも一緒。

なので、

```csharp
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(serialPort.IsOpen && serialPort.RtsEnable) {
                e.Cancel = true;
                var tryCloseThread = new Thread(() => {
                    try {
                        serialPort.Close();
                    }
                    catch(Exception ex) {
                        MessageBox.Show(ex.Message);
                    }

                    this.Invoke((MethodInvoker)(() => this.Close()));
                });
                tryCloseThread.Start();
            }
        }
```

こんなかんじでRtsEnableがTrueの場合はウィンドウのCloseイベントを一旦キャンセルして別スレッドでSerialPortをCloseしてから改めてウィンドウを閉じるとうまくいくようです。