16-cell BMS: Difference between revisions
m (Enable signal) |
|||
(18 intermediate revisions by 3 users not shown) | |||
Line 11: | Line 11: | ||
The software is not yet finished, see forum for progress <ref>https://openinverter.org/forum/viewtopic.php?t=2338</ref> | The software is not yet finished, see forum for progress <ref>https://openinverter.org/forum/viewtopic.php?t=2338</ref> | ||
The boards can be bought in the [https://openinverter.org/shop/index.php?route=product/product&product_id=74 shop]. | |||
== Pinout == | == Pinout == | ||
Line 19: | Line 21: | ||
On the top right is the vehicle interfacing or low voltage connector. From top to bottom it is low voltage GND, 12V, enable_in, enable_out, CANH, CANL. GND, 12V, CANH, CANL are connected to all modules in parallel whereas enable_out is chained to enable_in of the next module. The first enable_in must be connected to 12V whenever the BMS should become active, e.g. when charging or driving. The module with 12V on the enable_in auto-detects as "main" module, collects data from the subsequent modules and sends more high level CAN messages | On the top right is the vehicle interfacing or low voltage connector. From top to bottom it is low voltage GND, 12V, enable_in, enable_out, CANH, CANL. GND, 12V, CANH, CANL are connected to all modules in parallel whereas enable_out is chained to enable_in of the next module. The first enable_in must be connected to 12V whenever the BMS should become active, e.g. when charging or driving. The module with 12V on the enable_in auto-detects as "main" module, collects data from the subsequent modules and sends more high level CAN messages | ||
On the bottom right is the temperature sensor and current sensor input. Top to bottom it is tsensor1+, | On the bottom right is the temperature sensor and current sensor input. Top to bottom it is tsensor1+, tsensor1- (not silkscreened), tsensor2+ , tsensor2-(not silkscreened), 5V, CUR+, CUR-, GND. A single ended current sensor is just connected to CUR+. | ||
Right now the current sensor is only supported on the main module and only | Right now the current sensor is only supported on the main module. | ||
=== Connector part numbers === | |||
{| class="wikitable" | |||
|+ | |||
!Connector | |||
!Manufacturer | |||
!Header | |||
!Counterpart | |||
!Pins | |||
|- | |||
|LV connector (supply, CAN, enable) | |||
|Phoenix Contact | |||
|1803468 | |||
|1803617 | |||
| | |||
|- | |||
|Temperature and current sensors | |||
|Molex | |||
|640454-8 | |||
|1375820-8 | |||
|1445336-1 | |||
|- | |||
|Cell taps | |||
|Molex | |||
|1-640456-7 | |||
|1-1375820-7 | |||
|1445336-1 | |||
|} | |||
=== Power supply === | |||
The pins GND and 12V must be connected to a permanent 12V source in order to ensure correct operation. In other words, don't connect 12V for example to your cars ignition 12V that is interrupted whenever you stop the car. | |||
The enable input of the first (main) module is connected to an interruptable 12V source. This input starts up the BMS. Enable can stay high but even a 1s pulse is enough to start the BMS, as it will then keep itself running as long as needed. | |||
The BMS will then keep running as long as current flows through the battery. 2 hours after it has measured the last current flow it will shut down. It will use these 2 hours to let the cells settle to their open circuit voltage and will do balancing and [[16-cell BMS#State of Health calculation|SoH calculation]]. Afterwards it will shut itself down and consume only a few uA from your 12V source. | |||
== Communication == | == Communication == | ||
All configuration, queries and firmware updates are done via the CAN bus. There is no independent web interface like known from e.g. the inverter. However there is a solution to connect an ESP32 via CAN and then have the known web interface <ref>https://openinverter.org/forum/viewtopic.php?p=56913#p56913</ref> or you can use Dave Fiddes "oic" tool for the command line<ref>https://openinverter.org/forum/viewtopic.php?t=2907</ref>. It is important to know that the main module has node-id | All configuration, queries and firmware updates are done via the CAN bus. There is no independent web interface like known from e.g. the inverter. However there is a solution to connect an ESP32 via CAN and then have the known web interface <ref>https://openinverter.org/forum/viewtopic.php?p=56913#p56913</ref> or you can use Dave Fiddes "oic" tool for the command line<ref>https://openinverter.org/forum/viewtopic.php?t=2907</ref>. It is important to know that the main module has node-id 10 and the subsequent ones 10+i (i index of submodule, so 1st submodule has node-id 11). The base node id can be configured to start at something else than 10. | ||
'''All info given here is preliminary and will likely change!''' | '''All info given here is preliminary and will likely change!''' | ||
Apart from that the modules output periodic CAN messages depending on their role. | Apart from that the modules output periodic CAN messages depending on their role. The base address for cyclic messages defaults to 0x1F4 and can be changed by modifying pdobase on the main module. pdobase is ignored on the sub modules. | ||
All modules output on ID | All modules output on ID 501 (0x1F5) + i | ||
{| class="wikitable" | {| class="wikitable" | ||
|+ | |+ | ||
Line 70: | Line 107: | ||
The main module does not output its local accumulated values but accumulated values over all modules. | The main module does not output its local accumulated values but accumulated values over all modules. | ||
In addition the main module outputs on id | In addition the main module outputs on id 500 (0x1F4) | ||
{| class="wikitable" | {| class="wikitable" | ||
!start | !start | ||
Line 110: | Line 147: | ||
=== Reading individual cell voltages === | === Reading individual cell voltages === | ||
Cell voltages are only available via CAN SDO from the module that measures them. So voltages 1-16 must be queried from the first (main) module, 17-32 from the 2nd (1st submodule) and so on. With Dave Fiddes tool this translates to:<syntaxhighlight lang="bash"> | Cell voltages are only available via CAN SDO from the module that measures them. So voltages 1-16 must be queried from the first (main) module, 17-32 from the 2nd (1st submodule) and so on. With Dave Fiddes tool this translates to:<syntaxhighlight lang="bash"> | ||
oic -n | oic -n 10 dumpall #dumps all values including cell voltages from 1st module | ||
oic -n | oic -n 11 dumpall #same for second module | ||
oic -n | oic -n 11 read u0 #Read first cell voltage of second module | ||
</syntaxhighlight>For querying voltages say in your arduino sketch you'll need to acquire every voltage separately by sending and receiving SDO messages | </syntaxhighlight>For querying voltages say in your arduino sketch you'll need to acquire every voltage separately by sending and receiving SDO messages | ||
<code> | <code>0x60A#0x40 idhigh 0x21 idlow 0 0 0 0</code> | ||
idhigh and low are the bytes of the internal ID. The id of the first cell voltage is 2006 (0x7D6) so for querying the 1st cell voltage of the first module you send | idhigh and low are the bytes of the internal ID. The id of the first cell voltage is 2006 (0x7D6) so for querying the 1st cell voltage of the first module you send | ||
<code> | <code>0x60A#0x40 0x07 0x21 0xD6 0 0 0 0</code> | ||
And you will receive the reply | And you will receive the reply | ||
<code> | <code>0x58A#0x43 0x07 0x21 0xD6 0xD0 0xED 0x01 0</code> | ||
0x0001EDD0 is the voltage value which decodes to mV x 32. So 0x1EDD0/32 = 3950.5 mV. | 0x0001EDD0 is the voltage value which decodes to mV x 32. So 0x1EDD0/32 = 3950.5 mV. | ||
The next cell would be on 0x7D7 and the last on 0x7E5. The next 16 voltages are then queried from node id | The next cell would be on 0x7D7 and the last on 0x7E5. The next 16 voltages are then queried from node id 0x60B. | ||
=== Configuration Parameters === | === Configuration Parameters === | ||
There is currently a limited set of parameters which will be extended as the software grows. This table always reflects the latest git commit. Check the page history for previous versions. | There is currently a limited set of parameters which will be extended as the software grows. This table always reflects the latest git commit. Check the page history for previous versions. | ||
These are the parameters of v0. | These are the parameters of v0.20.B | ||
{| class="wikitable" | {| class="wikitable" | ||
|+ | |+ | ||
Line 177: | Line 214: | ||
|16 | |16 | ||
|16 | |16 | ||
|Number of cells connected to the module | |Number of cells connected to the module, power cycle required following saving parameters to update. | ||
|- | |- | ||
|balmode | |balmode | ||
Line 189: | Line 226: | ||
2=Only dissipate high cells | 2=Only dissipate high cells | ||
3=Do both | 3=Do both | ||
By default balancing starts 60s after power up or in fact 60s after idcavg settled below 1A. You will see opmode changing to RunBalance | |||
|- | |- | ||
|ubalance | |ubalance | ||
Line 204: | Line 243: | ||
|Number of seconds to wait after stop of current flow to measure unloaded cell voltage und start balancing | |Number of seconds to wait after stop of current flow to measure unloaded cell voltage und start balancing | ||
|- | |- | ||
| colspan="6" |'''Battery | | colspan="6" |'''Battery Characteristics''' | ||
|- | |||
|dischargemax | |||
|A | |||
|1 | |||
|2047 | |||
|200 | |||
|Maximum discharge current | |||
|- | |- | ||
| | |nomcap | ||
|Ah | |||
|0 | |||
|1000 | |||
|100 | |||
|Nominal capacity | |||
|- | |||
|icc1 | |||
|A | |||
|1 | |||
|2000 | |||
|50 | |||
|Maximum charge current of empty battery | |||
|- | |||
|icc2 | |||
|A | |||
|1 | |||
|2000 | |||
|30 | |||
|Maximum charge current of roughly half-charged battery | |||
|- | |||
|icc3 | |||
|A | |||
|1 | |||
|2000 | |||
|20 | |||
|Maximum charge current of rather full battery | |||
|- | |||
|ucv1 | |||
|mV | |||
|3000 | |||
|4500 | |||
|3900 | |||
|First constant voltage set point. Transition to next one when dropping below icc2 | |||
|- | |||
|ucv2 | |||
|mV | |mV | ||
| | |3000 | ||
|4500 | |4500 | ||
| | |4000 | ||
| | |Second constant voltage set point. Transition to next one when dropping below icc3 | ||
|- | |- | ||
|ucellmax | |ucellmax | ||
|mV | |mV | ||
| | |3000 | ||
|4500 | |4500 | ||
|4200 | |4200 | ||
| | |Third constant voltage set point and maximum cell voltage | ||
|- | |- | ||
| | |ucellmin | ||
| | |mV | ||
| | |1000 | ||
| | |4500 | ||
| | |3300 | ||
| | |Minimum loaded cell voltage. Discharge limit is dropped to stay above this value | ||
|- | |- | ||
|chargeXsoc | |chargeXsoc | ||
Line 241: | Line 315: | ||
|Percentage of charge current at soc X. In between two points charge current is interpolated | |Percentage of charge current at soc X. In between two points charge current is interpolated | ||
|- | |- | ||
| | |sohpreset | ||
|% | |||
|10 | |||
| | |100 | ||
| | |||
| | |||
|100 | |100 | ||
| | |This is the starting value for the state of health (SoH) calculation. The value is continuously updated by the BMS as SoH is estimated. | ||
|- | |- | ||
| colspan="6" |'''Sensor Setup''' | | colspan="6" |'''Sensor Setup''' | ||
Line 282: | Line 347: | ||
|tempsns | |tempsns | ||
| | | | ||
| | | 0 | ||
|3 | |3 | ||
| | | 0 | ||
|Temperature sensor | |Temperature sensor 0=None, 1=Channel1, 2=Channel3, 3=Both (set tempsns 0 in dashboard if dropdown not availble and value set as -1) | ||
|- | |||
|tempres | |||
|Ohm | |||
|10 | |||
|500000 | |||
|10000 | |||
|Nominal sensor resistance at 25°C | |||
|- | |||
|tempbeta | |||
| | |||
|1 | |||
|100000 | |||
|3900 | |||
|beta factor of Steinhart equation (see data sheet) | |||
|- | |- | ||
| colspan="6" |'''Communication''' | | colspan="6" |'''Communication''' | ||
Line 294: | Line 373: | ||
|2047 | |2047 | ||
|500 | |500 | ||
|base COB Id for cyclic messages. Main module uses pdobase, 1st submodule uses pdobase+ | |base COB Id for cyclic messages. Main module uses pdobase and pdobase+1, 1st submodule uses pdobase+2 and so on | ||
|- | |- | ||
|sdobase | |sdobase | ||
Line 301: | Line 379: | ||
|0 | |0 | ||
|63 | |63 | ||
| | |10 | ||
|base node ID for SDO queries. Main module uses sdobase, 1st submodule sdobase+1 and so on | |base node ID for SDO queries. Main module uses sdobase, 1st submodule sdobase+1 and so on | ||
' | |} | ||
=== Spot values === | |||
All values that the BMS either measures or calculates are called spot values. While all modules show all spot values, some only make sense on the main module. The column "scope" denotes that. "M" means the value is only relevant on the main module, "S" means it's relevant on main and sub modules. | |||
{| class="wikitable" | |||
|+ | |||
!Name | |||
!Unit | |||
!Scope | |||
!Description | |||
|- | |||
|version | |||
| | |||
|S | |||
|Firmware version | |||
|- | |||
|opmode | |||
| | |||
|S | |||
|State of operation | |||
* BOOT - System starting | |||
* GETADDR - Waiting for address assignment | |||
* SETADDR - Setting address of next node | |||
* REQINFO - Requesting info from sub modules | |||
* RECVINFO - Receiving info | |||
* INIT - Initialize BMS hardware | |||
* SELFTEST - Running hardware self tests | |||
* RUN - normal operation when current flows through battery | |||
* IDLE - battery is idle, i.e. no current through it for a given time | |||
* ERROR - selftests detected a critical error, operation halted | |||
|- | |||
|lasterr | |||
| | |||
|S | |||
|Last detected error, see Errors | |||
|- | |||
|errchan | |||
| | |||
|S | |||
|Cell tap channel causing the error, if applicable | |||
|- | |||
|modaddr | |||
| | |||
|S | |||
|Address of module | |||
|- | |||
|modnum | |||
| | |||
|S | |||
|Total number of modules | |||
|- | |||
|totalcells | |||
| | |||
|M | |||
|Total number of cells | |||
|- | |||
|counter | |||
| | |||
|S | |||
|Revolving counter 0-15 100 ms clock rate | |||
|- | |||
|uptime | |||
|s | |||
|S | |||
|Time in s since power was applied to 12V pin | |||
|- | |||
|chargein | |||
|As | |||
|M | |||
|Charge into the battery | |||
|- | |||
|chargeout | |||
|As | |||
|M | |||
|Charge from the battery to load | |||
|- | |||
|soc | |||
|% | |||
|M | |||
|State of charge, i.e. available charge in percent of fully charged battery | |||
|- | |||
|soh | |||
|% | |||
|M | |||
|State of health, i.e. available total charge in percent of data sheet value when new | |||
|- | |||
|chargelim | |||
|A | |||
|M | |||
|Current limit for charging the battery, see [[16-cell BMS#Charge Curve|Charge Curve]] | |||
|- | |||
|dischargelim | |||
|A | |||
|M | |||
|Current limit for discharging, see [[16-cell BMS#Calculating current limits|Calculating current limits]] | |||
|- | |||
|idc | |||
|A | |||
|M | |||
|Present current through battery, negative for current out of the battery, positive for current into the battery | |||
|- | |||
|idcavg | |||
|A | |||
|M | |||
|Average current of the last second | |||
|- | |||
|power | |||
|W | |||
|M | |||
|Average battery power of the last second | |||
|- | |||
|tempmin | |||
|°C | |||
|M | |||
|Minimum temperature of all modules | |||
|- | |||
|tempmax | |||
|°C | |||
|M | |||
|Maximum temperature of all modules | |||
|- | |||
|uavg | |||
|mV | |||
|M | |||
|average cell voltage over entire pack | |||
|- | |||
|umin | |||
|mV | |||
|M | |||
|Minimum cell voltage of entire pack | |||
|- | |||
|umax | |||
|mV | |||
|M | |||
|Maximum cell voltage of entire pack | |||
|- | |||
|udelta | |||
|mV | |||
|M | |||
|Difference between minimum and maximum cell voltage of entire pack | |||
|- | |||
|utotal | |||
|mV | |||
|M | |||
|Sum of all cell voltages, i.e. pack voltage | |||
|- | |||
|uX | |||
|mV | |||
|S | |||
|Present voltage of cell X of current module, see also [[16-cell BMS#Reading individual cell voltages|Reading individual cell voltages]] | |||
|- | |||
|uavgX, uminX, umaxX, tempminX, tempmaxX | |||
|mV, °C | |||
|M | |||
|Average/Minimum/Maximum cell voltage and temperature of sub module X | |||
|- | |||
|uXcmd | |||
| | |||
|S | |||
|State of balancer | |||
|- | |||
|cpuload | |||
|% | |||
|S | |||
|Time the CPU spends actually doing something | |||
|} | |} | ||
== Operation == | == Operation == | ||
=== State of Charge calculation === | |||
The State of Charge (SoC) lets you know how many percent of charge you battery still holds, in percent of a fully charged battery. | |||
Determining SoC is split into two methods: determining the SoC from cell voltage and then tracking it as current flows in or out of the pack. | |||
To estimate the SoC a 10-point lookup table is implemented that maps from cell voltage to SoC. The accuracy of this table is directly related to the accuracy of the estimation. You can edit the lookup table by changing the '''chargeXsoc''' parameters. The voltages you enter are open circuit voltages. These can only be obtained when there was no current flow through the battery for a certain amount of time, typically 10-30 minutes. This time is configured via the '''idlewait''' parameter. | |||
So whenever the BMS has the chance to estimate the SoC it will do so. You can adjust '''idlewait''' to a low value to start estimating quickly. It means the first estimates will be off but it will become more accurate if the pack remains unloaded for longer time. | |||
The subsequent tracking of SoC requires the BMS to know the current through the battery. Right now it supports analog current sensors (e.g. hall effect) with an operating voltage of 5V. Within this class it supports differential and single ended sensors, configurable via '''idcmode'''. The parameters '''idcofs''' and '''idcgain''' are used for calibration. When no current is flowing adjust '''idcofs''' until '''idcavg''' shows 0A. Then put a known current through the sensor and adjust '''idcgain''' until '''idcavg''' shows the correct current. | |||
Lastly the BMS needs to know the energy or charge content of your battery in Ah. This is configured with the '''nomcap''' parameter and can be found in the data sheet. | |||
=== State of Health calculation === | |||
The State of Health (SoH) lets you know how the remaining fully charged capacity, in percent of the data sheet value, i.e. when the battery was new. Batteries slowly degrade over time and usage cycles. | |||
Whenever we do a SoC estimation as described above, we also update the SoH. If the estimated SoC has changed by more than 20% we compare the energy that we have taken out of the battery to the difference in SoC. If the two match, the SoH is 100%. If we have taken more energy from the battery then the difference in SoC suggests, SoH is above 100% (and you likely configured a wrong '''nomcap'''). Otherwise SoH is < 100%. | |||
As this method comes with certain inaccuracies we only change the long term SoH slowly, i.e. every estimate is slowly averaged onto the long term SoH. This should balance out inaccuracies. | |||
The spot value '''soh''' shows the current long term SoH. The value is also reflected onto '''sohpreset''', a saveable parameter. Whenever you need to interrupt the BMSes power supply you should go should save parameters so that next time it starts up with the correct long term SoH. | |||
=== Calculating current limits === | === Calculating current limits === | ||
Line 312: | Line 577: | ||
==== Charge Curve ==== | ==== Charge Curve ==== | ||
The initial approach was to assign a certain charge current to points in the SoC curve. This method can be skewed if the SoC calculation is off for some reason. The new method deploys 3 CC/CV regulators. This was copied from oberving VWs charge curve of the MEB cars. The first regulator has the highest charge current and aims for a voltage typically around 3.9V for many NMC chemistries. When current drops below the CC value of the second regulator then that one is used, aiming for a higher voltage of typically 4V. And finally the 3rd regulator takes over aiming for the charge end voltage of typically 4.2V. When setting all 3 to the same values a simple CC/CV curve is generated. | |||
==== Voltage limits ==== | ==== Voltage limits ==== | ||
Line 320: | Line 583: | ||
==== Temperature limits ==== | ==== Temperature limits ==== | ||
High temperature will cut back both charge and discharge current whereas low temperature will only cut back charge current. These limits only work when you've set up your temperature sensors correctly. Please check the readings against a reference before relying on it. | |||
Charge current has a proportional, multi-step temperature derating, i.e. the charge curve defined by iccX and ucvX is scaled with a derating factor the lower the temperature or when the maximum temperature is approached | |||
* Below '''-20°C''' no charging is possible | |||
* Between '''-20°C and 0°C''' charging with 0-30% of the charge curve is allowed | |||
* Between '''0°C and 25°C''' charging with 30% to 100% of the charge curve is allowed | |||
* Between '''43°C and 50°C''' charging with 100% to 0% of the charge curve is allowed, i.e. '''above 50°C''' charging is no longer allowed. | |||
Discharging is not limited at low temperatures as high discharge currents at low temperatures are not known to cause damage. As the battery heats up discharge current is limited, starting at '''46°C''' until discharging is no longer allowed when reaching '''53°C'''. | |||
==== Energy limits ==== | ==== Energy limits ==== | ||
Not enforced yet. Some OEM BMSes cut back charge current after a certain amount of energy has been put into the battery. E.g. When starting charge at 50% charge rate will end up higher at 70% than if we start charging at 10%. | Not enforced yet. Some OEM BMSes cut back charge current after a certain amount of energy has been put into the battery. E.g. When starting charge at 50% charge rate will end up higher at 70% than if we start charging at 10%. | ||
== References == | == Calibration == | ||
As every instrument the voltage measurement needs to be calibrated to cancel out tolerances in the involved components. There are 4 parameters for calibration. | |||
* '''gain:''' The translation ration between the ADC digits and mV. This applies to all channels | |||
* '''correction0, 1 and 15''': This allows special tuning of said 3 channels because they use a slightly different current path and thus need a small gain correction | |||
=== Calibration procedure === | |||
First we determine the general gain. Pick any of the voltages between u2 and u14 which share a common gain factor. Use a good multimeter to measure the physical voltage of your reference channel. Now tune the '''gain''' parameter until your multimeter and the BMS voltage read the same value +-0.5mV. Now all channels from 2-14 should read correctly. | |||
Then for channels 0, 1 and 15 also measure the physical voltage input and tune the respective '''correctionX''' parameter until multimeter and BMS report the same voltage. The correction factors can be negative if a channels reads too high. | |||
Save your parameters to flash to make the calibration permanent. | |||
== Flashing firmware == | |||
If you have sourced the BMS boards from an alternative source then you will need to flash the bootloader and firmware onto the BMS. This is a relatively straightforward process once understood but took me a while to get it to work. | |||
Firstly you will need an STLink dongle, I used this one <ref>https://www.amazon.co.uk/dp/B07QBLNDPM?psc=1</ref> but I believe any will do. | |||
I used STM32CubeProgrammer <ref>https://www.st.com/en/development-tools/stm32cubeprog.html</ref> on Linux to flash my board and also (prior to flashing) upgrade the STLinkdongle to the latest firmware (might not be necessary but felt it was wise to do so). | |||
Once you've got STM32Cubeprogrammer installed you're probably best erasing the STM32 Chip to ensure you're starting from a clean board. Navigate to the "Erasing & Programming" tab on the left and connect to your STlink dongle (if you havent already). Once connected, (you can also update the firmware on the dongle at this point on the "Memory & File Editing" tab), connect the dongle to the BMS board via the VDCG pins... | |||
(V = Voltage 3.3V, D = SWDIO, C = SWCLK, G = Gnd) | |||
and perform a "full chip erase". | |||
Once thats complete head over to Github to download the Bootloader <ref>https://github.com/jsphuebner/stm32-CANBootloader/releases</ref> and download the hex file (you may need to be logged in to download the files). | |||
Once downloaded, in STM32CubeProgrammer, navigate to the "Memory & File Editing" tab and open the hex file you just downloaded, once opened you can use the "Download" button to flash the bootloader to your BMS board. | |||
You can now repeat the above process of opening the hex file and "downloading" to the BMS board for the main BMS firmware which is available from the BMS Github repository <ref>https://github.com/jsphuebner/FlyingAdcBms/actions</ref> . | |||
Once all that is done, you can move onto the Web interface. You'll need the "Can Backend" version of the ESP32 Web Interface available here <ref>https://github.com/jsphuebner/esp32-web-interface/tree/can-backend</ref> (click the green code button and download zip) | |||
Once downloaded download Arduino IDE 1.8.19 (you'll need this version, nothing beyond version 2 as this does not support SPIFFS flashing as far as I'm aware) and set it up for flashing to ESP32 boards <ref>https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html</ref> and also flashing ESP32 Spiffs <ref>https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/</ref>. | |||
To setup the Web interface Arduino sketch, you'll want to unzip and (IMPORTANT) RENAME the folder to "esp32-web-interface" to match the ino file within the folder, if you don't do this you'll have problems further down the line. Once unzipped and renamed, open the esp32-web-interface.ino sketch file and upload it to your ESP32 dev board. when this completed you'll also need to upload the SPIFFS image, you can do this by going to Tools and "ESP32 Sketch Data Upload" and choosing SPIFFS. once this is complete, you can connect your ESP32 to your BMS board via CAN and power them and they shuld work (note you'll need the 12V enable in line connected also). | |||
Once on the Web interface, select Node ID 10 and a speed of 500kbps and you'll have all your wonderful BMS spot values and params ready to go. | |||
== Operating Limits== | |||
* Max voltage for the board is 70v | |||
* Max number of cells is 16 | |||
==References== | |||
<references /> | <references /> |
Latest revision as of 17:46, 2 February 2025
The goal of this BMS is to use mostly decade old components to create a 16 channel cell voltage meter. It uses a very accurate sigma/delta ADC for superb resolution. It can drain about 100mA from a cell or charge it with 50 mA via the DC/DC converter. The ability to both charge and discharge cells speeds up balancing despite the low balancing current.
When turned off it drains no current from the connected cells at all.
On the low voltage side it features two inputs for temperature sensors and one input for a 5V current sensor, differential or single ended.
It can be cascaded to be used with more than 16 cells and has an automatic addressing scheme to forgo the use of jumpers or DIP switches. All communication is done via CAN. Supply voltage is 12V with about 20 mA draw when not balancing.
The software is not yet finished, see forum for progress [1]
The boards can be bought in the shop.
Pinout
The pinout is also written to the bottom silkscreen.
On the left side you see 17 inputs. The first one is connected to the negative pole (GND) of your (sub)pack, the subsequent ones to the positive poles of up to 16 cells. At least the lower two cell inputs must be connected. When cascading modules the positive-most input of module n is also the GND of module n+1, so must be spliced to connect to both.
On the top right is the vehicle interfacing or low voltage connector. From top to bottom it is low voltage GND, 12V, enable_in, enable_out, CANH, CANL. GND, 12V, CANH, CANL are connected to all modules in parallel whereas enable_out is chained to enable_in of the next module. The first enable_in must be connected to 12V whenever the BMS should become active, e.g. when charging or driving. The module with 12V on the enable_in auto-detects as "main" module, collects data from the subsequent modules and sends more high level CAN messages
On the bottom right is the temperature sensor and current sensor input. Top to bottom it is tsensor1+, tsensor1- (not silkscreened), tsensor2+ , tsensor2-(not silkscreened), 5V, CUR+, CUR-, GND. A single ended current sensor is just connected to CUR+.
Right now the current sensor is only supported on the main module.
Connector part numbers
Connector | Manufacturer | Header | Counterpart | Pins |
---|---|---|---|---|
LV connector (supply, CAN, enable) | Phoenix Contact | 1803468 | 1803617 | |
Temperature and current sensors | Molex | 640454-8 | 1375820-8 | 1445336-1 |
Cell taps | Molex | 1-640456-7 | 1-1375820-7 | 1445336-1 |
Power supply
The pins GND and 12V must be connected to a permanent 12V source in order to ensure correct operation. In other words, don't connect 12V for example to your cars ignition 12V that is interrupted whenever you stop the car.
The enable input of the first (main) module is connected to an interruptable 12V source. This input starts up the BMS. Enable can stay high but even a 1s pulse is enough to start the BMS, as it will then keep itself running as long as needed.
The BMS will then keep running as long as current flows through the battery. 2 hours after it has measured the last current flow it will shut down. It will use these 2 hours to let the cells settle to their open circuit voltage and will do balancing and SoH calculation. Afterwards it will shut itself down and consume only a few uA from your 12V source.
Communication
All configuration, queries and firmware updates are done via the CAN bus. There is no independent web interface like known from e.g. the inverter. However there is a solution to connect an ESP32 via CAN and then have the known web interface [2] or you can use Dave Fiddes "oic" tool for the command line[3]. It is important to know that the main module has node-id 10 and the subsequent ones 10+i (i index of submodule, so 1st submodule has node-id 11). The base node id can be configured to start at something else than 10.
All info given here is preliminary and will likely change!
Apart from that the modules output periodic CAN messages depending on their role. The base address for cyclic messages defaults to 0x1F4 and can be changed by modifying pdobase on the main module. pdobase is ignored on the sub modules.
All modules output on ID 501 (0x1F5) + i
start | length | item | description |
---|---|---|---|
0 | 13 | umin | minimum cell voltage of module i |
16 | 13 | umax | maximum cell voltage of module i |
30 | 2 | counter | counts from 0-3 (anti-freeze) |
32 | 13 | uavg | average cell voltage of module i |
48 | 8 | tempmin | Minimum temp +40 on module i |
56 | 8 | tempmax | Maximum temp +40 on module i |
The main module does not output its local accumulated values but accumulated values over all modules.
In addition the main module outputs on id 500 (0x1F4)
start | length | item | description |
---|---|---|---|
0 | 11 | chargelim | maximum charge current in A |
11 | 11 | dischargelim | maximum discharge current in A |
22 | 10 | soc | Pack soc in 0.1% |
32 | 16 | idcavg | Averaged current reading of last 1s in 0.1A - signed! |
48 | 10 | utotal | Total pack voltage in V |
62 | 2 | counter | counts from 0-3 (anti-freeze) |
Reading individual cell voltages
Cell voltages are only available via CAN SDO from the module that measures them. So voltages 1-16 must be queried from the first (main) module, 17-32 from the 2nd (1st submodule) and so on. With Dave Fiddes tool this translates to:
oic -n 10 dumpall #dumps all values including cell voltages from 1st module
oic -n 11 dumpall #same for second module
oic -n 11 read u0 #Read first cell voltage of second module
For querying voltages say in your arduino sketch you'll need to acquire every voltage separately by sending and receiving SDO messages
0x60A#0x40 idhigh 0x21 idlow 0 0 0 0
idhigh and low are the bytes of the internal ID. The id of the first cell voltage is 2006 (0x7D6) so for querying the 1st cell voltage of the first module you send
0x60A#0x40 0x07 0x21 0xD6 0 0 0 0
And you will receive the reply
0x58A#0x43 0x07 0x21 0xD6 0xD0 0xED 0x01 0
0x0001EDD0 is the voltage value which decodes to mV x 32. So 0x1EDD0/32 = 3950.5 mV.
The next cell would be on 0x7D7 and the last on 0x7E5. The next 16 voltages are then queried from node id 0x60B.
Configuration Parameters
There is currently a limited set of parameters which will be extended as the software grows. This table always reflects the latest git commit. Check the page history for previous versions.
These are the parameters of v0.20.B
Name | Unit | Min | Max | Default | Description |
---|---|---|---|---|---|
BMS | |||||
gain | uV/dig | 1 | 1000 | 586 | Scaling factor from ADC digits to mV |
correction0 | ppm | -10000 | 10000 | -1250 | The first two and the last channel have slightly different topology and need their own correction factor |
correction1 | ppm | -10000 | 10000 | 1500 | |
correction15 | ppm | -10000 | 10000 | 1000 | |
numchan | 1 | 16 | 16 | Number of cells connected to the module, power cycle required following saving parameters to update. | |
balmode | 0 | 3 | 0 | Balancing mode
0=No balancing 1=Only bump up low cells 2=Only dissipate high cells 3=Do both By default balancing starts 60s after power up or in fact 60s after idcavg settled below 1A. You will see opmode changing to RunBalance | |
ubalance | mV | 0 | 4500 | 4500 | Voltage above which top balancing is started |
idlewait | s | 0 | 100000 | 60 | Number of seconds to wait after stop of current flow to measure unloaded cell voltage und start balancing |
Battery Characteristics | |||||
dischargemax | A | 1 | 2047 | 200 | Maximum discharge current |
nomcap | Ah | 0 | 1000 | 100 | Nominal capacity |
icc1 | A | 1 | 2000 | 50 | Maximum charge current of empty battery |
icc2 | A | 1 | 2000 | 30 | Maximum charge current of roughly half-charged battery |
icc3 | A | 1 | 2000 | 20 | Maximum charge current of rather full battery |
ucv1 | mV | 3000 | 4500 | 3900 | First constant voltage set point. Transition to next one when dropping below icc2 |
ucv2 | mV | 3000 | 4500 | 4000 | Second constant voltage set point. Transition to next one when dropping below icc3 |
ucellmax | mV | 3000 | 4500 | 4200 | Third constant voltage set point and maximum cell voltage |
ucellmin | mV | 1000 | 4500 | 3300 | Minimum loaded cell voltage. Discharge limit is dropped to stay above this value |
chargeXsoc | % | 0 | 100 | Percentage of charge current at soc X. In between two points charge current is interpolated | |
sohpreset | % | 10 | 100 | 100 | This is the starting value for the state of health (SoH) calculation. The value is continuously updated by the BMS as SoH is estimated. |
Sensor Setup | |||||
idcgain | dig/A | -1000 | 1000 | 10 | Gain (or actually division factor) of current sensor |
idcofs | dig | -4095 | 4095 | 0 | 0A offset |
idcmode | 0 | 3 | 0 | Current sensor mode 0=Off, 1=Single Ended ADC, 2=Differential ADC, 3=ISA current shunt | |
tempsns | 0 | 3 | 0 | Temperature sensor 0=None, 1=Channel1, 2=Channel3, 3=Both (set tempsns 0 in dashboard if dropdown not availble and value set as -1) | |
tempres | Ohm | 10 | 500000 | 10000 | Nominal sensor resistance at 25°C |
tempbeta | 1 | 100000 | 3900 | beta factor of Steinhart equation (see data sheet) | |
Communication | |||||
pdobase | 0 | 2047 | 500 | base COB Id for cyclic messages. Main module uses pdobase and pdobase+1, 1st submodule uses pdobase+2 and so on | |
sdobase | 0 | 63 | 10 | base node ID for SDO queries. Main module uses sdobase, 1st submodule sdobase+1 and so on |
Spot values
All values that the BMS either measures or calculates are called spot values. While all modules show all spot values, some only make sense on the main module. The column "scope" denotes that. "M" means the value is only relevant on the main module, "S" means it's relevant on main and sub modules.
Name | Unit | Scope | Description |
---|---|---|---|
version | S | Firmware version | |
opmode | S | State of operation
| |
lasterr | S | Last detected error, see Errors | |
errchan | S | Cell tap channel causing the error, if applicable | |
modaddr | S | Address of module | |
modnum | S | Total number of modules | |
totalcells | M | Total number of cells | |
counter | S | Revolving counter 0-15 100 ms clock rate | |
uptime | s | S | Time in s since power was applied to 12V pin |
chargein | As | M | Charge into the battery |
chargeout | As | M | Charge from the battery to load |
soc | % | M | State of charge, i.e. available charge in percent of fully charged battery |
soh | % | M | State of health, i.e. available total charge in percent of data sheet value when new |
chargelim | A | M | Current limit for charging the battery, see Charge Curve |
dischargelim | A | M | Current limit for discharging, see Calculating current limits |
idc | A | M | Present current through battery, negative for current out of the battery, positive for current into the battery |
idcavg | A | M | Average current of the last second |
power | W | M | Average battery power of the last second |
tempmin | °C | M | Minimum temperature of all modules |
tempmax | °C | M | Maximum temperature of all modules |
uavg | mV | M | average cell voltage over entire pack |
umin | mV | M | Minimum cell voltage of entire pack |
umax | mV | M | Maximum cell voltage of entire pack |
udelta | mV | M | Difference between minimum and maximum cell voltage of entire pack |
utotal | mV | M | Sum of all cell voltages, i.e. pack voltage |
uX | mV | S | Present voltage of cell X of current module, see also Reading individual cell voltages |
uavgX, uminX, umaxX, tempminX, tempmaxX | mV, °C | M | Average/Minimum/Maximum cell voltage and temperature of sub module X |
uXcmd | S | State of balancer | |
cpuload | % | S | Time the CPU spends actually doing something |
Operation
State of Charge calculation
The State of Charge (SoC) lets you know how many percent of charge you battery still holds, in percent of a fully charged battery.
Determining SoC is split into two methods: determining the SoC from cell voltage and then tracking it as current flows in or out of the pack.
To estimate the SoC a 10-point lookup table is implemented that maps from cell voltage to SoC. The accuracy of this table is directly related to the accuracy of the estimation. You can edit the lookup table by changing the chargeXsoc parameters. The voltages you enter are open circuit voltages. These can only be obtained when there was no current flow through the battery for a certain amount of time, typically 10-30 minutes. This time is configured via the idlewait parameter.
So whenever the BMS has the chance to estimate the SoC it will do so. You can adjust idlewait to a low value to start estimating quickly. It means the first estimates will be off but it will become more accurate if the pack remains unloaded for longer time.
The subsequent tracking of SoC requires the BMS to know the current through the battery. Right now it supports analog current sensors (e.g. hall effect) with an operating voltage of 5V. Within this class it supports differential and single ended sensors, configurable via idcmode. The parameters idcofs and idcgain are used for calibration. When no current is flowing adjust idcofs until idcavg shows 0A. Then put a known current through the sensor and adjust idcgain until idcavg shows the correct current.
Lastly the BMS needs to know the energy or charge content of your battery in Ah. This is configured with the nomcap parameter and can be found in the data sheet.
State of Health calculation
The State of Health (SoH) lets you know how the remaining fully charged capacity, in percent of the data sheet value, i.e. when the battery was new. Batteries slowly degrade over time and usage cycles.
Whenever we do a SoC estimation as described above, we also update the SoH. If the estimated SoC has changed by more than 20% we compare the energy that we have taken out of the battery to the difference in SoC. If the two match, the SoH is 100%. If we have taken more energy from the battery then the difference in SoC suggests, SoH is above 100% (and you likely configured a wrong nomcap). Otherwise SoH is < 100%.
As this method comes with certain inaccuracies we only change the long term SoH slowly, i.e. every estimate is slowly averaged onto the long term SoH. This should balance out inaccuracies.
The spot value soh shows the current long term SoH. The value is also reflected onto sohpreset, a saveable parameter. Whenever you need to interrupt the BMSes power supply you should go should save parameters so that next time it starts up with the correct long term SoH.
Calculating current limits
Limiting charge and discharge current are the only means of this BMS to protect the battery. It has no means to turn off the current flow by itself but instead relies on the connected components to obey the calculated current limits that are sent out via CAN. The current limits are calculated in a number of steps.
Charge Curve
The initial approach was to assign a certain charge current to points in the SoC curve. This method can be skewed if the SoC calculation is off for some reason. The new method deploys 3 CC/CV regulators. This was copied from oberving VWs charge curve of the MEB cars. The first regulator has the highest charge current and aims for a voltage typically around 3.9V for many NMC chemistries. When current drops below the CC value of the second regulator then that one is used, aiming for a higher voltage of typically 4V. And finally the 3rd regulator takes over aiming for the charge end voltage of typically 4.2V. When setting all 3 to the same values a simple CC/CV curve is generated.
Voltage limits
Voltage limits are enforced via a P-regulator. I.e. as we approach the upper or lower voltage limit current is cut back to stay away from the limits until it reaches 0A. The upper and lower limit is configurable
Temperature limits
High temperature will cut back both charge and discharge current whereas low temperature will only cut back charge current. These limits only work when you've set up your temperature sensors correctly. Please check the readings against a reference before relying on it.
Charge current has a proportional, multi-step temperature derating, i.e. the charge curve defined by iccX and ucvX is scaled with a derating factor the lower the temperature or when the maximum temperature is approached
- Below -20°C no charging is possible
- Between -20°C and 0°C charging with 0-30% of the charge curve is allowed
- Between 0°C and 25°C charging with 30% to 100% of the charge curve is allowed
- Between 43°C and 50°C charging with 100% to 0% of the charge curve is allowed, i.e. above 50°C charging is no longer allowed.
Discharging is not limited at low temperatures as high discharge currents at low temperatures are not known to cause damage. As the battery heats up discharge current is limited, starting at 46°C until discharging is no longer allowed when reaching 53°C.
Energy limits
Not enforced yet. Some OEM BMSes cut back charge current after a certain amount of energy has been put into the battery. E.g. When starting charge at 50% charge rate will end up higher at 70% than if we start charging at 10%.
Calibration
As every instrument the voltage measurement needs to be calibrated to cancel out tolerances in the involved components. There are 4 parameters for calibration.
- gain: The translation ration between the ADC digits and mV. This applies to all channels
- correction0, 1 and 15: This allows special tuning of said 3 channels because they use a slightly different current path and thus need a small gain correction
Calibration procedure
First we determine the general gain. Pick any of the voltages between u2 and u14 which share a common gain factor. Use a good multimeter to measure the physical voltage of your reference channel. Now tune the gain parameter until your multimeter and the BMS voltage read the same value +-0.5mV. Now all channels from 2-14 should read correctly.
Then for channels 0, 1 and 15 also measure the physical voltage input and tune the respective correctionX parameter until multimeter and BMS report the same voltage. The correction factors can be negative if a channels reads too high.
Save your parameters to flash to make the calibration permanent.
Flashing firmware
If you have sourced the BMS boards from an alternative source then you will need to flash the bootloader and firmware onto the BMS. This is a relatively straightforward process once understood but took me a while to get it to work.
Firstly you will need an STLink dongle, I used this one [4] but I believe any will do.
I used STM32CubeProgrammer [5] on Linux to flash my board and also (prior to flashing) upgrade the STLinkdongle to the latest firmware (might not be necessary but felt it was wise to do so).
Once you've got STM32Cubeprogrammer installed you're probably best erasing the STM32 Chip to ensure you're starting from a clean board. Navigate to the "Erasing & Programming" tab on the left and connect to your STlink dongle (if you havent already). Once connected, (you can also update the firmware on the dongle at this point on the "Memory & File Editing" tab), connect the dongle to the BMS board via the VDCG pins...
(V = Voltage 3.3V, D = SWDIO, C = SWCLK, G = Gnd)
and perform a "full chip erase".
Once thats complete head over to Github to download the Bootloader [6] and download the hex file (you may need to be logged in to download the files).
Once downloaded, in STM32CubeProgrammer, navigate to the "Memory & File Editing" tab and open the hex file you just downloaded, once opened you can use the "Download" button to flash the bootloader to your BMS board.
You can now repeat the above process of opening the hex file and "downloading" to the BMS board for the main BMS firmware which is available from the BMS Github repository [7] .
Once all that is done, you can move onto the Web interface. You'll need the "Can Backend" version of the ESP32 Web Interface available here [8] (click the green code button and download zip)
Once downloaded download Arduino IDE 1.8.19 (you'll need this version, nothing beyond version 2 as this does not support SPIFFS flashing as far as I'm aware) and set it up for flashing to ESP32 boards [9] and also flashing ESP32 Spiffs [10].
To setup the Web interface Arduino sketch, you'll want to unzip and (IMPORTANT) RENAME the folder to "esp32-web-interface" to match the ino file within the folder, if you don't do this you'll have problems further down the line. Once unzipped and renamed, open the esp32-web-interface.ino sketch file and upload it to your ESP32 dev board. when this completed you'll also need to upload the SPIFFS image, you can do this by going to Tools and "ESP32 Sketch Data Upload" and choosing SPIFFS. once this is complete, you can connect your ESP32 to your BMS board via CAN and power them and they shuld work (note you'll need the 12V enable in line connected also).
Once on the Web interface, select Node ID 10 and a speed of 500kbps and you'll have all your wonderful BMS spot values and params ready to go.
Operating Limits
- Max voltage for the board is 70v
- Max number of cells is 16
References
- ↑ https://openinverter.org/forum/viewtopic.php?t=2338
- ↑ https://openinverter.org/forum/viewtopic.php?p=56913#p56913
- ↑ https://openinverter.org/forum/viewtopic.php?t=2907
- ↑ https://www.amazon.co.uk/dp/B07QBLNDPM?psc=1
- ↑ https://www.st.com/en/development-tools/stm32cubeprog.html
- ↑ https://github.com/jsphuebner/stm32-CANBootloader/releases
- ↑ https://github.com/jsphuebner/FlyingAdcBms/actions
- ↑ https://github.com/jsphuebner/esp32-web-interface/tree/can-backend
- ↑ https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html
- ↑ https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/