Arduino IDEを仮想COMポートに繋いでWindowsアプリから通信を吸い出す実験

※ Qiitaの下書きが10件までという異常な少なさなので追い出し...

これを置いている間にqemuが正式版でAVRをサポートしたのでArduino IDEを使わない方向で作戦を大巾に修正して、結局用意していたArduinoエミュレータは捨てることにした。

----

# 以前の内容

Arduinoエミュレータが欲しいので用意することにした。AVRエミュレータは [simavr](https://github.com/buserror/simavr) とか [Simulavr](https://www.nongnu.org/simulavr/) や qemu が既にあるので、これとArduino IDEを仮想COMポート [com0com](http://com0com.sourceforge.net/) で接続するのが良いはず。

... simavrを使ったArduinoエミュレータとしては [simulIDE](https://www.simulide.com/p/home.html) が既に有るけど、自前のデバイスを足す方法がよくわからず。。GUIも要らないし。。

※ 今回は仮想COMポートを経由するところまでで、実際のArduinoエミュレータの組み立ては次回。

# やること / できないこと

目的は「Arduino IDEとエミュレータを仮想COMポート経由で直結し、Arduino実機と同じUIで書き込み等行いたい」と言える。

- https://www.arduino.cc/en/uploads/Main/arduino-uno-schematic.pdf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/f2408b19-b288-bb74-d9a4-dc48815cac8d.png)

(DTR信号は直結ではなくコンデンサを通している。これはDTRがローになる瞬間をリセット"パルス"にするため

Arduino の回路図を確認すると、RXD(受信データ)、TXD(送信データ)、DTR(端末レディ)の3線が接続されていることがわかる。このため、仮想COMポートを経由して、この3線をエミュレータに入力する必要がある。

## DTR信号は正確には処理できない

実際の文字列データをやりとりするRXD/TXDと違って、DTRのような制御信号は1ビットのI/Oポートとして使える(DTRは出力専用)ため、PCのプログラム(= Arduino IDE)から任意のタイミングでOn/Offできる。この性質から、 ↑ の回路図にあるように、DTR信号はリセット信号として使われている。

Wikipediaの [ヌルモデム](https://ja.wikipedia.org/wiki/%E3%83%8C%E3%83%AB%E3%83%A2%E3%83%87%E3%83%A0) のページにあるように、PC側のDTRは、com0comの他端から見るとDSR(とDCD)に接続されている。LinuxやWindowsのようなPC側のAPIでは、これらの信号が変化したタイミングは取得できるが、 **信号の状態が変化するのを待つAPIしか存在しない** つまり **取りこぼしした際の救済措置は一切存在しない** ため、注意する必要がある。

(言い換えると、Linuxの `TIOCMIWAIT` や、Win32の `WaitCommEvent` は `0` → `1` と `1` → `0` の **両方** に反応し、どちらか片方に限定することができないため、Wait後に何か処理している内にビット状態が変化してしまうと見逃しが起こる。

このため、今回の目的では100 %正確に動作することは期待できない。これらのOSの通信APIは、モデムとかを操作する側(いわゆるDTE)のために設計されているので、モデム(いわゆるDCE)になりきるためにAPIが完備されているとは限らないと言える。)

# 仮想COMポート com0com の準備

Windows上の仮想COMポートデバイスとしてはcom0comが著名で、今の com0com はちゃんと64bit版が提供されている。インストール後、適当にポートを追加してからデバイスマネージャを開いてビックリマークが付いていないか確認する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/7364a984-e67a-7690-551f-6d3388c25e19.png)

ビックリマークが付いている場合は、プロパティ → ドライバーの更新 からWindows Update経由でドライバを入手することになる。

Arduino IDEは(デバイス名先頭に `\\.\` を付けないので) **`COM`で始まる名前のポートしか使えない** ようだ。なので、仮想COMポートの片方を "use Ports class" して適当なCOMポート番号を振るのが良い。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/1b4a3d8c-c15a-6e55-d7a7-f04c69550642.png)

逆端はこちらで用意するプログラムからopenするので名前は適当でもちゃんと指定すれば動作する。

# Win32 APIを使ったシリアルポートの監視

今回のプログラムもCygwinで実装している。実はCygwinには `/dev/ttyS0` 等でシリアルポートを直接扱う機能が付いているが、 `TIOCMIWAIT` のような必要なIOCTLが実装されていないようなのでWin32 APIを使っている。

一般的なPOSIX系OSと同様に、Win32でもシリアルポートはファイルのように扱うことができる。おおまかな処理の流れは、

1. [`CreateFile` APIでシリアルポートのデバイスファイルをopenする(今回の場合は `\\.\VIRT0`)](https://github.com/okuoku/avrtest_serial/blob/25a12f8ec0510af2206b30b29064a366d4405606/check.c#L176)
2. [`SetCommState` , `SetCommMask` APIでシリアルポートのボーレート等を設定する](https://github.com/okuoku/avrtest_serial/blob/25a12f8ec0510af2206b30b29064a366d4405606/check.c#L127)
3. [(文字受信スレッド) `ReadFile` で受信データを読み取る](https://github.com/okuoku/avrtest_serial/blob/25a12f8ec0510af2206b30b29064a366d4405606/check.c#L46)
4. [(DSR監視スレッド) `WaitCommEvent` で変化を待ち、`GetCommModemStatus`でDSRの状態を取得する](https://github.com/okuoku/avrtest_serial/blob/25a12f8ec0510af2206b30b29064a366d4405606/check.c#L73)

のようになる。 `ReadFile` および `WaitCommEvent` の各APIはブロッキングAPIであるため、これらのAPIを呼び出すためにスレッドを2つ用意している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/ea718731-9357-9054-a930-6ad8d23458d7.png)

Arduino IDEを起動して適当に書き込みボタンを押すと、 `0x30` や `0x20` が送られてきていることがわかる。Arduinoのブートローダであるoptiboot( https://github.com/Optiboot/optiboot )は、 [STK500 Communication Protocolのサブセットを実装していて](https://github.com/Optiboot/optiboot/wiki/HowOptibootWorks) これらのバイト列の意味はSTK500 Communication Protocolで定義されている。

`0x30 0x20` で [`STK_GET_SYNC`](https://github.com/Optiboot/optiboot/blob/6f98751c6d184ec4a9c7f1fe0f0dcf12f9db2cc9/optiboot/bootloaders/optiboot/stk500.h#L18) と `CRC_EOP` を表わしていて、AVR側からは `0x14 0x10` (`STK_IN_SYNC` `STK_OK`) で応答することが期待されている。