マイコン工作実験日記

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

USBスピーカからのFM再生

2009-06-30 23:10:12 | MP3プレーヤ
FMチューナからの音声出力をCodec経由でI2Sで受けて、USBスピーカから出力できるようにしました。



USBスピーカへの出力はすでにpcmsender_taskとして用意してあるので、新たにI2S入力から受信したFM音声データをpcmsender_taskに渡すタスク(tuner_task)を作ってやっただけです。



player usbで出力先としてUSBスピーカを選択しておき、fm startコマンドにより、tuner_taskが起動されることでUSBスピーカからの出力ができます。pcmsender_taskは I2Sへの出力もできるので、結果的にI2Sで受けたFM音声データをI2Sへ送り戻すことでヘッドフォンから再生することも可能です。しかしながら、I2S受信も割り込み処理で受けているので、送受ともに割り込みで受けることになります。実際に試してみると、割り込み処理負荷が増えた結果、データのとりこぼしが発生してしまいます。割り込みでFIFOのデータを送受するのではなく、DMAを使ってデータの送受をした方が良さそうです。

また、現在はtuner_taskはplayer_taskの配下にはないので、操作を間違えるとtuner_taskとmp3decoder_taskの両方を走らせることもできてしまいます。player_taskで一元的に管理して、操作性も良くするような改善が必要だと考えています。

問題発生

2009-06-28 22:18:32 | Weblog
USBスピーカからFM再生するためのコードを追加して動くようになったので、そろそろ記事を書こうかと思っていたところ、問題が発生してしまいました。

どういうわけかシリアルポートが突然動かなくなってしまったのです。LPC2388基板をPCにつないでも、USBシリアルポートを認識してくれません。USB経由での電源供給はちゃんとできており、LEDが点滅してくれるのでどうやらご本尊のLPC2388は生きているようです。シリアルUSB変換を担当しているCP2102がお亡くなりになったのかもしれません。ちょっと基板を眺めただけでは、接触不良個所があるようにも見えません。

JTAGアクセスも問題無くできたので、ブート後自動的にSDカードのMP3を再生開始するようにソフトを組んでみたところ、ちゃんと動いてくれました。SDもUSBホストもちゃんと動作しているので、やはり問題があるのはシリアルポートだけの可能性が高いのではないかと想像しています。

そうは言っても、やはりシリアルポートが動いてくれないと、メッセージの表示が見えませんし、再生操作に必要なコマンドを叩くこともできません。この状況から抜け出すための方策を検討中です。今のところ、次の3つが候補です。
  1. CN2から出ているRXD1, TXD1のシリアルポートを使うようにソフトを変更し、このポートに秋月のUSBシリアル変換ボードをつなぐ。
  2. USBデバイスポートでCDCを動かして、デバックポートとして使う。
  3. バックナンバーの在庫が豊富な書泉ブックタワーへ行って、5月号をもう一冊買ってくる。
2番目の方策が一番スマートなのですが、ちょっと作業が面倒。1番は手持ちの材料で対応できそうですが、3番が一番安直な方策でもあります。さぁ、どうしようかなぁ。

6/29 追記 本件解決しました。コメント欄で説明しました。

AT91SAM9G10

2009-06-25 12:58:20 | Weblog
ATMELのサイトをチェックしていたら、AT91SAM9G10が新たに出ていることに気がつきました。どうやらAT91SAM9261を高速化(266MHz)してLCDコントローラが機能強化されたようです。AT91SAM9G20が先に出ていますが、こちらはAT91SAM9260の高速化版で400MHz。

9GシリーズはAT91SAM926xの高速版をシリーズ化するのであろうと思われるわけですが、なぜかクロック速度がずいぶんと違います。AT91SAM9G10についてはすでにEK(評価キット)も準備できているようで、LinuxのイメージもLinux4SAMに上がっています。このサイトでExperimental Patchesの項を読んでいたらAT91SAM9G45という型番が出ています。こちらはAT91SAM9263の高速版ではないかと思われます。まだ公式には発表されていないと思うのですが、Linuxのパッチが先に出てしまうんですね。

FMを聴いてみる

2009-06-22 23:17:13 | MP3プレーヤ
FMチューナAR1010は、AR1000とソフト的にはほぼ同一であるので以前実験に使ったコードをそのまま流用します。もっとも、以前はAT91SAM7SでしたのでI2C部分はLPC2388に書き換え、I2C1を使うことにしました。

まずはI2Cを操作することでの選局です。受信感度の数字から、ちゃんと指定した周波数で選局できていることが確認できます。



続いて実際に音を聞いてみることにします。FMチューナの出力はMMcodec01のLINE入力につながっています。Codecの入力としてLINEを選択してやれば、CodecのI2S出力端子からはFMのステレオPCMデータが出力されるので、これをCodecのI2S入力に戻してやればヘッドフォンから再生することができるわけですが、そんなまどろこっしいことをしなくても、Codecのバイパス機能を使えばCodec内部で折り返すことができます。Codec内部での折り返しなので、LPC2388はaudioパスには直接は関与しないのですが、ヘッドフォンの音量をI2C経由で制御できますので、FMチューナ機能をサポートするにはこれだけでも用が足ります。

MP3再生の時と出力レベルの大きさに差異が生じないか心配でしたが、実際に実験してみると、FMチューナの出力を最大にしてCodecのライン入力では+0dBから+3dB程度を選択してやると、音量的にはちょうど具合が良いようです。

いちおうこれでヘッドフォンでのFM再生はできましたが、バイパス機能ではUSBスピーカからの再生はできません。そこで次はちゃんとI2S経由でFMチューナ出力を取り込んで、USBスピーカから再生させることにします。

FMチューナを追加

2009-06-18 23:12:13 | MP3プレーヤ
昨今のMP3プレーヤの自作にはFMチューナも欠かせないもののようですから、こいつ↓を追加してみました。



使用したのはSparkfun取り扱いのAR1010ボードです。すでに以前AR1000を使っての受信実験までは記事にしていますが、そのまま放置状態になっていました。そのAR1000を使っても良かったのですが、何ヶ月か前から使用チップがAR1000からAR1010に変更になったうえに2.54mmピッチのボードが用意されたので、これを新たに購入してみた次第です。AR1010になることで何かイイコトがあるのではないかと期待していたのですが、実際にデータシートを読んでみるとAR1000からRDS機能を削ったのがAR1010であり、むしろAR1000の廉価版のようです。日本国内ではRDS機能は使えないので機能的にはAR1010で充分なのですが、機能拡充を期待していたのでちょっとガッカリ。それでも、2.54mmピッチになって配線が容易になったので、今回はこちらのボードを使うことにしました。



FMチューナのオーディオ出力はそのまま直接MMCodecのLINE入力につなぎ、TLV320AIC23経由でヘッドフォンとUSBスピーカから出力することにします。MMcodec01にはオーディオ入力用のジャック(LINE)が載っているので、ボードに直接配線することはせずに、100円ショップで買ってきた2.5mmプラグの延長コードをぶった切ってつないでみました。配線は簡単でいいのですが、さらに不格好になってしまいました。

再生制御機能 -- その2

2009-06-15 22:53:55 | MP3プレーヤ
前回の記事で書いた再生制御機能を実際に使った様子を、掲載しておきます。



  • まず最初にplayer usbにより、出力デバイスとしてUSBスピーカを選択しています。
  • つぎに、player insertによりSDカードが挿入されていることをplayer_taskに通知してやります。するとplayer_taskではSDカードのディレクトリを走査し、MP3ファイル名を集め、その総数を表示します。
  • player playにて1曲目の再生が開始されます。この処理は、player_taskが1曲目のMP3ファイル名を渡してmp3decoer_taskを起動することで実現されています。
  • player pauseにより再生の一時休止が行われ、player playにより再開されます。これらの処理は、player_taskからmp3decoder_taskを休止(sus_tsk)ならびに再開(rsm_tsk)することで実現されています。
  • 停止操作は、mp3decoder_taskが参照するフラグをセットすることで停止を指示しています。


この他にも、次の曲へスキップするnext, 曲の最初から再生し直すrewind, そして前の曲へ戻る prevコマンドを用意したので、最低限の選曲/再生操作がおこなえるようになりました。現状ではSDカードのルート・ディレクトリにMP3データが入っていることを想定しており、アルバムをディレクトリ配下に入れることを想定していません。この点は、今後の課題のひとつです。

再生制御機能

2009-06-13 21:50:32 | MP3プレーヤ
LPC2388でI2S経由のCodecとUSBホスト経由のスピーカのどちらからもMP3を再生出力できるようになったのはいいのですが、まだまだ実験的な操作性ですので、これをちょっとは改善していこうかと思います。現状はシリアル・コンソールから再生を開始してしまうと、曲の最後までの再生が終了するまでの間、音量調節や再生の停止すらできません。とっても不便なので、まずはこの問題から改善することにしました。

考え方としてはプレーヤ制御を担うタスクを新たに用意して、シリアル・コンソールタスクから分離させればいいだろうということで、全体のタスク構成を次のようにしてみました。



mp3decoder_taskがMP3のファイルをひとつデコードして再生するタスクの本体です。このタスクを制御するのが、新たに用意するplayer_taskです。このタスクは外部から再生操作指示のイベントをもらい、その指示に応じてmp3_playerタスクを起動/終了させたり、休止/再開させることとします。操作指示を与えるためのボタンとかはまだ存在していませんので、当面はシリアル・コンソールからのコマンド入力により操作指示イベントを生成させることとします。

mp3player_taskではSDカードからMP3ファイルを読み込み、そのデコード結果をダブル・バッファに書き込んでいきます。mp3player_taskがデコード作業中にpcmsender_taskは、すでにデコードされたPCMデータを別のバッファから読み込んで、出力先のCODECあるいはUSBスピーカへ出力できるようにリングバッファに詰め込み直しています。USBスピーカの場合には、OHCIの出力のためにTD(Transfer Descriptor)の作成もおこなっていますので、実際には両者が使用するバッファの形式は異なっていますが、アドレス空間としては同一のDMA可能な空間を利用しています。

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スピーカへ出力しています。

SDカードからの再生

2009-06-07 18:57:06 | MP3プレーヤ
LPC2388のUSBホスト機能を用いてスピーカを鳴らす作業はいくつかの点でつまづいたものの、スピーカが鳴り始めています。この点については次回にでも記事にすることとして、きょうはSDカードからMP3ファイルを読み込んで再生する部分についてです。

SDカードのアクセス部分については、基本的にChaNさんのコードを流用させていだきました。FatFs R0.07を使っていますが、今回はLFNのサポートは落とし、読み取り専用にコンフィグして利用することとしました。MCIのドライバ部分については、コマンドの終了待ちと転送の終了待ちの部分でポーリング待ちするようになっていたので、これらについてはTOPPPERS/JSPのSemaphoreを使って待ち合わせするように変更しています。

MP3の再生部分のコードも、メモリ内の配列データの再生ではなくSD上のファイルから読み込んだデータを再生するように修正。これでようやくと、1曲まるまるデコードしてMMcodec01から再生出力できるようになりました。実際に聞いてみると、1曲の再生中に数回「ブツッ」と音がすることに気がつきました。どうやら、Codecへの出力処理が間に合わないことがあるようです。調べてみると、codecの割り込み優先度がMCIからの割り込みより低くなっていたので、codecからの割り込みを最優先にしたところ問題解決。



cplayコマンドで再生するファイル名を指定しているのですが、全文字指定するのも面倒なので、指定した文字数分だけマッチする最初のファイル名が見つかった時点で、再生ファイル名を決定しています。WMPでエンコードするとトラック番号がファイル名の最初に付くようですね。



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バイト送ってやることにします。