※ Qiitaの下書きが10件までという異常な少なさなので追い出し...
これを置いている間にqemuが正式版でAVRをサポートしたのでArduino IDEを使わない方向で作戦を大巾に修正して、結局用意していたArduinoエミュレータは捨てることにした。
以前の内容
Arduinoエミュレータが欲しいので用意することにした。AVRエミュレータは simavr とか Simulavr や qemu が既にあるので、これとArduino IDEを仮想COMポート com0com で接続するのが良いはず。
... simavrを使ったArduinoエミュレータとしては simulIDE が既に有るけど、自前のデバイスを足す方法がよくわからず。。GUIも要らないし。。
※ 今回は仮想COMポートを経由するところまでで、実際のArduinoエミュレータの組み立ては次回。
やること / できないこと
目的は「Arduino IDEとエミュレータを仮想COMポート経由で直結し、Arduino実機と同じUIで書き込み等行いたい」と言える。
(DTR信号は直結ではなくコンデンサを通している。これはDTRがローになる瞬間をリセット"パルス"にするため
Arduino の回路図を確認すると、RXD(受信データ)、TXD(送信データ)、DTR(端末レディ)の3線が接続されていることがわかる。このため、仮想COMポートを経由して、この3線をエミュレータに入力する必要がある。
DTR信号は正確には処理できない
実際の文字列データをやりとりするRXD/TXDと違って、DTRのような制御信号は1ビットのI/Oポートとして使える(DTRは出力専用)ため、PCのプログラム(= Arduino IDE)から任意のタイミングでOn/Offできる。この性質から、 ↑ の回路図にあるように、DTR信号はリセット信号として使われている。
Wikipediaの ヌルモデム のページにあるように、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版が提供されている。インストール後、適当にポートを追加してからデバイスマネージャを開いてビックリマークが付いていないか確認する。
ビックリマークが付いている場合は、プロパティ → ドライバーの更新 からWindows Update経由でドライバを入手することになる。
Arduino IDEは(デバイス名先頭に \\.\
を付けないので) COM
で始まる名前のポートしか使えない ようだ。なので、仮想COMポートの片方を "use Ports class" して適当なCOMポート番号を振るのが良い。
逆端はこちらで用意するプログラムからopenするので名前は適当でもちゃんと指定すれば動作する。
Win32 APIを使ったシリアルポートの監視
今回のプログラムもCygwinで実装している。実はCygwinには /dev/ttyS0
等でシリアルポートを直接扱う機能が付いているが、 TIOCMIWAIT
のような必要なIOCTLが実装されていないようなのでWin32 APIを使っている。
一般的なPOSIX系OSと同様に、Win32でもシリアルポートはファイルのように扱うことができる。おおまかな処理の流れは、
CreateFile
APIでシリアルポートのデバイスファイルをopenする(今回の場合は\\.\VIRT0
)SetCommState
,SetCommMask
APIでシリアルポートのボーレート等を設定する- (文字受信スレッド)
ReadFile
で受信データを読み取る - (DSR監視スレッド)
WaitCommEvent
で変化を待ち、GetCommModemStatus
でDSRの状態を取得する
のようになる。 ReadFile
および WaitCommEvent
の各APIはブロッキングAPIであるため、これらのAPIを呼び出すためにスレッドを2つ用意している。
Arduino IDEを起動して適当に書き込みボタンを押すと、 0x30
や 0x20
が送られてきていることがわかる。Arduinoのブートローダであるoptiboot( https://github.com/Optiboot/optiboot )は、 STK500 Communication Protocolのサブセットを実装していて これらのバイト列の意味はSTK500 Communication Protocolで定義されている。
0x30 0x20
で STK_GET_SYNC
と CRC_EOP
を表わしていて、AVR側からは 0x14 0x10
(STK_IN_SYNC
STK_OK
) で応答することが期待されている。