2024年4月10日水曜日

STM32F303K8で超音波距離計の試作

 STM32F3で超音波距離計を作ろうと思ったんだけど、結構厳しい。特にOPAMP(PGA)のゲインがx16までしか設定できないから、アナログ系で利得を稼げない(PGAでなくて、普通のオペアンプみたいに外付け抵抗で設定してやればもっとゲインを稼げる)。とりあえず今回は比較的短距離(数m)を測りたいので、そこまで高い感度は必要ないかな、ということで続行。

 最初はDACでパルスを出そうかと思ってたんだけど、超音波素子をドライブできないことが判明し、TIMのPWMを出力するように変更。STM32のDACってインピーダンス結構高いのな。


 回路構成

 PB5, PB7からTIM3のPWMを出力、PA5からPGAのバイアスを出力、PA7からPGAへ入力。

 全体のタイミングの管理にTIM2を、送信パルスとADCのトリガにTIM3を、超音波素子は送信用の素子で受信器も兼ねる、というような構成。TIM3の役割がコロコロ変わるし、送信時はGPIOをAFにしたり、受信時はOutputでGNDに引っ張ったりAnalogで受け取ったり、あちこち動作モードを切り替えなきゃいけないので、ソフトウェアがかなり煩雑。

 STM32F303K8T、RAMが12Kしかないのが厳しい。ADCが12bitで、1024ポイントのダブルバッファだとそれだけで4K必要になるし、IFは512ポイント持つと32bit複素数で4K、合わせて8Kになる。


 試しに机に置いたセンサで天井までを計測

 パルスの先頭からADCのサンプリング開始までのディレイが4ms、デシメーション後は64us/sampleで、85あたりにピークがあるから、4ms+64us*85=9.44ms、340*9.44m/2=1.60、ということで、実測値の1.53mとほぼ一致。誤差と言い切るにはちょっとズレてるけど、天井は面的な反射だから斜めの伝搬は距離が伸びるとか言い訳すればだいたいいい感じの値になる(天井は超音波くらいの波長だと鏡面反射だから斜め方向の後方散乱ほとんど無いだろという気もするけど)。


 デバッグ用のオシロ画面

 黄色が主に送信パルスを見ている。PRI20ms(PRF50Hz)で打ってる。

 紫はADC前のPGA Voutを見ている。4msの位置から下側にノイズが増えているが、これはADCのコンデンサを充電している部分。その後にブロック状に上下にノイズが増える部分がある。これはデシメーションの処理に同期しているので、マイコン内部の消費電力か何かが漏れてるんだと思う。UARTの転送とかやると盛大にノイズが乗るから、ちょっとした消費電力の変動がアナログ回路にかなりの影響力がありそう。アナログ重視で使うならVDDとVDDAはしっかり分離しておかないとマズそう。F3はGPIOもVDDAで駆動されているらしいから、VDDとVDDAを分離してもGPIO動かしたら暴れるかもしれないけど。/* 以前Nucleo-G474REでAMラジオを作ったときもOPAMPに謎のノイズが入ってたけど、VDD/VDDAあたりが原因だったのかも。G4はGPIOはVDDAではないらしい。 */

 ローカルの生成はNCO、デシメーションはCIC(R64, N3)で処理している。超音波の圧電素子は十分に帯域幅が狭いと考えられるから、Nはあまり大きい必要はないはず。ADCは1Mspsで、IFは15.625ksps。もうちょっと低くてもいいけど、Rを大きくするとビット幅が足りなくなる。現状でも計算上はビット幅が足りない。アナログ信号強度が低いから飽和していないだけで。

 今回はパルス幅が狭いから単純なCWパルスだけど、必要であればチャープ信号も出せることは確認済み。ただしDACみたいに固定サンプリングレートのアナログ波形でなく、パルス周期やパルス幅のデータセットを与えてやる必要があるので、計算が面倒くさい。昔どっかで計算式見たような気もするんだけど、どこで見たんだか思い出せね。。。forブン回してrevの整数部とか位相の前後を判定するとか、あるいはそれを二分探索するとか、いろいろ方法は考えられるけど、それなりに面倒くさそう。チャープ信号を出したところで狭いメモリ空間でどうやって圧縮するんだって問題があるしな。リファレンスをコンパイル時に生成してFlashに置いておけば処理自体はできそうな気はするけど、今回はパルス幅も狭いし。G474REあたりだとRAMも広いから、快適にパルス圧縮とか、複数波形を並べて移動物体抽出とか、いろいろ遊べそう。


 IFのMagnitudeをDACで出力。レーダで言うところのビデオ信号みたいなやつ。

 紫がADCに入る信号、黄色がDACから出てきたIFのMagnitude。DACからの信号はCICの遅延が含まれている。IFは5パルスをコヒーレント積分している(5パルス毎にDACの波形が更新される)。ブレッドボードで組んで長いジャンパを何本も飛ばしているからか、商用電源の50Hzが見えている。ローカル+CICで狭帯域のBPF特性があるから50Hz程度はほとんど除去できるはずだけど(あとPRFが25Hzなのでコヒーレント積分すれば除去できるはず)。

 天井からの反射は多少の変動はあれど安定した強度を示している。一方で手に持った反射体では強度の変動が非常に大きい。コヒーレント積分しているので、ドップラ成分で強度が落ちたりしてるんだと思う。

 32bit複素数(整数)から16bitのMagnitudeへ変換するのに、Cのforとかで実装すると99サイクル/ポイントくらい。アセンブリで書くと43サイクル/ポイントくらい。倍以上早くなる。

 IFはローカルとかCICで結構大きな値になるので、Magnitudeの計算には適当なゲインで16bitに収まるようにしている。これは引数で受け取っているが、ARM ABIでは浮動小数点引数が1個の場合はs0で渡される。計算の途中(インラインアセンブラの上書きレジスタ)にs0, s1を使うと引数を一旦別のレジスタにコピーするので、関数の呼び出し1回毎にvmovが1回走る。気にするほどじゃないけど、気になるので、計算をs0,s1ではなくs1,s2を使うようにして、s0で渡される引数をコピーしないようにしてみると、明らかに遅くなる(43.24c/p→47.21c/p)。

 オブジェクトレベルで逆アセンブルするとレジスタの指定以外は全く同じバイナリになっている。ラベルの前にnopを入れたりすると実行時間が変わるので、命令(ループのジャンプ先)のアラインメントの関係なのかな? プログラムのチューニング面倒すぎ。。。


 STM32F3のHAL_TIM_Base_Stop、CCxが有効だと機能しないのが謎い。リファレンスマニュアルにはCENをクリアするにはCCERのCCxEが云々みたいな話は書いてないし、TIM3->CR1 &= ~TIM_CR1_CEN;みたいに書けばちゃんと止まるから、HAL側の都合だと思うんだけど。しかしなんでCCxEがセットされているとCENをクリアする処理をスキップする、みたいな実装になっているんだろう? HAL_TIM_PWM_StartをHAL_TIM_Base_Stopで止めないため、みたいなことなんだろうか。

 なんだかんだ、結局HAL系のライブラリを使うんでなく、構造体を直接書き換えるほうが楽じゃねーかといういつものやつ。

 CMSISのDSPライブラリの三角関数系の関数(arm_sin_cos_f32とか)、角度が1回転以上だと絶妙に変な動作になるのが結構不便。たぶん周波数領域で折り返して計算してるんだろうけど、math.hのsinfとかcosfとかとかなり違う結果になる。


 とりあえず、ブレッドボードレベルではSTM32F303K8ワンチップ(+いくつかの受動素子)で超音波距離計を作る目処はたった。あまり長距離は感度が得られないけど、5m程度なら問題ないはず。

0 件のコメント:

コメントを投稿