実機を使わずに STM32 を動かして、三相電力を測ってみる — Python の中だけで(2電力計法)

  • python
  • stm32
  • 組み込み
  • 電子工作
  • 電力計測

まとめ(先に結論)

  • マイコン(STM32)のプログラムは、実機のボードを買わなくても PC の中で動かせます。Unicorn という CPU エミュレータを使うと、ARM のコードを 1 命令ずつ PC 上で実行できます
  • STM32 の周辺機能(今回は ADC=アナログ電圧を数値に変える部分)は、エミュレータには付いてきません。でも 「レジスタは結局ただのメモリ番地」 なので、その番地への読み出しを横取りして、こちらから値を渡してやれば、自分でモデル化できます
  • 題材は 三相電力を「2電力計法」で測る話。中性線のない 3 相 3 線式なら、3 本の電線に流れる電力を 測定器 2 台だけで測れてしまう、という少し不思議な方法です
  • 12bit ADC で測ったところ、解析解(数式で出した真の値)に対して 誤差は 0.01% ほど。「思っていたより、ずっとよく合うんだな」というのが正直な感想でした

最初にお断りしておくと、これは実際の電気や本物の基板にはつながない、PC の中だけの数値実験です。3 相の波形を数式で作り、それを 12bit ADC に見立てて、エミュレートした STM32 に測らせています。実機の話ではない、という前提で読んでいただけたら嬉しいです。

そもそも:実機もボードも無しで、STM32 って動かせるの?

マイコンの勉強というと、ふつうはボードを買ってきて、書き込んで…という流れだと思います。私もずっとそう思っていました。でも調べてみると、CPU そのものを PC 上でエミュレートする道具がいくつかありました。

今回使ったのは Unicorn Engine です。Python から呼べて、ARM(STM32 の中身は ARM Cortex-M という CPU です)の機械語を、メモリ上に置いて 1 命令ずつ実行してくれます。プログラム自体は、ふつうに C で書いて arm-none-eabi-gcc(ARM 向けの gcc)でコンパイルしたものを、そのまま流し込みます。

ただ、Unicorn が動かしてくれるのは CPU の計算部分だけです。STM32 についている ADC やタイマといった「周辺回路」は入っていません。これは一見困りますが、考えてみると STM32 のレジスタ(周辺回路を操作する窓口)は、プログラムから見ればただの決まったメモリ番地です。たとえば ADC の読み取り口は 0x4001204C という番地で、ここを読むと変換結果が返ってくる、という約束になっています。

なので、「この番地への読み出しが起きたら、こちらが用意した値を返す」 というフックを Python 側で仕掛けてやれば、ADC を自分でモデル化できます。私はここで「なるほど、周辺回路って結局メモリ番地の約束ごとなんだ」と腑に落ちました。

三相電力を、測定器 2 台で測る話

題材に選んだのは、電力の測定です。家庭のコンセントは 1 本の線(と中性線)ですが、工場などで使う三相は 3 本の電線で電気を送ります。位相が 120° ずつずれた 3 つの波で、合計の電力を測りたい。

素直に考えると「3 本それぞれにメータを付けて 3 台で測る」となりそうですが、3 本だけで中性線が無い配線(3 相 3 線式)なら、測定器は 2 台で足ります。これは ブロンデルの定理と呼ばれる、古くからの結果です。直感的には、3 相 3 線式では 3 本の線電流を同じ向きで足すと、どの瞬間もゼロになる(行った分は必ず帰ってくる)ので、1 本ぶんは他の 2 本から決まってしまう=測らなくてよい、というイメージです。

2電力計法の結線

具体的には、A 相と C 相に電流計を入れ、電圧は残りの B 相を基準にして測ります。2 台の測定器がそれぞれ「電圧 × 電流」を 1 周期ぶん平均した値を W1W_1W2W_2 とすると、

  • 有効電力 P=W1+W2P = W_1 + W_2

これだけです。W1+W2W_1 + W_2 が、バランスの取れた負荷でもそうでなくても、ちゃんと全体の電力になる――初めて知ったときは、ちょっと不思議でした。

PC の中だけで測ってみる

全体の流れはこうです。すべて 1 台の PC の中で完結していて、実機もオシロも使いません。

実機なしのパイプライン

  1. Python で 3 相の波形(線間電圧 2 つ、線電流 2 つ)を数式で作る
  2. ADC モデルで、その波形を 12bit(0〜4095 の整数)に量子化する。アナログの電圧を、段々のついた数値に丸める部分です
  3. STM32 のファーム(C で書いて ARM 向けにコンパイルしたもの)を Unicorn の中で実行する。ファームは ADC1->DR を読みにいき、その読み出しを Python のフックが横取りして、さっきの量子化サンプルを 1 つずつ渡す
  4. ファームが W1,W2,PW_1, W_2, P を計算してメモリに書き込み、それを Python が読み戻して、数式の真の値と比べる

ファーム側は、特別なことはしていません。ADC から 4 チャンネルを順に読み(今回は 4 チャンネルを同じ時刻のサンプルとして渡しているので、同時サンプリング相当です)、「電圧 × 電流」を足し込んでいって、最後に平均するだけです。STM32F4(Cortex-M4F)には単精度の浮動小数点演算器(FPU)が載っているので、float でそのまま書きました。

やってみた

設定は、線間電圧 200V・50Hz・力率 0.8(遅れ)・線電流 10A の平衡負荷。数式から出る真の電力は P=3×200×10×0.82771P = \sqrt{3} \times 200 \times 10 \times 0.8 \fallingdotseq 2771 W です。

下の図の一番下が見どころで、2 台の測定器が見ている瞬時電力 p1,p2p_1, p_2 は時間とともに大きく波打っています(片方は一瞬マイナスにもなります)。でも 1 周期ぶん平均すると、それぞれ W1786W_1 \fallingdotseq 786 W、W21986W_2 \fallingdotseq 1986 W に落ち着き、足すと約 2771 W。ちゃんと真の値になりました。

波形と瞬時電力

エミュレートした STM32 が出した結果がこちらです。

指標 真の値(解析解) STM32(12bit, Unicorn)
有効電力 P 2771 W 2772 W
ADC 読み出し回数 5120 回(4ch × 1280 サンプル)
誤差 約 0.01%

5120 回の ADC 読み出しを全部フックで横取りして渡しても、ちゃんと最後まで動いて、ほぼ真の値が返ってきました。やる前は「エミュレータの中だし、どこかで破綻するのでは」と身構えていたので、すんなり動いたのは少し拍子抜けでした。

ADC のビット数を変えてみる

せっかくなので、ADC の分解能(ビット数)を変えると誤差がどう変わるかも見てみました。同じセンサ・同じ平均で、ADC のビット数だけを 6〜16bit に振り、ファームをそのつど再コンパイルして測り直しています。サンプルが量子化の段のどこに当たるかで結果がぶれるので、サンプリング開始の位相をランダムに変えた多数回の RMS(実効値)誤差を取りました。

ADCビット数と誤差

縦軸を対数にしたグラフでほぼ直線、つまり 1bit 増えるごとに誤差がだいたい半分になっていきます。量子化の誤差が 2 のべきで効いてくる、という素直な結果です。STM32 で標準的な 12bit のところを見ると、有効電力 P で 0.005% 程度(こちらは位相を振った RMS 値です)。

おもしろかったのは、無効電力 Q の誤差が、P より大きめに出ること。今回の構成(平衡負荷)では、無効電力は Q=3(W2W1)Q = \sqrt{3}\,(W_2 - W_1) と、W1W_1W2W_2 の引き算で求めます(この式が使えるのは平衡負荷のときだけで、結線や相順を変えると符号も変わります)。量子化で生じた W1,W2W_1, W_2 それぞれの誤差が、和で求める PP よりも、差で求める QQ のほうに相対的に大きく出てくるんですね。12bit 付近では Q の誤差が P の 2.5 倍ほどでした(ビット数によって比は揺れますが、Q のほうが大きめになりやすい)。引き算は誤差に弱い、という基本を、数字で見せられた気がしました。

正直な感想と、次回への宿題

今回いちばん「やってよかった」と思ったのは、周辺回路を自分でモデル化するところでした。ADC を「番地への読み出しを横取りして値を返すもの」と捉え直すと、実機が無くてもプログラムの動きを最後まで追えます。デバッガを Python で書いているような感覚で、これはこれで勉強になりました。

一方で、今回は 平衡負荷という素直な条件なので、すんなり測れた面もあります。次回は、

  • 3 相のバランスが崩れた不平衡負荷でも P=W1+W2P = W_1 + W_2 が本当に成り立つのか
  • 力率が低いと、片方の測定器がマイナスを指す現象
  • 4 チャンネルを同時ではなく順番にサンプリングしたときの、時間ずれによる誤差

このあたりを試してみたいと思っています。

この記事のコードは lab-stm32-power にまとめてあります(MIT ライセンス)。

ここまで、「実機なしでマイコンを動かして、電力を測る」最初のステップの記録でした。間違いや「ここはこう考えるともっと良い」があれば、ぜひ教えてください。読んでくださって、ありがとうございました。

この記事は Zenn に初出したものを加筆・補足したものです ── Zenn の元記事を見る