ESP32に昇圧回路を外付けし、特別なICを使わずにESP32内蔵のADCとPWMを使ってMicroPythonから出力電圧の監視とPWM制御をする昇圧型DC-DCコンバータの実験を行ったので記事に残しておく。
ESP32を使った昇圧型DC-DCコンバータ回路
昇圧型DC-DCコンバータの原理回路のLTspiceシミュレーションを下記記事にて行った。
MicroPythonからPWMを使う方法について下記記事にまとめた通り、ESP32の出力ピン全てでPWMを使うことが出来る。
というわけで、ESP32に以下のような回路を外付けすればマイコン制御の昇圧型DC-DCコンバータを作ることが出来る。複雑な回路にも見えるが、大事な部分は”Up Converter”のテキストの下に青線で囲んだ昇圧回路だけで、他は入出力電圧を測定するための回路や負荷をMOSFETで制御するための物。特にR2とQ2は省略してしまって負荷抵抗RLを手動で付け外しするのでも良いかもしれない。また、出力側にESP32のGPIO端子と混ざって1+,1-,2+,2-とあるのは昇圧出力をモニターするためのUSBオシロADALM2000の測定入力端子だ。

MOSFETを選ぶ時の注意点
上の回路ではMOSFETのゲートをESP32のGPIOで直接駆動しており、GPIOの3.3Vで確実にオン出来るようゲート閾値電圧が低めのMOSFETを選ぶ必要がある。今回使用したAO3400というMOSFETはゲート閾値電圧が1V前後と低く、ドレイン電流も4〜5A程度流せる物で、別記事にまとめた通りLTspiceのシミュレーションで諸々確認を行った。
ゲート駆動用のGPIOピンはデフォルトLow出力の物を使う
MOSFETのゲート駆動を行うESP32のGPIOピンはMicroPythonのシステム初期化でデフォルトでLow出力になっている物を選ぶようにする。プルアップされた入力ピンなどはデフォルトで3.3Vが出ており、そこにMOSFETのゲートを繋ぐといきなりOn状態になって予期せぬドレイン電流が流れてしまうので注意。テスターを当ててGPIO2,4なら問題無さそうだったので、これらのピンを使うことにした。
電圧・電流測定のための回路
昇圧回路の入出力電圧はESP32に直接入力出来る定格電圧3.3Vを超えているので、抵抗分圧してからGPIO33とGPIO35へ入力するようにしている。またシャント抵抗R5を負荷とGNDの間に挟んでその電圧降下から出力電流を測定している。
PWM昇圧回路を制御するMicroPythonプログラム
上記の昇圧回路を動作させるためにはMicroPythonで以下の事を行えばよい。
- 昇圧出力電圧をADCで測定する
- 出力電圧の測定結果をに応じてPWMのデューティ比を制御する
以前の記事でI2C接続のssd1306ディスプレイに電流・電圧の測定値を表示するMicroPythonプログラムを作ったが、上記の1.は既にこのプログラムに含まれており2.のPWMの制御を追加すれば最低限の動作はOKだ。
というわけで、以前に作ったプログラムにPWM制御と測定結果のディスプレイ表示を追加して以下のようなMicroPythonプログラムを作った。
from volt import MilliVolt
from machine import Pin, I2C, Timer, PWM
from ssd1306 import SSD1306_I2C
class UpConverter(object):
def __init__(self, pin_vin, pin_vout, pin_miliamp, pin_pwm, vin = 5, vout = 12):
# 電圧/電流への変換係数
self.r1 = 1000 # 分圧抵抗1kΩ
self.r2 = 10000 # 分圧抵抗10kΩ
self.coef_v = (self.r1+self.r2)/(self.r1*1000)
self.coef_mi = 1.0 # シャント抵抗1Ω
# 入力電圧の範囲(-20%〜+20%迄許容)
self.vin_lb = (vin*0.8)
self.vin_ub = (vin*1.2)
# 出力電圧の上限(この電圧を超えたら出力を止める)
self.vout_ub = vout*1.2 # 12V*1.2=14.4V
# 出力電圧の許容範囲(±3%)
self.vout_uptgt = vout*1.03
self.vout_lotgt = vout*0.97
# PWMデューティ比の上限(ターゲット+10%の電圧に対する理論値)
self.duty_ub = int(round(1024*(1.0 - self.vin_lb/(vout*1.1))))
# 各種変数の初期化
self.vout = vout
self.pwon = 0;
self.vi = MilliVolt(pin_vin)
self.vo = MilliVolt(pin_vout)
self.mi = MilliVolt(pin_miliamp)
self.vo.atten(MilliVolt.ATTN_6DB)
self.val_vi = 0
self.val_vo = 0
self.val_mi = 0
self.duty_step = 4 # PWM周波数100kHz時のデューティ比解像度は256なので
self.duty = 0
self.pwm = PWM(pin_pwm, freq=100000, duty=self.duty)
def measure(self):
self.val_vo = self.vo.get()*self.coef_v
if ( self.pwon > 0 and self.val_vo < self.vout_ub ):
# 出力電圧をターゲット電圧と比較してデューティ比を増減する
if ( self.val_vo < self.vout_lotgt and self.duty < self.duty_ub ):
self.duty += self.duty_step
elif ( self.val_vo > self.vout_uptgt and self.duty > 0 ):
self.duty -= self.duty_step
else:
self.duty = 0 # 出力を止める(入出力電圧の異常時)
self.pwm.duty(self.duty)
def disp(self, disp):
disp.fill(0)
self.val_vi = self.vi.get()*self.coef_v
self.val_mi = self.mi.get()*self.coef_mi
# 入力電圧をチェックしてOKならself.pwonをセットする
if ( self.pwon > 0 and self.val_vi < self.vin_lb ):
# 入力電圧が許容レンジを外れた
disp.text('Input Out Of Range', 0, 32);
self.pwon -= 1 # カウンタを減算(2回連続ドロップしてたらオフに)
elif ( self.val_vi > self.vin_lb and self.val_vi < self.vin_ub ):
self.pwon = 2 # 入力電圧が正常なのでオン(ドロップカウンタをリセット)
else:
self.pwon = 0 # 入力電圧がレンジ外なのでオフする
# ディスプレイに各種測定値を表示する
disp.text('Vi:' + str(self.val_vi) + 'V', 0, 0);
disp.text('Vo:' + str(self.val_vo) + 'V', 0, 8);
disp.text('Io:' + str(self.val_mi) + 'mA', 0, 16);
disp.text('Duty:' + str(self.duty), 0, 24);
disp.show()
if __name__ == '__main__':
i2c = I2C(0)
oled = SSD1306_I2C(128, 64, i2c)
upconv = UpConverter(Pin(33), Pin(35), Pin(34), Pin(2), 5, 12)
tim0 = Timer(0)
tim1 = Timer(1)
tim0.init(period=10, callback=lambda t:upconv.measure())
tim1.init(period=500, callback=lambda t:upconv.disp(oled))
Timerオブジェクトによる定期的な呼び出し
10ms間隔で電圧測定と測定結果に基づくPWMデューティ比の変更を行い、500ms間隔で各種情報(入出力電圧、出力電流、デューティ比)のディスプレイ表示を行っている。
あくまで簡易的なPWM制御
PWMのデューティ比変更が出力電圧に反映される迄にはL1とC1の時定数で決まるタイムラグが存在する。上のプログラムでは現在の出力電圧の測定値に基づいて即デューティ比を変更しているが、これだとタイムラグのせいでターゲット電圧近傍で出力電圧がバタバタと振動してしまうのだ。この振動を緩和するために上記プログラムでは「出力電圧がターゲット電圧の±3%に入ったらPWMのデューティ比をいじらない」というルールを設けているが、それでも振動は起こってしまう。
ピタッと出力を安定させるためには、電圧の測定結果と回路の伝達特性に基づいてデューティ比の調整量を計算で求めるような厳密なフィードバック制御をマイクロ秒オーダーの間隔で高速に行う必要があるだろう。
PWM基本周波数は100kHz
特に深い理由は無いのだが、PWMの基本周波数は100kHzとした。以前の記事で調べた通り、ESP32のPWMは基本周波数によって解像度が変わり、100kHzの場合は256ステップとなるため、上記プログラムでも解像度に合わせた制御を行うようにした。
実回路で動作を確認
上記プログラムはPCからampyを使って実行することが出来る。
ampy run upconv.py
ampyの実行が完了した後もTimerオブジェクトによってデューティ比の調整や電圧測定のメソッドが定期的に呼び出されるため、昇圧型DC-DCコンバータとしての動作は継続される。またこの時、MicroPythonの対話型インタフェースが使える状態なのでシリアルコンソールを繋いで対話型インタフェースで以下を実行するとQ2のMOSFETがOnとなって負荷抵抗RLに電流が流れる。
from machine import Pin
pin = Pin(4, Pin.OUT)
pin.on()
負荷をOnした時の実回路の写真。ディスプレイには出力電圧が約12Vで出力電流が約250mAと表示されている。

下はADALM2000+Scopyによる電圧計を使って、出力電圧と負荷に繋がったシャント抵抗R3の電圧を測定したときの画面キャプチャ動画。負荷をOnした瞬間に出力電圧が一瞬5V付近まで下がるが徐々に12Vに戻る様子と、逆に負荷をOffした瞬間に一瞬電圧が16V付近まで一瞬跳ね上がってすぐに12Vに戻る様子がわかる。

下は電圧ログの画面を拡大した物。約40秒前に負荷がOnし、約17秒前に負荷がOffしている。On/Offの瞬間は出力電圧が乱れるものの、概ね12Vをキープ出来ている。

出力電圧を15Vに変更してみる
出力電圧を変更するには、上記プログラムの71行目でUpConverterの初期化パラメータを以下のように変更するだけだ。
upconv = UpConverter(Pin(33), Pin(35), Pin(34), Pin(2), 5, 15)
ディスプレイ表示を確認すると、一次側の5V電源に少し電圧ドロップがあるようだが、2次側には期待通り概ね15Vが出ている。

負荷をOn/Offさせ、ADALM200+Scopyによる電圧ログをキャプチャした結果。負荷へ流れる電流は約300mAへと増加しているが、MOSFETなどの部品に異常発熱も無く問題無さそう。

まとめ
ESP32に昇圧回路を追加して、MicroPythonでPWM制御を行う昇圧型DC-DCコンバータを作成した。ディスプレイに電圧や電流の測定結果をリアルタイム表示させPWMの制御を行うといった複雑な動作をさせている割に、MicroPythonのプログラムは100行足らずで済んだ。
今回作成したPWMの制御は簡易的な物で負荷の急激な変動に追従出来ない。複雑な計算を行う本格的なフィードバック制御を行えば改善出来る可能性はあるけれど、そこまでやるなら専用ICを使った方が良いと思う。ただ、バッテリーの充電などであれば負荷が急激に変動することも少ないため、例えば5V電源から鉛バッテリーの充電を行うような用途には使えるかもしれない。
コメント