CentSDRの実験をしている過程で、DCオフセット由来の不要信号の対策をしています。原因は2箇所あり、それぞれ違った要因で生じていました。参考になるかもしれませんので、少し解説しておきます。
上の写真はアンテナを接続せず無信号の状態でサンプリングした信号のスペクトラムを表示したものです。無信号にもかかわらず、中央に信号が見えています。内部のSSB復調信号処理の都合上、1.3kHzずれた位置に見えているのですが、ADCから取り込んだ時点では0HzすなわちDC成分です。サンプル値に常に0からのずれが残っている状態となっているわけです。
復調後のオーディオ信号のスペクトラムが下記です。こちらでも0Hzに応答が見えています。
このDCオフセットはADC由来です。本来無信号であればゼロとなってほしいのはずですが、どうしてもさまざまな要因からオフセットが生じてしまいます。無用な成分ですので消してしまうことが望ましい。対策は、DC=0Hzを通さないフィルタ、すなわちHPFを入れることになります。カットオフ周波数は、数Hz程度となるようにします。
HPFを実装する具体的方法ですが、使用しているCodecチップTLV320AIC3204にはDSPブロックが含まれていますので、かなりの自由度でデジタルフィルタを入れることができます。Codec内部のDSPを使うことで、MPUのサイクルを消費せずに済むわけです。下記に基本構成の場合のブロック図を示します。この構成の他にもFIRやIIRなど、さまざまなパターンが用意されています。詳しくはリファレンスガイドをご覧ください。
この図中の”1st Order IIR”が、ADCのDCオフセットを削除するために設けられたフィルタブロックです。DCオフセット削減程度の処理であれば緩い特性でOKですので、1次のIIRで十分というわけです。ここに適切な係数を与えてやればお望みのフィルタ特性が得られます。
このHPF用の係数を設計します。いつものJupyter notebookでやってみます。まずは準備として下記をインポートしておきます。
%matplotlib inline
import pylab as pl
import numpy as np
from scipy import signal
scipy.signalのiirfilter関数を使って、1次のIIRフィルタを、High Pass Filterとして設計します。カットオフ周波数は適当に0.0001と指定していますが、これはナイキスト周波数に対して0.0001、すなわちサンプリング周波数48kHzの場合、ナイキスト周波数は24kHzなので、カットオフは2.4Hzということです。
この周波数特性を確認してみます。両対数グラフにプロットを示します。ちゃんとHPFの形になっていることがわかります。対数なので見えていませんが、0Hzの応答は0となっています。
さて、係数を、DSPに組み込むためには、24ビットの符号付き固定小数点に変換する必要があります。ごにょごにょして、それぞれバイト列に変換します。
b[0] = 0x7f, 0xfa, 0xda
b[1] = 0x80, 0x5, 0x26
-a[1] = 0x7f, 0xf5, 0xb5
これをI2C経由でチップに書き込みます。コードを下記に示します。2つのチャネルでレジスタが違いますので、それぞれ同じデータを書き込みます。係数を書き込んだあと、係数バッファを切り替えることでフィルタの動作に反映されます。ADCを動作させながら切り替える場合はこの一手間が必要です。動作開始前に係数を設定するのであればバッファ切り替えは不要です。
const uint8_t adc_iir_filter_dcreject[] = {
/* len, page, reg, data.... */
/* left channel C4 - C6 */
12, 8, 24,
/* Pg8 Reg24-35 */
0x7f, 0xfa, 0xda, 0x00,
0x80, 0x05, 0x26, 0x00,
0x7f, 0xf5, 0xb5, 0x00,
/* right channel C36 - C38 */
12, 9, 32,
/* Pg9 Reg 32-43 */
0x7f, 0xfa, 0xda, 0x00,
0x80, 0x05, 0x26, 0x00,
0x7f, 0xf5, 0xb5, 0x00,
0 /* sentinel */
};
void tlv320aic3204_config_adc_filter()
{
const uint8_t *p = adc_iir_filter_dcreject;
while (*p != 0) {
uint8_t len = *p++;
uint8_t page = *p++;
uint8_t reg = *p++;
I2CWrite(AIC3204_ADDR, 0x00, page);
while (len-- > 0)
I2CWrite(AIC3204_ADDR, reg++, *p++);
}
I2CWrite(AIC3204_ADDR, 0x00, 0x08); /* Select Page 8 */
I2CWrite(AIC3204_ADDR, 0x01, 0x05); /* ADC Coefficient Buffers will be switched at next frame boundary */
I2CWrite(AIC3204_ADDR, 0x00, 0x00); /* Back to page 0 */
}
これを組み込みます。さきほどと同様に無信号時のスペクトラムを見てみると、DCの信号が消えてちゃんとフラットになっています。
オーディオのスペクトラムを見てみると、0Hzの成分は消えたのですが、別の小さな信号が残っていることがわかります。
信号処理過程の中間の信号を見てみると、またもや0Hzに不要信号が生じていることがわかります。
SSB復調の信号処理ブロック図を示します。ADCで取り込んだ信号をまず最初に1.3kHz周波数シフト、次いでfc=1.3kHzのLPFを通します。最後に、また1.3kHz逆向きに周波数シフトしています。こうして2.6kHz幅の復調帯域幅を得ています。LPFにCMSIS DSPライブラリを使って、6次のBiquad IIRフィルタを実装しています。この内部でDC成分が生じているようです。最後にまた周波数シフトしているので結果として1.3kHz付近の不要信号となっているわけです。
CMSIS DSPライブラリのソースコードを調べてみます。ファイル
CMSIS/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c
にある
arm_biquad_cascade_df1_q15() 関数です(141行目付近)。
固定小数点のシフトを使った計算をしているのですが、適切でない箇所があるようです。2べきの割り算をシフトで行うことはよくやることなのですが、符号の扱いには注意が必要です。たとえば、正の数に対して右シフトを繰り返すと8→4→2→1→0とどんな数も最後には0になります。ところが負の数を右シフトする場合は、-7→-3→-1→-1→-1→-1 と、-1すなわち全ビットが1になったあとは変化しなくなります。このように右シフトによる割り算は、正と負で結果が非対称になっていますので、適切に扱われていないと期待しない結果が生じます。CMSIS DSPライブラリで生じているDCオフセットはこれが原因のようです。
これを回避するため下記の修正(FIXコメントの行)を追加します。0と-1のギャップを埋めるために0.5を足しておくのです。
/* acc += a1 * y[n-1] + a2 * y[n-2] */
acc = __SMLALD(a1, state_out, acc);
/* FIX: compensate dc offset caused from bit shift operation */
acc += 1 << lShift;
/* The result is converted from 3.29 to 1.31 if postShift = 1, and then saturation is applied */
/* Calc lower part of acc */
acc_l = acc & 0xffffffff;
/* Calc upper part of acc */
acc_h = (acc >> 32) & 0xffffffff;
この対策を施すと、無事DC成分が消えました。無信号でオーディオフラットです。やったね!
公式にARMから提供されているCMSIS DSPライブラリといえども、注意して使用する必要があるようです。今回の対策は、LPFを実装する目的には効果がありましたが、一般に適用するには注意が必要かもしれません。
というわけで、同じくDCオフセットに関するトピックでしたが、ハードウェアからソフトウェアまで、いろいろな原因があり得ます。その対策2題でした。
それにしてもスペクトラムが実機で表示できるのはとても便利です。前はデータを取り出してグラフを描いたりしていたのですが、画面を見れば一目で効果がわかります。
おまけでCentSDRのハードウェアブロック図です。若干数のキット頒布の準備をしていますが、なかなか進捗しません。アナウンスをお待ちください。
参考
- CentSDRを組み立ててみました
- TLV320AIC3204 Application Reference Guide https://www.ti.com/lit/ml/slaa557/slaa557.pdf
- CMSIS DSPライブラリ https://github.com/ARM-software/CMSIS
- 原因箇所 https://github.com/ARM-software/CMSIS/blob/master/CMSIS/DSP_Lib/Source/FilteringFunctions/arm_biquad_cascade_df1_q15.c#L143