Measuring cell voltages in series pack with LT1990 - Page 1
Forum section: electronics / projects

author: scttnlsn
author_rank: Newbie
timestamp: May 12, 2017, 06:34:03 pm
content: I've tried replicating the circuit described inhttps://www.eevblog.com/forum/projects/correct-way-to-measure-high-voltage-battery-pack/msg248302/but I'm having some issues and hoping someone can help me out.I have 4 LiFePO4 cells in series and I'm using LT1990 precision differential amps to try and monitor the voltage of each cell. All 4 cells are currently at 3.35V and I've attached a schematic of my circuit.I was getting some unexpected readings:VCELL2 = 3.418VVCELL3 = 3.395VVCELL4 = 3.344VAnd so I tried it out with the separate +12V and -5V rails (as shown inhttps://www.eevblog.com/forum/projects/correct-way-to-measure-high-voltage-battery-pack/msg248302/) and some bypass caps. Here's what I'm getting with the split supply:VCELL2 = 3.342VVCELL3 = 3.271VVCELL4 = 3.344VThat still doesn't seem right to me since those values are pretty far outside the expected error of the amp. Does anyone have any advice for me?Thanks a lot!

author: tatus1969
author_rank: Super Contributor
timestamp: May 12, 2017, 07:29:10 pm
content: can't see anything obviously wrong. You do nothing that the chip shouldn't be able to do. Maybe a problem somewhere else? Are you loading the battery during your test? That could cause voltage drops and ground offsets. How did you measure the true voltage, how the opamp result?I see that these chips are quite expensive. Maybe this one is an alternative solution: BQ76925. I have tested source code for it.

author: scttnlsn
author_rank: Newbie
timestamp: May 12, 2017, 07:39:56 pm
content: The only load on the cells with the +13.4V supply is the LT1990s themselves. When testing with the +12V/-5V I was using a separate supply and so there was no load at all on the battery. The cells are all brand new and so I expect the internal resistance of each to be quite low.I measured the true voltage of each cell with a multimeter directly across the cell terminals and read 3.350V (+/- 0.001V) for each. When reading the output of the amps I'm using the same multimeter with COM connected to GND and I probe the OUT pin of the LT1990s.I began playing around with the BQ76920. Would be interested to see the code you're using for the BQ76925 if you don't mind sharing it.Thanks!

author: tatus1969
author_rank: Super Contributor
timestamp: May 12, 2017, 07:58:57 pm
content: no problem, here it is (STM32 family). It is a bit interwoven with the rest of the module through some globals (e.g. time = 0.5ms tick, fCellConversion = raw 10 bit ADC conversion result from VCOUT pin, VCC=3.3V), but I hope it is still clear enough.Code:float fCellConversion = 0;float fCellVoltageRaw = {0,0,0,0,0,0};float fCellVoltage = {0,0,0,0,0,0};float fRefOffset = 0;float fRefGain = 0;float fAdcGain = 0;float fCellOffset = {0,0,0,0,0,0};float fCellGain = {0,0,0,0,0,0};float fBalTemp = 0;bool balPresent = FALSE;enum{CHECK_CHARGER,CHECK_CONNECTION,MEASURE_VREF,MEASURE_V0,MEASURE_V1,MEASURE_V2,MEASURE_V3,MEASURE_V4,MEASURE_V5,MEASURE_TEMP,DO_BALANCING,} balState = CHECK_CONNECTION;uint32_t balSwitches = 0;bool bChargerConnected = FALSE;uint32_t timeout_bal = 0;uint32_t timeout_chrg_check = 0;bool bBalSwap = FALSE;bool bChargeEnable = FALSE;bool bChargeComplete = FALSE;///////////////////////////////////////////////////////////////////////////////void waitTicks( uint32_t ticks ){uint32_t then;then = time + ticks;while( (int32_t)(then - time) > 0 );}///////////////////////////////////////////////////////////////////////////////void wait( float delay ){waitTicks( (uint32_t)(delay / TIME_STEP) );}///////////////////////////////////////////////////////////////////////////////bool WaitI2CEvent( uint32_t flag ){uint32_t event, timeout = 1000;do{event = I2C_GetLastEvent(I2C1);if( !--timeout ){return FALSE;}}while( (event & flag) == 0 );return TRUE;}///////////////////////////////////////////////////////////////////////////////void WriteBalancer( uint8_t reg, uint8_t value){I2C_GenerateSTART(I2C1, (FunctionalState)ENABLE);if( !WaitI2CEvent(I2C_FLAG_SB) ){I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);return;}I2C_Send7bitAddress(I2C1, (0x20 | reg) << 1, I2C_Direction_Transmitter);if( !WaitI2CEvent(I2C_FLAG_ADDR) ){I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);return;}I2C_SendData(I2C1, value);WaitI2CEvent(I2C_FLAG_BTF);I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);while( I2C_GetLastEvent(I2C1) & I2C_FLAG_BUSY );}///////////////////////////////////////////////////////////////////////////////uint8_t ReadBalancer( uint8_t reg ){uint8_t result;I2C_GenerateSTART(I2C1, (FunctionalState)ENABLE);if( !WaitI2CEvent(I2C_FLAG_SB) ){I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);return 0;}I2C_Send7bitAddress(I2C1, (0x20 | reg) << 1, I2C_Direction_Receiver);if( !WaitI2CEvent(I2C_FLAG_BTF) ){I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);return 0;}result = I2C_ReceiveData(I2C1);I2C_GenerateSTOP(I2C1, (FunctionalState)ENABLE);while( I2C_GetLastEvent(I2C1) & I2C_FLAG_BUSY );return result;}///////////////////////////////////////////////////////////////////////////////int32_t compl2( uint32_t number, uint32_t bits ){number &= ( 1 << bits ) - 1;if( number & (1 << (bits - 1) ) )return (int32_t)number - (int32_t)(1 << bits);elsereturn (int32_t)number;}///////////////////////////////////////////////////////////////////////////////enum{BAL_STATUS = 0x00,BAL_CELL_CTL,BAL_BAL_CTL,BAL_CONFIG_1,BAL_CONFIG_2,BAL_POWER_CTL,BAL_CHIP_ID = 0x07,BAL_VREF_CAL = 0x10,BAL_VC1_CAL,BAL_VC2_CAL,BAL_VC3_CAL,BAL_VC4_CAL,BAL_VC5_CAL,BAL_VC6_CAL,BAL_VC_CAL_EXT1,BAL_VC_CAL_EXT2,BAL_VREF_CAL_EXT = 0x1B,};void updateBalancer( void ){I2C_InitTypeDef I2C_InitStruct;uint8_t reg1, reg2;uint16_t reg3;float fBalTempRaw;// wait balancer discharging period before performing next actionif( timeout_bal > time ){return;}// stop balancing in case it is enabledWriteBalancer( BAL_BAL_CTL, 0 );// check if all cells are full and balancedbChargeComplete = TRUE;for( int i=0; i<6; i++ ){bChargeComplete &= (fCellVoltage >= 4.19f) && (fCellVoltage <= 4.21f);}// control charging switch based on charger presence, cell voltages, temperature, and balancer activitybChargeEnable = ( balPresent&& bChargerConnected&& !bChargeComplete&& (fBattTemp < 50.0f)&& (balSwitches == 0) ) ? TRUE:FALSE;if(bChargeEnable){GPIO_WriteBit( GPIOB, GPIO_CHRG_EN, Bit_SET );}else{GPIO_WriteBit( GPIOB, GPIO_CHRG_EN, Bit_RESET );}// work through balancer state machineswitch( balState ){//////////////////////////////////////////////////////////////////////////////case CHECK_CHARGER:// periodically check if charger is still connectedif( time > timeout_chrg_check ){// This disconnects the charger voltage from the bus voltage,// which is necessary to detect if the charger is still connected.GPIO_WriteBit( GPIOB, GPIO_CHRG_EN, Bit_RESET );// allow some time to discharge input capaticancewaitTicks(20);// check if charger is connectedbChargerConnected = (fChrgVoltage > 24.0f) ? TRUE:FALSE;// update timeout for next checktimeout_chrg_check += (uint32_t)(5.0f / TIME_STEP);}// loop through all states againbalState = CHECK_CONNECTION;break;//////////////////////////////////////////////////////////////////////////////case CHECK_CONNECTION:if( ReadBalancer( BAL_CHIP_ID ) == 0x10 ){WriteBalancer( BAL_CONFIG_2, 0x01 ); // VCOUT gain 0.6, VREF = 3.0V nomWriteBalancer( BAL_POWER_CTL, 0x47 ); // SLEEP_DIS, vc+ref+thermistor enabled// Calculate VREF offset and gainreg1 = ReadBalancer( BAL_VREF_CAL );reg2 = ReadBalancer( BAL_VREF_CAL_EXT );fRefOffset = (float)compl2( ((reg2 & 0x06) << 3) | (reg1 >> 4 ), 6 ) * 0.001f;fRefGain = 1.0f + (float)compl2( ((reg2 & 0x01) << 4) | (reg1 & 0x0F), 5 ) * 0.001f;// Calculate VC[0..5] offset and gainreg1 = ReadBalancer( BAL_VC_CAL_EXT1 );reg2 = ReadBalancer( BAL_VC_CAL_EXT2 );reg3 = ((uint16_t)(reg1 & 0xF0) << 4) | reg2;for( int i=0; i<6; i++ ){reg1 = ReadBalancer( BAL_VC1_CAL + i );fCellOffset = (float)compl2( (((reg3 >> (11 - 2*i)) & 0x01 ) << 4) | (reg1 >> 4 ), 5 ) * 0.001f;fCellGain  = 1.0f + (float)compl2( (((reg3 >> (10 - 2*i)) & 0x01 ) << 4) | (reg1 & 0x0F), 5 ) * 0.001f;}balPresent = TRUE;// connection successful, continue with next statebalState = MEASURE_VREF;}else{fCellVoltage =fCellVoltage =fCellVoltage =fCellVoltage =fCellVoltage =fCellVoltage = 0;// perform I2C reset in case of lockupI2C_Cmd(I2C1, (FunctionalState)DISABLE);I2C_SoftwareResetCmd( I2C1, (FunctionalState)ENABLE );I2C_SoftwareResetCmd( I2C1, (FunctionalState)DISABLE );I2C_InitStruct.I2C_ClockSpeed = 50000;I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;I2C_InitStruct.I2C_OwnAddress1 = 0;I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_Init(I2C1, &I2C_InitStruct);I2C_Cmd(I2C1, (FunctionalState)ENABLE);balPresent = FALSE;// balancer does not respond, back to charger detectionbalState = CHECK_CHARGER;}break;//////////////////////////////////////////////////////////////////////////////case MEASURE_VREF:WriteBalancer( BAL_CELL_CTL, 0x30 ); // VREF * 0.85waitTicks(5);fAdcGain = ( ( 3.0f * fRefGain + fRefOffset ) * 0.85f ) / fCellConversion;if( fAdcGain < 20e-6 || fAdcGain > 80e-6 ){// value out of range, assume problem with balancer and restartbalState = CHECK_CHARGER;}else{// continue with next statebalState = MEASURE_V0;}break;//////////////////////////////////////////////////////////////////////////////case MEASURE_V0:case MEASURE_V1:case MEASURE_V2:case MEASURE_V3:case MEASURE_V4:case MEASURE_V5:WriteBalancer( BAL_CELL_CTL, 0x10 | (balState - MEASURE_V0) ); // VC[0..5]waitTicks(5);fCellVoltageRaw[balState - MEASURE_V0] =( fCellConversion * fAdcGain + fCellOffset[balState - MEASURE_V0] ) * fCellGain[balState - MEASURE_V0] / 0.6f;balState++;break;//////////////////////////////////////////////////////////////////////////////case MEASURE_TEMP:WriteBalancer( BAL_CELL_CTL, 0x16 ); // Vtemp,intwaitTicks(5);fBalTempRaw = 25.0f - ( ( fCellConversion * fAdcGain - 1.2f ) * 1.35f /*+++ datasheet gain and offset seem to be wrong? */ ) / 4.4e-3f;if( fBalTemp == 0.0f ){fBalTemp = fBalTempRaw;}else{fBalTemp = fBalTemp * 0.9f + fBalTempRaw * 0.1f;}// continue with next statebalState = DO_BALANCING;break;//////////////////////////////////////////////////////////////////////////////case DO_BALANCING:// update cell voltage valuesfor( int i=0; i<6; i++ ){if( fCellVoltageRaw > 0.0f && fCellVoltageRaw < 10.0f ){if( fCellVoltage == 0 ){fCellVoltage = fCellVoltageRaw;}fCellVoltage = fCellVoltage * 0.9f + fCellVoltageRaw * 0.1f;}}// perform balancing as necessarybalSwitches = 0;if( !bChargeComplete ){// check which cell needs balancingfor( int i=0; i<6; i++ ){if( fCellVoltage > 4.2f ){balSwitches |= (1<< i);}}// adjacent cells cannot be balanced simultaneouslyif( bBalSwap ){bBalSwap = FALSE;balSwitches &= ~((balSwitches & (1+4+16)) << 1);balSwitches &= ~((balSwitches & (1+4+16)) >> 1);}else{bBalSwap = TRUE;balSwitches &= ~((balSwitches & (2+8+32)) << 1);balSwitches &= ~((balSwitches & (2+8+32)) >> 1);}// apply balancingif( balSwitches ){WriteBalancer( BAL_BAL_CTL, balSwitches );// start 1 sec discharge timertimeout_bal = time + (uint32_t)(1.0f / TIME_STEP);}}// continue with next statebalState = CHECK_CHARGER;break;//////////////////////////////////////////////////////////////////////////////default:while(1);}}Depending on what you want to do, this solution could also be an option:https://www.eevblog.com/forum/projects/introducing-my-project-'kicksurfer'/msg1203499/#msg1203499

author: scttnlsn
author_rank: Newbie
timestamp: May 12, 2017, 08:10:40 pm
content: Thanks. I'll take a look at both of those.I'd still like to get the LT1990 circuit figured out so if anyone else has suggestions, I'm all ears.

author: digsys
author_rank: Unknown
timestamp: May 15, 2017, 12:24:56 am
content: Quote from: scttnlsnJust a bit busy right now, but I'll definitely sort it out in a couple hrs-ish. They areslightlytricky to use, but I've never had an issue with them - awesome chips.

author: digsys
author_rank: Unknown
timestamp: May 15, 2017, 01:22:41 pm
content: Some notes: I found that the LT1990 definitely needs a -ve rail, irrespective of what the specs say. The MAX1044 cct I posted is simple and very efficient.You say you did try the separate 12V and -5V rails. Was it as per my posted cct? 0V must be referenced to all supply rails and Battery Pack 0V?The full circuit / layout details are in my gallery -www.pbase.com/digsys/image/161061592/originalI'm assuming you're checking against a reasonably accurate DMM? Did you have a rf cap and 50-100K resistor across the inputs? as per my cct. I find it helps a lotwithsurface noise, especially when there is NO load. Have you run a check against a DMM, on the cells? - without a load AND with a say 100mA load.I can't see anything obvious at this point, so we'd need more test results. As I said, never had a problem with accuracy once I sorted those couple issues.Post back and I'll investigate further.There are other lower voltage diff-amps in that series - " LT1990, LT1991, LT1995, LT1996 - one of the others may suit you better "

author: scttnlsn
author_rank: Newbie
timestamp: May 15, 2017, 07:39:37 pm
content: Yes, I'm using separate +12V and -5V rails for the V+ and V- pins on the LT1990s. I'm not using the MAX1044 at this point but rather just using two external power supplies to provide the two voltages. I tried everything again and this time added some 51K resistors across the cells (did not have those before). Updated schematic is attached.With no load:cell # / cell voltage / amp output1 / 3.333 / N/A2 / 3.335 / 3.3243 / 3.338 / 3.3304 / 3.339 / 3.287With constant 100mA load:cell # / cell voltage / amp output1 / 3.329 / N/A2 / 3.330 / 3.3153 / 3.332 / 3.3194 / 3.330 / 3.278I think those resistors improved the accuracy but some of these values still seem a bit far off to me. I'm probing the output of each LT1990 with a Uni-T UT61E. COM is connected to GND and the other probe on the output pin of the LT1990s.Thanks for the full schematic. Curious what sort of accuracy you typically see on the outputs of these amps? Also, I'm curious to hear more about the reason for those 50K resistors. How are they helping and what have you seen in the past without them?Thanks for your help!

author: digsys
author_rank: Unknown
timestamp: May 15, 2017, 11:26:19 pm
content: Quote from: scttnlsnTo answer your last question - 2 reasons - 1/Surface charge / ion migration. Unloaded cells (pretty muchalltypes) suffer charge migration when they go off load.This gives false readings as to the actual voltage. A perfect example (but for a different reason) is - Put a cell under heavy load, remove the load - and track the voltage.It starts to climb for a while, and eventually sort of stabilizes. But ion movement / charge is still occurring. I chose 50K as a compromise, during a race, I go as low as <10K.During the huge dynamics of a race, I need to know the cell voltages pretty accurately, and can't wait for things tosettle??2/ RF noise. We have huge amounts of noise from our 1.2KW solar MPPTs and 20KW 3phase motor controller. You do your best to minimize it, but it's pretty impossible to get ridof it all. The RC helps a lot. My new packs have RF/caps on every cell (module). Very few chemistries can handle switching / line noise above a few / several 100HzWithout this dampening, I've recorded up to 40Vrippleon a 180VDC pack. Even though this may be for a special case ie solar race car, I do it on EVERY pack I make,regardless of the use. Just a safety precaution.Going back to your problem - not sure - The readings are better, as you say, but I can't think of a reason right now. ALl ours are accurate to app 0.1% and up to 1-2-3% underextreme dynamics, but that's expected. Maybe try one of the other ICs in that familly I listed? Guess it's back to leg work :-)

author: tatus1969
author_rank: Super Contributor
timestamp: May 16, 2017, 08:47:58 am
content: if your problem is noise, then I would add an RC filter like below. What you have drawn in your last schematic is not yet that. Basically all BMS IC manufacturers do it like this.

author: scttnlsn
author_rank: Newbie
timestamp: May 16, 2017, 02:59:17 pm
content: Thank you both for your suggestions and explanations. I'll continue to play around and try different things and will post back if I discover something that works. I'll also take a look at those other LT parts.In the meantime I have a TI BQ769x0 BMS IC that is doing the trick.

author: digsys
author_rank: Unknown
timestamp: May 16, 2017, 10:13:49 pm
content: Yeah, it's an tough one. Maybe a ground path issue? after all, your down in the mVs. Maybe a decoupling issue? At least you know the chip works, so go from there.

author: prandov
author_rank: Newbie
timestamp: September 14, 2017, 09:24:00 pm
content: Did you come up with a configuration that worked and were ever you able to stabilize your measurements with the LT1990? Also, would adding a 1V offset eliminate the need for the -5V?

author: scttnlsn
author_rank: Newbie
timestamp: September 20, 2017, 12:41:30 pm
content: I never got the LT1990 configuration working well enough and ended up going with a completely different approach (optoisolated ADC per cell)

author: prandov
author_rank: Newbie
timestamp: September 25, 2017, 03:44:37 pm
content: I've been using regular LM662 op-amps set up as difference amplifiers powered by B+. It works reliably for 8s batteries, but was hoping to find a more integrated solution. As you discovered, the LT 1990 seems like a great choice.Did you try any of the TI or Analog options - Like the INA149 or the AD8479?

author: scttnlsn
author_rank: Newbie
timestamp: September 25, 2017, 07:50:57 pm
content: No, I did not try either of those, though they both look promising. Would be curious to hear back if you give them a shot.

author: jvspin
author_rank: Newbie
timestamp: November 14, 2017, 10:34:34 pm
content: Apologies for resurrecting an old topic but I was curious where the LT1990 was being used and came across this thread.The input range specifications on the datasheet imply the LT1990 needs the inputs to the internal amplifier to be at least 1V above V- and 0.85V below V+ for the part to operate correctly.In your circuit, tying both the reference pin (1) and V- (4) to ground would put the internal amplifier inputs to at best, 0.496V above V- due to the 1M:38.3k resistor divider.13.4V (top of the stack)/27=0.496V. This will not work correctly.If you use the formula in the datasheet and plug VREF=0V into the equation you get an input range of 27V to 112V on a single +5V,0V supply.The part should work fine in your circuit on +12V/-5V supplies with the REF pins at ground. I can think of two possible reasons for the errors you're seeing.1) The most likely reason is that the reference pins from each part are not tied together before going to a single ground point. The output voltage of each part is referred to it's REF pin, so any error voltage on the REF pin due to currents flowing in the line to ground will show up as an error in the output.To check if this is the problem, instead of measuring from the output of each LT1990 to ground, measure from each part's output to its reference input (pin 1).2) The Gain1 and Gain2 pins are not completely open. Any currents from these pins can cause a gain error.