注記(2022/10/30)
Keiths' SDRのサンプリング周波数は48KHzであることが実機による実験で確認できました。
修正(2022/07/28)
信号処理連鎖を流れるデータの単位は128サンプルのデータブロックであることを認識し、修正しました。
Keiths' SDRのRx信号処理連鎖ブロック図
Keiths' SDR(K7MDL局Mike OM版)のRx信号処理連鎖をコードから読み解いて抜き出したもの(α版)を下記に再録します。
今回は、Rx信号処理連鎖の先頭のInputオブジェクト(朱色点線内★)を調べました。
Inputオブジェクト(AudioInputI2S_F32クラス)
Teensy Audio BoardからF32(32bit浮動小数点)型のステレオ・オーディオ信号(IQ信号)を取り込むオブジェクトです。Rx信号連鎖の起点になります。 なお、SGTL5000を搭載するディフォルトのPJRC社のオーディオ・ボードには、IQ信号が順不同になるバグがあるらしいのですが、その解決策等を含めたコミュニティの成果の詳細は別の機会に報告します。
Inputオブジェクトが、IQ信号入力に係わるハードウェアの初期化も請け負っていると思われます。IQ信号入力に係わるハードウェア(青)とソフトウェア(赤)の関係図を下記に示します。なお、この図はコードとi.MX RT1060データシートから読み取ったものであり、将来修正の可能性があります。
コンストラクタ
IQ信号2channel分のサンプリング周波数は96/2=48kHzです。サンプリングはMCU(i.MX RT1060)のPLLがマスターとなってオーディオ・ボードを管理します。Inputオブジェクトのコンストラクタはbegin()関数をコールし、その関数の中で以下のMCU(i.MX RT1060)の初期化を実行しています。
- DMAチャネル・オブジェクトへのチャネルの割り当て
- MCUとオーディオ・ボード間のPLL Clockを含めたI2S通信の設定
- DMAチャネル・オブジェクトへのISR(割込みサービス・ルーチン)の割り当て
MCUの機能ブロック図を下記に示します。
Inputオブジェクトに係わる朱色点線のMCUモジュールについて、以下にメモします。
eDMA
MCU(i.MX RT1060)のDMA(Direct Memory Access)モジュールは、eDMA(enhanced DMA)と称されており、TCD(Transfer Control Descriptor)を用いて柔軟なデータ転送を可能にしています。Keiths' SDRは、DMAチャネル・クラスのbegin()関数内で、IQサンプリングデータ転送のためのDMAチャネルをTCDに設定しています。eDMAが転送するデータの単位は、128サンプルのデータブロックです。I信号128サンプルとQ信号128サンプルを交互に転送しています。
MCU内の eDMAがCPUコア(Arm Cortex-M7)とは独立にIQサンプリングデータの転送を取り仕切るため、低速なI/O処理に対するCPUコアの空転は発生しません。Teensyが搭載するMCUのCPUコアは600MHzで動作する高速1コアであるため、CPUコアを空転させない実装が求められます。
I2S/SAI
MCU(i.MX RT1060)のI2S/SAI(Inter-IC Sound / Synchronous Audio Interface)モジュールがI2Sバスを介して、オーディオ・ボードからIQ信号サンプリング・データを受け取り、eDMAでメモリに転送しているものと思われます。クラス名のAudioInputI2S_F32は、この「I2S」を冠しています。
begin()関数内で、スレーブ動作のオーディオ・ボードに対してI2S/SAIに必要なClock信号を提供するために、CCM内のAnalog System PLLを設定しています。
CCM
MCU(i.MX RT1060)のCCM(Clock Controller Module)がAnalog System PLLの中のAudio / Video PLLを用いて、オーディオ・ボードに供給する様々なClock信号を生成します。
I2S/SAIに必要なClock信号は、①audio master clock、②bit clock、③bus clockの三つです。それぞれTeensy 4.1のピンに、①23:MCLK、②21:BCLK、③20:LRCLKとして割り当てられ、オーディオ・ボードに供給されます。③bus clock(LRCLK)がサンプリング周波数96kHz48KHzに設定されます。
NVIC
IQ信号サンプリング・データのDMA転送完で割込を発生させ、ISR(割込みサービス・ルーチン)の中のupdate_all関数によって信号処理連鎖を起動しているものと思われます。ただし、この関数の中身は下記1行のみです。
static void update_all(void) { NVIC_SET_PENDING(IRQ_SOFTWARE); }
CPUコア(Arm Cortex-M7)の割込コントローラNVIC(Nested Vectored Interrupt Controller)に対して、IRQ_SOFTWARE番号の割込を保留状態にセットしているだけです。IRQ_SOFTWARE番号にはsoftware_isr()関数を割り当てています。この関数が信号処理連鎖起動の割込処理本体になります。
ここでの「保留」は実行中断という意味ではなく、実行予約という意味で使われていると思われます。割込優先順位に応じて順番が回って来れば実行されると思います。IRQ_SOFTWAREの割込優先順位は208/255番と低く設定されています。ハードウェア割込を優先するためと思われます。
規模の小さい組込ソフトウェアでは、割込でフラグセットのみ行い、ポーリングループでフラグを見て割込処理を起動するといった実装方法を取ることがあります。割込処理を長くすると多重割込処理が複雑になるからです。ここでは、そのような処理をシステマチックに行っている印象です。
IRQ_SOFTWAREはIRQ70番のエイリアスのようですが、ARM v7-M Architecture Reference Manualでは、IRQ70番は「Reserved」になっていてユーザには解放されていないようです。使用されていない割込番号をHackして独自に活用しているのでしょうか・・・?。OSが載っている訳ではないため、将来、「Reserved」が「Used」になっても問題はないと思いますが、Teensy 5?にバージョンアップした時には要注意です。別のハードウェア・モジュールがIRQ70番を使用している可能性もあり得ます。
保留設定後に実行されるsoftware_isr()関数の中身は、信号処理連鎖を構成するオブジェクトに対する更新処理update()関数の順次適用の繰り返しです。Inputオブジェクトに対する更新は、I信号とQ信号の個別チャネルの更新です。チャネル更新の中では以下の処理を行っています。
- F32型オーディオ・データのスケール正規化
- 次の信号処理オブジェクトへのデータ送信
- メモリ・ブロックの解放
メモリ解放と言っても参照カウンタを減じるだけで、メモリ・ブロックを参照する他の信号処理オブジェクトが存在する限り解放はされません。こうして安全に、メモリ・ブロック上のIQ信号データは信号処理連鎖を構成するオブジェクトに転送されて行きます。
サンプリング周波数の再考
悩み
Rx信号処理連鎖の後段のHilbert FIR Filterを調べているときに、フィルタの設計仕様と通過特性の確認計算値が合わないことに悩みました。確認計算した遮断周波数が設計仕様に対して倍異なるのです。
信号処理の骨幹を成すサンプリング周波数の間違いが原因として濃厚と仮説を立てました。Decimation(Down Sampling)等の絡みも疑ったのですが、遮断周波数が倍異なることに対してロジックが合いません。(Keiths' SDRではDecimationを採用していないと暫定的に結論付けました。)
気付き
コードを精査している中で、サンプリング周波数を言及する時に2つの視点があることに気付きました。ADCの視点と信号処理の視点です。
通常、「サンプリング周波数」と言えば、後者の信号処理の視点で言及していると思い込んでいました。しかし、Inputオブジェクトのコードでは、暗黙の中に前者のADCの視点で言及していることに気付きました。
確信
InputオブジェクトのISR(割込みサービス・ルーチン)の中で、128サンプルのデータブロックを格納するi2s_rx_bufferに対するIOサンプリン・データの充填状況を調べています。バッファの前半部が充填中か、あるいは後半部が充填中かを調べ、前半部が充填中の時のみupdate_all関数を起動しています。つまり、ADCサンプリング周波数96kHz48KHz/(128x2)サンプルに同期した割込みの中の2回に1回しかRx信号処理連鎖の更新を起動していません。これにより、信号処理視点のサンプリング周波数は48KHzになります。(煩雑ですが、信号処理の起動周波数は96kHz48KHz/128サンプル/2回=375187.5Hz、信号処理の起動周期は2.75.4msecになります。)
具体的に見てみます。バッファの前半部にI信号を格納し、後半部にQ信号を格納すると仮定します。割込がかかった後にバッファ充填状況を確認に行き、バッファ前半部に時刻nのI信号を格納中であれば、時刻n-1のIQ信号は揃っていることを意味します。時刻n-1のIQ信号をRx信号処理連鎖の後段に送致することが可能です。
逆に、割込がかかった時にバッファ後半部にQ信号を格納中であれば、時刻n-1のIQ信号はまだ揃っていません。次の割込を待つ必要があります。
もっとスッキリと実装できないものかとも思ったのですが、eDMAがI信号あるいはQ信号のどちらのデータをメモリに転送したかを知りようがないため、このような実装になっているものと思います。MCU(600MHz)とI2S/SAI(96kHz48KHz)の速度が大きく乖離しているために成り立つ実装とも思います。
Teensy 4.1のIOピン数は多いため、PJRC社のオーディオボードを2枚スタックしてI信号およびQ信号専用のADCにすれば、サンプリング周波数96kHzを容易に実現できると思うのですが、そのような議論はないようです。サンプリング周波数と同時にbit数も拡張したいとのことから、別のADCへの換装が議論されています。
結論(暫定)(決定)
PJRC社のオーディオボードを採用したKeiths' SDRのRx信号処理連鎖のサンプリング周波数は48KHzである・・・と思います。
(Keiths' SDRのソフトウェア開発に係わるOM諸氏には周知の暗黙知と思いますが、コードも含めて明示されている情報はまだ見つかっていません。)
悩み再び!?(2022/08/12)
信号連鎖ブロック図の後段に位置するFilterConvオブジェクトではサンプリング周波数96kHz48KHzでBPFが設計されていることが分かりました。もしかしたら、Hilbert FIR FilterはTeensyディフォルトのオーディオ周波数44.1kHz48KHzで設計されているのではないか?との疑念が浮上しました。44.1kHz48KHzで設計したHilbert FIR Filterを96kHzで使用すると帯域が約2倍に拡大します。しかし、後段のFilterConvオブジェクトで帯域を絞るため、実害は無く仕様のミスマッチに気付かない可能性があるのではなかと推測しています。最終的にはハードウェアを組んで、信号処理連鎖の起動周期を測定しないと白黒が付かない状況です。