本日未明、やっと USB ペリフェラルが動きました。辛かった...
難易度としては、PIC の USB ペリフェラルは最上位レベルです。本記事はそのハマりどころ等々をつらつらと述べたいと思います。
PIC18FのUSBペリフェラル
USB マイコンということでこのマイコンを弄ってきましたが、これを弄った感想を述べます。
- 8bit マイコンだからって舐めちゃいけない
- ハマりどころ多すぎ
- ペリフェラルがニート
- STM32 や LPC11U35 以上にユーザーが書くべきコード数が多い
- 「AVR と同じっしょ」と思っていると泣きを見る
要するに地獄だってことです。まあ STM32 等と同じようにメモリ管理等が面倒くさいということを書いても特に新規性はなさそうですので、本記事は USB ペリフェラルの持つハマりどころをいくつか述べていきたいと思います。また、複雑になるのを防ぐために Ping-Pong バッファは使っていませんので悪しからず。
重要なことですが、「これだから PIC はクソ」と言う訳ではありません。感情を抜きにして「非常に大変だった」ということを述べるにとどまります。結果として、本当に大変でしたが弄っていてとても楽しかったです。PIC に対して不満タラタラ、と言う訳では決して無いのでその点はご注意あれ。
参考サイト
ハマった時に色々と参考にしたサイトたちです。
ハマりどころの列挙
それでは行きましょう。いずれもユーザーマニュアルをしっかり読めばハマらないはずです(結果論)。僕自身あまり読まずにハマりまくったので一概に文句を垂れるような身分ではないですが...
SETUP受信後、次のフレームが自動で無効化される
SETUP パケットを一度受信すると、次の通信が無効化されます。これは現在受信している SETUP パケットの処理中にデータの欠損が無いようにするためです。親切な機能ですが、他のマイコンには無い独特な仕様ですので、一応ハマりどころかなあと。
SETUP パケットを受信すると同時に、ペリフェラルは UCON
レジスタの PKTDIS
をセットします。このビットはペリフェラルのみによってセットされ、ソフトウェアからはクリアのみ可能です。
このビットが1になっていると次のパケットを受信しないので、割り込み関数内ですぐ 0 にする必要があります。こうすると次のデータステージ・ステータスステージに移行可能です。
具体的には、SETUP受信→割り込み関数内で次のステージのための送受信準備→PKTDIS
を 0 にする、という処理でいいと思います。
USTATはFIFOになっている
こちらもハッキリとユーザーマニュアルに書いてあります。特殊なレジスタですので軽く説明します。
USTAT
とは転送完了割り込みにおいて、どのレジスタがどの転送方向で割り込みが入ったかを示すレジスタです。タイトルの通り、こちらは4段構えの FIFO となっており、多重の転送完了レポートに対応しているというわけですね。
データバス上には FIFO 先頭のデータが流れており、UIR
レジスタの TRNIF
ビットをクリアすることで次の情報が FIFO から取り出されます。一度 TRNIF
を 0 にクリアして所定クロック後に、もしまだ FIFO にデータが残っている場合は USTAT
にセットされ、TRNIF
も再度 1 になります。重要なのは、TRNIF
ビットを 0 にクリアする前に USTAT
をどこかにコピーしておく必要があるということです。僕はこれに対して以下の様なコードを書いています。
for(int i = 0; i < 4 && UIRbits.TRNIF; i++) { // 4 (at max.) FIFO buffers to USTAT
const USTATbits_t stat = USTATbits; // USTATデータを退避
if( stat.ENDP == 0 ) {
if( ep0_fsm == SETUP ) {
last_setup = setup;
}
ep0_handler();
ep0_fsm = ep0_next_state(ep0_fsm);
ep0_prepare_for_status();
}
UIRbits.TRNIF = 0; // ここでフラグクリア
}
DATA0/DATA1のトグルは自分で処理する
先に「ペリフェラルがニート」と言ったのはこれに対してです。各フレームには SETUP, DATA0, DATA1 などが付きますが、DATA0/1 に関しては自分でトグルさせる必要があります。バッファテーブルの BDnSTAT
で言うと、6bit 目の DTS
でこの値を調整します。と同時に DTSEN
ビットも立てて処理します。こうしないと、DATA0/1 が一致しない受信ではバッファの書き換えが起こらず割り込みも入りません。ということは、データの送信も上手く行かないことになります。流石にこの辺はペリフェラルにやってもらいたかったのですが、自分でグローバル変数を用意して、割り込みごとに手動でトグルさせる他ないようです。
〆
以上です。列挙してみて、思ったより少ないなぁと思いましたが、この辺で長い時間ハマっていたのでねぇ...
今後ライブラリを使わずに自力レジスタ操作で USB をやりたい人に対しての参考になるといいなと思って書きました。それでは頑張っていきましょう。