Can-bus emulation for cruise control: Difference between revisions

From The 07K Wiki

(Initial page)
 
No edit summary
 
Line 74: Line 74:
===Code example===
===Code example===
This is a very rough sketch to show a possible implementation. This example would be for an Arduino controller using a MCP2515 shield (hat).
This is a very rough sketch to show a possible implementation. This example would be for an Arduino controller using a MCP2515 shield (hat).
// demo: Cruise control switch, analog to can-bus message for VW PQ35
 
// Very rough sketch, but should work. Feel free to improve
<syntaxhighlight lang="c" line="">
// demo: Cruise control switch, analog to can-bus message for VW PQ35
#include <SPI.h>
// Very rough sketch, but should work. Feel free to improve
 
#define CAN_2515
#include <SPI.h>
 
// Set SPI CS Pin according to your hardware
#define CAN_2515
 
#if defined(SEEED_WIO_TERMINAL) && defined(CAN_2518FD)
// Set SPI CS Pin according to your hardware
const int SPI_CS_PIN  = BCM8;
 
const int CAN_INT_PIN = BCM25;
#if defined(SEEED_WIO_TERMINAL) && defined(CAN_2518FD)
#else
const int SPI_CS_PIN  = BCM8;
const int CAN_INT_PIN = BCM25;
// For Arduino MCP2515 Hat:
#else
// the cs pin of the version after v1.1 is default to D9
 
// v0.9b and v1.0 is default D10
// For Arduino MCP2515 Hat:
const int SPI_CS_PIN = 9;
// the cs pin of the version after v1.1 is default to D9
const int CAN_INT_PIN = 2;
// v0.9b and v1.0 is default D10
#endif
const int SPI_CS_PIN = 9;
const int CAN_INT_PIN = 2;
#endif
#ifdef CAN_2518FD
 
#include "mcp2518fd_can.h"
 
mcp2518fd CAN(SPI_CS_PIN); // Set CS pin
#ifdef CAN_2518FD
#endif
#include "mcp2518fd_can.h"
mcp2518fd CAN(SPI_CS_PIN); // Set CS pin
#ifdef CAN_2515
#endif
#include "mcp2515_can.h"
 
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
#ifdef CAN_2515
#endif
#include "mcp2515_can.h"
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
const int buttonMain = 4;
#endif
const int buttonRes = 2;  
 
const int buttonSet = 0;
const int buttonMain = 4;
const int buttonRes = 2;  
byte oldSwitchState = 0x0;
const int buttonSet = 0;
byte sequence = 0x0;
 
byte oldSwitchState = 0x0;
void setup() {
byte sequence = 0x0;
    SERIAL_PORT_MONITOR.begin(115200);
 
    while(!Serial){};
void setup() {
    SERIAL_PORT_MONITOR.begin(115200);
    while (CAN_OK != CAN.begin(CAN_500KBPS)) { // init powertrain can bus : baudrate = 500k  
    while(!Serial){};
        SERIAL_PORT_MONITOR.println("CAN init fail, retry...");
 
        delay(100);
    while (CAN_OK != CAN.begin(CAN_500KBPS)) { // init powertrain can bus : baudrate = 500k  
    }
        SERIAL_PORT_MONITOR.println("CAN init fail, retry...");
    SERIAL_PORT_MONITOR.println("CAN init ok!");
        delay(100);
    pinMode(buttonMain, INPUT_PULLUP);
    }
    pinMode(buttonRes, INPUT_PULLUP);
    SERIAL_PORT_MONITOR.println("CAN init ok!");
    pinMode(buttonSet, INPUT_PULLUP); // default state 12v
    pinMode(buttonMain, INPUT_PULLUP);
}
    pinMode(buttonRes, INPUT_PULLUP);
    pinMode(buttonSet, INPUT_PULLUP); // default state 12v
void loop() {
}
 
    byte switchState = 0x0;
void loop() {
 
    // Read analog inputs
    byte switchState = 0x0;
    int stateMain = digitalRead(buttonMain);
 
    int stateRes = digitalRead(buttonRes);
    // Read analog inputs
    int stateSet = digitalRead(buttonSet);
    int stateMain = digitalRead(buttonMain);
    int stateRes = digitalRead(buttonRes);
    // You can more inputs and adjust the logic according the the can-bus chart
    int stateSet = digitalRead(buttonSet);
    if (stateMain == LOW) {
 
        switchState |= (1 << 0);  // Set bit 0
    // You can more inputs and adjust the logic according the the can-bus chart
    }
    if (stateMain == LOW) {
    if (stateSet == LOW) {
        switchState |= (1 << 0);  // Set bit 0
        switchState |= (1 << 1);  // Set bit 1
    }
    }
    if (stateSet == LOW) {
    if (stateRes == LOW) {
        switchState |= (1 << 1);  // Set bit 1
        switchState |= (1 << 2);  // Set bit 2
    }
    }
    if (stateRes == LOW) {
        switchState |= (1 << 2);  // Set bit 2
    }
    // Check if the switch state has changed
 
    if (switchState != oldSwitchState) {
 
        Serial.println(switchState);  // Print new state for monitoring purposes
    // Check if the switch state has changed
        oldSwitchState = switchState; // Update oldSwitchState
    if (switchState != oldSwitchState) {
    }
        Serial.println(switchState);  // Print new state for monitoring purposes
        oldSwitchState = switchState; // Update oldSwitchState
    // Define bytes for can message (0x38A)
    }
    byte byteTwo = switchState & 1;
 
    // Define bytes for can message (0x38A)
    byte byteThree = switchState >> 1;
    byte byteTwo = switchState & 1;
    byteThree = byteThree | 0xC; // Could also be 0x4 (See Sender coding)
 
    byteThree = (sequence << 4) | byteThree;
    byte byteThree = switchState >> 1;
    byteThree = byteThree | 0xC; // Could also be 0x4 (See Sender coding)
    byte checksum = byteTwo ^ byteThree;
    byteThree = (sequence << 4) | byteThree;
 
    unsigned char payload[4] = {checksum, byteTwo, byteThree, 0x00};
    byte checksum = byteTwo ^ byteThree;
    CAN.sendMsgBuf(0x38A, 0, 4, payload);
 
   
    unsigned char payload[4] = {checksum, byteTwo, byteThree, 0x00};
    sequence = (sequence + 1) & 0x0F; // increment for the next frame
    CAN.sendMsgBuf(0x38A, 0, 4, payload);
   
    delay(20); // Send every 20ms
    sequence = (sequence + 1) & 0x0F; // increment for the next frame
}
 
    delay(20); // Send every 20ms
}
</syntaxhighlight>
 
== Emulating the ABS module (Vehicle speed) ==
The ABS module needs to be emulated as the ECU track the vehicle speed for the cruise control.
 
In practice, it would be possible to do Assembly patches and make the ECU track the engine speed. Although, this extensive work would need to be repeated across multiple firmwares. It makes more sense to emulate the ABS module, especially considered that this requires a skill set out of reach for most readers.
 
Regarding the ABS, more CAN message will need to be emulated:
{{Note|note-reminder|This section is under development. Feel free to add content.}}
 
== Monitoring the values received by the ECU ==
You can use any suitable scanner, such as VCDS, and monitor the channel '''066''' in the measurement values.
 
This channel lists:
 
* Vehicle speed
* Clutch/Brake switch values
* Cruise control switch values
* Cruise control status

Latest revision as of 17:10, 20 February 2026

Introduction

This is intended as a reference for those who would like to emulate the can-bus messages for the cruise control.

Application: Any swapped vehicles that do not have can-bus support. It would cover both the Bosch ME7.1.1 and a ME17.5/ME17.5.6

Hardware required

You can use any microcontroller that have CAN-bus capability, such as an Arduino or ESP32. You could also use the SpeedPulser Pro that already has a GPS and CAN-bus capability, you would only need to alter the source code and implement the new CAN-bus message:

CAN-bus: What is this sorcery?

CAN-bus is a network used by modules to transmit data/information with each other. With only two wires, you can transmit a multitude of messages between modules (multiplexing).

In the past, you would have a single wire for every input. In those instances, every button of your cruise control switch has its own signal wire.

The objective of the CAN-bus emulation is to take all those signals, feed them into your microcontroller and output CAN-bus messages that the ECU will understand, as if it was on a factory MK5/MK6 2.5 chassis.

CAN-bus messages required:

  1. State of the cruise control switch (Ex.: Main switch, SET, RES, etc.), normally provided by the steering column module
  2. Vehicle speed, normally provided by the ABS module

Vehicle speed acquisition

The vehicle speed can be retrieved through the following means:

  • GPS module
  • VSS sensor
  • Hall-effect sensor mounted into the instrument cluster to read the speed of the speedometer cable
  • There are more ways to gather this information depending on your creativity

Emulating the cruise control switch

The ECU receive the cruise control switch information through the CAN message 0x38A. Here is a detailed description of this CAN message

ID 0x38A, 4 bytes, Sent every 20 ms
Signal Byte Start bit Bits Range / Values Notes
Checksum (See below) 1 0 8 0..255 (Phys = Hex)
Main switch 2 0 1 0 = OFF
1 = ON
Latching ON/OFF
Tip switch OFF 2 1 1 0 = not pressed
1 = pressed
Tip switch DECEL 2 2 1 0 = not pressed
1 = pressed
Tip switch ACCEL 2 3 1 0 = not pressed
1 = pressed
Cruise decelerate (hold) 2 4 1 0 = not decelerating
1 = decelerating
Cruise accelerate (hold) 2 5 1 0 = not accelerating
1 = accelerating
Cruise control stalk error 2 6 1 0 = OK
1 = stalk error
Not used 2 7 1
Tip switch SET 3 0 1 0 = not pressed
1 = pressed
Tip switch RESUME 3 1 1 0 = not pressed
1 = pressed
Sender coding 3 2 2 00 = Body network
01 = Steering column module
10 = Engine
Values per spec
Message counter 3 4 4 0..15 (Phys = Hex) Rolling counter
Not used 4 0 8 Byte 4 unused

Checksum

The checksum is a formula used to validate the integrity of the message, and detect possible data corruption during the transmission of the message. If the checksum does not match the data, the ECU might reject the message. (Implausible message)

Checksum GRAneu.png

This checksum is calculated only with Exclusive OR (XOR) operations

Code example

This is a very rough sketch to show a possible implementation. This example would be for an Arduino controller using a MCP2515 shield (hat).

// demo: Cruise control switch, analog to can-bus message for VW PQ35
// Very rough sketch, but should work. Feel free to improve

#include <SPI.h>

#define CAN_2515

// Set SPI CS Pin according to your hardware

#if defined(SEEED_WIO_TERMINAL) && defined(CAN_2518FD)
const int SPI_CS_PIN  = BCM8;
const int CAN_INT_PIN = BCM25;
#else

// For Arduino MCP2515 Hat:
// the cs pin of the version after v1.1 is default to D9
// v0.9b and v1.0 is default D10
const int SPI_CS_PIN = 9;
const int CAN_INT_PIN = 2;
#endif


#ifdef CAN_2518FD
#include "mcp2518fd_can.h"
mcp2518fd CAN(SPI_CS_PIN); // Set CS pin
#endif

#ifdef CAN_2515
#include "mcp2515_can.h"
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
#endif

const int buttonMain = 4;
const int buttonRes = 2; 
const int buttonSet = 0;

byte oldSwitchState = 0x0;
byte sequence = 0x0;

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

    while (CAN_OK != CAN.begin(CAN_500KBPS)) { // init powertrain can bus : baudrate = 500k 
        SERIAL_PORT_MONITOR.println("CAN init fail, retry...");
        delay(100);
    }
    SERIAL_PORT_MONITOR.println("CAN init ok!");
    pinMode(buttonMain, INPUT_PULLUP);
    pinMode(buttonRes, INPUT_PULLUP);
    pinMode(buttonSet, INPUT_PULLUP); // default state 12v
}

void loop() {

    byte switchState = 0x0;

    // Read analog inputs
    int stateMain = digitalRead(buttonMain);
    int stateRes = digitalRead(buttonRes);
    int stateSet = digitalRead(buttonSet);

    // You can more inputs and adjust the logic according the the can-bus chart
    if (stateMain == LOW) {
        switchState |= (1 << 0);  // Set bit 0
    }
    if (stateSet == LOW) {
        switchState |= (1 << 1);  // Set bit 1
    }
    if (stateRes == LOW) {
        switchState |= (1 << 2);  // Set bit 2
    }


    // Check if the switch state has changed
    if (switchState != oldSwitchState) {
        Serial.println(switchState);  // Print new state for monitoring purposes
        oldSwitchState = switchState; // Update oldSwitchState
    }

    // Define bytes for can message (0x38A)
    byte byteTwo = switchState & 1;

    byte byteThree = switchState >> 1;
    byteThree = byteThree | 0xC; // Could also be 0x4 (See Sender coding)
    byteThree = (sequence << 4) | byteThree;

    byte checksum = byteTwo ^ byteThree;

    unsigned char payload[4] = {checksum, byteTwo, byteThree, 0x00};
    CAN.sendMsgBuf(0x38A, 0, 4, payload);
    
    sequence = (sequence + 1) & 0x0F; // increment for the next frame

    delay(20); // Send every 20ms
}

Emulating the ABS module (Vehicle speed)

The ABS module needs to be emulated as the ECU track the vehicle speed for the cruise control.

In practice, it would be possible to do Assembly patches and make the ECU track the engine speed. Although, this extensive work would need to be repeated across multiple firmwares. It makes more sense to emulate the ABS module, especially considered that this requires a skill set out of reach for most readers.

Regarding the ABS, more CAN message will need to be emulated:

This section is under development. Feel free to add content.

Monitoring the values received by the ECU

You can use any suitable scanner, such as VCDS, and monitor the channel 066 in the measurement values.

This channel lists:

  • Vehicle speed
  • Clutch/Brake switch values
  • Cruise control switch values
  • Cruise control status