455KHz USB受信機の信号系を組みました。IFデジタルAMラジオへ向けてまた前進です。あとはPPIから信号を入れてやればとりあえず再生できる程度まできています。
処理方式はSSB変復調で使用したSSB複素復調(ハートレー)方式から、ウェーバー方式に切り替えています。当初の方式は逆サイドバンドからの低域リークが予想より大きく、100Hzで-20dB前後ものリークがあったためです。詳しい検討はしていないものの、これはおそらくフィルタのタップ数が有限であることから来ています。48Kサンプル/sの場合、100Hzの信号は480サンプルの波長を持ちます。しかし、実際に用いた複素フィルタのタップ数は205タップです。そこできちんとした位相シフトが行えなかったのではないでしょうか。しっかりとした実験をしているわけではありませんが、今のところはこれが原因だと考えています。
この問題を解決するためにプログラムを組みなおしてウェーバー方式にしました。ウェーバー方式の場合、必要なスペクトルは0Hzを中心に広がりますので、不要信号はすべて高域となり、FIRフィルタで問題なく除去できます。
少々大きな図ですが、以下にダイアグラムを示します。MIPSの計算値は当初概算用にメモしたもので、あまり意味はありません。
図1 455KHz USB受信機
実際に組んでみたところ、必要MIPSはADSP-BF533 Rev 0.2用のビルドで100MIPS、Rev 0.4用のビルドで90MIPSでした( VisualDSP++ 4.0 September Update 2005)。
出力のpeak to peak値を元に周波数特性を測定してみました。
図3は455KHzを中心とした±10KHzの振幅特性です。設計では最後のLPFを遮断周波数3.6KHzとなるようKeiser窓で設計しています。3.8KHzシフトされて全体としては200Hzから7400Hzまでを通過域とするようなフィルタとなっています。通過域内部の平坦性についてはおおむね満足できる性能です。また、逆サイドバンドからの抑圧も十分に出来ているように見えます。
図2 USBおよびLSB域の特性
複素フィルタによる実装で問題になった逆サイドバンドからの漏洩を見るために、455KHz近辺を拡大したのが図3です。455KHzに大きな切れ込みがありますが、これは出力がDCとなったためpeak to peakを測定できないのが原因です。無視してください。この切れ込みから左側が逆サイドバンドからの漏洩となります。
漏洩はLPFの高域での遮断特性によって決まっており、十分離れれば50dBの抑圧を確保できます。一方、キャリア周波数周辺での抑圧にはやはり限度があります。グラフからは読みにくいですが、-10Hzのときに28dB、-60Hzのときに40dB、-80Hzのときに50dBの抑圧を得ることが出来ます。その後、抑圧量はバウンドを繰り返しながら大きくなっていきます。複素フィルタ法では-100Hzのときに20dB程度でしたから大きな改善が得られています。
逆サイドバンドからの抑圧を十分と見るかどうかは設計方針次第です。今回は近接局からの受信を目的としていますのでこれでよしとします。
一方通過域を見ると遮断周波数である200Hzは6dB減衰しています。今後手直しをするとすれば、むしろこちらになるでしょう。
図3 キャリア周辺の周波数特性
プログラムをダウンロードできるようにリンクしておきます。sync_receiver2が今回の方式で、興味がある方のために複素フィルタ方式のsync_receiverもサンプルとしてつけておきました。まだUSB受信機なのでsync_receiverという名前は変ですが、ご勘弁を。
ダウロード(616KB) : sync_receiver.zip
以下は信号処理部、processing.cppを整理したものです。
#include "t_services.h" #include "kernel_id.h" #include "sync_receiver.h" #include "ad1836a.h" #include "stk.h" #include "c408khz.h" #define LPFTAPS 255 #define DECIM1TAPS 31 #define DECIM2TAPS 63 #define DECIM3TAPS 127 #define COUNT SAMPLE_PER_INTR // 割込みあたりのサンプル数を扱いやすい名前に再定義 // 中間バッファ変数の宣言 static short sound[COUNT], signal[COUNT*64], sp[COUNT*64]; static short temp_r[COUNT*64], temp_i[COUNT*4]; static short temp2_r[COUNT*64], temp2_i[COUNT*4]; static short lo_r[COUNT*64], lo_i[COUNT*4]; // オーディオLPFの係数 static short lpf_coeff[LPFTAPS]={ #include "lpf.txt" }; // 第一デシメーターの係数 static short decim1_coeff[(DECIM1TAPS+4)*2]={ /* 大きさは(タップ数+デシメーション比)の倍 */ #include "decim1.txt" }; // 第二デシメーターの係数 static short decim2_coeff[(DECIM2TAPS+4)*2]={ #include "decim2.txt" }; // 第三デシメーターの係数 static short decim3_coeff[(DECIM3TAPS+4)*2]={ #include "decim3.txt" }; static struct CODEC_BUFFER tx_buf; void modem_task(VP_INT extinf) { struct CODEC_BUFFER * rx_buf; // デシメーター stk::CFilter * decim1 = new stk::CDecimator( decim1_coeff, DECIM1TAPS, 4, true ); // 1/4ダウンサンプル stk::CFilter * decim2 = new stk::CDecimator( decim2_coeff, DECIM2TAPS, 4, true ); // 1/4ダウンサンプル stk::CFilter * re_decim3 = new stk::CDecimator( decim3_coeff, DECIM3TAPS, 4, true ); // 1/4ダウンサンプル stk::CFilter * im_decim3 = new stk::CDecimator( decim3_coeff, DECIM3TAPS, 4, false ); // 復調器のLPF stk::CFilter * re_lpf = new stk::CFirFilter( lpf_coeff, LPFTAPS ); stk::CFilter * im_lpf = new stk::CFirFilter( lpf_coeff, LPFTAPS ); // 408KHzの局発@3072KSPS C408kHz * lo1 = new C408kHz(); // 47KHz+3.8KHzの局発@192KSPS stk::CDDS * lo2 = new stk::CDDS( 1051372203+85004561 ); // 47KHz+3.8KHz // 3.8KHzの局発@48KSPS stk::CDDS * lo3 = new stk::CDDS( 340018244 ); // 3.8KHz act_tsk( CODEC_TASK ); // コーデック制御タスクを起動 while( 1 ) { int sample, slot; // コーデック制御タスクから入力データを取り込む (ダミー) rcv_dtq( CODEC_RX_DTQ, (VP_INT*)& rx_buf ); // 入力データからLとRを取り出して加算する。 // SLOT 0 : Internal L // SLOT 4 : Internal R // 24 bit データを加算後, 16bitに // 丸めるために17bitシフトする for ( sample = 0; sample < COUNT; sample++ ) sound[sample] = ( rx_buf->data[sample][0] + rx_buf->data[sample][4] ) >> 17; // sound にはモノラルデータが入っている。 // ここまでダミー // 455KHz 受信機として意味があるのはここから。 lo1->run( lo_r, COUNT*64 ); // 第一局発 408KHz stk::mul( temp2_r, signal, lo_r, COUNT*64 ); // ダウンコンバート decim1->run( temp_r, temp2_r, COUNT*64 ); // 第一デシメーター decim2->run( temp2_r, temp_r, COUNT*16 ); // 第二デシメーター // 以下、Weaver方式 // ---------- USB 復調 ------------- // 複素局発(+47KHz+3.8KHz 解析信号)を生成 lo2->run( lo_r, lo_i, COUNT*4 ); // -47KHz-3.8KHzでダウンコンバート stk::mul( temp_r, lo_i, temp2_r, COUNT*4 ); // -47Khz-3.8KHzにするために stk::mul( temp_i, lo_r, temp2_r, COUNT*4 ); // sin/cosを入れ替える // 第三デシメーター re_decim3->run( temp2_r, temp_r, COUNT*4 ); // 実部 im_decim3->run( temp2_i, temp_i, COUNT*4 ); // 虚部 // -3.8KHzより下に存在する不要なLSB分を除去する // オーディオLPF re_lpf->run( temp_r, temp2_r, COUNT ); // 実部 im_lpf->run( temp_i, temp2_i, COUNT ); // 虚部 // 複素局発(+3.8KHz 解析信号)を生成 lo3->run( lo_r, lo_i, COUNT ); // +3.8KHzでアップコンバート stk::mul( temp2_r, lo_r, temp_r, COUNT ); stk::mul( temp2_i, lo_i, temp_i, COUNT ); stk::sub( sound, temp2_r, temp2_i, COUNT ); // 実信号化 // ---------- 出力バッファにコピー ------------- for ( sample = 0; sample < COUNT; sample++ ) { tx_buf.data[sample][0] = sound[sample] << 16;//rx_buf->data[sample][0]; tx_buf.data[sample][4] = sound[sample] << 16;//rx_buf->data[sample][4]; } // コーデック制御タスクに出力信号を渡す snd_dtq( CODEC_TX_DTQ, (VP_INT) & tx_buf );