Saturday, 2 October 2021

Arduino CAN spoofing i-MiEV ABS signals

 Usually in the i-MiEV the ABS module will detect the wheel speed from wheel speed sensors to calculate and change the accelerator pedal map depending on speed. ie the higher the speed the more regen is applied when taking your foot off the accelerator.

However, now that the i-MiEVs guts are in the BMW the ABS module doesn't have the sensors to detect speed hooked up. 

There are two ways to solve this, either splice into the BMWs wheels speed sensors and hope that the signal from them is compatible with the i-MiEV system, or...

Use an Arduino with a CAN-BUS Module to simulate the CAN signals usually sent from the ABS module to spoof the ECU into thinking it has a properly functioning ABS system.

 

To start this process me and a friend started by taking a log of an unmodified i-MiEVs CAN stream using Savvy CAN, after this going through the CAN PiDs one by one to find possible data streams and record them. 

To do this under the RE Tools menu we used Frame Data Analysis to find these.

 

The easiest way to find the data is by looking at the Bytes Graph (Bottom Right) which shows line zig-zagging up and down. 

If the data here isnt encrypted or has a large scale it will usually be in two bytes, usually 0-1, 2-3, 4-5, 6-7. 

As shown below the graph these bytes are colour coded on the graph, 0 = Blue, 1 = Green etc. 

 

When zoomed into the graph you can see the Red byte 3 drop from a value of  255 to 0  at the same time the Black byte 2 increases by 1, the same goes for bytes 4 and 5 slightly after that. 

This indicates that these two pairs of bytes must have a large scale which is multiplied by the High byte (2 with 3 and 4 with 5 in this case)

The next step is to make a graph using the values found to collect and gather all the usable data streams, this is listed under the RE Tools menu (Graph Data)

 

With the blank graph showing right click it and select add a new graph. 

I have already filled this graph setting out with the graph found just before, 0x200 as the ID, and selecting bytes 2 and 3. each byte is made up of 8 bits, so when selecting the first bit of byte 2 we type a data length of 16 which then selects the whole of byte 2 and 3 seen in green in the screenshot.

 

When this is added play around with the Bias which moves that specific graph up and down the value axis, as well as Scale which allows you to.. well scale the graph ie enter 10 and the graph will multiply all values in the graph by 10. (name it once you've figured out what its for)

Going through all the IDs one by one eventually gave us this.

 The humps right at the top are both RPM, with the similarly shaped humps at the bottom being wheel speed. The darker looking hump second from the bottom is actually all 4 wheel speeds with the same Bias and Scale as they all read similar values.

Using these graphs with the Scale and Bias function I found the RPM to Wheel speed equation which was "(RPM / 3) + 49152"

 Now the wheel speed sensors can be "locked" to the speed of the motors RPM allowing for the i-MiEV to think it has a functioning ABS system allowing for full regen off the accelerator and full power when flooring. 

I have not tested this with the brake pedal sensor so I am only getting accelerator lift off regen, but when combined with the i-MiEVs "B" driving mode there is heaps of regen avaliable.

The Arduino code I used to do this: (This has been cut from the full code I use to show just the ABS spoofing part)

#include <defaults.h>
#include <global.h>
#include <mcp_can.h>

    int cRPM;  
    int wRPM;
    int dRPM;

long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];
char msgString[128];                        // Array to store serial string

#define CAN0_INT 2                              // Set INT to pin 2
MCP_CAN CAN0(10);                               // Set CS to pin 10


void setup() {

CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_16MHZ); // Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.

 
  CAN0.setMode(MCP_NORMAL);                     // Set operation mode to normal so the MCP2515 sends acks to received data.

  pinMode(CAN0_INT, INPUT);                            // Configuring pin for /INT input

}

void loop() {

CAN0.readMsgBuf(&rxId, &len, rxBuf);             // Read data: len = data length, buf = data byte(s)
 
         //Serial.println(rxId);
         if(rxId == 0x288){     //Reads motor rpm from CAN
       
           cRPM = rxBuf[2] * 256; //Adjusts RPM to actual decimal numbers
           cRPM += rxBuf[3];
           cRPM -= 10000;

  
           wRPM = cRPM / 3; //Adjusts RPM to wheel speed (Spoofs ABS signals)
           wRPM += 49152;

           //seperate speed signal (For dash?) with different ratio
           dRPM = cRPM * 2;

           byte high = highByte(wRPM); //converts decimal number into two HEX bytes
           byte low = lowByte(wRPM);
           byte high0 = highByte(dRPM); //converts decimal number into two HEX bytes
           byte low0 = lowByte(dRPM);
          
           byte data200[8] = {0x00, 0x03, high, low, high, low, 0xFF, 0xFF};  //loads data into arrays
           byte data208[8] = {0x00, 0x20, 0x60, 0x01, high, low, high, low};
           byte data215[8] = {high0, low0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
          
           CAN0.sendMsgBuf(0x200, 0, 8, data200); //sends out CAN messages
           CAN0.sendMsgBuf(0x208, 0, 8, data208);
           CAN0.sendMsgBuf(0x215, 0, 8, data215);

}