16.93MHz All Mode SDR(Transceiver)

Xの投稿もご参照ください.

固定周波数(16.93MHz)のオールモードSDRトランシーバを試作した.構成は下図のとおり.

かつてSSBジェネレータと呼ばれていたものに相当する.SSBジェネレータは当然SSB(一部CWも)のみ送受可能だが,SDRであれば多モードにしてもハードウェアの規模は変わらないのでオールモードトランシーバにする.これにコンバータを接続すれば任意のバンドのトランシーバや受信機が構成できる.

ただ,SDRにありがちな「メニューを呼び出して操作する」ような形態ではなく,古い無線機のようにVRやロータリSWによって操作するものにした.そのためLCDを接続しなくてもつまみの方向で設定状態がわかるので,かつてのSSBジェネレータを使用するのとほとんど同じ感覚で使用できる.LCDを使用しない場合,Sメータを振らせる電圧を出力するようにした.

作成した基板と回路図.




以下の動画は,ファームウェアを制作し動作をさせたもの.


PBT動作
送信
パッシブDBMを接続して7MHzを受信

ほぼ満足できるファームウェアができたので,ハードの修正内容を反映した基板をあらためて製作することにする.最終的にはGithubでファームウェアを含め情報を公開予定.

P.S.
回路修正が必要となったため,今回の試作の残りの基板(未実装8枚)はボツとなりますが,もし必要な方がありましたら無償で差し上げます.ファームウェアも必要でしたら提供可能です(現時点ではファームウェアだけの提供はしません).ただし無保証,原則サポート無しという条件を承諾していただける方限定です.詳細はメールでご連絡ください.

終了しました.(12.17, 2023)

30kHz All Mode SDR(Receiver)

ギターエフェクタ用に作成したボードを使って,30kHzオールモード(AM, FM, SSB)レシーバの動作確認をした(最終的にはこれの前にダウンコンバータをつけて広帯域レシーバに仕上げる予定).

エフェクタ用基板

このボードはESP32-S3にI2SインターフェースのAudioCodecを接続した単純なものなので,実験用としても使いやすい.

プログラムは過去のブログ記事のとおりだが,中心周波数30kHz,帯域+-25kHzとするためサンプリング周波数のみ120kHzに変更している.

余談になるが,過去に動作していたプログラムが動作しなかった.原因は esp32 のボードマネージャのバージョン.2.0.10以降だとDSP関係の処理(FIRフィルタ)がまともに動作しない.DSPライブラリに不備があるようだ.そういえばFFTも,条件によって虚数部の符号が逆になったりインデックスが1つずれたり…  とりあえず2.0.9 を使っていこうと思う.

ADCにはPCM1808を使っている.これの動作保証は96kHzまでだが問題なく動作した.倍の192kHzでも動作したので120kHzサンプリングで動作させてもあまり問題はなさそう.
DACはPCM5102Aでこちらの最大サンプリング周波数は384kHzで余裕がある.

最終的にレシーバに仕上げるにはこれに前にダウンコンバータが必要で,一般的には直交ミキサを用い,その出力をIQ 2ch入力で受けて60kHz離れたイメージを除去する.また各モードで必要な帯域制限は信号処理で行う.

しかし,現実問題として直交ミキサをどんなに調整しても広帯域でかつ満足できる性能で動作させることは難しい.具体的にはあらゆる周波数で60kHz離れたイメージを十分減衰させる(つまりIQバランスを保つ)のは難しく,調整も面倒なので(物理的な)直交ミキサは使いたくない.

このような理由でダウンコンバート時のイメージはフィルタで切ることを前提にして,上図のように入力は解析信号(IQ 2ch)ではなく実信号(1ch)とした.

 


AM受信

 


FM受信


SSB(USB)受信


SSB受信(逆サイド)

フィルタ設計

過去の記事で,ESP32-S3でSDRを構築するためのコードを示してきたが,その中でフィルタの係数については具体的なものは示していなかったので,フィルタ係数を求める方法についてまとめておく.

ツールとしては GNU Octave を用いる.

以下に示すスクリプトを GNU Octave で実行すればフィルタ係数が記述されたヘッダファイルが生成される.

 

1.IIR LPF(8次エリプティック LPF)

%  This file name: "IIR_LPF_design.m"
%-----------------------------------------------
% To generate "IIR_LPF.h",
%    execute this script for MATLAB/GNU Octave.
%
%      Jan. 5, 2023  by T. Uebo
%-----------------------------------------------
pkg load signal;
clear;
close all;
clc;

%-- Modify this section if you need ----
fs=96e3; %[Hz]
fc=5e3; %{Hz]
Rp=1;  %[dB]
Rs=100; %[dB]
%----------------------------------------

% 8th LPF
ord=8;

% Elliptic LPF
[b,a]=ellip(ord, Rp, Rs, 2*fc/fs);

% Chebyshev LPF
%[b,a]=cheby1(ord, Rp, 2*fc/fs);

figure(1);
freqz(b,a,2048,fs);

[sos, g]=tf2sos(b,a);
g=g.^(1/(ord/2));
B0=g.*sos(1,1:3);
A0=sos(1,5:6);
B1=g.*sos(2,1:3);
A1=sos(2,5:6);
B2=g.*sos(3,1:3);
A2=sos(3,5:6);
B3=g.*sos(4,1:3);
A3=sos(4,5:6);


FID=fopen("IIR_LPF.h", "w");
fprintf(FID,"// %dth IIR Elliptic LPF\n" ,ord);
fprintf(FID,"// fs=%d[Hz]\n" ,fs);
fprintf(FID,"// fc=%d[Hz]\n" ,fc);
fprintf(FID,"// Ripple=%d[dB]\n", Rp);
fprintf(FID,"// Att=%d[dB]\n", Rs);
fprintf(FID,"// \n");
fprintf(FID,"//      b0 + b1*Z^(-1) + b2*z^(-2)  \n");
fprintf(FID,"// H(z)=--------------------------- \n");
fprintf(FID,"//       1 + a1*z^(-1) + a2*z^(-2)  \n");
fprintf(FID,"// \n");
fprintf(FID,"// {b0, b1, b2, a1, a2}\n");

fprintf(FID,"float biquad0[] =\n");
fprintf(FID,"{%e,%e,%e,%e,%e};\n",[B0 A0]);

fprintf(FID,"float biquad1[] =\n");
fprintf(FID,"{%e,%e,%e,%e,%e};\n",[B1 A1]);

fprintf(FID,"float biquad2[] =\n");
fprintf(FID,"{%e,%e,%e,%e,%e};\n",[B2 A2]);

fprintf(FID,"float biquad3[] =\n");
fprintf(FID,"{%e,%e,%e,%e,%e};\n",[B3 A3]);

fclose(FID);

 

2. FIR LPF

%  This file name: "FIR_LPF_design.m"
%-----------------------------------------------
% To generate "FIR_LPF.h",
%    execute this script for MATLAB/GNU Octave.
%
%      Jan. 5, 2023  by T. Uebo
%-----------------------------------------------
pkg load signal;
clear;
close all;
clc;

%-- Modify this section if you need ----
Nfir=256;
fs=16e3;  %[Hz]
fc=3e3;    %[Hz]
%----------------------------------------

b = fir1(Nfir-1,2*fc/fs, 'low',...
         blackmanharris(Nfir) );

figure(1)
freqz(b,1,2048, fs)
figure(2)
plot(b);

FID=fopen("FIR_LPF.h", "w");

fprintf(FID, "float FIR_LPF[]={\n");
for k=[1:Nfir-1]
fprintf(FID,"%e,\n", b(k));
end;
fprintf(FID,"%e\n", b(Nfir));
fprintf(FID,"};\n");

fclose(FID);

 

3. 複素係数FIR BPF

%  This file name: "CPLX_BPF_design.m"
%-------------------------------------------------
% To generate "CPLX_filter.h",
%    execute this script for MATLAB/GNU Octave.
%
%      Jan. 5, 2023  by T. Uebo
%-------------------------------------------------
pkg load signal
clear;
close all;
clc;

%--- Modify this section if you need --------
% Define Pass Band: f1(Hz) to f2(Hz)
f1=250;
f2=2850;

%Sampling frequency
fs=16e3;
%Number of Taps
Ndat=256;

%--Set a Window function---
%wf=ones(Ndat,1);
%wf=hanning(Ndat);
%wf=hamming(Ndat);
wf=blackman(Ndat);
%wf=blackmanharris(Ndat);
%wf=chebwin(Ndat);
%--------------------------------------------


%--- Calc. complex FIR coeff. ---------------
if (f1<f2)
  fa=f1+fs/2;
  fb=f2+fs/2;
else
  fa=f2+fs/2;
  fb=f1+fs/2;
end

ka=round( fa/(fs/Ndat) );
kb=round( fb/(fs/Ndat) );

fres=[ zeros(1,ka-1)...
       ones(1,kb+1-ka)...
       zeros(1,Ndat-kb)];

fres=[fres(Ndat/2+1:end) fres(1:Ndat/2)];

imp_res=ifft(fres);

Ich=[real(imp_res(Ndat/2+1:end))...
     real(imp_res(1:Ndat/2))];
Qch=[imag(imp_res(Ndat/2+1:end))...
     imag(imp_res(1:Ndat/2))];
Ich=Ich.*wf.';
Qch=Qch.*wf.';


%----Generate coeff. file ------------------
FID=fopen('CPLX_filter.h', 'w');

fprintf(FID, 'float CF_Re[%d]={\n' , Ndat);
for p=[1:Ndat-1]
fprintf(FID,'%e,\n', Ich(p));
end;
fprintf(FID,'%e\n', Ich(Ndat) );
fprintf(FID,'};\n');

fprintf(FID, 'float CF_Im[%d]={\n' , Ndat);
for p=[1:Ndat-1]
fprintf(FID,'%e,\n', Qch(p) );
end;
fprintf(FID,'%e\n',  Qch(Ndat) );
fprintf(FID,'};\n');

fclose(FID);



%---- Display results ------------------------
figure(1); plot(Ich);
ylabel('FIR coeff. (Real Part)');
figure(2); plot(Qch);
ylabel('FIR coeff. (Imaginary Part)');
figure(3);
[hr,fr]=freqz(Ich+j*Qch,[1], Ndat*16,'whole',fs);
fr=fr-fs/2;
hr=[hr(Ndat*8+1:end); hr(1:Ndat*8)];
plot(fr, 20.*log10( abs(hr)) );
xlabel('Frequency(Hz)');
ylabel('Response(dB)');
grid on;

figure(4)
subplot(1,2,1)
plot(fr, 20.*log10( abs(hr)) );
xlabel('Frequency(Hz)');
ylabel('Response(dB)');
grid on;
axis([f1-500, f1+500, -80 10]);

subplot(1,2,2)
plot(fr, 20.*log10( abs(hr)) );
xlabel('Frequency(Hz)');
ylabel('Response(dB)');
grid on;
axis([f2-500, f2+500, -80 10]);

Freeverb (Teensy Audio ライブラリ)

Teensy 4 でリバーブの処理をさせようと思い,ライブラリにあるFreeverb を試してみたところ,FIRフィルタと同様信号レベルが低下してくるとノイズが生じてくる(残響音の減衰の最後の方は特に顕著).やはりこれも16bit整数処理の影響かと思われるので,32bitフロートで書き直すことにする.

書き直すにあたってまず,ライブラリのソースを調べて具体的な処理内容を確認した.

Freeverbの中身はSchroederのアルゴリズムがベースになっていた.ただし,よく見かける 4個のComb filterと2個のAllpass filter によるものではなく,下図のようにそれぞれ2倍の 8個と4個になっていて,”High quality Reverb effect”との表記があった.

ただし,この処理ブロックは,残響のテイルの部分を生成するものなので,空間の残響をそれなりにシミュレートするには,Early reflection の生成や Pre delay の処理を追加する必要がある.

Comb filter は以下のようになっていた.

図のようにダンピング処理のための1次LPFを持っているので自然なリバーブが得られそう.
(このフィルタをBPFとかにすれば,Shimmer Reverb になるのかも?)

Allpass filter は以下のとおり.

ライブラリソースでは a=0.5 に固定されているようだが,その場合 1-a^2 = 0.75 でなければならない.コードをみるとこれ(1-a^2)も 0.5 になっているように解釈できた(自分の読み間違いかもしれないが).単なるバグなのか何か意図があってそうしているのかわからないがもしそうなら特性はAllpass にはなっていない.とはいえ残響音の密度を上げるという観点からは聴感上大きな違いが出るようにも思えないのであまり気にしないでもいいか… 書き直しの際はセオリー通り 1-a^2=0.75 にする.

Fully digital SSB generator(2)

1.試作した完全ディジタルSSBジェネレータ

ソフトウェアは前の記事で示したように,AF PSNの処理の後に極座標変換処理を行い,その絶対値を振幅,また位相の微分値とキャリア周波数の和を周波数として,DDSを制御するコードを追加した.

2.スペクトルの確認
キャリア周波数:7.1MHz
モード:LSB
帯域:300Hz – 2800Hz
入力:1kHzの正弦波

逆サイドバンド,キャリア漏れは全く確認できない.
帯域内の盛り上がりはおそらくノイズと思われる.上の写真のような作りのため当然かと.
基板をおこせば改善できそう.

動作の様子

Fully digital SSB generator(1)

1.はじめに
PSNでSSBを発生させる場合,AF PSN をディジタル化することで無調整で実用的なレベルのAFのIQ信号が簡単に得られる.
とはいえ,キャリア漏れとかサイドバンドサプレッションに関しては,結局その後のRF直交ミキサの性能や調整状態に依存してしまう(AF信号が理想的であっても).特にキャリア漏れについては周波数が大きく変わると再調整の必要があり広帯域にわたって調整なしで使うのは難しい.ということで,AF PSNだけでなくSSBジェネレータ全体をディジタル化したい.

2.AF PSN について
AF PSNの実現方法としては,入力(実信号 Ai )をヒルベルト変換しそれを虚数部 Aq とするのが一般的( Ai + jAq ).
時間領域で考えれば,ヒルベルト変換は広帯域にわたって同一振幅で正確に90度位相シフトすることが重要で,その点に着目して設計や調整を行う.

一方,周波数領域で考えれば,AF PSNは負(または正)の周波数成分をカットするフィルタである(AF PSNの記事).
これはヒルベルト変換を周波数領域で説明しただけのことなので,ヒルベルト変換でもフィルタでも変わりはない.実現手法が違うため具現化したときにそれに起因した差が生じているだけである.

周波数領域では次のように考えることができる.
実信号の正の周波数成分と負の成分は複素共役の関係にあるので,もとの実信号は単に Ai ではなく,正の成分 (Ai + jAq)/2 と負の成分 (Ai – jAq )/2 の和で,Ai=(Ai + jAq)/2 + (Ai – jAq)/2 となっているので,実信号の負の周波数成分をカットすることで自ずと Ai + jAq (の1/2)となる.

そもそも,実信号 Ai を正の周波数成分と負の成分に分離したときに,その虚数部 Aq を表すものがヒルベルト変換なわけで,「ヒルベルト変換操作で生成した Aq を虚数部として付加する」というのはエミュレーションであって,考え方としては本質的でないような気はする.

AF PSNがフィルタとみなせるならば,それにバンドパス特性を併せ持たせて,例えば,正の 300Hz から 3kHz を通過させる特性にすれば,BPFを別に設ける必要がない.

3.PSN式SSBジェネレータ(従来方式)
AF PSN で得られた信号(Ai + jAq)を周波数シフトすればSSBが得られる.具体的には目的周波数を ωc とすれば複素正弦波 exp(jωc t)=cos(ωc t) + jsin(ωc t)を掛ければよい.
複素SSB信号=(Ai + jAq) * ( cos(ωc t) + jsin(ωc t) )
最終的に必要なのは実信号(実部)なので,
SSB信号=Ai*cos(ωc t) – Aq*sin(ωc t)
これが直交ミキサを使う方法の原理で下図のような構成となる.

4.完全ディジタル化
Ai + jAq を 極座標形式に変換する.

これを目的周波数 ωc にシフトするために複素正弦波 exp(jωc t) を掛けると次式のように複素SSB信号が得られる.

最終的に必要なのは実信号(実部)なので,

この式からわかるように周波数 ωc のSSB信号を得るには,周波数 ωc のキャリア信号に対して,振幅をm(t)で,周波数をω(t)で同時に変調すればよい.
これは振幅変調機能のあるDDSを使えば簡単に実現できるので,ディジタルAF PSNとDDSを組み合わせれば,完全ディジタルSSBジェネレータが実現できる(下図).

当然,振幅あるいは周波数それぞれ単独で変調を掛けることもできるので,SSBだけでなくAMもFMも簡単に発生させることができるし,DDSの本来の機能によって任意の周波数のキャリアを生成できるので,オールバンドオールモードのジェネレータが完全ディジタルで作れることになる.

とりあえず,AD9951を使って実験してみたところ,動作は問題ない様子.

Si5351単体で3MHz以下の直交信号を出力する

Si5351Aは Multisynth の delay パラメータを使って直交信号を出力することができるが,パラメータの設定範囲が最大127のため,直交信号を出力できる範囲はおよそ3MHz以上に限られる.(Si5351で直交信号) そこで 別の方法によって 3MHz以下の直交信号を得ることにした.

Si5351の構成は図通りで,Mutisynthの分周比M0, M1をある値で固定しておき,PLLのフィードバック分周比Nを変化させて周波数をコントロールするのが一般的.

si5351_01.png

今回のポイントは,M0, M1も分数分周比をとることができるので,M0, M1によって周波数を細かく制御することが可能という点.ただ,Mの値の制御ではスムーズな周波数制御はできないと思うのでMの制御は位相差π/2を得るためだけの手段とし,周波数の制御はPLLの分周比Nの制御で行う.

Mの制御で希望の位相差を得るには,M0,M1を時間差をつけて変化させればよい.
例えば,M0, M1 = m’ としておいてPLLリセットする(これで位相が0にそろう).
その後, M0 = m  (m<m’,  M1 = m’  はそのまま) とすれば  f I>f Q となり
周波数差 fd = f I – f Q  が生じる.
これによって,f I の位相が f Q に対して時間経過とともに進んでいく.
経過時間を Td  とすれば,位相の進み θd は次式で表される.

eq1

よって,θd = π/2 となる Td は,

eq2.png

となるので,この時間経過後に M1 = m にセットすれば,π/2の位相差を持った信号が得られる.この操作は一度行えばよい.それ以降,m の変更がなく,かつ,周波数の変更をPLLで行えば位相差は維持される.

例として,2.0MHzの直交信号を得る場合について手順を示す.
条件は,m = 300,  fd = 4Hz,  Multisynthのモードは fractional.

  1.   fvco = 300*2.0MHz = 600MHz となるよう  N =  600MHz/25MHz = 24  にセット
  2.  出力周波数 f I, fQ = 2.0MHz –  4Hz  となるよう,
    M0, M1=600MHz / ( 2MHz-4Hz ) = 300.0006 にセット
  3.  PLLリセット
  4.  M0 = 300 にセット
  5.  Td = 1/(4fd) = 62.5ms 経過後に M1 = 300 にセット

これで2MHzの直交信号が得られる.以降,周波数を変化させる場合,例えば1.99MHzに変更するには,Nのみ変更し,N = 1.99MHz*300 / 25MHz = 23.88 にセットする.
ここで重要なのはPLLリセットしないこと.
fd は任意だが小さい方が位相差の精度がよいかもしれない.ただしTdが大きくなり待ち時間が長くなる.

以下動作例

出力周波数=1.18MHz
20200827_193438

出力周波数=588kHz
20200827_193537.jpg

 

保証された動作ではないかもしれないが,いまのところ変な動作はしていない様子.

SDR using dsPIC for SSB MODE

FM,AMモードはまだ納得できる状態ではないが,SSBに関しては問題なさそうなので,ファームウェア(ソースコード)などを公開した.

Source code,  Circuit diagram

IIR, FIR フィルタの特性を変えるためのツールも置いてある.

dsPICの内部処理は以下のとおり.

SSB_demod.jpg

原理的にはこれまで通り,複素係数フィルタで正(または負)の周波数成分をカットするフィルタ式.
コードを書いていて思ったのだが実際の処理内容はPSNと酷似している.なので,複素係数フィルタをAF PSN とみなしてもよいと思う.
hilbert 変換自体が負の周波数をカットするハイパスフィルタだし,一方,複素係数フィルタの虚数部は hilbert 変換器と同じものになっていることを考えれば.本質的に PSN方式 もフィルタ方式も同じということだろう(もちろん実現手法の違いはある).

FM demodulation (SDR using dsPIC)

FMの復調機能の追加を行った.
AMの復調ができているので,それを少しモディファイするだけでOK.
つまりAMは振幅を求めるがFMは位相を求めて微分すればよいので,下図のような構成になる.

FM_demod.jpg

tan の逆関数は安直にテーブル参照にした.math ライブラリのatanやatan2関数は処理が重くて使えなかった.
今のところSGからのFM信号はうまく復調できている.リミッタを深く効かせた通常のFM復調と違ってノイズは多め.この方式はI,Q信号の瞬時値から位相を計算するため,リミッタが使えないから仕方ない.

もう少しテストが必要かもしれないが,とりあえず SSB(USB/LSB),CW,AM,FM モードの受信が可能になった.

AM demodulation (SDR using dsPIC) (2)

Direct Conversion 方式でAM復調がうまくいった… 確かにほぼうまくいったのだが,問題があることがわかった.
ローカル信号のリークが周波数によっては予想以上に大きくなるところがあり,アンプをDC結合したためにリークの直流成分でアンプが飽和してしまい,その周波数では受信不能となる.飽和しないレベルであれば動作はするが,ダイナミックレンジが狭くなる.
直前の記事で,「直交ミキサでベースバンドに変換するDC方式はなかなか難しい」と書いた通りになった.

まず,アンプをAC結合に戻し直流による飽和が起こらないようにした.

次に,ローカル発振(VFO)周波数 ωc を受信周波数 ωm より ωi  低くして,受信したAM信号の周波数を ωi に変換し,これを増幅した後AD変換してdsPIC に取り込むようにした.
今回 ωi = 2 π 12.5kHz としている.

AM_demod2.jpg

その後信号処理で,上図に示すようにベースバンドに変換している.
(乗算器が4個必要なのは,複素数同士の掛け算のため)
LPF以降は前回と同じ.

dsPIC33FJの能力の限界に近い処理量のようだが,AM受信自体は前回と同様問題なくできている様子.受信周波数を500kHzから54MHzまで変えてみたが,受信できなくなる周波数は無いようので,しばらくこれで動作確認をしてみる.

AM demodulation (SDR using dsPIC)

dsPICによるSDRでSSBやCWはうまく受信できている.これに加えてAMモードも実装したい.AM受信はSSBモードでもできるが,キャリア周波数を完全に合わせないと受信信号のピッチが変わってしまう.会話なら問題も少ないが音楽は不協和音になってしまって聞きづらいので,やはりAMモードが必要だと思う.

AMの復調をするには,下図のように,IchとQchをそれぞれ2乗して加算したあと平方根をとればよい.

AM_demod

理屈は簡単だが,直交ミキサでベースバンドに変換するDC方式だと,キャリア周波数が0Hz(直流)に変換されるので実際にはなかなか難しい.なので多くは,キャリア周波数を0Hzではなく10kHzなどに変換し,それを信号処理してAM復調をする.

ただ今回使用しているdsPICの性能にあまり余裕がないので,上図のとおりDC方式にする.

LPFの出力は次式となる.eq1_2

AMの場合,A + m(t) > 0 なので,

eq3

これの直流分 A/2 をカットすれば復調完了で,式(3)にはキャリア周波数とローカル周波数の差の要素は含まれないので,チューニングのずれで復調される信号ピッチが変わるということはない.

それはいいのだが,実際はミキサの出力にはローカル信号のリークに起因する直流成分が加算される.DBMにしてもリークを0にすることは現実的には困難で,また厄介なことにリークの量は一定ではなく,周波数によって変化する.

リークは直流として生じるので問題なさそうに思えるが,そうではない.
リーク成分を vi, vq として,それらを考慮した場合のLPFの出力を I’ , Q’ とすれば,

eq4_5

このとき,出力は,

eq6

となる.面倒なのでこれ以上の式の展開はしないが,明らかに出力に歪が生じてしまう.チューニングがあっている(ωc=ωm)のときは歪だけだが,チューニングがずれると式(6)の第3,4 項によってビート成分とその高調波も出てくる.

結局,リーク成分vi, vq を除去することが必須であることがわかる.ただ厳密にはそれは不可能なので,条件をつけて譲歩するしかない.

その条件は,「厳密にチューニングがあう(ωc=ωm)ことはない」ということ.

もし ωc=ωm だったら,

eq7_8

となるが,残念なことに第1項と第3項はどちらも直流で分離不可能.

仮に式(7),(8)から直流分を除去すると次式となり,

eq9_10

出力は次式となる.

eq11

元の信号を全波整流したものとなってしまって復調できない
(  m(t) が正負の値をとることに注意).
AMの場合,A+m(t)が常に>0.そうなるようにAが足されていることに大きな意味がある.

 

…ということで,ωc=ωmのときはあきらめて,厳密にそうなることは稀だということに期待する.

ωc≠ωmならば,式(1), (2)をみればわかるように,I, Q は,周波数差|ωc – ωm|に相当する周期をもつ交流となる.リーク込みのLPF出力は式(4), (5) だが,これの時間平均をとれば,I,Qはともに0に収束し最終的に vi, vq のみとなって,リークによる直流分が得られる.得られた値を信号処理時にI, Q信号から引けばよい.

今回,平均処理を2~3秒間の移動平均としてみる.例えば,平均時間2秒なら,1/2=0.5Hz以上周波数がずれていればよい.AMで0.5Hz以内にゼロインするのは難しいと思うので実用的にはOKだろう.たまたま合ってしまう可能性はゼロではないけど,受信信号とローカル信号は全く無相関なのでその状態が長く続くとも思えない…(と希望的観測しておこう).最終的に約2.6秒とした.

ここまで書いて気付いたが,これってカットオフ周波数の非常に低いHPFで直流をカットしているのと同じことだった.ただカットオフ周波数が0.5Hzとかになるので,容量がものすごく大きなCが要る(10000uFとか)し,カットオフ周波数の調整も面倒そうなので,信号処理でやる意味はありそう.

実際の動作の様子はこちら( https://youtu.be/txrccQwB3Pc )

チューニングのずれによるピッチの変化がなくなりAMらしくなった.少しビートが残っている感じがするが,まあいいんじゃないかと思う.むしろチューニングの目安となっていいかもしれない.

ついでにAGC処理も追加しておいた.
ちなみにスピーカのすぐ上に見える回路はアナログでAGCをやろうとした残骸.
信号処理でAGCがうまくいったので使っていない.
また,直流分も通すためにミキサからAD変換までの回路をDC結合に変更した.

そろそろ基板におこせる段階かな.
その前にFMもやってみようか…

SDR using dsPIC33FJ64GP802 (2)

レシーバをテストしていて気づいたのだが,RF入力のレベルが小さくなっていくと,あるレベル(しきい値)でノイズゲートが掛かったようにいきなり無音となる.さらによく調べてみたら,しきい値付近のレベルだと信号やノイズがとぎれとぎれになってブツブツという音がする.どうもAD変換時の量子化が原因のようだ.しきい値はAD変換の量子化レベルであって,dsPICの場合,12bitなので,3.3V/4096=805 uV.
これ以下の変化は検知できず,一定値の信号として処理され結果として無音になる.また,これを超えるか超えないか微妙なレベルだと変なノイズが出る.

量子化レベル以下の変化を取り込む方法として,よく知られているものとしてはディザー信号を重畳させる方法がある.

つまり,量子化レベルより十分大きな振幅を持つ信号(ディザー信号)を目的信号に加え,量子化レベルを超えさせる.ディザー信号は,目的信号と周波数帯域が重ならないスペクトルを持つ信号とする(一般的には,目的信号より高い周波数領域に成分を持つノイズなど).

目的信号+ディザー信号をAD変換で取り込んだら,信号処理(フィルタ)でディザー信号の成分をカットすればよい.

数値計算で確認してみた結果を以下に.

入力信号は振幅0.1,周波数800Hzとする.

nodither
+/-0.5を量子化の区切りとする.
上段のグラフのように入力信号の振幅が0.1の場合,AD変換器で量子化されると中段のグラフのように一定値0となってしまい入力信号の情報は失われる.当然スペクトラムも下段のとおり全域で0となる.

次に,振幅50,周波数6.25kHzの正弦波をディザー信号として加えてみる.

dither

ディザー信号を入力信号に重畳すれば,量子化されても入力信号の情報は失われないことが
スペクトルをみればわかる.
不要な6.25kHzは,デシメーションフィルタがカットしてくれるので問題ない.

 

もう一つの例として,入力信号が量子化レベルをぎりぎり超える程度の振幅(0.55とした)を持つケースを以下に示す.

nodither2
入力信号は取り込めているが高調波成分が生じている.量子化された波形をみれば当然.

 

ディザー信号ありの場合.

dither2

こちらの方が,高調波成分が少なく,低ひずみで信号が取り込めているのがわかる.

 

実回路で確認.
下図のとおり,DACのLポートから6.25kHzを出力し,オペアンプの入力に加えた.

dither_circuit1

とりあえず,空中配線で…

20200620_225906

信号レベルが低くても無音となることはなく,変なノイズも発生しなくなった.

 

SDR using dsPIC33FJ64GP802

dsPICで受信用のAF PSN試作して動作確認まで行っていたが,それを使って実際に受信機を組んでみた.

Source code ,  Circuit diagram

動作の様子 Youtube

今のところ,AGCはもちろん,RFアンプも入力のフィルタもないが,それなりにうまく動作しているようなので,きちんとした受信機に仕上げてみようと思う.

AF PSN for DC Reciever

以前,SSB送信用のAF PSN をdsPICで製作した.
その後,受信用もできないかと気になりつつも,ずいぶん時間が経ってしまった.
もちろん,dsPIC33FJ64GP802にこだわらなければ何とでもなるが,
このdsPIC  1個でできれば面白いと思う.

よく知られているとおり,PSN受信機の構成は以下のようになる.

r01.jpg

AF PSNの部分をdsPIC33FJ64GP802 1個で構成したいのだが,
ADCが12bitであることと、2ch同時サンプルができないことが課題.
特に,12bitでは受信用としてダイナミックレンジが満足できない気がして,
いま一つやる気が起きなかったが,ようやく,「とりあえずやってみるか」
という気分になってきた.

受信用も,送信用のAF PSN同様,複素係数フィルタを使う.
r02.jpg

解析信号でブロック図を書くと以下のとおりで,送信用AF PSNもそうだが,
本質的にはフィルタ式と同じ.r03.jpg

 

結論から言えば,以下のような構成で,受信用AF PSNを実現できた.
r04.jpg

r06.jpg

r07b.jpg

r08b.jpg

一応設計通り動作している様子.
実際に受信機として使い物になるかどうかは,後日評価したい.

 

以下は詳細.

1.AD変換
送信用は,入力信号がほぼ話者の声だけと考えられるので,
アンチエイリアスフィルタが無くてもほぼ問題なかったが
受信用はそういうわけにはいかず,AD変換前にナイキスト周波数以上の成分を
十分に除去しなければならない.
カットオフ周波数がナイキスト周波数(1/2サンプリング周波数)に近い
シャープな切れ味のLPFを使って,
サンプリング周波数をあまり上げることなく十分な帯域をとりたいところだが,
そのようなフィルタは,カットオフ付近の移相量が大きく素子感度が高いため
温度変化など環境変化やその他様々な要因で特性が変化しやすい.
一番の問題は,そのようなフィルタが2個(I, Q)必要なことと,
それらが常に同じ特性でなければならないこと.
したがって,素子感度を下げ2個フィルタの特性のばらつきをできるだけ抑えたい.

そのためには,カットオフ付近で位相がなだらかに変化するフィルタが望ましいので,
ベッセルフィルタがよいと思う.
当然,カットオフ(振幅)特性もなだらかになり,シャープな切れ味にはならないので,
アンチエイリアスの能力は低下する.
そこでサンプリング周波数をできるだけ高くする.今回は100kHzとした.
また,dsPIC33FJ64GP802は48MHzのオーバークロックで動作させる.

I,Q 2ch同時サンプルはできず,2μs程度の遅延がありサンプリングタイミングがずれる.
2μsは,例えば3kHzにおいては,位相で 2.16deg に相当する.
これは無視できるレベルではないので,後段の複素係数FIRフィルタで
この遅延を補正することにした.

 

2.デシメーション用 IIRフィルタ
100kHzサンプリングですべて処理できればいいのだが,
dsPIC33FJ64GP802の性能では難しそうなので,
サンプリング周波数を1/8(12.5kHz)に下げる.
そのためには,後段の複素係数フィルタのナイキスト周波数(6.25kHz)以上の成分を
十分に除去しなければならない.
(このIIRフィルタのサンプリング周波数は100kHz)
I,Q 2chのフィルタが必要だが,ディジタルフィルタなので,
特性に違いが生じたり変化する心配はない.
できるだけシャープなカットオフ特性をもったLPFを使いたいので,
今回は,4次Elliptic LPF にしてみた.
IIRフィルタは設計が面倒だが,動作時はFIRより少ない計算量で済む.

2次IIRフィルタは以下のような構成になる.4次はこれを2段直列に接続する.

r05.jpg
kは本来不要なパラメータだが(k=1でよい),dsPICのように固定小数点演算の場合は,
計算途中で結果がオーバーフローしないようにk>1とする必要がある.
今回は k=4 とする.

IIRフィルタの設計には,GNU Octave (フリーウェア)を使った.
設計用のスクリプト ( IIR_ellip_LPF_design.m ) を実行すれば,
b0/k, b1/k, b2/k, a1/k, a2/k, k を定義するヘッダファイルが生成される.
signal パッケージを使うので,Octaveを起動したらコマンドラインで,

pkg load signal

とタイプしておく必要がある.

次に,dsPICでIIRフィルタを実行するソースコード(dsPIC33,DSPライブラリ使用)を
以下に記載しておく.

//— IIR ———————————–
//4th IIR Elliptic LPF coeff.
//fs=100000[Hz],  fc=3000[Hz]
//Ripple=1[dB],  Att=70[dB]

fractional _XDATA(2) IIR_coef0[] =
//{b0/k, b1/k, b2/k, a1/k, a2/k, log2(k) }
{ 53, -26, 53, 15311, -7199,  2};

fractional _XDATA(2) IIR_coef1[] =
//{b0/k, b1/k, b2/k, a1/k, a2/k, log2(k) }
{720, -1161, 720, 15706, -7794,  2};

fractional _YBSS(2) Z_Re_0[5];
fractional _YBSS(2) Z_Re_1[5];
fractional x, y;

void IIR_Ellip_LPF(void)
{
//– 1st stage——————————————
Z_Re_0[0]=x;
y=VectorDotProduct(5, &Z_Re_0[0], &IIR_coef0[0]);
y<<=IIR_coef0[5];
Z_Re_0[2]=Z_Re_0[1];  Z_Re_0[1]=Z_Re_0[0];
Z_Re_0[4]=Z_Re_0[3];  Z_Re_0[3]=y;

//– 2nd stage——————————————
Z_Re_1[0]=y;
y=VectorDotProduct(5, &Z_Re_1[0], &IIR_coef1[0]);
y<<=IIR_coef1[5];
Z_Re_1[2]=Z_Re_1[1];  Z_Re_1[1]=Z_Re_1[0];
Z_Re_1[4]=Z_Re_1[3];  Z_Re_1[3]=y;
//output:  y;
}

DSPライブラリ関数にはIIRフィルタもあるが,処理速度が遅かったので
VectorDotProduct 関数を使って自作してみた.
これはI chのみなので,もう一組IIRフィルタが必要となる.

伝達特性(設計値)は,以下のとおり
(横軸はナイキスト周波数50kHzで正規化)r09.jpg

 

3.複素係数FIRフィルタ(PSN)
サンプリング周波数とタップ数が違うだけで送信用と同じ.
今回128タップとしている(これが限界).

FIRの係数を求める際,AD変換の遅延を補正するよう考慮している.
具体的には,遅延をTとして Im側にのみ周波数特性にexp(- j2πf T) を掛けている.
 Source code,   Circuit diagram, and  Octave Script for filter design 

FIRフィルタ

前回AF PSNを複素係数のFIRフィルタで作成したが,その係数を変えれば任意の周波数特性を持ったフィルタが作れることはいうまでもなく,AF PSN はその一例にすぎない.

FIRフィルタの係数は,周波数特性を逆フーリエ変換すれば得られるので手間はかからない.とはいえ手計算できるものでもないので,AF PSN 作成の際に,係数を求めるために作ったツールをここにUPしておいた(Calc_Coeff_of_CPLXBPF.m).

これは,拡張子mが示すようにMATLAB,GNU Octave 上で動くスクリプトである.
GNU Octave はフリーウェアでこちらから入手でき,Windows の場合,octave-4.4.0-w64_1-installer.exe ならインストールは容易かと思う.

スクリプト内でパスバンドの周波数を書き換えて実行すれば,その特性を実現する係数を定義するヘッダファイルが自動生成される.
これをincludeしてコンパイルすれば,設定どおりのパスバンドを持ったBPFが実現できる.
複素係数フィルタであるが,実数部のみ使用すれば普通のフィルタと同じである.

さて,前回製作したAF PSNであるが,パスバンド内のリップルとパスバンド外の減衰特性が気になった.周波数特性を求めたところ下図のようになった.

Rect

理由は,窓関数を使用していなかった(=矩形窓)ためである.
そこで,フィルタ係数に窓関数を掛けてみた.
(窓関数はスクリプト内で指定できるようにしてある)

Hann 窓
han

Hamming 窓
hamming

Blackman 窓
blackman

Bartlett 窓
bartlett

使用する窓関数としては Hamming か Blackman になるだろう.
Blackman のほうが減衰特性がなだらかであるが,それでも十分急峻な減衰特性なのでこれがよいと思う.
(ヘッダファイルとCソースファイルを更新しているので,日付を確認の上ご利用願います)

音響ホーン

以前は40KHzのトランスデューサ(写真左)を使ってバットディテクターを製作していたが,
40KHz付近しか観測できない,という制約があった.
今回のバットディテクターでは,
広帯域で超音波を観測するためにMEMSマイク(写真右)を使っている.
ただ,トランスデューサは40KHz付近の感度は高く,
40KHzに限った感度の比較では,MEMSマイクは今一つな感じがする.

20180311_133045~2

写真を見ればわかるように,
MEMSマイクの音響ホールは直径0.5mmしかない.
トランスデューサの方は,内部の振動板らしきものは,直径8mmほどある.
開口面積がMEMSマイクの方が格段に小さいため,
感度が悪いのも無理はない.

そこで,音響ホーンを用いて開口面積を大きくし,感度の向上を試みた.

20180311_131230~2

3Dプリンタで作成した超音波用の音響ホーン.

定量的なデータはとっていないが,聞き比べた感じでは,
トランスデューサと同等以上の感度が得られているようだ.

バットディテクタ

知人からの相談がきっかけで,
新しい方式のバットディテクターを開発,試作した.

詳細はこちら

バットディテクターは,コウモリの出す超音波を可聴音に変換するもので,
主な方式として次の3種類のものが従来からある.

・ヘテロダイン式 (Heterodyne)
・フリークエンシーディビジョン式(Frequency Division:周波数分周式)
・タイムエキスパンション式(Time Expansion:時間軸延長式)
Wikiより.

これらの方式では,

・ヘテロダイン式
チューニング操作による探索が必須.周波数が大きく異なるものは同時観測できない.
・周波数分周式
レベルが最も高い信号しか観測できない.周波数以外の情報が失われる.
・時間軸延長式
リアルタイムで観測できない.

といった短所がある.

今回開発したのものは,
超音波領域の信号のスペクトルを周波数方向に圧縮し
全てを可聴音の領域に詰め込む方法.
「スペクトラム圧縮式」としておく.
この方式では,従来方式の上記欠点が改善される.
つまり,チューニング操作不要で,
周波数に関わらず同時に複数対象を
リアルタイムに観測できる.

sc01

信号処理方法としては,下記のように2種類考えられる.
今回は,下の方法で試作をしてみた.
20171212-2