Celebrating the independent kiwi spirit of invention.

Solar PV: Battery Management System

By Ian Mander, 12-17 February 2022.



A battery management system (BMS) is attached to a battery, and protects it from over charge, over discharge, over current while charging and discharging, and from being charged or discharged while outside a given temperature range. A good BMS will also balance the cells in the battery, and allow monitoring of individual cell voltages, not just the total voltage. A higher end BMS will offer Bluetooth connectivity, so monitoring can be done from a cellphone, and could also allow a user to turn the battery on and off from their cellphone.

I'm specifically looking at making a BMS for a LiFePO4 battery. This battery chemistry is very safe and can be operated within certain safety limits without a BMS, but to protect the battery it's still better to use one, especially if regular and unsupervised deep discharges are made. In other words, the BMS keeps an eye on the safety limits for you.

Ideally a solar charge controller will work together with the BMS, and might even be the same combined device. In the short term I'll be using the simple PWM charging controller from my solar panel, so it should be easier to build my own 4S (4 cells in series) BMS designed around an Arduino.


Features easy to implement

Feature Implementation
Cell voltage monitoring

Need to know what voltage each individual cell is at. This will enable many of the protection features to be implemented.

Will need a ring (or eye) terminal with light gauge wire on each of the cell positive terminals, but all except the positive terminal of the first cell have a voltage higher than the 5.0 V the Arduino can accept.

With my solar panel's charge controller I should only need to be able to measure up to about 15 V, but I think I've seen higher spikes while charging lead acid (which doesn't seem right for several reasons). Regardless, for future proofing the BMS (ie, when I incorporate an MPPT charge controller) being able to measure up to the open circuit voltage of the raw panel output – around 20.5 to 22 V – seems important.

The first cell which is under 5.0 V so the Arduino can measure it directly. The easiest way to measure the other cell voltages is using a voltage divider from ground to the positive of each cell, and take the difference of adjacent voltages. The high impedance of the Arduino ADC (analogue to digital converter) input combined with an internal reference voltage should give reasonably accurate samples.


  • Lots of different values of resistors needed.
  • Offers less precision than preferred (two decimals places) for the top three cells. For example, using 10 kΩ and 39 kΩ resistors on the topmost cell drops 24.5 V down to 5.0 V for convenient sampling by the Arduino, which with 10 bit sampling gives an input voltage resolution of 0.024 V.
  • Introduces uneven parasitic draw, so the cells will not drain evenly, necessitating balancing.
Cell balancing

Keep the individual cell voltages at about the same level.

Easiest implementation is to do it manually on the rare occasions it's needed. If the cells start off well balanced they won't need further balancing in the near future.

It wouldn't be much harder to monitor the cell voltages and on the cell voltage screen flash a cell voltage if it's more than a certain amount higher or lower than the other cells, then do a manual balance as convenient.

Over charge protection

Maximum recommended charge for a LiFePO4 cell is 3.65 V, or 14.6 V total, so disconnect charging when any cell reaches 3.65 V or if the total exceeds 14.6 V. These limits could be reduced if less than 100% charge is wanted, to put less stress on the battery.

Granted cell voltage monitoring, this can be done in software but depends on being able to turn on and off the battery.

Under charge protection

Minimum recommended charge for a LiFePO4 is 2.50 V, or 10.0 V total, so disconnect load/discharging when any cell drops to 2.50 V or if the total drops to 10.0 V. Because there's not a lot of capacity when the voltage drops below about 3.0 V I'd probably set the minimum voltage cutoff to something a bit higher than 2.5 V.

Granted cell voltage monitoring, this can be done in software but depends on being able to turn on and off the battery.

Current sensing

Sensing the current is not too hard, but sensing really big discharge current will require a moderately expensive sensor. If I keep discharge current less than 30 A then a single ACS712 sensor will be fine.

  1. For low current items such as op amps (see voltage monitoring below) and the Arduino itself, it should be possible to use a series resistor (through which the current is flowing) placed across the inputs of an op amp. (The op amp unused for the first cell voltage monitoring should do.) However, it would be rather hard to use that to measure its own current draw. Much easier to just assume 0.7 mA draw per dual op amp (or measure the current draw and program it in).

  2. The INA219 current sensor uses a 0.1 Ω shunt (a bit high) but can be used for high-side current sensing. However, it has a maximum of 3.2 A; not suitable for either charging or discharging.

  3. The ACS712 Hall effect current sensor has 5 A, 20 A, and 30 A versions. Either the 20 A version (actually ±20 A, with 100 mV/A) or the 30 A (±30 A, 66 mV/A) will do nicely for this job. The minimum division (least significant bit, or LSB) of the 20 A version with 10 bit sampling works out to 0.049 A. The 30 A version has an LSB of 0.074 A for 10 bit – not very accurate.

    Does it have low enough noise to be accurate with 10 bit depth sampling? The datasheet says the 30 A version has peak to peak noise of 7 mV. This works out to 0.106 A using Arduino 10 bit sampling. The 20 A version has 11 mV peak to peak noise. I think some averaging might be needed.

    I've read that extra precision can be achieved by oversampling, for example averaging 64 samples to give a 13 bit sample (with maximum value 8184 not 8191), with LSB 0.0061 A – excellent for a 2 decimal place display. The display is not going to update at more than 2 Hz, so 64 samples isn't a problem. Does it really work, though? If there's 7 mV of noise in the readings I'll have to take multiple samples anyway, just to get a reasonable 10 bits of precision.

    For programming it'll be simpler to use just the 30 A sensor for everything.

    30 A through the ACS712's shunt resistance of 1.2 mΩ gives 1.08 W, which with the copper board used in the datasheet testing equates to about a 25 °C rise in junction temperature above ambient; not too bad, but it would be warm enough to be too hot to touch. It remains to be seen how much copper the supplied sensor boards have.

Greater total current output

Having just 30 A output will be rather limiting. Increase current output capability while still measuring the current.

Multiple ACS712 sensors can be used to measure separate loads without problem, although it might help if the individual ACS712 sensors are spaced apart from each other. Each 30 A sensor can be separately connected to (for example) two or three lighter sockets fused at 15 A or 10 A each (respectively). This would also allow greater options for turning off part of the output without affecting other loads, and perhaps saving some blown fuses in the process.

Multiple ACS712 sensors could very likely be run in direct parallel for the same load without issue. (They should be somewhat self-balancing because of the increase in resistance with increase in temperature of the shunt resistor, and have enough safety margin because with 0.066 V/A for the 30 A version, at full range they can measure about 37 A. The internal shunt is rated up to 5x overcurrent.)

One ACS712 will be dedicated to the charge current, giving information about the power coming from the solar panel. (An extra solar panel could again be separately measured.) Because my solar panel short circuit current is rated at about 11 A I could use a 20 A version of the ACS712, giving 0.049 A resolution. This is enough for a single decimal place display, but not two decimal places.

Temperature monitoring

It's best to keep the battery cool to ensure a long cycle life, but the battery also has a temperature range over which it is allowed to be charged or discharged, 0 °C to 60 °C, with reduced rates just inside those limits.

Tape a temperature sensor onto the side of the battery and disconnect battery under 0 °C or over 60 °C for both charging and discharging, and warn if the temperature goes outside a smaller range, like 10 °C to 45 °C (where charging and discharging at reduces rates is recommended), perhaps with a buzzer.


Features harder to implement

Feature Implementation
Turn battery on and off electronically

This important feature allows the battery to be disconnected from the solar panel and/or load if any of the monitored voltages or currents is outside of the absolute limits. Input and output (charge and discharge) should be able to be turned on and off independently, and manually as well as automatically.

Fuses can be used to handle overcurrent situations, but it would be good to be able to save on fuses.

It will basically need multiple well heat sinked MOSFETs in parallel (or just one for each of charge and discharge if I don't go for particularly high current). Being able to use 10 V (or more) to switch the MOSFETS is nice because it offers a reduced resistance compared to switching with 5 V (more than 30% less resistance with my lowest resistance MOSFET). There's still enough resistance that a single MOSFET running 30 A will dissipate 5.85 W. Need heatsink!

The default state should probably be to have the battery off/disconnected, and only connect it if the required conditions are met.

By using a conservative discharge cutoff the Arduino could stay powered for a bit longer to allow for checking why it has just cut out. However, everything should be switched off if the battery voltage gets too low. The discharge data could be written to EEPROM before final shutdown.

Turn battery on and off physically

If the battery is kept in storage for several months, parasitic drain may become an issue.

  1. Install a switch able to cope with high current. Sounds expensive.

  2. Unbolt the ground terminal when kept in storage. What a hassle. And it should be ready to go in a power cut.

  3. Design the BMS so it has very low parasitic current, and thus can be left unattended for at least a couple of months without significant drain. This will take a bit more thought and testing, but is clearly the best option.

Prevent discharging through solar panel

The charging input should have discharge prevention to prevent the battery's charge being burnt off as heat by the solar panel every time the solar panel voltage drops below the battery voltage.

My present solar panel's charge controller has discharge protection, so it's probably not an urgent feature.

I think the highest rated diode I have is 5 A, but there's a way to do it with a MOSFET or two (taking into consideration their internal diode, which makes it trickier). Basically, turn it off if reverse current is detected on (either of) the charging input(s).

Cell voltage monitoring

Need to know what voltage each individual cell is at, and without losing resolution due to having to measure higher voltages with a voltage divider.

Use an op amp on each of the three top cells to output just the voltage of each cell, configured as differential amplifiers (like the "DC summing amplifier" configuration in the datasheet). This means the Arduino will be able to sample each cell voltage directly, without a voltage divider, giving higher precision for voltage measurements the three highest cells. The Arduino using 10 bit sampling will give 0.0049 V resolution for all cells – perfect for showing 2 decimal places. (A small ~1.3x gain could be used to get a little more precision in the reading but it's more bother for little real gain.)

The LM358 (dual op amp, 8 pin dual inline package) runs on up to 32 V, so powering it directly from the battery is fine. (Each op amp should be powered by the whole battery, not by the voltage of the cell it's measuring to ground) Only 11 cents (NZ) for the two needed to measure the three highest cells! Throw in a few 100 kΩ resistors to set up the required configuration (or 100 kΩ and 130 kΩ for 1.3x gain). Sadly, current draw is little more, typically 0.7 mA per LM358 instead of about 0.63 mA for all three voltage dividers, but the op amps could be turned off when not needed using just a single ordinary PNP transistor, not a moderately low resistance P-channel MOSFET for each voltage divider.

It has been suggested that using an op amp configured as a voltage follower on each cell will reduce the current draw on each cell as it's being measured. The money cost is insignificant – just another 5.5 cents (NZ) for the extra dual op amp package to be able to measure all the cells (using the unused op amp from one of the existing packages). However, op amps draw current just being powered on; the extra LM358 would draw more current (0.7 mA) than the resistors on all the input of the differential amplifiers.

If 100 kΩ resistors are used on the differential amplifiers, the highest the current draw will be through the total 200 kΩ resistance to ground on cell 4 when fully charged: I = V / R = 14.6 / 200,000 = 73 µA. The total current for the resistors of all three differential amplifiers will be up to 164 µA – far less than the total 2.1 mA the LM358s themselves are using. The resistor current is not going to have a measurable affect on the voltage of the cells, or the runtime, but it would be good to be able to turn off the op amps if/when monitoring is not wanted (which might be a bad idea).

Also, my op amps are not rail to rail, meaning the output of cell 4's voltage follower would not be the same as the battery voltage, which the op amp is powered by the battery.

Precise cell voltage monitoring

16 bit sampling will give much more precise cell voltage measurements. For simple display purposes the op amps give enough voltage resolution, but extra precision will allow energy to be calculated more accurately.

Use an ADS1115 – a 16 bit, 4 channel ADC (analogue to digital converter), 5 V operation. With the four channels it can measure the voltage of all four cells at once, relative to ground (using op amps on the highest three cells, as described above). The channels are typically matched to 0.05%, so it's quite consistent. Uses I2C serial communication with Arduino, so multiple ADS1115 ADCs can be used at once (up to four, giving a total of 16 channels).

One quirk is that it uses two's complement for its samples (ie, positive and negative values), so for just positive values it's effectively a 15 bit ADC – still gives a tiny 0.125 mV resolution (with full-scale input of 4.096 V, which can be changed with the built-in programmable gain amplifier). Very nice.

Cell balancing

Automatically maintain the voltage on all cells at approximately the same level. The discharge current could be up to 1C, but keeping it at or under the solar charge current sounds like a good idea. This feature should probably only function when charging.

Monitor the cell voltages and if a particular cell has a significantly higher voltage than the others then use a MOSFET to turn on a connected resistor to flatten that cell a bit.

Need some high power resistors, obviously. $17.16 for five 0.5 Ω, 50 W resistors. Maximum draw 7.3 A, or about 27 W each. Could do it more slowly with a 48 cent 3.3 Ω, 5 W resistor (Jaycar trade price), 1.1 A or 4.0 W.

From the amount of heat generated in the battery box, will also need forced ventilation.

Precise current sensing, with separate solar panel charging current

Again, 16 bit sampling will give much more precise current measurements, and will hopefully allow more accurate capacity and energy values to be calculated – the 0.074 A of the Arduino's 10 bit sampling is only good enough for a single decimal place. (Bearing in mind the ACS712 has a total output error of 1.5% and noise of 7 mV.)

Use an ADS1115 for this too. With the four channels, four ACS712 current sensors can be monitored at once, for a total output current up to 90 A and solar charging input up to 30 A (very nice to have that separate).

One problem is the ADS1115 has a full scale range of 4.096 V, which corresponds to 24 A for the 30A ACS712 and about 16 A for the 20 A ACS712. There are several work-arounds.

  1. Use the programmable gain amplifier in the ADS1115, set to ⅔. This would give a little more headroom (eg, 5.3 V when running on 5.0 V; specifically VDD + 0.3 V, with VDD no more than 5.5 V). Gives a resolution of 0.1875 mV, which is, um, something in mA.

  2. Wire the ACS712 in reverse, giving a value from 0 V (representing high current) up to 2.5 V (representing no current). An op amp could be used to invert that.

  3. Use an op amp (and a 2.5 V zener diode on the inverting input) to shift and amplify the signal, giving around 1.16 mA resolution for the full original 2.5 V range (corresponding to about 37.9 A), or 0.92 mA steps for measuring just the 30 A rating (use fuses).

Very precise current sensing

With the sampling resolution available with the ADS1115, I'd just go with a 30 A sensor for charging, but for more accurate sensing some changes could be made to it and the discharge sensors.

  1. My solar panel has a short circuit current rated at about 11 A, so a 20 A ACS712 would be fine. But because the charging current will only be negative (in to the battery), the ACS712 will only give a signal between 0 V and 2.5 V. With an op amp to invert and amplify the signal to the ADS1115's full-scale 0.0-4.096 V the 15 bit sampling could give a resolution of just 0.46 mA (while still coping with up to 15 A charging current).

  2. Probably even more accurate (especially given the ACS712 has a total output error of 1.5%) but more fiddly to set up would be to simply use a shunt resistor made from 2 mm copper wire feeding an op-amp which the ADS1115 samples (with or without the programmable gain amplification set to 16x). Calibration needed.


Features for displaying information

Feature Implementation
Separate screens showing battery/load statistics.

I'll use a 20 x 4 character illuminated LCD screen – the 16 x 2 character LCD screen is just a bit small for the information I want to display. I also have a board with several buttons arranged for navigation, which will allow interaction (changing between screens and eventually changing limit values without having to reprogram). Some example information screens:

           1   1   2
1  4   8   2   6   0

OVERVIEW      13.35V
 -12.33A    -164.61W
 178.91Ah     2290Wh
  89.4%    Est:13.9h

           1   1   2
1  4   8   2   6   0

OUTPUT        13.35V
 -16.72A    -223.21W
INPUT         13.35V
  +4.39A     +58.61W

           1   1   2
1  4   8   2   6   0

c1: 3.45V  c2: 3.46V
c3: 3.44V  c4: 3.45V

Max charge     4.39A
Max disch.    16.72A

           1   1   2
1  4   8   2   6   0

PV 22.0V | BAT 13.2V
   0.15A |     0.72A
  1.23Ah |    1.42Ah
Eff 87.8%| Remn: 52%

The PV voltage in the last display will only be needed once I get a different charge controller.

Are there small LCD screens which would give better resolution for a reasonable price?

Keep track of capacity and energy

This can only be a rough approximation, but it'll provide useful information.

Capacity = current * time
Energy = capacity * voltage

New capacity = old capacity + current * time
[where current can be negative/discharging or positive/charging]


Features for future implementation

Feature Implementation
Cell voltage monitoring

Need to know what voltage each individual cell is at.

A much more advanced approach for high voltage batteries (not really applicable here) would be to put an ATtiny13A or similar microcontroller on each cell to measure its voltage, then report its data to the main microcontrollor through an optical isolator. The ATtiny13A has 10 bit ADC with an internal voltage reference, and could easily be powered by the LiFePO4 cell it was monitoring.

Each ATtiny could also run an LED cell voltage display (green, yellow, orange, red), again powered by its own cell.

Active MPPT charge controller.

Genuine MPPT (maximum power point tracking) is required to get to get maximum power from solar panels. Using something like a perturb and observe algorithm is not too hard to achieve using an Arduino and a buck converter circuit.

Making a synchronous buck circuit with 98% efficiency is much harder.

Selectable voltage for charge, float, and discharge cutoff, or select preset voltages for a particular battery type. Temperature dependant; over 25 °C requires lower maximum charge voltage.
Turn individual items on or off at certain times (ie, multiple load outputs on timers). Requires an RTC (real time clock) on the Arduino.
Bluetooth monitoring Transmit realtime battery data over Bluetooth.
Bluetooth control

Receive (and execute) battery control commands over Bluetooth.

Turn on/off the battery, for example.



There are some very nice features here that are reasonably straight forward to get set up – in theory, at least. It's also nice to have some very easy features which can be used to get the BMS up and going before getting stuck into the more complicated features.


Celebrating the independent kiwi spirit of invention.

Return to ianman HOME | Back to Aqualab Home | Return to TOP
Inventions: Super Soaker Backpack | Air Cannon | Car Interior Lighting | LED Torch

* This would have been an ad.

When you buy stuff from Asian sellers:
Please don't buy stuff from a country in the middle of intimidating its neighbours.