**EDIT**
I’ve had a couple of questions around the CAN library I use. It seems I’ve probably been using a very out of date version of the Seeed Studios library for some time now but selecting “Seeed Library” in the declarations in this code as it is set above makes it compatible with the latest (tested on Seeed CAN-BUS Shield Library 2.3.1). I suggest using the most recent version available which can be installed from the Arduino library manager or from GitHub. If you’re having problems with this code but didn’t previously with my older code it’s likely this is your problem. The code should still work with the older library with the “MCP_Library” option selected but there’s not much point.
I’ve also just noticed the code on this page seems to have some issue being copied/pasted from this page so here’s a link to the Arduino INO file:
The original post starts below:
Following a couple requests recently from people I’ve decided to post my code as it currently stands. I’ve been meaning to tidy it up and crop out all the extraneous bits but I’ve just not had time so here we go. I describe this as “working code” simply because it’s the one I’m still working on!
There’s a lot going on here so don’t expect it to be an immediate plug and play and additionally there are extra variables and things that I’ve used for testing with no purpose otherwise so don’t be surprised if you can’t work out what all of it is for. One trick bit I’ve added is if a specified digital input is tied to 0V when the Arduino powers up it starts in a listen mode where if the ECU is still connected it logs the exchange between it an the immobiliser and stores the data to the internal EEPROM memory. If you then disconnect the ECU and remove the 0V jumper it will wait for the immobiliser to try to initialise by matching its code to the one logged and sent the stored response. I don’t know if this will work correctly on all cars but it should. As per one of my previous posts you can actually just write random data in this exchange as long as the packet structure is right and it’ll work.
Similarly the code also includes the update for the ODO and trip meters based on ABS speed data so that should all work ok hopefully.
The latest section I was working on when other things started taking all my time again is to decode CAN packets from a Megasquirt ECU to control. Generally this should work but you might want to modify this to either not overwrite certain if you are getting them from elsewhere such as temperature for the cluster reading from an analogue input rather than CAN. There is an enable boolean for this (MSCAN) at the top of variable declaration but it’s defaulted to false to stop it messing with anything normally.
As ever if anyone wants to know any more about what’s going on just post a comment at the bottom. Sometimes it takes me a while to respond but I try to answer everyone.
My only other request is if you link to this page when sharing this elsewhere, mostly because I find it really interesting to see how it’s being used!
// Code modified by Jonathan Coe (www.chamberofunderstanding.co.uk) 2021 with the following: // // Fixed variable rollover issue with speeds over 163 // Added new definitions to allow switching to Leonardo CAN hardware // Added new definitions to allow use of Seeed CAN library rather than MCP_CAN clones // Added startup LED blink to confirm unit powered // Added two short LED blinks when can chip started successfully // Added slow LED blinking when CAN chip failed to start // Added function to pull immobiliser challenge/response packets from existing vehicle // Added EEPROM functions to store config data // Added Code to check immobiliser requests against data from previous scans (retained through power cycle) and respond with stored answer // Added Code to increment the Odometer/Trip based on live speed from ABS system // Added Code to decode Megasquirt CAN data for engine. // // This code is a development from the work done by Dave Blackhurst (details below) which in itself was based // on earlier work from this website which in itself included research done by others before on the ID's // // ************************************************************************************** // Arduino Leonardo code for replacing the PCM within a Mark 1 RX8 // This code allows you to leave the CANBUS in place, just removing the PCM // There are plenty of ID's on the CANBUS I do not understand, and any use of this code is strictly at your own risk // // Features turned on, possibly working - at least they do not complain on the dashboard, // ABS / DSC // Traction Control // Immobiliser Deactivated // Power Steering Enabled // RPM Controllable // Vehicle Speed set by Wheel Sensors // Warning lights turned off (you can turn them on in the code if you wish) // // Written by David Blackhurst, dave@blackhurst.co.uk 06/10/2019 // // Some parts of this code have been dissected from other sources - in researching this project I copied a lot of code to play with // Sorry if some of this is yours - just let me know and I will attribute it to you. // // I have throttle pedal code in here to translate the output of the primary throttle signal to the range my controller required. This is unlikely to be helpful to anyone else // unless you happen to have the dodgy chinese controller I have. // // Again use AT YOUR OWN RISK #include <Arduino.h> /// ********************* Option Selection ********************* // JC 21/01/20 - Updates to select hardware version to allow support for Leonardo CAN // and preferred CAN library (either the standard MCP-CAN versions or SEEED version) for compiler // Comment out to select correct hardware #define LEO_CAN // #define Seeed_CAN // Comment out to select correct CAN library #define Seeed_Library //#define MCP_Library #ifdef Seeed_CAN // Configure Pins for Seeed CAN Shield #define CANint 2 #define LED 13 #define CAN_CS 10 #define Set_Immobiliser 1 #endif #ifdef LEO_CAN // Configure Pins for Leonardo CAN #define CANint 7 #define LED 23 #define CAN_CS 17 #define Set_Immobiliser 4 #endif #ifdef Seeed_Library #include "mcp2515_can.h" mcp2515_can CAN0(CAN_CS); // Configure CAN SPI Chip Select #endif #ifdef MCP_Library #include <mcp_can.h> #include <mcp_can_dfs.h> MCP_CAN CAN0(CAN_CS); // Configure CAN SPI Chip Select #endif #include <EEPROM.h> // Load EEPROM library to save configuration data /// ********************* End of Option Selection ********************* // Enable MS_CAN Decode bool MSCAN = false; // Variables for Throttle Pedal int analogPin = A1; int outputPin = 5; int val = 0; int lowPedal = 0; int highPedal = 0; int convertThrottle = 0; int base = 0; int output = 0; // Declarations for loop delays long lastRefreshTime = 0; long ODORefreshTime = 0; // Variables for PCM, Only overrideable if PCM removed from CAN bool checkEngineMIL; bool checkEngineBL; byte engTemp; byte odo; bool oilPressure; bool lowWaterMIL; bool batChargeMIL; bool oilPressureMIL; // Variables for PCM, Only overrideable if PCM removed from CAN int engineRPM; int vehicleSpeed; byte throttlePedal; // Variables for ABS/DSC, Only overrideable if ABS/DSC removed from CAN bool dscOff; bool absMIL; bool brakeFailMIL; bool etcActiveBL; bool etcDisabled; // Variables for Wheel Speed // JC 21/01/20 - changed from int to long as variable rollover was causing speeds over 163 to go negative long frontLeft; long frontRight; long rearLeft; long rearRight; //Variables for reading in from the CANBUS unsigned char len = 0; unsigned char buf[8]; unsigned long ID = 0; //Setup Array's to store bytes to send to CAN on Various ID's byte send201[8] = {0, 0, 255, 255, 0, 0, 0, 255}; byte send420[7] = {0, 0, 0, 0, 0, 0, 0}; byte send212[7] = {0, 0, 0, 0, 0, 0, 0}; //Setup PCM Status's required to fool all other CAN devices that everything is OK, just send these out continuously byte send203[7] = {19,19,19,19,175,3,00}; // {19,19,19,19,175,3,19} data to do with traction control byte send215[8] = {2,45,2,45,2,42,6,129}; // {2,45,2,45,2,42,6,129}, experimented with {2,0,2,0,2,0,0,0} but no idea byte send231[5] = {15,0,255,255,0}; // {15,0,255,255,0} or {255,0,255,255,0} byte send240[8] = {4,0,40,0,2,55,6,129}; // No idea what this is for byte send620[7] = {0,0,0,0,0,0,4}; //needed for abs light to go off, byte 7 is different on different cars, sometimes 2,3 or 4 {0,0,0,0,16,0,4} byte send630[8] = {8,0,0,0,0,0,106,106}; //needed for abs light to go off, AT/MT and Wheel Size byte send650[1] = {0}; //Cruise Light, 0 = Off, Bit 6 = Green "Cruise", Bit 7 = Yellow "Cruise Main" // Declarations for testing 4B0/4B1 VSS Rx on Megasquirt. // 180 mph //byte send4b1[8] = {113, 40, 113, 40, 113, 40, 113, 40}; // 180 mph rear, 160mph front //byte send4b1[8] = {100, 149, 100, 149, 113, 40, 113, 40}; // 100 mph //byte send4b1[8] = {62, 221, 62, 221, 62, 221, 62, 221}; // 10 mph //byte send4b1[8] = {6, 73, 6, 73, 6, 73, 6, 73}; //KCM / Immobiliser replies for Dave Blackhurst //byte send41a[8] = {7,12,48,242,23,0,0,0}; // Reply to 47 first : 0x 07 0C 30 F2 17 00 00 00 //byte send41b[8] = {129,127,0,0,0,0,0,0}; // Reply to 47 second : 0x 81 7F // Immobiliser replies for Jon Coe byte send41a[8] = {7,120,192,226,94,0,0,0}; // Reply to 47 first : 0x 07 78 C0 E2 5E 00 00 00 // Bytes 0 is the same, bytes 3 & 4 dont seem to matter, 5,6,7 are zero byte send41b[8] = {129,127,0,0,0,0,0,0}; // Reply to 47 second : 0x 81 7F // Immobiliser Blank byte response_a[8] = {7,0,0,0,0,0,0,0}; byte response_b[8] = {129,127,0,0,0,0,0,0}; // This always seems to be this value (0x 81 7F) so used as default byte request_a[8] = {6,127,0,0,0,0,0,0}; byte request_b[8] = {8,0,0,0,1,0,0,0}; // Time delay for Odo long ODOus = 4500000; // Set to max 4,500,000 to keep dash MIL warning lights awake at 0 speed void printhex( byte [], int ); // JC - Prototype for function to send byte array to serial monitor as HEX pairs // Actual function is later in code - prototypes not technically required in Arduino IDE void setup() { Serial.begin(115200); Serial.println("Start Setup"); // Give a Wakeup Blink - Disabled to speed up boot /* pinMode(LED, OUTPUT); digitalWrite(LED, HIGH); delay(500); digitalWrite(LED, LOW); */ pinMode(CANint, INPUT); // Set CAN interrupt pin as input pinMode(Set_Immobiliser, INPUT_PULLUP); // Configure Button to setup immobiliser as input with pullup enabled if (CAN0.begin(CAN_500KBPS) == CAN_OK) { // Connect to CAN chip Serial.println("Found High Speed CAN"); // JC 21/01/20 - Added two short blinks of LED to identify CAN chip started // Disabled to improve speed /* digitalWrite(LED, HIGH); delay(200); digitalWrite(LED, LOW); delay(100); digitalWrite(LED, HIGH); delay(200); digitalWrite(LED, LOW); */ } else { Serial.println("Failed to find High Speed CAN"); while (1) { Serial.println("Loop Forever"); // JC 21/01/20 - Added long blinking of LED to identify CAN chip fault digitalWrite(LED, HIGH); delay(1000); digitalWrite(LED, LOW); delay(1000); } } // Populates CAN buffers with meaningful initial data // for live use this will prevent dash lights defaulting to ON etc setDefaults(); // JC - Sets up some default values to fill CAN registers with sensible data in case nothing else is written later //******* JC 25/01/21 Immobiliser Compatibility Mods ******* // Check if First Run Immobiliser Code Scanning is Enabled if (digitalRead(Set_Immobiliser) == 0){ immobiliserCodeSet(); } // Check if stored values exist in EEPROM and if not setup from program defaults if (EEPROM.read(0) == 0 && EEPROM.read(1) == 0){ writeByteArrayIntoEEPROM(0, request_a, 16); writeByteArrayIntoEEPROM(16, response_a, 16); writeByteArrayIntoEEPROM(32, request_b, 16); } // Pull Immobiliser codes from EEPROM //delay(5000); delay to allow serial startup - disabled for speed readByteArrayFromEEPROM(0, request_a, 8); Serial.print("Request A from EEPROM : "); printhex(request_a,8); readByteArrayFromEEPROM(16, response_a, 8); Serial.print("Response A from EEPROM : "); printhex(response_a,8); readByteArrayFromEEPROM(32, request_b, 8); Serial.print("Request B from EEPROM : "); printhex(request_b,8); } // ******* JC 25/01/21 Code to write Hex array to Serial ******* void printhex(byte b[], int sizeOfArray){ Serial.print("0x "); for (int i=0;i<sizeOfArray;i++){ if(b[i]<10){ Serial.print("0"); } Serial.print(b[i],HEX); Serial.print(" "); } Serial.println(""); } ////////////////////////////////////////////////////////////////////////////////////// // // ******* JC 25/01/21 Code to Scan Immobiliser codes from a working system ******* // Set_Immobiliser is a digital input which is configured as // INPUT_PULLUP and tied to ground to enable this mode. // ////////////////////////////////////////////////////////////////////////////////////// void immobiliserCodeSet(){ digitalWrite(LED, HIGH); delay(3000); digitalWrite(LED, LOW); Serial.println("Immobiliser Code Read Mode"); //immobiliserCodeSet; int immSet1 = 0; do if(CAN_MSGAVAIL == CAN0.checkReceive()) { // Check to see whether data is read CAN0.readMsgBufID(&ID, &len, buf); // Read data if(ID == 0x47) { //71 Dec is 47 Hex - Keyless Chat if(buf[0] == 0x6 && buf[1] == 0x7F){ memcpy(request_a, buf, 8); immSet1++; Serial.print("Request 1 Found! - "); printhex(buf,8); } if (buf[0] == 0x8){ memcpy(request_b, buf, 8); immSet1++; Serial.print("Request 2 Found! - "); printhex(buf,8); } } if(ID == 0x41) { if(buf[0] == 0x07){ memcpy(response_a, buf, 8); immSet1++; Serial.print("Response 1 Found! - "); printhex(buf,8); } } Serial.print("CAN Data Received - Found "); Serial.println(immSet1); } while (immSet1 < 3); digitalWrite(LED, HIGH); delay(200); digitalWrite(LED, LOW); delay(100); digitalWrite(LED, HIGH); delay(200); digitalWrite(LED, LOW); delay(100); digitalWrite(LED, HIGH); delay(200); digitalWrite(LED, LOW); Serial.print("All Codes Found - Saving..."); writeByteArrayIntoEEPROM(0, request_a, 16); writeByteArrayIntoEEPROM(16, response_a, 16); writeByteArrayIntoEEPROM(32, request_b, 16); Serial.println("Ok!"); Serial.println(""); Serial.println(" Turn off ignition, reset to normal mode and reboot"); while (digitalRead(Set_Immobiliser) == 0); // Halt here as long as input held low } // ******* End of Immobiliser Scan Routine ******* void setDefaults() { Serial.println("Setup Started"); // StatusMIL engTemp = 145; //Roughly in the middle odo = 0; oilPressure = 1; // For the gauge, 1 is OK, 0 is L checkEngineMIL = 0; checkEngineBL = 0; lowWaterMIL = 0; batChargeMIL = 0; oilPressureMIL = 0; // StatusPCM engineRPM = 1000; // RPM vehicleSpeed = 0; // km/h + 10000 throttlePedal = 0; // % // StatusDSC dscOff = 0; absMIL = 0; etcActiveBL = 0; etcDisabled = 0; brakeFailMIL = 0; /* Serial.println("Start wait to ensure Throttle Pedal is on"); delay(500); lowPedal = 341; //analogRead(analogPin) - 40; Temporary fixed value //read the throttle pedal, should be around 1.7v minus 40 to ensure no small throttle inputs highPedal = 803; //4v // Voltage to read from Pedal 1.64v - 4.04v // Going to use a safe range 1.7v to 4v // Low of 1.7v has been read above as can fluctuate // 1.7v = INT 341 // 4v = INT 803 // (highPedal - lowPedal) = RANGE FROM RX8 PEDAL // out for 1024 (5v max), controller wants 4.5v max = 920 (adding 40 to help stabilise) convertThrottle = 960 / (highPedal - lowPedal); Serial.print("Low Pedal "); Serial.print(lowPedal); Serial.print(", High Pedal "); Serial.println(highPedal); Serial.println("Setup Complete"); */ } // ********** JC 25/01/21 - Add Functionality to read/write arrays from EEPROM ********** // Taken from www.roboticsbackend.com and expanded for byte arrays void writeIntArrayIntoEEPROM(int address, int numbers[], int arraySize) { int addressIndex = address; for (int i = 0; i < arraySize; i++) { EEPROM.write(addressIndex, numbers[i] >> 8); EEPROM.write(addressIndex + 1, numbers[i] & 0xFF); addressIndex += 2; } } void readIntArrayFromEEPROM(int address, int numbers[], int arraySize) { int addressIndex = address; for (int i = 0; i < arraySize; i++) { numbers[i] = (EEPROM.read(addressIndex) << 8) + EEPROM.read(addressIndex + 1); addressIndex += 2; } } void writeByteArrayIntoEEPROM(int address, byte numbers[], int arraySize) { int addressIndex = address; for (int i = 0; i < arraySize; i++) { EEPROM.write(addressIndex, numbers[i] >> 8); EEPROM.write(addressIndex + 1, numbers[i] & 0xFF); addressIndex += 2; } } void readByteArrayFromEEPROM(int address, byte numbers[], int arraySize) { int addressIndex = address; for (int i = 0; i < arraySize; i++) { numbers[i] = (EEPROM.read(addressIndex) << 8) + EEPROM.read(addressIndex + 1); addressIndex += 2; } } // ********** End of EEPROM Section ********** void updateMIL() { send420[0] = engTemp; //send420[1] = odo; send420[4] = oilPressure; if (checkEngineMIL == 1) { send420[5] = send420[5] | 0b01000000; } else { send420[5] = send420[5] & 0b10111111; } if (checkEngineBL == 1) { send420[5] = send420[5] | 0b10000000; } else { send420[5] = send420[5] & 0b01111111; } if (lowWaterMIL == 1) { send420[6] = send420[6] | 0b00000010; } else { send420[6] = send420[6] & 0b11111101; } if (batChargeMIL == 1) { send420[6] = send420[6] | 0b01000000; } else { send420[6] = send420[6] & 0b10111111; } if (oilPressureMIL == 1) { send420[6] = send420[6] | 0b10000000; } else { send420[6] = send420[6] & 0b01111111; } } void updatePCM() { int tempEngineRPM = engineRPM * 3.85; int tempVehicleSpeed = (vehicleSpeed * 100) + 10000; send201[0] = highByte(tempEngineRPM); send201[1] = lowByte(tempEngineRPM); send201[4] = highByte(tempVehicleSpeed); send201[5] = lowByte(tempVehicleSpeed); send201[6] = (200 / 100) * throttlePedal; //Pedal information is in 0.5% increments } void updateDSC() { if (dscOff == 1) { send212[3] = send212[3] | 0b00000100; } else { send212[3] = send212[3] & 0b01111011; } if (absMIL == 1) { send212[4] = send212[4] | 0b00001000; } else { send212[4] = send212[4] & 0b11110111; } if (brakeFailMIL == 1) { send212[4] = send212[4] | 0b01000000; } else { send212[4] = send212[4] & 0b10111111; } if (etcActiveBL == 1) { send212[5] = send212[5] | 0b00100000; } else { send212[5] = send212[5] & 0b11011111; } if (etcDisabled == 1) { send212[5] = send212[5] | 0b00010000; } else { send212[5] = send212[5] & 0b11101111; } } long calcMicrosecODO(float speedKMH){ long uS; float freq; float speedMPH; Serial.print("Speed = "); Serial.print(speedKMH/100); Serial.println(" km/h"); speedMPH = speedKMH / 160.934; // Required frequency for timer 1 ISR // 1.15 is 4140 (Pulse per Mile) / 3600 (1hr in seconds) // 0.7146 is 2572.5 (pulse per KM) / 3600 freq = speedMPH * 1.15; Serial.print("Freq = "); Serial.print(freq); Serial.println(" Hz"); uS = 1000000/freq; if(uS < 4500000 && uS > 0){ return (uS);} else { return (4500000); } } void sendOnClock(){ // Do not increment ODO byte when step is = 4.5s // slower than this updateMIL must still be called so // warning lights don't turn on but speed may be zero! if ( ODOus < 4500000){ send420[1]++; } updateMIL(); CAN0.sendMsgBuf(0x420, 0, 7, send420); } void sendOnTenth() { //PCM Status's to mimic the PCM being there, these may be different for different cars, and not all are always required, better safe and include them all. CAN0.sendMsgBuf(0x203, 0, 7, send203); CAN0.sendMsgBuf(0x215, 0, 8, send215); CAN0.sendMsgBuf(0x231, 0, 8, send231); CAN0.sendMsgBuf(0x240, 0, 8, send240); CAN0.sendMsgBuf(0x620, 0, 7, send620); CAN0.sendMsgBuf(0x630, 0, 8, send630); CAN0.sendMsgBuf(0x650, 0, 1, send650); updateMIL(); CAN0.sendMsgBuf(0x420, 0, 7, send420); //Moved to sendOnClock to update at timer ISR frequency for ODO updatePCM(); CAN0.sendMsgBuf(0x201, 0, 8, send201); // Send to Megasquirt VSS Sim - For testing Megasquirt ABS decode // CAN0.sendMsgBuf(0x4b1, 0, 8, send4b1); /* Add this section back in if you want to take control of ABS / DSC Lights. updateDSC(); CAN0.sendMsgBuf(0x212, 0, 7, send212); */ } void loop() { //Send information on the CanBus every 100ms to avoid spamming the system. if(micros() - lastRefreshTime >= 100000) { lastRefreshTime += 100000; sendOnTenth(); } // Call function to updateMIL on variable timebase if(micros() - ODORefreshTime >= ODOus) { ODORefreshTime += ODOus; sendOnClock(); } //Read the CAN and Respond if necessary or use data if(CAN_MSGAVAIL == CAN0.checkReceive()) { // Check to see whether data is read CAN0.readMsgBufID(&ID, &len, buf); // Read data //digitalWrite(LED, HIGH); //delay(1); //digitalWrite(LED, LOW); if(ID == 0x212) { // 0x212 = 530 for(int i = 0; i<len; i++) { // Output 8 Bytes of data in Dec Serial.print(buf[i]); Serial.print("\t"); } //Serial.print(time); // Timestamp Serial.println(""); //Serial.println(line); // Line Number } //Keyless Control Module and Immobiliser want to have a chat with the PCM, this deals with the conversation if(ID == 0x47) { //71 Dec is 47 Hex - Keyless Chat /* //***** Fixed Coding for Dave Blackhurst's Car ******* if(buf[1] == 127 && buf[2] == 2) { // 0x 06 7F 02 00 00 00 00 00 CAN0.sendMsgBuf(0x041, 0, 8, send41a); // 0x041 = 65 } if(buf[1] == 92 && buf[2] == 244) { // 0x 08 5C F4 65 22 01 00 00 CAN0.sendMsgBuf(0x041, 0, 8, send41b); // 0x 81 7F 00 00 00 00 00 00 } //***** Fixed Coding for Jon Coe's Car ******* // Some experimentation showed that on the initial request for my car byte 2 was a 01 not a 02 // however all codes so far begin 06 7F for either car so this was used. // Similarly in the second message from the immobiliser bytes 1-4 change but byte 5 is always 01 on either vehicle // The negotiation happens during every start but the codes only seem to cycle whenever the battery // is disconnected if(buf[0] == 0x6 && buf[1] == 0x7F ) { // 0x 06 7F 01 00 00 00 00 32 //printhex(buf,8); // Transmit out received request on debug serial port - breaks timings on vehicle. CAN0.sendMsgBuf(0x041, 0, 8, send41a); // 0x041 = 65 } if(buf[0] == 0x8 && buf[5] == 0x1 ) { // 0x 08 94 29 BC 91 01 00 32 CAN0.sendMsgBuf(0x041, 0, 8, send41b); // 0x 81 7F 00 00 00 00 00 00 } */ // ********** JC 25/01/21 - Add Functionality to use immobiliser responses stored in EEPROM ********** //if(memcmp(buf, request_a, 8) == 0){ // Check first request matches stored pattern (difference = 0) if(buf[0] == request_a[0] && buf[1] == request_a[1] ) { CAN0.sendMsgBuf(0x041, 0, 8, response_a); // Send stored response to 0x041 = 65 } //if(memcmp(buf, request_b, 8) == 0) { // Check second request starts with "08" if(buf[0] == request_b[0] && buf[5] == request_b[5] ) { CAN0.sendMsgBuf(0x041, 0, 8, response_b); // Send second response - seems to always be 0x 81 7F 00 00 00 00 00 00 } } //Read wheel speeds to update Dash //if(ID == 1200) { //1201 Dec is 4b1 Hex - Wheel Speeds ----> check this address. Wheel speeds for dash 4B1 -> 201 if(ID == 0x4B0) { frontLeft = (buf[0] * 256) + buf[1] - 10000; frontRight = (buf[2] * 256) + buf[3] - 10000; rearLeft = (buf[4] * 256) + buf[5] - 10000; rearRight = (buf[6] * 256) + buf[7] - 10000; //Going to check front wheel speeds for any issues, ignoring the rears due to problems created by wheelspin if (frontLeft - frontRight > 500 || frontLeft - frontRight < -500) { //more than 5kph difference in wheel speed checkEngineMIL = 1; //light up engine warning light and set speed to zero vehicleSpeed = 0; } else { vehicleSpeed = (((frontLeft + frontRight) / 2) / 100); //Get average of front two wheels. } //Update timer count value with live speed for ODO //OCR1A = calcTimer1Count((frontLeft + frontRight) / 2); // delay in MS for ODO ODOus = calcMicrosecODO((frontLeft + frontRight) / 2); Serial.print("ODO Step : "); Serial.print(ODOus); Serial.println("us"); // Dump speed to serial for debug - this is just a cropped int. //Serial.println(vehicleSpeed); } // Decode for Megasquirt - JC /* This section matches the various fields used by Megasquirt * CAN send to decode for the RX8 cluster. Not all fields are * converted from the Megasquirt as most are not required. * The CAN start address for Megasquirt is the default 0x5F2 (1520) */ if(MSCAN == true){ if(ID == 0x5F0) { //1520 Dec is 5F0 Hex - Megasquirt Block 0 // Block 0 - Seconds, PW2, PW2, RPM, 2 bytes each engineRPM = (buf[6] * 256) + buf[7]; } if(ID == 0x5F2) { //1522 Dec is 5F2 Hex - Megasquirt Block 2 // Block 2 - Baro(kPa*10), MAP(kPa*10), MAT(degF*10), CLT(degF*10) // Edit map to set "normal" range on cluster (in degF) where needle stays centred // outside of the normal range needle will rapidly increase or decrease. int normMin = 140; // 60 degC int normMax = 220; // 104 degC engTemp = map((buf[6] * 256) + buf[7],normMin*10,normMax*10,110,150); } if(ID == 0x5F3) { //1523 Dec is 5F3 Hex - Megasquirt Block 3 // Block 3 - TPS (%*10), Batt (V*10), EGO1(Depricated on MS3), // EGO2(Depricated on MS3), 2 bytes each throttlePedal = ((buf[0] * 256) + buf[1]) / 10; } if(ID == 0x624) { //1572 Dec is 624 Hex - Megasquirt Block 52 // Block 54 - CANin_1(?),CANout_1, CANout_2, // Knock_ret (deg*10, 1 byte), Fuel flow (cc/min*10, 2 byte), // Fuel Consumption(l/km, 2 byte) // First 3 bytes appear wrong in the Megasquirt CAN documentation // testing shows byte 1 is CANout_1 not CANin_2 // byte 2 is CANout_2 byte CANout_1 = buf[1]; byte CANout_2 = buf[2]; // Read Check engine light from Megasquirt checkEngineMIL = bitRead(CANout_1,0); // Blink traction control light - Disabled due to ABS unit //etcActiveBL = bitRead(CANout_1,1); // Read Oil Pressure light from Megasquirt oilPressureMIL = bitRead(CANout_1,2); // Also set cluster "gauge" to match warning light if(oilPressureMIL == 1){ oilPressure = 0; } else{ oilPressure = 1; } } } // Close MSCAN mode check } // Close CAN message receive processing /* Reading throttle sensor for electric drive control - //Throttle Pedal Work val = analogRead(analogPin); // read the input pin //See Set Defaults Method for Calculations if (val < 110 || val > 960) { // If input is less than 0.5v or higher than 4.5v something is wrong so NO THROTTLE val = 0; } base = val - lowPedal; if (base < 0) { base = 0; } output = base * convertThrottle; if (output > 960) { output = 960; } throttlePedal = (100 / 960) * output; analogWrite(outputPin,(output/4)); */ }
Also for completeness here is the alternate section which handles the immobiliser with random data in response_a but otherwise is much the same as the other version. The immobiliser module hashes this with the RFID code off the key and passes it back and request_b just gives the immobiliser the OK. The only issue you might get is if other versions of the car use a different packet format because both my methods rely on matching the packet structure to pick it out of the data stream.
//******* JC 25/01/21 Immobiliser Compatibility Mods ******* // Check if First Run Immobiliser Code Scanning is Enabled if (digitalRead(Set_Immobiliser) == 0){ immobiliserCodeSet(); } // Check if stored values exist in EEPROM and if not setup from program defaults if (EEPROM.read(0) == 0 && EEPROM.read(1) == 0){ writeByteArrayIntoEEPROM(0, request_a, 16); writeByteArrayIntoEEPROM(16, response_a, 16); writeByteArrayIntoEEPROM(32, request_b, 16); } // Pull Immobiliser codes from EEPROM //delay(5000); // Delay used for serial diagnostics to give the monitor time to connect before sending data readByteArrayFromEEPROM(0, request_a, 8); Serial.print("Request A from EEPROM : "); printhex(request_a,8); //readByteArrayFromEEPROM(16, response_a, 8); response_a[1] = random(255); response_a[2] = random(255); response_a[3] = random(255); response_a[4] = random(255); Serial.print("Response A - Random Filler : "); printhex(response_a,8); readByteArrayFromEEPROM(32, request_b, 8); Serial.print("Request B from EEPROM : "); printhex(request_b,8); }
The only other thing you need to do for the random data is generate some random data, I did this by using a floating input as a random seed. The data doesn’t actually need to be random and you can just set fixed values for response_a bytes 1-4 or whatever as long as the rest of the response structure is correct.
void setup() { //pinMode(A1, INPUT); randomSeed(analogRead(A1)); // A5 Not actually connected on Leonardo CAN - Used as seed value
As ever, your mileage may vary and you get no guarantee from me!
Jon,
Thank you for posting this! My 2009 RX-8 just lost its transmission so I’m about ready to start swapping. I’ll have to load this code up and see how much of it works unmodified on the Series 2.
Hi there! I’ve been following this project intermitently for the past year or so, and i’m quite sure i’ve been able to use your code to make an RX8 cluster work on a PC. However, this last piece of code is giving me lots of trouble to even compile, and even when it does, nothing happens, disabling all needles and lighting every single warning light on the cluster.
I’m quite sure i’m doing something horribly wrong but can’t really see what.
Hi, I’m always interested to hear from people following this project. Sadly everything else tends to get in the way, you’ve probably seen elsewhere on the blog I’ve got construction work going on which has hampered progress a bit but there are still things ongoing.
This latest branch of code has quite a few options in it for different configurations, specifically I added support for the Hobbytronics Leonardo-CAN device I mentioned in a previous write up. This is a very good module but unfortunately isn’t cross compatible with the standard CAN shields due to different pins on the arduino to connect the CAN chip. Near the top you will find a line that says “comment out to select correct hardware. In the code I posted the LEO_CAN mode is set, if you’re not using that module this needs to be swapped to the more common “Seeed” type, which also covers a lot of similar clone boards like the Sparkfun one and loads of ebay ones. The line without the two forward slashes is the one it will use. If neither works check the Arduino pin numbers your hardware uses for the CAN_CS and CAN_INT lines to the MCP2515 (pins CS and INT respectively) and this should get you going. Further to this there’s a new completely hardware I’ve developed that I’ll be adding details of when I get chance which basically has two independent CAN interfaces on one small Leonardo board along with various other options to do clever things. It’s intended for processing/intercepting live data using an expanded version of this code for engine swaps and similar applications. So that’s one of the things that I’ve been doing that’s not up yet. I’ve got the first prototype working here so keep an eye out for that!
The other issue you might be having is similar, there are options below the hardware selection to use either of two CAN libraries, there’s a Seeed Studios version and the original version I’d used in previous code. Both are very similar and work with the same hardware but internally have slightly different functionality – this option dates from when I was doing some development work. It seems when I posted the code I was still working in Seeed one and if you don’t have this installed it wont work right. Either change back to the standard one if you still have it or download the latest Seeed Arduino CAN library and you should be ok. A lengthy search around my PC suggests the original library I used may have actually been an archaic version of the seeed studios library so I suggest just use the current version with the configuration as the “seeed” option and hopefully it’ll compile fine.
Good luck, if you’re still having problems I’m happy to try to help!
Jon
Hello
Great post, i would like know if you know the address of the right button of tripmeter ?
Regards
Hi,
As far as I’ve been able to tell both the buttons on the cluster only have any effect within the cluster itself. The section about incrementing the trip meter via CAN increments both trip A and B on the cluster but the button will still select/reset the trip as normal even if it’s not getting CAN data. The one on the right which changes the displayed unit (kM/H or MPH) again works fine as it just changes what’s displayed not the actual stored data in the cluster. The cluster does output some data via CAN on ID 0x430 but it doesn’t seem to report whether or not either of the switches is pressed as the CAN data it sends doesn’t appear to change unfortunately.
Jon
Hi to get the read/store immobiliser function working what is the specified pin that needs to be grounded on the sv1 connector of the leonardo board.
I have everything working other than a flashing immobiliser light
Regards Dan
Hi Dan,
It’s been a while since I’ve looked at it but glancing at the code it’s declared in the definitions right at the start as ‘Set_Immobiliser’ . For the Leonardo CAN-BUS (the hobbytronics board) you use the LEO_CAN hardware definition which uses Arduino pin 4 (D4) which is broken out on pin 18 of SV1 on that board. That’s the very end pin on the outer row on the end with the LED, D4 should be silkscreened onto the board. It’s the one diagonally opposite the GND pin which I think I did to make it easier to find when fumbling round under a dashboard. If you cant get it working give me a yell as the immobiliser code hasn’t had a huge sample size for testing but it should work from what I’ve seen so far!
Hope this helps, I’m always interested to hear what this code is being used for so if you want to share what your project is it’d be great to hear.
Jon
Hi Jon
Thanks for your reply and all your hard work on this project enabling people to use this code to overcome simalar problems
I managed to link Ov to D4 as detailed above to grab the immobiliser code and sure enough it works a treat.. Which is great… So thanks for that
Project is 56 plate Rx8 with chevy aluminium 5.3 V8, ss2 camshaft, single turbo 72mm, running an auto 4L80e transmission, slowly made built and put together by myself over the last 3 years
She’s not running yet but will be in a couple of months.
Cheers Dan
Hi Dan,
Great to hear you got it all working, it’s always nice to hear! Your project sounds pretty epic, great work!
Jon
Hi, been trying to understand how I could use this code in order to have a functional rx8 with an AJ30 swap.
As far as I understand LEO+Shield with this code would act as a bus translator from aj30 to rx8 cluster? there are mentions of megasquirt in the code…? please explain.
Hi,
Apologies, been really busy lately. So the short version is the Leo/canbus combination was built primarily to control the dash and various subsystems of the car (ABS/PAS/etc) without the factory Mazda ECU in the car to avoid having any warning lights, and in its most basic configuration that’s what it does however clearly this alone isn’t terribly useful because for most engine swaps you’d still want to be able to show useful information on the standard instruments, for example engine RPM. So the code also makes it relatively easy to write in a value you want to show in any addressable field or make a light turn on on command, with minor changes you can link physical inputs on the leonardo to the Canbus dash so you can do things like connect an oil pressure switch and have it state shown on the dash warning light.
So to answer your question, it was intended in my application to be used with an AJ30 engine but my plan rapidly progressed to turbocharge it so there was no way the standard ECU would be able to run it afterwards and so standalone engine management would be required. Long ago I decided to go for the megasquirt 3X as the cheapest way to get all the features I needed. Looking at it again many years later there are probably better options but times move on. As a result I started building a section of code that would read canbus data from the canbus interface on the Megasquirt such that it could be remapped/scaled/etc as if its coming from the original RX8 ecu and sent to the dash over the other interface. Now the live engine RPM from the megasquirt can be shown on the RPM gauge in the cluster. Obviously the same idea can be used to get any data you wanted off the megasquirt ECU or even a new interpreter built on the same logic to read from any other ECU you might want with a canbus interface. If you wanted to get really creative you probably could even pull data from a USB OBD adapter from factory ecu’s and all sorts of things or use the leonardo as a canbus IO expander for a megasquirt or other ECU.
Hopefully that all makes sense?
Jon