PICのUSB機能でWindowsとVCP接続する時の不具合を少し解消

開発者向けです。他の方は読み飛ばしてください。

PIC18F25K50マイコンの内蔵USB機能を使いCDC(Communications Device Class)・仮想COMポート(VCP)でWindows PCと接続する装置について、2つの点が気になっていました。USBの仕組みはよくわからないまま試行錯誤でびみょうに解決できたのでまとめておきます。お困りの方は参考にしてください。

まず1つめ

Windows 10では長時間連続通信しても問題ない(ミスがあっても送受信できる)のに、Windows 8 / 7 / Vista / XP だとたまにエラーで通信が止まりそれ以降はUSBケーブルを抜き差しするまで送受信できなくなっていました。デバイスマネージャーでは普通に認識されたままです。

検索するとWindows 8まではOS標準ドライバー usbser.sys にこの問題があり、Windows 10でようやく修正されたということでした。USBインターフェースICを追加すればそのメーカー製の全OS問題なしドライバーで解決するでしょうが、それではUSB内蔵マイコンを選んだ意味がありません。usbser.sys にかわるドライバーも見つけられませんでした。

PICで異常が認識できるフラグがないか地道に調べると、MikroCの USB Device Library で Data interface IN endopoint とコメントされている番号のエンドポイントの STALL Enable bit (今回2だったので UEP2.EPSTALL)が通信エラーの瞬間から 1 になっていました。再送受信できるWindows 10でも同様です。なおこのビットをすぐクリアしても何も変わりませんでした。

2つめ

装置とUSBをつないだ際にWindowsデバイスマネージャーでそのCOMポートにびっくりマークが付いて通信できないことがたまに起きるというPCがありました。こうなるとUSBケーブルを抜き差しするまで何もできません。

ふたたび地道に調べると、このときPICの内部的にはUSBケーブルを抜いたのと同じ状態になっていました。MikroCでは

USBDev_GetDeviceState() == _USB_DEV_STATE_SUSPEND

です。

ということで、それぞれ以下のように対策しました

1つめ。WindowsアプリがOSバージョンを調べ、8以前ならなら STALL Enable bit が立ったときこれのクリア(再エラーに備える意)とUSBのリセット、10以降なら無視、を装置との最初の通信で指示しておく

2つめ。装置USBの状態が変わったとき以下の順だったらUSBをリセットする(詳しくは後述の余談にて)。正しく認識されるまで繰り返される

_USB_DEV_STATE_ADDRESS
_USB_DEV_STATE_SUSPEND

USBのリセットは、USB Module Enable bit (UCON.USBEN) を 0 にして100ms前後ち 1 にするという感じ(Windows 10 / 8 / 7 / Vista)。ただXPは1,200ms以上のウェイトがないと通信再開に失敗する場合があります。USBENbit = 1 にしていいタイミングがわかる仕組みがあれば短時間化できそうですが見つけられませんでした(USBがdisable状態なのでムリなんでしょうね。UCON.SUSPND と UCFG.UPUEN の組み合わせでも“抜き差し”できますが同じようです)。

リセットすると1つめではこれの度にWindowsでUSB脱着音が鳴ります(ちょっと気になる)。2つめは全く違和感ありません。

Windowsアプリ側では通信異常時、少し待ってポート再設定と送受信の再開を行います。

うちでは上記のウェイトの時だいたい800ms(XPは1,300ms)で再接続できるようなので、早めにつながることがあるのも考慮して500ms(XPは1,000ms)後から再接続を100msごと10回繰り返し、だめならUSBケーブル抜けか異常としました。

余談です。PCによってUSB認識時の USBDev_GetDeviceState() の順序は次の2パターンありました。まずは、いまどきのPC。

1. _USB_DEV_STATE_DEFAULT
2. _USB_DEV_STATE_ADDRESS
3. _USB_DEV_STATE_CONFIGURED
(失敗時 3. _USB_DEV_STATE_SUSPEND)

古いPC。OSにかかわらず(2008年製PC+Windows 10でも確認)、USBをいったん取り外す(?)SUSPENDが先に行われています。

1. _USB_DEV_STATE_DEFAULT
2. _USB_DEV_STATE_SUSPEND
3. _USB_DEV_STATE_DEFAULT
4. _USB_DEV_STATE_ADDRESS
5. _USB_DEV_STATE_CONFIGURED

USBケーブルを外すと以下の状態に。これは新旧変わりません。

1. _USB_DEV_STATE_SUSPEND

というわけで今のSUSPENDが実際にはどういう状態かという判断はこの直前が重要なようです。