We have developed our own SOC algorithm which is pretty basic at this stage.
Vocabulary
SOC (State Of Charge):
Present charge level remaining in the battery / full charge in the battery
Decreases during every cycle, increases during charge, is 100% at 4.20V
Pack’s reported SOC is limited by the lowest cell’s SOC
e.g. 4Ah remaining, 10Ah when full ⇒ SOC = 40%
SOH (State Of Health):
Full charge in the battery / full charge when the battery was assembled
Decreases slowly along the battery cycles. Can indicate when to retire the battery
Pack’s reported SOH is limited by the weakest cell’s SOH
e.g. 9.6Ah when full, 10Ah when full when the pack was new ⇒ SOH = 96%
IR (Internal Resistance):
Indication of the battery’s ability to output current. Less is better. Tends to grow
DC IR and AC IR readings are different, see BatResearch article on the topic
Can be measured at pack level or (better) at cell level (group of // cells)
Depending on cell model, can range from 10mOhm to >70mOhm on ours
OCV (Open Circuit Voltage):
Voltage on the cell terminals when the battery has relaxed (current ~0.0A) for more than 90 minutes
The curve SOC=f(OCV) is specific to each cell model but ~independent of cell aging so we can easily derive SOC from a relaxed cell’s voltage if we have measured and know the curve
Example of an SOC=f(OCV) curve:
Roadmap
- Coulom counting + reset at 4.18V DONE
- Add OCV curve + cal at low SOC after 90min DONE
- SOH calculation DONE
- Two SOC scales
- Internal Resistance estimation
- IR and SOH at cell-level, SOP, EOL
Preliminary specification (10/03/22)
Basic algorithm:
- Coulomb counting for SOC(%)
- Reset to 100% when voltage > 4.18 (exact value TBD)
- Calibrate SOC based on OCV curve when battery inactive for more than 90min
- Update Present Capacity when recharged to 100% after a calibration
- Recalculate SOH = Present Capacity / Design Capacity
Initial BMS configuration
When the battery is assembled, some parameters must be entered in the BMS, which depend on the cells inside the battery:
Parameter | Unit | Description |
Design Capacity | Ah | For new cells only = nominal capacity found in the datasheet (e.g. 4P * 3350mAh = 13.4Ah) |
Factory Capacity | Ah | Real capacity measured after battery assembly, for new and for 2nd Life cells:
= measured sum capacity of the weakest group of parallel cells (e.g. 4P * 2.75Ah = 11Ah)
Can be measured with a “calibration cycle” right after battery assembly |
Present Capacity | Ah | = Design Capacity when assembling the pack, then Factory Capacity when measured |
Design Internal Resistance | mOhm | Measured before assembly |
Factory Internal Resistance | mOhm | Measured right after assembly. |
Present Internal Resistance | mOhm | Design Internal Resistance when assembling the pack, then Factory IR when measured |
SOC estimation during use
Normal operation Coulomb Counting
- Coulomb counting
- Integrate output current (add or subtract depending on current’s direction) in a counter when DSG and/or CHG is activated, to get remaining Ah
- Divide this value by Present Capacity ⇒ gives SOC (between 0.00 and 1.00)
- Reset SOC to 100% when voltage ~4.2V per cell (exact value TBD) which is a good reference point
- Global SOC should be based off of the lowest cell
- Only do this calibration if the lowest cell voltage is > a threshold above which we have good confidence in the SOC=f(OCV) curve. Probably around 3.45V, but we will need to have the curve of many cells to make sure we can use a fixed threshold for all, or if it is cell model dependent
SOC calibration phase
Due to the possible drift in Coulomb counting (integration), we have to calibrate the gauge regularly:
- At least every few cycles (if 4.2V cal has not been reached)
- After the battery has been sitting unused for a long time (SOC does not move while a small current is drawn by the sleeping BMS, which slowly discharges the pack)
We can do the calibration by:
- Looking at the lowest cell’s voltage when relaxed for more than 90 minutes (OCV)
- Reading the corresponding SOC in a look-up table (interpolate)
To get this SOC=f(OCV) curve we have to measure it on a test bench for each cell model we encounter, it is specific to the cell model used in the pack
SOC vs voltage (OCV)SOH estimation
After an SOC calibration, if a charge to 4.2V is done, we can update Present Capacity:
- Present Capacity = Ah_recharged / (1 - SOC_calib)
And then update the SOH:
- SOH = Present Capacity / Design Capacity
Example for a 9800mAh nominal pack:
- Battery has been sleeping for more than 90 minutes
- Voltage of the lowest cell is 3.72V ⇒ from the curve, SOC is 17%
- A charger is plugged, the battery charges
- Lowest cell reaches full threshold (e.g. 4.18V)
- ⇒ SOC is set to 100%
- 8670mAh have been recharged
- We update Present Capacity = 7490 / (1 - 0.17) = 9024mAh
- We update SOH = 9024 / 9800 = 92%
First cycle calibration
We could have a calibration mode in the BMS.
Assembled pack is connected to dummy load + charger with a mean to control its activation
- Charge. Once the cells are in balance at 4.2V, initialize SOC to 100%
- Discharge to 2.5V /cell, integrate current and set Design Capacity (Ah)
- Charge to storage (e.g. 30%)
During this calibration phase, monitor the balance between the cells, temperature rise and other significative values to check for any faults
Note: we could also have a SOC=f(OCV) curve measurement implemented directly in the BMS with the same hardware setup
IR estimation
During use, we can calculate the Internal Resistance, which gives a good indication, when going too high, that a cell is defective.
Method: measure voltage sag during big current draws, divide it by current to get battery IR:
- R = dU / dI
It should be taken at the right times (high current delta), around a certain SOC range (~middle) and the value should be filtered. See Ardupilot method with Kalman filter.
Note: IR should be calculated for global pack and for individual cells. Any imbalance or too high value should be supervised.
To go further (Not implemented yet)
Two SOC scales
We need two SOC scales
- One, real, precise for this cell model
- = Internal SOC
- One for the client / user with configurable reserves at each end
- = User SOC
- e.g.:
- User_SOC_100% = Internal_SOC_95% (to improve cycle life)
- User_SOC_0% = Internal_SOC_10% (to prevent deep discharge)
Battery health thresholds and End Of Life criteria
EOL (End Of Life):
Criteria based on SOH and/or IR degradation
e.g. 4Ah remaining, 10Ah when full ⇒ SOC = 40%
With multiple-level thresholds, we could :
- Warning ⇒ draw attention on an relatively high value
- Highlight this battery in our system to study its logs, find the cause and prevent premature failure
- Not fault indicated to the user
- Alert ⇒ draw user’s attention on a fault
- Maintenance required, our system is aware of this and we can inform the user
- Carry on battery operation but the user is informed
- Fault ⇒ stop operation when the level makes the battery unusable
- Our system is aware of the fault
- The user has not choice but bring the battery to maintenance
Note: same logic should be applied to other quantities such as battery temperature, cell balancing times, which are indicators of the battery’s use and health
How many km are requested by the fleet operator? EOL if < xkm ? Or capacity?
It seems they ask for cycle life values based on cell datasheet, so their demands are vague (e.g. >80% after 500 cycles...)
SOH thresholds
When any cell’s SOH < SOH_threshold
Threshold | SOH_thr value |
Warning | 0.92 |
Alert | 0.85 |
Protection | 0.50 |
Note: we should also monitor max-min differences between cells and have a threshold here too
IR thresholds
When any cell’s (Present IR / Design IR) > IR_threshold
Threshold | IR_thr value |
Warning | 1.3 |
Alert | 1.6 |
Protection | 2 |
Note: we should also monitor max-min differences between cells and have a threshold here too
State Of Power
SOP (State Of Power):
A sense of how much power (in watts) the battery can deliver at a given time
Depends on present temperature, IR, SOC, and needs pack thermal modeling
Can be used to reduce motor power (requires com with the controller) when the battey approaches its limits to prevent damage or protection trigger / operation stopping
e.g. battery at 50°C, max continuous current is calculated to be 10A ⇒ 400W at 40V ⇒ controller is asked to not draw more than 400W continous
Calculate SOP?
Not useful at the moment we have no control over max power on most of the controllers used by clients. We could use this indicatively, or to trigger a warning but without stopping operation
Note: we could know the ambient temperature if we have location and time
SOH filtering
LP filtering on the value to reduce variability and improve accuracy
Weight with amplitude of the recharge?
SOC/SOH at cell-level
If we later mix different cell models in a same battery pack, we need to track the SOC/SOH for each different cell group, which may have different SOC=f(OCV)
Individual SOC measurement allows for more efficient balancing
Cycle counting
Two things to implement:
- Charge/discharge cycle count
- Count the number of charges and discharges (2 consecutive charges or discharges are considered as 1)
- Allows e.g. to have a view of how many times the battery has been used. Divide by the time in circulation and we have the use frequency, along other useful stats
- Full cycles count
- Count the total Wh discharged and Wh charged to estimate the number of “full cycles”
- Useful for studying the total energy the pack has been able to give back versus its aging versus the cycle depht etc
Interesting values to log (to discuss):
- Mean DOD and DOC?
- Each charge > 10%, log temp and start/end SOC, for later analysis
Protections
The voltage-based protections in the BQ should be last-resort. We should primarily use the SOC to limit usage
Algorithm tests
SOH calibration test
Unplug 1 cell (insert insulation paper between cell and contact plate?) and run a calibration
- Check if SOH updates correctly
- Check if warnings have triggered correctly
Literature
Read this explanation on ImpedanceTrack on TI
See this “how to calibrate a smart battery” article