前回記事ではMicroPythonでESP32内蔵ADC測定値を電圧に変換するクラスを作成した。今回はその応用として、電圧と電流をESP32内蔵ADCを使って測定しssd1306ディスプレイにリアルタイム表示する実験を行ったので記事に残しておく。
MicroPythonのTimerクラスを使って電圧を一定間隔で測定/表示する
電流測定の前の下準備として、ADCで定期的に電圧測定値を行い測定結果をssd1306ディスプレイにリアルタイム表示するMicroPythonプログラムを書いた。
マイコンで定期的に何かを実行したい場合はタイマ割り込みを使うのが定番だが、ArduinoなどのC/C++系プラットフォームではタイマ割り込みの割込ハンドラに引数を渡せない場合が多く、グローバル変数を使う羽目になってプログラムが読みにくくなりがち。しかし、MicroPythonのTimerクラスを使うとひと味違う形でプログラムが書ける。
下は前回記事で作ったMilliVoltクラス(volt.pyとしてMicroPython側にampy putで転送済み)を使い、GPIO34の電圧値をmV単位で500msごとに読み出してディスプレイに表示させるプログラム。Timerクラスのコールバック関数にラムダ式(無名関数)を指定するのがポイントで、19行目でコールバックさせたいperiodic_meter関数に初期化済みのオブジェクト変数を引数としてセットした状態でTimerオブジェクトに渡せるのでグローバル変数を使わずに済んでいる。
毎度ながら思うのだが、複雑な機能もMicroPythonならコンパクトに書けるのは嬉しい。
from volt import MilliVolt from machine import Pin, I2C, Timer from ssd1306 import SSD1306_I2C def periodic_meter(mv, disp): val = mv.get() disp.fill(0) disp.text(str(val) + 'mV', 0, 0); disp.show() if __name__ == '__main__': i2c = I2C(0) oled = SSD1306_I2C(128, 64, i2c) mv = MilliVolt(Pin(34)) mv.atten(MilliVolt.ATTN_11DB) oled.fill(0) tim0 = Timer(0) tim0.init(period=500, callback=lambda t:periodic_meter(mv, oled))
電圧測定のためのハードウエア配線
たまにはfritzingを使って回路を書いてみた。ssd1306ディスプレイとESP32はGPIO18,19によるハードウエアI2Cで接続し、可変抵抗を介してGPIO34に電圧を印加させる。

上のプログラムをampy runなどで実行させ可変抵抗を回してGPIO34へ印加させる電圧を変化させると、ディスプレイの電圧表示にリアルタイムで反映されるのが確認出来る。
ESP32内蔵ADCで電流値を測定するには?
ESP32内蔵ADCを使って電流値を測るには、測定対象の回路にシャント(分流)抵抗を挟んでシャント抵抗の両端電圧として間接的に測定することになる。ESP32内蔵ADCの測定レンジは0V〜約1.1mVであり、〜1A程度の電流測定を行いたい場合はシャント抵抗を1Ωにすれば丁度ADCの測定レンジに合った測定が出来る。またESP32の内蔵ADCはGNDとの電位差しか測定出来ないため、必ずローサイド側での電流測定となる。
パワーMOSFETを用いた実験回路
実験用にパワーMOSFETを使った以下の回路を組んで、ゲート電圧とドレイン電流を同時測定してみる。この回路でMOSFETのゲートにかかる電圧は最大約5Vとなるため、R2とR3で約1/5に抵抗分圧してからESP32のGPIO35に入力して測定する。またドレイン電流の測定については、実際にはソース側に入れたシャント抵抗R5の電圧をGPIO34に入力して測定する(ローサイド側での電流測定)。ソースとGNDの間に抵抗を入れてしまうとその抵抗で発生する電位差はそのままVgs(ゲート-ソース電圧)のドロップに繋がるためVgsのしきい電圧が低めのMOSFETを選ぶようにする。
回路図のMES1とMES2の位置にマルチメータを挟み、ESP32による電圧と電流の測定値と合っているか確認する。また+5Vの電源からは1A近く電流を取り出したいので、ACアダプタや安定化電源から供給するようにする(USB経由でPCから電源を貰っているESP32ボードから取ると容量オーバーになるため)。

測定用のMicroPythonプログラム
基本は冒頭のプログラムと同様MilliVoltクラスを使った測定だが、測定対象ごとに必要なMilliVoltオブジェクトを束ねるためのMeasureIVクラスを定義した。MeasureIVクラスのmeasureメソッドを10msごとに呼び出してADCから値の取得を行い、dispメソッドを500msごとに呼び出している。
このプログラムでは電圧と電流の表示しか行っていないので値の取得と表示を分けても意味は無いのだが、電圧や電流値に応じた処理を高頻度で行い、値の表示は500ms位の低頻度で行う事をイメージしてプログラムを書いてみた。
from machine import Timer from volt import MilliVolt from machine import Pin, I2C from ssd1306 import SSD1306_I2C class MeasureIV(object): def __init__(self, pin_milivolt, pin_miliamp): self.v = MilliVolt(pin_milivolt) self.mi = MilliVolt(pin_miliamp) self.coef_v = (100+22)/(22*1000) # 抵抗分圧の定数で係数を決める self.coef_mi = 1.0 self.val_v = 0 self.val_mi = 0 def measure(self): self.val_v = self.v.get() self.val_mi = self.mi.get() def disp(self, disp): disp.fill(0) disp.text('Vgs:' + str(self.val_v*self.coef_v) + 'V', 0, 0); disp.text('Id:' + str(self.val_mi*self.coef_mi) + 'mA', 0, 8); disp.show() if __name__ == '__main__': i2c = I2C(0) oled = SSD1306_I2C(128, 64, i2c) ivmes = MeasureIV(Pin(35), Pin(34)) oled.fill(0) tim0 = Timer(0) tim1 = Timer(1) tim0.init(period=10, callback=lambda t:ivmes.measure()) tim1.init(period=500, callback=lambda t:ivmes.disp(oled))
電圧・電流の同時測定結果
上で電圧測定のために作った回路をベースにMOSFET回路を追加した物をブレッドボードに組んでampy runでプログラムを実行する。半固定抵抗でゲート電圧を変えながらマルチメーターの値とディスプレイに表示された値と比較をしてみたところ、以前の記事で確認した時と同様にマルチメーターの測定値と大差は無さそうな感じだった。特にキャリブレーションを行わずに、抵抗の回路定数で係数を決めただけの割には優秀な印象。

まとめ
ESP32の内蔵ADCを使った電流測定を試してみた。電流はシャント抵抗の電圧降下による間接的な測定のためシャント抵抗の精度にも影響され、ESP32の内蔵ADCはGNDとIOピンの間の電位差しか測れないためローサイド側の電流測定に限定されるという制限もある。とはいえ、シャント抵抗を挟むだけで手軽に電流が測定出来るので活用出来るシーンは色々あるんじゃないかと思う。
コメント