SSブログ

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

1516092

正しいHIDデバイスを追い求めるシリーズ、四の段です。

電源投入後の状態設定

USBデバイスは、到着するトランザクションに従って、状態を刻々と変化させて処理を行います。 そのため、プログラムは、ソフトウェアで実現したステートマシンとして動作します。 ステートマシンは、電源投入後にある特定の状態に入ります。 この状態を設定するのが、initUSB(void)という関数です。

//----------------------------------------------------------------------------
//  Make a transition to powered but not reset yet.
//----------------------------------------------------------------------------
void initUsb() {
  // set default address
  UADDR         = 0;

  // disable Interrupts
  UIR0  = 0b00000000;
  //        |||||||+-- RXD0IE = 0   disable RXD0 interrupt
  //        ||||||+--- TXD0IE = 0   disable TXD0 interrupt
  //        |||||+---- Reserved (0)
  //        ||||+----- TXD1IE = 0   disable TXD1 interrupt
  //        |||+------ RXD2IE = 0   disable RXD2 interrupt
  //        ||+------- TXD2IE = 0   disable TXD2 interrupt
  //        |+-------- SUSPEND = 0  disable Suspend function
  //        +--------- EOPIE = 0    disable EOP interrupt

  // clear all Flags in UIR1
  UIR2  = 0b11111111;
  //        |||||||+-- RXD0FR = 0   clear RXD0 flag
  //        ||||||+--- TXD0FR = 0   clear TXD0 flag
  //        |||||+---- RESUMEFR = 1 clear RESUME flag
  //        ||||+----- TXD1FR = 1   clear TXD1 flag
  //        |||+------ RXD2FR = 1   clear RXD2 flag
  //        ||+------- TXD2FR = 1   clear TXD2 flag
  //        |+-------- RSTFR = 1    clear bus-reset flag
  //        +--------- EOPFR = 1    clear EOP flag

  // initialize endpoint #0
  UCR0  = 0b00000000;
  //        ||||++++-- TP0SIZ = 0 reset packet size register
  //        |||+------ RX0E = 0   disable EP0 receiver
  //        ||+------- TX0E = 0   disable EP0 transmitter
  //        |+-------- Reserved (0)
  //        +--------- T0SEQ = 0  reset TX sequence bit
  
  // initialize endpoint #1
  UCR1  = 0b00000000;
  //        ||||++++-- TP1SIZ = 0 reset packet size register
  //        |||+------ FRESUM = 0 disable Force RESUME
  //        ||+------- TX1E = 0   disable EP1 transmitter
  //        |+-------- STALL1 = 0 disable Force EP1 STALL
  //        +--------- T1SEQ = 0  reset TX sequence bit

  // initialize endpoint #2
  UCR2  = 0b00000000;
  //        ||||++++-- TP2SIZ = 0 reset packet size register
  //        |||+------ RX2E = 0   disable EP2 receiver
  //        ||+------- TX2E = 0   disable EP2 transmitter
  //        |+-------- STALL2 = 0 disable Force EP2 STALL
  //        +--------- T2SEQ = 0  reset TX sequence bit

  // initialize others
  UCR3  = 0b01000100;
  //        |||||||+-- ENABLE1 = 0  disable EP1
  //        ||||||+--- ENABLE2 = 0  disable EP2
  //        |||||+---- PULLEN = 1   enable pull-up at D-
  //        ||||+----- Reserved (0)
  //        |||+------ ISTALL0 = 0  disable EP0 Force STALL for IN
  //        ||+------- OSTALL0 = 0  disable EP0 Force STALL for OUT
  //        |+-------- TX1STR = 1   clear TX1ST flag
  //        +--------- Reserved (0)

  // initialize force bits.
  UCR4  = 0b00000000;
  //        |||||||+-- FDM = 0    disable Force D- port
  //        ||||||+--- FDP = 0    disable Force D+ port
  //        |||||+---- FUSBO = 0  disable Force USB output
  //        +++++----- Reserved (00000)

  // make a transition	
  usbState      = US_POWERED;   // powered, but not yet reset

  UADDR_USBEN   = 1;            // enable USB interface
}

初期設定関数は、これまでのサンプル・プログラム例では、main(void)関数から呼び出されていました。 USB関連のレジスタを初期設定したら、ステートマシンの状態をUS_POWEREDに設定します。

次に発生するイベントは、「bus-resetイベントによる割り込み」です。 bus-resetイベントが発生すると、CONFIGレジスタのURSTDビット(USB Reset Disable)の状態によって異なる動作を行います。 URSTDがリセットされている場合、bus-resetイベントによりマイコンはリセットされます。 また、URSTDがセットされている場合、bus-resetイベントによるマイコンのリセットが禁止されているので、代わりにUSB割り込みが発生します。 bus-resetによるリセットおよび割り込みの両方を禁止する方法は無いようです。

USB割り込みハンドラを整備する

MC908JB16マイコンは、USBバス上のイベントやトランザクションの終了ごとに割り込みが発生します。 このため、プログラムは、割り込み発生により状態遷移を繰り返すステート・マシンとして実装されることになります。

//----------------------------------------------------------------------------
// USB Interrupt Handler
// All Interrupt Sources of the JB16's integrated USB peripheral
// will be treated by this ISR
//----------------------------------------------------------------------------
void interrupt VectorNumber_USB isrUSB() {
    :
}

このハンドラの中をうめていきます。 また、長くなりそうだ。

バス・リセットの検出

マイコンがbus-resetイベントを検出したら、割り込みが発生します。 割り込みハンドラの中には、このイベントを検出するディスパッチャを入れておきます。

  if (UIR1_RSTF) {
    // USB Reset Signal State detected
    resetUsb();                 // call bus-reset handler
  }

このバス・リセットというイベントは、発生する頻度が低いので、サンプル・プログラムでは、 一番後ろにディスパッチャが配置されています。

bus-resetイベントに対する処理は、以下の通りです。

//----------------------------------------------------------------------------
//  USB bus-reset handler
//----------------------------------------------------------------------------
void resetUsb(void) {
  initUsb();                    // Soft Reset of USB Systems
  
  // INT enabled for: end of packet
  UIR0  = 0b00000011;
  //        |||||||+-- RXD0IE = 1   enable RXD0 interrupt
  //        ||||||+--- TXD0IE = 1   enable TXD0 interrupt
  //        |||||+---- Reserved (0)
  //        ||||+----- TXD1IE = 0   disable TXD1 interrupt
  //        |||+------ RXD2IE = 0   disable RXD2 interrupt
  //        ||+------- TXD2IE = 0   disable TXD2 interrupt
  //        |+-------- SUSPEND = 0  disable Suspend function
  //        +--------- EOPIE = 0    disable EOP interrupt

  
  // enable EP0 receiver only.
  UCR0_RX0E     = 1;            // EP0 Receive Enable

  // make a transition
  usbState      = US_DEFAULT;   // Device is powered and reset
}

resetUsb()からは、initUsb()を呼び出して確実に初期化を行います。 そして、次に発生するイベントに対応する設定を行います。 次は、SET_ADDRESSリクエストが来るはずなので、EP0受信機をイネーブルし、さらにUS_DEFAULT状態に遷移します。

サンプル・プログラムでは、この段階ですべての割り込みを有効にしていました。 もちろん、各エンド・ポイントを有効にしなければ、割り込みが発生することはありません。 ひとつだけ不明なのは、End Of Packet (EOP)の扱いです。 ここで、有効にすべきか否か。 そもそも、サンプル・プログラムでは、まともに処理がされていなかったイベントです。

ひとまず、ここまで。

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

参考文献

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

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

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

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

nice! 0

コメント 5

Tsuneo

頼もしい。では、しっかり話をすることにしましょう。

デバイス・ステートの変遷は、USB2.0 スペックのこのページにまとめられています。
Figure 9-1. Device State Diagram (usb_20.pdf p240)

前回この一部(Default - Address - Configured ステート)を見ましたが、今度は、その前の部分を見てみましょう。

Attached ステート
| <--- D- (D+) ラインのプルアップ抵抗
Powered ステート
| <--- バスリセット
Default ステート

USB2.0スペックはAttached とかPowered に関しても具体的にどのタイミングを指すのか用語が一貫していません。実際上、最も重要なのはプルアップ抵抗の接続の有無なので、そこで分けるのが通説となっています。本来ならば、Powered ステートの後にConnected ステートを充てるべきなのでしょうね。

そうすると、
Attached ステート
- デバイス側のUSB コネクターがハブやホストのソケットに、単に挿された状態
- VBUS ラインから5Vは供給されている
Powered ステート
- デバイスの接続がハブ(ルートハブ)によって認識された状態

プルアップ抵抗が何故重要かというと、ハブ(ルートハブ)がバスの D-とD+ラインの電圧を監視して、デバイスの接続を検出するからです。この電圧はデバイス側でD- (ロースピード)または D+(フルスピード)ラインにプルアップ抵抗を接続することで変化します。この MCU のデータシートでは、このページの D- ラインの抵抗R1 がそれですね。

11.7.1 Voltage Regulator
Figure 11-10. Regulator Electrical Connections (MC68HC908JB16.pdf, p177)

このプルアップ抵抗はこのUSBエンジンではチップ上にのっていて、このビットでエネーブルにします。
PULLEN (USB Control Register 3)
つまり、これをエネーブルにしないと、Powered ステートにはなりません。

USBプラグの接続からバスリセットまでのタイミングは、USB2.0スペックのこの図にのっています。
Figure 7-29. Power-on and Connection Events Timing (usb_20.pdf p150)

この図はフルスピードデバイスの例なので、D+ラインがプルアップ抵抗でΔT2 の後に立ち上がっています。その直後の波はチャッタリングのつもりでしょう。その後、バスリセットでΔT5 の期間D+ラインが0に落ちています。

この図で見るべきことは、
1) プラグ接続後、100ms以内にプルアップ抵抗をエネーブルにしなければならない(ΔT2)。
USBのロゴを正式に取るためコンプライアンステストに通すのであれば、遵守すべきスペックです。後で述べますが、バスパワーデバイスではそこそこきつい制約です。しかし、ほとんどの場合ハブはプラグ接続をメカニカルに検出していませんから、このΔT2 が問題になることは実際はありません。USB を単なる電源として使っている USB扇風機なんてのがまじめにやっているはずもありません。

2) プルアップ抵抗をエネーブルにした後、最小100 ms後にはバスリセットがハブ(ルートハブ)から送られてくる(ΔT3)。そして、エニュメレーションが開始する。

つまりまじめにスペックを遵守すれば、バスパワーデバイスでは電源投入(USBプラグの接続)後 200ms そこそこでエニュメレーションが始まってしまうわけです。USB ハードディスクなどデバイスによっては、まだ電源投入後のイニシャライゼーションをやっている最中ということも往々にしてあります。こういったデバイスでは、USBサポート部分だけを先に立ち上げてホストの相手をさせておきます。エニュメレーションはさっさと済ませて、上位のプロトコル(例えばマスストレージではTest Unit Ready)を使ってまだ準備中だよ、とホストに教えます。

バスパワーデバイスで、別にコンプライアンステストはどうでもいいよという場合は、なるべくプルアップ抵抗をエネーブルするタイミングを遅らせてください。つまり、デバイス全体のイニシャライゼーションの最期に持って来るのがよいでしょう。そうすれば、イニシャライゼーションの最中にエニュメレーションが始まって失敗することがなくなります。この失敗例はあちこちのUSB関係のフォーラムで何度か見かけました。例えば、いずれもプルアップ抵抗が外付けのMCUで、これが常時エネーブルになっていた場合でしたが、
- 開発途中までは順調に動いていたのに、巨大な配列を加えた途端にエニュメレーションできなくなった。
- 接続するPCによってエニュメレーションできたりできなかったりする。
といった形で出現します。いずれもイニシャライゼーションが長引いてエニュメレーションに間に合っていませんでした。本来ならプルアップ抵抗をファームウェアで制御するよう回路を変更すべきですが、次善の策として、main()内で行っていたPLLの設定をスタートアップに移し、電源オン直後からシステムクロックを高速化してイニシャライゼーションを時間的に短縮することで解決しました。


プルアップ抵抗が重要なもう一つの理由は、これを使ってデバイス側からバスリセットを引き起こすことができる点です。プルアップ抵抗をディぜーブルし、ホストのデバイスドライバーの処理を見込んで 100ms ほど後に再度エネーブルにします。ハブは一度デバイスのコネクションが落ちたと判断し、プルアップ抵抗のエネーブル後、バスリセットからエニュメレーションへと進みます。これは、USBでのファームウェアアップデート後、ホストに再度エニュメレーションをやらせる常套手段です。

Tsuneo
by Tsuneo (2008-03-23 10:14) 

noritan

良くわかりました。

もし、"Attached"と"Default"の間に時間のかかるシステムの(USB以外の部分の)初期化を入れるつもりなら、"PULLEN"のイネーブルをなるべく遅らせて"Attached"を長引かせた方が良いということですね。ただし、ナンチャッテUSBの場合には。

このマイコンの場合は、制御可能なプルアップ抵抗を内蔵しているので、何とかなりますが、外付けだとUSBシステムを優先して立ち上げなくてはならない。その点、USB-シリアル・コンバータという独立したハードウェアを使うと、メイン・システムに関わらず、USBシステムは勝手に立ち上がるので便利、ということでしょうか。


私が使っている、MC908JB16のSOICパッケージ版では、PLLが使えないようになっています。別のパッケージでPLLを使おうとすると、PLL Lockだけで数十msec必要なので、忙しくなりそうです。この場合、エニュメレーションのリクエストに対して"NAK"を返すだけの設定にしておくと、時間稼ぎができないでしょうか。

あるいは、PLLがLockしたら、改めて、デバイス側からバス・リセットを発行して、エニュメレーションを開始させるというのは、どうでしょうか。プルアップをはずしたとたんに、VBUS電源を切られたら、ひとたまりもありませんが。

by noritan (2008-03-23 11:21) 

Tsuneo

>その点、USB-シリアル・コンバータという独立したハードウェアを使うと、メイン・システムに関わらず、USBシステムは勝手に立ち上がるので便利

なるほどその通りですね。そういう形でUSB-シリアル・コンバータを考えたことが無かったので良い着眼点を教えていただきました。USB-ATA、USB-SATAブリッジチップなどもこのくくりに含められるでしょう。

CypressのEZ-USB SXは、インテリジェントUSBペリフェラルといった感じのチップですが、まさにこの独立したハードウェアを体現したチップです。外部からデスクリプタを与えるだけで、エニュメレーションなど基本的なUSBのハンドリングは何のコーディングも無しにやってくれます。FPGAやDSPなどスタートアップに時間がかかるデバイスにUSB接続を付加するのに使われます。


>この場合、エニュメレーションのリクエストに対して"NAK"を返すだけの設定にしておくと、時間稼ぎができないでしょうか。

多少はできますよ。50msほどですが。
まず、USB2.0スペックは、ホストからのリクエストに対するデバイスの反応時間を、大枠として5秒としています。
9.2.6.1 Request Processing Timing (usb_20.pdf p246)

で安心していると、標準デバイスリクエストについてはもっと厳しい基準を持ってきます。かなり細かい基準なので、詳細は下記の章を読んでください。Set_Address(データステージの無い標準デバイスリクエスト)の50ms が最も厳しい部分ですね。
9.2.6.4 Standard Device Requests (usb_20.pdf p246)


このD+/D-プルアップ抵抗に関するΔT2 のスペックは前に書いたように、USBの正式なロゴマーク(http://www.usb.org/home に出ているマーク)がマーケッティング戦略上自社の製品に是非欲しいという会社の方以外、それほど拘らなくても良いと思います。正式なロゴマークの無い製品は、全部といってはなんですがかなりの部分このスペックに関しては「なんちゃって」USBです。Cypressなどアプリケーションノートで堂々とプルアップ抵抗のタイミングを遅らせろ、なんて書いてますし。


ただ、USBの開発ボードを名うっているにもかかわらず、ファームウェアからon/offできない固定のD+/D-プルアップ抵抗を載せているような市販のボードは、素人さんの設計とせせら笑ってやって下さい。それについてくるファームウェアのサンプルもそれなりと。
例えばKEILのMCB2140ボードとか :-)
この回路図でD+ラインのR35ですね。
http://www.keil.com/mcb2140/mcb2140-schematics.pdf

Tsuneo
by Tsuneo (2008-03-23 16:54) 

Tsuneo

> ひとつだけ不明なのは、End Of Packet (EOP)の扱いです。

このMCUのデータシートのここに、どう使うか簡単に書いてありますね。
11.5.3 Suspend (MC68HC908JB16.pdf p173)
Firmware should monitor the EOPF flag and enter suspend mode by
setting the SUSPND bit if an EOP is not detected for 3ms.

EOPは、本来各パケットの最後にターミネータとして付加されるものです。
しかし、ここで言うEOPは、特定の目的に独立して使われる場合を指します。

デバイスは、直接接続しているUSBバス(アップストリームポートと呼びます)で 3ms の間バスアクティビティがないと、サスペンド・ステータスに移行しなければなりません。フルスピード、ハイスピードではSOF (Start Of Frame)パケットが1msのUSBフレーム毎に流れていて、SOFを止めない限りデバイスがサスペンドすることはありません。ロースピードではSOFの替わりにkeep-aliveと呼ばれる独立したEOPがハブから流されます。このEOPはUSBフレーム毎に最低1つはハブから流されることになっています。

サスペンド、レジュームについては、また後ほど。

Tsuneo
by Tsuneo (2008-03-23 23:26) 

noritan

1) エニュメレーションの時間稼ぎのお話
使用を遵守しようとすると、時間的にかなり厳しい規格なのですね。どこまで、妥協するかだな。

2) サスペンドのお話
チラッとSUSPEND状態の要件を見ました。消費電流500uAですか。これはまた厳しい。マイコンの場合には、止まらざるを得ない電流値ですね。しかも、EOPの時間切れを認識するためにタイマを用意しなくちゃならないし。だんだん、オペレーティング・システムを作りたくなってきたぞ。
で、また、RESUMEしてから最初のパケットが到着するまでの時間に規定があるんですよね。RESUMEの部分は、まだ仕様書も見ておりません。

by noritan (2008-03-24 22:56) 

コメントを書く

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

トラックバック 0

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

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