LT1 Swapped Porsche - Gauge Functionality

Hi all, I am working on a project where I have a Porsche 997 swapped with a 2016 LT1. I have everything up and running at this point with a few details to wrap up before a test drive. I’ll post a link to my build thread on Rennlist for anyone interested.

One of the things I need to do is figure out how to get my gauge cluster to work. Some have used the Porsche ECM, fed it a couple sensors, and they gets most things working. My plan is bypass that entirely, and just pass GM high speed CAN data from the GM controller to the corresponding signals on the Porsche DRIVE CANBus. Macchina M2 is my planned device for this. I am a mechanical engineer, so this is a bit of a weak area for me, but not entirely new. I just haven’t messed with Arduino much before. Another member here pointed me to this product, he used it with his LS swapped 996. I started with his code, and hack some things in here… I’m not sure if I’m even on the right track. I have a lot of learning to do obviously

Starting which his code, he was passing through everything, which is not what I need to do, so I think I need to rearrange my code a bit. I only want to pick and choose certain signals and resend them to a different message, some I will need to edit based on different scaling. RPM is the same at least, so I’m starting with that.

I didn’t include the GM frame or Porsche frame I’m referencing. Not sure if moderators don’t want those shared? I have DBC files for all signals I plan to send.

My build thread:

My code below:

//Reads all traffic on CAN0 and forwards it to CAN1 (and in the reverse direction) but modifies some frames first.
// Required libraries
#include “variant.h”
#include <due_can.h>

#include <Arduino.h>
#include <pins_arduino.h>
#include <stdint.h>

#include “M2_IO.h”
#include <M2_12VIO.h>

M2_12VIO M2IO; //Constructor for the M2_12Vio library

//Leave defined if you use native port, comment if using programming port
#define Serial SerialUSB

void setup()
{

Serial.begin(115200);

M2IO.Init_12VIO(); // Initialise the M2I/O library
M2IO.Setpin_12VIO(1, ON, SOURCE);
pinMode(Led5, OUTPUT); // Set the RED LED PIN as an OUTPUT to enable flasing or Flash ERROR Condition
digitalWrite(Led5, LOW); // turn the RED LED ON by making the PIN LOW

// Initialize CAN0 and CAN1, Set the proper baud rates here
Can0.begin(CAN_BPS_500K);
Can1.begin(CAN_BPS_500K);

//By default there are 7 mailboxes for each device that are RX boxes
//This sets each mailbox to have an open filter that will accept extended
//or standard frames
int filter;
//extended
for (filter = 0; filter < 3; filter++) {
Can0.setRXFilter(filter, 0, 0, true);
Can1.setRXFilter(filter, 0, 0, true);
}
//standard
for (int filter = 3; filter < 7; filter++) {
Can0.setRXFilter(filter, 0, 0, false);
Can1.setRXFilter(filter, 0, 0, false);
}

int RPM = 0;
}

void printFrame(CAN_FRAME &frame) {
Serial.print(“ID: 0x”);
Serial.print(frame.id, HEX);
Serial.print(" Len: “);
Serial.print(frame.length);
Serial.print(” Data: 0x");
for (int count = 0; count < frame.length; count++) {
Serial.print(frame.data.bytes[count], HEX);
Serial.print(" “);
}
Serial.print(”\r\n");
}

void loop(){

CAN_FRAME incoming;

if (Can0.available() > 0) {
Can0.read(incoming);
if (incoming.id == 0xFF) {
printFrame(incoming);
RPM = incoming.data.bytes[1]};
}
myFrame.id = 0xFF;
myFrame.length = 8
myFrame.data[2] = RPM;
Can1.sendFrame(myFrame);
}

This is the signal I am trying to implement for CAN1, not sure if I set it up right at all (again, frame held out). I believe 18 is a typo, it starts at bit 16 in the other DBC file I have, so I have this entered in as the 3rd byte (2) for where it writes to.

EngineSpeed

I think the first thing I need to do is set this code up not as a pass through for all messages, but rather to filter out certain messages. It seems all the good stuff I need (tach, oil pressure, coolant, idiot lights, etc) is on 4 or so frames from the GM ECU, and received by the same number on the Porsche side. I’m trying to find examples of people using the due_can setup to better understand the functionality or how to properly set it up.

So what I did was setup a filter.

//By default there are 7 mailboxes for each device that are RX boxes

//This sets up filtering to look for the messages needed to run a gauge cluster Can0 is the Engine side, Can1 is the Porsche side
int filter;
//extended
for (filter = 0; filter < 3; filter++) {
Can0.setRXFilter(filter, 0xFF, 0, true);

//At this time, no messages going from Porsche to GM so comment out
//Can1.setRXFilter(filter, 0, 0, true);
}

//If Standard frames, enter below
//standard
//for (int filter = 3; filter < 7; filter++) {
//Can0.setRXFilter(filter, 0, 0, false);
//Can1.setRXFilter(filter, 0, 0, false);

So now only frame FF is passing through. I have nothing going from Porsche to GM right now, I think I will end up passing a few items though in the end (vehicle speed, sport mode? - it would be sweet to get active rev matching functional).

The later section of code I think should be setup differently now. No need for an if statement I guess.

CAN_FRAME incoming;

if (Can0.available() > 0) {
Can0.read(incoming);
if (incoming.id == 0xFF) {
printFrame(incoming);
RPM = incoming.data.bytes[1]};
}
myFrame.id = 0x105;
myFrame.length = 8
myFrame.data[2] = RPM;
Can1.sendFrame(myFrame);
}

Sorry, I’m very new to messing with Arduino. My job (calibrator for one of the Big Three) has me reading lots of code, never writing.

Also, as I look at my DBC files, I realize engine speed is on two frames in both. So I need to set that up. I believe one is little endian and one is big endian. That stuff I can sort out later.

I have my M2 now. I made a code that sends on CAN1 some random numbers to run the tach and it works! CAN0 I have setup to monitor CAN1bus as well to verify my message goes out. Next step will be getting RPM from the engine to my outgoing signal.

Two questions… is there something I am doing wrong for power. I have the Under the Hood version. I have 12V to pin23 and ground to Pin 12. I only get the board to power on if I have USB plugged in… Are there more spots that need power/ground?
EDIT: Figured this one out. Ground needs to go to the digital ground (any of them?) as well.

Using due_can, to write to certain bytes, I can use, "myFrame.data.byte[1], can I do something similar with individual bits? For example, in my case, the first 7 bits of byte one send accelerator pedal. Bit 8 is an engine running flag.

Man, this is a great project @superman22x! I’d love to see a video of your tach moving or whatever else you want to show off.

Regarding powering UTD - I think you’ve got it. Pin 23V gets 12V and and of the DGND pins get GND.

https://docs.macchina.cc/m2-docs/detailed-reference/installation#under-the-hood-uth

@CollinK - can you point us at any examples that shows changing/sending individual bits?

Sure! I’ll try and grab something in the next couple days! Hoping to take it for a drive soon.

I found a way to manipulate individual bits by using bitSet(x,n). But for example one signal starts at bit 0 Byte 0 and is 10 bits long. Or another starts on Bit 7 of byte 5 and is 8 bits long. It seems odd, but I’m pretty certain on these…

If you’re really up to date with due_can and can_common then there is built-in bit manipulation. If you have a CAN_FRAME object like so:

CAN_FRAME myFrame;

Then you can do this:

myFrame.bit[3] = 1;

But, that’s rather tedious when you need to set, say, 10 bits. Let’s say you know that RPM is stored across 12 bits, all 8 in the first byte (so 0-7) and the lower 4 of the next byte in the frame (8-12). This would then be spread across myFrame.data.byte[0] and myFrame.data.byte[1]. What if other stuff is stored in the upper 4 bits of that second byte, byte[1]? You could do something like this:

uint16_t RPM = 2000;
CAN_FRAME frame;
Can0.read(frame);
frame.data.byte[0] = RPM & 0xFF; //lower 8 bits of the RPM
frame.data.byte[1] = (frame.data.byte[1] & 0xF0) | ( (RPM >> 8) & 0xF );
Can1.sendFrame(frame);

Now, that includes a rather nasty line. Here it is broken down with comments:

frame.data.byte[1] = frame.data.byte[1] & 0xF0; //zero out the bottom 4 bits but leave the top 4 alone
uint8_t valu = RPM >> 8; //shift RPM down 8 bits so we're left with just the top 8 but shifted down to where the lower 8 used to be
valu = valu & 0xF; //keep only the lower 4 bits of those 8, zeroes out the upper 4
frame.data.byte[1] = frame.data.byte[1] | valu; //form a composite of the existing upper 4 bits plus the new lower 4 bits from valu

So, that’s basically how you compose a series of bits that fall within two or more bytes in the message. Is it confusing? Very much so. Believe me, it took me a lot of work to create the DBC code in SavvyCAN because of how confusing this all is. It gets easier with time. You just have to take it slowly and work through it. Basically the idea is to use & which is “AND” to mask away bits you are about to change. Then use | which is “OR” to compose new bits into those positions.

This is extremely helpful, thank you! I think I can figure it out with some testing. Takes me a few tries usually, then it clicks

I’m a little stuck on something… Trying to use what you showed to do this. I have a value, torque, that comes in on all of Byte[3] and the lower of byte[4]. So in total it’s a 12 bit number. I then need to multiply and offset it to get it to the correct scale for the Porsche output. Once I have that final number, I send it out as only an 8 bit message.

I keep getting Low and High bits mixed up. But here is my example, am I going in the write direction here?

EngTorqL=incoming.data.bytes[3];
EngTorqH=incoming.data.bytes[4];

EngTorqH = EngTorqH & 0xF0;
EngTorqH = EngTorqH >> 4;
EngTorq=EngTorqL | EngTorqH ; //EngTorq assigned as int earlier

EDIT: I may have figured out… something? It seems to work. The above did not. I made EngTorq a uint16_t as well. Does changing EngTorqL from a byte to uint16_t cause problems?

EngTorq = ((uint16_t)EngTorqL << 4) | ((EngTorqH & 0xF0) >> 4);