Digital Speedometer To Car's Instrument Cluster Via CAN Bus
Có thể bạn quan tâm
Embed the widget on your own site
Add the following snippet to your HTML:

Implementing Arduino CAN bus shield and digital speedometer to car's LCD-display in instrument cluster.
Read up about this project on 
Digital Speedometer to Car's Instrument Cluster via CAN Bus
Jussi Ristiniemi34 40,852
Jussi RistiniemiPublished January 19, 2021 © GPL3+Digital Speedometer to Car's Instrument Cluster via CAN BusImplementing Arduino CAN bus shield and digital speedometer to car's LCD-display in instrument cluster.
IntermediateFull instructions providedOver 2 days40,852Things used in this project
Hardware components | |||||
![]() |
| × | 1 |
| |
| × | 1 | |||
| × | 1 | |||
![]() |
| × | 1 | ||
![]() |
| × | 1 | ||
![]() |
| × | 1 | ||
![]() |
| × | 2 | ||
![]() |
| × | 2 | ||
![]() |
| × | 2 | ||
| × | 1 | |||
Software apps and online services | |||||
![]() |
| ||||
![]() |
| ||||
Story
Start of the Project
It all started in the summer of 2020, when I bought an Audi TT 2002. Unlike my previous cars, it didn’t have a digital speedometer in the instrument cluster’s little LCD-display (Driver Information System DIS in Audi). Due to the lack of digital speedometer, I decided to implement it myself. To cut a long story short, I thought I could take advantage of existing GitHub-project [1] and wrote the code for that setup first. Though I soon realized the setup used in the existing project was different to mine, so I had to start it all over.
Basic Idea
Audi TT's DIS-display shows the radio station or the CD-track in the upmost third of the display. My plan was to replace the radio info with digital speedometer. From the previously mentioned project I found out that the radio sends information to the instrument cluster via three one-way data wires. When I pulled my radio out, I found out that my radio didn't use these three wires, but CAN bus instead.
Audi radios without CAN bus (-2001 )
- one-way data connection between the radio and the instrument cluster
- three wires for data transfer (DATA, CLOCK, ENABLE) (I thought my car had these)
Audi radios with CAN bus (2002-)
- two-way data connection (Half-duplex) between the radio and the instrument cluster implemented with CAN bus [2]
- data transmission rate 100 kbps (Infotainment CAN) [3][4]
- two wires for data transfer (CAN-High, CAN-Low) (My car had these)
Reading vehicle speed data
Before I tapped into CAN bus with Arduino, my plan was to acquire the vehicle speed signal from the radio's GALA-wire (Graduated Audio Level Adjustment). GALA increases radio volume automatically according to vehicle speed. Since my car has CAN bus, there is no need for GALA-wire and the instrument cluster sends vehicle speed data on the bus every 200 ms. I'm used these CAN-messages in my digital speedometer solution.
Designing Fault Tolerant CAN-shield for Arduino
After doing a little research on the CAN bus (especially in VW-group cars), I found out that the bus used in my car was a so called Fault Tolerant CAN bus [4]. For me it meant that I couldn't use off-the-shelf CAN-shields for Arduino.
I decided to design a circuit board for the CAN-shield and integrate my Arduino Nano into it. Main components needed for the CAN-shield were
- MCP2515 Stand-Alone CAN Controller with SPI Interface (Easy to communicate with Arduino via SPI)
- TJA1055 Enhanced fault-tolerant CAN transceiver
- 16 MHz Crystal Oscillator
- resistors, capacitors and screw terminal block
- circuit board
TJA-1055T Application Hints [5] -document was useful when I dimensioned resistors and capacitors. I used Arduino CAN Tutorial [6] when I drew the connections between Arduino and MCP2515.
I designed the circuit board in Autodesk Eagle. I didn't pay too much attention to the circuit board design specifics, because I knew that manufacturing and shipping of the board would take its own time. I ordered the printed circuit board from Itead.cc.
Circuit Board Design in Autodesk Eagle Final Printed Circuit Board Final ProductReceiving and sending CAN-frames
I used this great MCP2515 library for Arduino, that allowed me to easily send and receive frames on the CAN bus. Using the library was very straightforward, since it was so well implemented. In the following piece of code are examples of receiving and sending CAN frames.
// Receivestruct can_frame canMsg;void loop() { if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) { if(canMsg.can_id == 0x351){ // ID for speed information on CAN-Bus lv = canMsg.data[1]; uv = canMsg.data[2] velocity = ((uv<<8)+lv-1)/190; } }}// Send struct can_frame canMsg2;const uint8_t SPACE = 0x20;const uint8_t KMH_MESSAGE [8] = {SPACE, SPACE, 0x4b, 0x4d, 0x2f, 0x48, SPACE, SPACE}; //ASCII for ' KM/H 'void setup() { canMsg2.can_id = 0x263; // Message ID for the second row of the DIS canMsg2.can_dlc = 8; // Length 8 bytes for(int j=0; j<8; j++){ canMsg2.data[j] = KMH_MESSAGE[j]; }void loop() { mcp2515.sendMessage(&canMsg2); delay(40); // The car radio sends it own radio station data every 0,8s to // the instrument cluster, so I have to send my data with much // higher rate, so the display won't start to blink}CAN IDs
I found Audi CAN bus message identifier list on Canhack.de [7] and I found the IDs for the first and second text line of the DIS. I also discovered the ID for vehicle speed information on txboard.de [8].
TestedCAN IDs:
- 0x261 first DIS text line (length: 8 bytes; form: ASCII)
- 0x263 second DIS text line (length: 8 bytes; form: ASCII)
- 0x635 radio illumination (length: 3 bytes; from left to right: radio display backlight, radio buttons backlight, (?); (dimmest 0x0 - brightest 0x64)
CANID0x351 (8 bytes from left to right):
- 1. Ignition: 0x8 Ignition on
- 2.LV Vehicle speed lower value ("Once the engine is running and the car is stationary, changes the lower value to 1.")
- 3.UV Vehicle speed upper value
- 4. Unknown
- 5. Unknown
- 6. Outside temperature 1: (Decimal value)/2 - 40 Celsius
- 7. Outside temperature 2: (Decimal value)/2 - 40 Celsius (two different outside temperature sensors?)
- 8. Unknown
Vehicle speed calculation:
speed in km/h = ((UV << 8) + LV - 1) / 200
Message with ID 0x351 captured on the CAN bus: 8 0 0 0 0 60 60 0
During the capture, car had ignition on, and engine wasn't running. CAN frame suggests that ignition was on, temperature in my garage was 8 degrees Celsius and vehicle speed was zero, which seems about right.
Timeline
- 1/19/2021: I haven't been able to test my speedometer in practice, because it's winter and I only drive the car during summer. Luckily, the hardest part is over and I have the final product in my hands. The rest of this project is just programming and testing the speedometer in practice.
- 3/8/2021: The device is now installed under the dashboard and it's ready for testing and further development.
- 3/27/2021: I took the car for a spin and after I did some modifications to the code, the speedometer started working properly.
Sources
I used the following information from the previously mentioned GitHub-project [1] in my GALA Speedometer code:
- message length fixed 18-bytes
- last byte is checksum
- way to calculate the checksum
[1] GitHub: derpston/Audi-radio-DIS-reader
[2] Volkspage: VW Self Study Programme 186
[3] Volkspage: VW Self Study Programme 238
[4] Volkspage: VW Self Study Programme 269
[5] TJA-1055T Application Hints
[6] circuitdigest.com: Arduino CAN Tutorial
[7] canhack.de: Audi RNS-E CAN Aufschlüsselung
[8] tx-board.de: ID 0x351
Author
Jussi Ristiniemi
Student at Tampere University, Finland
2nd year in Electrical Engineering
Read moreSchematics
Board

Schematics

Code
- Can Bus Speedometer
- GALA Speedometer
Can Bus Speedometer
ArduinoCode for the setup with CAN bus compatible radio (Chorus)#include <SPI.h> #include <mcp2515.h> struct can_frame canMsg, canMsg1, canMsg2; int velocity,lv,uv = 0; const int INTERVAL = 800; // ms const int REFRESH_RATE = 20; // ms bool init_state = true; const uint8_t ASCII [10] = {0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39}; // ASCII for numbers 0..9 const uint8_t SPACE = 0x20; const uint8_t KMH_MESSAGE [8] = {SPACE, SPACE, 0xB, 0xD, 0x2f, 0x8, SPACE, SPACE}; //ASCII for ' km/h ' const uint8_t AUDI_TTe [8] = {65, 117, 4, 9, 32, 84, 84, 5}; // "Audi TTe" const uint8_t QUATTRO [8] = {113, 117, 0x01, 116, 116, 114, 0xF, 32}; // "quattro" const uint32_t MASK = 0xD440000; // Mask for id 0x351 = 0b 0110 1010 00 1 <=> 0xD440000 = 0b 0110 1010 00 1 0000 0000 0000 0000 00 . Extended from 11 to 29 bits const int SPEED_ID = 0x351; MCP2515 mcp2515(10); void setup() { canMsg1.can_id = 0x261; // Message ID for the first row of the DIS canMsg1.can_dlc = 8; canMsg2.can_id = 0x263; // Message ID for the second row of the DIS canMsg2.can_dlc = 8; for(int j=0; j<8; j++){ canMsg2.data[j] = KMH_MESSAGE[j]; } mcp2515.setFilterMask(0, 0, MASK); mcp2515.setFilter(0, 0, MASK); mcp2515.setBitrate(CAN_100KBPS, MCP_16MHZ); mcp2515.setNormalMode(); } void clear(){ for(int i=0; i<8; i++){ canMsg1.data[i] = SPACE; // Initialising the speed message (first row of canMsg2.data[i] = KMH_MESSAGE[i];; // DIS) with SPACES } } bool is_received(int id){ if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) { if(canMsg.can_id == id){ return true; } return false; } } void audi_tt(){ for(int i=0; i<8; i++){ canMsg1.data[i] = AUDI_TTe[i]; canMsg2.data[i] = QUATTRO[i]; } for(int j=0; j<3000/REFRESH_RATE;j++){ mcp2515.sendMessage(&canMsg1); mcp2515.sendMessage(&canMsg2); delay(REFRESH_RATE); } } void loop() { if(init_state){ audi_tt(); init_state = false; clear(); } if (is_received(SPEED_ID)) { clear(); lv = canMsg.data[1]; uv = canMsg.data[2]; velocity = ((uv<<8)+lv-1)/200; velocity = round(velocity); if(velocity < 10){ canMsg1.data[2] = ASCII[velocity]; } else if(velocity < 100){ canMsg1.data[2] = ASCII[velocity/10]; // Splitting integer to single digits canMsg1.data[3] = ASCII[velocity%10]; } else{ canMsg1.data[2] = ASCII[velocity/100]; canMsg1.data[3] = ASCII[(velocity/10)%10]; canMsg1.data[4] = ASCII[velocity%10]; } int i = 0; while(true){ if(i >= INTERVAL){ return; } else if(i % REFRESH_RATE == 0){ mcp2515.sendMessage(&canMsg1); mcp2515.sendMessage(&canMsg2); } i++; delay(1); } } // The car radio sends it own radio station data every 0,8s to // the instrument cluster, so I have to send my data with much // higher rate, so the display won't start to blink }GALA Speedometer
ArduinoCode for the setup with Concert-radio/* Digital speedometer to Audi Driver Information System Reads vehicle speed from Audi Concert Gala-wire ( Sends information to instrument cluster via Audi concert pins) */ #define pulse_ip 2 #define enable 6 #define sck 7 #define sda 8 unsigned long ontime,offtime, period; float freq = 0; int final_speed = 0; float speed_ = 0; float circumference = 0.63719; // in meters uint8_t header = 0xf0; uint8_t command_byte = 0x1c; uint8_t message [18], checksum; // numbers from 0 to 9 in ASCII-form uint8_t ASCII [10] = {0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39}; uint8_t KMH [5] = {0x20,0x4B,0x4D, 0x2F,0x48}; // ASCII for: ' km/h' uint8_t SPACE = 0x20; // ASCII for: ' ' // the setup function runs once when you press reset or power the board void setup() { pinMode(pulse_ip, INPUT); // pin for vehicle speed signal (GALA) pinMode(enable, OUTPUT); pinMode(sck, OUTPUT); pinMode(sda, OUTPUT); } // the loop function runs over and over again forever void loop() { ontime = pulseIn(pulse_ip,HIGH); offtime = pulseIn(pulse_ip,LOW); period = ontime+offtime; freq = 1000000.0/(period*2); // 2 periods per one wheel rotation speed_ = freq*circumference*3.6; speed_ = round(speed_); final_speed = speed_; message[0] = header; message[16] = command_byte; message[1] = SPACE; // For alignment message[2] = SPACE; if(final_speed < 10){ message[3] = ASCII[final_speed]; message[4] = SPACE; message[5] = SPACE; } else if(final_speed < 100){ message[3] = ASCII[final_speed / 10]; message[4] = ASCII[final_speed % 10]; message[5] = SPACE; } else{ message[3] = ASCII[final_speed / 100]; message[4] = ASCII[final_speed / 10]; message[5] = ASCII[final_speed % 10]; } for(int i = 6; i < 11; i++){ // for alignment message[i] = SPACE; } message[11] = KMH[1]; message[12] = KMH[2]; message[13] = KMH[3]; message[14] = KMH[4]; message[15] = SPACE; checksum = 0; for(int j = 0; j < 17; j++){ checksum += message[j]; } checksum ^= 0xff; message[17] = checksum; digitalWrite(enable, HIGH); // Sending the data for(int i = 0; i < 18; i++){ uint8_t data = message[i]; for(int k = 0; k < 8; k++){ if(data & 0x80){ digitalWrite(sda, HIGH); } else{ digitalWrite(sda, LOW); } digitalWrite(sck, LOW); digitalWrite(sck, HIGH); data <<=1; //Shifting 1 left } } digitalWrite(enable, LOW); delay(500); }Credits

Jussi Ristiniemi
0 projects • 11 followersFollowComments
Related channels and tags
- automotive
- monitoring
Từ khóa » Vw Can Bus Id List
-
IDoka/awesome-automotive-can-id: Unpretentious Attempt To Collect ...
-
[PDF] Data Exchange On The CAN Bus I - Volkspage
-
VW Can Bus Hacking With SavvyCan & Cantact - Voltlog #342
-
How To Read Vw Can Bus - General Discussion - Macchina
-
VAG-COM: CAN-Bus Information - Ross-Tech
-
CAN Message IDs Used In Volkswagen GOLF MK5
-
List Of CAN ID Descriptions (from )
-
VW-CAN - OpenStreetMap Wiki
-
VAG CAN Information. - Autosport Labs
-
EOS CAN-BUS Hacking | Volkswagen Eos Forum
-
CAN Bus: Who's Online, And Who Said What? - Picoauto
-
Instructions | CAN BUS Gaming Simulator
-
CAN Identifier List - Beckhoff Information System - English









_t9PF3orMPd.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)

