Controlling BMW E90 instrument cluster

Hello!

I want to put a BMW E90 instrument cluster into a car that only has digital display. So I bought one off ebay thinking it would be a fairly straightforward job. To my mistake, most of people who did this years ago have posted links that are dead now and they themselves are not available any longer.

I found the following resources

Several YT videos

I bought a Seeed Can Bus Shield V2.0 and connected it to my Arduino Uno:

and connected CAN-HI pin to supposed CAN-HI pin on the cluster and CAN-LO pin to CAN-LO of the cluster. I also connected 12V adapter to the cluster (+ to +, - to -)

the cluster does power on by wiggling the needles a bit and if I press the button on it, display turns on for about 15 seconds

but when I try to use the suggested turn-on CAN message, nothing happens. I tried both 500KBPS and 100KBPS, but still nothing. This is the code I used:

#include <mcp_can.h>
#include <SPI.h>

/*SAMD core*/
#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
#define SERIAL SerialUSB
#else
#define SERIAL Serial
#endif

const int SPI_CS_PIN = 9;

MCP_CAN CAN(SPI_CS_PIN); // Set CS pin

void setup()
{
    SERIAL.begin(115200);

    while (CAN_OK != CAN.begin(CAN_100KBPS))
    {
        SERIAL.println("CAN BUS Shield init fail");
        SERIAL.println(" Init CAN BUS Shield again");
        delay(100);
    }
    SERIAL.println("CAN BUS Shield init ok!");
}

byte data[8] = {0, 0, 0, 0, 0, 0, 0, 0};

void loop()
{
    data[0] = 0x45; //0x45;  /Key Status
    data[1] = 0x42; // 0x40;  //Transponder Detected
    data[2] = 0x69; // 0x21;  //Terminal Status
    data[3] = 0x8F; // Steering lock?
    data[4] = 0xE2; //Counter and Checksum

    CAN.sendMsgBuf(0x130, 0, 5, data);
    delay(100); // send data per 100ms
}

It is a direct combination of Seeed’s example and start code from that blog, where I put in the wake-up message from that guy’s blog, listed above. I checked the output of CAN shield with an oscilloscope and can confirm that it does look like something is being sent. Also, Arduino Uno sends me this message via serial

Enter setting mode success 
set rate success!!
Enter Normal Mode Success!!
CAN BUS Shield init ok!

I also tried the read example to see if the cluster sends any messages but I got nothing back. Im a bit puzzled. Does anybody have any suggestion please? Thank you!

1 Like

In the time elapsed I have made some progress. Initially I didnt connect the ground wire to arduino, thats why it didnt work. Here is correct pinout:

1     o o     10
2     o o     11
3     o o     12
4     o o     13
5     o o     14
6 C_H o o     15
7 C_L o o     16
8     o o     17
9 VCC o o GND 18

(Dont forget to connect GND to Arduino!)

I managed to turn the cluster on, turn on the backlights and move the rpm needle. But I still cant do it consistently and everything in the same program. I read that cluster is picky about how messages and which messages come and at what intervals. I try reordering the message sends a bit, putting in delays and sometimes I manage to move the rpm needle, sometimes the lights turn on, but cant make it consistently useful. There is information that wake signal should be sent every 100ms but I cannot make it work even if I send it every 100ms. It even happened that I put into program message for turning rpm needle, then I reupload without this message and the rpm needle moves. Maybe there is something about how Seeed CAN Shield sends messages (buffers etc).

I tested turning blinkers on by sending the command once in setup, but the cluster was acting as if it was spammed by that same command and showed CAN bus errors. Im starting to suspect there is something up with my CAN bus shield

If anybody could shed any light onto this I would be very grateful!

Here is the code:

#include <SPI.h>
#include "mcp2515_can.h"

#define lo8(x) (uint8_t)((x)&0xff)
#define hi8(x) (uint8_t)(((x) >> 8) & 0xff)

const int SPI_CS_PIN = 9;
const int CAN_INT_PIN = 2;
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin

void sendIgnitionKeyOn()
{
    uint16_t canId = 0x130;
    uint8_t len = 5;

    uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0};

    buf[0] = 0x45; // Key Status / T15 ON message
    buf[1] = 0x40; // Transponder Detected
    buf[2] = 0x21; // Terminal Status
    buf[3] = 0x8F; // Steering lock?
    buf[4] = 0xFE; // Counter and Checksum

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void sendRPM(uint16_t rpm)
{
    uint16_t tempRpm = rpm * 4;

    const uint16_t canId = 0x0AA;
    const uint8_t len = 8;

    uint8_t buf[8] = {0xFE, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0xFE, 0x99};

    buf[4] = lo8(tempRpm);
    buf[5] = hi8(tempRpm);

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void sendLightsOn()
{
    const uint16_t canId = 0x21A;
    const uint8_t len = 3;

    uint8_t buf[8] = {0b00000101, 0b00010000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void leftBlinkerOn()
{
    const uint16_t canId = 0x1F6;
    const uint8_t len = 2;

    uint8_t buf[8] = {0x91, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void rightBlinkerOn()
{
    const uint16_t canId = 0x1F6;
    const uint8_t len = 2;

    uint8_t buf[8] = {0xA1, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void sendFuelLevel(uint16_t litres)
{
    uint16_t canId = 0x349;
    const uint8_t len = 5;

    uint8_t buf[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    uint16_t sensor1 = litres * 160;
    buf[0] = lo8(sensor1);
    buf[1] = hi8(sensor1);

    uint16_t sensor2 = sensor1;
    buf[2] = lo8(sensor2);
    buf[3] = hi8(sensor2);

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void setup()
{
    while (CAN_OK != CAN.begin(CAN_100KBPS))
    {
        delay(100);
    }

    sendIgnitionKeyOn();
    sendFuelLevel(24);
    sendLightsOn();
    sendRPM(800);
}

List of can commands: CarPC Install

You should be sending the can bus messages in a loop not in the setup. They are required to constantly receive the messages. I believe ignition on should be received about every 100ms and turn signals should be about 800ms.

xadonxander:
You should be sending the can bus messages in a loop not in the setup. They are required to constantly receive the messages. I believe ignition on should be received about every 100ms and turn signals should be about 800ms.

Hello sir!

I’ve tried putting messages into loop but it doesn’t seem to work for me. Maybe the 0x130 ignition signal should have its counter increased, as it says here: CAN BUS Codes

Byte 4

xx = 1.4 Second counter

I tried doing this (incrementing those bits every 1400ms) but the cluster then keeps resetting

For blinkers I did put it in loop:

#include <SPI.h>
#include "mcp2515_can.h"

#define lo8(x) (uint8_t)((x)&0xff)
#define hi8(x) (uint8_t)(((x) >> 8) & 0xff)

const int SPI_CS_PIN = 9;
const int CAN_INT_PIN = 2;
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin

void sendIgnitionKeyOn()
{
    uint16_t canId = 0x130;
    uint8_t len = 5;

    uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0};

    buf[0] = 0x45; // Key Status / T15 ON message
    buf[1] = 0x40; // Transponder Detected
    buf[2] = 0x21; // Terminal Status
    buf[3] = 0x8F; // Steering lock?
    buf[4] = 0xFE; // Counter and Checksum

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void sendRPM(uint16_t rpm)
{
    uint16_t tempRpm = rpm * 4;

    const uint16_t canId = 0x0AA;
    const uint8_t len = 8;

    uint8_t buf[8] = {0xFE, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0xFE, 0x99};

    buf[4] = lo8(tempRpm);
    buf[5] = hi8(tempRpm);

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void sendLightsOn()
{
    const uint16_t canId = 0x21A;
    const uint8_t len = 3;

    uint8_t buf[8] = {0b00000101, 0b00010000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void leftBlinkerOnInitial()
{
    const uint16_t canId = 0x1F6;
    const uint8_t len = 2;

    uint8_t buf[8] = {0x91, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void rightBlinkerOnInitial()
{
    const uint16_t canId = 0x1F6;
    const uint8_t len = 2;

    uint8_t buf[8] = {0xA1, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void keepLeftBlinkerOn()
{
    const uint16_t canId = 0x1F6;
    const uint8_t len = 2;

    uint8_t buf[8] = {0x91, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void keepRightBlinkerOn()
{
    const uint16_t canId = 0x1F6;
    const uint8_t len = 2;

    uint8_t buf[8] = {0xA1, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    CAN.sendMsgBuf(canId, 0, len, buf);
}

void sendFuelLevel(uint16_t litres)
{
    uint16_t canId = 0x349;
    const uint8_t len = 5;

    uint8_t buf[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    uint16_t sensor1 = litres * 160;
    buf[0] = lo8(sensor1);
    buf[1] = hi8(sensor1);

    uint16_t sensor2 = sensor1;
    buf[2] = lo8(sensor2);
    buf[3] = hi8(sensor2);

    CAN.sendMsgBuf(canId, 0, len, buf);
}

uint32_t timestamp100ms = 0;
uint32_t timestamp800ms = 0;
bool firstBlinkerTurnOn = true;

void setup()
{
    while (CAN_OK != CAN.begin(CAN_100KBPS))
    {
        delay(100);
    }
}

void loop()
{
    if (millis() - timestamp100ms > 99)
    {
        sendIgnitionKeyOn();

        timestamp100ms = millis();
    }

    if (millis() - timestamp800ms > 799)
    {
        // Turn blinker on after 30 seconds to avoid kombi
        // missing the initial message when its turning on
        if (millis() > 30000)
        {
            if (firstBlinkerTurnOn == true)
            {
                leftBlinkerOnInitial();

                firstBlinkerTurnOn = false;
            }

            keepLeftBlinkerOn();
        }

        timestamp800ms = millis();
    }
}

and the kombi does turn on after a few seconds, warning lights flash, but then turn off after about 2 seconds. The screen stays on but nothing else happens (if I understood correctly on how to use them: CAN BUS Codes)

1F6 2 91 F1 145 241
Left turn signal on (sent following initial 242)
1F6 2 91 F2 145 242
Left turn signal on (sent first, followed by 241)

Im really at a loss :frowning: I was so happy when they turned on but then I hit a brick wall

Okay I have found this Arduino forum thread https://forum.arduino.cc/index.php?topic=569052.0 and basically it sounds exactly whats happening to me. I didn’t try with the scope, but symptoms sound similar. So I tried this guy’s solution and switched to this lib he is talking about. I googled that SPI bandwidth of mcp2515 is 1Mbps, so I adjusted accordingly. And now I’m at the same spot as this guy: E90 Can bus project (E60, E65, E87....) - Page 2 The kombi is going through reset sequence all the time like its missing something

How it looks like: http://shrani.si/f/4/GQ/1TTxeB2f/1/vid20201213212805.gif

Code

#include <CAN.h>

#define lo8(x) (uint8_t)((x)&0xff)
#define hi8(x) (uint8_t)(((x) >> 8) & 0xff)

const uint8_t CS_PIN = 9;
const uint8_t IRQ_PIN = 2;

void sendIgnitionKeyOn()
{
  CAN.beginPacket(0x130);

  CAN.write(0x45);
  CAN.write(0x40);
  CAN.write(0x21);
  CAN.write(0x8F);
  CAN.write(0xFE);

  CAN.endPacket();
}

void sendRPM(uint16_t rpm)
{
  uint16_t tempRpm = rpm * 4;

  CAN.beginPacket(0x0AA);

  CAN.write(0xFE);
  CAN.write(0xFE);
  CAN.write(0xFF);
  CAN.write(0x00);
  CAN.write(lo8(tempRpm));
  CAN.write(hi8(tempRpm));
  CAN.write(0xFE);
  CAN.write(0x99);

  CAN.endPacket();
}

void sendIgnitionStatus()
{
  CAN.beginPacket(0x26E);

  CAN.write(0x40);
  CAN.write(0x40);
  CAN.write(0x7F);
  CAN.write(0x50);
  CAN.write(0xFF);
  CAN.write(0xFF);
  CAN.write(0xFF);
  CAN.write(0xFF);

  CAN.endPacket();
}

void sendFuelLevel(uint16_t litres)
{
  uint16_t sensor = litres * 160;

  CAN.beginPacket(0x349);

  CAN.write(lo8(sensor));
  CAN.write(hi8(sensor));
  CAN.write(lo8(sensor));
  CAN.write(hi8(sensor));
  CAN.write(0x00);

  CAN.endPacket();
}

void sendAirbagSeatbeltCounter()
{
  static uint8_t count = 0x00;

  CAN.beginPacket(0x0D7);

  CAN.write(count);
  CAN.write(0xFF);

  CAN.endPacket();

  count++;
}

void sendABSBrakeCounter2()
{
  CAN.beginPacket(0x19E);

  CAN.write(0x00);
  CAN.write(0xE0);
  CAN.write(0xB3);
  CAN.write(0xFC);
  CAN.write(0xF0);
  CAN.write(0x43);
  CAN.write(0x00);
  CAN.write(0x65);

  CAN.endPacket();
}

void sendABSBrakeCounter1()
{
  static uint8_t count = 0xF0;

  CAN.beginPacket(0x0C0);

  CAN.write(count);
  CAN.write(0xFF);

  CAN.endPacket();

  count++;
  if (count == 0x00)
  {
    count = 0xF0;
  }
}

void seatbeltLight(bool state)
{
  uint8_t thirdBit;

  if (state == false)
  {
    CAN.beginPacket(0x581);
    thirdBit = 0x28;
  }
  else
  {
    CAN.beginPacket(0x394);
    thirdBit = 0x029;
  }

  CAN.write(0x40);
  CAN.write(0x4D);
  CAN.write(0x00);
  CAN.write(thirdBit);
  CAN.write(0xFF);
  CAN.write(0xFF);
  CAN.write(0xFF);
  CAN.write(0xFF);

  CAN.endPacket();
}

void sendSpeed(uint16_t speed)
{
  static uint32_t lastTimeSent = 0;
  static uint16_t lastReading = 0;
  static uint16_t count = 0xF000;

  uint16_t speedValToSend = ((millis() - lastTimeSent) / 50) * speed / 2;
  speedValToSend += lastReading;

  lastReading = speedValToSend;
  lastTimeSent = millis();

  CAN.beginPacket(0x1A6);

  CAN.write(lo8(speedValToSend));
  CAN.write(hi8(speedValToSend));
  CAN.write(lo8(speedValToSend));
  CAN.write(hi8(speedValToSend));
  CAN.write(lo8(speedValToSend));
  CAN.write(hi8(speedValToSend));
  CAN.write(lo8(count));
  CAN.write(hi8(count));

  CAN.endPacket();
}

void sendLightsOff()
{
  CAN.beginPacket(0x21A);

  CAN.write(0b00000000);
  CAN.write(0b00000000);
  CAN.write(0x00);

  CAN.endPacket();
}

uint32_t timestamp100ms = 0;
uint32_t timestamp200ms = 0;

void setup()
{
  Serial.begin(115200);
  while (!Serial)
  {
  };

  CAN.setPins(CS_PIN, IRQ_PIN);
  CAN.setSPIFrequency(1E6);

  while (!CAN.begin(100E3))
  {
    Serial.println("CAN BUS Shield init fail");
    Serial.println(" Init CAN BUS Shield again");
    delay(100);
  }
  Serial.println("CAN BUS Shield init ok!");

  timestamp100ms = millis();
  timestamp200ms = millis();
}

void loop()
{
  if (millis() - timestamp100ms > 99)
  {
    sendIgnitionKeyOn();
    sendRPM(800);
    sendSpeed(0);

    timestamp100ms = millis();
  }

  if (millis() - timestamp200ms > 199)
  {
    sendFuelLevel(24);
    sendIgnitionStatus();
    sendAirbagSeatbeltCounter();
    sendABSBrakeCounter2();
    sendABSBrakeCounter1();
    seatbeltLight(false);
    sendLightsOff();

    timestamp200ms = millis();
  }
}

Found out what problem was under this video

It was the termination resistor (trace marked P1) on Seeed CAN bus shield that was disrupting communication. When I cut the trace everything worked

More info about E90 cluster control: gitHub link