Curious-Ninja Logo
  • Home
  • Projects
    • Aviation
      • King Air Guides
    • BMW E46
      • K/I-Bus Interface
    • IT
      • UserLogos
  • Blog
  • About me
  • Contact
Curi0us Ninja Logo

Arduino & BMW K/I-Bus Interface

  • Project Table of Contents:
  • Intro
  • I/K-Bus Technical Details
  • Schematic Descriptions
  • Project Downloads
  • Arduino Programming
  • Vehicle Integration
  • Operation & Testing
  • I/B-Bus Messages
10 Apr2016

Arduino & BMW I/K Bus Interface – Programming – v0.0.1 alpha

April 10, 2016. Written by ninja. Posted in Blog, BMW E46, E46 K-bus, Projects

*** Project: Arduino & BMW K/I-Bus Interface ***
** Intro located here ***

The examples below are from version 0.0.1-alpha. This is an older version of the code, and requires and outdated Arduino IDE, as well as some modifications to the IDE core files. I suggest looking at the latest versions (which do not require core modifications and can use the latest Arduino IDE),  available: here.

All of the Arduino I/K-Bus code is available on my BitBucket page, as well as Google Drive (embedded below).
Like I mentioned earlier, the coding was mostly done by ian332isport, and only slightly modified for projects’ my purposes.
Most of the code is fairly well commented, but I will provide a general description here anyway.

There are two slight modifications that need to be made to the Arduino core files in order for this branch of code to work properly.
The files are “HardwareSerial.cpp” and “HardwareSerial.h”, located in the following directory:
“C:\Program Files (x86)\Arduino\hardware\arduino\cores\arduino”
This is due to the modification of function “Serial.peekn(n)”. Both of the modified files are included on my BitBucket page.

The Arduino IDE version that must be used is version 1.0.5 – this is due to modifying the two core files mentioned above.
I have not checked if these files have changed in any versions after 1.0.5.

⚠ Module cannot be rendered as the requested content is not (longer) accessible. Contact the administrator to get access.

Code description:

• E46_KBus.ino

This is the main program file (The one that must be opened with the Arduino IDE). The first section below is simply setting up variables we will use throughout the code.

E46_KBus.ino


// v0.0.1-alpha branch 
#include <SoftwareSerial.h>

// Setting up our variables
volatile boolean clearToSend = false;
boolean goodPacket; //boolean value set true if good IBUS message detected
int ibusEvent = 0; 
int EventID = 0; // Event number (for case select)
byte ibusByte[40]; // Byte array to store incoming messages.. Max message length per BMW design should be 32, plus the source and length bytes, makes a total of 34. Sticking to 40 for safety - don't want to overrun the array.
byte inByte[40];
byte source;
byte length;
byte destination;
byte databytes[36]; // Byte array to store message data bytes. ibusByte array of 40 - 4 for (Source, length, destination, checksumByte) = 36
byte checksumByte;
byte outgoingMsg[32];
int outgoingSize;
const int EN = 4; // HIGH enables Melexix TH3122 chip
const int senSta = 3; // SEN/STA output from Melexix TH3122 - Needs to be on pin 3 for interrupt 1 to work (on UNO/NANO - probably different on Mega)
const byte packetGap = 10; // Length of gap between messages we send
unsigned long currentTime;
unsigned long packetTimer;
unsigned long ibusSleepTimer;
// Debug - Used for sending debug messages to terminal window.
SoftwareSerial mySerial(7, 8); 
// 7 is Rx, 8 is Tx

// All these messages are either compared with incoming messages, or can be sent out onto the bus. Add or
// remove as required, but you need to make the necessary changes further down as well.
byte SEND_MESSAGE[32] = { 
  0xC8 , 0x00 , 0x80 , 0x23 , 0x42 , 0x32 };
byte KEY_IN [7] PROGMEM = { 
  0x44 , 0x05 , 0xBF , 0x74 , 0x04 , 0x00 , 0x8E }; // Ignition key in
byte KEY_OUT [7] PROGMEM = { 
  0x44 , 0x05 , 0xBF , 0x74 , 0x00 , 0xFF , 0x75 }; // Ignition key out
byte CD_STOP [7] PROGMEM = { 
  0x68 , 0x05 , 0x18 , 0x38 , 0x01 , 0x00 , 0x4C }; // CD Stop command
byte CD_PLAY [7] PROGMEM = { 
  0x68 , 0x05 , 0x18 , 0x38 , 0x03 , 0x00 , 0x4E }; // CD Play command
byte CD_PAUSE [7] PROGMEM = { 
  0x68 , 0x05 , 0x18 , 0x38 , 0x02 , 0x00 , 0x4F }; // CD Pause command
byte MFL_VOL_UP [6] PROGMEM = { 
  0x50 , 0x04 , 0x68 , 0x32, 0x11 , 0x1F }; // Steering wheel Volume Up
byte MFL_VOL_DOWN [6] PROGMEM = { 
  0x50 , 0x04 , 0x68 , 0x32, 0x10 , 0x1E }; // Steering wheel Volume Down
byte MFL_RT [5] PROGMEM = { 
  0x50 , 0x03 , 0xC8 , 0x01, 0x9A }; // Steering wheel R/T
byte DSP_VOL_UP_1 [6] PROGMEM = { 
  0x68 , 0x04 , 0x6A , 0x32, 0x11 , 0x25 }; // Rotary Volume Up 1 step
byte DSP_VOL_DOWN_1 [6] PROGMEM = { 
  0x68 , 0x04 , 0x6A , 0x32, 0x10 , 0x24 }; // Rotary Volume Down 1 step
byte DSP_SRCE_CD [6] PROGMEM = { 
  0x68 , 0x04 , 0x6A , 0x36, 0xA0 , 0x90 }; // DSP Source = CD

The section below is the setup and the main loop functions. The main loop runs millions of times per second – it first checks if the I/K-Bus has been idle for over 60 seconds in order to put the TH3122 IC into sleep mode, and shutdown the Arduino. The TH3122 will wake up automatically when it receives K-Bus voltage once again. The main loop then sends any messages that are awaiting in the circular buffer. And finally, the loop reads the serial buffer and passes the bytes to the readIbus() function.

void setup()
{
  Serial.begin(9600, SERIAL_8E1); // Set hardware serial for 9600 baud, Even parity and one stop bit
  mySerial.begin(9600); // Set debug serial speed
  mySerial.println(F("--IBUS reader--"));
  pinMode (EN, OUTPUT); // initialize pin.
  pinMode(senSta, INPUT); // Configure pin as input
  digitalWrite (EN, HIGH); // write pin high to enable TH3122.
  senStaTimer(); // Start contention timer
  packetTimer = currentTime;
  ibusSleepTimer = millis();
}
void loop()
{
  // Sleep Timer - Check if I-Bus has been inactive for 60 seconds
  if (millis() - ibusSleepTimer >= 60000) { 
    digitalWrite (EN, LOW); // Shutdown TH3122
  }
  
  // Check if we have packets waiting to be sent onto the I-bus in our circular Buffer.. and send them.
  // Inter packet gap timer on send
  if(clearToSend && cbAvailable() > 0 ) { // If clear to send, and messages waiting in circular buffer, send them.
  //if(cbAvailable() > 0 ) { // BENCH TESTING - NO clearToSend
    if (millis() - packetTimer >= packetGap) {
      sendIbusPacket();
      packetTimer = millis(); 
    }
  }
  
  if (Serial.available() >= 2) { // if 2 or more bytes in serial buffer, go and check for bus message
  readIbus();
  }
}

The next section is the readIbus() function. It’s pretty self explanatory and well commented 😛

// This is where all the I/K-bus reading happens
// Message/Variable format example:
//  0xE8    0x05      0xD0       0x59  0x11  0x02      0x77
// source  length  destination    databytes[0-2]   checksumByte
void readIbus() {
  goodPacket = false;
  source = Serial.peekn(0);
  length = Serial.peekn(1);
  if (length >= 0x03 && length <= 0x24) { // Check if length byte between decimal 3 & 36
    if (Serial.available() >= length + 2) { // Check if enough bytes in buffer - based on length byte
      for (int i = 0; i <= length + 1; i++) {
        inByte[i] = Serial.peekn(i);
      }
      // Calculate checksum.
      checksumByte = 0;
      for (int i = 0; i <= length; i++){
        checksumByte ^= inByte[i]; 
      }
      if (inByte[length + 1] == checksumByte){ // Test for good checksum.
        ibusSleepTimer = millis();  // Restart sleep timer. Need to do this every time a message is detected

        // only process messages we're interested in. Add or remove from list as required
        if (source != 0x50 &&  // MFL
            source != 0x68 &&  // RAD
            source != 0x18 &&  // CDC
            source != 0x6A &&  // DSP
            source != 0xC8 &&  // TEL
            source != 0x44 &&  // EWS
            source != 0xE8     // RLS
            ) {
          Serial.remove(length + 2); // discard message if it's not one we want
          return;
        }
        // Populate our incoming message byte array
        for (int i = 0; i <= length + 1; i++){
          ibusByte[i] = Serial.read();
        }
        goodPacket = true;
        
        // Identify destination byte
        destination = ibusByte[2];
        
        // Identify message databytes - populate our databytes array
        for (int i = 0, s = 3; i <= length - 3; i++, s++) {
          databytes[i] = ibusByte[s];
        }
        
        printDebug("rx"); //Print to SoftwareSerial for debug purposes
        comparePacket(); // Do stuff with the message
      }
      else {
        printDebug("rx"); // Print to SoftwareSerial for debug purposes
        Serial.remove(1); // Checksum was bad.. Delete first byte in buffer and restart on next byte
      }
    }
    else { // Not enough data.. Loop around and see if more bytes have arrived
      return; 
    }
  }
  else { // Length not in range.. Delete first byte in buffer and restart on next byte
    Serial.remove(1);
    return; 
  }
}

And then we have the comparePacket() function which is called from the readIbus() function whenever we receive a verified valid I/K-Bus message. This is where you “do stuff” when you receive a message you’re interested in. You can either compare it to the list of variables set-up at the top of the code, or you can use a few if (x) {y} statements to do what you need. Some examples included in v0.0.1-alpha are: Printing text on the business radio LCD display, sending a “CD Stop” button command, and printing text onto the software serial (for debug) when the rain/light sensor detects darkness.
// This is where we compare the received message with all the message bytes
// at the top of the code, or to known source/destination, and respond accordingly.
// To send data back onto the I-Bus, we first write the packets into our circularBuffer
// TODO: 
// 1. Simplify i-Bus message sending into single functions
// 2. Implement timer to remove written text from radio display after X seconds
void comparePacket() {
  if(goodPacket == true) {
    // Message from MFL_RT
    if(memcmp_P(ibusByte, MFL_RT, 5) == 0 ) {
      // This is how to send a known i-Bus message, from list at top of code..
      // TODO - simplify this into single function. example: sendMSG(CD_STOP);
      // It's sending the CD_STOP message whenever you press the
      // R/T button on the steering wheel. This is just an example, and probably
      // not something you would ever want to do.
      cbWrite(sizeof(CD_STOP)); 
      for(int i = 0; i < sizeof(CD_STOP); i++) {
        cbWrite(pgm_read_byte(&CD_STOP[i]));
      }
    }
    // Message from MFL_VOL_UP
    if(memcmp_P(ibusByte, MFL_VOL_UP, 6) == 0 ) {
      // This is how to send text message to the Business CD/Radio display
      // TODO - simplify this into single function. example: textRadio("Example");
      char strng[] = "Vol UP"; 
      printText(strng, strlen(strng));
    }
    // Message from MFL_VOL_DOWN
    if(memcmp_P(ibusByte, MFL_VOL_DOWN, 6) == 0 ) {
     char strng[] = "Vol DOWN";
     printText(strng, strlen(strng));
    }
    // Message from RLS to LCM
    if ((source == 0xE8) && (destination == 0xD0)){
      if (databytes[1] == 0x11) { 
        mySerial.println("It's dark out, lights commanded on");
        // TODO: 
        // Blinking alert LED wire will come into arduino as a digital input (Radar detector alert LED).
        // Implement dimming feature here. If this I-Bus message is present, find a method to dim this LED
        // Arduino output to actual LED == Increase resistance to ground? PWM? Or activate a relay that includes resistor?
      }
    }
  }
}

And finally we have the sendIbusPacket() function which reads the circular buffer and writes any awaiting messages onto the K-Bus via the TH3122 IC.
// This is the code that removes messages from the circular buffer and does the actual sending.
void sendIbusPacket() {
  outgoingSize = cbRead();
  if(digitalRead(senSta) == LOW && outgoingSize <= 32) { // Double checks we are still clear to send
  //if(outgoingSize <= 32) { // BENCH TESTING - NO clearToSend
    for ( int i = 0; i < outgoingSize; i++) {
      outgoingMsg[i] = cbRead();
      Serial.write(outgoingMsg[i]); // write byte to IBUS.
    }
    printDebug("tx");
  }
  else {
    cbRemove(outgoingSize);
    return; 
  }
}

• auxFunctions.ino

This file contains auxiliary functions. The first one is the printText() function used to create a message that will display text on the business radio LCD display. The function builds the message and sends it to the circular buffer.

// This function is used to print text onto the single line display of a BMW Business CD/Radio.
// It builds the message and calculates the checksum before it writes it to the outgoing circular buffer.
void printText(char * instrng , size_t messageLen) {
  for(int i = 0, s = 0; i <= messageLen; i++, s++) {
    SEND_MESSAGE[i + 6] = instrng[s];
  }
  SEND_MESSAGE[1] = 5 + messageLen;
  int checksumPos = SEND_MESSAGE[1] + 1;
  int checksumByte = 0;  
  for (int i = 0; i <= SEND_MESSAGE[1]; i++){
    checksumByte ^= SEND_MESSAGE[i];
  }
  SEND_MESSAGE[checksumPos] = checksumByte;
  cbWrite(SEND_MESSAGE[1] + 2);
  for(int i = 0; i < SEND_MESSAGE[1] + 2; i++) {
    cbWrite(SEND_MESSAGE[i]);
  }
}

The next function is printDebug() – it is used along with software serial to display I/K-Bus messages into a serial monitor window (Such as PuTTy). I am using pins 7 (Rx) and 8(Tx) on the Arduino for the software serial, with a USB to TTL converter (These wires must be crossed to the converter).
// Print HEX I-BUS message to SoftwareSerial - for debug 
void printDebug(char debugType[3]) {
  if (debugType == "rx") {
    mySerial.print("Rx: ");
    for(int i = 0; i <= length + 1; i++) {
      mySerial.print(ibusByte[i], HEX);
      mySerial.print(" ");
    }
    if(goodPacket == true) {
      mySerial.println();
    }
    else {
      mySerial.print(" ");
      mySerial.println("Message Bad");
    }
  }
  if (debugType == "tx") {
    mySerial.print("Tx: ");
    for ( int i = 0; i < outgoingSize; i++) {
      mySerial.print(outgoingMsg[i], HEX);
      mySerial.print(" ");
    }
    mySerial.println();
  }
}

• circularBuffer.ino

This is the circular buffer (FiFo) that holds messages in queue to be written to the I/K-Bus when it is clear to send.

// This code is basically a circular (FiFo) buffer used to store outgoing messages until the bus is clear to send.
// As soon as we detect a suitable gap in bus activity, we send the messages that are stored in the buffer.
//=====================================
//    variables - not accessible by user

const byte cbBufSize = 18;

byte cbBufTail = 0;
byte cbBufHead = 0;
char cbBuffer[cbBufSize]; // use same buffer for cb and receive


//=====================================
//    user accessible functions
//=====================================


int cbAvailable() {

  byte cbByteCount = (cbBufSize + cbBufHead - cbBufTail) % cbBufSize;
  return cbByteCount;
}

//============

byte cbRead() {

  if (cbBufHead == cbBufTail) {
    return -1;
  }
  else {
    byte c = cbBuffer[cbBufTail];
    cbBufTail = (cbBufTail + 1) % cbBufSize;
    return c;
  }
}

//============

char cbWrite( byte cbInByte ) { // User calls this
  if ((cbBufHead + 1) % cbBufSize == cbBufTail) {
    return -1;
  }
  cbBuffer[cbBufHead] = cbInByte;
  cbBufHead = (cbBufHead + 1) % cbBufSize;
  return 0;
}

//============

char cbWrite( byte cbInByte[], byte cbNumInBytes ) { // User calls this
  if ((cbBufHead + 1) % cbBufSize == cbBufTail) {
    return -1;
  }
  int i = cbBufHead;
  for (byte n = 0; n < cbNumInBytes; n++) {
    cbBuffer[(n + i) % cbBufSize] = cbInByte[n];
    cbBufHead = (cbBufHead + 1) % cbBufSize;
  }
  return 0;
}

void cbRemove(int n) {
  if(cbBufHead != cbBufTail) {
    cbBufTail = (cbBufTail + n) % cbBufSize;
  }
}

//============

int cbPeek(void) {
  if (cbBufHead == cbBufTail) {
    return -1;
  }
  else {
    return cbBufTail;
  }
}

//========END=============

• senStaTimer.ino

This code is a form of collision prevention – it ensures it’s “clear to send” on the I/K-Bus, by using the SEN/STA signal from the TH3122. It monitors the signal via an interrupt and sets the flag “clearToSend” to true or false depending on that signal.

// This is all to do with making sure the bus is clear to send messages.
// It monitors the SEN/STA signal from the TH3122. As soon as the signal goes
// high, it sets a flag that it's 'not clear to send'. As soon as the signal goes low,
// it starts a timer for approx 1.5 milliseconds to check the signal is staying low,
// and then changes the flag state to 'clear to send'. No need to make changes here.

byte gapTimerCount = 94; // gives a test interval of 94 * 16 = 1504 usecs

void senStaTimer() {
  // this sets the appropriate timer mode and then attaches an interrupt to Pin 3

  TCCR2A = B00000000; // set normal mode (not fast PWM)

  TCCR2B |= ((1 << CS22) | (1 << CS21)); // Start timer 2 at Fcpu/256

  attachInterrupt(1, startTimer, FALLING);

}


//========================

void startTimer() {
  // when a falling pulse is detected on pin3 the clock is started to trigger another
  //  interrupt after NN microseconds (determined by the value on gapTimerCount)

  clearToSend = false;
  OCR2A = TCNT2 + gapTimerCount; // sets up Timer2A to run for the sample period
  TIMSK2 |= (1 << OCIE2A);  // Enable Timer2 Compare Match A Interrupt
  TIFR2  |= (1 << OCF2A);   // Clear  Timer2 Compare Match A Flag 

  detachInterrupt(1);
  attachInterrupt(1, clearToSendReset, RISING);
}

//========================

void clearToSendReset() {
  // when a rising pulse is detected on pin3 clearToSend = false

  clearToSend = false;

  detachInterrupt(1);
  attachInterrupt(1, startTimer, FALLING);
}

//========================


ISR(TIMER2_COMPA_vect) { // this function runs when the Timer2A compare expires

  TIMSK2 &= ~(1 << OCIE2A);  // Disable Timer2 Compare Match A Interrupt

  clearToSend = true; 
}

Project Continued:
• Intro
• Technical Details
• Schematic Description
• Resources & Downloads
• Programming
• Integration
• Messages

Sharing is caring 🙂 Share this page with your friends!
  • Total0
  • Facebook
  • Twitter
  • Reddit
  • LinkedIn
  • Pinterest

Tags: arduino, bmw, bmw e46, bmw i-bus, bmw k-bus, c plus plus, c++, communications, diy, e46, electronics, i-bus, k-bus, programming, projects, serial, technical, wiring

Arduino & BMW K/I-Bus Interface

  • Project Table of Contents:
  • Intro
  • I/K-Bus Technical Details
  • Schematic Descriptions
  • Project Downloads
  • Arduino Programming
  • Vehicle Integration
  • Operation & Testing
  • I/B-Bus Messages
NEW POSTS
  • King Air quickGuides December 29, 2018
  • Arduino & BMW I/K Bus Interface – Operation & Testing November 19, 2017
  • Arduino & BMW I/K Bus Interface – Programming – v0.1.0 beta April 11, 2016
  • Arduino & BMW I/K Bus Interface – Programming – v0.0.1 alpha April 10, 2016
  • UserLogos & Fast Dial August 10, 2015
ARCHIVES
  • December 2018
  • November 2017
  • April 2016
  • August 2015
  • July 2015
  • May 2015
TAGS
arduino bmw bmw e46 bmw i-bus bmw k-bus c++ chrome communications c plus plus details diy downloads e46 electronics fast dial firefox i-bus integration k-bus messages navcoder programming projects schematic serial speed dial technical testing userlogos wiring
Built with HTML5 and CSS3 - Curious.Ninja
  • Home
  • Projects
    • Aviation
      • King Air Guides
    • BMW E46
      • K/I-Bus Interface
    • IT
      • UserLogos
  • Blog
  • About me
  • Contact