Flying ADC 16 channel BMS 96S test
- johu
- Site Admin
- Posts: 6679
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 361 times
- Been thanked: 1519 times
- Contact:
Re: Flying ADC 16 channel BMS 96S test
Thanks for checking on that.
I carried out an experiment of my own with 64 LFP cells all charged to the maximum of 3.4V or roughly 220V total.
With the 4 blocks not interconnected I observed nothing strange. So I connected them all with 500 Ohm in between and measured across one of them with the scope to be able to see any unwanted current spikes. Nothing.
It's a mix of 2.1 and 2.3 boards.
Became brave and connected them straight through without the resistors to see if anything happened. Again, nothing.
Will build more battery bricks (have about 120 cells)
I carried out an experiment of my own with 64 LFP cells all charged to the maximum of 3.4V or roughly 220V total.
With the 4 blocks not interconnected I observed nothing strange. So I connected them all with 500 Ohm in between and measured across one of them with the scope to be able to see any unwanted current spikes. Nothing.
It's a mix of 2.1 and 2.3 boards.
Became brave and connected them straight through without the resistors to see if anything happened. Again, nothing.
Will build more battery bricks (have about 120 cells)
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- Proton
- Posts: 261
- Joined: Sat May 06, 2023 2:23 am
- Location: Georgia/US
- Has thanked: 173 times
- Been thanked: 27 times
Re: Flying ADC 16 channel BMS 96S test
Do you have any 3.7v batteries to go over 4v? Even some small 26500 batteries would work.
- johu
- Site Admin
- Posts: 6679
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 361 times
- Been thanked: 1519 times
- Contact:
Re: Flying ADC 16 channel BMS 96S test
In fact I developed the BMS with a 16S NMC battery that I charged to 4.1V. Like said, I've never seen it blow up when only one module is used.
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- uhi22
- Posts: 1106
- Joined: Mon Mar 14, 2022 3:20 pm
- Location: Ingolstadt/Germany
- Has thanked: 200 times
- Been thanked: 607 times
Re: Flying ADC 16 channel BMS 96S test
That's a good starting point. In theory, nothing should break even if we inject voltage between the separate battery modules, like 10V DC, 100V DC, 400V rectangle, etc, right? Because the we assume "perfect isolation". But thinking of this:
1. There is capacitance across the opto. If we apply a rectangle between the battery and the chassis ground (which basically a isolation monitoring device could do), we can open the FETs, if the voltage divider, formed by C_iso and C_GS provides sufficient voltage on the gate.
2. Also a weak resistive connection (1 MOhm) over C_iso, together with higher voltage, has the same effect.
3. Also the C_CE needs to be checked, because it also forms a voltage divider with C_GS, in case the OUTP is jumping.
Github: http://github.com/uhi22 --- Patreon: https://www.patreon.com/uhi22
- johu
- Site Admin
- Posts: 6679
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 361 times
- Been thanked: 1519 times
- Contact:
Re: Flying ADC 16 channel BMS 96S test
Thanks!
So adding 22n in parallel to R6 should have a positive effect, right? Now the combined C_GS >> C_iso and C_CE which should pretty much rule out false triggers?
The dead time now needs to be increased to around 2 ms until R6 has sufficiently discharged C_22. With the given time constants (one switch every 25 ms) that should be no problem
So adding 22n in parallel to R6 should have a positive effect, right? Now the combined C_GS >> C_iso and C_CE which should pretty much rule out false triggers?
The dead time now needs to be increased to around 2 ms until R6 has sufficiently discharged C_22. With the given time constants (one switch every 25 ms) that should be no problem
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- uhi22
- Posts: 1106
- Joined: Mon Mar 14, 2022 3:20 pm
- Location: Ingolstadt/Germany
- Has thanked: 200 times
- Been thanked: 607 times
Re: Flying ADC 16 channel BMS 96S test
Hm, I'm not really convinced of my assumption anymore.
The C_iso is around 1pF (https://www.mouser.com/datasheet/2/143/ ... M5gYtOZNbV) and the C_gs specified as "max 600pF" (http://www.stansontech.com/download/Tre ... SRG_V1.pdf), so if we assume 300pF we would have a divider 1/300. So for reaching the treshold voltage (above 1V), we would need 300V. Of course, the layout may increase the C_iso, but I would not treat this as 100% proof to the the root cause.
Maybe multiple effects come together to reach the worst case, e.g. the above combined with some resistive leakage due to humidity. 1V threshold voltage together with 10k needs only 100µA. How much could the 10k be lowered?
The C_iso is around 1pF (https://www.mouser.com/datasheet/2/143/ ... M5gYtOZNbV) and the C_gs specified as "max 600pF" (http://www.stansontech.com/download/Tre ... SRG_V1.pdf), so if we assume 300pF we would have a divider 1/300. So for reaching the treshold voltage (above 1V), we would need 300V. Of course, the layout may increase the C_iso, but I would not treat this as 100% proof to the the root cause.
Maybe multiple effects come together to reach the worst case, e.g. the above combined with some resistive leakage due to humidity. 1V threshold voltage together with 10k needs only 100µA. How much could the 10k be lowered?
Github: http://github.com/uhi22 --- Patreon: https://www.patreon.com/uhi22
- johu
- Site Admin
- Posts: 6679
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 361 times
- Been thanked: 1519 times
- Contact:
Re: Flying ADC 16 channel BMS 96S test
Probably a lot. Cell drain reduction was priority but maybe I overshot the target there?
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
- uhi22
- Posts: 1106
- Joined: Mon Mar 14, 2022 3:20 pm
- Location: Ingolstadt/Germany
- Has thanked: 200 times
- Been thanked: 607 times
Re: Flying ADC 16 channel BMS 96S test
Maybe go with 2k2 || 10nF, and look what the statistics will say.
And reduce the cell drain by reducing the sampling frequency. Maybe introducing two modes with A: fast sampling, higher drain and B: slow sampling, low drain, so depending on the use case everybody can optimize just by setting a parameter.
And reduce the cell drain by reducing the sampling frequency. Maybe introducing two modes with A: fast sampling, higher drain and B: slow sampling, low drain, so depending on the use case everybody can optimize just by setting a parameter.
Github: http://github.com/uhi22 --- Patreon: https://www.patreon.com/uhi22
- uhi22
- Posts: 1106
- Joined: Mon Mar 14, 2022 3:20 pm
- Location: Ingolstadt/Germany
- Has thanked: 200 times
- Been thanked: 607 times
Re: Flying ADC 16 channel BMS 96S test
Another direction of thinking: At which voltage the flying ADC is flying, before any of the mux switches are activated? By which path is this voltage determined?
- into direction of chassis ground, it is isolated via the 5V/5V DCDC and the data isolators. Fine.
- into direction of battery, the voltage can rise or fall until a PFET will reach the avalanche voltage (>100V according to http://www.stansontech.com/download/Tre ... SRG_V1.pdf). Not much current can flow, it is only like static electricity.
Using this situation as starting point, could this lead to an overload of U_GS or an unintended switching of other channels at the moment when the first MUX channel turns on?
Edit: One possible damage scenario is caused by the Miller capacity. It turns the FET on, even if the gate is grounded by resistor. We have 20pF Miller, and C_GS<600pF. Let's assume 400pF. This forms a voltage divider 20pF/400pF, which is factor 20. The threshold voltage of 1V is easily reached by applying a voltage jump on the drain of 20V. Having the ADC sitting around at +100V or -100V at the beginning, it is easy to get a jump of 100V U_DS, resulting in a gate voltage of 5V. Multiple FETS will start to draw current in this situation, until the 10k gets the gates discharged.
Also for this scenario, lowering the 10k and adding a parallel gate capacitance would help.
Edit 2: An other scenario is, that in such a situation the maximum allow U_GS of +/-20V is violated. Because the source can get a quickly rising voltage via the body diode of the other FET, and the gate is still sitting and hold by the C_CE of the optocoupler. If the C_CE is in the same order of magnitude as the C_GS (20pF) or even higher, the gate voltage is only following the source with delay.
Also for this case, an additional gate capacitance would help.
- into direction of chassis ground, it is isolated via the 5V/5V DCDC and the data isolators. Fine.
- into direction of battery, the voltage can rise or fall until a PFET will reach the avalanche voltage (>100V according to http://www.stansontech.com/download/Tre ... SRG_V1.pdf). Not much current can flow, it is only like static electricity.
Using this situation as starting point, could this lead to an overload of U_GS or an unintended switching of other channels at the moment when the first MUX channel turns on?
Edit: One possible damage scenario is caused by the Miller capacity. It turns the FET on, even if the gate is grounded by resistor. We have 20pF Miller, and C_GS<600pF. Let's assume 400pF. This forms a voltage divider 20pF/400pF, which is factor 20. The threshold voltage of 1V is easily reached by applying a voltage jump on the drain of 20V. Having the ADC sitting around at +100V or -100V at the beginning, it is easy to get a jump of 100V U_DS, resulting in a gate voltage of 5V. Multiple FETS will start to draw current in this situation, until the 10k gets the gates discharged.
Also for this scenario, lowering the 10k and adding a parallel gate capacitance would help.
Edit 2: An other scenario is, that in such a situation the maximum allow U_GS of +/-20V is violated. Because the source can get a quickly rising voltage via the body diode of the other FET, and the gate is still sitting and hold by the C_CE of the optocoupler. If the C_CE is in the same order of magnitude as the C_GS (20pF) or even higher, the gate voltage is only following the source with delay.
Also for this case, an additional gate capacitance would help.
Github: http://github.com/uhi22 --- Patreon: https://www.patreon.com/uhi22
- johu
- Site Admin
- Posts: 6679
- Joined: Thu Nov 08, 2018 10:52 pm
- Location: Kassel/Germany
- Has thanked: 361 times
- Been thanked: 1519 times
- Contact:
Re: Flying ADC 16 channel BMS 96S test
In favour of this theory FETs always failed at power-up. Once the system is up and running I have never seen or heard of failures.
At power-up all FETs are open, this should be ensured by the pull-down resistors. So the ADC floats *somewhere*
I have witnessed the Miller effect on a simple H-bridge. The low-side FETs V_GS can be clamped to 0 with a strong driver and about 1 Ohm gate resistor. Still when you turn on the upper FET you get a spurious turn-on of the lower FET as well. It is only very short and doesn't do much damage because of the strong pull-down. But with 10k this is a different story (despite the lower Miller cap of the smaller FET)
At power-up all FETs are open, this should be ensured by the pull-down resistors. So the ADC floats *somewhere*
I have witnessed the Miller effect on a simple H-bridge. The low-side FETs V_GS can be clamped to 0 with a strong driver and about 1 Ohm gate resistor. Still when you turn on the upper FET you get a spurious turn-on of the lower FET as well. It is only very short and doesn't do much damage because of the strong pull-down. But with 10k this is a different story (despite the lower Miller cap of the smaller FET)
Support R/D and forum on Patreon: https://patreon.com/openinverter - Subscribe on odysee: https://odysee.com/@openinverter:9
Re: Flying ADC 16 channel BMS 96S test
so my 36S MEB modules have been in use for <1 week, so far no issues. Balancing is slow, I am helping the weakest cells with PSU CV set below before it starts tripping the hell out of ADC measurements on the adjacent cells and OCP <300mA.
The gate resistor has 8200pF 100V cap across it, extra 3R on cell taps through the polyfuse. Same i2cdelay=5 as other 2.3HW. Pretty thick coat of conformal coat. I have it completely shutting off with ignition at the moment. Cell taps go in pretty close proximity to the phase cables, which see <300A phase current.
Current bmsio code looks like this:
The gate resistor has 8200pF 100V cap across it, extra 3R on cell taps through the polyfuse. Same i2cdelay=5 as other 2.3HW. Pretty thick coat of conformal coat. I have it completely shutting off with ignition at the moment. Cell taps go in pretty close proximity to the phase cables, which see <300A phase current.
Current bmsio code looks like this:
Code: Select all
oid BmsIO::ReadCellVoltages()
{
const int totalBalanceCycles = 30;
static uint16_t balanceCycles[18] = {0};
static uint8_t chan = 0;
static float sum = 0, min, max, avg;
int balMode = Param::GetInt(Param::balmode);
bool balance = Param::GetInt(Param::opmode) == BmsFsm::IDLE && Param::GetFloat(Param::uavg) > Param::GetFloat(Param::ubalance) && BAL_OFF != balMode;
FlyingAdcBms::BalanceStatus bstt;
if (balance)
{
if (balanceCycles[chan] == 0)
{
balanceCycles[chan] = totalBalanceCycles; //this leads to switching to next channel below
}
else
{
balanceCycles[chan]--;
}
if (balanceCycles[chan] > 0 && balanceCycles[chan] < (totalBalanceCycles - 1))
{
float udc = Param::GetFloat((Param::PARAM_NUM)(Param::u0 + chan));
float balanceTarget = 0;
switch (balMode)
{
case BAL_ADD: //maximum cell voltage is target when only adding
balanceTarget = CorrectVoltage(Param::GetFloat(Param::umax));
break;
case BAL_DIS: //minimum cell voltage is target when only dissipating
balanceTarget = CorrectVoltage(Param::GetFloat(Param::umin));
break;
case BAL_BOTH: //average cell voltage is target when dissipating and adding
balanceTarget = CorrectVoltage(Param::GetFloat(Param::uavg));
break;
default: //not balancing
break;
}
// Calculate adaptive thresholds based on voltage delta and minimum values
float deltaV = CorrectVoltage(Param::GetFloat(Param::umax)) - CorrectVoltage(Param::GetFloat(Param::umin));
float chargeThreshold = MAX(3.0f, deltaV * 0.02f); // At least 3mV or 2% of delta
float dischargeThreshold = MAX(1.0f, deltaV * 0.01f); // At least 1mV or 1% of delta
// Static variables to cache global values for slave nodes
static float cachedUmin = 0.0f;
static float cachedUmax = 0.0f;
static float cachedUavg = 0.0f;
static uint32_t lastCacheUpdateTime = 0;
static uint32_t cacheCounter = 0;
cacheCounter++;
// Cache values with a timeout mechanism for slave nodes
if (!bmsFsm->IsFirst()) {
// Current cached values
float currentUmin = CorrectVoltage(Param::GetFloat(Param::umin));
float currentUmax = CorrectVoltage(Param::GetFloat(Param::umax));
float currentUavg = CorrectVoltage(Param::GetFloat(Param::uavg));
// Check if values changed significantly (received update from master)
bool valuesChanged = (ABS(currentUmin - cachedUmin) > 1.0f) ||
(ABS(currentUmax - cachedUmax) > 1.0f) ||
(ABS(currentUavg - cachedUavg) > 1.0f);
if (valuesChanged) {
// Update cached values
cachedUmin = currentUmin;
cachedUmax = currentUmax;
cachedUavg = currentUavg;
lastCacheUpdateTime = cacheCounter;
}
// Make balancing decisions more sensitive on slave nodes
chargeThreshold = MAX(1.5f, deltaV * 0.01f); // More aggressive (1.5mV minimum)
dischargeThreshold = MAX(0.5f, deltaV * 0.005f); // More aggressive (0.5mV minimum)
// If cache is stale (no updates for 200 cycles ~5 seconds), use even more aggressive thresholds
if ((cacheCounter - lastCacheUpdateTime) > 200) {
// Use more aggressive thresholds if not receiving regular updates
chargeThreshold = MAX(1.0f, deltaV * 0.005f); // Very aggressive
dischargeThreshold = MAX(0.3f, deltaV * 0.0025f); // Very aggressive
}
// Special case for significant outliers
float significantDeviation = 50.0f; // mV
if (ABS(udc - balanceTarget) > significantDeviation) {
// For big deviations, use even more aggressive thresholds
if (udc < balanceTarget)
chargeThreshold = 1.0f; // Extremely aggressive charge threshold for low cells
else
dischargeThreshold = 0.3f; // Extremely aggressive discharge threshold for high cells
}
}
if (udc < (balanceTarget - chargeThreshold) && (balMode & BAL_ADD))
{
bstt = FlyingAdcBms::SetBalancing(FlyingAdcBms::BAL_CHARGE);
}
else if (udc > (balanceTarget + dischargeThreshold) && (balMode & BAL_DIS))
{
bstt = FlyingAdcBms::SetBalancing(FlyingAdcBms::BAL_DISCHARGE);
}
else
{
bstt = FlyingAdcBms::SetBalancing(FlyingAdcBms::BAL_OFF);
balanceCycles[chan] = 0;
}
Param::SetInt((Param::PARAM_NUM)(Param::u0cmd + chan), bstt);
}
else
{
FlyingAdcBms::SetBalancing(FlyingAdcBms::BAL_OFF);
}
}
else
{
balanceCycles[chan] = totalBalanceCycles;
bstt = FlyingAdcBms::SetBalancing(FlyingAdcBms::BAL_OFF);
Param::SetInt((Param::PARAM_NUM)(Param::u0cmd + chan), bstt);
}
//Read cell voltage when balancing is turned off
if (balanceCycles[chan] >= totalBalanceCycles)
{
float gain = Param::GetFloat(Param::gain);
int numChan = Param::GetInt(Param::numchan);
bool even = (chan & 1) == 0;
if (chan == 0)
gain *= 1 + Param::GetFloat(Param::correction0) / 1000000.0f;
else if (chan == 1)
gain *= 1 + Param::GetFloat(Param::correction1) / 1000000.0f;
else if (chan == 15)
gain *= 1 + Param::GetFloat(Param::correction15) / 1000000.0f;
//Read ADC result before mux change
float udc = FlyingAdcBms::GetResult() * (gain / 1000.0f);
Param::SetFloat((Param::PARAM_NUM)(Param::u0 + chan), udc);
min = MIN(min, udc);
max = MAX(max, udc);
sum += udc;
//First we sweep across all even channels: 0, 2, 4,...
if (even && (chan + 2) < numChan)
chan += 2;
//After reaching the furthest even channel (say 12) we either change over to a higher odd channel
else if (even && (chan + 1) < numChan)
chan++;
//or lower odd channel
else if (even)
chan--;
//Now we sweep across all odd channels until we reach 1
else if (chan > 1)
chan -= 2;
//We have no reached chan 1. Accumulate values and restart at chan 0
else
{
chan = 0;
avg = sum / Param::GetInt(Param::numchan);
Accumulate(sum, min, max, avg);
min = 8000;
max = 0;
sum = 0;
}
FlyingAdcBms::SelectChannel(chan);
FlyingAdcBms::StartAdc();
}
}