CMU CAN ‘spoofing’

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.

MickeyS70

Moderator
Staff member
Supporting Member
Joined
Feb 23, 2022
Messages
1,018
Location
West of Ireland
I had a long running issue in my 2012 Imiev where cell 88 voltage started to creep up compaired to the rest of the pack. This offset wasn’t changing under load therefore I deducted that the problem is CMU12 and not the cell itself.

I have replaced the LTC chip on the very same board before so I knew the work involved but have no longer access to a lift and therefore put off the repair. The issue has affected winter range significantly as cell 88 stops the charging process way ahead of the other cells.

Encouraged by @pievs post using a CAN ‘man in the middle approach’ to correct a faulty temperature reading, I used the same approach to correct the voltage accordingly and got my range back.

https://myimiev.com/threads/main-traction-battery-upgrade-i-miev.5458/

In theory this method could also be used to ‘fool’ the BMU to work with NMC cells however correcting SOC on the BMU/EVECU side of the CAN bus seems to be a better option.

It’s also worth mentioning that later I-MIEV versions (2015 onwards??) have the BMU moved from under the bench into the main battery pack..
 
Great job to fix the issue with a CAN bridge MIM from CMU to BMU.

Do you think it is an issue with the LTC chip again, or maybe some component or solder joint in the voltage measurement circuit?

i wonder if anyone has opened a late model pack to inspect the cells and BMU? Why did they move the BMU--to keep us from having access?
 
Do you think it is an issue with the LTC chip again, or maybe some component or solder joint in the voltage measurement circuit?
Probably a solder joint as replacing a SMD chip was a first for me...

i wonder if anyone has opened a late model pack to inspect the cells and BMU? Why did they move the BMU--to keep us from having access?

See some photos in links below

https://www.evmonitor.info/2018/07/mitsubishi-miev-pack-details-cmu.html
https://www.evmonitor.info/2018/07/mitsubishi-miev-battery-pack.html

The voltage formula described in the link didn't work for my 2012 IMiev, I used cell_Voltage=(MSB * 256 + LSB) / 200 + 2.1V (thanks to @coulomb for figuring this out)

I gather most EV's actually have the BMU now inside the HV pack, maybe Mitsu just followed the trend..
 
Last edited:
I'd seen Nikolay's work before but didn't realize it was a 2017, good find.

The CMU board appears to be the same; the purple wrapper on the cells appears the same as LEV50(N); also same BMU.

i suppose it would be easier to keep the BMU in the pack near to the current sensor, isolation sensor, voltage and temperature sensors, and Pack CAN buss, and then just use vehicle CAN buss to talk to the EV-ECU, rather than run them all to outside the Pack. Keep it simple.
 
I would to spoof the P1A52 error for some test on a 2012 Ion . I alredy ordered arduino2 and dual can shield, how to make firmware for the spoofing? i see the example but need some more help... i know C language but i had no experience on CAN messages....
 
ok, I seem to have understood how to use libraries programmatically.
But I would like to understand how to filter the CAN codes to eliminate the error: if the error code is sent by the BMS then I should filter and eliminate that, if instead the error is generated in the car's control unit based on the cell voltages, then I should probably set a minimum voltage value that doesn't trigger the error. Or do I have to continuously send a reset command?
 
I would to spoof the P1A52 error for some test on a 2012 Ion . I alredy ordered arduino2 and dual can shield, how to make firmware for the spoofing? i see the example but need some more help... i know C language but i had no experience on CAN messages....
I already did this because I had a failed CMU board. Not sure if I posted the code. I will look and if I can’t find it on this forum I will post it here.
 
Thankyou @piev, i looked many and many post but haven't find it. But I may have missed it...
another question: the "man in te middle" is inserted between master BMS and the general canbus, right?
Ok, I had to read a little to refresh my mind. So, P1A52 is a cell voltage error from CMU01. There are two ways to spoof this 1) put a bridge between the BMU and CMU to change this data 2) I believe you can also put a bridge between the ECU and the BMU. In the case of the 2) I think the code listed on the thread can be modified to fix this error. I did it with a failed temperature and a low voltage with my BMU to ECU bridge. I am a little rusty but I used Due Can to log data to find out which bits to toggle then I capture the data changed it and sent it on to the ECU. I believe I also had to mask some Error bits that get set by the BMU. In the case of the CMU to BUM bridge I believe all you do is change the data, I don't remember any error bits from the CMU. Here is the code I used in the BMU to ECU bridge. PID's 0x6E1 through 0x6E4 is the CMU data (voltage and temperature) coming from the BMU. I change any values that are out of range and then retransmit to the CMU. I also had to change the error PID 0x5A1 to clear the error.


Code:
// 
void loop() {  //Main program 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) {  // the BMU sends 0x373 100 times a second

      ah = incoming.data.byte[2];  //store current bytes
      al = incoming.data.byte[3];
      vh = incoming.data.byte[4];  //store voltage bytes
      vl = incoming.data.byte[5];

      if (incoming.data.byte[0] > 65 && incoming.data.byte[0] < 210) { b0 = incoming.data.byte[0]; }  //update b0 to good data between 2.75 and 4.2
      if (incoming.data.byte[1] > 65 && incoming.data.byte[1] < 210) { b1 = incoming.data.byte[1]; }

      incoming.data.byte[0] = b0;
      incoming.data.byte[1] = b1;

      Can1.sendFrame(incoming);  //send the last good cell voltage

      // end 0x373

    } else if (incoming.id == 0x374) {  // the BMU sends 0x374 10 times a second, one 0x374 for every 10 0x373

      volts = (vh * 256 + vl) / 880.0;      //the average cell voltage from 3.05v to 4.1v. 88 is the number of cells
                                            //in a car with omly 80 cells change 88 to 80
      if (flag == 0) {                      //check to see if this is a new power up
        if (j == 20) {                      //wait 20 0x374 frames until CMU is providing good data
          storeSoC2();                      //get battery SoC based on voltage
          remAh1 = (SoC2 / 100.0) * capAh;  //calculate the start remaining Ah in the battery based on voltage
          remAh2 = (SoC2 / 100.0) * capAh;  //calculate the start remaining Ah in the battery based on voltage
          flag = 1;
        }
        j = j + 1;
      }

      if (ah > 48 && ah < 206) {                 //update amps to good data between -200 and 200
        amps = (ah * 256 + al - 32700) / 100.0;  //Use 32700 not 32768 32700 is the calibrated value
      }
      float Ah = amps / 36000.0;  //The amphours to or from the battery since the last 0x374

      //presTime = millis();  //calculate the interval between 0x373 frames for amp hour calculation
      //step = (presTime - prevTime) / 1000.0 / 3600.0;
      //prevTime = presTime;


      if (amps > -1.0 && amps < 1.0) {  //store 10ths of a second elapsed while the amps are > -1 and < 1
        deciSec += 1;
      } else {
        deciSec = 0;
      }

      remAh1 = remAh1 + Ah;           //update remaining amp hours based on current in or out of battery
      SoC1 = 100.0 * remAh1 / capAh;  //compute SoC1 based on the remaining Ah

      if (deciSec > 6000) {               //when battery current has been low for long enough for battery voltage to settle = 10 min
        storeSoC2();                      //gets SoC2 based on battery voltage
        remAh2 = (SoC2 / 100.0) * capAh;  //correct remaining capacity based on voltage
      } else {                            //current has not been low, long enough to use the SoC based on voltage
        remAh2 = remAh2 + Ah;             //update remaining amp hours based on current in or out of battery
        SoC2 = 100.0 * remAh2 / capAh;    //compute SoC2 based on the remaining Ah
      }

      Serial.print("remAh1 = ");Serial.println(remAh1);
      Serial.print("remAh2 = ");Serial.println(remAh2);
      Serial.print("volts = ");Serial.println(volts);

      incoming.data.byte[0] = 2 * SoC1 + 10;  //modify the SoC coming from BMU
      incoming.data.byte[1] = 2 * SoC2 + 10;
      incoming.data.byte[6] = 2 * capAh;      //modify the capacity coming from BMU

      Serial.print("SoC1 = ");Serial.println(SoC1);
      Serial.print("SoC2 = ");Serial.println(SoC2);

      Can1.sendFrame(incoming);  //send the corrected SoCs and the correct 100% capacity to the ECU

      // end 0x374

    } else if (incoming.id == 0x6E1) {  //The BMU sends a 0x6E1 every 0.04 seconds or 25 per second. In cars with only 80 cells remember to block modules 6 og 12.

      if ((incoming.data.byte[4] == 0 && incoming.data.byte[5] > 130) || (incoming.data.byte[4] == 1 && incoming.data.byte[5] < 164)) {  // b4 to b7 are updated with good data between 2.75 and 4.2
        b41 = incoming.data.byte[4];
        b51 = incoming.data.byte[5];
        b61 = incoming.data.byte[6];
        b71 = incoming.data.byte[7];
      }

      incoming.data.byte[4] = b41;
      incoming.data.byte[5] = b51;
      incoming.data.byte[6] = b61;
      incoming.data.byte[7] = b71;

      Can1.sendFrame(incoming);  //send the corrected voltages to the ECU

      // end 0x6E1
    } else if (incoming.id == 0x6E2) {  //The BMU sends a 0x6E2 every 0.04 seconds or 25 per second In cars with only 80 cells remember to block modules 6 og 12.

      if ((incoming.data.byte[4] == 0 && incoming.data.byte[5] > 130) || (incoming.data.byte[4] == 1 && incoming.data.byte[5] < 164)) {  // b4 to b7 are updated with good data between 2.75 and 4.2
        b42 = incoming.data.byte[4];
        b52 = incoming.data.byte[5];
        b62 = incoming.data.byte[6];
        b72 = incoming.data.byte[7];
      }

      incoming.data.byte[4] = b42;
      incoming.data.byte[5] = b52;
      incoming.data.byte[6] = b62;
      incoming.data.byte[7] = b72;

      Can1.sendFrame(incoming);  //send the corrected voltages to the ECU

      // end 0x6E3
    } else if (incoming.id == 0x6E3) {  //The BMU sends a 0x6E3 every 0.04 seconds or 25 per second

      if (incoming.data.byte[0] != 6 && incoming.data.byte[0] != 12) {                                                                     // modules 6 and 12 only contain 4 cells not 8
        if ((incoming.data.byte[4] == 0 && incoming.data.byte[5] > 130) || (incoming.data.byte[4] == 1 && incoming.data.byte[5] < 164)) {  // b4 to b7 are updated with good data between 2.75 and 4.2
          b43 = incoming.data.byte[4];
          b53 = incoming.data.byte[5];
          b63 = incoming.data.byte[6];
          b73 = incoming.data.byte[7];
        }
        incoming.data.byte[4] = b43;
        incoming.data.byte[5] = b53;
        incoming.data.byte[6] = b63;
        incoming.data.byte[7] = b73;
      }

      Can1.sendFrame(incoming);  //send the corrected voltages to the ECU

      // end 0x6E4
    } else if (incoming.id == 0x6E4) {  //The BMU sends a 0x6E4 every 0.04 seconds or 25 per second

      if (incoming.data.byte[0] != 6 && incoming.data.byte[0] != 12) {                                                                     // modules 6 and 12 only contain 4 cells not 8
        if ((incoming.data.byte[4] == 0 && incoming.data.byte[5] > 130) || (incoming.data.byte[4] == 1 && incoming.data.byte[5] < 164)) {  // b4 to b7 are updated with good data between 2.75v and 4.2v
          b44 = incoming.data.byte[4];
          b54 = incoming.data.byte[5];
          b64 = incoming.data.byte[6];
          b74 = incoming.data.byte[7];
        }
        incoming.data.byte[4] = b44;
        incoming.data.byte[5] = b54;
        incoming.data.byte[6] = b64;
        incoming.data.byte[7] = b74;
      }

      Can1.sendFrame(incoming);  //send the corrected voltages to the ECU

      // end 0x6E4
    } else if (incoming.id == 0x5A1) {  //This hides an error bit from the CMUs. It helps when the error is due to a bad CMU. Use with caution because it can hide real problems with the cells

      if (incoming.data.byte[1] == 48) { incoming.data.byte[1] = 16; }

      Can1.sendFrame(incoming);  //send the corrected error bit to the ECU

      // end 0x5A1
    } else Can1.sendFrame(incoming);  //send any captured frames from BMU other than 0x373, 0x374 and 6E1-6E4 to ECU.

  }  // end incoming frame from the BMU

  if (Can1.available() > 0) {  //look for frames from ECU and forward to BMU
    Can1.read(incoming);
    Can0.sendFrame(incoming);
  }

}  //end main program loop
 
It's a lot easier to put the bridge on the internal BAT CAN bus (between the BMU & CMUs) and change the wrong information before it reaches the BMU.

However, if you're already using CAN spoofing for NMC upgrades, incorporating a CMU correction algorithm into the code is the way to go (no need for a second bridge).

Some more info below:
https://myimiev.com/threads/cell-cross-referencing-pid-to-physical-cells.5645/#post-50067
I did that too but only until I got the BMU to ECU bridge figured out.
Code:
//Reads all traffic on CAN0 and forwards it to CAN1 (and in the reverse direction) but modifies some frames first.
// Required libraries
#include "variant.h"
#include <due_can.h>

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

void setup()
{

  Serial.begin(115200);
 
  // Initialize CAN0 and CAN1, Set the proper baud rates here
  Can0.begin(CAN_BPS_500K);
  Can1.begin(CAN_BPS_500K);
 
  Can0.watchFor();  //class CANRaw instance watchfor()
}

/*void sendData()
{
  CAN_FRAME outgoing;
  outgoing.id = 0x400;
  outgoing.extended = false;
  outgoing.priority = 4; //0-15 lower is higher priority
 
  outgoing.data.s0 = 0xFEED;
    outgoing.data.byte[2] = 0xDD;
  outgoing.data.byte[3] = 0x55;
  outgoing.data.high = 0xDEADBEEF;
  Can0.sendFrame(outgoing);
}*/



/*void printFrame(CAN_FRAME &frame) {
   Serial.print("ID: 0x");
   Serial.print(frame.id, HEX);
   Serial.print(" Len: ");
   Serial.print(frame.length);
   Serial.print(" Data: 0x");
   for (int count = 0; count < frame.length; count++) {
       Serial.print(frame.data.bytes[count], HEX);
       Serial.print(" ");
   }
   Serial.print("\r\n");
}*/
void loop(){
  CAN_FRAME incoming;
  //static unsigned long lastTime = 0;

  if (Can0.available() > 0) {
  Can0.read(incoming);
  //printFrame(incoming);
    if (incoming.id == 0x653){
      if (incoming.data.byte[0] == 0x05){
         incoming.data.byte[2] = incoming.data.byte[1];
    Can1.sendFrame(incoming);}
  }
    /*if (incoming.id == 0x373){
       Can1.sendFrame(incoming);  }
    if (incoming.id == 0x375){
          Can1.sendFrame(incoming);}
      if (incoming.id == 0x5A1){
       Can1.sendFrame(incoming);  }
    if (incoming.id == 0x6E1){
          Can1.sendFrame(incoming);}
      if (incoming.id == 0x6E2){
       Can1.sendFrame(incoming);  }
    if (incoming.id == 0x6E3){
          Can1.sendFrame(incoming);}
       if (incoming.id == 0x6E4){
       Can1.sendFrame(incoming);  }   */                   
   //Can1.sendFrame(incoming);
   //printFrame(incoming);
   }

  if (Can1.available() > 0) {
  Can1.read(incoming);
 // printFrame(incoming);
  Can0.sendFrame(incoming);
  }

 /* if ((millis() - lastTime) > 1000)
  {
     lastTime = millis();
     sendData();   
  }*/
}
 
Back
Top