MicroPythonのPWM制御でリチウムイオン電池の充電器を作る

ESP32に降圧回路を外付けし、特別なICを使わずにESP32内蔵ADCを使ってMicroPythonから電圧と電流の監視とPWM制御を行うリチウムイオン電池の充電器を作ってみたので記事にまとめる。

スポンサーリンク

リチウムイオン電池の充電について

リチウムイオン電池は概ね以下のような条件で充電すればよい。

  • 充電前半は定電流充電
  • 充電後半は4.2Vの定電圧充電

マイコンで電圧と電流を監視しつつ制限値を超えないようにPWMのデューティ比の制御を行えば良く、充電動作の考え方としては以前に作った鉛バッテリーの充電器とほぼ同じだ。

ESP32利用の自作リチウムイオン電池充電器

とりあえず写真を。写真右下のブレッドボードにESP32マイコンによる充電回路は組んであり、写真左下で充電しているのはこの記事で紹介した古い携帯から取り出したバッテリーパック。瓶に入れているのは、万が一バッテリーパックが過熱しても手で持って対処出来るようにするための安全対策のつもり(^^;

photo

ディスプレイにマイコンによる測定値を表示しているのだが、念の為マルチメータでも測定を行っており(写真左上が充電電流0.27mA、右上がバッテリー電圧4.04Vを示している)マイコンの測定結果と概ね一致している。

ESP32外付け回路

外付け回路は以下のような降圧コンバータと電圧/電流測定用の抵抗のみのシンプルな物。回路図では省いたが上の写真にある通りSSD1306 OLEDディスプレイを取り付けて電圧と電流の値を表示させている(電圧・電流の表示に関する詳細は以前の記事を参照)。

image

Pch MOSFETを使ったハイサイド制御を行っているためESP32のPWM出力をインバータで反転してゲートに入れている。基本的な動作は以前の記事で作った鉛バッテリーの充電回路と全く同じだが、扱う電圧が低いのでゲートドライバの回路が簡単な物で済んでいる。

この回路の欠点は電流測定のためのシャント抵抗Rsの値が1Ωと大き目である点。この抵抗による電圧降下のため充電電流はせいぜい500mA程度に制限される。

MicroPythonプログラム

プログラムリスト

MicroPythonのプログラムリストは以下の通りで、100行足らずで済んでいる。電圧測定のために以前作ったMilliVoltクラスを使っている。

from volt import MilliVolt
from machine import Pin, I2C, Timer, PWM
from ssd1306 import SSD1306_I2C

class LiIonCharger(object):
    def __init__(self, pin_vin, pin_vout, pin_miliamp, pin_pwm, vin = 5, vend = 4.1, chgcur = 100):
        # 電圧/電流への変換係数
        self.r1 = 10000   # 分圧抵抗10kΩ
        self.r2 = 10000   # 分圧抵抗10kΩ
        self.r_shunt = 1.0 # シャント抵抗1Ω
        self.coef_v = (self.r1+self.r2)/(self.r1*1000)
        self.coef_mi = self.r_shunt
        self.coef_vdrop = self.r_shunt/1000 # 電圧ドロップ補正係数
        # 入力電圧の範囲(-10%〜+20%迄許容)
        self.vin_lb = (vin*0.9)
        self.vin_ub = (vin*1.2)
        # duty比の上限
        self.duty_lim = 1019
        # 終始電圧
        self.vend = vend
        # 充電電流の設定
        self.current(chgcur)
        # 各種変数の初期化
        self.is_charge = True
        self.vi = MilliVolt(pin_vin)
        self.vo = MilliVolt(pin_vout)
        self.mi = MilliVolt(pin_miliamp)
        self.vi.atten(MilliVolt.ATTN_11DB)
        self.vo.atten(MilliVolt.ATTN_11DB)
        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)
        self.state = 0;

    def chgon(self):
        self.is_charge = True

    def chgoff(self):
        self.is_charge = False

    def current(self, chgcur):
        # ターゲット電流の設定(±10%許容)
        self.chgcur_lb = chgcur*0.9
        self.chgcur_ub = chgcur*1.1

    def measure(self):
        raw_mi = self.mi.get()*self.coef_mi
        self.val_mi = self.val_mi*0.95 + raw_mi*0.05
        inst_val_vo = self.vo.get()*self.coef_v
        inst_val_vo -= self.coef_vdrop*self.val_mi # 配線の電圧降下を補正
        self.val_vo = self.val_vo*0.95 + inst_val_vo*0.05
        self.val_vi = self.vi.get()*self.coef_v
        if ( self.is_charge and self.val_vi > self.vin_lb and self.val_vi < self.vin_ub ):
            if ( self.val_vo < self.vend ):
                # 出力電流をターゲット電流と比較してデューティ比を増減する
                if ( self.val_mi < self.chgcur_lb and self.duty < self.duty_lim ):
                    self.duty += self.duty_step
                    self.state = 1
                elif ( self.val_mi > self.chgcur_ub and self.duty > 0 ):
                    self.duty -= self.duty_step
                    self.state = 2
                else:
                    self.state = 3
            elif ( self.duty > 0 ):
                # 終始電圧まで電圧を下げる
                self.duty -= self.duty_step
                self.state = 4
        else:
            self.duty = 0 # 出力を止める(入出力電圧の異常時等)
            self.state = 5
        self.pwm.duty(self.duty)

    def disp(self, disp):
        disp.fill(0)
        # ディスプレイに各種測定値を表示する
        chgstr = 'ChgOn' if self.is_charge else 'ChgOff'
        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.text('State:' + chgstr + ',' + str(self.state), 0, 32);
        disp.text('(Vend:' + str(self.vend) + 'V)', 0, 40);
        disp.show()

if __name__ == '__main__':
    i2c = I2C(0)
    oled = SSD1306_I2C(128, 64, i2c)
    charger = LiIonCharger(Pin(33), Pin(35), Pin(34), Pin(2), 5, 4.2, 500)

    tim0 = Timer(0)
    tim1 = Timer(1)
    tim0.init(period=50, callback=lambda t:charger.measure())
    tim1.init(period=500, callback=lambda t:charger.disp(oled))

プログラムの実行方法

PCのコマンドラインからampyで実行すればOK。

ampy run liioncharge.py

一度実行すれば後はタイマ割り込みで一連の動作が自動で行われるため、REPL(UART経由のインタープリタ)が使える状態になり、充電状態の変更が可能。REPLから出来るのは以下の操作だ。

  • 充電の停止
>>> charger.chgoff()
  • 充電の再開
>>> charger.chgon()
  • 電流の変更(充電電流をmA単位で指定)
>>> charger.current(300)

まとめ

というわけで、ESP32への簡単な外付け回路とMicroPythonプログラムによるリチウムイオン電池の充電器を作ってみた。ESP32内蔵ADCで電流を測定するために大きいシャント抵抗を使っており充電電流はせいぜい500mA位迄しか流せないが、充電電流をREPLで対話的に変えられるので充電テストにはそれなりに便利に使える。

この充電器を使って、ノートPCのバッテリーパックから取り出したリチウムイオンポリマー電池の充電テストを行っており、別記事にまとめてある。

コメント