ESP32のPWMをMicroPythonから制御して5V電源を12〜15Vに昇圧する

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の測定入力端子だ。

anime

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で以下の事を行えばよい。

  1. 昇圧出力電圧をADCで測定する
  2. 出力電圧の測定結果をに応じて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と表示されている。

graph

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

anime

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

graph

出力電圧を15Vに変更してみる

出力電圧を変更するには、上記プログラムの71行目でUpConverterの初期化パラメータを以下のように変更するだけだ。

    upconv = UpConverter(Pin(33), Pin(35), Pin(34), Pin(2), 5, 15)

ディスプレイ表示を確認すると、一次側の5V電源に少し電圧ドロップがあるようだが、2次側には期待通り概ね15Vが出ている。

graph

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

graph

まとめ

ESP32に昇圧回路を追加して、MicroPythonでPWM制御を行う昇圧型DC-DCコンバータを作成した。ディスプレイに電圧や電流の測定結果をリアルタイム表示させPWMの制御を行うといった複雑な動作をさせている割に、MicroPythonのプログラムは100行足らずで済んだ。

今回作成したPWMの制御は簡易的な物で負荷の急激な変動に追従出来ない。複雑な計算を行う本格的なフィードバック制御を行えば改善出来る可能性はあるけれど、そこまでやるなら専用ICを使った方が良いと思う。ただ、バッテリーの充電などであれば負荷が急激に変動することも少ないため、例えば5V電源から鉛バッテリーの充電を行うような用途には使えるかもしれない。

コメント