SSブログ

USBプロジェクト - ファームウェアに立ち返る (6) [USB]このエントリーを含むはてなブックマーク#

1516092

気の向くままに、プログラムを書きなおしてみました。

割り込みハンドラ

割り込みハンドラでは、状態を優先とした分岐をやめました。 SETUP, OUT, IN のそれぞれのトランザクションにより、doSetup(void), doEp0Out(void), doEp0In(void) の三つの関数で処理をさせます。

  // Check RXD at EP0
  if (UIR1_RXD0F) {             // any data received via EP0?
    if (USR0_SETUP) {           // SETUP packet?
      switch (doSetup()) {      // go to setup
        case ERR_STALL:     forceStall();   break;
      }
    } else {                    // or a normal packet
      switch (doEp0Out()) {     // receive it
        case ERR_STALL:     forceStall();   break;
      }
    }
    UCR0_RX0E = 1;              // Activate EP0 Receiver again
  }                               

  // Check TXD at EP0
  if (UIR1_TXD0F) {             // has EP0 sent Data?
    switch (doEp0In()) {
      case ERR_STALL:       forceStall();   break;
    }
    UCR0_RX0E = 1;              // Activate EP0 Receiver again
  }

双方とも最後にRX0E=1が入っているのは、常時SETUPトランザクションを受け入れるためです。

それぞれの関数は、エラーコードを返してくるので、割り込みハンドラ内でSTALL処理を行います。 他の種類のエラー処理があるかも知れませんが、今はこれだけです。

//----------------------------------------------------------------------------
//  Error code
//----------------------------------------------------------------------------
#define     ERR_NONE        (0)
#define     ERR_STALL       (1)

SETUPトランザクションの処理

doSetup(void)では、リクエスト・コードに従って、処理関数を呼び出します。 現在は、SET_ADDRESSリクエストだけ実装しています。

//----------------------------------------------------------------------------
//  Process for SETUP transaction.
//----------------------------------------------------------------------------
byte doSetup(void) {
  UIR2_RXD0FR = 1;              // Acknowledge
  UCR0_RX0E = 0;                // Turn off EP0 receiver

  copySetupBuffer();            // Copy SETUP parameters

  if(USR0_RP0SIZ != 8) {        // SETUP Transaction must be Size=8
    return ERR_STALL;           // otherwise we have an Error Condition
  }
  switch (setupBuffer.bmRequestType & 0x60) {
    case 0x00:  // Standard Request Decoder:
      switch (setupBuffer.bRequest) {
        case SET_ADDRESS:   // 5
          return setAddressSetup();
        default:
          return ERR_STALL;
      }
      break;
    case 0x20:  // Class Request Decoder:
      switch (setupBuffer.bRequest) {
        default:
          return ERR_STALL;
      }
      break;
    default:    // Unsupported Request
      return ERR_STALL;
  }
}

SET_ADDRESSリクエストのSETUP

SET_ADDRESSリクエストのSETUPトランザクションに対する処理を行います。

//----------------------------------------------------------------------------
//  SET_ADDRESS Standard Device Request Handler
//  called by SETUP handler.
//  according to USB2.0 spec page 256
//----------------------------------------------------------------------------
byte setAddressSetup(void) {
  // Confirm STATE
  switch (usbState) {
    case US_DEFAULT:
    case US_ADDRESSED:
      break;
    default:                        return ERR_STALL;
  }
  
  // SET_ADDRESS transaction validation
  if (setupBuffer.wIndex != 0)      return ERR_STALL;
  if (setupBuffer.wLength != 0)     return ERR_STALL;
  if (setupBuffer.wValue >= 128)    return ERR_STALL;
  
  // configure endpoint #0 to send an empty DATA1
  // at the next IN Transaction
  UCR0  = 0b10100000;
  //        ||||++++-- TP0SIZ = 0 reset packet size register
  //        |||+------ RX0E = 0   disable EP0 receiver
  //        ||+------- TX0E = 1   enable EP0 transmitter
  //        |+-------- Reserved (0)
  //        +--------- T0SEQ = 1  set TX sequence bit
}

最初に状態を確認します。 DEFAULTおよびADDRESSED状態i以外の時の処理は、"not specified"(不定)なので、STALLを返すことにしました。

次にSETUPパラメータの値を確認します。 パラメータに誤りがあったら、STALLを返します。

INトランザクションの処理

INトランザクションは、doEp0In(void)関数で処理されます。 まだ、SET_ADDRESSリクエストのステータス・ステージの処理しか実装されていません。

//----------------------------------------------------------------------------
//  Process for EP0-IN transaction.
//----------------------------------------------------------------------------
byte doEp0In(void) {
  UIR2_TXD0FR = 1;              // Acknowledge
  UCR0_TX0E = 0;                // Turn off EP0 transmitter
  
  switch (setupBuffer.bmRequestType & 0x60) {
    case 0x00:          // Standard Request
      switch (setupBuffer.bRequest) {
        case SET_ADDRESS:   // 5
          return setAddressStatus();
        default:
          return ERR_STALL;
      }
      break;
    case 0x20:          // Class Request
      switch (setupBuffer.bRequest) {
        default:
          return ERR_STALL;
      }
      break;
    default:    // Unsupported Request
      return ERR_STALL;
  }
}

SET_ADDRESSリクエストのSTATUS

ここだけは、ほとんど変わっていません。 ステータス・ステージが終了した事実が、SET_ADDRESSリクエストが正しく受信できたことを意味しているので、この関数ではエラーは発生しないはずです。

//----------------------------------------------------------------------------
//  SET_ADDRESS Standard Device Request Handler
//  called by STATUS handler.
//  according to USB2.0 spec page 256
//----------------------------------------------------------------------------
byte setAddressStatus(void) {
    // A STATUS transaction was received.
    // Set the USB address.
    UADDR = (byte)setupBuffer.wValue | UADDR_USBEN_MASK;
    if (setupBuffer.wValue == 0) {
      // make a transition to DEFAULT
      usbState = US_DEFAULT;
    } else {
      // make a transition to ADDRESSED
      usbState = US_ADDRESSED;
    }
    // Reset the request code.
    setupBuffer.bRequest = REQUEST_COMPLETE;
    
    return ERR_NONE;
}

今日のところは、こんな気分です。 明日は、また、変わるかも。

付録 : USBプロジェクト索引

参考文献

USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)

USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)

  • 作者: インターフェース編集部
  • 出版社/メーカー: CQ出版
  • 発売日: 2006/07
  • メディア: 単行本

nice!(0)  コメント(9)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 9

DAI

進んでますね。
私には、まだまだ念仏のようで、シナプスが反応していません。
とにかく、まず、用語に慣れないと話になりません。

ところで、別の角度からアプローチをと、9S12UFを入手したのに、フリスクでは、JMのオンパレードですね。まだ高いけど、DEMOJMはDEMOQEそっくり。

by DAI (2008-03-26 00:13) 

Tsuneo

>双方とも最後にRX0E=1が入っているのは、常時SETUPトランザクションを受け入れるためです。

RX0Eが落ちているとSETUPに反応しないという仕様は安易だなと思います。
UADDR_USBENビットでエンジンがエネーブルになっていれば、SETUPトランザクションは常時受け入れる、という方が当然でしょう。SETUPが来てしまえば、エンジンはACKを返す以外選択肢はありません。NAKもSTALLも返せません。SETUPを無視してはいけないのです。無視できるのは、データがノイズなどで壊れていたときだけです。

8.5.3 Control Transfers (usb_20.pdf p226)
The function receiving a SETUP must accept the SETUP data and respond with ACK; if the data is corrupted, discard the data and return no handshake.

このフローチャートを見ると、SETUPに反応するかどうかを決めるのに、RX0E以外にRXD0Fも見ていますね。
Figure 11-30. SETUP Token Data Flow for Receive Endpoint 0 (MC68HC908JB16.pdf p203)

もしUE0Rx7-0レジスタに直前のSETUPやOUTパケットが残っていて新たなSETUPデータでオーバーライトされたとしても、どうせ残っていたデータは新たなSETUP受信で廃棄されるだけです。だからSETUPを常時受け付けても全く問題は無いのです。


まあ、文句を言っていても始まらないので、SETUPを無視する(ACKを返さない)とどうなるかについて検討しておきましょう。

デバイスがSETUPにBus Turn-around Time 以内にACKを返さないと、これはホスト側ではエラーと解釈されます。
8.7.2 Bus Turn-around Timing (usb_20.pdf p237)

USBの原則として、エラーは最初のトランザクションを含めて3回まではホストコントローラが自動的にリトライし、その後は上位のホスト・ドライバの実装次第です。

4.5.2 Error Handling (usb_20.pdf p19)
The protocol allows for error handling in hardware or software. Hardware error handling includes reporting and retry of failed transfers. A USB Host Controller will try a transmission that encounters errors up to three times before informing the client software of the failure. The client software can recover in an implementation-specific way.

念のため、ホストコントローラのスペックも見ておきましょう。各ホストコントローラのスペックはこのページから辿れます。
http://www.usb.org/developers/resources/

OHCI (hcir1_0a.pdf)
4.3.1.3.6.1 Transmission Errors (p24)
For errors in this category, USB defines a policy that allows the transaction to be retried for up to three times before the transfer is failed and returned to the client. The Host Controller supports this policy with the ErrorCount field.

UHCI (UHCI11D.pdf)
3.4.1.3 Processing Bulk, Control and Interrupt Transfer Descriptors (p29)
7. Else if the transaction is unsuccessful, but the error threshold has not been reached, the TD is left active so it can be retried in a subsequent frame. Go to 10.
8. Else if the transaction was unsuccessful and exceeded the error threshold, the TD is marked inactive. Go to 10.

EHCIでは直接ホストコントローラではなく、ハブ上のTT (Transaction Translator)が間に入ってフル/ロースピードのトランザクションを扱います。
11.17.1 Bulk/Control Split Transaction Sequences (usb_20.pdf p364)
There is no timeout response status for a transaction because the full-/low-speed handler must perform a local retry of a full-/low-speed bulk or control transaction that experiences a transaction error. It locally implements a "three strikes and you're out" retry mechanism.

ということで、3回まではホストコントローラが自動的にリトライすることが確認できました。

では、どのくらの時間SETUPトランザクションを受付けないでおけるかについて検討してみましょう。
リトライの間隔はホストコントローラによって違います。UHCIは次のUSBフレームまで待ちますが、OHCIとEHCI(TT)は最悪フレーム内でリトライします。ワーストケースは3回立続けにリトライが入ると考えたほうがよいでしょう。SETUPトランザクションの最中にRX0Eを落とした場合どうなるかについては、DATAフェーズに入っていれば大丈夫そうですが安全をみてこれも駄目としましょう。そうすると、ワーストケースは3回のうちの真ん中のACKの無いSETUPトランザクション1回分ということになります。

SETUPパケット:32ビット+EOP(3ビット)
Inter-packet Delay:2ビット
DATA0パケット:85ビット(ビットスタッフは考慮していない)+EOP(3ビット)
Inter-packet Delay:6.5ビット
計 131.5ビット (約87.7us / ロースピード)

本来このリトライはノイズに対処するためのものです。リトライを期待してSETUPを無視してしまうのは、ノイズ耐性を下げることになります。エンジンの設計上再考して欲しい点ですね。

Tsuneo
by Tsuneo (2008-03-26 18:04) 

Tsuneo

DAI さん
>私には、まだまだ念仏のようで、シナプスが反応していません。

普通はこんなレベルまで掘り下げません。USBのスペックは、掘れば掘るほどどこまでもいってしまうので。だから、私が書いている細かい話は無視していただいて実際の使用上なんの支障もありません。
noritan さんが手際よくまとめている部分のみ、見ていてください。

ここはすっかりスタックの再構築という上級編になりつつあるので、入門編をどこかでやっておいた方が良いかと思うんですが。誰かブログで呼んでくれませんかね。対話形式で反応を見ながらでないと今ひとつどこまで話せば良いのか見当がつかなくて。

既存のサンプルをどう加工するかというテーマで、実際のコードをいじりながら、
1) インターラプト、バルク・エンドポイントの扱い方
2) クラスの実装
あたりでどうでしょうか。どのUSB MCUでもOKです。

Tsuneo
by Tsuneo (2008-03-26 18:37) 

noritan

Tsuneoさん、いつもながら、詳しくありがとうございます。

改めて、SETUPトランザクションの部分を読んでみると、かなり厳しいのですね。"retried for up to three times"ということは、全くリトライを行わないという選択肢もあるということでしょうか。
「どうせ残っていたデータは新たなSETUP受信で廃棄されるだけです。」の部分に疑問がありますので、次回記事にまとめます。


DAIさんも、お隣に越してきてはいかがですか?

by noritan (2008-03-26 21:24) 

Tsuneo

>全くリトライを行わないという選択肢もあるということでしょうか。
いや、必ず初回を含めて3回まではリトライします。
"three strikes and you're out" retry mechanism
「三振でアウト」

Tsuneo
by Tsuneo (2008-03-27 00:10) 

masato

Tsuneoさん>誰かブログで呼んでくれませんかね。
これはエキスパートの指導を受けるチャンスですね。

こちらのやり取りを拝見しながら、JB8 のサンプルプログラムで良く判っていなかった部分を勉強しています。
話になかなか付いていけませんが、今回の HID ファームが出来たら改めて自分でも整理してみようと考えてます。
by masato (2008-03-27 18:18) 

DAI

> DAIさんも、お隣に越してきてはいかがですか?

そうですねえ、毎日は無理でも、週一くらいならできるかも。
とにかく、万年初心者ですから。
「秘密の」シリーズで行きましょうか。
by DAI (2008-03-27 21:55) 

DAI

「適当」シリーズの方が良いのかな。
by DAI (2008-03-27 21:57) 

hamayan

> 「適当」シリーズの方が良いのかな。

新たな展開も期待されます(笑)。
by hamayan (2008-03-28 09:01) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。