Main Traction Battery Upgrade i-MiEV

Mitsubishi i-MiEV Forum

Help Support Mitsubishi i-MiEV Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
Correction: capacityAh should be 90 not the value from the BMU..

If I understand correctly the value capacityAh is the max usable capacity, setting it to 90Ah is a good start for testing but probably needs to be a variable at a later stage when taking temperature compensation, allowable voltage range, SOH and whatever else affects it, into account?
 
MickeyS70 said:
It probably needs to be a variable at a later stage when taking temperature compensation, allowable voltage range, SOH and whatever else affects it, into account?

Hopefully the capacity doesn't change much. The temperature effect is not large. According to the Yuasa tech report on the LEV50 at -25oC the Ah capacity is 90% of the +25oC Ah capacity. I think that piev is right when he wrote that monitoring the voltage is sufficient to avoid problems. It should be enough just to change the 90 Ah to a new lower value as the cells age. This may be necessary once a month or once a year depending on how much the car is used.
 
Hopefully the capacity doesn't change much. The temperature effect is not large. According to the Yuasa tech report on the LEV50 at -25oC the Ah capacity is 90% of the +25oC Ah capacity. I think that piev is right when he wrote that monitoring the voltage is sufficient to avoid problems. It should be enough just to change the 90 Ah to a new lower value as the cells age. This may be necessary once a month or once a year depending on how much the car is used.
That’s probably true for the improved LEV50Ns as my original LEV50 pack certainly performs a lot worse in winter.

NMC cells operate best when warm (high 20s/low 30s deg C), hence EVs equipped with them usually have a battery heating/cooling system installed. That’s probably why it’s difficult to find reliable data about temperature related capacity changes but given the numerous reports of (new?) EV owners running out of juice during a cold snap might have something to do with it.

More important though ist the consensus that while NMC cells can be safely discharged in temperatures down to -20deg C, charging or regen in freezing temperatures can (will?) damage the cells permanently.

A quick fix would be to disable the car in freezing temperatures (set SOC to 0), next best: prevent regen and charging but the most elegant solution would be to redirect warm air into the battery pack.
 
Or maybe approach it from the other side. Instead of cheating the capacity, cheat the coulomb counter. Or maybe you could even physically shunt?
Sorry, I don't know English (Google translate)
 
Last edited:
Or maybe approach it from the other side. Instead of cheating the capacity, cheat the coulomb counter. Or maybe you could even physically shunt?
Sorry, I don't know English (Google translate)
That’s already been tried, while the general consensus was that it should work, in practice it didn’t for reasons unknown…
 
Computing the SoC from the voltage is quick and easy solution and it is a step in the right direction. But it doesn't work well unless the amps are between -1 and 1 for at least some minutes. When the battery is under load the SoC computed from the voltage can be as much as 20 % points below the true SoC.
I think you should use the battery's internal resistance value.

For example, if the battery voltage without load is 352V(4*88), the battery resistance is 66mOhm. If you give a load of 100A, then the voltage drop across the batteries will be 6.6V, the total voltage will be 352-6.6 = 345.4V.

Thus, knowing the load current, the total battery voltage and the internal resistance of the battery, you can determine the no-load voltage, relative to which the SOC should be calculated.



SOC 345.4|100A = SOC 352|0A
 
I think you should use the battery's internal resistance value.

For example, if the battery voltage without load is 352V(4*88), the battery resistance is 66mOhm. If you give a load of 100A, then the voltage drop across the batteries will be 6.6V, the total voltage will be 352-6.6 = 345.4V.

Thus, knowing the load current, the total battery voltage and the internal resistance of the battery, you can determine the no-load voltage, relative to which the SOC should be calculated.



SOC 345.4|100A = SOC 352|0A
That’s an interesting idea Frud, I incorporated coulomb counting with voltage used as a a remaining amp hour indicator. It seems to work very well. I still need to do some testing but I’m very pleased with the results.
 
I think you should use the battery's internal resistance value.

For example, if the battery voltage without load is 352V(4*88), the battery resistance is 66mOhm. If you give a load of 100A, then the voltage drop across the batteries will be 6.6V, the total voltage will be 352-6.6 = 345.4V.

Thus, knowing the load current, the total battery voltage and the internal resistance of the battery, you can determine the no-load voltage, relative to which the SOC should be calculated.
Interesting take, but unfortunately cell internal resistance isn’t a constant either; it is influenced by various factors, most importantly temperature.

As PIEV mentioned, the coulomb counting method is based on real-time, measured values. It needs however an accurate starting point and despite it’s flaws the voltage method seems to be the easiest way to do that.
 
That’s an interesting idea Frud, I incorporated coulomb counting with voltage used as a a remaining amp hour indicator. It seems to work very well. I still need to do some testing but I’m very pleased with the results.
Could I see your code? I have a lot of experience programming Arduino. Perhaps I could optimize the code for greater speed and performance.
 
Last edited:
That’s an interesting idea Frud, I incorporated coulomb counting with voltage used as a a remaining amp hour indicator. It seems to work very well. I still need to do some testing but I’m very pleased with the results.
Could I see your code? I have a lot of experience programming Arduino. Perhaps I could optimize the code for greater speed and performance.
 
Code:
// Arduino Due - Displays all traffic found on either canbus port
// By Thibaut Viard/Wilfredo Molina/Collin Kidder 2013-2014

// Modified by Paul Dove/ David Cecil to make a CAN bridge between ECU and BMU in 2012 Mitsubishi i-MiEV
// Reads all traffic on CAN0 and forwards it to CAN1 (and in the reverse direction)
// but modifies Soc1, SoC2 and Capacity values first PID 0x374 Bytes [0], [1], and [6].
// Required libraries

#include "variant.h"
#include <due_can.h>
#include <SPI.h>
//#include <SdFat.h> not used uncomment if using SD shield
//const int chipSelect = 4; //not used scared for SD card on Ethernet shield

//Leave defined if you use Due native port, comment if using programming port
#define Serial SerialUSB

//SdFat sd;//Not used at this time
//SdFile myFile;// scared in for writing to SD card using Ethernet shield

//Global variables
float volts;          //Average cell voltage
byte ah;              //Battery Voltage high byte PID 0x373 byte[4]
byte al;                //Battery Voltage low byte PID 0x373 byte[5]
float amps;             //Battery Curent
float remAh;          //Capacity remaining in battery
double capAh = 90;    //Battery Capacity
float SoCv;              //State of Charge based on battery voltage
float SoCah;            //State of Charge based on Coulomb counting
long presTime;          //Time in milliseconds
float step;           //Current interval
long prevTime = millis(); //time since last current calculation
int flag = 0;         //power up flag
long lowMins;         //Battery current at 0 timer
int j = 0;            //counter for valid data

void setup() {
 
  Serial.begin(115200); //Initialize Serial port on Arduino 115,200 Baud
 
// Initialize CAN0 and CAN1, Set the proper baud rates here
 
  Can0.begin(CAN_BPS_500K);
  Can1.begin(CAN_BPS_500K);

//Begin looking for data on Can0 and Can1

  Can0.watchFor();  //class CANRaw instance watchfor()
  Can1.watchFor();  //class CANRaw instance watchfor()
}
    
  //subroutine to store SoC based on voltage of the battery

void storeSoC() {       

     if (volts < 3.05376) {
        SoCv = 0;return;
      } else if (volts < 3.54912) {
        SoCv = 12.0962747344626 * volts - 36.9391199331124;return;
      } else if (volts < 3.65472) {
        SoCv = 60.5253601258929 * volts - 208.819755477546;return;
      } else if (volts < 3.70464) {
        SoCv = 122.699648332138 * volts - 436.049370070673;return;
      } else if (volts < 3.77376) {
        SoCv = 138.703950288505 * volts - 495.33954727031;return;
      } else if (volts < 3.7776) {
        SoCv = 1629.7714158898 * volts - 6119.20772303549;return;
      } else if (volts < 3.78528) {
        SoCv = 814.885707944995 * volts - 3040.89547270319;return;
      } else if (volts < 3.79488) {
        SoCv = 665.778961384803 * volts - 2476.48468708382;return;
      } else if (volts < 3.81216) {
        SoCv = 362.171425753317 * volts - 1324.33052226661;return;
      } else if (volts < 3.83136) {
        SoCv = 325.95428317799 * volts - 1186.26498002665;return;
      } else if (volts < 3.86592) {
        SoCv = 181.085712876656 * volts - 631.221334516933;return;
      } else if (volts < 3.91584) {
        SoCv = 122.699648332139 * volts - 405.505479872993;return;
      } else if (volts < 3.95232) {
        SoCv = 167.904781928189 * volts - 582.521550213749;return;
      } else if (volts < 3.99264) {
        SoCv = 158.518800329719 * volts - 545.425147422485;return;
      } else if (volts < 4.04064) {
        SoCv = 130.381713271193 * volts - 433.083888149129;return;
      } else if (volts < 4.09248) {
        SoCv = 120.72380858444 * volts - 394.05977215565;return;
      } else if (volts < 4.14816) {
        SoCv = 117.180923519597 * volts - 379.560585885481;return;
      } else if (volts < 4.20384) {
        SoCv = 112.398028682064 * volts - 359.720372836221;return;
      } else {SoCv = 112.782956058589;return;}

     }
  
        //Main program loop
void loop(){
  CAN_FRAME incoming; //capture can frames

  if (Can0.available() > 0) { //if there is an incoming frame read it
  Can0.read(incoming);
      if (incoming.id == 0x373){ //save BMU reported voltage and current in variable volts
        byte vh = incoming.data.byte[4];
        byte vl = incoming.data.byte[5];
        volts = (vh * 256 + vl) / 10.0 / 88.0;//4.1
        
          if (flag == 0){   //check to see if this is a new power up
            if (j == 20){   //wait 20 frames until CMU is providing good data

            storeSoC();     //get battery SoC based on voltage
            remAh = (SoCv/100)*capAh;    //calculate the capacity remaining in the battery based on voltage
            //Serial.print("remAh1 = "); Serial.println(remAh);
            //Serial.print("SoCv = ");Serial.println(SoCv);
            flag = 1;
            }    j=j+1;     
        }
        byte ah = incoming.data.byte[2];  //store current bytes and calculate battery current
        byte al = incoming.data.byte[3];
        amps = (ah * 256 - al -32768)/100.0;
        //Serial.print ("volts = "); Serial.println (volts);
        //Serial.print ("amps = "); Serial.println (amps);
        
    presTime = millis();    //calculate the interval for amp hour calculation
        step = (presTime - prevTime)/1000.0/3600.0;
        prevTime = presTime;         
        remAh = remAh + amps*step;  //update remaining amp hours based on current in or out of battery since startup
          if (amps < 1 && amps > -1) {  //store minutes elapsed since current was 0
             lowMins += step * 60;     
          }   else {lowMins = 0;}
          //Serial.print("remAh2 ="); Serial.println(remAh);       
          Can1.sendFrame(incoming);  //send can frame out to ECU
      }
    if (incoming.id == 0x374){        //capture SoC and Capacity from BMU
       incoming.data.byte[6] = 0xB4;  //modify capacity to new capacity of 90 Ah
       if (lowMins > 3) {             //when battery current has been 0 for long enough for battery voltage to settle
       storeSoC();              //get SoC based on battery voltage
       remAh = (SoCv/100)*capAh;      //correct remaining capacity bsed on voltage
       //Serial.print("remAh3 = "); Serial.println(remAh);
       }
       else {  lowMins < 10;        //wait if current has not been 0 long enough
       SoCv = 100.0 * remAh / capAh;

        }
       //Serial.println(remAh);
       //Serial.print("SoCv = ");Serial.println(SoCv);
       incoming.data.byte[0] = 2 * SoCv + 10;  //modify data coming from BMU
       incoming.data.byte[1] = 2 * SoCv + 10; 

      Can1.sendFrame(incoming); //send corrected SoC based on 90 Amp hours to ECU
       }
    else Can1.sendFrame(incoming); //send any captured frame from BMU that was not 0x373 or 0x374 to ECU
  }
   if (Can1.available() > 0) { //look for messages from ECU and forward to BMU
      Can1.read(incoming);
      Can0.sendFrame(incoming);
    }
}
 
Code:
// Arduino Due - Displays all traffic found on either canbus port
// By Thibaut Viard/Wilfredo Molina/Collin Kidder 2013-2014

// Modified by Paul Dove/ David Cecil to make a CAN bridge between ECU and BMU in 2012 Mitsubishi i-MiEV
// Reads all traffic on CAN0 and forwards it to CAN1 (and in the reverse direction)
// but modifies Soc1, SoC2 and Capacity values first PID 0x374 Bytes [0], [1], and [6].
// Required libraries

#include "variant.h"
#include <due_can.h>
#include <SPI.h>
//#include <SdFat.h> not used uncomment if using SD shield
//const int chipSelect = 4; //not used scared for SD card on Ethernet shield

//Leave defined if you use Due native port, comment if using programming port
#define Serial SerialUSB

//SdFat sd;//Not used at this time
//SdFile myFile;// scared in for writing to SD card using Ethernet shield

//Global variables
float volts;          //Average cell voltage
byte ah;              //Battery Voltage high byte PID 0x373 byte[4]
byte al;                //Battery Voltage low byte PID 0x373 byte[5]
float amps;             //Battery Curent
float remAh;          //Capacity remaining in battery
double capAh = 90;    //Battery Capacity
float SoCv;              //State of Charge based on battery voltage
float SoCah;            //State of Charge based on Coulomb counting
long presTime;          //Time in milliseconds
float step;           //Current interval
long prevTime = millis(); //time since last current calculation
int flag = 0;         //power up flag
long lowMins;         //Battery current at 0 timer
int j = 0;            //counter for valid data

void setup() {
 
  Serial.begin(115200); //Initialize Serial port on Arduino 115,200 Baud
 
// Initialize CAN0 and CAN1, Set the proper baud rates here
 
  Can0.begin(CAN_BPS_500K);
  Can1.begin(CAN_BPS_500K);

//Begin looking for data on Can0 and Can1

  Can0.watchFor();  //class CANRaw instance watchfor()
  Can1.watchFor();  //class CANRaw instance watchfor()
}
   
  //subroutine to store SoC based on voltage of the battery

void storeSoC() {      

     if (volts < 3.05376) {
        SoCv = 0;return;
      } else if (volts < 3.54912) {
        SoCv = 12.0962747344626 * volts - 36.9391199331124;return;
      } else if (volts < 3.65472) {
        SoCv = 60.5253601258929 * volts - 208.819755477546;return;
      } else if (volts < 3.70464) {
        SoCv = 122.699648332138 * volts - 436.049370070673;return;
      } else if (volts < 3.77376) {
        SoCv = 138.703950288505 * volts - 495.33954727031;return;
      } else if (volts < 3.7776) {
        SoCv = 1629.7714158898 * volts - 6119.20772303549;return;
      } else if (volts < 3.78528) {
        SoCv = 814.885707944995 * volts - 3040.89547270319;return;
      } else if (volts < 3.79488) {
        SoCv = 665.778961384803 * volts - 2476.48468708382;return;
      } else if (volts < 3.81216) {
        SoCv = 362.171425753317 * volts - 1324.33052226661;return;
      } else if (volts < 3.83136) {
        SoCv = 325.95428317799 * volts - 1186.26498002665;return;
      } else if (volts < 3.86592) {
        SoCv = 181.085712876656 * volts - 631.221334516933;return;
      } else if (volts < 3.91584) {
        SoCv = 122.699648332139 * volts - 405.505479872993;return;
      } else if (volts < 3.95232) {
        SoCv = 167.904781928189 * volts - 582.521550213749;return;
      } else if (volts < 3.99264) {
        SoCv = 158.518800329719 * volts - 545.425147422485;return;
      } else if (volts < 4.04064) {
        SoCv = 130.381713271193 * volts - 433.083888149129;return;
      } else if (volts < 4.09248) {
        SoCv = 120.72380858444 * volts - 394.05977215565;return;
      } else if (volts < 4.14816) {
        SoCv = 117.180923519597 * volts - 379.560585885481;return;
      } else if (volts < 4.20384) {
        SoCv = 112.398028682064 * volts - 359.720372836221;return;
      } else {SoCv = 112.782956058589;return;}

     }
 
        //Main program loop
void loop(){
  CAN_FRAME incoming; //capture can frames

  if (Can0.available() > 0) { //if there is an incoming frame read it
  Can0.read(incoming);
      if (incoming.id == 0x373){ //save BMU reported voltage and current in variable volts
        byte vh = incoming.data.byte[4];
        byte vl = incoming.data.byte[5];
        volts = (vh * 256 + vl) / 10.0 / 88.0;//4.1
       
          if (flag == 0){   //check to see if this is a new power up
            if (j == 20){   //wait 20 frames until CMU is providing good data

            storeSoC();     //get battery SoC based on voltage
            remAh = (SoCv/100)*capAh;    //calculate the capacity remaining in the battery based on voltage
            //Serial.print("remAh1 = "); Serial.println(remAh);
            //Serial.print("SoCv = ");Serial.println(SoCv);
            flag = 1;
            }    j=j+1;    
        }
        byte ah = incoming.data.byte[2];  //store current bytes and calculate battery current
        byte al = incoming.data.byte[3];
        amps = (ah * 256 - al -32768)/100.0;
        //Serial.print ("volts = "); Serial.println (volts);
        //Serial.print ("amps = "); Serial.println (amps);
       
    presTime = millis();    //calculate the interval for amp hour calculation
        step = (presTime - prevTime)/1000.0/3600.0;
        prevTime = presTime;        
        remAh = remAh + amps*step;  //update remaining amp hours based on current in or out of battery since startup
          if (amps < 1 && amps > -1) {  //store minutes elapsed since current was 0
             lowMins += step * 60;    
          }   else {lowMins = 0;}
          //Serial.print("remAh2 ="); Serial.println(remAh);      
          Can1.sendFrame(incoming);  //send can frame out to ECU
      }
    if (incoming.id == 0x374){        //capture SoC and Capacity from BMU
       incoming.data.byte[6] = 0xB4;  //modify capacity to new capacity of 90 Ah
       if (lowMins > 3) { //когда ток батареи равен 0 достаточно долго, чтобы напряжение батареи стабилизировалось
       магазинSoC(); //получить SoC на основе напряжения батареи
       ремАч = (SoCv/100)*емкость Ач; //корректируем оставшуюся емкость в зависимости от напряжения
       //Serial.print("reMAH3 = "); Serial.println(рема);
       }
       еще { lowMins <10; //ждём, если текущее значение не было равно 0 достаточно долго
       SoCv = 100,0 * remah/capAh;

        }
       //Serial.println(рема);
       //Serial.print("SoCv = ");Serial.println(SoCv);
       incoming.data.byte[0] = 2 * SoCv + 10; //изменяем данные, поступающие от BMU
       incoming.data.byte[1] = 2 * SoCv + 10;

      Can1.sendFrame(входящий); //отправляем в ECU исправленный SoC на основе 90 ампер-часов
       }
    еще Can1.sendFrame(входящий); // отправляем любой захваченный кадр из BMU, который не был 0x373 или 0x374, в ECU
  }
   if (Can1.available() > 0) { //ищем сообщения от ECU и пересылаем их в BMU
      Can1.read(входящий);
      Can0.sendFrame(входящий);
    }
}[/КОД]
[/QUOTE]
 
For programming, it is better to use an integer data type. I see an unjustified use of the Float type in the code. This moment significantly reduces performance.
I don’t know why you calculate the average cell voltage. It is more optimal to use the total voltage. I see how the voltage conversion code in the SoC can be significantly reduced, but I don’t quite understand the coefficients used for the conversion. Please explain their meaning, and then I can significantly optimize the code. I think this will have a positive effect on performance.
 
Hey Frud,

I didn't write that part David wrote it. I think it is code from OBDZero. I originally used something similar with total voltage but specified a range rather than his method of less than. I believe that using total voltage rather than average cell voltage jumped around more. For instance, a difference in 1 volt at total will give you 0.011 Volt at the cell level. As far as function, the purpose of the SoC calculation using voltage is to correct the coulomb counter which tend to drift over time. This resets the Remaining amphours when the battery current is close to zero for over 10 minutes.
 
Code:
// Arduino Due - Displays all traffic found on either canbus port
// By Thibaut Viard/Wilfredo Molina/Collin Kidder 2013-2014

// Modified by Paul Dove/ David Cecil to make a CAN bridge between ECU and BMU in 2012 Mitsubishi i-MiEV
// Reads all traffic on CAN0 and forwards it to CAN1 (and in the reverse direction)
// but modifies Soc1, SoC2 and Capacity values first PID 0x374 Bytes [0], [1], and [6].
// Required libraries

#include "variant.h"
#include <due_can.h>
#include <SPI.h>
//#include <SdFat.h> not used uncomment if using SD shield
//const int chipSelect = 4; //not used scared for SD card on Ethernet shield

//Leave defined if you use Due native port, comment if using programming port
#define Serial SerialUSB

//SdFat sd;//Not used at this time
//SdFile myFile;// scared in for writing to SD card using Ethernet shield

//Global variables
float volts;          //Average cell voltage
byte ah;              //Battery Voltage high byte PID 0x373 byte[4]
byte al;                //Battery Voltage low byte PID 0x373 byte[5]
float amps;             //Battery Curent
float remAh;          //Capacity remaining in battery
double capAh = 90;    //Battery Capacity
float SoCv;              //State of Charge based on battery voltage
float SoCah;            //State of Charge based on Coulomb counting
long presTime;          //Time in milliseconds
float step;           //Current interval
long prevTime = millis(); //time since last current calculation
int flag = 0;         //power up flag
long lowMins;         //Battery current at 0 timer
int j = 0;            //counter for valid data

void setup() {
 
  Serial.begin(115200); //Initialize Serial port on Arduino 115,200 Baud
 
// Initialize CAN0 and CAN1, Set the proper baud rates here
 
  Can0.begin(CAN_BPS_500K);
  Can1.begin(CAN_BPS_500K);

//Begin looking for data on Can0 and Can1

  Can0.watchFor();  //class CANRaw instance watchfor()
  Can1.watchFor();  //class CANRaw instance watchfor()
}
    
  //subroutine to store SoC based on voltage of the battery

void storeSoC(){
  uint8_t   i       = 0;
  uint16_t  Vbat    = 0;
  uint16_t  Vsoc[]  = {2687,3051,3181,3235,3269,3301,3321,3323,3327,3333,3340,3351,3364,3383,3410,3446,3473,3500,3531,3565,3601};//
  Vbat = uint16_t(volts);
  while((Vbat >= Vsoc[i] && Vbat <= Vsoc[i+1]) != 1){
    i++;
  }
  if(Vbat >= Vsoc[0]){
    SoCv = float(map(Vbat, Vsoc[i], Vsoc[i+1], i*5, (i+1)*5));
  }else{
    SoCv = 0; 
  }
  i = 0;
}
 
        //Main program loop
void loop(){
  CAN_FRAME incoming; //capture can frames

  if (Can0.available() > 0) { //if there is an incoming frame read it
  Can0.read(incoming);
      if (incoming.id == 0x373){ //save BMU reported voltage and current in variable volts
        byte vh = incoming.data.byte[4];
        byte vl = incoming.data.byte[5];
        volts = (vh * 256 + vl);//4.1
        
          if (flag == 0){   //check to see if this is a new power up
            if (j == 20){   //wait 20 frames until CMU is providing good data

            storeSoC();     //get battery SoC based on voltage
            remAh = (SoCv/100)*capAh;    //calculate the capacity remaining in the battery based on voltage
            //Serial.print("remAh1 = "); Serial.println(remAh);
            //Serial.print("SoCv = ");Serial.println(SoCv);
            flag = 1;
            }    j=j+1;     
        }
        byte ah = incoming.data.byte[2];  //store current bytes and calculate battery current
        byte al = incoming.data.byte[3];
        amps = (ah * 256 - al -32768)/100.0;
        //Serial.print ("volts = "); Serial.println (volts);
        //Serial.print ("amps = "); Serial.println (amps);
        
    presTime = millis();    //calculate the interval for amp hour calculation
        step = (presTime - prevTime)/1000.0/3600.0;
        prevTime = presTime;         
        remAh = remAh + amps*step;  //update remaining amp hours based on current in or out of battery since startup
          if (amps < 1 && amps > -1) {  //store minutes elapsed since current was 0
             lowMins += step * 60;     
          }   else {lowMins = 0;}
          //Serial.print("remAh2 ="); Serial.println(remAh);       
          Can1.sendFrame(incoming);  //send can frame out to ECU
      }
    if (incoming.id == 0x374){        //capture SoC and Capacity from BMU
       incoming.data.byte[6] = 0xB4;  //modify capacity to new capacity of 90 Ah
       if (lowMins > 3) {             //when battery current has been 0 for long enough for battery voltage to settle
       storeSoC();              //get SoC based on battery voltage
       remAh = (SoCv/100)*capAh;      //correct remaining capacity bsed on voltage
       //Serial.print("remAh3 = "); Serial.println(remAh);
       }
       else {  lowMins < 10;        //wait if current has not been 0 long enough
       SoCv = 100.0 * remAh / capAh;

        }
       //Serial.println(remAh);
       //Serial.print("SoCv = ");Serial.println(SoCv);
       incoming.data.byte[0] = 2 * SoCv + 10;  //modify data coming from BMU
       incoming.data.byte[1] = 2 * SoCv + 10;

      Can1.sendFrame(incoming); //send corrected SoC based on 90 Amp hours to ECU
       }
    else Can1.sendFrame(incoming); //send any captured frame from BMU that was not 0x373 or 0x374 to ECU
  }
   if (Can1.available() > 0) { //look for messages from ECU and forward to BMU
      Can1.read(incoming);
      Can0.sendFrame(incoming);
    }
}
I changed the code of the storeSoC() function.
could you test it?
 
Frud said:
I changed the code of the storeSoC() function.
could you test it?

Thanks for your input Frud. I'm sure there are improvements that will increase performance.

However please do not change the original code for the SoCv. The original code is a series of straight lines that match the low amp voltage v SoC curve in a scientific article on the NMC cell. It gives as precise as possible conversion of the low amp voltage to SoC. This is important for the best estimate of the remaining Ah in the battery. The reason that this is important that a this value is used to start coulomb counting until the next low amp voltage. Some but not all of the places after the decimal in the slope and intercept are not necessary. It could be that a float value can replace the double precision of the slope and intercept but this requires a careful analysis which we can do later because there is no indication of performance issues yet.

Frud said:
I think you should use the battery's internal resistance value.

I would like to add to Mickey's comment on the temperature effect. The internal resistance decreases with increasing current. It also changes as the SoC changes. And if that isn't enough the battery is a capacitor as well as a resistor. This means that there is a time delay on the observed resistance.


Frud said:
I don’t know why you calculate the average cell voltage.

The cell voltages are used because there are are some cars with 80 cells and other cars with 88 cells. This makes it easy to change the code depending on which type of car one has. Again using a float value doesn't create performance issues yet.

Cheers
David
 
Thanks Frud and David.

I loaded Fruds code and took a drive. Here is the data.
 

Attachments

  • PID_2024_03_30_08_46_31.txt
    3.3 MB · Views: 0
  • Calc_2024_03_30_08_46_31[1].txt
    3 MB · Views: 0
  • OBD_2024_03_30_08_46_31[1].txt
    4 MB · Views: 2
Frud said:
For programming, it is better to use an integer data type.

This has got me to thinking about how to convert as much as possible in the code to integer arithmetic. It's a good idea but right now it is best to limit the changes until we have something running dependably.

However there is one thing I'm wondering about. In PID 0x373 byte 0 is the the highest cell voltage and byte 1 is the the lowest cell voltage. If we use these to compute the SoC based on voltage we can avoid the problem of 80 cell and 88 cell cars. We can compute the average of the highest and lowest cells and this will be very close to the average of all the cells. On the other hand the lowest voltage may in fact be the right value to use. As the cells are in series then the maximum Ah that can pass through the battery is equal to the Ah in the weakest cell and this is the cell with the lowest voltage. Any thoughts anybody?
 
This has got me to thinking about how to convert as much as possible in the code to integer arithmetic. It's a good idea but right now it is best to limit the changes until we have something running dependably.

However there is one thing I'm wondering about. In PID 0x373 byte 0 is the the highest cell voltage and byte 1 is the the lowest cell voltage. If we use these to compute the SoC based on voltage we can avoid the problem of 80 cell and 88 cell cars. We can compute the average of the highest and lowest cells and this will be very close to the average of all the cells. On the other hand the lowest voltage may in fact be the right value to use. As the cells are in series then the maximum Ah that can pass through the battery is equal to the Ah in the weakest cell and this is the cell with the lowest voltage. Any thoughts anybody?
I'm using a 2015 Zoe NMC pack in my solar storage and specifically got a 3rd party BMS capable of actively balancing the cells. I disabled the function to see how much they drift over time; could have saved that money, the cells are still within the same 3mV after a year of daily use.

Similar with my wife’s EV, it’s NMC cells are still perfectly in sync after nearly a year (I rarely charge to 100% to start it’s balancing routine).

I therefore recon the native balancing function (that was struggling with LEV50x) will be well capable of keeping the NMC cells in line for years to come??

As regards to 88 vs 80 cells, it’s just one parameter that can easily be changed, so unless there are other advantages using cell instead of pack voltage I’d leave it as it is…
 
Back
Top