How to: CAN bus from the EDC15 ALH ECM to an Arduino (and beyond)

mcneil

Veteran Member
Joined
Jun 23, 2010
Location
Pasadena, CA
TDI
2010 Golf TDI 4dr, 2001 Jetta TDI, Jeep TDI project
There's probably a million ways to do this but this is probably one of the easiest if you're like me and not a software programmer. This thread contains all the Arduino code and other resources I used to make it happen.

Here's my example: a 2000 Jeep TJ instrument cluster displaying RPM from the ALH ECM.
https://picasaweb.google.com/lh/photo/L9JY-zJcovIxaHFt7rLKndMTjNZETYmyPJy0liipFm0?feat=directlink

So there's the ECM, full of all sorts of data that we'd love to have looking up at us from the instrument cluster. Tach, water temp, boost, etc, it's all there as a digital value in the cluster.
There's three ways to get data out of the ECM:
1. OBD II over K-line
Standard, but slow. You have to ask for each piece of data using a standard set of OBD II codes.
2. VAG-com over K-line
How the VCDS tool talks to the ECM. I don't know the structure of these messages, but the speed over here is similar to logging with a VCDS.
3. CAN bus
Fastest, 500kbps. No lag. This is how the RPM signal gets from the ECM to the Instrument cluster in a factory ALH.

Connecting to the CAN bus you need a CAN transciever to read the messages from the bus and a microprocessor to get the message off the transciever and do something useful with them. I used the SeeedStudio CANshield and an Arduino Uno.

Also remember that the CAN bus at the OBD port doesn't offer the same features as the CAN bus between the ECM and Instrument cluster. Mk4s with the EDC15 had a gateway in the instrument cluster for the OBD port. So the gateway will filter messages coming to and from your transciever. By being a "bus", CAN networks aren't point to point but can be tapped in the middle. In my case, with no cluster, I just connected to the orange/black and orange/brown twisted pair coming off T121.

On the can bus, a typical message looks like this (but in binary)
640 65 42 28 14 42 0 35 41

The above string of numbers is one message recorded on the bus while idling. Going through each one of those numbers, we first have the CAN ID (640). My code looks for all messages with CAN ID = 640. I saw about half a dozen other CAN ID's on the bus, but I don't know what they do yet.

Next is the data - 8 bytes worth. It's described byte by byte, starting at zero because computer programmers start at zero. It took a frustated hour to remember that. Byte 0 in the above example message has a value of 65

Byte What it is
0 Don't know
1 Don't know, but suspect IQ or something like that
2 Don't know
3 RPM (1 byte = 64 RPM)
4 Don't know
5 TPS (0-255 where 255=floored)
6 Don't know
7 Don't know

This graph is how I figured out the bytes. This is the values of bytes 1, 3, and 5 as I did the little run in the above video.


So when a CAN ID 640 message appears on the bus, the Arduino applies a conversion factor and saves it to a variable named rpm_int.

Now the RPM is in the Arduino, we can display the value a couple ways.
1. Serial Monitor
This is also how I log data. Serial connections send it over USB to the laptop, and I log the data by pasting it to a text file. Good for science, not very useful when driving.
2. Display on an LCD screen
There's a ton of LCD screens on Sparkfun with Arduino libraries which you can display anything you want on. Going for an OEM look in my conversion, I wasn't interested in this option
3. Take control of you exisiting dash.

If your swap vehicle had CAN bus, you can use a second CAN shield to make your arduino a man-in-the-middle gateway. But CAN was only standard after 2008, up to that point it may or may not be there. Also, most of us are swapping the TDIs into older chassis anyway so let's assume you don't have a CAN dash.

Most gauges are air core. I used a Melexis 10407 air core gauge driver. This is a chip which makes the signals to move the needles on the gauges. You talk to it over a protocol called "SPI". Coincidently, SPI is the same way you talk to the CAN shield. Arduino has several good examples for SPI so I won't get into it deeply here.

Hooking up to the cluster is a lot of wiring because each big gauge is 4 wires, plus 2 wires for oil press, temp, etc. Eventually I'm going to print a PCB to do this with the Melexis 10407 on it. But for now it's a cardboard box with too many wires. It looks like this:



The function in the code which is probably most helpful to someone using the Melexis is the tachWrite. You give this function a value for RPM, and it does the calculations and communications to write it to the Melexis. Note that these values for quadrant and bytes per RPM are only valid for the TJ tachometer. You'll need to calibrate it for your own gauges.

I'll post the latest version of the code below, if you can make improvements and updates please feel free to do so and I'll include them in the latest.

Next steps for the code:
1. Fix the calibration issue with tachWrite (the blip at 2300 rpm)
2. Write the "speedoWrite" function which can take speed in MPH and send a needle position command to the Melexis
3. Write a function to control the 1-quadrant gauges
4. Decode the rest of the CAN ID's and Bytes, see if there's any juicy information there


Resources:

SeeedStudio CAN shield wiki
http://www.seeedstudio.com/wiki/CAN-BUS_Shield

Using a Mk6 cluster as a video game dashboard
http://www.seeedstudio.com/recipe/291-volkswagen-can-bus-gaming.html

Instructable on Jeep CAN bus hacking
http://www.instructables.com/id/Hack-your-vehicle-CAN-BUS-with-Arduino-and-Seeed-C/?ALLSTEPS



My pin configurations for the Arduino
Pin State Function To
0 Digital PD0
1 Digital PD1
2 Digital PD2 OUTPUT INT - CANBUS CAN Shield
3 Digital PD3
4 Digital PD4 OUTPUT RSTB Melexis - 15
5 Digital PD5
6 Digital PD6
7 Digital PD7 INPUT ERR Melexis 16
8 Digital PB0
9 Digital PB1 OUTPUT CS Melexis -11
10 Digital PB2 OUTPUT CS CAN Shield
11 Digital PB3 OUTPUT MOSI SPI Bus
12 Digital PB4 INPUT MISO SPI Bus
13 Digital PB5 OUTPUT SCLK SPI Bus/mlx 12
14 GND
 

mcneil

Veteran Member
Joined
Jun 23, 2010
Location
Pasadena, CA
TDI
2010 Golf TDI 4dr, 2001 Jetta TDI, Jeep TDI project
Latest Arduino Code version

/*
SPI Gauge Driver Chip Control (attempt 9)

This program controls a Melexis MLX10407 5 gauge air core
driver chip over a SPI interface with inputs recieved over CAN bus using
the SeeedStudio CAN shield v1.0

Hardware is configured as described on pg 9 of the 10407 datasheet

Version 8 runs a speedometer sweep and increments tach. Integer tach handling
is used and speedometer sweep is from earlier v3
tachWrite function first appears, and excess code is removed

CAN shield code from CANshield rec(loovee, 2014-6-13)
*/



#include <SPI.h>
#include "mcp_can.h"

// Constants for math and conversions
const int rpmPerQuad = 2700;
const int rpmOffset = 350 ;

// Constants for pin definition
const int slaveSelectPin = 9;
const int selectPinMISO=7;
const int selectPinMOSI=11;
const int clockPin=13;
const int ledPin=5;
const int resetPin=7;
// 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 = 10;

// Variables for math and conversion
byte testValueA=0b10010000;
byte testValueB=0b00000001;
byte maskSpeedo=0b10010000;
byte maskTach=0b10110000;
byte maskValueA=0b00001111;
byte maskValueB=0b11111100;
byte value=0;
byte quadSpeedo= 0b00000000;
byte quadTach= 0b00000000;
word wordTach = 0;
int speedo=500;
int increment = 1;
byte rpm_byte = 30; // tach in binary, 64.5 rpm/bit
int rpm_int = 1000; // tach in revolutions per minute
unsigned char len = 0;
unsigned char buf[8] = {65, 0, 0, 0, 0, 0, 30, 0};
unsigned int canId = 640;



MCP_CAN CAN(SPI_CS_PIN); // Set CS pin for CAN shield

void setup()
{
Serial.begin(115200);

START_INIT:

if(CAN_OK == CAN.begin(CAN_500KBPS)) // init can bus : baudrate = 500k
{
Serial.println("CAN BUS Shield init ok!");
}
else
{
Serial.println("CAN BUS Shield init fail");
Serial.println("Init CAN BUS Shield again");
delay(100);
//goto START_INIT;
}
// Initialize SPI bus for Melexis
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV32);
// set the pin modes
pinMode (slaveSelectPin, OUTPUT);
pinMode (selectPinMISO, INPUT);
pinMode (selectPinMOSI, OUTPUT);
pinMode (clockPin, OUTPUT);
pinMode (ledPin, OUTPUT);
pinMode (resetPin, OUTPUT);

// Set "at-rest" states for SS, clock and LED pins
digitalWrite(slaveSelectPin,LOW);
digitalWrite(clockPin, LOW);
digitalWrite(ledPin,LOW);
digitalWrite(resetPin,HIGH);
//wait
delay(1000);

// Perform a sweep of the tach to home the needle
for(int x = 8000; x >0; x--) {
tachWrite(x);
}
}

void loop(){


if(CAN_MSGAVAIL == CAN.checkReceive()) // check if data coming
{
CAN.readMsgBuf(&len, buf); // read data, len: data length, buf: data buf

canId = CAN.getCanId(); // store the CAN ID to a variable
if(canId == 640) // if the CAN ID is from the ECM
{
rpm_int = 64 * ( buf[3]); // Convert byte 3 to revs/minute
if(rpm_int<0){ rpm_int=0;} // don't allow negative rpms
// Print out a line to the serial port with the data for logging
Serial.print(rpm_int);
Serial.print("\t");
Serial.print(canId);
Serial.print("\t");

for(int i = 0; i<len; i++) // print the data
{
Serial.print(buf);
Serial.print("\t");
}
Serial.println();
}
}

// Sweep the speedo, because you might as well do something with the gauge
// until the VSS gets hooked up
if (quadSpeedo == 2 && speedo==1024) {
increment = -1;
}
if (quadSpeedo == 0 && speedo==100) {
increment = 1;
}
if (speedo == 1024 && increment > 0) {
speedo = 0;
quadSpeedo++;
}
if (speedo <= 0 && increment < 0) {
speedo = 1023;
quadSpeedo--;
}

// Write a value to the chip for Speedometer

testValueA=maskSpeedo | highByte((word(speedo) << 2 ));
testValueB=byte(lowByte(word(speedo) << 2) | quadSpeedo);
digitalGaugeWrite(testValueA, testValueB);

delayMicroseconds(800); // delay to keep the code from outrunning the Melexis
speedo= speedo + increment;


// Write rpm_int to the chip for Tach
tachWrite(rpm_int);
//rpm_int++;
}


int tachWrite(int rpm_int) { // takes a integer RPM value and sends to Melexis
wordTach = int(float((rpm_int+rpmOffset) % rpmPerQuad )/2.64);
quadTach = rpm_int / rpmPerQuad ;
wordTach = (wordTach << 2) | quadTach;
testValueA = maskTach | highByte(wordTach);
testValueB = lowByte(wordTach);
digitalGaugeWrite(testValueA, testValueB);
}

// Function to write to the Melexis over SPI for any two bytes
int digitalGaugeWrite(byte testValueA, byte testValueB) {
digitalWrite(slaveSelectPin,HIGH);
// wait one clock cycle
delayMicroseconds(2);
// transfer the first half of the two byte command
SPI.transfer(testValueA);
// transfer the second half
SPI.transfer(testValueB);
// Set the SS pin low to deactivate the chip
digitalWrite(slaveSelectPin,LOW);
}
/*********************************************************************************************************
END FILE
*********************************************************************************************************/
 

Vince Waldon

Top Post Dawg
Joined
Apr 25, 2009
Location
Edmonton AB Canada
TDI
2001 ALH Jetta, 2003 ALH Wagon, 2005 BEW Wagon
Very very cool... thanks for taking the time for such a detailed post.

Dunno what I'm gonna do with an Arduino under the hood...but now I can!
 

nztdi1

Veteran Member
Joined
Mar 29, 2015
Location
nz
TDI
golf mk4, touareg 2.5, octavia 2.0 8v, boats x2
Thanks for posting up this great info mcneil! I have transplanted a tdi into a '99 xj with guages supposedly run through 'ccd bus' i was intending to use as many jeep sensors as i could and retain the jeep ecm to get the guages working, seems like this method might be worth looking into instead.(i currently know next to nothing about can-bus etc) do you think i would have trouble getting tach/water temp/CEL working?
 

mcneil

Veteran Member
Joined
Jun 23, 2010
Location
Pasadena, CA
TDI
2010 Golf TDI 4dr, 2001 Jetta TDI, Jeep TDI project
Thanks for posting up this great info mcneil! I have transplanted a tdi into a '99 xj with guages supposedly run through 'ccd bus' i was intending to use as many jeep sensors as i could and retain the jeep ecm to get the guages working, seems like this method might be worth looking into instead.(i currently know next to nothing about can-bus etc) do you think i would have trouble getting tach/water temp/CEL working?
My 2000 TJ has CCD bus, unfortunately I couldn't figure out a way to make it compatible with CAN. It's a differential bus like CAN but the voltage levels are different, so you'd have to find a CCD transceiver.

You might be able to do something where you read RPM off the CAN bus, and have the Arduino send a PWM signal to the Jeep ECM as if it were the tach pickup.
 

mcneil

Veteran Member
Joined
Jun 23, 2010
Location
Pasadena, CA
TDI
2010 Golf TDI 4dr, 2001 Jetta TDI, Jeep TDI project
More information:

CAN ID 904 appears to report VSS.
Structure of the message is just 3 bytes.
Byte 0 and Byte 2 appear to be proportional to vehicle speed, but not exactly.
A byte value of 255 corrosponded to about 180 kph speed.

Edit: this doesn't appear to be consistent.
904 is some kind of resetting timer.
CAN ID 648 actually has the VSS on byte 3
 
Last edited:

xerootg

Veteran Member
Joined
Oct 20, 2015
Location
North Bend, WA
TDI
Mk7 Golf Sportwagen, BHW Jeep XJ
I would like to join in on the fun! I have been writing away today, I took your code and added to it:

http://pastebin.com/LWE9wTLV

This compiles but I can't test it with the little hardware I have. Once I have more hardware I should be able to really dig into the code you have provided thus far.

Not sure what kind of lag I are talking about with an arduino by adding this much (24 different values polled then retrieved and displayed accordingly), but I added all values available in an XJ gauge cluster.

I also implemented the check gauges light boolean, which can be used to run the check gauges light when the time comes.

I replaced my odometer with a 1.3" OLED display, been messing around with that since I am still waiting for a couple melexis to come china to run the gauges.



I am working on getting a method written to use the EEPROM to store the odometer in a wear spreading sort of way. I should have that for you to try out in a bit. I'm not sure how to measure distance though... but atleast the code will be there.

I am replacing all of the incandescent lights with some addressable LEDs I found at a local electronics store, so I included alot of what is needed to run those as far as a framework is concerned.

If you like what I have added as far as code, maybe we should get this stuff into a github repository.
 

mcneil

Veteran Member
Joined
Jun 23, 2010
Location
Pasadena, CA
TDI
2010 Golf TDI 4dr, 2001 Jetta TDI, Jeep TDI project
Code runs good on my system, no issues with latency. It can still move the needle faster than the ALH can change RPM.

I still need to fix a scaling problem with the Melexis at ~2200 rpm that causes the needle to blip to zero. Also, shut-down is awkward. The CAN data stops when the ignition shuts off, but the tach stays at the last report position (idle). Probably should have it zero values if CAN data stops.

Github would probably be a good idea.

I had another idea as well - I started laying out a PCB that would replace the one in the stock cluster. A simple 2-layer that would mount up to the back of the gauge dials, hold the Melexis 10407, have LEDs, and connect up to an arduino header.
 

xerootg

Veteran Member
Joined
Oct 20, 2015
Location
North Bend, WA
TDI
Mk7 Golf Sportwagen, BHW Jeep XJ
Code runs good on my system, no issues with latency. It can still move the needle faster than the ALH can change RPM.
That will likely be fixed when we start reading other IDs and reading analog pins.

shut-down is awkward. The CAN data stops when the ignition shuts off, but the tach stays at the last report position (idle). Probably should have it zero values if CAN data stops.
I have plans for this, as I have a boolean to track if the engine is running. all we need is a method to zero the gauges and an if in the read can method to putIsRunning(false); and on the next gauge update the needles will zero. I used setters and getters for events like this.

I would like to add some features the factory forgot, like having the odometer display with the door open and the engine not running and the ability to disabled the door buzzer. I started working on some code to use the odometer reset as an interrupt to change whats on my LCD, I'm thinking that a 1 button interface is doable.

Another idea is I also have my cruise buttons I need to convert the resistor ladder to the pulses that the ECU expects. I could likely interface those into some sort of interface for the gauges or even use them as controls for my head unit.

Github would probably be a good idea.
When I get home tonight I'll see about setting one up. Should be good to keep everything sync'd.

I had another idea as well - I started laying out a PCB that would replace the one in the stock cluster. A simple 2-layer that would mount up to the back of the gauge dials, hold the Melexis 10407, have LEDs, and connect up to an arduino header.
Sounds great! I started drawing one up with my butchered OEM PCB, but those sort of layouts are not my strong point. That sort of thing would be good to track with the code. I do plan on using an Arduino mega 2560 so I can keep all of the analog inputs digital. The LEDs I picked out only need one digital pin. The LCD I picked up use 5, but two are shared with the melexis (SPI). I am fairly certain Cherokees have different pinouts, but I am fairly certain that's not a big deal.

Do you have the 90 degree gauges figured out? I couldn't decide if those are sin/cos style like the speedo or tach or what the deal is with those...
 
Last edited:

vanbcguy

Veteran Member
Joined
Feb 22, 2013
Location
Vancouver, BC
TDI
'93 Passat - AHU mTDI with GTB1756VK
Check out Eagle to do PCB designs. Only gotcha is the "free" licence has a maximum board size limitation. Plenty large for Arduino shields but it probably won't let you make a cluster sized PCB design.
 

Fix_Until_Broke

Top Post Dawg
Joined
Aug 8, 2004
Location
Menomonee Falls, Wisconsin, USA
TDI
03 Jetta, 03 TT TDI
mcneil - cool stuff and good work

I've been struggling to be able to record ECU data with my DAQ system and never realized that the EDC15/ALH used CAN communication. I can record multiple CAN networks and be time synchronous with analog and digital channels but decoding the messages is the more challenging part, but it looks like you've got a start on it.

Do you know of anywhere there's a list of these CAN messages/ID's and their respective values? A .dbc or .ncd file would be absolutely awesome, but I'm assuming these are hard to come by unless you work for VW R&D.

I'm so excited that there's a live 500k CAN bus that I can read on my car. Do you have any idea what busload they run (guessing it's less than 66% as that's typically the max loading that SAE allows)?

Worst case is that I can record everything broadcast and then start decoding it bit by bit (pun intended :))

Does this follow SAE 11898 by chance? Many of those messages are standardized, but they don't have to be if they're not generally available. At that point you can do anything you want in a message instead of the typical standard 8 or 16 bits per channel. At that point, it's very difficult to decode unless you know what you're looking for.
 

xerootg

Veteran Member
Joined
Oct 20, 2015
Location
North Bend, WA
TDI
Mk7 Golf Sportwagen, BHW Jeep XJ
The damos for my EDC16U31 discuss J1979, so its possible it is capable of using it. I couldn't find a damos for McNeil's EDC15, so I can't be certain. What I did find, was some documentation on an EDC15 in an Audi in German. The addresses are templated.

Motor: x0xH / x8xH
Motor-Peripherie: x1xH
Kombi: x2xH
Getriebe: x4xH
ACC: x6xH
ABS: xAxH
ABS-Peripherie: xCxH

Some of the known addresses are:

0C0H Lenkwinkel (RB-Sensor)
0C2H Lenkwinkel (ITT-Sensor für ITT-FDR)
0C4H Lenkwinkel (ITT-Sensor für RB-FDR)
0C8H (Gierraten / Querbeschleunigung)
100H MSG1 (V6 TDI)
112H PSG1 (V6 TDI)
1A0H Bremse 1
280H Motor 1
288H Motor 2
2A0H Bremse 4
320H Kombi 1
380H Motor 3
388H Motor 4
420H Kombi 2
440H Getriebe 1
4A0H Bremse 3
480H Motor 5
500H MSG2 (V6 TDI)
512H PSG2 (V6 TDI)
520H Kombi 3
580H Motor Flexia
5A0H Bremse 2
7C0H Lenkwinkel-Konfigurierungskennung (RB)
7C2H Lenkwinkel-Initialisierungskennung (ITT)

I do have enough CAN experience to really be able to identify what standard this follows. Does anyone else?
 

xerootg

Veteran Member
Joined
Oct 20, 2015
Location
North Bend, WA
TDI
Mk7 Golf Sportwagen, BHW Jeep XJ
My German is rusty, but after digging through canhack.de and McNeil's logs, I have a pretty solid diagram. I am missing some details, like ECU provided coolant temp, MIL, and glowplugs, but I am getting there. We will likely have to emulate a coolant temp from the cluster for the ECU to be happy.

It would be nice if I could get some engine CAN dumps from other users out there, along with a description of the drive, temp, and distance.

I am fairly certain I will be able to pull boost via TP2.0, I found a project on github that did just that BUT there are rumors that 0x588 has the boost values in some ECU software versions.



FROM ECU
coolant temp =-48+(coolant*75)
boost = pressure, decimal, factor 10
acc% = decimal(petal)*0.4
engine rpms = (dec(rpms2)+256*dec(rpms1))/4
speed (kph?) = (vss)
Fuel Consumption = fuel_consumption (accumulated?)
petals* = 41(0x29)=no petals, 40(0x28)=acc depressed.

*There are different values for this, McNeil's was different than an Audi A4, 0x01=no acc, 0x08=no clutch, 0x09 means no petals

FROM CLUSTER
Fuel Volume (L) = fuel_level&0x7f, fuel_level&0x80==0x80 = low warning
Outside Temp (Debounced) = ((temp_ext_delay-100)/2)
Outside Temp = ((temp_ext-100)/2)
Coolant Temp = (coolant_temp-64)*.75

I am hoping to get this into the arduino sketch over the next week, keep an eye on the github link above.
 

xerootg

Veteran Member
Joined
Oct 20, 2015
Location
North Bend, WA
TDI
Mk7 Golf Sportwagen, BHW Jeep XJ
OK guys... I found documentation for ALL CAN BUS traffic on VAG EDC15+ equipped vehicles. I used WinOLS to atleast confirm that the IDs in this chart exist in a couple EDC16U31 based vehicles.

Here is a link to my spreadsheet. :cool:



Now that I have solidified the math and have all of the values needed to run a dash, its time for me to actually write the code I keep saying I will do. I like the idea of a stock looking dash like McNeil is working on, but I also found a blank dashboard insert for 97-2006 Jeeps that I am tempted to put generic VDO gauges into and drive with PWM off an arduino just because the Viewline Onyx gauges with a black bezel look so pretty.

I did confirm that the EDC16U31 tune for a 2004 Jetta (BEW) is looking at the coolant temperature from ID 0x420 for some of its calculations (using WinOLS and an a2l), so I will need to get that info onto the CAN BUS. I am sure that it can be "tuned out" or that it might run fine without it, but it just doesn't seem right to ignore something like that when I have options.

Since my swap isn't even really close to anything tangible and I can't seem to get my sensor bench working it would be really nice if I could get someone to dump a drive around town off their engine bus. It would make testing much easier. If someone needs help getting an Arduino or Raspberry Pi set up to make such a dump I can help.
 
Last edited:

tekki

New member
Joined
Dec 16, 2012
Location
FINLAND
TDI
Octavia 1U5 ASV, 1J1 4MOT ASZ
http://kols.free.fr/TDIclub/Funktionsbeschreibung EDC15+ P127-PEA.pdf

CAN messages, page 491 onwards.
I have some CAN dumps from 1.9TDI PD engine, a great free software for can recording, replaying and analyzing is Busmaster: https://rbei-etas.github.io/busmaster/ supporting a number of CAN adapters. I have used a PEAK USB CAN adapter. Busmaster also supports "database" of messages, so you can just tell it every bits/bytes and their factor and it will present values just you described. Helps to understand the messages a lot, rather than just rolling 8-bit hex values.
 
Top