2012年1月28日土曜日

安価な自作USBを作ってみる

VUSBを使った自作USBデバイスを作ってみたら、思った以上に簡単だったのでご紹介。

今まで自作USBデバイスと言うと、PIC18F2550を使ってました。これ自体は350円と安いのですが、標準フレームワークが純正C18専用ってとこが微妙。自分はSDCC向けにフルスクラッチでUSBスタック書いてたけど、USB関連のレジスタは簡単なビットフィールドの説明以外、詳しい触り方の情報がないので・・・まぁ普通はしないですね。

で、電子工作屋さんとしてもう一つ選択しに出てくるのがAVRシリーズ+VUSBという選択。こっちはハード的にはTINY2313でも動かせるので石の値段はたった100円で済む。ただし、ソフトウェア的にUSBの物理層を話そうとするので、USB 1.1 low-speedまでが限界。と言っても、自作レベルでそれ以上の転送レートが必要になるようなデバイスを作る事はそうそうないので、まずは用が足ります。

という事で、今回はVUSBの調査も兼ねてAVRで作ってみる事にしました。周辺に必要な素子は、自分の場合、回路箱から適当に見繕ってるんだけど。個別で集めるとなんだかんだで300円くらいになっちゃうのかなぁ。基板とUSBの端子、セラロック、4.7uの電解コンデンサ。あと抵抗とパスコンを少々。最後に3.3Vを作るためのお好みの回路。自分の場合、HIDaspx自作する時によくやってたのが、USBバスの5Vから電源用LEBを差して電圧降下で3.3Vを狙う方法。多少ずれても平気だけどホスト側との相性が出やすい、電圧が不安定になりがちでコンデンサで調整しないと動かない事も多々あり、たまにLEDが過負荷で飛ぶ・・・みたいな感じであまりオススメできません。今回はツェナーダイオードを使う方法で、2個直列させて安定3.3Vを作りました。ツェナー使ったことない人は向きを間違えないように注意。この辺りはチップが3.3Vを作ってくれるPIC18F2550のほうが圧倒的に楽で安心。

回路を作る上で注意しないといけないのは、リファレンス回路のままだと割り込みピンが全部持ってかれちゃうのとクロック出力が使えない、という2点。もしどちらかが使いたければ、ピン配置を変える必要ありです。ファーム側の変更は後述しますが、とても簡単なので遠慮なく変えちゃってOK。ただし、最低でもD+は割り込みピンに繋ぐ必要がある点だけ注意。ファームはD+のエッジをトリガにして割り込みで起動、クロックを数えながら物理信号を読み解くのです。同じくVUSBを使ってるHIDaspxなんかは、クロック出すためにピン配置変えてますので、回路のテスト用にとりあえずHIDaspxのファームを・・・という時には注意が必要。はまります(と言うか、はまりました)。特にヒューズの値が違うという点を忘れがち。

VUSBを使いながら、どれくらい自由にファームが組めるのか、という点が気になる所かと思います。前述の通り、物理信号の読み取りに割り込みハンドラが1つ取られます。あとは50msecに1回以上の頻度でusbPoll()を呼んであげればOK。usbPoll()の中では、割り込み側でパケットを検出していればホストからの要求を見て自動的に応答、あるいはユーザハンドラを起動して、ユーザからの応答を送り返す、といった処理をしてくれます。物理層読み取りと50msecの話があるので、リアルタイム制約の厳しい処理は共存できません。例えばソフトウェアUARTとかソフトウェアNTSC/VGAは書けないし、タイマー割り込みでPWM使って波形合成、みたいなのも安定させるのは難しそう。そういう時はもう1個マイコンを用意して、そっちでリアルタイム処理。チップ間はタイミング制約の緩いコマンドのやり取りで片付ける必要があります。

実際のファームの作成ですが、example以下にサンプルがあるので倣って作ればOK。USBのプロトコル知らなくても書けるレベルじゃないかな、これ。非常に簡単。ドライバ作らなくても良いって理由でHIDクラスのデバイスとして作る人は多いみたいなんだけど、自分はお気楽ドライバで好き勝手できるという点から、むしろカスタムクラスで作っちゃってます。
usbconfig.hというのがデバイスデスクリプタやピン配置を定義してるファイルで、マクロの値を説明に従って書き換えていけばOK。自分の時の書き換え例をここでdiffで見えるようにしてあります。D-のピン番号変えて、あとは要求電流を規約上最大の500mAにしてます。自作デバイスの消費電力とか、まず正確に把握するのは無理なので、とりあえず最大値を言っときましょう的な。実はMac使ってると、ここで宣言した以上の電力を使った時に「このデバイスの電力消費は多すぎ」みたいな警告ダイアログが出て、デバイスを無効化してくれます。ので、怒られたら増やして、みたいな対応もアリです。ベンダー/デバイスIDは個人で使ってる分には適当に。正規ID取得は結構お金かかるんだけど0x6666がPrototype product Vendor IDにアサインされてるんで、自分はこの値を使ってます。main処理はこんな感じ。usbInit()呼んだあとにusbDisconnect()状態で100msec以上待機、usbConnect()を呼んでからusbPoll()のループに入ってます。

プロトコル処理も今回は一番安易な方法。新規のエンドポイントを作らずに、デフォルトパイプでコントロール転送だけ使ってデバイス制御させてます。さっきのmain.cの中のusbFunctionSetupとlinux用ドライバのcrts_set() /crts_get()あたりを見比べてもらえば簡単に理解できると思います。

あとはMakefileの中で使ってるクロックを指定してやる必要があります。自分は20MHz使ってます。クロックによって物理層を読むときの遅延コードが変わるので、コードサイズにも若干影響します。他のクロックのほうが小さくなるって話を見たような気もしたんだけど、今手元で試した限りでは20MHzが一番小さくなってます。その他のファイルも合わせGoogle Codeに上げてあるので試しに何か作ってみようという方は参考にどうぞ。