小さなブラシDCモータに羽根を付けて、風速計を作ろうと予備実験したことがありました(以下ご参考)。
「#21-9 風速計を作ろう」
今回は、その完結編となります。
予備実験で得た知見:
先の実験では、Logiscopeのアナログ波形取得機能(【osc】コマンド)を使って、起電圧の波形だけ取得したのだけど、今回はちゃんとプログラムを組んで起電圧の出力電圧と回転数を測定しようと思います。
なお、予備実験に引き続き、STM32ボード(下図)を使って進めます。
回転数を計測するには、一定期間で脈動する山の数を計測する必要があります。従ってまず、起電圧を定期的に計測します。計測周期は、起電圧に現れる周期波形の最高周波数×2倍以上が必要です(標本化定理)。
予備実験によって、起電圧にはモータの回転数×2倍の脈動(山)が発生することが判っています。
起電圧を計測するSTM32搭載AD変換器の変換時間については、こちら「#5-4 デジタルオシロスコープ(STM32対応)」で解説済みとなっています。デフォルトでは、サンプル&ホールド時間が長めに取られていて(55.5サイクル)、変換時間が約5.7μsかかります。
500Hz(=2ms)のサンプリング周波数に対しては余裕があるので、そのままでも良かったのですが、変換精度よりも変換時間の方が重要なケースなので、こちらにある通り、変換時間を最短の約1.17μsに設定して使います。
adc_set_sample_rate (ADC1, ADC_SMPR_1_5);
上のAPIでサンプル&ホールド時間を1.5サイクルに設定しました。逐次変換時間12.5サイクルと合わせて、AD変換時間=14サイクル@12MHz(=約1.17μs)です。
サンプリング周波数(flw_frq_SMPL)で測定した起電圧値をVlpfと置きます。Vlpfに対して、適切な区間flw_MAX_MVA(=16)の移動平均を取ってVmvaを算出します。移動平均Vmvaは低域通過フィルタ(LPF)に相当するので、VlpfをデジタルLPFへ通したことになります。
こうすることで、DCモータ回転に伴う起電圧の脈動が平均化されて滑らかになります。
ここで、プログラムを使って自動的に回転数を計測する方法を考察しておきます。
予備実験の【osc】コマンドで取得したデータを使い、上のアルゴリズムをシミュレーションした時のタイミング図を以下に示します。
Vlpf(上図赤線)とVmva(上図青線)のクロスする点(上図緑●)を見つけて時間を計測すると、その逆数が山の周波数です。山の周波数÷2倍するとモータの回転数、更に×60倍するとrpm(revolutions per minute)となります。
緑●の上に表示した数値は、モータの回転数に換算した周波数です。
こうして機械的に計測してみると、回転は速い⇔遅いを交互に繰り返しているように見えます。山2個でDCモータ1回転分なので、DCモータの物理的特性に起因した測定結果だと考えられます。
回転数も、ある程度の区間flw_MAX_RPM(=32、偶数)で移動平均を取った方が良いみたいです。
DCモータを回転させたことによる起電圧は、周期的に脈動しています。電圧値が周期性を持っている場合、最低周波数の脈動が気にならないくらい長い期間の平均値を取らないと、測定値も脈動してしまいます。
この様な場合に使われる実効値と言う指標があります。
実効値とは、RMS(Root Mean Square)とも呼ばれ、その名の通りに、二乗(square)して時間積分したら、時間で割って単位時間当たりの平均値(mean)を求め、最後に平方根(root)を取ると算出できます。
マイコンなどでは、離散的な時間処理なので、「時間積分÷時間」 ⇒ 「全部加算して、その数で割る」で、OKです。
最後は平方根を演算しないとならないので、浮動小数演算を避けられません。が、最後の最後まで整数処理が可能な様に「1.2V」のような浮動小数値は使わず、「1,200mV」として、演算の途中経過は整数処理します。
起電圧には脈動(山と谷)があるので、想定する最低周波数の脈動を十分に平均化できる期間を使って実効値(RMS)を計算しなくてはなりません。
予備実験などから、1,000rpm(≒16.7Hz)を下回ると脈動の幅が減少して、山と谷を検出できなくなることが推定できます。よって、最低周波数を16Hzに設定して、その×8倍の範囲からRMSを計算することとします。
const int flw_MAX_RMS=8*flw_frq_SMPL/16; // flw: RMS, buf size
static int flw_rms_buf[flw_MAX_RMS]; // flw: RMS buffer
// RMS
flw_rms_sum += sq (vlpf) - flw_rms_buf[flw_rms_ptr];
flw_rms_buf[flw_rms_ptr]=sq (vlpf);
flw_rms_ptr++;
if (flw_rms_ptr>=flw_MAX_RMS)
flw_rms_ptr=0;
flw_rms=sqrt (flw_rms_sum/flw_MAX_RMS);
上の定数設定だと、バッファ(flw_rms_buf[ ])サイズは約1Kバイトなので、RAM容量の少ないMCU(Arduino UNO等)を使う場合は、もう少し工夫しなくてはならないでしょう。STM32はRAM容量20Kバイトなので、まだ余裕があり、このまま実行できます。
OLEDディスプレイ(下図)を使って、リアルタイムに回転数と起電圧の実効値を表示しようと思います。
こちら「#22-2 OLEDディスプレイを使う」で解説した様に、OLEDディスプレイの制御も接続も簡単です。
ただし、OLEDディスプレイへの描画時間には注意しなくてはなりません。
前の節で、サンプリングレートを2ms(→ 500Hz=flw_frq_SMPL)に設定したので、1行丸々描画(転送)していると、描画時間がサンプリングレートを超えてしまいます。
こういう場合、一定時間毎に確実に実行しなければならない処理の方を、タイマ割り込みなどで優先的に処理するのが一般的です。ですが、今回は割り込みを使わず、全てloop()関数内で(手動)スケジューリングして実行することとしました。
このためには、OLEDディスプレイへの描画を2ms以内に終了するように調整する必要があります。
そこで、1回のloop()関数内では1文字だけ描画して、次の1文字は次回loop()関数が実行された時に描画するような分散処理としました。
// update air flow meter
if (prn_ptr>16) // 2行目を描画中だけ、1行目を更新
sprintf (prn_buf0,"RPM=%4d[rpm]",flw_rpm);
if (prn_ptr<16) // 1行目を描画中だけ、2行目を更新
sprintf (prn_buf1,"RMS=%4d[mV]",flw_rms);
// update oled display
if (prn_ptr<16)
ole_print_chr (prn_buf0[prn_ptr],prn_ptr,0); // 1文字描画関数(1行目)
else
ole_print_chr (prn_buf1[prn_ptr - 20],prn_ptr - 20,1); // 1文字描画関数(2行目)
// update prn_ptr; 描画文字位置(1行目=0〜15[桁]、2行目=20〜35[桁])
prn_ptr++;
if (16<=prn_ptr && prn_ptr<20)
prn_ptr=20;
else if (prn_ptr>=36)
prn_ptr=0;
1データのサンプリング周期は2ms(=1/flw_frq_SMPL)なので、1画面=16文字×2行=32文字を表示するのにかかる時間=32文字×2ms=約64msです。人間から見たら一瞬の出来事なので、この様な処理になっていることは、目視では判らないと思われます。
以上の様な考察を経て完成したプログラムを以下にリンクしておきます。
完成版プログラム:air_flow.ino
コンパイルしてSTM32に書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
シリアルモニタコマンド仕様:
OLEDディスプレイの方にリアルタイムで、回転数[rpm]と起電圧の実効値[mV]が表示されるので、データ取得用の【show】コマンドだけを備えています。
詳細説明は「#21-9 風速計を作ろう」をご参照ください。
まず、ブレッドボードなどに、以下の回路を構築します。
実装例:
上の回路と、羽根を取り付けたブラシDCモータ、OLEDディスプレイ、STM32を以下表の様に接続します。
OLEDディスプレイ | 接続 | STM32 | 接続 | 風速計回路 | 接続 | ブラシDCモータ |
VCC | ← | 5V | → | Vcc | ||
GND | ⇔ | GND | ⇔ | GND | ||
PA0(ADC0) | ← | Vlpf | ||||
Vs | ⇔ | 端子1 | ||||
Vss | ⇔ | 端子2 | ||||
SCL | ⇔ | PB6(SCL) | ||||
SDA | ⇔ | PB7(SDA) |
※予備実験の接続とは、少し異なっているのでご注意ください。
以下に実験中の様子を示します。
では、実験を進めて行きましょう。
実験サンプル:【 】内はシリアルモニタからの送信データ
【show,1000】
OK: show 1000[ms]
SHOW,1050,640
SHOW,1072,655
SHOW,1109,665
:
: (中略)
:
SHOW,2122,1784
SHOW,2125,1779
SHOW,2128,1769
【show,0】
OK: show 0[ms]
【show,1000】コマンドを実行して、1秒毎に回転数[rpm]と起電圧の実効値[mV]を出力しました。風を送る扇風機の強/弱を切り替えたり、ブラシDCモータに取り付けた羽根に当たる風を手で遮るなどして回転数を変化させながら測定しました。
これをExcelに貼り付けてグラフ化してみると、以下図となりました。
考察:
このページの目標は、風速に従ってモータの回転数や起電圧の実効値が変動することを確かめることと、それによって風速の相対値を計測できることの確認、です。以上で目的は果たせたので、実験を終了とします。
※測定値から風速[m/s]に換算するには、風速を計測可能な測定器を使って校正が必要となります。
モータを回した時の起電圧がこんなに小さいものだとは思っていませんでした。×51倍の非反転増幅回路を通して、やっと実効値600〜1,700[mV]が観測できるレベルでした。使ったブラシDCモータ、小さいしね……。大きなモータだと、扇風機くらいの風では回らないだろうし。風力発電なんて、巨大な羽根が回っているだけに見えるけど、相当大変な装置(技術)なんだな〜。