Pages

BMW ibus

/*

 This is a sample implementation of the BMW 'ibus'
 network for Arduino. This code contains a sample
 bus receiver, a sample transmitter, and a sample
 CD changer emulator.
 
 The BMW ibus is present on several BMW, MINI,
 Land Rover, and Rolls Royce vehicles.
 
 This code requires a single digital I/O pin.
 
 This code is in the public domain.
 
*/

// useful for gauging baud rates for xmit
//#define DEBUG_TIME 1
#undef DEBUG_TIME

// useful for gauging baud rates for recv
#define DEBUG_DUMP 1
//#undef DEBUG_DUMP

//#define DEBUG_DECODE 1
#undef DEBUG_DECODE

//#define DEBUG_START 1
#undef DEBUG_START

#define DO_XMIT 1
// #undef DO_XMIT

/*
 * common section
 */

char nbuf[] = "0123456789abcdef";

void
printbyte(unsigned char byte) {
        Serial.print(nbuf[ (byte >> 4) & 0xf ]);
        Serial.print(nbuf[ byte & 0xf ]);
}
 
/*
 * soft-serial section.
 */
 
#define stride 10.5

int good_count;
int bad_count;
 
unsigned char sbound;        // the size of a bit
unsigned char hbound;        // where to sample the bit

void
ibus_setup() {
        sbound = (int)(stride + 1);                                               
        hbound = sbound / 2;
}
 
unsigned char xmit_bittimes = 21;
// bit-bang LEN worth of ibus traffic in BUFFER out PIN
// at 9600E1
void ibus_send(char pin, char *buffer, unsigned char len){
#ifdef DO_XMIT
        #define IBUS_HIGH(pin)  { pinMode(pin, INPUT); digitalWrite(pin, 0); }
        #define IBUS_LOW(pin) { digitalWrite(pin, 0); pinMode(pin, OUTPUT); }
          
        unsigned char x, y, z;
        int high_count;
        unsigned char parity;
        
        unsigned char b;
#ifdef DEBUG_TIME        
        unsigned long before;
        unsigned long after;
#endif
        unsigned char ck;
        unsigned char tmp;
        char bits[11];
        char bit;
        char rbit;
                
        if(!len)
                len = buffer[1] + 2;

        ck=0;       
        for(x = 0; x < len - 1; x++) {
                ck ^= buffer[x];
        }
        buffer[len - 1] = ck;

        top:
        
        // collision detection
        high_count = 0;
        while(high_count < xmit_bittimes * 30) {
                if(digitalRead(pin))
                        high_count++;
                else
                        high_count = 0;
        }
        
        IBUS_HIGH(pin);

#ifdef DEBUG_TIME
        before = millis();
#endif
        
        for(x = 0; x < len; x++) {
          
                b = buffer[x];

                // start bit
                bits[0] = 0;

                // compute parity. 
                // stop bit hides parity
                // and buffer computation time
                parity = 0;
                for(y = 0; y < 8; y++) {
                        tmp = ( (b & ( 1 << y)) ? 1 : 0);
                        bits[y + 1] = tmp;
                        parity += tmp;
                }
                parity = parity % 2; // even parity
                bits[9] = parity;
                bits[10] = 1; // stop bit
          
                for(y = 0; y < 11; y++) {
                        bit = bits[y];
                        if(bit) {
                                // we let the bus rise with a one
                                IBUS_HIGH(pin);

                                for(z = 0; z < xmit_bittimes; z++) {
                                        rbit = digitalRead(pin);
                                        if(!rbit) {
                                                Serial.println("C!");
                                                goto top;
                                        }
                                }
                        } else {
                                // we pull the bus down with a zero
                                IBUS_LOW(pin);
              
                                for(z = 0; z < xmit_bittimes; z++) {
                                        rbit = digitalRead(pin);
                                        if(rbit) {
                                                Serial.println("IC!");
                                                return;
                                        }
                                }
                        }
                }
        }
        
        IBUS_HIGH(pin);

#ifdef DEBUG_TIME        
        after=millis();
        Serial.print("F: TXt ");
        Serial.print("/");
        Serial.print(len);
        Serial.print(" ");
        Serial.println(after - before);
#endif
#endif
}
                          

// decode ibus traffic from the array of packed bit samples
char
ibus_decode(unsigned char *byte_buffer, char byte_len, unsigned char *bit_buffer, int bit_len) {
#        define GETBIT(n) ( ( bit_buffer[(n) >> 3] & (1 << ((n) & 0x7) ) ) >> ( (n) & 0x7 ) )
#        define TESTBIT(n) ( bit_buffer[(n) >> 3] & (1 << ((n) & 0x7) ) )

        char state, byte, bit, bits;
        char clockslide, oldclockslide;
        int stopslide, oldstopslide;
        int x, nx, z;
        char count;
        unsigned char byte_cursor;
 
        byte_cursor = 0;
        count = 1;                                                                
        x = 0;                                                                    
        state = 0;                                                                
        clockslide = 0;                                                           
        stopslide = 0;     
        bits = 0;
        byte = 0;        
            
#ifdef DEBUG_DECODE                                                                                
        Serial.print("X: FRAME: ");
        Serial.println(len);                
#endif
        
        while( x + sbound < bit_len ) {                                                    
       
#ifdef DEBUG_DECODE          
                Serial.print("X: x: ");
                Serial.println(x);
#endif         
                oldclockslide = clockslide;                                       
                oldstopslide = stopslide;                                         
                                                                                
                if( !state ) {                                                    
                        bits = 0;                                                 
                        byte = 0;                                                 
                }                                                               
               
                // where the next bit should start                                                                 
                nx = clockslide + stopslide + (int)(count * stride);                    
                count++;                                                                                                                
                                                                                
                if(TESTBIT(x + hbound)) {
#ifdef DEBUG_DECODE
                        Serial.println("X: true");
#endif
                        bit = 1;                                                  
                        bits++;                                                 
                        for(z = 1; z <= hbound; z++)                                  
                                if(!TESTBIT(x + hbound - z)) {                            
                                        if( (1 + hbound - z) == 2)              
                                                clockslide++;                   
                                        break;                                  
                                }                                               
                        if(z == hbound + 1) {                                       
                                for(z = x + hbound; z < nx; z++) {                      
                                        if(!TESTBIT(z)) {                             
                                                if( (nx - z) == 2)            
                                                        clockslide--;
                                                break;                          
                                        }                                       
                                }                                               
                        }                                                       
                } else {
#ifdef DEBUG_DECODE
                        Serial.println("X: false");
#endif
                        bit = 0;                                                  
                        for(z = 1; z <= hbound; z++)                                  
                                if(TESTBIT(x + hbound - z)) {                             
                                        if( (1 + hbound - z) == 2)               
                                                clockslide++;                   
                                        break;                                  
                                }                                               
                        if(z == hbound + 1) {                                       
                                for(z = x + hbound; z < nx; z++) {                      
                                        if(TESTBIT(z)) {                              
                                                if( (nx-z) == 2)             
                                                        clockslide--;           
                                                break;                          
                                        }                                       
                                }                                               
                        }                                                       
                }                                                               
                                                                                
                if(!state) {                                                                   
                        if(bit) {
                                Serial.println("X: bad start?");                          
                                // a bad start is a perfectly natural thing.
                                // that's how transmissions end                            
                                goto done;                                      
                        }                                                       
                        state++;                                                
                } else if(state < 9) {                                            
                        byte |= bit << (state - 1);                                     
                        state++;                                                
                } else if(state == 9) {                                           
                        if(bits % 2) { 
                                bad_count++;                          
                                Serial.println("X:\t BAD PAR");
                                ibus_dump(bit_buffer, bit_len);
              
                                byte_cursor = 0;                                
                                goto done;                                      
                        }                                                       
                        state++;                                                
                } else if(state == 10) {                                                      
                        if(!bit) {
                                bad_count++;                          
                                Serial.print("X:\t BAD ST AFTER ");
                                Serial.println((int)byte_cursor);
                                ibus_dump(bit_buffer, bit_len);
                                
                                byte_cursor = 0;                                
                                goto done;                                      
                        }                                                       
                        for(z = x + hbound; z < bit_len; z++) {                              
                                if(!TESTBIT(z)) {                                     
                                        stopslide += z - nx;                        
                                        break;                                  
                                }                                               
                        }                                                       
                        byte_buffer[byte_cursor++] = byte;
                        if(byte_cursor == byte_len)
                                goto done;

                        if(z == bit_len) {
                                //if(debug) {                                      
                                        //Serial.print("X: READ FRAME LENGTH: ");
                                        //Serial.println(z);
                                //}   
                                goto done;                                      
                        }                                                       
                        state = 0;                                                
                }                                                               
                                                                                
                x = nx + (clockslide - oldclockslide) + (stopslide - oldstopslide);       
        }

        Serial.println("X: buf too small");        
                                                                                
        done:                                                                   
        //if(debug) {                                                              
                //printf("SLIDE: %d over %d samples\n",clockslide,x); 
                //Serial.print("X: SLIDE ");
                //Serial.print(clockslide);
                //Serial.print(" over ");
                //Serial.print(x);
                //Serial.println(" samples");      
        //}
  
        //xprintf("Y: %d\n", byte_cursor);
        if(byte_cursor)
                good_count++;
          
        Serial.print("I: ");
        for(x = 0; x < byte_cursor; x++) {
                printbyte(byte_buffer[x]);
                Serial.print(" ");
        }
                Serial.println("");
               
        return byte_cursor;
}

// this loop is timing sensitive
int
ibus_sample_loop(char pin, unsigned char *bit_buffer, int clim) {
        unsigned char bindex;
        int bit_cursor;
        int bogus_cursor;
        char state;
  
        int high_count;
        int bogus_count;
        char c;
        char ival;
        char s;
  
#define MAX_PREAMBLE 90 // stride * 9 (seven bits, parity, stop) 
//#define MAX_PREAMBLE 150  
  
        high_count   = 0;
        bogus_count  = 0;
        bit_cursor   = 0;
        bindex       = 0;
        state        = 0;
        bogus_cursor = 0;
  
        // our machine has two branches
        // per iteration. one switch()
        // and one back branch here.
        top:
                
        bindex = (bit_cursor >> 3);
        ival = 1 << (bit_cursor & 0x7);

        c = bit_buffer[bindex];
    
        // we hoist all the branching to one spot.
        // on simple machines, like the AVR, the
        // extra cost to compute a unified branch
        // is outweighed by the low branch cost.
        // I still favor it for ease of reasoning
        // about the coverage and cost of each branch
        // of the automata.
        
        s = digitalRead(pin)
            | ((high_count > MAX_PREAMBLE) << 1)
            | ((bit_cursor >= clim) << 2)
            | state;
            
        switch(s) {

        /* looking for preamble. we count bit times here, so timing should mirror sample loop */
        case 0:
                c |= ival;                  // dummy logior
                c &= ~ival;                 // dummy logand
                bit_buffer[bindex] = c;     // dummy indirect assignment
                bogus_count++;              // dummy increment
                high_count = 0;               
                bogus_cursor++;             // dummy cursor increment
                goto top;
        case 1:
                c |= ival;                  // dummy logior
                c &= ~ival;                 // dummy logand
                bit_buffer[bindex] = c;     // dummy indirect assignment
                high_count++;               
                bogus_count = 0;            // dummy assignment
                bogus_cursor++;             // dummy cursor increment
                goto top;
      
        /* armed. looking for start. we are completely timing insensitive here */
        case 2:
                /* got start */
                high_count = 0;
                bit_buffer[bindex] = 0;
                bit_cursor++;
                state = 8;                    
                goto top;
        case 3:
                goto top;
      
        case 4:
        case 5: 
        case 6:
        case 7:
                Serial.println("U: imp!");
                return 0;
                // -- these would mean that the bit cursor is pegged without state being set. impossible.
    
        case 8: /* pin: 0 */
                c |= ival;                 // dummy logior
                c &= ~ival;                
                bit_buffer[bindex] = c;    
                bit_cursor++;
                bogus_count++;             // dummy increment
                high_count = 0;              
                goto top;
        case 9: /* pin: 1 */
                c &= ~ival;                // dummy logand
                c |= ival;
                bit_buffer[bindex] = c;
                bit_cursor++;
                bogus_count = 0;           // dummy assignment
                high_count++;         
                goto top;
      
        case 10: /* max high. pin 0 */
        case 11: /* max high. pin 1 */
        case 12: /* max cursor. pin 0 */
        case 13: /* max cursor. pin 1 */ 
        case 14: /* max high. max cursor. pin 0. */
        case 15: /* max high. max cursor. pin 1. */
                return bit_cursor;
    
        default:
                Serial.println("bad state");
                return 0;
        }       
}

void ibus_dump(unsigned char *bit_buffer, int bit_cursor) {
#ifdef DEBUG_DUMP  
        int x;
        
        Serial.print("W ");
        Serial.println(bit_cursor);
        Serial.print("D ");
        for(x = 0; x < bit_cursor; x += 8) {
                Serial.print((int)bit_buffer[x >> 3]);
                Serial.print(" ");
        }
        Serial.println("");
#endif
}

/*
 *  CD section
 */
 
char
tobcd(char n) {

        return (n % 10) | ((n / 10) << 4);
}
 
/* some canned commands
 * send will compute the checksum. we can set it to ff
 */
//char volume_up[]="\x50\x04\x68\x32\x11\x1F"; // turn volume up. Works!
//int volume_up_len=6;

char cd_buffer_respond[] = "\x18\x04\x68\x02\x00\xff"; // cd present
char cd_buffer_respond_len = 6;

char cd_buffer_playing[] = "\x18\x0a\x68\x39\x00\x09\x00\x3f\x00\x02\x03\xff"; // cd changer is playing disc 2 track 3
char cd_buffer_playing_len = 12;
byte current_track = 0;
byte disc_byte=9;
byte track_byte = 10;

char cd_buffer_announce[] = "\x18\x04\xff\x02\x01\xff"; // announce cd changer presence
char cd_buffer_announce_len = 6;

#ifdef DEBUG_START
char boot_buffer[] = "\xD0\x07\xBF\x5B\x60\x00\x04\x00\xFF"; // flash the IKE lights
char boot_buffer_len = 9;
#endif
 
unsigned long last_update = 0;
char responded = 0;
char fake_track = 0;
char fake_disc = 0;

void
fakecd(char pin, unsigned char *byte_buffer, char byte_cursor) {
        unsigned long m;
  
        if(byte_cursor > 3) {
                if(byte_buffer[2] == 0x18) {  
                        if( (byte_buffer[3] == 0x01) ) {
                                responded = 1;
                                ibus_send(pin, cd_buffer_respond, cd_buffer_respond_len);
                                Serial.print("M: CD: poll\n");
                        } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x00) ) {
                                ibus_send(pin, cd_buffer_playing, cd_buffer_playing_len);

                                cd_buffer_playing[track_byte] = tobcd(fake_track + 1);

                                fake_track++;
                                fake_track %= 32;

                                Serial.print("M: CD: cur trck?\n");
                        } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x01) ) {
                                Serial.print("M: CD: stp\n");
                                responded = 0;
                        } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x03) ) {
                                Serial.print("M: CD: strt\n");
                                responded = 1;
                        } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x04) ) {
                                if(byte_buffer[5] == 1) {
                                        Serial.print("M: CD: rew\n");
                                } else {
                                        Serial.print("M: CD: ff\n");
                                }  
                        } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x05) ) {
                                if(byte_buffer[5] == 1) {
                                        if(fake_track>=0)
                                                fake_track--;
                        } else {
                                fake_track++;
                                fake_track %= 32;
                        }
                        cd_buffer_playing[track_byte] = tobcd(fake_track + 1);
                        ibus_send(pin, cd_buffer_playing, cd_buffer_playing_len);

                        responded = 1;
                } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x06) ) {
                        fake_disc = byte_buffer[5];
                        cd_buffer_playing[disc_byte] = tobcd(fake_disc);
                        ibus_send(pin, cd_buffer_playing, cd_buffer_playing_len);

                        responded = 1;
                        Serial.print("M: CD: dsc\n");        
                } else if( (byte_buffer[3] == 0x38) && (byte_buffer[4] == 0x07) ) {
                        Serial.print("M: CD: scn\n");
                //} else {
                //        Serial.print("M: CD: unhandled request\n");
                }
                }    
    
        }
  
        // send an update every ten seconds
        m=millis();
        if(!responded) {
                if( (!last_update) || (m > (last_update + 10000))) {
                        last_update = m;
                        Serial.println("M: AN");
                        ibus_send(pin, cd_buffer_announce, cd_buffer_announce_len);
                }
        } else {
                if( (!last_update) || (m > (last_update + 10000))) {
                        last_update = m;
                        Serial.println("M: ST");
                        ibus_send(pin, cd_buffer_playing, cd_buffer_playing_len);
                }          
        }
}

#define DEBUG 1
#define debug 1

#include 

#define IBUS_PIN 7

void setup() {
  
  //start serial connection
  Serial.begin(9600);
  
  Serial.println("B2");

  power_adc_disable();
  power_spi_disable();
  power_twi_disable();

  //configure the pin as an input. this line is pulled up externally
  pinMode(IBUS_PIN, INPUT);
  
  Serial.println("S");
  
  ibus_setup();                    
  
#ifdef DEBUG_START 
  ibus_send(IBUS_PIN, boot_buffer, boot_buffer_len);
#endif

  // a useful way to recalibrate xmit_bittimes after changes to the loop
  /*
  for(x=18;x<22;x++) {
      Serial.print("trying ");
      Serial.println(x);
    xmit_bittimes=x;
    ibus_send(IBUS_PIN, boot_buffer, boot_buffer_len);
    ibus_send(IBUS_PIN, boot_buffer, boot_buffer_len);
    delay(8000);
  }
  */

  Serial.println("N");
}

void loop() {
        int bit_cursor;
        static unsigned char byte_buffer[16]; // ibus decode output buffer
        char byte_cursor;
#       define BUFSZ 160                 // we have a really tiny amount of memory free
        static unsigned char bit_buffer[BUFSZ]; // raw sample buffer
        int clim = BUFSZ * 8;            // number of bits we can sample
  
        while(1) {
                // this is only necessary for our own sanity.
                // if we keep it, we could eek some more
                // performance from the sample loop
                // by removing the bit clear path
                memset(bit_buffer, 0, BUFSZ);
                
                bit_cursor = ibus_sample_loop(IBUS_PIN, bit_buffer, clim);
                
                //ibus_dump(bit_buffer, bit_cursor);

                byte_cursor = ibus_decode(byte_buffer, sizeof(byte_buffer), bit_buffer, bit_cursor);
                fakecd(IBUS_PIN, byte_buffer, byte_cursor);
                
                //Serial.print("Y: "); Serial.print(bit_cursor); Serial.print(" "); Serial.println((int)byte_cursor); 
        }
}

1 comment:

  1. Hi, this is a great sketch and I hope to use it as a basis for my project and so far its going well and the sketch works with my board/ibus.

    However there are a couple of problems that I hope you ca help with.

    1: the ibus_send always sends corrupted data back onto the ibus (cd announce, is always discarded as its corrupt)

    2; I cant seem to be able to target a source packet to be able to start my routine! is there any way to show (in debug) to show the hex source address for each packet (like the radio etc 68)??

    many thanks, John

    ReplyDelete