MicroPythonのPWM制御を使って鉛バッテリーの充電器を作る

過去記事でMicroPythonのPWM制御を使った昇圧型DC-DCコンバータの実験を扱った。

MicroPythonプログラムだとPWMの制御スピードが遅く出力電圧を負荷の変化に高速追従させる事は難しいが、バッテリーの充電であれば負荷が急激に変化する事も無いのでMicroPythonでもスピードの問題は無いだろう。今回はMicroPythonでPWM制御を行う鉛バッテリー充電器を作ったので記事にまとめた。

スポンサーリンク

自作充電器のハードウエア構成

システムのイメージをブロック図に描くと以下のような感じ。
MicroPythonからPWMの制御を行って降圧コンバータを駆動する。

blockDlg

充電管理のための電圧/電流の監視にはINA219と内蔵ADCを組み合せて使うことにした。

  • 内蔵ADCで入力電源の電圧を測定
  • INA219で鉛バッテリーへの出力電流/電圧を測定

降圧コンバータはPチャネルのパワーMOSFETによる以下のような回路とした。

回路図

関連記事

複雑なハードウエア構成にも見えるが、個々の回路要素は全て過去記事で取り扱った物ばかりで、それらを寄せ集めただけだ。

降圧コンバータ

当ブログで最初に扱った降圧コンバータはNチャネルMOSFETを使った回路だった。

マイコンで電圧/電流測定を行うには回路全体でGNDを共通に出来るPチャネルMOSFETによるスイッチングの方が都合が良いのだが、Vgsの耐圧の問題をクリアする必要があり、パルス電位をスライドさせる回路を考えた。

この時の回路をベースにして、負荷抵抗の代わりにLC回路などを追加したのが上の回路図で挙げた降圧コンバータだ。

ESP32内蔵ADCによる入力電圧測定

ESP32内蔵ADCは補正が必要なため、MicroPythonで精度良く電圧測定するにはひと手間必要。

INA219による出力電圧/電流測定

SSD1306によるディスプレイ表示

充電器ハードウエアの制作

降圧コンバータには大電流を流すためクエン酸エッチングで専用基板を自作し、ESP32マイコンや周辺デバイスは基本的にブレッドボード上に回路を組んだ。ESP32マイコンとSSD1306ディスプレイだけで手持ちの400穴ブレッドボードが一杯になるため、INA219モジュールなど電圧/電流測定回路は小型ブレドボードに配置。

Photo

降圧コンバータ基板の表面。MOSFETやショットキーダイオードなど主な半導体は表面実装の物を手ハンダで取り付けた。

Photo

充電対象の鉛バッテリーは、50Ahのディープサイクルバッテリー。

https://akizukidenshi.com/download/ds/long/wp50-12_20180126.pdf

I/Oピン割当

ESP32のI/Oピンは以下の表のように割り当てた。

ESP32ピン割当
2PWM Out
18I2C SCL
19I2C SDA
35ADC(入力電源電圧監視)

INA219とSSD1306がI2C接続なのでI/Oピンを殆ど使わずに済んでいる。

鉛バッテリー充電制御MicroPythonプログラム

充電アルゴリズムについて

せっかくのマイコン制御なので、以下のような充電アルゴリズムとした。

  1. バルク充電: 定電流充電(充電上限電圧に達するまでの間)
  2. フロート充電: 定電圧充電(バルク充電で上限電圧到達後に移行)

充電電流は入力電源や充電回路など各構成要素の耐電流を考慮、充電電圧上限も鉛バッテリーの仕様から決める必要がある。
降圧コンバータ回路に使ったMOSFETやダイオードの定格電流が3〜4Aということもあり、定電流充電は少し控えめに2A程度に抑える想定。また、鉛バッテリーの仕様に基づいてバルク充電の上限電圧は14.5V、フロート充電電圧は13.5Vとした。

プログラムリスト

さすがMicroPython、わずか100行足らずで上記の充電アルゴリズムを実現するプログラムが書けてしまった。
基本は上記充電アルゴリズムに従って充電の電流/電圧をコントロールするが、安全のためにPWMのデューティ比を約0.9に制限している。
PWM周波数は50kHzに設定。

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

SHUNT_OHMS = 0.1
VDIV_R1 = 1000   # 分圧抵抗1kΩ
VDIF_R2 = 10000  # 分圧抵抗10kΩ
VHIS = 0.1       # ヒステリシス電圧
TRANS_TLD = 10   # 状態遷移の閾値
PWM_FREQ = 50000 # PWM周波数
DUTY_INIT = 750  # デューティ比初期値
DUTY_STEP = 4    # PWM周波数100kHz時のデューティ比解像度は256なので
DUTY_LIM = 900   # duty比の上限

class SolarCharger(object):
    def __init__(self, pin_adc, i2c, pin_pwm, vend = 14.5, imax = 2000, vfloat = 13.5):
        # 測定用オブジェクト
        self.in_adc = MilliVolt(pin_adc)       # 内蔵ADC(入力電圧)
        self.in_adc.atten(MilliVolt.ATTN_6DB)
        self.coef_adc = (VDIV_R1+VDIF_R2)/(VDIV_R1*1000) # 電圧の変換係数
        self.out_ina = INA219(SHUNT_OHMS, i2c) # INA219(出力電圧/電流)
        self.out_ina.configure()
        # 目標電圧/電流
        self.imax = imax # 最大電流(mA)
        self.vend = vend # 終止電圧(V)
        self.vfloatub = vfloat # フロート充電電圧上限(V)
        self.vfloatlb = vfloat - VHIS # フロート充電電圧上限(V)
        # PWM設定
        self.duty = DUTY_INIT
        self.pwm = PWM(pin_pwm, freq=PWM_FREQ, duty=self.duty)
        # 各種変数の初期化
        self.pwon = 0;
        self.state = 0 # 0:バルク充電, 2:フロート充電, -1:電源無し
        self.transcnt = 0

    def charge_control(self, disp):
        # 電圧/電流測定
        in_v = self.in_adc.get()*self.coef_adc
        out_v = self.out_ina.voltage()
        out_mi = self.out_ina.current()
        # 電源検出
        if in_v < out_v:
            self.state = -1
            self.duty = 0
        else:            
            if self.state < 0:
                self.state = 0
                self.duty = DUTY_INIT
                self.transcnt = 0
        # 状態別のPWM制御
        ddelta = 0
        if self.state == 0: # バルク充電
            if out_mi > self.imax:
                ddelta = -DUTY_STEP
            else:
                ddelta = DUTY_STEP
            self.transcnt = self.transcnt+1 if out_v > self.vend else 0
            if self.transcnt > TRANS_TLD:
                # TRANS_TLD回連続で超えたらフロート充電に移行
                self.transcnt = 0
                self.state = 1
        elif self.state == 1: # フロート充電
            if self.vfloatub < out_v:
                ddelta = -DUTY_STEP
            elif self.vfloatlb > out_v:
                ddelta = DUTY_STEP
        # duty比チェック&PWM設定
        if ddelta != 0:
            self.duty += ddelta;
            if self.duty > DUTY_LIM:
                self.duty = DUTY_LIM
            elif self.duty < 0:
                self.duty = 0
        self.pwm.duty(self.duty)
        # ディスプレイに各種測定値を表示する
        disp.fill(0)
        disp.text('Vin:%.2fV' % in_v, 0, 0);
        disp.text('Vout:%.2fV' % out_v, 0, 8);
        disp.text('Iout:%.2fmA' % out_mi, 0, 16);
        disp.text('Duty:%d' % self.duty, 0, 24);
        disp.text('State:%d' % self.state, 0, 32);
        disp.show()

if __name__ == '__main__':
    i2c = I2C(0)
    oled = SSD1306_I2C(128, 64, i2c)
    scharger = SolarCharger(Pin(35), i2c, Pin(2))

    tim0 = Timer(0)
    tim0.init(period=200, callback=lambda t:scharger.charge_control(oled))

充電器の動作確認

入力電源は、古いノートパソコンのACアダプタ(19.5V,2A)から供給。電源と充電回路の間に念の為2Aのヒューズを挟んで測定を行った。

パワーMOSFETのゲート電位波形をチェック

まずは回路と鉛バッテリーとの接続を外した状態でESP32のPWM出力に応じてパワーMOSFETのゲート電圧波形を確認。上記プログラムをampy runで実行しUSB接続のオシロスコープADALM2000で測定したところ、下の画面キャプチャのようにESP32からのPWM出力パルス波形(オレンジ色)に対して、パワーMOSFETのゲート電位波形(紫色)が期待通りの波形になっていることが確認出来た。

Scopy

電力変換効率をチェック

次はいよいよ鉛バッテリーと接続。電源と充電回路の間にマルチメータを挟んで供給電流を測定しながら動作させ、マルチメータとディスプレイから数字を拾ったところ一次側18.93V/1.38Aに対して二次側が14.38V/1.48Aと読み取れた。INA219の測定誤差で二次側の電圧/電流が1割程度小さく出ているようで、それに基づいて補正すると変換効率は概ね90%程度と計算出来る。

Photo

バルク充電からフロート充電への遷移も問題無し

バルク充電中は徐々に電圧が上昇して行き、14.5Vに到達するとフロート充電に遷移することが確認出来た。ただINA219に測定誤差があるようなので過充電を防止するために終始電圧をもう少し低めに設定しても良いかもしれない。

降圧コンバータ基板はけっこうな発熱

バルク充電では1A超の電流を流すため、降圧コンバータ基板の発熱が気になった。特にパワーインダクタは触れないくらい熱くなるので、もう少し大容量な物に交換した方が良さそう。

まとめ

マイコン制御のバッテリー充電器を自作する構想は自分の中でずっと温めていたが、
不慣れなアナログ回路まわりは壁にぶち当たっては試行錯誤の連続で、なんとか完成まで辿り着けた感じ。

特に素人設計のパワー段周辺の回路や終始電圧の設定などが適切かどうか心配な所もあるが、だましだまし使って行こうと思う。

何はともあれマイコン制御で電圧/電流の測定も出来るわけだし、マイコンで充放電サイクルのログを取ってグラフ表示させる、みたいな事もやってみたい。また、今回はACアダプタから電源を取ったが、電源をソーラーパネルにすれば殆ど同じ回路でソーラー充電器としても使えるのではないかと思っている。追って記事にする予定。

コメント