USBプロジェクト - ファームウェアに立ち返る (11) [USB]
昨日に続き、SET_ADDRESSリクエストなどで使用される、データ無しコントロール転送のシーケンスを考えてみました。
シーケンス図
データ無しコントロール転送をポーリングするシーケンスを書きました。 昨日のシーケンスと異なり、DATAステージがありません。 この転送では、SETUPトランザクションに全てのデータが含まれています。 そのため、他のデータの授受を行うことなく、STATUSトランザクションを発行すると転送が終わります。 STATUSトランザクションは、コントロール読み出し転送のDATAステージの処理を流用しています。
メインルーチンは、STATUS応答をEP0-INバッファに書き込んだら、転送の終了をポーリングによって待ち、転送完了により実際の処理を行います。 ポーリングを使っているため、転送終了を待つ時間、原則として他の仕事をすることができません。 もったいないな。
ポーリングに代わる方法は無いか
このモデルでは、ポーリングによって転送の終了を待っているので、待ち時間の間、メインルーチンでフラグを監視しなくてはなりません。 これは、重い処理です。
解決策のひとつは、割り込み処理ルーチンに処理を書いてしまう事です。 しかし、この方法は、割り込み処理ルーチンでの処理時間が長くなってしまい、結果としてトランザクションに対する応答時間が長くなってしまいます。 リアル・タイム・オペレーティング・システム(RTOS)であれば、コントロール転送とアプリケーションの二つのタスクに分割するところですが、今の所、RTOSを導入・開発する予定はありません。
もうひとつの解決策として考えたのが、タイマ割り込みを使って、周期的にフラグを確認する方法です。 この方法によると、最大応答時間を割り込み周期で規定できます。 ただし、複雑な処理を行わせると、USB割り込み処理が遅くなってしまいます。
周期的タイマ割り込みを使う事にすると、コントロール転送の処理は、逐次、自動的に処理を行うことが出来ます。 このため、アプリケーション側には、特にコードを書く必要はなく、アプリケーションの処理に専念することができます。
次回は、コントロール書き込み転送、または、タイマ割り込みを使ったUSBの処理について考える予定です。
2008-04-02 20:39 コメント欄の内容を追記しました。
Tsuneoさんからのコメント
多重割込みの無いMCUではこれが悩みの種ですね。 割込みで速い応答時間を保証するため、割込みでの処理を出来るだけ軽くする必要があります。そうすると、タスクスケジューリングに安易に割込みが使えなくなる。メインでスケジューリングをやるか(協調マルチタスク)、なるべく軽いタスクスイッチを割込みに組込む(RTOS)ことになります。
ポリングが重いかどうかは、コーディングによりますね。 こういう最小ポーリングループだと重いでしょう。フラグが立つまでの時間をすべてポーリングが喰っていますから。
void usb_task( void ) { while ( ! setupReady ); // 最小ポーリングループ setupReady = 0; setup_parser(); ... }
しかし、上の最小ポーリングループをメインループに解放してやれば、ポーリング中に他のタスクも実行でき、ポーリングに費やす実行時間は相対的に減ります。
void main( void ) { // // 初期化 // while(1) { // メインループ usb_task(); application_task(); } } void usb_task( void ) { if ( ! setupReady ) return; // ポーリングループを解放 setupReady = 0; setup_parser(); ... }
これがメインループを使った協調マルチタスクの原型です。RTOSよりもお手軽なので小規模のシステムでは良く使われます。しかし、 ポーリング周期(タスクの応答時間)がメインループで実行する他のタスクによって決まる - 最大ポーリング周期は、他のタスクの最大実行時間 という大きな制約があります。それぞれのタスクの一回の実行時間を短く保つため、タスクの中身を細かく分割する必要があります。分割は概ね原型のように時間のかかりそうな待ちループの部分で行います。分割されたサブタスクを順次実行するため、ステートマシンやデシジョンツリーが使われます。
例えばUSBでは、
// USBタスクステートマシン enum { st_USB_IDLE, st_USB_NODATA_STATUS, st_USB_READ_DATA, st_USB_READ_STATUS, .... }; unsigned char usb_task_state = st_USB_IDLE; unsigned char * usb_TX_buf; unsigned char usb_TX_count; void usb_task( void ) { switch( usb_task_state ) { case st_USB_IDLE: if ( ! setupReady ) return; setupReady = 0; setup_parser(); .... break; case st_USB_NODATA_STATUS: .... usb_task_state = st_USB_IDLE; break; case st_USB_READ_DATA: if ( usb_TX_count >= setupData.wLength ) usb_TX_count = setupData.wLength; else { usb_add_ZLP = FALSE; if ( (usb_TX_count & (EP0_MAXPACKETSIZE - 1)) == 0 ) usb_add_ZLP = TRUE; } usb_Write_TX( usb_TX_buf, usb_TX_count, usb_add_ZLP ); usb_task_state = st_USB_READ_STATUS; break; .... default: break; } }
タイマによるフラグは最小応答時間を規定して定期的なタスクの実行を助けます。しかしこの場合も、それぞれのタスクの最大実行時間は、タイマの周期よりも短かくする必要があります。
付録 : 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
- メディア: 単行本
>しかし、この方法は、割り込み処理ルーチンでの処理時間が長くなってしまい、結果としてトランザクションに対する応答時間が長くなってしまいます。
多重割込みの無いMCUではこれが悩みの種ですね。
割込みで速い応答時間を保証するため、割込みでの処理を出来るだけ軽くする必要があります。そうすると、タスクスケジューリングに安易に割込みが使えなくなる。メインでスケジューリングをやるか(協調マルチタスク)、なるべく軽いタスクスイッチを割込みに組込む(RTOS)ことになります。
>このモデルでは、ポーリングによって転送の終了を待っているので、待ち時間の間、メインルーチンでフラグを監視しなくてはなりません。 これは、重い処理です。
ポリングが重いかどうかは、コーディングによりますね。
こういう最小ポーリングループだと重いでしょう。フラグが立つまでの時間をすべてポーリングが喰っていますから。
void usb_task( void )
{
while ( ! setupReady ); // 最小ポーリングループ
setupReady = 0;
setup_parser();
...
}
しかし、上の最小ポーリングループをメインループに解放してやれば、ポーリング中に他のタスクも実行でき、ポーリングに費やす実行時間は相対的に減ります。
void main( void )
{
//
// 初期化
//
while(1) { // メインループ
usb_task();
application_task();
}
}
void usb_task( void )
{
if ( ! setupReady ) return; // ポーリングループを解放
setupReady = 0;
setup_parser();
...
}
これがメインループを使った協調マルチタスクの原型です。RTOSよりもお手軽なので小規模のシステムでは良く使われます。しかし、
ポーリング周期(タスクの応答時間)がメインループで実行する他のタスクによって決まる
- 最大ポーリング周期は、他のタスクの最大実行時間
という大きな制約があります。それぞれのタスクの一回の実行時間を短く保つため、タスクの中身を細かく分割する必要があります。分割は概ね原型のように時間のかかりそうな待ちループの部分で行います。分割されたサブタスクを順次実行するため、ステートマシンやデシジョンツリーが使われます。
例えばUSBでは、
// USBタスクステートマシン
enum {
st_USB_IDLE,
st_USB_NODATA_STATUS,
st_USB_READ_DATA,
st_USB_READ_STATUS,
....
};
unsigned char usb_task_state = st_USB_IDLE;
unsigned char * usb_TX_buf;
unsigned char usb_TX_count;
void usb_task( void )
{
switch( usb_task_state ) {
case st_USB_IDLE:
if ( ! setupReady ) return;
setupReady = 0;
setup_parser();
....
break;
case st_USB_NODATA_STATUS:
....
usb_task_state = st_USB_IDLE;
break;
case st_USB_READ_DATA:
if ( usb_TX_count >= setupData.wLength )
usb_TX_count = setupData.wLength;
else {
usb_add_ZLP = FALSE;
if ( (usb_TX_count & (EP0_MAXPACKETSIZE - 1)) == 0 )
usb_add_ZLP = TRUE;
}
usb_Write_TX( usb_TX_buf, usb_TX_count, usb_add_ZLP );
usb_task_state = st_USB_READ_STATUS;
break;
....
default: break;
}
}
>もうひとつの解決策として考えたのが、タイマ割り込みを使って、周期的にフラグを確認する方法です。 この方法によると、最大応答時間を割り込み周期で規定できます。
タイマによるフラグは最小応答時間を規定して定期的なタスクの実行を助けます。しかしこの場合も、それぞれのタスクの最大実行時間は、タイマの周期よりも短かくする必要があります。
Tsuneo
by Tsuneo (2008-04-01 13:14)
Tsuneoさん、コメントありがとうございます。
本文の文章は、花粉症の抗ヒスタミン薬と戦いながら書いたので、読み返してみると意味が通じないところが、ちらほら。お恥ずかしい。
協調マルチタスクは、USB処理の部分は割と楽にステートマシンになると思いますが、アプリケーション本体をステートマシンに置き換えられるかというところが課題になると思います。幸い、本体部分は全く考えていないので、ゼロからステートマシン前提で書いていけるとは思います。適度にタスクを分割しなくてはならないのだけど、計算するのは、面倒だな。タイマでコンテキスト・スイッチを作っちゃおうか。
by noritan (2008-04-02 20:26)
こういったリアルタイム・システムの設計では、各イベントに対する応答時間の要求をしっかり押さえておくことが肝心です。標準デバイスリクエストに関する応答時間の要求についてはこれまでの議論で一度出ていますが、ここでまとめておきましょう。
Set_Address
- SETUPステージの最初からSTATUSステージの終了まで: 50 ms max
- STATUS ステージの終了からアドレスレジスタ(UADDR)の変更まで: 2 ms max
他の標準デバイスリクエスト ー ()は必要なときのみ実装
データ無しコントロール転送
- Clear_Feature、Set_Feature、Set_Configuration、(Set_Interface)
- SETUPステージの最初からSTATUSステージの終了まで: 50 ms max
コントロール読込み転送
- Get_Status、Get_Descriptor、Get_Configuration、Get_Interface、(Synch_Frame)
- SETUPステージの最初からDATAステージの最初のパケットまで: 500 ms max
- DATAステージのそれぞれのパケット: 500 ms max
- DATAステージの最後のパケットからSTATUSステージの終了まで: 50 ms max
コントロール書込み転送
- (Set_Descriptor)
- SETUPステージの最初からSTATUSステージの終了まで: 5秒 max
9.2.6.3 Set Address Processing (usb_20.pdf p245)
9.2.6.4 Standard Device Requests (usb_20.pdf p245)
こうしてみると、MHzオーダーで走っているMCUにとっては、それほど実現が大変なタイミングではないというのが判ると思います。
>アプリケーション本体をステートマシンに置き換えられるかというところが課題になると思います。
通常、それほど細分化する必要はありません。ユーザーによるキー入力待ちなどは無限に待つ可能性があるので引っかかりますが、一定時間内に確実に応答があるような周辺へのアクセスの待ちループはほとんど見逃して良いでしょう。
実際に走っている状態でタイマ割込みをワッチドッグの様に使って、時間超過が無いことを検証できます。
- ループの頭で毎回タイマの値を最大応答時間に合わせて初期化します。
- もしタイマ割込みが発生すれば、時間超過です。
Tsuneo
by Tsuneo (2008-04-02 22:27)