Codec2WalkieTalkieが気になる

 こんなgitリポジトリを見つけてしまいました。

 readmeを読むと、Codec2で圧縮した音声をデータ通信用の無線モジュールに流してデジタル式のWalkieTalkieを作るというPOCだと書いてあります。昔ガキトラと呼ばれた子供用のおもちゃトランシーバで遊んだことを思い出しました。時代が進みそんなおもちゃをデジタル技術で手作りできるワクワクを感じさせるリポジトリです。

 面白そうなので試したいと思い、売れ残りのSTM32F407G-DISC1を購入しました。このボードはNucleoシリーズができる前からあると記憶しております。今となっては流通量が少ないみたいです。とりあえず試すためのボードはこれで良しとして、問題は無線モジュールです。対応している無線モジュールを見ると技適が無いものばかりです。とりあえずは部品箱に眠っているXBeeあたりで試すことにします。

 XBeeを使うのであれば 日本で使えないRadioHeadフォルダー以下は捨ててしまっても良さそうなのと、プロジェクトコードは最近STが推しているCubeMXで作られていないようなので、入出力部を含むメインは新規に作り直すことにしました。まずは音声の入出力部をCubeMXで定義したのですが、いろいろハマったので記録として残しておきます。プロジェクトの作成はSTM32F407G-DISC1を選択すれば、大方の設定は自動で行われるのでこれを選びました。

 最初にDACへのデータ出力を定義します。Codec2WalkieTalkieのソースコードのコメントより20msに160サンプルを扱うということなので非圧縮部のデータレートは8kHzです。

 I2S3を以下のように設定しました。

 送信側のDMAを割り当てます。モードはCirculer、Data WidthはHalf Wordとします。

 次にMEMSマイクのインタフェースを設定します。この設定はハマりました。結果は以下のようになります。同時に受信側のDMAを割り当てます。モードはCirculer、Data WidthはHalf Wordとします。

 

 設定のポイントはオーディオ周波数(32kHz)です。STM32F407G-DISC1のMEMSマイク(MP45DT02)のクロック周波数は最低でも1MHzにする必要があります。それ以下の場合はPDMの出力がありません。最低1MHzの仕様も見落としてハマりました。したがって、クロック出力が1MHz以上になるようにオーディオ周波数を設定する必要があります。で、なぜ32kHzなのか?

 STM32F407G-DISC1のマイクは1つでモノラルです。一方、I2SはLR交互にデータが並ぶステレオを前提とした作りになっています。オーディオ周波数=32kHzでフレームフォーマット=16bitを設定すると発生するクロックは、32 x 16 x 2(ステレオ) = 1024kHzとなります。I2Sはステレオ仕様ですが読み込まれたデータはLRに関係なく時系列に並びますので純粋に1024kHzのモノラルなPDMデータを取得できることになります。

 DACの使い方はこちらを参考にしました。

github.com 上記サンプルのようにDMAにメモリさえ割り当ててしまえば、適切なタイミングでデータをコピーするだけで音声を出力…と思ったのですがDMAが動いてくれません。普段はこんなことないのに何で??デバッグプリントでNDTRレジスタを確認しても値に変化がない状態です。しばし途方に暮れましたが、同じ現象を乗り切ったVLOGのおかげで解決しました。CubeMXの闇を見た感じです。

qiita.com DMAのバッファリングサイクルをLEDの点滅で確認できるように125ms分と決めました。マイク側は1024kHz / 16bit = 64kHz(15.6us)単位にNDTRレジスタがカウントされます。したがって、uint16_tの配列の長さは8000(8000 x 15.6us = 125ms)となります。DAC側は8kHz x 2(ステレオ) = 16kHz(62.5us)単位にNDTRレジスタがカウントされます。したがってuint16_tの配列の長さは2000(2000 x 62.5us = 125ms)となります。

 いよいよループバックテストのメインロジックが書けるようになりました。メインループでマイク側のNDTRレジスタをポーリングし、1データ受信したらi2s_dma_word_rx()関数を呼び出します。この関数の呼び出しタイミングは64kHz程度になる予定、出力側は8kHzなので1/8にデシメーションし8kHzを作ります。ここはCodec2WalkieTalkieとほぼ同じ処理です。少々乱暴にも見えますが8kHzのタイミングでFIRフィルタ後の値を取得し、そのままDACのDMAバッファに値を代入します。入出力ともに正常動作すれば8kHzタイミングで同期されているので特にFIFOなどを挟む必要はないはずです。

 ループバックテストは目論見通りに動作したので今回はここまでです。いろいろハマってCodec2まで到達しませんでしたが続きを楽しみたいと思います。