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) を掛けている.
(設計用のスクリプト    Calc_Coeff_of_CPLXBPF_adj.m )

 

P.S.
動作確認レベルのプログラムなので,これを公開する予定はありませんが,
もしご希望の方がいましたら,ご連絡いただければ検討いたします.

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の回路図とArduino Sketch はこちらに公開しておいた.(動画
画像表示の際アンチエイリアス処理を施しているので,まずその違いを以下に.

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

アンチなし.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信号 を出すようにするのと,
カウンタと同様,周波数オフセット値の設定など自由にできるようにする機能を
追加していく.