USBプロジェクト - ファームウェアに立ち返る (8) [USB]
そんなに急いで、何処へ行く。
USBにとって、割り込みが必要な仕事はどれなんだろう。
急ぎの仕事は何ですか?
「少なくともSETUPトランザクションだけは、取りこぼしが無いように」と考えて、必要のない部分を割り込みハンドラから出してみました。 その結果、ほとんどの仕事は、割り込みハンドラに居る意味がないとわかってきました。 あれ?急ぎの仕事って、どれだったっけ。
SETUPを取りこぼす原因となっていたのは、EP0に到着した、OUTトランザクションです。 このトランザクションを早く処理しないと、次に来るかもしれないSETUPトランザクションを取りこぼす可能性があります。 でも、割り込みまで駆使して処理しなくてはならないのは、これだけなんですよね。
データ無しコントロール転送の場合
データ無しコントロール転送は、SETUPトランザクションに続いて、STATUS-INトランザクションが到着します。 このとき、TX0E=0としておくと、マイコンはホストに対してNAKを返します。 従って、あわてて処理をする必要はありません。
データ付きコントロールIN転送の場合
データ付きコントロールIN転送は、SETUPトランザクションに続いて、DATA-INトランザクションが到着します。 ここでも、TX0E=0としておくと、マイコンはホストに対してNAKを返します。 従って、あわてて処理をする必要はありません。 割り込みハンドラの外で、エンドポイントのバッファにデータを詰めてからTX0E=1とするだけで、データがホストに渡ります。
その後のSTATUS-OUTトランザクションに対する応答は、次に発生するであろうSETUPトランザクションが迫ってきているかもしれないので、すみやかに処理する必要があります。
コントロールOUT転送の場合
コントロールOUT転送は、SETUPトランザクションに続いて、DATA-OUTトランザクションが到着します。 エンドポイントにSETUPパケットが残ったままの状態(RXD0F=1)にしておくと、マイコンは、ホストに対してNAKで応答します。 ここだけ考慮するなら、割り込みハンドラ内で早めにRXD0FR=1とする事には意味がないし、しないほうが良いはずです。 ところが、後で述べるトランスファの中断という事態を考えるとSETUPパケットをそのまま残すわけにもいきませんので、すみやかに処理を行うことになります。
転送の最後にSTATUS-INトランザクションが到着しますが、これもTX0E=0としておくと、マイコンは、ホストに対してNAKで応答します。 急いで受信したDATA-OUTトランザクションの処理がすっかり終わって、暇になってからTX0E=1にするだけで十分です。
トランスファが中断される場合
処理中のトランスファが中断される場合には、予期せずSETUPトランザクションが到着する事態も考えられるので、EP0-OUTバッファは、常にあけておく必要があります。 そのため、RXD0Fイベントを割り込みで優先的に処理する必要があります。
インタラプト転送の場合
インタラプト転送は、エンドポイントを片方向で使用するため、TX1EとRX2Eをマイコンの都合で制御することができます。 そのため、割り込みで急いで処理する必要はありません。
まとめると
こうして、整理して考えてみると真に割り込みでの処理が必要なのは、SETUPとEP0-OUTが到着するRX0DFイベントだけという図式が見えてきます。 割り込み機能が装備されているから、他のイベントにも割り込み処理が必要かと思っていたら、他のイベントは、アプリケーションの都合に合わせて動作させても良いみたいです。
これをアプリケーションに適用する時の問題点は、アプリケーション・タスクと割り込みタスクに加えて、USBの入出力タスクという新たなタスクが必要になるというところです。 あまり、手をかけずに「マルチ・タスク」みたいな事をさせるには… Visual BASIC の DoEvents 式にすると、可能は可能だな。 UNIX で言うところの yeild() ですね。
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
>インタラプト転送は、エンドポイントを片方向で使用するため、TX1EとRX2Eをマイコンの都合で制御することができます。 そのため、割り込みで急いで処理する必要はありません。
これまた良い着眼点です。これはバルク転送でもそうなんですね。
まさに、入門編「インターラプト・バルクエンドポイントの扱い方」で取り上げたかった内容です。
USBのエンドポイント(EP)の挙動は、実はUART とそっくりです。
USB IN EP: UART TX - バッファ・エンプティでフラグが立ち、割込みが起こる
USB OUT EP: UART RX - バッファにデータが到着したときにフラグが立ち、割込みが起こる
どちらも、割込みでもフラグのポリングでも扱えます。違いは、UARTは1バイトずつですが、USBはパケットを扱う点です。しかし、データの塊という観点に立てばコードの流れは全く同じです。だから、UARTを扱うのとそっくり同じにUSBのエンドポイントを扱えばよいのです。まず、UARTだとどうコードするかを考え、それをUSBのエンドポイントに適用してください。USBだからといって難しいことは何もないのです。
ほとんどのサンプルは、インターラプト・バルクエンドポイントを割込みで処理しています。これは多くの場合良い実装とは言えません。割込みには、何かが起きたというイベントとしての性格があります。エンドポイントの割込みはホストが起こしたイベントであって、デバイス側のタイミングとは無関係です。そこにデバイス側のイベントで駆動されるべき処理を押し込めようとしても無理があるのです。デバイス側のイベントの処理の中でエンドポイントを扱う方が、はるかに自然で素直なコードが書けます。
例を引いて考えてみましょう。
1)スイッチ入力をPCに送る場合
デバイスは、スイッチ入力があったときにホストに通知するとします。
UARTだと、スイッチのスキャンルーチンかスイッチの割込みで、UARTのTXにデータを送り込みます。USBでも同じです。スイッチのスキャンルーチンかスイッチの割込みで、IN EPにデータを詰めてエンドポイントのレディフラグを叩きます。どちらも、スイッチ入力というデバイス側のイベントで駆動されます。
2) PCからコマンドを受ける場合
UART ではコマンドが多バイト長の場合、データ落ちを防ぐため割込みを使ってバッファにコマンドを受けます。コマンドのすべてのバイトがそろった時に、フラグを立ててmainのタスクに通知します。
USB ではパケットで受けるので、ハードウェアがUARTの割込みに相当する部分をやってくれます。mainのタスクはフラグをチェックするだけで良いのです。さらにエンドポイントに前のパケットが残っている場合、エンドポイントはホストにNAKを返します。ホストコントローラはNAKが返される限り何度でも再送を繰返します。パケットが落ちることはありません。だから、デバイスは前のコマンドの処理を終えたタイミングで、OUTエンドポイントにデータがあればそれを読みに行けばよいのです。
では、どういう場合にエンドポイント割込みを使って転送するのが適しているのでしょうか。
- IN EPでは、1) 送るべきデータがバッファ上にすでに確定していて、2) データサイズがwMaxPacketSize より大きく、3) それを最高速で送りたい時
- OUT EPでは、1) 送られるデータの総量に見合うバッファが確保されていて、2) 送られるデータのサイズがwMaxPacketSize より大きく、3) それを最高速で送りたい時
どちらの場合もシーケンスの最初の起動はエンドポイント割込みの外側から行う必要があります。
こうして見ていくと、大半の用途ではエンドポイント割込みによる転送は不適切であることが判るでしょう。ほとんどは他のイベントでエンドポイントを扱うか、あるいは、転送するデータによって両方を使い分けるというのが最適解のことが多いですね。
では、サンプルは何故エンドポイント割込みを多用するのか。単にホストアプリで非同期呼出し(OVERLAPPED)を使いたくないだけです。解説するのが面倒だから? USBのホストアプリで非同期呼出しは必須です。それを解説しないで何のサンプルか。
某解説書では、エンドポイント割込みによる転送を実装したデバイスから、最も新しいデータをホストが読み出すための涙ぐましい努力が語られています。しかし、ホストが要求するときにデータを送るという発想がすでに古いのです。RS232の時代に8bitマイコンが貧弱だったため、PCがすべてを処理せざるを得なかった時代の遺物です。新しいデータかどうかデバイスに判断させ新しいデータだけを送れば、ホストは常に新しいデータを手にすることが出来ます。マウスやキーボードはユーザーが入力して初めてデータを送っています。これがUSB時代の発想です。
Tsuneo
by Tsuneo (2008-03-29 23:08)