dsPICでSSBトランシーバー(SSBジェネレーター)
カテゴリ<SDR> [DSP 自作 7MHz ]
HDSDRというフリーのSDRソフトを使った、SSBトランシーバーは完成しましたが、いざ完成すると、それはそれで、色々と不満も出てきます。 HDSDRの+/-48KHzのバンドスコープは、ワッチする場合、大変便利ですが、交信するにははなはだ、不便でなりませんでした。 とにかく了解度が悪く、交信相手に、こちらの信号は59で届いているのに、相手の信号は29から39でしかありません。 この同じ信号をTS-930で受信すると、59です。 この原因は、広帯域の受信バンドの為、S/Nが悪化していること、強レベルの隣接局によるAGCの為、ノイズフロアが常に上下している事などが考えられます。 HDSDRの機能の中に、ノイズリダクションやノイズブランカ―、オーディオ帯の周波数可変可能なBPFやノッチフィルターなど、了解度を改善する機能が満載されている理由が判るような気がします。 そして、これらの機能を駆使しても、通常の会話は了解出来ても、コールサインの確認に手間取る状態でした。
これを改善するには、多くのメーカー製トランシーバーが採用しているように、バンドスコープ用IFと復調用IFを完全に分離するしかないようです。 一方、バンドスコープ用IFと復調用IFを共用しながら、バンドスコーブの幅を必要最小限に狭めたSDRトランシーバー SDR-3 が、「おじさん工房」から商品化されております。 多分ここまで、狭めたら、受信能力の悪化は小さいと予想されますので、今度はこれを手本として、どこまで自作できるかトライする事にします。
このプロジェクトを開始するに当たり、そのノウハウをパクってきた元はJA1QVM OMのブログです。 これから、SDRの勉強をしようとインターネットを検索していましたら、探していた情報がそのまま出ていました。 OM Very TKS.
左のグラフは、OMの記事を真似て作った、31TAPのFIR LPFの実測データです。 ここまでできると、SSBジェネレーターに必要な300-500TAPのフィルターは簡単にできてしまうと考えましたが、300や500のTAP係数をどうやって入力するのかが最大の課題となりました。 また、メモリーの配置はリンカー任せにしないと、トラブルの連続でしたので、そこも解決しなければなりません。
まずは、OMに習って、ハードの回路図からです。
dsPICはdsPIC33Fj64GP802です。 これを始めた2020年6月の時点では、dsPIC33CHがメインとなっている状況ですが、この最新のdsPICに対する情報は少なく、ひと昔前の33Fで勉強し、最終的には33CHに乗せ換えるという魂胆です。
後日、dsPIC33CHも使いましたが、SSBジェネレーター用としては使いにくく、このdsPIC33FJが最適なようです。
この回路図もOMのプログからのパクリですが、最初、VR1を固定抵抗で済ませていたところ、いざADCが動き出すと、片側からサチってしまうので、オリジナル通り、半固定抵抗に変えました。 しかし、後日、ミキサーを実装したところ、この半固定抵抗の合わせ次第で、サブキャリア漏れが大幅に悪化する事が判りました。 結局、この半固定抵抗は多回転タイプに変更し、かなりクリチカルな調整が必要なようです。 また、DACはL/R出力となっていますが、R-chはまだ配線されておりません。
dsPICでSDRを構築する、あるいは、その要素技術を公開するインターネットサイトは、沢山ある訳では無く、限られた情報を基に、自分で考えないとダメみたいです。 そして、その数少ないサイトのなかから、XC16によるプログラム例を探して、それを、今回の目的に合うようにアレンジしていくわけですが、コンパイルすると、エラーになったり、ものすごい数のワーニングが出たりします。 そこで、今まで読んだ事が無かった、XC16コンパイラのマニュアルを一通り読んでみました。 その中で、興味が沸いたのは、CCI という構文です。 これから、何年か、プログラムのコピペに耐えられようにしようとすると、従来の構文では、不都合が生じる可能性があり、後日、困るのは本人ですから、このSSBジェネレーターは、CCI 構文で進行する事にしました。 以前のXC16の構文を理解していない事が幸いし、以外と楽に乗り換えが出来ました。
まずは、CCI 準拠ですが、XC16-GCCの Option categorise:の中にあるPreprocessing and messagesを開き、その中にある、Use CCI syntaxにチェックマークを入れます。
次に、XC16(Global Options)を開き、Additonal options:の枠に -menable-fixed の文字を追加し、最後に下の方にある [Apply]を押します。 これはCCI とは関係ありませんが、 固定小数点形式の数値を使う為の処置です。 後日判明した事ですが、XC16(Global Options)ではなく、XC16-gccのOptionに記述するのが正しいようです。ただし、記事の中でも出てきますが、結局、固定小数点は使いませんでした。
今回、構想するSSBトランシーバーの原型は、「おじさん工房」のSDR-3にあることに触れましたが、使おうとしているdsPICは、SDR-3を構成するハードやソフトにはとても及びませんので、せめて、SDR-3の構成は維持したまま、基本部分を慣れ親しんだPICで実現しようともくろみます。 従い、SSBの発生も「おじさん工房」が提案した第4の方法とします。
まず、ADCやDACのサンプリングレートですが、ADCとDACのレートを一緒にし、余計な処理が生じないようにしようとすると、サンプリング周波数は、限られた周波数しか使用できません。
ADCのサンプリングレートはFcy=40MHzを80くらいから65535までの任意の整数で割った周波数に設定できます。 一方DACのクロックレートはfvco=160MHzから、1,2,4,8,16,32,64,128,256の8種類の数字のどれかで割り算した周波数(ACLK)をさらに256で割った周波数になります。 この条件から、DSP内部で作るミキサーのサブキャリア周波数を8KHz以上にしようとすると、ADCのクロックレートは40MHz/1024の39.0625KHzしか有りません。 これより低い周波数は、この1/2の19KHz台となり、ミキシングして得られたUSB信号がナイキスト周波数以上になってしまいます。 この39KHz台の場合、dspの処理時間は1024サイクル以下でなければならず、十分な特性のフィルターを作れない可能性がありますが、その限界が見えたら、さっさと、33CHに乗り換える事にします。
一方、DACも同じく39.0625KHzにするには、DACのクロック(ACLK)は39.0625 x 256=10MHzであれば良いので、Fvco(160MHz)を1/16に設定すれば良い事が判ります。
従い、Xtal OSCの周波数は、不動在庫している24MHzとして、これからPLL周波数の160MHzを作る事にしました。
このCファイルは、後述のクロック条件の下で、ADCから入力された信号をFIRフィルター処理して、DACから出力するまでのプログラムです。 Hファイルは、TAP係数を羅列したファイルです。
実装されているTAP係数は15TAPのFIR LPF用ですが、この実装方法は後程、詳しく説明いたします。
dsPICはそのクロック周波数を高くする必要から、PLLを使いますが、この設定です。
Xtal=24MHzを1/3に分周し、8MHzにします。 この時の分周比 3がデータシートに出てくるN1となります。 そして、PLLPREの設定値は3-2=1です。
また8MHzを20倍して160MHzのPLL VCO周波数をつくりますが、20がデータシートのMとなります。 そして、PLLDIVの設定値は20-2=18です。
160MHzを2分周して80MHzのfoscを作りますが、このときの分周比 2がN2となります。 そして、PLLPOSTの設定値は2-2=0です。
foscが80MHzとなると、PIC内のシステムクロックはその1/2の40MHzとなり、これで、このdsPICは最高速度で動作する事になります。
DACのACLKはfvcoを1/16しますので、APSTSCLRの設定値は 3 になります。
dsp部分で積和演算を行うために、リングメモリが必要となりますが、その設定をX_MODset()という関数で作っています。 ここは、理屈抜きで、このように記述すればOKですが、ここで、TAP数により設定値を変える必要があります。 リングメモリーの先頭番地は、リンカーで決められますので、それをベースに計算する事にしています。従い、プログラム上からは、判りません。
initmain()のなかで、各種初期設定をおこないますが、各設定の順序は、試行錯誤した結果です。 コメントのみ変更して再コンパイルしたら、動かないとか、訳の分からないトラブルを軽減(完全になくすではありません)出来ました。 また、CAST変換がうまくいかずに、時々動かないという問題を少なくするために、デイレーを何か所かに入れてあります。 うまく動かないときは、このディレーを長くしたり、場所を変えたり、時にはPICkit3を外してみたりして、現在の状態に落ち着いています。
後日判明した事ですが、プログラムが動かなかったり、単純な再コンパイルで動作不調になる原因の一つが、リングメモリーの制御用に指定したワーキングレジスタをリンカーが勝手に他の用途に使ってしまう事にあるようです。 リングメモリーを使う時は、リンカーが勝手にワーキングレジスタを使わないようにユーザーが設定しなければならないとコンパイラの説明書に書いてありました。 具体的な方法はこちらを参照して下さい。dsPIC33CHの記事ですがdsPIC33FJも同じです。
Timer3からの割り込みの中に、DSP処理が全部はいりますが、今回は、FIR LPFを動作させるだけのプログラムがインラインアセンブラで記述されています。 ここまでは、JA1QVM OMの記事の通りです。 ただ、絶対アドレスをリンカー任せにしていますので、その記述が異なります。
XC16内で定義した変数は、その先頭にアンダバーをつけると(XC16で定義した変数がabcの場合、_abcとする)、アセンブラでも同じ変数として認識してくれますので、この手法で、XC16で得た各変数の絶対アドレスをアセンブラの中に埋め込んで、絶対アドレスの問題は解決しました。
main()処理は、main()が回っているインジケーターとしてLEDの点滅だけです。 このLEDの点滅を割り込み処理の中にいれ、DSPの速度測定をしようとすると、ADCが動かなったり、ソフトが止まったりしますので、DSPの処理時間を調べる別の方法を考えねばなりません。
次に、このプログラムにFIR係数を実装する方法を説明します。
FIRフィルターの係数計算は、色々な方法がありますが、私はここで計算した値を使っています。
2023年12月以降、edgeのバージョンによってはリンクがつながらない事があるようです。 同じでは有りませんが、緊急避難としてフィルター係数の計算を公開しているサイトが有りましたので、そこからダウンロードしたアプリの使い方を説明した記事をこちらに置いています。 使い方はこのページで紹介しているものと異なりますのでご注意下さい。
TAP数や、遮断周波数の係数を入力して、出てきた数値の後ろに[,]がつくようにチェックマークを入れておきます。
まず、必要とする遮断周波数を正規化値として入れる必要があります。この正規化値は次のようにして求めます。
求めたい遮断周波数 / ADCのサンプリング周波数
求めたい遮断周波数が3000Hz、サンプリング周波数(ADCのクロック周波数)が39.0625KHzの場合、正規化値は3/39.0625=0.0768となります。 この係数の最大値は0.5です。
これで計算した結果が上のPC画面ショットのごとく、左側に縦に羅列されますので、これをすべてコピーします。 そして、float_Tap.hというファイルの中にペーストしておきます。 また、最初の行に、TAP数(Tap_Num)を記入して置きます。
このようにすることで、後程、TAP数や遮断周波数を変えたTAP係数の作成が簡単に行えます。
ここに示した、プログラムファイルは、ADCのデータをとりあえず3KHzのLPFを通しただけのものです。 このプログラムを作成する上で、一番トラブったのは、CASTでした。 TAP係数はdouble形式で提供されますので、XC16の中の変数に代入するとき、floatに変換し、これを固定小数点形式に変換する必要があります。 XC16 で固定小数点は _Fract と明示する事になっていますが、いくらやってもうまくいきません。 結局、float形式を16bitのsigned intに変換する事で、DACの出力が出るようになりました。 後日、判った事ですが、極性付き整数は、極性付き固定小数点の一形式と同じで、かつこの逆も言えるという事でした。
今後、これにサブキャリアによるミキサーと、USBのみ取り出すBPFを仕込み、約10KHzのサブキャリアで変調したUSBジェネレーターを完成させることにしますが、どうやって実装するかは、全く白紙状態です。 いつになることやら!
下のスペクトルは1KHz信号を加え、DACの出力をWave Spectraで表示させたものです。