かつかれーのメモ帳

実験ノートか勉強記録

Atmelマイコンを使ってPCからLチカ (汎用HID IOライブラリを公開しました)

この記事は何?

PCとマイコンの間で、USBインターフェースを通じてデータの送受信をするためのデモコードを公開したので簡単に解説します。

背景

かつてAtmelから、ヒューマンインターフェースデバイス(HID)の実装について基礎的なところを抜き出したアプリケーションノートとサンプルコードが公開されていて*1、PCからinterrupt転送を利用してデータの送受信をする方法が解説されていました。
自作デバイスのファームウエアを書く際のテンプレとしてまあ使えたのですが、最低限の処理というにはちょっと分量が多く読みづらい部分もあったので、本当に最低限と言える部分だけ整理して公開することにしました。

対応マイコン

以下の通りです。ほぼレジスタ名を共有しているので、相互に移植する際にコードの修正はほとんど要りません。同一のシリーズ内であれば挙動は全く同じです(プログラムを書き込むFlash領域の容量に差があるだけで、コア部分は同じなので)。

  • AT90USB 2シリーズ
    • AT90USB82, AT90USB162:
  • 同6シリーズ
    • AT90USB646, AT90USB1286
  • 同7シリーズ
    • AT90USB647, AT90USB1287
  • U2シリーズ(未実装、多少修正すれば動くと思います)
    • ATmega8U2, ATmega16U2, ATmega32U2
  • U4シリーズ(実装したけど未検証)
    • ATmega16U4, ATmega32U4

AT90USBの2シリーズはピン数自体少なく、6, 7シリーズの下位互換のような感じです*2。一方、7シリーズは「6シリーズ+USB Host機能」なのでDeviceモードで使用する分には基本的に6シリーズと同じに見えます。レジスタに書き込むべき値が違う箇所が稀にあったり、ブートローダーのアドレスが違ったりする程度です。
ちなみに、U2シリーズとU4シリーズの関係は2シリーズと6シリーズの関係に近いです。

ソースコード

Teensyプロジェクトのコードを借りている部分があるので(MakefileとUSB周りのコードの一部)、MITライセンスを引き継いで公開しています。
GitHub - dskk/AT90USB_HID_sample
ベースとなるコードはhidio_162, hidio_646, hidio_usbkey*3です。改変例(オマケ)として、646とusbkeyをそれぞれゲームパッドとしたものも同梱されています。それぞれ動作は確認したので、同名ファイル間で適宜diffなど取りながら読んでみて下さい。

普段意識しなければならない処理は全部main.cにまとめたので基本的にこれだけ読んでもらえれば処理は追えると思います。ディスクリプタ類はdesc.hに定義しています。

動作の概略

main関数の頭でデバイスの初期化をして、続く無限ループでio_task関数とusb_task関数を順に呼ぶだけです。io_task関数内にはピン入出力の処理を、usb_task関数内にはinterrupt転送によるデータの送受信処理を書きます(hid_report_send関数・hid_report_recv関数に分けて実装してあります)。
複数エンドポイント(EP)にも対応できますが、今回は最低限の実装ということでEP1をsendに(USBの言葉で言うとHost基準なのでINレポートになります)、EP2をrecvに割り当てています。

その他、USBデバイスとして要求される初期化処理・割り込み処理の類*4はusb.cに全部書いてあって、勝手にいい感じにしてくれるようになっています。結構汎用的な書き方になっているので、ちょっと変わったデバイスを作るのでない限りこのファイルを改変する必要はありません。

レポートディスクリプタに定義した通り、send, recv共にデータ長は8バイトです。これは元のDemoの値を引き継いでいます。
send関数ではピン入力状態を8バイト送信しています*5
recv関数では受け取ったデータをピン出力に反映させています。いわゆるLチカ用のコードですね。8バイト受け取りますが、ピン入力状態は都度上書きされるため最後の1バイト以外は一瞬過ぎてあんまり意味がありません。

Host側コード

PC側で使うPythonコード、hidapi_sample.pyをリポジトリのルートに置きました。
hidapiライブラリに依存するのでpipなどで入れておいて下さい。

  • バイスをオープン
  • バイスのmanufacturer_stringを取得
  • 試しに0x00を8バイト送信
  • forループでピン入力状態に0~255を順に反映

するだけのコードです。write関数に渡す配列の先頭がレポートID(常に0として良いです)として扱われるため、長さが9バイトな点には注意です。
デモファイルには含めていませんが、h.read(8)のようにすればデバイスからデータを受け取ることもできます。

コードの改変方法

当コードをベースに自作デバイスを作るにあたって、書き換える必要があるのはdesc.hとMakefileとmain.cです。
desc.hにはディスクリプタがまとめてあるので、適宜書き換えます。hidioサンプルとgamepadサンプルを比べると分かりやすいと思います。
Makefileでは、ボードに応じてMCUとF_CPUを更新する必要があります。
main.cには先述の通りメインループが含まれるので、ここに所望の処理を記述します。hidioサンプルでも、入出力ポート名や初期化の方法などチップごとに適当に変更しています。
その他のファイルは全プロジェクトで共通のはずです。

ただ、AT90USB162だけはinterrupt転送に使うエンドポイントでダブルバッファ*6を有効にすることができない*7のでこれだと動かなくて、普段弄らなくていいdesc.hの末尾を弄りました。特別な理由がなければダブルバッファは有効にしておいた方が何かと楽でしょうし、162のプロジェクトのdesc.hは他チップ用には使わないことをお勧めします。

おわりに

自分用の備忘録の側面が強く、最小限の解説に留めてしまったので不明点あればいつでもコメントかリプ下さい!可能な範囲で対応します。 同志カツカレー (@Tov_KK) | Twitter

*1:おそらく今はWeb上のアーカイブにしか上がっていないが、AVR328という通し番号が振ってある文書に付属するUSBKEY_STK525-series6-hidio-2_0_2-docというデモファイル。とりあえずAVR.JPとかではまだ見つかる。他にAVR276, 329あたりも関連しているので、読む方は参考にして下さい。

*2:2シリーズでは機能の制約が大きかったり一部レジスタの省略や改編があったりして、6, 7シリーズのプログラムを移植する際は多少修正を要します。

*3:AT90USBKEY2という評価ボードのこと。搭載チップはAT90USB1287。LEDやジョイスティックが最初から乗っているので動作確認が容易。秋月でも扱いがある実装済み基板なので、今回の対象チップを触り始めるなら最も手軽?

*4:エニュメレーションとか、HIDレポート以外のデータのやり取りとか

*5:なんか動作してる感?が欲しかったので、実際には1ずつカウントアップして連番にしています

*6:詳細な説明は割愛しますが、非同期FIFOの長さが1ではなく2になるという機能。例えばデバイスからの送信の際に「さっき送ったデータをPCが引き上げるのを待って(=FIFOが空になるのを待って)からデータを流し込む」必要がなくなるので、切れ目ないデータ送信が可能になる。

*7:コンパイルは通るのですが、PCと通信させたときにエラーも出さずしめやかにハングしてしまいます。デバッグしづらいからやめてくれ…