Talking to GMLAN on my Vectra-C with my Arduino

So I thought I would open out this new blog with a success story. I got my Arduino Uno with Sparkfun canbus shield to talk to my car (03 plate Vauxhall Vectra-C).

To do this there are a couple of things I found out from reading around the subject and I have recorded them here for posterity.

You need a special cable to talk to the GMLAN with the Sparkfun shield. I used this one and wired it up per the diagram here with two exceptions. I connected the CAN-H line to pin 1 on the OBD connector and I left the CAN-L line disconnected. For reference the pins on the ODB connector are…

 

    Pin 1 - SW-LS-CAN (33kB) or DW-FT-CAN (+) (<125kB)
    Pin 2 - J1850
    Pin 3 - MS-CAN (+) (95kB)
    Pin 4 - Battery - (Chassis Ground)
    Pin 5 - Signal Ground
    Pin 6 - ISO 15765 HS-CAN (+) (500kB)
    Pin 7 - K-Line
    Pin 8 -
    Pin 9 - DW-FT-CAN (-) (<125kB)
    Pin 10 - PWM
    Pin 11 - MS-CAN (-) (95kB)
    Pin 12 - K-Line (KW82 Prot.)
    Pin 13 - Reserved
    Pin 14 - ISO 15765 HS-CAN (-) (500kB)
    Pin 15 - L-Line
    Pin 16 - Battery + (Constant 12V Power)

 

You need to connect the CAN-L of the MCP2515 on the shield to GND because this is single wire CAN (SWCAN). The easiest way to do this is probably by soldering a header onto the 5V/GND/CAN-H/CAN-L part of the shield and then linking the CAN-L to GND there. I guess you could also connect the CAN-L line to ground in the OBD connector if you preferred.

With this taken into account you just need some code that works. There is some excellent discussion and example code on the Carmodder forums here. The code in that thread is a good starting point as it mostlydemonstrates how to correctly init the MCP2515 at the correct speed as well has how to send and receive messages. Some caveats…

Most of the discussion on that thread is about 29bit CAN and my Vectra is only 11bit so I have to use the (commented out) 11bit can send function in the mcp2515.pde file. There are also some other differences including smaller 2 byte IDs, no dedicated priority byte and 8 bytes for data.

I don’t claim to understand the SPI init code but it did not work for me it just froze my Arduino, instead of the provided initSPI I used this..

 

  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  SPI.begin();

 

I replaced the 11bit send function with one that accepted a variable length array for the data instead of fixed D1,D2,D3 args

 

//-----------------------------------
//Send Standard 11 bit Packet
//-----------------------------------
// byte D0,byte D1,byte D2,byte D3,byte D4,byte D5,byte D6,byte D7)
byte can_send_11bit_message(uint16_t id, int length, byte packetdata[])
{
  //See that buffer is free
  byte status;
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0xA0);    //Read Status
  status=SPI_ReadWrite(0xFF);
  digitalWrite(P_CS,HIGH);
  //Read Status devuelve
  //Bit0.- CANINTF.RX0IF
  //BIT1.- CANINTF.RX1IF
  //BIT2.- TXB0CNTRL.TXREQ
  //BIT3.- CANINTF.TX0IF
  //BIT4.- TXB1CNTRL.TXREQ
  //BIT5.- CANINTF.TX1IF
  //BIT6.- TXB2CNTRL.TXREQ
  //BIT7.- CANINTF.TX2IF

  //Search for a free buffer
  byte buffer_free;
  byte address;
  if (bit_is_clear(status,2)){
    buffer_free=1;
    address=0x31;    //Registro Standard ID buffer 0
  }else if (bit_is_clear(status,4)){
    buffer_free=2;
    address=0x41;    //Registro Standard ID buffer 1
  }else if (bit_is_clear(status,6)){
    buffer_free=4;
    address=0x51;    //Registro Standard ID buffer 2
  }else{
    return 0;    //No hay buffer libre, no se ha transmitido msje  
  }

  //Set priorities on sending buffers
  //Is independent of the priorities of the bus can => (greater priority = lowest id)
  //It always puts the current buffer (final Msg) with the lowest priority

  switch (address){
    case 0x31:
      mcp2515_write_register(0x30,(0<>3));
  mcp2515_write_register(address+1, (uint8_t) (id< Registro TXBnDm
  int c = 0;
  while (c < length) {    
    mcp2515_write_register(address+c+5,packetdata[c]);
    Serial.print("Wrote byte ");
    Serial.print(packetdata[c]);
    Serial.print(" to address ");
    Serial.println(address);
    c++;
  }
//  mcp2515_write_register(address+c+5,D0);   //Byte0
//  mcp2515_write_register(address+6,D1);   //Byte1
//  mcp2515_write_register(address+7,D2);   //Byte2
//  mcp2515_write_register(address+8,D3);   //Byte3
//  mcp2515_write_register(address+9,D4);   //Byte4  
//  mcp2515_write_register(address+10,D5);  //Byte5
//  mcp2515_write_register(address+11,D6);  //Byte6
//  mcp2515_write_register(address+12,D7);  //Byte7  

  // Enviar mensaje CAN
  digitalWrite(P_CS,LOW);
  SPI_ReadWrite(0x80 | buffer_free);  //RTS(Message Request To Send)
  digitalWrite(P_CS,HIGH);
  return 1;
}

 

So armed with the working send and receive functions for the carmodder forums we can log data from the canbus and also inject our own packets. I used a small uSD card for logging that seemed easiest.

 

void processMessage() {
  Serial.print(heady);
  Serial.print(" ");
  Serial.println(datalength);

  File myFile = SD.open("output.txt", FILE_WRITE);
  if (myFile) {
    myFile.print(millis());
    myFile.print(" : ");
    myFile.print(heady, HEX);
    myFile.print(" | ");
    myFile.print(datalength, DEC);
    myFile.print(" | ");
    for (int i=0; i < datalength; i++) {
      myFile.print(message[i], HEX);
      myFile.print(" ");
    }
    myFile.println("");
    myFile.close();
  }
}

 

The code above writes every line to a file on the SD card including a timecode, the header, the length and the data bytes. Like this..

 

108670 : 622 | 8 | 0 0 40 0 0 0 0 0 
108692 : 360 | 3 | 0 0 0 
108712 : 340 | 2 | 0 0 
108730 : 626 | 8 | 0 11 0 0 0 0 0 0 
108751 : 440 | 8 | A8 6C 40 FF 0 70 50 C 
108775 : 170 | 1 | 0 
109383 : 400 | 5 | 2 0 0 0 0 
109619 : 230 | 5 | 5 0 40 0 0 
109640 : 405 | 6 | 20 0 0 0 0 0 
109691 : 420 | 5 | C2 0 10 0 0 
109710 : 23A | 3 | 0 0 0 
109731 : 305 | 3 | 0 81 90 
109757 : 525 | 1 | 0 
109775 : 445 | 2 | 0 62 
110191 : 360 | 3 | 0 0 0 
110211 : 350 | 2 | 2 0 
110230 : 340 | 2 | 0 0 
110247 : 170 | 1 | 0 
110383 : 400 | 5 | 2 0 0 0 0

 

To send messages is also relatively straightforward…

 

byte packet[] = {0x00, 0x04, 0x00, 0xF3, 0x5C};
if (can_send_11bit_message(0x0415, sizeof(packet), packet)) {
   Serial.println("Sent packet succesfully");
}
delay(100);

 

On my Vectra-C the CAN packet above operates the passenger front electric window. I tested this by binding the commands for up and down on the window to the joystick on the canbus shield.

Posted in Arduino, Car | Tagged , , , | 2 Comments

2 Responses to Talking to GMLAN on my Vectra-C with my Arduino

  1. Pingback: Sniffing GMLAN with a Raspberry Pi | b.pa.ulthom.as

  2. Pingback: How I got my Amazon Echo to start my car. – Jryanishere Blog

Leave a Reply