Skip to content

Commit

Permalink
Merge pull request #1 from Alfaa123/Due
Browse files Browse the repository at this point in the history
Due
  • Loading branch information
Alfaa123 authored Feb 26, 2021
2 parents c9d23cd + 65fd1d8 commit ef951a2
Show file tree
Hide file tree
Showing 113 changed files with 145 additions and 431,986 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ Reverse engineering the Volvo VIDA protocol to gather diagnostic information not

**Old video of the project with the OLED screen is here:** https://www.youtube.com/watch?v=hdAKEG6ggRk

[![Video](https://raw.githubusercontent.com/Alfaa123/Volvo-CAN-Gauge/4dSystems/IMG_20210218_131628285.jpg)](https://www.youtube.com/watch?v=hdAKEG6ggRk "Video")
[![Video](https://raw.githubusercontent.com/Alfaa123/Volvo-CAN-Gauge/Due/IMG_20210218_131622663.jpg)](https://www.youtube.com/watch?v=hdAKEG6ggRk "Video")

# Hardware:
- 2011 Volvo C30 T5
- 4D Systems ULCD-220RD round LCD Display (https://4dsystems.com.au/ulcd-220rd)
- Machinna M1.1 (Old, no longer manufactured) (More info here: https://www.macchina.cc/guide/m1/software/software-setup-arduino-ide-1516)
- Arduino Due
- Copperhill Technologies Dual CAN Interface For Arduino Due With Extended Power Range (https://copperhilltech.com/dual-can-bus-interface-for-arduino-due-with-extended-power-range/)
- Custom designed 3d-printed LCD mount designed to fit the shape of the bottom left corner of the dashboard bezel. (STL is in the Display Mount folder)

**NOTE: The Machinna M1.1 is basically an Arduino Mega with a buck style regulator and a few built in interfaces. In this project, we're only using the CAN bus interface, which is identical to a regular CAN bus shield based on the MCP2515. The only change you'll need to make in order for this to run on a regular Mega + a CAN shield is to change the CS pin of the CAN shield, as the Machinna uses a 'non-arduino' pin for this purpose.**

# Libraries:
- geneArduino: Used to send data to the 4D Systems display.
- Seeed CAN bus shield: CAN shield library with modifications to allow it to work with the Machinna M1.1
- due_can: Used to easily access the built in CAN interfaces on the Arduino Due (https://github.com/collin80/due_can)

# Basic Functional Description:

Expand All @@ -41,9 +40,8 @@ When the gauge switches pages, the needle will smoothly sweep to the next positi

- Compatibility with other vehicles is unknown at this point. Most likely, any other cars using the Volvo Bosch ME9 implementation will work with no modifications (although I have heard some reports that the broadcast IDs change from vehicle to vehicle, so that may need to be tweaked)
- Volvo uses extended IDs for their CAN frames. I am not 100% sure why they do this yet.
- Volvos of this vintage have 2 CAN networks, a High Speed bus at 500kbps and a Low Speed bus at 125kbps. The high speed bus is connected to the ECU, steering modules, braking and other modules. The low speed bus is connected to the radio, door modules, instrument cluster and other associated accessories. The CEM (Central Electronics Module) acts as a gateway between the high and low speed busses. This project connects directly to the high speed bus.
- Volvos of this vintage have 2 CAN networks, a High Speed bus at 500kbps and a Low Speed bus at 125kbps. The high speed bus is connected to the ECU, steering modules, braking and other modules. The low speed bus is connected to the radio, door modules, instrument cluster and other associated accessories. The CEM (Central Electronics Module) acts as a gateway between the high and low speed busses. This project connects to both the high and low speed busses.
- Older vehicles have a diagnostic relay that needs to be activated via K-line in order to access the CAN buses via the OBD2 port. Because mine does not require this, I don't have much information about it.
- The Arduino Mega is not fast enough to recieve all the traffic on the bus. I strongly suspect that we miss a lot of traffic with this implementation, espically because we don't use inturrupt based message handling.

# Other Resources:
- http://hackingvolvo.blogspot.com/
Expand Down
Binary file added Sniffing/GVRET.bin
Binary file not shown.
210 changes: 140 additions & 70 deletions Volvo-CAN-Gauge.ino
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
#include <SPI.h>
#include "src/can/mcp_can.h"
#include <Arduino.h>
#include <due_can.h>
#include <genieArduino.h>

long Boost, EngineSpeed, CoolantTemp, IntakeTemp, VehicleSpeed, ButtonHeld, IgnitionAngle;

//The various static CAN messages we send.
CAN_FRAME BP;
CAN_FRAME RPM;
CAN_FRAME COL;
CAN_FRAME IAT;
CAN_FRAME IA;
CAN_FRAME VHS;

long Boost;
long EngineSpeed, CoolantTemp, IntakeTemp, VehicleSpeed, ButtonHeld, IgnitionAngle;
unsigned char BP[8] = {0xCD, 0x7a, 0xa6, 0x12, 0x9d, 0x01, 0x00, 0x00};
unsigned char RPM[8] = {0xCD, 0x7a, 0xa6, 0x10, 0x1d, 0x01, 0x00, 0x00};
unsigned char COL[8] = {0xCD, 0x7a, 0xa6, 0x10, 0xd8, 0x01, 0x00, 0x00};
unsigned char IAT[8] = {0xCD, 0x7a, 0xa6, 0x10, 0xCE, 0x01, 0x00, 0x00};
unsigned char IA[8] = {0xCD, 0x7a, 0xa6, 0x10, 0x36, 0x01, 0x00, 0x00};
unsigned char VHS[8] = {0xCD, 0x7a, 0xa6, 0x11, 0x40, 0x01, 0x00, 0x00};
int x, Brightness;
unsigned char len = 0, flagRecv = 0, Page = 0, Index = 0;
unsigned char buf[8];
bool NightMode, Ignition;
static long updatePeriod = millis();
static int gaugeAddVal = 1;
static int gaugeVal = 0;
static int gaugeCurrentValue = 0;
Genie genie;
#define RESETLINE 8

MCP_CAN CAN(10); // Normally where the CS pin is set. Because the CAN library has been hacked to use direct port manipulation, this no longer matters.
#define RESETLINE 2

void setup()
{
//Serial.begin(115200);
Serial1.begin(115200); // Serial1 @ 200000 (200K) Baud
genie.Begin(Serial1); // Use Serial1 for talking to the Genie Library, and to the 4D Systems display
Serial.begin(115200);
//SerialUSB.begin(115200); // Serial1 @ 200000 (200K) BaudNuu

genie.Begin(Serial); // Use Serial1 for talking to the Genie Library, and to the 4D Systems display
pinMode(RESETLINE, OUTPUT); // Set D4 on Arduino to Output (4D Arduino Adaptor V2 - Display Reset)
digitalWrite(RESETLINE, 0); // Reset the Display via D4
delay(100);
digitalWrite(RESETLINE, 1); // unReset the Display via D4
Can0.begin(CAN_BPS_500K);
//Can1.begin(CAN_BPS_125K);
CanFrames();
delay (5000);
while (CAN_OK != CAN.begin(CAN_500KBPS)) // init can bus : baudrate = 500k
{
delay(100);
}
CAN.init_Mask(0, 1, 0xFFFFFFFF); // Masks and filters setup for the can interface. We use these to keep resource usage low by only looking for traffic we care about.
CAN.init_Mask(1, 1, 0xFFFFFFFF);
CAN.init_Filt(0, 1, 0x00400021);
CAN.init_Filt(1, 1, 0x00000000);
CAN.init_Filt(2, 1, 0x19E00006);
CAN.init_Filt(3, 1, 0x0100082C);
CAN.init_Filt(4, 1, 0x19000026);
CAN.init_Filt(5, 1, 0x00000000);
//while(!SerialUSB);
//SerialUSB.print("Test");
// Masks and filters setup for the can interface. We use these to keep resource usage low by only looking for traffic we care about.
Can0.setRXFilter(0,0x00400021, 0xFFFFFFFF, 1);
Can0.setRXFilter(1,0x19E00006, 0xFFFFFFFF, 1);
Can0.setRXFilter(2,0x0100082C, 0xFFFFFFFF, 1);
Can0.setRXFilter(3,0x19000026, 0xFFFFFFFF, 1);
genie.WriteContrast(0);
}

Expand All @@ -60,20 +55,20 @@ void loop() //The main loop sends all the various CAN messages to the ECU so
UpdateDisplay();
}
x++;
if (x > 1 && (Page==0)){
CAN.sendMsgBuf(0x000FFFFE, 1, 8, BP); //Give us boost pressure!
if (x > 10 && (Page==0)){
Can0.sendFrame(BP);
x = 0;
}
if (x > 1000 && (Page==2)){
CAN.sendMsgBuf(0x000FFFFE, 1, 8, COL); //Give us coolant temp!
Can0.sendFrame(COL);
x = 0;
}
if (x > 500 && (Page==1)){
CAN.sendMsgBuf(0x000FFFFE, 1, 8, IAT); //Give us intake temp!
Can0.sendFrame(IAT);
x = 0;
}
if (x > 1 && (Page==3)){
CAN.sendMsgBuf(0x000FFFFE, 1, 8, IA); //Give us intake temp!
Can0.sendFrame(IA);
x = 0;
}

Expand Down Expand Up @@ -103,7 +98,7 @@ void UpdateDisplay() { //This function takes the data retrieved in
gaugeCurrentValue += gaugeAddVal;
genie.WriteObject(GENIE_OBJ_IANGULAR_METER, 1, gaugeCurrentValue);
genie.WriteObject(GENIE_OBJ_ILED_DIGITS, 1, gaugeCurrentValue);
updatePeriod = millis() + 2;
updatePeriod = millis() + 0;
}
}
else if (Page == 2){
Expand All @@ -115,7 +110,7 @@ void UpdateDisplay() { //This function takes the data retrieved in
gaugeCurrentValue += gaugeAddVal;
genie.WriteObject(GENIE_OBJ_IANGULAR_METER, 2, gaugeCurrentValue);
genie.WriteObject(GENIE_OBJ_ILED_DIGITS, 2, gaugeCurrentValue);
updatePeriod = millis() + 2;
updatePeriod = millis() + 0;
}
}
else if (Page == 3){
Expand All @@ -140,18 +135,24 @@ void UpdateBrightness() { //This function simply updates t
}

void PowerSaveLoop(){ // This is where we come if the ignition message goes low. We loop here indefinitely until we sense the ignition has come back on. OLED is off and filters are activated to ignore any traffic except the ignition status broadcast.
CAN.init_Filt(1, 1, 0x19E00006);
Can0.setRXFilter(0,0x00000000, 0xFFFFFFFF, 1);
Can0.setRXFilter(1,0x19E00006, 0xFFFFFFFF, 1);
Can0.setRXFilter(2,0x00000000, 0xFFFFFFFF, 1);
Can0.setRXFilter(3,0x00000000, 0xFFFFFFFF, 1);
while (1 == 1) {
delay(2000);
if (CAN_MSGAVAIL == CAN.checkReceive())
delay(200);
if (Can0.rx_avail())
{
CAN.readMsgBuf(&len, buf);
unsigned long canId = CAN.getCanId();
if (canId == 0x19E00006) {
if ((buf[6] & B01000000) != Ignition) {
Ignition = (buf[6] & B01000000);
CAN_FRAME INCOMING;
Can0.get_rx_buff(INCOMING);
if (INCOMING.id == 0x19E00006) {
if ((INCOMING.data.bytes[6] & B01000000) != Ignition) {
Ignition = (INCOMING.data.bytes[6] & B01000000);
Can0.setRXFilter(0,0x00400021, 0xFFFFFFFF, 1);
Can0.setRXFilter(1,0x19E00006, 0xFFFFFFFF, 1);
Can0.setRXFilter(2,0x0100082C, 0xFFFFFFFF, 1);
Can0.setRXFilter(3,0x19000026, 0xFFFFFFFF, 1);
UpdateIgnition();
CAN.init_Filt(1, 1, 0x00000000);
break;
}
}
Expand All @@ -160,38 +161,37 @@ void PowerSaveLoop(){ // This is where we come if the i
}

void MessageRecieveLoop(){ //I was never able to get inturrupt based frame handling working, so this loop is here to check if we have any new messages. If we do, update variables.
while (CAN_MSGAVAIL == CAN.checkReceive())
while (Can0.rx_avail())
{
//flagRecv = 0;
CAN.readMsgBuf(&len, buf);

unsigned long canId = CAN.getCanId();
if (canId == 0x00400021) {
if (buf[4] == 0x9d) {
Boost = buf[5];
CAN_FRAME INCOMING;
Can0.get_rx_buff(INCOMING);
//SerialUSB.println(INCOMING.id,HEX);
if (INCOMING.id == 0x00400021) {
if (INCOMING.data.bytes[4] == 0x9d) {
Boost = INCOMING.data.bytes[5];
}
if (buf[4] == 0xd8) {
CoolantTemp = buf[5];
if (INCOMING.data.bytes[4] == 0xd8) {
CoolantTemp = INCOMING.data.bytes[5];
CoolantTemp = CoolantTemp*0.75-48;
}
if (buf[4] == 0xCE) {
IntakeTemp = buf[5];
if (INCOMING.data.bytes[4] == 0xCE) {
IntakeTemp = INCOMING.data.bytes[5];
IntakeTemp = IntakeTemp*0.75-48;
IntakeTemp = IntakeTemp*1.8+32;
}
if (buf[4] == 0x36) {
IgnitionAngle = buf[5];
if (INCOMING.data.bytes[4] == 0x36) {
IgnitionAngle = INCOMING.data.bytes[5];
IgnitionAngle = IgnitionAngle*191.25/255;
}
}
if (canId == 0x19E00006) {
if ((buf[6] & B01000000) == !Ignition) {
Ignition = (buf[6] & B01000000);
if (INCOMING.id == 0x19E00006) {
if ((INCOMING.data.bytes[6] & B01000000) == !Ignition) {
Ignition = (INCOMING.data.bytes[6] & B01000000);
UpdateIgnition();
}
}
if (canId == 0x19000026){
if (buf[7] && B00100000){
if (INCOMING.id == 0x19000026){
if (INCOMING.data.bytes[7] && B00100000){
if (ButtonHeld < 1){
ButtonHeld = millis();
}
Expand All @@ -208,16 +208,16 @@ void MessageRecieveLoop(){ //I
ButtonHeld = 0;
}
}
if (canId == 0x0100082C) {
if ((buf[1] & B10000000) != NightMode) {
NightMode = buf[1] & B10000000;
if (INCOMING.id == 0x0100082C) {
if ((INCOMING.data.bytes[1] & B10000000) != NightMode) {
NightMode = INCOMING.data.bytes[1] & B10000000;
if (!NightMode) {
Brightness = Brightness * 0.8;
}
UpdateBrightness();
}
if ((buf[0] & B00001111) != Brightness) {
Brightness = (buf[0] & B00001111) * 15;
if ((INCOMING.data.bytes[0] & B00001111) != Brightness) {
Brightness = (INCOMING.data.bytes[0] & B00001111) * 15;
if (!NightMode) {
Brightness = Brightness * 0.8;
}
Expand Down Expand Up @@ -254,3 +254,73 @@ void UpdateIgnition() { //This is where we go if the ignition stat

}
}

void CanFrames()
{
BP.extended = 1;
BP.id = 0x000FFFFE;
BP.length = 8;
BP.data.bytes[0]=0xCD;
BP.data.bytes[1]=0x7a;
BP.data.bytes[2]=0xa6;
BP.data.bytes[3]=0x12;
BP.data.bytes[4]=0x9d;
BP.data.bytes[5]=0x01;
BP.data.bytes[6]=0x00;
BP.data.bytes[7]=0x00;
RPM.extended = 1;
RPM.id = 0x000FFFFE;
RPM.length = 8;
RPM.data.bytes[0]=0xCD;
RPM.data.bytes[1]=0x7a;
RPM.data.bytes[2]=0xa6;
RPM.data.bytes[3]=0x10;
RPM.data.bytes[4]=0x1d;
RPM.data.bytes[5]=0x01;
RPM.data.bytes[6]=0x00;
RPM.data.bytes[7]=0x00;
COL.id = 0x000FFFFE;
COL.extended = 1;
COL.length = 8;
COL.data.bytes[0]=0xCD;
COL.data.bytes[1]=0x7a;
COL.data.bytes[2]=0xa6;
COL.data.bytes[3]=0x10;
COL.data.bytes[4]=0xd8;
COL.data.bytes[5]=0x01;
COL.data.bytes[6]=0x00;
COL.data.bytes[7]=0x00;
IAT.extended = 1;
IAT.id = 0x000FFFFE;
IAT.length = 8;
IAT.data.bytes[0]=0xCD;
IAT.data.bytes[1]=0x7a;
IAT.data.bytes[2]=0xa6;
IAT.data.bytes[3]=0x10;
IAT.data.bytes[4]=0xce;
IAT.data.bytes[5]=0x01;
IAT.data.bytes[6]=0x00;
IAT.data.bytes[7]=0x00;
IA.extended = 1;
IA.id = 0x000FFFFE;
IA.length = 8;
IA.data.bytes[0]=0xCD;
IA.data.bytes[1]=0x7a;
IA.data.bytes[2]=0xa6;
IA.data.bytes[3]=0x10;
IA.data.bytes[4]=0x36;
IA.data.bytes[5]=0x01;
IA.data.bytes[6]=0x00;
IA.data.bytes[7]=0x00;
VHS.extended = 1;
VHS.id = 0x000FFFFE;
VHS.length = 8;
VHS.data.bytes[0]=0xCD;
VHS.data.bytes[1]=0x7a;
VHS.data.bytes[2]=0xa6;
VHS.data.bytes[3]=0x11;
VHS.data.bytes[4]=0x40;
VHS.data.bytes[5]=0x01;
VHS.data.bytes[6]=0x00;
VHS.data.bytes[7]=0x00;
}
Loading

0 comments on commit ef951a2

Please sign in to comment.