FM復調のFPGA実装(2)

前回までに、ミキサとCICはFPGA化ができました。

今回は、それに続くFIR一段分とCORDICによるarctanを実装しました。

FM復調全体の流れは次の図のようになっていますが、今回は図中のarctanまでFPGA化しました。

“FM flow”

NCO, ミキサ, CICは自分で実装しましたが、 FIR, arctanはそれぞれXilinxのFIR, CORDIC IPを使用して楽をしました。 FIRフィルタはマルチチャンネルに対応できるので、回路的には1つだけでI,Q両方を扱うことができますが、 乗算器も潤沢にあるので、それぞれのチャンネルでFIR回路を置いています。 また、arctanもY,X入力最大幅の49bitまで使用しています。

図の最下部にある40MHz, 160MHzというのはそれぞれの回路ブロックの動作クロック周波数になります。 160MHzはADCに使用されている40MHzのクロックをMMCMにて4逓倍して生成します。

1段目FIRの入力は、わずか1.25MHzしかありませんので、160MHzに対して160/1.25=128となり、 128クロックに1回しかデータが入力されません。さらに出力は2/5に間引かれますので、 平均して320(=128*5/2)クロックに1回しか出力されません。

このように、データは回路の動作周波数に対して間欠的にしかやってきませんので、 255タップのFIRフィルタを実装するにしても、ほんの少しの乗算器を使用するだけで実装できます。

ただ、最初はFIR, arctanを200MHzで動かそうとしましたが、 arctanのところでタイミングがメットしませんでした(WNS < 0)。 これだけのビット幅があるのに160MHzで動くのだから大したもんです。

arctanの出力をPCに取り込んで、残りの部分をソフトウェアで処理したところ、ちゃんと音声が再生できました。 あとは、残りの回路を粛々と実装します。FIR IPの使い方は分かったので、 手で実装しないといけないのはdiff(偏角の差分)とde-emphasisの部分になりそうです。

以下はおまけと備忘録

XilinxのFIR IPは、coeファイルというテキストファイルを指定することで、FIRの係数を設定できますが、 このファイルでは"-6.595122999934834e-03"といった指数表記の小数は使えないようです。 “-0.0065951…“のようにする必要があります。そのため

cat data.txt | awk '{print sprintf("%.12f", $1)}' > coef255_008.coe

のようにして表記を変更しました。

使用しているFIR, CORDIC IPコアの設定画面は次のようになっています。

FIR Compiler

“FIR Filter Options”

“Channel Specification”

“Implementation”

“Detailed Implementation”

“Interface”

“Summary”

CORDIC

“Configuration Options”

“AXI4 Stream Options”

FM復調のFPGA実装(1)

前回までに、ADCでキャプチャした生データをパソコン上で処理することで、 FMラジオを視聴できることが確認できました。

PC上のソフトウェアでFM復調を行っている部分を、徐々にFPGA化していこうとしています。

いきなり全体をRTLで実装しても、動作しなかった場合のデバッグが大変です。 それで、ADCに近い側からFPGAの回路に落とし込みます。今回は複素ミキサ(ADC入力にチューニング周波数のsin, cosを掛け算することで、I, Q信号を得る)と、 CICフィルタを実装しました。

2の補数表現とビットシフトを注意深く行えばそれほど難しくないはずの回路でしたが、わりと手こずりました。 複素ミキサはFPGAのBRAMにてテーブルルックアップを行ってsin, cosの値を得るようにしています。

具体的には、32bitの値を持つ400エントリを使用して、各エントリに対応するsin, cosの値を符号付きで13bitずつ入れています。 なぜ400エントリかというと、40MHzのサンプリングレートで例えば84.7MHzを受信しようとする場合、 これはナイキスト周波数以上なので実際には4.7MHzとしてADCから見えます。 (4.710^6) / (4010^6) = 47 / 400ですから、ADCの400サンプルに対して、4.7MHzは47周期現れます。 この400回分について、sin(2 * π * i * (47/400)) (i=0…399)をテーブルとして準備しておきます。 テーブルの中身自体は、Zynqで動作するLinuxアプリケーションにて初期化しています。 BRAMはZynqのアドレス空間と、RTLの両方からアクセスできるようにTrue Dual Portで動作させています。

ADCの各クロックごとにアドレスをインクリメントすることで、対応する周波数のsin, cosの値を得ることができます。 それをADCから出力されている符号付き12bitと掛け算し、下位ビットは切り捨ててCICフィルタに入力します。

CICフィルタは3ステージの1/32間引き(1.25MHz出力)を行っています。出力は29bitとしています。

これで毎秒のデータ量は1.25MHz*8bytes=10MBytesに減少しましたので、メモリ上に取り込むことのできる時間が一気に長くなりました。 PC側でサンプリングレートの調整を実装すれば(基板に実装されている発信器の周波数とPC上の周波数は異なるため、 48kHz出力を行うにしても、クロックレートの微調整のためにサンプルレート変換が必要)、 リアルタイム転送とFM変調処理が行えるのですが、そちらはまだ今後の課題ということで。

結局最後まで手こずった原因は、BRAMのアドレスの下位2bitは無視されていることに気づかず、 0-399までのカウンタをそのままアドレスに入力していたことでした。 0-399のカウンタに下位2bit"00"を付加してBRAMの32bitアドレスとすることで、 想定通りの正弦波が得られたようです。

それでも、PC上で処理した場合より明らかに音質は悪いです。もう少しミキサのビット数を増やしたりしてみようかと思います。

2017/4/20追記: ミキサの出力を17bitにして、CICを32bitまで拡張しました。 ミキサ出力の部分も実際のデータを観測して、オーバーフローしない範囲でできるだけ下位ビットを取るようにして、 可能な限り精度を落とさないようにしました。CICの結果から残りをパソコンのソフトで処理してみたら、 かなり音質は改善しました。これならひとまず良さそうです。 残るFIRとCORDICによるarctanの実装を粛々と行っていけばFM復調のFPGA化が完成する予定です。

また、これまでソフトウェアではarctanを求める前に複素数の割り算をすることで、偏角の差分を一発で求めていました。 でも、FPGA化する場合、これだと乗算と加算が入るので、CICのデータが32bitあると64bitほどになってしまいます。 それで、まずarctanを各データに対して行って、その結果の偏角を引き算する、という計算順序に変更しました。 arctanが返す角度の範囲が-πからπまでなので、単純に引き算するだけだと、この範囲を超えてしまうことがあります。 そのため、下記のように必要な場合2πずらすようにしました。

    private double[] fm_demodulate_sub(double[] i, double[] q)
    {
        double[] result = new double[i.Length];
        double prev = 0.0, tmp;

        for (int x = 0; x < i.Length; x++)
        {
            tmp = Math.Atan2(i[x], q[x]);
            if (tmp - prev > Math.PI)
            {
                result[x] = tmp - prev - Math.PI * 2.0;
            }
            else if (tmp - prev < -Math.PI)
            {
                result[x] = tmp - prev + Math.PI * 2.0;
            }
            else
            {
                result[x] = tmp - prev;
            }
            prev = tmp;
        }
        return result;
    }

これならばarctanの計算に必要な精度はI, Qそれぞれ32bitで良いことになります。

FM復調(モノラル)の実装

前回までに、ADCでキャプチャした生データをパソコン上で処理することで、 AMラジオが視聴できることが確認できました。

加えて今回はFMも復調できるようになりました。

FM復調の流れは、次のようになります。

  • ADCデータ(12bit@40MHz)に、チューニングしたい周波数のsin,cosを生成(NCO)してそれぞれ掛け算してI, Qデータを得る
  • I, Qデータを4ステージCICフィルタで1/32に落とす(1.25MHz)
  • 続いてFIRフィルタで2/5倍(500kHz)に周波数変換する。2/5倍で使用する係数は255タップ、遮断周波数0.08fs(fc=200kHz)とする。
  • 500kHzサンプリングのI, Qデータの隣り合ったサンプル同士からarctan(タンジェントの逆関数)をもとめる。 具体的には、Math.Atan2(i[x-1]*q - q[x-1]*i , i[x-1]*i + q[x-1]*q )を求める。 I, Qを複素平面のx,y座標とみなして、C とするとき、arctan(C[x+1]/C )を求めることで、サンプル間の偏角の変化量を求めることになります。
  • 上記の偏角の変化量をFIRフィルタで2/5倍(200kHz), 3/5倍(120kHz), 2/5倍(48kHz)に周波数変換する。 係数はすべて255タップ、遮断周波数はそれぞれ0.08fs(fc=80kHz), 0.08fs(fc=48kHz), 0.0625fs(fc=15kHz)とする。0.0625fsとすることで、15kHz以下を通過させるLPFとなります。 19kHzにステレオ放送用のパイロット信号が入っているので、その前の段階で十分に減衰できるようにします。
  • 48kHzまでサンプルレートが落とされたデータ系列に対して、デエンファシスをかける。具体的には、 Low-Pass Single-Pole IIRフィルタを通します。 上記ページによれば、減衰量dはd=e^(-2pifc)という関係になるようです。fs=48kHzなので、大体3kHzで-3dBになればよいようなので、 fc=3kHzとし、d=e^(-2pi(3/48))=0.67523…となります。
  • デエンファシス後のデータを(適当にゲイン調整を行ってから)WAVファイルに出力する。

上記のように、FMでのステレオ復調は行っていません。これでなんとか音声として聞こえるようになりました。 各フィルタの遮断周波数は、変換後の1/2fsよりもそれなりに小さいところで設定しておかないと、折り返しがもろに入ってしまうと思われます。 そのため、上記のように1/2fsの80%, 62.5%と設定しています。

ここまでで、曲がりなりにも論理的にはAM/FMの復調ができることは確認できました。すぐに思いつく今後の方向性としては

  • FPGA化
  • ADCの精度確認と改善

があります。後者は機材をそろえたり、受動部品の定数を変更したりしてみないと難しいと思われるので、 ひとまずFPGA化の方を少しずつ進めていこうと思います。また、基板上には音声出力用のICも搭載しているので、 そちらの動作確認もしないといけません。

参考にしたページ

FMトランスミッタ: プリエンファシス(1)

三重大学の資料?

エンファシス

石川高専 山田洋士 研究室 (FIRフィルタの窓関数法による設計)

ADC基板でのAM/FMキャプチャと復調

ADC基板でAM信号をキャプチャして、 パソコン上のソフトウェアでAM復調を行う、というところまで最低限動作しました。

波形を取得しFFTしたときの画面を示します。

“AM Capture”

赤丸で囲ってあるところは、それぞれ810kHz(AFN), 1422kHz(RFラジオ日本)と思われます。 実際にC#で適当にこしらえたソフトウェアで810kHzをAM復調してみたら、カントリーミュージックが流れていました。

AM復調の流れは、ざっと次のようになります。

  • ADCデータ(40MHz@12bit)に、チューニングしたい周波数のsin,cosを生成(NCO)してそれぞれ掛け算してI, Qデータを得る
  • I, Qデータを4ステージCICフィルタで1/32に落とす(1.25MHz)
  • 続いてFIRフィルタで2/5倍(500kHz)、2/5倍(200kHz)、6/25倍(48kHz)と変換を行う。2/5倍で使用する係数は51タップ、遮断周波数0.2fs。 6/25倍で使用する係数は51タップ、遮断周波数0.04fs。
  • 48kHzまで落としたI, Qデータからsqrt(I^2+Q^2)を求めて振幅を求める(AM復調)。 この値をwav形式(モノラル)でファイルに出力する。

SDRについては勉強中の身なので、上記のタップ数や遮断周波数の設定などはあまりよろしくないのかも? しれません。 現状NCOとFIR演算は整数演算ではなく、浮動小数点演算を用いています。 また、FIRのために実装したポリフェーズフィルタもコードが正しいか、まだちょっと不安です。

ただ、上記画面からもわかるように、他にも954kHz(TBSラジオ, 100kW)や1134kHz(文化放送, 100kW), 693kHz(NHK第二, 500kW)などの、出力が大きい局の信号がほとんどとれていません。

Wikipedia - AFNによれば、AFNは出力が50kWらしいので、 他の局も十分受信できても良いように思います。 手元にちゃんとしたAMアンテナが無いので、FMロッドアンテナを基板に装着しているのですが、それが原因なのか? それとも、サンプリングの折り返しなどの原因があるのか(AM側の入力はBPFなどを一切設けていない)? まだ不明です。

また、810kHzなども、「なんとか聞こえる」という程度であり、お世辞にも音質は良いとは言えません。 所有している乾電池式ポータブルラジオの方がはるかに音質が良いです。 もっとも、これについては復調処理に問題があるのかもしれません。

4/5追記: やはり間違いがありました。Wikipedia - サンプリング周波数変換 の通り、a/b倍の周波数変換をするときには、変換後の周波数の1/2の周波数を遮断周波数としなければいけないようです。 上記の設定だと、変換後の1/2fs以上の周波数が折り返してしまいます。 そういうわけで、2/5倍に使用する係数は0.1fsを遮断周波数とし、6/25倍に使用する係数は0.02fsとしました。 また、タップ数もそれぞれ71, 255に増やしました。 そうしたら音質が(まだまだ十分ではないですが)かなり改善されました。

4/11追記: さらに変更して、現在の信号処理の流れは次のようになりました。

  • ADCデータ(40MHz@12bit)に、チューニングしたい周波数のsin,cosを生成(NCO)してそれぞれ掛け算してI, Qデータを得る
  • I, Qデータを4ステージCICフィルタで1/32に落とす(1.25MHz)
  • 続いてFIRフィルタで2/5倍(500kHz, fc=156.25kHz)、2/5倍(200kHz, fc=62.5kHz)、3/5倍(120kHz, fc=48kHz)、2/5倍(48kHz, fc=15kHz)と変換を行う。 使用する係数は255タップ。2/5倍で使用する係数の遮断周波数は0.0625fs。3/5倍で使用する係数の遮断周波数は0.08fs。 上記の通り、2/5倍で0.1fsとすることもできるとは思いますが、それだと1/2fsで-3dBしか落ちませんので、 周波数変換後の高音部にかなりエイリアスが入り込んでしまうと思います。
  • 48kHzまで落としたI, Qデータからsqrt(I^2+Q^2)を求めて振幅を求める(AM復調)。振幅が符号付き16bitに収まるようにゲイン調整を行う。 この値をwav形式(モノラル)でファイルに出力する。

追記終わり

ちなみに、FM側の入力波形を見てみると、次のようになっています。

“FM Capture”

赤丸はおそらく81.3MHz(J-Wave)と84.7MHz(FMヨコハマ)でしょう。まだこちらは復調処理は書いていません。

4/11追記: FM復調もできましたので、記事にしました。 AMでそれなりにコツがわかったので、わりとあっさりと書けました。 ソフトウェアで処理するのであればarctanも気にせずに使えますしね。

それにしても、やはりノイズが大きいように思います。信号発生器は所有していないのですが、 1万円程度でDDSタイプのものが買えるようなので、 特定の周波数の正弦波を入力して、FFTすることで、ADCとしての性能を調査してみないとダメかもしれませんね。

ADC基板到着

ついに、Elecrowに発注していたADC基板が到着しました。

3 / 9 (木)にPayPalにて支払いを行い、3 / 29に発送のお知らせメールが到着し、3 / 30 (木)に到着しました。 Elecrowから送られてきたL/Tは2-3weeksでしたが、ぴったり3週間で到着です。

3 / 29までは全く音沙汰がなかったので、ちゃんと進んでいるのか、それとも実装に失敗しているなどの問題が起きていないか、心配でした。

完成した写真を送ってくれたという方もいるようですが、私の場合はいきなり発送されました。 OCSの履歴を見てみると、3/28の夜に中国から送られて3/29の午前には日本に到着していました。 配達はさすがに速いですね。

ちなみに、基板2枚実装で241USD, π割引3.14USDで237.86USDでした。日本円で27,784円でした。1USD=116.8円なので、 あまりレートは良くないタイミングでした。

基板の包装は次のような感じでした。

基板のパッケージ

テンション上がります。

ちなみに、かの有名なElecrowガチャは2枚でした(実装したのも2枚)。

おまけ基板

ステンシルは入っていませんでした。再度製造を依頼する場合に必要になるので、こちらから頼まなければ保管しておいてくれているのかもしれません(未確認)。

ADCの部分は次のような感じです。

ADCパターン

KiCadに習熟していないせいで、若干センターのGNDが甘い気もしましたが、勢いで作ってしまいました。

あらかじめ購入しておいたSMA-BNC変換コネクタとジャンパを装着した状態が次です。

テンションMAX

うーん、美しい。初めての基板製作で我ながらよくここまで頑張った。 フットプリントがおかしくて墓石になることもなかったようです。 また、ちゃんとDIP部品もしっかり基板に入っています。ヘッドフォン端子はフットプリントが結構変態的だったので、ちゃんと基板に入るか心配でした。 ちなみに、画面下の赤いのは、SMAコネクタを保護しているプラスチックのキャップです。

ちょっと部品間隔が狭すぎるかと予想していましたが、しっかり実装できているようです。抵抗アレイはほとんど横並びです。 自分の実力だと、これほど近接していたらチップ部品の交換はしんどいですね。

右下のジャンパの接続のためのシルクも小さいけどしっかりと見えます。

それにしても、実際の基板ができた時の満足感はすごいですね。もうデバッグはいらない気分になってしまいます。

この基板をこれまた購入済みのZ-turn Boardに装着すると、次のような感じです。

最高の組み合わせ

さすがに80ピンもあると抜き差しは慎重にやらないと危険です。

1枚の基板はこの80ピンコネクタが少し曲がっているという不具合がありましたが、ひとまず手でなんとか挿入できるくらいまでは真っ直ぐに直しました(汗)。

あ、もちろん、電源を入れる前に電源とGNDがショートしていないことは確認しましたよ。

いよいよ(すぐにアダプタを抜ける態勢で)緊張の電源ON!!

「ピー」 ….. え??? ひとまず電源を抜きます。

おかしい?? ショートしている?? というわけで基板チェック開始。いろいろ調べてみたけど、やはりショートは問題ない。 どうやらJ6のジャンパを入れてADCの電源が入ると音が鳴る。J6がoffだと音はしない。

いろいろ調べてみたところ、どうやらビープを駆動するNPNトランジスタのゲートに接続されているネットがオープン状態なのですが、 そこの電位が数百ミリボルトあがっているようでした。それでトランジスタがONしてしまっているのではないかと思いました (参考)。 こうなってしまっている原因は不明ですが、ショートは恐らくないことを考えると、 ADC出力が開始されることで、当該ネットに近い他のネットからのクロストークを受けてしまっているのかもしれません。

というわけで、本来ならばビープのネットをプルダウンするのが良い解決策だと思いますが、FPGAで当該ネットをLow駆動することで音は出なくなりました。 ひとまず変更したデザインをQSPI ROMに書き込んで、起動時にコンフィグが終了すれば音が止むようになりました。

ちょっと先が思いやられますが、デバッグを続けます。

« 4/7 »