マイコン工作実験日記

Microcontroller を用いての工作、実験記録

UVCサンプルを動かす

2016-10-20 22:14:20 | USB
GitHubで見つけたSTM32用UVCの試験ブログラムからコードを引っ張ってきて使ってみたら、わりとアッサリと動いてくれました。もともとのコードはグレーから白に全画面の色が変化するだけなので、いまひとつ動いている実感に欠けます。そこで、XMOSのサンプルのようにRGBのカラーバーを表示して、スクロールさせてみました。







表示にはWindows 10のカメラアプリを使用。ソフトでは160×120の画面情報を送っているのですがカメラアプリが自動的に拡大表示してくれているようです。

Ubuntu上でもcheeseを使って同じように表示できることを確認できたのですが、Mac上でPhoto Boothを使って画像を表示しようとすると、Photo Boothが例外エラーで止まってしまいます。どうして!? WIndowsはMacの上の仮想マシンで動いているのに。。。

画面情報はIsochronous転送で1ms毎に 1ラスタ分のデータを送信していますので、120ラスタから成る1画面を送るには120msかかることになり、8.3FPSに相当します。1ラスタ分のデータを送信するペイロードは、ヘッダ2バイトとYUVデータ320バイトの合計322バイト長になります。エンドポイントのwMaxPacketSIzeを倍にして、1度に2ラスタ分を送ろうと試みたのですが、そうするとカメラを認識してくれません。どうやらwMaxPacketSizeが512バイトを超えるとカメラとして動いてくれないようです。Mac OSX(or WIndows?)のUSBバンド幅の管理制御によって制限を受けているのでしょうか??

UVCサンプルを探してみた

2016-10-18 21:42:13 | USB
Nucleo-L476RGにUSBミニコネクタを繋げたので、これだけでハードの準備は完了。次はソフトの準備に取り掛かります。カメラ画像をUSBで送るには、STM32L476でUVC (USB Video Class)のデバイス機能を実現すれば良いわけですが、CubeMX並びにSTのUSBドライバではUVCクラスのドライバは提供されていません。USB Coreの上に自分でクラスドライバを作ればいいのですが、正しく実装するためにはデバイスディスクリプタから正しく用意してやることが必要となります。

ディスクリプタについてはUVCの仕様書でもサンプルが説明されていたりしますが、簡単に実装して動作試験するためには低機能でも構わないのでできるだけシンプルな方が好都合です。そこで、マイコンを使ったUVCについての資料を探してみようと考えググってみると、XMOSのAPノートが見つかりました。480×320の単一解像度しか持たないUVCデバイスをカメラ無しで擬似的に実現する内容となっています。ハードとしてカメラを使わないので、カラーバーを表示することしかできないというデモですが、実験としては十分に面白いので、このアイデアはそのままいただくことにします。ソースも提供されており、ディスクリプタの具体的な内容もよく分かり助かります。また、ドライバのコードの作りとしては、エンドポイント毎にスレッドを動かす構成となっているようで、このあたりがXMOSらしくて興味をそそられるところなのですが、深入りすると戻ってこれなくなりそうな予感もするのでこれ以上の追求はやめておきます。

次に、GitHubでSTM32を使ったプロジェクトを探してみました。



iliasamさんのプロジェクトは白黒画像データをPCに送るコードが用意されていますが、古いライブラリを使っているようです。coelamonさんのpractice_eclはiliasamさんのコードがベースになっているようですが。現在のHALを使っているのでそのまま流用できそうす。そこで、コイツを拝借することにしました。

OHCIでのアイソクロナス転送 - その2

2009-06-09 23:53:41 | USB
OHCI USBホストを使ってつないだUSBスピーカから、MP3再生ができるようになりました。すでに記事にしたとおりの簡略化した設計方針に基づいて実装したのですが、実際に音が出るまでに、2つの点でつまずいていました。

まずひとつめは、TDの設定。TDの内容が正しく設定されっていないと、OHCIはフレームの送受信をおこなってくれないのです。わかりやすいのはフレーム番号のフィールドで、現在のフレーム番号近くの値になっていないと、OHCIはすぐには送受信を開始してくれません。今回は、TDを設定する際に10ms後に送信するようにフレーム番号を設定したのですが、ちっとも送信してくれませんでした。



仕様書を読み直してわかったのは、CC (Condition Code)の必要性。この部分には転送結果が入るのですが、転送開始前にはまだHCに参照されていないことを示す値Not Accessedを設定しておく必要があったのでした。そして、さらにはCCの設定はTDの先頭部分だけではなく、PSW(Packet Status Word)の部分にも隠れているのが罠です。Offset/PSWの部分は、転送前には転送に使用するバッファの先頭へのオフセットを保持し、転送が終了するとホストコントローラ(HC)により転送結果を示すPSWに書き換えられます。



PSWは上図のようにCCとSIZEの部分からなるのですが、「このCCの部分がNot Accessedになっている場合に、コントローラはデータをOffsetとして解釈する」と規定されていたのです。この説明がOffsetの説明部分ではなくPSWの説明部分に出てくるというのが見落としてしまった原因です。

CCの設定もようやくとクリアしたのですが、まだ転送を開始してくれませんでした。2つめの問題個所はOHCIのレジスタのひとつ、HcPeriodicStartの設定値でした。このレジスタは1msのUSBフレームのうち、コントロールならびにアイソクロナス転送を開始するタイミングを指定するレジスタです。OHCIの仕様書には、「普通は3E67hを設定する」みたいな説明があるので、素直なわたしはそのとおりの値を設定していたのですが、これが罠でした。

USBは12MHzのクロックで動作するので、タイミングも12MHzのクロック数で数えます。1ms周期は12,000となり、16進数で表せば2EE0hです。コントローラは1ms周期が始まるとこのクロック数をカウントダウンしてゆき、HcPeriodicStartと一致するとインタラプト/アイソクロナス転送の処理を開始します。HcPeriodicStartとして3E67hを指定していては、1msよりも大きな値になっているので、いつまで待っても送信開始タイミングになりません!! つまり、OHCIの仕様書の明らかな間違いですね。試しに2000hを指定すると、無事に転送できるようになりました。

自分でも、典型的なハマリ易い罠に見事にはまってしまったと思います。どうにかこの罠から自力で這い出せて、喜んでいるところです。以下、再生実行の画面です。



host setupコマンドを実行することで、USB Hostの初期化処理をおこない、さらにUSBスピーカの音量設定とインターフェースの切り替えまでを実施しています。uplayコマンドによりSDカード上のファイル名(の最初の2文字)を指定して選曲するとともに、その再生を開始してUSBスピーカへ出力しています。

OHCIでのアイソクロナス転送

2009-06-04 00:23:27 | USB
OHCIの動き方/使い方の概要が理解できたので、USBスピーカを使うために必要なアイソクロナス転送をサポートするための、実装方式について考えました。OHCI上でのアイソクロナス転送は、周期的に実行される転送という観点からは1ms毎に実行されるインタラプト転送と同じ扱いになっていますが、使用するTD (Transfer Descriptor)の形式が他の転送方式とは違っています。

選定したUSBスピーカは、なにやらHIDデバイス機能も持った複合デバイスとなっていますが、今回のプロジェクトではスピーカのオーディオ出力機能だけを使うことにします。このように簡単化してしまえば、USBホストでサポートするのはコントロール転送と、オーディオPCM出力のためのアイソクロナス転送だけになります。よって、インタラプト転送はサポートしないことにしてしまいます。コントロール転送は、もともとのUSB Host Liteのものを流用していますので、新規に追加するのはOUT方向のアイソクロナス転送だけということになり、周期的転送処理のためのリスト構造を次の図のように単純化かつ固定化してしまうことにします。



OHCIではHCCA(Host Controller Communication Area)のInterrupt Tableにおいて、32ms分の周期的実行に実行する転送を表現できる仕組みになっています。アイソクロナス転送は1ms間隔で実行する必要がありますので、このテーブルには全て同一のED(End point Descriptor)を設定してやります。こうすることで、EDが指定しているエンドポイント(すなわちスピーカ出力)に対しての転送が毎回発生することになります。EDのFフラグはリンクされるTDのフォーマットがアイソクロナス転送用のものであることを示し、このEDには4つのTDをリンクさせます。ひとつのTDでは、下図のように8つの送信バッファを指定することができるので、8ms分の転送を表現することができます。このTDを4つ使うことで32ms分の転送を表現しようというわけです。


TDのには8つのオフセットフィールドがあり、この情報で8回の転送で使用するバッファの先頭アドレスからのオフセットを指定します。Starting Frameは転送開始する時刻を指定し、指定されたFrame番号に到達すると指定バッファアドレスからの転送が実行されます。実際の転送が行われる時点で、必要なTDとバッファが用意されていればいいので、実際には32回分の転送に相当するTDとバッファを全てあらかじめ用意しておく必要はないのですが、LPC2388にはUSB転送用のメモリ領域が16KBも用意されているので、32ms分をすべて用意することにしておきます。

このようにして、TDが示すバッファに再生すべきPCMデータを入れてやれば、あとはOHCIが指定のタイミングで自動的にデータを送出してTDのリストから外してくれます。リストから外れたTDはHCCAのDoneHeadが示すリストに付け替えられるので、これを割り込みで検出し、新たなPCMデータを示すTDを作成してEDからのリストに追加してやれば、順次再生データの送出ができることになります。

1ms毎に送信されるPCMデータの量は48KHzサンプリングの場合には、次のようになります。
1ms毎のサンプル数: 48,000 samples/sec = 48samples/msec
ステレオなので:    48 samples * 2 channel = 96samples
1サンプルは16bitなので: 2byte * 96 = 192byte
これを32ms分用意したとしても、192 * 32 = 6144 = 6KBなので、メモリは余裕十分です。このように、48KHzの時には毎回192バイトづつ転送すればいいのですが、44.1KHzサンプリングの場合には、1msでは176.4バイトという半端な数字になってしまいます。しかたがないので、10ms分のデータのうち9ms分は176バイト送り、残りの1ms分は180バイト送ってやることにします。

音量設定とインタフェースの活性化

2009-05-28 23:23:36 | USB
USBスピーカのディスクリプタが読めたので、その情報に基づいて音量設定とPCM出力インタフェースの活性化を行います。本来であれば、ディスクリプタをきちんと解析/解釈すれば、どんなUSBスピーカにも対応することが可能なのですが、試験に使えるスピーカがいくつもあるわけではないし、購入したSONYのスピーカに対応することしか考えません。読み出したディスクリプタからわかったインタフェース番号やエンドポイント番号を半ば決め打ちでコード書いちゃっています。

音量やミュートの制御をおこなうには、Audio Control Interfaceを使いますが、そのインタフェース番号は0番であることが、ディスクリプタからわかっています。そこで、まずはこのインタフェースから現在の音量/ミュート設定を読み出してやります。
> host
Initializing Host Stack
Host Initialized
Connect a Audio device
Port = 1
I  04 00 00 00 01 01 00 00
AI 24 01 00 01 28 00 01 01
AI 24 02 01 01 01 00 02 03 00 00 00
AI 24 03 06 01 03 00 09 00
AI 24 06 09 01 01 01 02 02 00
I  04 01 00 00 01 02 00 00
I  04 01 01 01 01 02 00 00
AI 24 01 01 01 01 00
AI 24 02 01 02 02 10 02 80 bb 00 44 ac 00
E  05 01 09 c8 00 01 00 00
AE 25 01 01 01 01 00
I  04 02 00 01 03 00 00 00
AD 21 00 01 00 01 22 32 00
E  05 83 03 04 00 20
IF #0, alt 0 is audio control.
IF #1, alt 0 is audio streaming.
IF #1, alt 1 is audio streaming.
-- Endpoint 1-OUT (200) is type ISOCHRONOUS
IF #2, alt 0 is HID.
-- Endpoint 3-IN (4) is type INTERRUPT
Audio device connected
> host muteget 0
0
> host volget 1
CUR: 65472 (c0 ff) MIN: 58272 (a0 e3) MAX: 65520 (f0 ff)
> host volget 2
CUR: 65472 (c0 ff) MIN: 58272 (a0 e3) MAX: 65520 (f0 ff)
>
引数の0, 1, 2はチャネル番号を表し、マスター、左、右に対応します。ミュートはオフ状態ですので、改めて設定する必要もないようです。音量はCUR, MIN, MAXの3つの値を取得して表示しています。CURが現在の設定値ですが、上記のように電源が入った直後では最大値に近い値に設定されています。ちょっと、どうかしらと思う動作仕様です。そこで、次のように設定して音量を下げてやります。マスターを操作すると左右の両方に影響を与えるのですが、音量設定ではマスターがサポートされていないようなので、左右別々に設定してやります。
> host volset 1 59000
> host volset 2 59000
> host volget 1
CUR: 58944 (40 e6) MIN: 58272 (a0 e3) MAX: 65520 (f0 ff)
> host volget 2
CUR: 58944 (40 e6) MIN: 58272 (a0 e3) MAX: 65520 (f0 ff)
>
実際に設定できた値が指定した値と異なっていますが、これはスピーカ側で設定できる値の解像度に制約があり、丸められた値しか設定できないためです。

現在のオーディオ出力はalt #0 になっており、PCM出力のエンドポイントと関連づけられていません。そこで、alt #1に変更してやります。
> host ifget 1
0
> host ifset 1 1
> host ifget 1
1
>
これでスピーカ側ではPCM出力動作が開始されているハズ。あとはアイソクロナス転送でPCMデータを送りつけてやれば、スピーカが鳴ってくれるでしょう。

上記の設定変更は、全てUSBのEP0を使うコントロール転送で制御しています。コントロール転送での書き込みや読み出しの基本ルーチンはUSB Host Liteで用意されているので、これを使っているのですが、当初正しく書き出し読み出しができなくて悩みました。音量を設定変更したつもりでも、読み出してみるとちっとも値が変わっていなかったのです。
USB_INT32S Host_CtrlRecv(USB_INT08U bm_request_type,
                         USB_INT08U b_request,
                         USB_INT16U w_value,
                         USB_INT16U w_index,
                         USB_INT16U w_length,
                volatile USB_INT08U *buffer)

USB_INT32S Host_CtrlSend(USB_INT08U bm_request_type,
                         USB_INT08U b_request,
                         USB_INT16U w_value,
                         USB_INT16U w_index,
                         USB_INT16U w_length,
                volatile USB_INT08U *buffer)

使っているのは上記ふたつの関数で、引数bufferで読み書きするデータを保持するバッファを指定します。ところが、USB Host Liteのコードを調べれてみると、これらふたつの関数の実装では引数bufferは全く参照されておらず(!!)、決め打ちのバッファを使うようになっていたのです。こんなのアリ?! サンプル以外の用途には簡単には流用できないようにワザと罠をしかけてあったんでしょうか。

USB Host Liteを使ってみようとお考えの方は、ご注意ください。ちなみに、インターフェース6月号記事のサンプルもこういうコードになっていますが、筆者の方の落ち度ではなく、もともとNXPが配布しているUSB Liteのコードからして、こうなっています。

ディスクリプタの読み込み

2009-05-26 00:39:29 | USB
LPC2388のUSBホスト機能を使って、USBスピーカのディスクリプタの読み込みと簡単な解析を試してみました。



NXPの提供するUSB LiteのサンプルはMass Storage Device用にできていますが、Enumerationの手順は同じですから、基本ロジックはそのまま拝借。Config Descriptorを読み込んだところで、その解析だけAudio Device用に書き換えてみたようなものです。もうひとつの変更点は、割り込み待ちの処理。USB Liteは割り込みは使っているものの、RTOSは必要としないように作られています。そのため、ディスクリプタの読み込みに使われるControl転送が終了するのを、フラグ変数のポーリングによって待つようになっていました。これでは他のタスクが動かないので、TOPPERS/JSPのSemaphore機能を使って待ち合わせるように変更してあります。

Configurationの設定まで動きましたので、すでにデバイス自体は有効になっているのですが、実際にスピーカを鳴らすには
  1. 必要であれば、ミュートのオフ、再生ボリュームの設定
  2. エンドポイントを持たないインタフェース(IF #1, alt 0)から、実際にPCMを出力できるエンドポイントを持つインタフェース(IF #1, alt 1)への切り替え。
  3. 再生するPCMデータの送出開始
という手順がまだ必要です。

アクセス速度の改善

2009-03-14 19:39:15 | USB
Mass Storageでのアクセス速度が遅かったので、改善を試みてみました。

SDへの書き込みが遅い原因としては、前回も予想したように書き込み時のACMD23を使っていないことが考えられます。一方、SDからの読み込みも満足できる数字ではないので、これも改善したいところです。こちらの方は、USBのコードを確認したところ、すぐに原因に思いあたりました。なんと、FIFOのPING-PONG機能を使っていませんでした。PING-PONG機能とはダブルバッファリング機能のことで、USBエンジンのFIFOに書き出した送信データをUSBホスト側に送信している間に、次の送信データをあらかじめもうひとつのFIFOに貯め込んでおける機能のことです。

上記2点を改善したところ12MBのファイルで

READ: 約15秒 (800KB/sec)
WRITE: 約35秒 (342KB/sec)

というところまでは改善できました。およそ倍ですね。SDとのI/Oは8ブロック(4KB)単位でおこなっていますが、Windows XPからは128ブロック(64KB)単位でのI/O要求がきているようです。したがってマイコン内の処理単位も大きくしてやればさらなる改善も可能かと思われますが、32KBしかないRAMをこれ以上割くのはもったいないので、こんなところで我慢することにしようかと思います。READの方は800KB * 8 = 6.4Mbpsですから、USBフルスピード12Mbpsの半分は出ていることになります。

気がつくと、なんだかんだで1か月くらい複合デバイスやっていたので、いい加減何か違うことやりたくなってきました。

複合デバイスは動いたが

2009-03-08 22:01:55 | USB
CDC+MSCの複合デバイスは、SD R/Wを修正することでどうにかちゃんと動くようになりました。デバイスマネージャでの表示は、下図のようになりました。



CDC経由のシリアルコンソールを使ってタスクを表示すると、こんな↓感じです。msd_taskがMass Storage Deviceの処理をおこなうために追加したタスクです。usb_taskが受信したパケットのクラスを判別して、MSCの場合には msd_taskに、CDCの場合には console_taskに処理を引き継ぐようにしています。



Mass Strorageの方だけを Windowsの「ハードウェアの安全な取り外し」操作で取り外しても、残ったCDCの方のシリアルコンソール機能はちゃんと動いてくれています。このように複合デバイスとして動かすことには一応成功したのですが、MSDとしてのアクセス速度が遅いです。12MBほどのファイルのコピーで実験してみると
   Read:  30秒弱  (400KB/sec)
   Write: 約60秒  (200KB/sec)

という悲惨な成績。最初は512バイトSD/USBから読み出してはUSB/SDに書き出すという1ブロック単位のR/Wだったのでもっと遅かったのですが、8ブロック毎のR/Wにしたところ、現在の数字になっています。特に、書き込みの遅さは耐えられない感じがします。ATMELのライブラリのコードでは、ACMD4323による書き込みブロック数の設定をおこなっていないので、書き込み前の消去に余分な時間を費やしているのかもしれません。このあたりの変更から始めて、改善を試みようかと思います。


残り容量間違いの原因

2009-03-01 22:48:49 | USB
USB Mass Strorageでディスクの残り容量が間違っている原因がわかりました。MSCに関連するUSBの処理の方を中心に見ていましたが、原因はSDカードからの読み取りの方にありました。いままで短いテキストファイルを読み出す程度の試験しかしていなかったので気づかずにいましたが、ATMELのライブラリコードがちょっとおかしいようです。

まずは、問題の症状から。参考にしたATMELのライブライのコードでは、SD/MMCからの読み出しにCmd18 (Read Multiple Block)を使っています。例えば、ブロック#200から2ブロックを連続して読みだすと、合計1024バイトを連続して読みだします。



ところが、この2ブロックを1ブロックづつに分けて読み出すと。。。



なんと、2ブロック目であるブロック#201の最初の部分が読めていないではありませんか。そこで、もう一度#201を読み出してみると、



今度はちゃんと読めています。こんな怪しい動きをしていたのが影響してちゃんとFATを読めていなかったようです。

トレースからもわかるように、連続したブロックを2度に分けて読み出した時には、新たなコマンドは投入されていません。Cmd12を入れるまではCmd18が生きていますので、ライブラリの設計者は最初のブロックの読み出し終了時にいったん止めておいたクロックを再投入してやれば、読み出しの続きができるだろうと考えたようです。ところが、実際には最初のブロックの読み出し終了後、SDカードにはクロックがいくつか送られてしまいデータが読み捨てられてしまっているのではないかと思われます。

ここは素直に、一度に複数ブロックを読むのであればCmd18を、1ブロックしか読まないのであれば、Cmd17を使うようにしてやれば問題は修正できるでしょう。ソースを読むと、書き込みも同じ処理になっているので、こちらも修正が必要なようです。FatFsを使って音声を録音した際にも同じ問題が発生していたのかもしれませんが、クラスタ単位で連続して書きこんでいるために音声のとびには気が付きにくかったのかもしれません。




ディスクは見えてきたけれど

2009-02-26 07:47:08 | USB
ひき続き、USB Mass Storageをやっています。Read Capacity, Mode Sense(6), Read(10), Request Sense, Test Unit ReadyとSCSIコマンドを追加実装してやることで、めでたくWindows XPがMBR, FATそしてルートディレクトリを読みにくるようになりました。

XP上のマイコンピュータからもディスクを見ることができます。ディスクを開けば、ファイルのアイコンも表示できます。ところが、プロパティの表示がおかしい。こんなにディスク使っていないはず。



確認のために、Let's NoteにSDカードを挿してみると、やはり使用領域サイズが間違っています。ディスクの容量は一致していますから、Read Format CapacitiesやRead Capacityコマンドは正しく機能していると判断していいでしょう。使用領域サイズが正しくないということはFATが正しく読みこめていないということでしょうね、きっと。ディレクトリは一応読めているからこそ、ディスク内容アイコンも表示されているハズなのですが。。



READ(10)コマンドの処理自体は、SDカードから読みだしたブロックデータをUSBのBULKエンドポイントを使って送信しているだけで比較的簡単な処理です。そうすると、SDのデータがちゃんと読めていないのか、それともUSBにちゃんと送信できていないのかのどちらか(あるいは両方?)ということになりそうです。