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 

Signal Meter

VFOのダイアル表示で行った画像処理を使って,シグナルメータを作ってみた.
バックグラウンドにスケールイメージのビットマップを貼り付けておき,
その上を指針が1つ動くだけなので,VFOのダイアル表示よりは処理は軽い.
とは言え,オブジェクト(今回は指針だけだが)のローテーション,
アンチエリアシングやバックグラウンドとのアルファブレンドといった処理を行い,
16bitカラー表示をするので,それなりの性能のMPUが必要かと思い,
PIC32MX250F128B を使ってみた.
(USBは使わないので,PIC32MX150…でいいのだが手持ちがあったので)

20190925_220237.jpg

20190925_220317.jpg

20190925_221951.jpg

動画はこちら

96x64ピクセルのOLEDにしては,きれいな表示ができた気がする.

VFO(6)

ディジタルVFOの場合,周波数ステップの切替えが必須かと思う.
ただ,実際操作してみるとステップ切替え操作はかなり煩わしいもので,なんとかしたいと思っていた.
切替え頻度を減らすにはパルス数の多いエンコーダを使えばよさそうだが,今一つスッキリ解決するような気がしない.それにエンコーダが高価.

そこで,特に目新しいものではないが回転速度を検出してそれに応じて連続的に周波数ステップを変化させるようにしてみた.ここに動画をUPしておく

今回試した方法は次のとおり.
ダイアル速度を vd,加速を始める速度しきい値を vth として,次式のように変数 L に積算していく.
(1) vd>vth のときのみ, L=L + (vd – vth);

また,速度 vdが 0 のときは,減速用の定数をRdec として,
(2) L=L – Rdec;   ただし,L<0 ならL=0;

次に,Lの値からRaccを加速率の定数として次式でステップ数を決める.

(3) ステップ=最小ステップ + Racc*L^n;

現状,n=2 としているが,他の定数も含め操作感に関わる値なので,いろいろと調整するといいと思う.また際限なくステップが大きくなるのを回避するためには,Lの上限を設定すればいい.

ダイアルの速度の検出は簡単で,エンコーダカウント値の絶対値が速度そのものなので,特に何か処理が必要なことはない.
公開しているソースで言えば,count という変数の絶対値が速度( vd=|count| ).
理由は,count はメインループの1回の処理時間当たりのカウント値なので,その絶対値はダイアルの速度に比例したものとなるため.

結局,メインループの処理1回で変化させる周波数Δfは,

(4)    Δf=count*( 最小ステップ + Racc*L^n );

動画は,
vth=2, Racc=0.002, Rdec=1.0, n=2, 最小ステップ=10[Hz]
で動作させたもの.

個人的には,ステップ切替えスイッチは無くてもいい,と思えるくらいの操作感になっていると思う.

VFO(5)

VFOの回路図とソースコードを下記に公開しておいた.
Circuit diagram  and  Arduino Sketch
youtube Movie

 

画像表示の際アンチエイリアス処理を施しているので,まずその違いを以下に.

アンチエイリアス処理なし

アンチなし.jpg

アンチエイリアス処理あり

20190212_234133.jpg

(数字のフォントが違うのはアンチエイリアス処理とは関係ありません…)

この違いが重要かどうかは人それぞれかと思うが,個人的には上のアンチエイリアス処理なしの表示だと気分がよくない.

次に,LCDとOLEDの比較を.

まず,LCD

20190212_220612.jpg

20190212_220500.jpg

入手できるほとんどのLCDは視野角が広くなく,見るべき方向が規定されている.
このLCDは向かって右から見ると非常にきれいだが,左からみると線は欠けるし色合いもおかしい.
アンチエイリアス処理による微妙な輝度を再現できていない感じ.

たぶん携帯電話用のLCDだと思うので,縦において少し下方から見るように作られているのだと思う.実際ほとんどのLCDのデータシートには,View angle : 6 o’clock と表記がある.

OLEDの場合.

20190212_224237.jpg

20190212_224229.jpg

当然ながら,どの角度から見ても見え方に変化はなくきれい.

OLEDが100%よさそうだが欠点もある.

20190209_021913.jpg

明るい色のベタはむらが出るし(写真ではわかりにくいが.縞が見えるのはスキャンによるもの),ドライブ電流が足りなくなるのか暗くなる.
「一部だけ白」などはきれいだか,「全面白」とかはあまりよくない.

結論としては,暗いバックグラウンド色で使うならOLED.
明るいバックグラウンド色にするなら見る角度限定でLCDか…

VFO(4)

LCDへの表示がうまくいったので,次はOLED(NHD-1.69-160128UGC3) に表示させてみる.
これのコントローラチップは,SEPS525
16bitごとにCSの区切りが必要なのかどうか読み切れなったが,なんとなく「当然必要でしょ」という雰囲気が感じられたので,LCDのときのようにはいかないかも… と思いながら,とりあえず同じ転送プログラムを試してみた.
やはりNG…
spi.write16() を使って1ピクセルずつ送るとOKだったので,16bitごとにCSの区切りが必要のようだ.
そこで転送プログラム以下のようにしてみた.

s2.png

ブロックサイズを16にしてやることで,16bitごとにCSがネゲートされうまくいった.

20190212_234133.jpg
しかし,CSが頻繁にネゲートされるのでその時間(1回あたり600ns程度)が加わり,転送時間が25ms程度になってしまった.
これにダイアルイメージ描画時間35msとあわせて,更新レートはおよそ60msとなった.
これくらいになると動きのスムーズさがいま一つと感じるのは否めない.
RX621のシステムでは8bitパラレル転送+描画時間20msで更新レートが約30ms程度だったので,それに比べるとかなり動きが悪く見える.

何とかしたいところだが,これ以上プログラムの最適化で何とかできるようにも思えないので,デュアルコアを活用し描画処理と転送を別のコアで行うことで速度UPを試みる.

こちらのサイトを参考にさせていただき,メインのLOOPとは別のコアで動作するもう一つのタスクを作った.

setup()内で,

xTaskCreatePinnedToCore(task0, “Task0”, 4096, NULL, 1, NULL, 0);

として,次の関数を用意すればいいようだ.

void task0(void* arg)
{
while (1)
{
//SPI転送処理
delay(1);
}
}

delay(1) がないとシステムリセットが繰り返し発生した.
よくわかっていないが,おそらくOSに処理を返す必要があるからだろうと推測.
また,同じディレイ時間にも関わらず delayMicroseconds(1000) ではNGだった.

とにかく,これでloop() とtask0() が別コアに割り当てられ同時に動く(と思う).
あとは,この2つの処理間でのデータの同期に気を付ければいい.

というわけで,
一時はあきらめて,IOピンがまだ余っているのでOLEDはパラレルでいこうか…,などと考えたが,OLEDでもSPIで更新レートを50ms以下になんとかすることができた.

もっと簡単にRX621からESP32に移行できると思っていたが,思いの外苦労してしまった.
とは言え,初めてESP32を使ったのにもかかわらず,短時間でそれなりのものが組めたのは,いろんな方がネットにUPしてくれている情報のおかげなのは間違いない.

感謝の意味を込めて今回作成したソース(VFOsys)をこちらに公開しておく(無保証,サポートなしで).
エンコーダを回せば,ダイアルが動き(動画)その周波数が出力されるだけのものなので,そこからはこのソースをベースに各々機能を追加していただければと思う.

とにかく,ようやくシステムがうまく動き出したので,次はLCDとOLEDの比較をしてみよう.

VFO(3)

RX621で開発を進めていたところESP32が気になってきて調べてみると,
さらに安価で動作クロックも速く,しかもデュアルコア…
これは乗り換えるしかない,ということで  ESP32-DevKitC  を入手.20190206_125309~2.jpg
Arduino環境で開発できるようで,こちらを参考にさせていただきセットアップ.

ESP32のIO数があまり多くないので,ディスプレイはSPIで制御することにする.
幸い,OLED(NHD-1.69-160128UGC3) は,シリアル/パラレルどちらの接続もできる.
同じ解像度(160 x 128)のシリアル接続のLCDも用意して比較することにした.

20190213_024447.jpg

回路は下記のとおり.
LCDとOLEDは排他利用となる.Si5351は秋月電子のモジュール.

VFO.PNG

20190212_233108.jpg 20190212_233146.jpg

ディスプレイのインターフェース信号の配置が気持ち悪いが,ESP32内蔵のSPIを使うには
こうなってしまうようだ.
任意のピンでSPIができるようだが,その場合ライブラリによるソフトウェアSPIになり,転送速度がかなり遅くなるらしい.
以前,ダイアル表示のカウンタを作ったとき経験だが,ダイアルイメージの更新レートがおおよそ50ms以上になってくるとダイアルの動きがスムーズに見えなくなってくる感じだったので,これ以下にはしたい.

SPIのレジスタを直接操作しないと無理だろうなと思い,いろいろ調べてみると,
こちらのサイトに手掛かりがあり参考になった.
ESP32のリファレンスマニュアルも読んでみると,SPIには32bit x 16 (512bit)のバッファがあって,そこにデータを貯めておき(例では,半分の256bit),これを1ブロックとして一機に送出する方法らしい.これを必要回数繰り返す.
今回は,16bit/pixelx128x160=327680 bit 転送しないといけないので,
バッファをフルに使えば,327680/512=640回 となる.

以下のようなプログラム(抜粋)を組んで動作を確認した.
配列GRAM65k[][](16bitカラーのイメージデータ)をすべてディスプレイに転送する.

s1.png

オシロで波形を見てみると,1ブロック転送中はCSはアサートされたままになるようだ.
16bitごとにCSを区切らないといけないのでは?と思ったが,どうやらST7735やILI93xxといったディスプレイコントローラはその必要はないようだ.

肝心の転送時間だが,SPIクロック周波数が27MHzでおよそ12ms だった.
クロック周波数がディスプレイコントローラの動作範囲を超えているが,
「実力的にOK」との情報が多数あったのでそれに期待.

結果としては,上手く動作した.
20190212_220612.jpg

ただ,転送時間は12msだがダイアルイメージの描画処理に35msほどかかっている.
RX621のときは20ms以下だったのでかなり遅くなってしまったのは予想外だった.
CPUクロックが2倍以上なのにどういうことだろう?
当然ながら,速くなることを期待してたのでかなりテンションが下がる…
バックグラウンドで動いているfreeRTOSのせいかもしれないし,
デュアルコアも活かせてないし仕方ないか…

でもまあ,CPU自体RXよりかなり安価でRAMが520KBもあるのは捨てがたい.
現状,更新レートは目標通り50ms以下(12ms+35ms)にはなっていて,視覚的にもあまり違和感はないのでこのまま進めることにしよう.

次は,OLEDで動作確認しないと.

VFO(2)

アナログダイアル表示のVFOシステムがようやく具体的になってきた.
以前つくったカウンタのハードはほぼそのままで,ファームだけを新しく作ろうと思っていたが,コスト下げるためにハードも新しくすることにする.

CPUにPIC32MZを使っていたが,高いのでRX621にする.
基板を起こすまでは秋月のボードでデバッグできるので,その辺りも選択理由.
ただし,RAMが96kBなのでカラーディスプレイの解像度を,160x128に落とさざるを得ない.ただ,それもコストダウンにつながるのでいいかと…
160x128だと画面が1.7~1.8インチ.小さすぎないか少々気になるが,機械のパネルに配置するには,むしろこれくらいの大きさがちょうといいかもしれない.

ところが,LCDを探していると同じ解像度と大きさのカラーOLEDが見つかった.

NHD-1.69-160128UGC3

3000円を超えるのでどうしようか迷ったが,一度使ってみることにした.
(コストダウンしようとしているのに本末転倒…)
LCDは,見る角度で色合いが変わったり,見にくくなるのが非常に気になっていたため
それが解消されるなら,ありかなと思う.

とりあえず表示だけさせて雰囲気を確認.

oled1.jpg
アンチエイリアス処理をしているので輪郭が少々ぼやけるが,画面が1.7インチと小さいので,目視ではあまり気にならない.
この解像度(160x128)では,アンチエイリアス処理をしないと微妙な角度の短い線などは,見るに堪えない形状になる.

こんな角度でも色合いや見やすさは変わらない.20190122_125705_Burst01.jpg20190122_125736.jpg

よさそうなので,もう少しファームを作り込んでみた.

oled5.jpg

あとは,Si5351AからLo 信号と,必要に応じてCar信号 を出すようにするのと,
カウンタと同様,周波数オフセット値の設定など自由にできるようにする機能を
追加していく.

スペアナ制御ツール

先日,アンリツのスペアナMS2661Aを中古で入手した.
9kHz~3GHzの帯域で,RBWの最小は30Hz(オプション付)と
性能的には納得して購入したが,測定データの保存方法で悩んでしまった.

20181010_061554

メモリインターフェースオプションが付いていて,
PCカードメモリにデータを保存できるようだが,
今時PCカードメモリなど入手困難なので,SD用PCカードアダプタを使ってみた.

結果は残念ながら,”メモリタイプが異なる”とのメッセージが出てNGだった.
このスペアナが作られた時代にはSDは無かったのかもしれない.

仕方がないので,GPIBかRS232Cポート経由で,
データをPCに転送して表示・保存することにした.
ただ,プログラムを作る必要があるので少々面倒だ.
(なので,できればメモリインターフェースを使いたかった)

測定器といえばGPIBというところだが,
PC側にもGPIBインターフェースが必要になり,面倒なのでRS232Cを使うことにした.
ただ,通信速度が9600bps と,今時としてはかなり遅い感じがするのは否めない.

簡単なプログラムを作って試したところ,うまい具合にデータの取得・表示ができた.

42641369_1386471288151860_1717727983411658752_n

42727699_1386471231485199_1732282193058725888_n

必要な人は他にはおそらくいないと思うが,Windows10で動作するMS2661A用の
データキャプチャソフトをこちらに置いておく (  MS2661A.exe ) 

センター,スパン,スタート,ストップ周波数に関しては,
このソフトからリモートコントロールできる.
通信速度が遅いので,データの取得には  3, 4秒かかる.
これが速ければ,リアルタイムでPCに波形表示ができるのだが・・・

FT817ND   433MHz とその第2高調波
FT817_430M

433MHz拡大
FT817_433.000MHz

 

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ソースファイルを更新しているので,日付を確認の上ご利用願います)

AF PSN

PSN方式でSSBを発生させるためのAF PSNを試作した.01
PSN方式のSSBジェネレータは上図のような構成であるが,大概AF PSNの実現がネックとなる.

アナログ回路でAF PSNを実現する方法としては,ナガード型PSN,多段のオールパスフィルタ,PPSNなどが知られている.
これらは,性能が十分でなかったり,回路規模が大きい,部品定数の精度が要求される,など少々難しいところがあり,さらにアナログであるが故,環境(周囲温度など)の変化による特性の変化にも気を付ける必要がある.

そこで今回はAF PSN をディジタル信号処理で実現することにした.これは近年よく見かけるSDRと同様なものである.

まず,SSBの発生方法から確認していく.SSBの生成方法はシンプルに考えれば下図のとおりである.
02
周波数シフトは,AF PSNの後の直交変調器で行われるので,
結局AF PSNとは,AF信号の負(あるいは正)の周波数成分を除去するものである.

ならばバンドパスフィルタ(BPF)で実現できそうだが,フィルタの周波数特性は,下図の上段に示すように,正の周波数領域と負の領域で対称なものしか実現できない(実係数フィルタの場合).

03

しかしながら,フィルタの係数を複素数にすれば(複素係数フィルタ),上図下段に示すような非対称な周波数特性をもったフィルタが実現できる.

複素係数フィルタを用い,信号を複素数表現すれば,SSB生成の流れは下図のようになる.
04

今回はこの複素係数フィルタを,dsPIC33FJ64GP802 を用いディジタル信号処理で実現してみた.下図のように,複素係数フィルタの出力の実部がAF PSNの0°出力,虚部が90° 出力に相当する.
05

 

複素係数フィルタの構成は,512Tap FIR フィルタとした.
06

 

フィルタの周波数特性(設計目標)は下図のとおり.
+300Hz~+3kHzのバンドパスで,負の領域はすべて阻止する.
Fres_cmplx

 

これを逆フーリエ変換し,FIRフィルタの係数を求めた.
サンプリング周波数は,使用するdsPICの性能により 約10kHz に設定した.

フィルタ係数(実部:R0 – R511)
FirCoeff_re

フィルタ係数(虚部:Q0 – Q511)FirCoeff_im

複素係数フィルタのプログラムのソースコード  Source  code

dsPIC周辺の接続は下図のとおり.07

 

動作確認の結果を以下にまとめておく.

20180706_071500 dsPIC

入力200Hz
f0200

入力300Hz
f0300

入力1000Hz
f1000

入力2000Hz
f2000

入力2900Hz
f2900.jpg

入力3000Hz
f3000

これらの結果を見る限りは使い物になりそうな感じがする.
部品選別や調整などなしに,プログラムを書き込みさえすればこれと全く同じ結果が得られる.
あとは実際にSSBジェネレータにしたときにどのような品質の信号になるか,確認が必要だろう.

VFO

カウンタ基板にはSi5351が実装できるようにすでにパターンを作っているので,
次にダイアルイメージ表示のVFOをやってみようと思う.
カウンタの場合はすべて受け身の動作でよかったが,
VFOとなると,システムから考え直さないといけない.

20180507_211713~2

周波数はエンコーダで設定するが,
周波数に応じて外部のバンドパスフィルタを切り替える,
などということも必要となるから,そういう制御信号をださないといけないし,
どの周波数のとき,どのフィルタを使うのか,
そこはユーザが設定できるようにしないといけない.
Si5351を使うなら,もちろんキャリア信号も出したいから,
その周波数の設定と,モードによっては受信時はオフに…
と,考えたらきりがない感じだが,少し時間をかけて検討しよう.

周波数カウンタ

周波数カウンタは,今では格安のキットなどもあるので,
今更…な感があるが,今回少々変わったものを作った.
とはいえ,実用性がよいとか,性能が向上したとかではない.

カウントした周波数を数値ではなく,
あえて読み取り精度が低下するダイアルイメージで表示する.
20180415_132715

昔,ラジオなど作るときに一番工作に苦労したのが周波数表示ダイアルだった.
今は,周波数カウンタやPLL,DDSといったデバイスがあるので
そのようなダイアルは不要であるが…
とはいえ,ディジタル表示よりもダイアルがいい,と思うことがときどきあり,
そのあたりが製作の動機である.

表示には2.4インチのTFTカラーLCDを使った.
CPUはPIC32MZEF.
SRAMが512kBあるので外付けメモリなしでOKだった.

 

表示のレイアウトなど,気分でいろいろと変られるように設定用のPCアプリもつくってみた.
”dcon.exe”

dconfg

以下は設定例.
sample00

完成品を¥10,000 で頒布しています.
お問い合わせのフォームからご連絡ください.

Si5351で直交信号

直交信号が得られると非常に有用なので,Si5351で試してみた.
MultiSynth のDelay機能を使えば,ch間の位相差(時間差)を設定できる.
結論としては,3MHz以上で直交信号を得ることができた.

サンプルコード(Si5351Aの設定に関する部分のみ)を修正しておいた.
ch0, ch1 に出力されるようになっている.

以下詳細.

Delayは,td= N/(4*fvco) —-(1)
で,N は,0~127の範囲で設定できる.

出力周波数は, fout=fvco/(M*R) —- (2)
(M:MultiSynthの分周比,R:出力dividerの分周比)

90度位相差に相当するDelayは, td=1/(4*fout) —- (3)

(1), (2), (3) より,
N=M*R

つまり,fvco と fout の比(fvco/fout )を設定すれば
90度の位相差となる.

ここでNの値として設定できるのが最大127まで,というのが制限になる.
fvcoの下限は375MHzであるから,
(fvco=fxtal*(a+b/c) で,a>=15, fxtal=25MHz.よって25*15=375 )

375/127≒2.95MHz.これが,直交信号が得られる下限となる.

ちなみに,VCO自体の周波数範囲は,
使用したチップの実測で 200MHz~1160MHz くらいあるようで,
意外と広帯域である.

3MHz
20170314_3M

7MHz
20170314_7M

14MHz
20170314_14M

21MHz
20170314_21M

28MHz
20170314_28M

50MHz
20170314_50M

Si5351

Clock Generator Si5351A搭載のモジュールを入手したので動かしてみた.
今回作成したサンプルコード
(Si5351Aの設定に関する部分のみ)を置いておく.

これだけで2.5kHz ~ 200MHzの任意の周波数を発生できる.
周波数分解能は出力周波数にもよるが,約6Hz(150MHz~200MHz時)
出力周波数が低いほど高分解能になる(Divider で分周するため).
20170313_1

7.000000MHz 出力
20170313_7M

受信機でモニタした限りでは,通信に使えそうなC/Nではないかと思う.
矩形波なので高調波の問題はあるが,
受信用のローカルOSCとして使用するならこのままでいいかもしれない.

200.000000MHz 出力
20170313_200M

こちらは,モニタできないので波形のみ観測.
100MHzのオシロなので,フィルタがかかってきれいな波形になっているが,
信号がおよそ200MHzで出ていることは確認できる.

位相も設定できるようなので,後日試してみたい.