FPGA FMチューナーとRaspberry piのI2S接続

やりたいこと

FPGA FMチューナーは, 特に電波状況が良い場合,信じられないほど良い音がします. これをデジタルで録音しておきたいと考えました. このチューナーにはS/PDIF出力がありますので, これで接続してPC録音するなら簡単です. ただ,一日2時間程度の録音のためにPCを常時上げっ放しにはしたくない.

そこで,Raspberry piで録音できないかと考えました. Raspberry piなら上げっ放しでも良いですし, タイマーで立ち上げることもできます. 録音のためには,「S/PDIF→DAI→RaspiのI2S」というハードを作れば良いわけですが, 実はFMチューナー内部でのFPGAとDAC (PCM1781)の接続はI2Sですので, I2SでRaspiに直結できるのではと考えました. Raspberry piでのI2Sを使った再生(DAC)の情報は沢山あるのですが, 録音に使う話は少ないので,ちょっと詳しく書いてみます.

ハード編

基本的なことを幾つか確認します. まずPCM1781の接続方式です. I2Sと似た方式のright-justifiedなどがあるので確認が必要です. PCM1781の1番ピンはNCですが,内部でpulldownされているので,これでI2Sです.問題なし.

次に電圧.元々のPhilipsのI2Sの規格ではTTLレベルのようですが, Raspberry piのGPIOは3.3Vです. FPGAとPCM1781も3.3V接続ですので,電圧も問題なし.

最後に,I2Sのパラメータです. FPGAとPCM1781のI2S接続は,192,000Hzの24bitとなっていますが, ロジックアナライザで観測したところ,1ワードを32クロックで送信していました. これは特殊なことではなく,I2Sはこういうもののようです. 元々,I2Sにはワード長という概念はありません. I2SではWS信号の切り替わった直後(正確には切り替わった1クロック後)から, データをMSBからLSBの順で送信して行きます. 受信側は,16bitで十分なら16bitで打ち切ればよいわけです. (実際,今回は16bitしか使っていません.) I2Sの設定をしているプログラム (bcm2708-i2s.c) も確認しましたが, このような信号でもちゃんと受信できるようになっています.

接続回路ですが,オリジナルのFMチューナーは次のようになっています.

FPGA─抵抗(100オーム)─PCM1781
今回はパターンカットしやすさから抵抗とPCM1781の間でカットしました. GPIOは双方向なので,出力が衝突した時の保護のため,更に500オームを挟みました. (何となく5mAくらいなら流れても大丈夫だべ,という適当な値.)
FPGA─抵抗(100+500オーム)─Raspberry pi I2S (=GPIO)
という接続です.ピン対応は次の通りです.ハードは,とても簡単です.
DATA (=SD): 元のPCM1781の6番 → GPIO30
BCK (=SCLK): 同7番 → GPIO28
LRCK (=WS): 同8番 → GPIO29
GND: GND

ソフト編 (ドライバー)

ディストリビューションはRaspbian (Wheezy, Sept-2014) で, Linuxカーネルは3.12.28+です. GPIOからI2S入力するためには,ドライバーが必要です. Raspberry pi用のADC/DAC基板が何種類か売られています. これらはI2S接続ですので,それらのドライバーを使わせて貰います.

それらの既存のドライバーを流用して新しいドライバープログラムを作ることも簡単です (Kconfig,Makefile,arch/arm/mach-bcm2708/bcm2708.cをチョコっといじる)が, 今回は横着して,RPI DACのドライバーをそのまま改造してしまうことにしました. (もちろん,これをやると本物のRPI DACは接続できなくなります.) RPI DACを選んだのは,このドライバーが一番シンプルそうだったからです.

RPI DACは,DACとしてpcm1794aを搭載していますので,そちらも修正します. つまり,以下の2つのファイルを修正します.

I2S受信側のドライバー: sound/soc/bcm/rpi-dac.c

	return snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
ここは既にこうなっているのですが,重要なところなので確認です. 上の第2引数は,32クロックで2ワード(2チャンネル)を受信するという意味です. 他のドライバーでは, この部分はワード長の2倍としているものもあります. RPI DACは32クロック固定方式のようです. FPGAもたまたま同じ方式だったので修正しなくて良いわけです.
	.dai_fmt = 〜
		   SND_SOC_DAIFMT_CBS_CFS,
これを次のように変更します.
	.dai_fmt = 〜
		   SND_SOC_DAIFMT_CBM_CFM,
これはcodec側から見て,bit clockとframe clockを駆動するかどうかの指定です. 元はS (Slave) になっていますが,今回はcodec側が駆動するのでM(Master)にします.

I2S送信側のADC/DACのドライバー: sound/soc/codecs/pcm1794a.c

	.playback = {
この行を次のように変更します.
	.capture = {
もちろん,再生デバイスではなく,録音デバイスになるという意味です.

最後は不要なのですが,32bit録音したくなるかもという場合は,

		.formats = 〜
この行に次を追加します. これは,ドライバーが対応可能なフォーマットを指定しているだけで,動作とは関係ありません.
		.formats = 〜
			   SNDRV_PCM_FMTBIT_S32_LE

あとはカーネルを普通に作成します.カーネルコンパイルの方法はWeb等を参照して下さい.

ソフト編 (ドライバーの設定)

作成したドライバーをロードするための設定をします. Web上の情報が錯綜していますが,Raspbian (Wheezy, Sept-2014)では, HiFiBerryの設定方法をマネるのが一番良いようです.

/etc/modprobe.d/rasp-blacklist.confの中の次の行をコメントアウトします.
(下の2行はコメントアウトしなくていいと思いますが.)

blacklist i2c-bcm2708
blacklist snd-soc-pcm512x
blacklist snd-soc-wm88704

/etc/modulesに以下の行を追加します.

snd_soc_bcm2708
bcm2708_dmaengine
snd_soc_rpi_dac
snd_soc_pcm1794a
/etc/asound.confを作成します.これはsoxしか使わないなら関係ないかも.
pcm.!default {
  type hw card 0
}
ctl.!default {
 type hw card 0
}
これでリブートして,
arecord -l
とやってRPI DACが(入力デバイスとして)見えれば成功です.

ソフト編 (録音ソフト)

arecord -c2 -r192000 -f S16_LE out.wav
とすれば録音はできます.が,実際は音がブツブツと途切れて聞けたものではありません. バッファリング時間を調整して,--buffer-time=100000 とすると良いようです.

現在はarecordでなく,soxを使っています.

export AUDIODEV=hw:0,0
export AUDIODRIVER=alsa
rec -q --buffer 50000 -c2 -b16 -r192000 out.wav
bufferサイズを指定しなくても録音できますが,時々overrunが出ます. 上の値が一番良いようです.(この値が65535を越えると無限にエラーが出続けます.)

ただし,それでもSDカード (TranscendのSDHC Class10 16GB, カタログスペック 20MB/S) への保存だと1〜2分に一回程度のoverrunが出ます. USBメモリへ直接保存するとかなり減少し,2時間で0回ということもあります.

48,000Hzに落として,ついでにflacにします.

sox -q --buffer 50000 -c2 -b16 -r192000 -d -r48000 out.flac downsample 4
これで2時間で500MBくらいでしょうか. overrunはほぼ出ません.

RTCタイマーによるRaspberry piの電源オン

やりたいこと

Raspberry piとタイマーのI2C接続ができたので, 元々の目的であった,指定時刻でのFM放送の録音を考えます. そのためには,ある時刻に電源を入れる必要があります. 最初は,安いプログラムタイマーを使っていましたが, もう少し何とかならないかと考えてみました.

Raspi用のRTCとしては,DS3231を組み込んだモジュールが販売されています (例えばこちら). DS3231にはAlarm機能が付いていますので,これを使ってみます.

ハード

Raspberry piの電源回りはとてもシンプルで, 単に電源が入れば立ち上がります.リセット信号もありません. DCのオン・オフにはSSRは適していないようなので,リレーを使います. 回路はこんな感じ.

[回路部品の説明]

抵抗の値は,手元にある適当なものでだいたい動くと思います. 実際,今の回路は図の値とは少し違います.

リレーは OMRON G6B-1114P-US DC5V です.接点定格は30V 5A.

コンデンサは,Raspberry piがrebootする時にUART TxDが一秒程度落ちる間, 電源を維持するために必要です. 現在は220μが入っていますが,100μでも十分な気がしますので,回路図ではこの値にしてあります.

トランジスタ,特に前段の2SC1815はほとんど何でもいいと思います. それに2SC1815オリジナルは製造中止なので, 互換品(例えばKSC1815)を使うことになります.

電源オンのスイッチはチャタリングする可能性があるので,2SA1015のベースでなく, コンデンサのところに付けています. 今は220μなので,やや長目に押す必要がありますが,100μならチョン押しでいいはずです.

[動作の説明]

DS3231 INT(3番ピン)は,通常はハイインピーダンスで,Alarm指定時刻でLowになります. これを使って2SA1015をオンにします.(なお,INTは後述するA1Fがクリアされるまで, ずっとLowのままです.)

一旦立ち上がったら,Raspberry pi側でshutdownを実行するまで, INTの値に関係なく電源はオンのままでなければなりません. それには,Raspが生きていることをハード側で知る必要があります. いろいろ調べたところ, UART TxD(8番ピン)が内部でプルアップされていることを使うのが良さそうです. ただし,UARTは本来の用途には使えなくなってしまいます. (GPIO4を使うと良いという情報もありました.)

以上については,こちらを参考にしました.

なお,電源の容量ですが,安い5V 2AのACアダプタを使っていた時には, DS3231がリセットされる現象に悩みました. 突入電流で電圧が降下していたようです. 定常状態での電流は実測で0.9A(Raspi+チューナー+ファン)ですが, 突入電流は2A以上になると思われます.

ソフト

DS3231は,I2CでRaspberry piに接続します.I2Cをいじるには,i2c-toolsを使います. なお,DS3231のI2Cアドレスは0x68です.

現在は,次のような流れで録音しています.

Alarmレジスタの設定は,DS3231のデータシートを見れば分かると思いますので,注意点だけ. 時間はすべてUTCですので,AlarmレジスタにはUTCの値を書き込む必要があります. 曜日の数え方も次のようにずれています.(個人的には日曜日=0で始まるべきだと思うんですけどね.)

ケースへの組込み

全基板をシャープのBSチューナーTU-HD1のケースに入れてみました.

電源基板が傾いているのは,既存のホールを利用したためです. 電源は5V 10Aなので,ちょっと大き過ぎです.そのうち小型のものに変えます. FMチューナー基板はアンテナコネクタがリアパネルに出るように, Raspberry pi基板はLANケーブルがリアから挿せるように配置しました. I2S接続基板は再利用品なので汚いです.

POWER ONのスイッチと電源オン表示のLEDは,TU-HD1のフロントパネルのものを利用しています. また,USBメモリは頻繁に使うので, Raspberry piのUSBコネクタをフロントパネルに移設しました.


[トップへ] inserted by FC2 system