[4] もっとハード寄りの解説
1)
FFT計算について
・
計算速度の検証 (RP2040からRP2350へ)
FFT計算の仕様を決定する上で、ライブラリの計算速度を検証しました。
RP2040 (Raspberry Pi Pico)の実測値
サンプル数 計算時間 回数/秒
128 9ms 111256 19ms 53
512 42ms 24
1024 89ms 11
デコード速度の上限を50WPMとすると、1dotは24msecですので、128サンプルの場合(9ms/回)では、1dotの判定に2.5サンプルしかデータが無く50WPMのデコードの実現が厳しいとわかりました。(逆に言えば、1dotの判定に5サンプル必要と考えると、上限は25WPMと実用レベルにならないと判断。)
そのため、MCUをRP2350 (Raspberry Pi Pico2 浮動小数点演算機構有り)に変え、実測値をしてみました。
結果は、「サンプル数 128 の時、計算時間は 1.8ms」という「5倍速い」結果となりました。
最終的には、FFT計算後の信号シェーピング処理を加えて、全体で約4msの処理となっています。 (50WPMの1dotに対し、6サンプルと判定に十分なデータ数を確保できています。)
・
設定パラメタ (解析範囲、解像度) から FFT計算のパラメタ
CW Decoderは、解析周波数範囲を300Hz-900Hz、解像度を20Hzとしました。
FFT計算のサンプル数は128サンプルとしましたので、設定パラメタの解像度20Hzから、サンプリング周波数は2560Hzとなります。 これにより、FFT解析周波数範囲が 0-1280Hzとなり、必要な帯域 300Hz-900Hzをカバーできている事が確認できました。
・
計算回数とデータシフト
FFT計算から信号シェーピングの処理は、約4ms (1/256秒)に1回必要です。
一方、サンプリング周波数 2560HzでFFT計算用の128サンプルを取得する時間は、1/20秒 (50ms)ですので、FFT計算にサンプリングデータの準備が間に合わないという矛盾があります。
そのため、FFT計算用のデータは、直近の128サンプルを使用して、1/256秒毎に計算します。(128サンプル中、10サンプルは最新のもの、残りは前回のデータを利用)
2) 割込みの優先順位
・
Arduinoの割込み (優先度はすべて同じ)
CW Decoderでは、ADCのサンプリングにTimer割込みを使用しています。
また、グラフ表示のために、定期的な画面描画(更新)も必要ですので、Core1からCore0へGPIO割込みをかけています。(割込みが2個ある)
Arduinoでは、すべての割込み処理の優先順位が同じ設定になっていますので、このままでは片方の割込み処理中は、もう一方の割込み処理は無視されてしまいます。
そのため、ADCのTimer割込みの優先度を、GPIO割込みより相対的に上げる設定が必要です。
下はテストプログラムで処理の状況を確認した例です。(それぞれの割込み処理ルーチンの冒頭で、Test Pinの出力をHighにし、処理終了時にLowにして観測)
Arduinoの標準設定(すべての割込み処理の優先順位が同じ)
GPIO割込み(Core0)の優先順位を下げた場合
GPIO割込み処理中でも、ADCのTimer割込みを優先して処理している。
CW Decoderで使用しているカラー液晶(240x320x16bit)の画面消去には、153.6kByteを更新する(データ転送する)必要があります。CW DecoderではSPIの転送速度を通常の4倍(40MHz)で使用していますが、それでも、31msかかります。そのため、この優先順位の設定は必須となります。
(グラフィック制御に、既存のライブラリを使用していないのは、このあたりの設定を自由に変更できないためです)
3) 画面描画の錯綜
・
グラフ画面表示を作ったら何が起こったか
最初のバージョンは、デコードした文字だけを表示する全画面表示だけでした。これはCore0のメインループから1文字ずつ出力するものです。(コンソールへの出力)
そこに、リアルタイムでグラフを表示するグラフ画面表示を追加しました。これは、Core1からの割込みで、Core0がグラフ表示する処理としました。
テストをしたところ、画面が崩れる(でたらめになる)現象が起きました。
画面表示(具体的にはILI9341へのコマンド/データの書き込み)は、両方ともCore0が出力しますので、競合によるデッドロックは発生しません。
よくよく考えたところ、メインループで文字を出力している最中やスクロールのために画面を書き換えている最中に、グラフの書き換えが発生した場合、ILI9341へのコマンド/データが、文字描画とグラフ描画のそれぞれから送られて混在(錯綜)してしまいます。これが原因で画面が崩れている事がわかりました。
・
対策
対策として、どちらかが書き込んでいる間は、他方が待つことが考えられますが、グラフ描画はリアルタイムですので、待たせる=信号処理が遅れる ことにつながります。
そのため、文字とグラフの画面描画(更新)プログラムを1つにまとめると同時に、文字データもグラフデータもバッファに入れ、グラフ更新と同時に、バッファ内の文字データも表示する形にしました。
・
画面描画とデコード処理の対策 (Core0内の処理)
割込みの優先順位でも書きましたが、LCDの画面更新には最大31msかかります。この間のCore0は優先度の高い割込み処理をしていますので、メインループのデコーダ処理が止まってしまいます。
デコーダ処理は、常にCore1から出るKeyのOn/Off信号を監視している必要があり、このデータ(Keyのステータス)は4ms毎に更新されます。そのためOn/Off監視が31ms間止まってしまうのは致命的です。
対策として、Core1のKey On/Off信号の直接監視をやめて、On/Offデータはバッファに書き込むことにしました。その結果、Core0のデコード処理は(リアルタイムでKeyのステータスを読むのではなく)、バッファのデータを(少し遅延して)読み込んで疑似的にKey
On/Offの時間監視をするように変更しました。
(画面更新中のデータは、まとめて読み出す事ができます)
これにより、画面描画中のKey On/Offデータを取りこぼすことなく処理ができるようになりました。
以上でCW Decoderのソフトウェア構造に関する説明を終わります。
(3) 実際のプログラムの構造 < [Home] > (5) まとめ
コメント
コメントを投稿