Forwarding with FlexCAN_T4 While Modifying Some

I have an Arduino DUE right now, installed in my vehicle acting as "man in the middle" between the integrated center stack and the IHS canbus network. I'm using the due_can library to forward messages in both directions, but filtering out messages on can 0 with ID 0x3EA, and modifying Byte 3 before forwarding the modified message to can 1.

I now want to incorporate a 3rd canbus connection, so I can also send a command to the vehicle's C-bus, when the 0x3EA message is received on can 0.

So I purchased this Teensy 4.0 Triple CAN Bus Board from Copper Hill and am attempting to recreate my working code for the Teensy.

The FlexCAN_T4 library seems the best fit for the job but I'm struggling with the filtering logic for the message ID's.

Here is the code I have running on the Arduino:

Code:
// **************************** DEFINITIONS ********************************
#include "variant.h"
#include <due_can.h>
#define Serial SerialUSB   //Leave defined if using native port, comment if using programming port .. lots of traffic so best to use native
// ************************** SERIAL PRINT FUNCTION *************************
void printFrame(CAN_FRAME *frame, int filter) {
   Serial.print("Fltr: ");
   if (filter == 1) Serial.print("FWD");
   else if (filter == 2)  Serial.print("VHL");
   else if (filter == 10) Serial.print("DSC");
   else Serial.print("???");
   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");
}
//************************************** CALLBACK FUNCTIONS **************************************
void gotFrame0(CAN_FRAME *frame) {                               //Function to identify any message received on CAN 0
   //Serial.print("RX0: ");                                      //uncomment line to print frames that are going out
   //printFrame(frame, 1);                                       //uncomment line to print frames that are going out
   Can1.sendFrame(*frame);                                       //Forward message received on CAN 0 to CAN 1
}
void gotFrame3EA(CAN_FRAME *frame) {                             //Function to identify CAN 0 messages with ID of 0x3EA
   frame->data.byte[3]=0x29;                                     //Modify Byte 3 with value 29 for SRT No, or 69 for SRT Yes
   //Serial.println("Sending the following to CAN 1");           //uncomment line to print frames that are going out
   //printFrame(frame, 2);                                       //uncomment line to print frames that are going out
   Can1.sendFrame(*frame);                                       //Send modified message to CAN 1
}
       
void gotFrame1(CAN_FRAME *frame) {                               //Function to identify any message received on CAN 1
   //Serial.print("RX1: ");                                      //uncomment line to print frames that are going out
   //printFrame(frame, -1);                                      //uncomment line to print frames that are going out
   Can0.sendFrame(*frame);                                       //Forward message received on CAN 1 to CAN 0
}
//**************************************** INITIALIZATION *******************************************
void setup() {
  //Serial.begin(115200);                          //Uncomment for serial
  Can0.begin(CAN_BPS_125K);                        //Initialize CAN0, Set the proper baud rate 
  Can1.begin(CAN_BPS_125K);                        //Initialize CAN1, Set the proper baud rate 
  Can0.setRXFilter(0, 0x3EA, 0x7FF, false);        //CAN 0 filter to identify messages with ID of 0x3EA
  Can0.setRXFilter(0, 0, false);                   //CAN 0 catch all mailbox - no mailbox ID specified
  Can1.setRXFilter(0, 0, false);                   //CAN 1 catch all mailbox - no mailbox ID specified
  Can0.setCallback(0, gotFrame3EA);                //Register callback function for VehConfig 3 messages
  Can0.setGeneralCallback(gotFrame0);              //this function will get a callback for any CAN 0 mailbox that doesn't have a registered callback
  Can1.setGeneralCallback(gotFrame1);              //this function will get a callback for any CAN 1 mailbox that doesn't have a registered callback
}
//**************************************** MAIN LOOP ************************************************
void loop(){                                //note the empty loop here. All work is done via callback as frames come in - no need to poll for them
}

And as stated, it's been working great for several weeks now.

Here is my attempt to create the same functionality with FlexCAN_T4, but I'm obviously not getting something correct with my use of the library:

Code:
// **************************** DEFINITIONS ********************************
#include <FlexCAN_T4.h>
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can0;
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> Can1;
FlexCAN_T4<CAN3, RX_SIZE_256, TX_SIZE_16> Can2;
// ************************** SERIAL PRINT FUNCTION *************************
void printFrame(CAN_message_t &frame, int filter) {
   Serial.print("Fltr: ");
   if (filter == 1) Serial.print("FWD");
   else if (filter == 2)  Serial.print("VHL");
   else if (filter == 10) Serial.print("DSC");
   else Serial.print("???");
   Serial.print(" ID: 0x");
   Serial.print(frame.id, HEX);
   Serial.print(" Len: ");
   Serial.print(frame.len);
   Serial.print(" Data: 0x");
   for (int count = 0; count < frame.len; count++) {
       Serial.print(frame.buf[count], HEX);
       Serial.print(" ");
   }
   Serial.print("\r\n");
}
//************************************** CALLBACK FUNCTIONS **************************************
void gotFrame0(const CAN_message_t &frame) {                //Function to handle messages received on CAN 0
   //Serial.print("RX0: ");                                 //uncomment line to print frames that are going out
   //printFrame(frame, 1);                                  //uncomment line to print frames that are going out
   Can1.write(frame);                                       //Forward message received on CAN 0 to CAN 1
}
void gotFrame3EA(const CAN_message_t &frame) {              //Function to handle CAN 0 messages with ID of 0x3EA
   CAN_message_t modifiedFrame = frame;                     //Make a copy of the original frame
   modifiedFrame.buf[3] = 0x29;                             //Modify Byte 3 with value 29 for SRT No, or 69 for SRT Yes
   //Serial.println("Modified frame sent to CAN 1");        //uncomment line to print frames that are going out
   //printFrame(frame, 2);                                  //uncomment line to print frames that are going out
   //*****Send message A to Can2*****
   Can1.write(modifiedFrame);                               //Send modified message to CAN 1
   //*****Send message B to Can2*****
}
       
void gotFrame1(const CAN_message_t &frame) {                //Function to identify any message received on CAN 1
   //Serial.print("RX1: ");                                 //uncomment line to print frames that are going out
   //printFrame(frame, -1);                                 //uncomment line to print frames that are going out
   Can0.write(frame);                                       //Forward message received on CAN 1 to CAN 0
}
//**************************************** INITIALIZATION *******************************************
void setup()
{
  Can0.begin();
  Can0.setBaudRate(125000);
  Can0.setMaxMB(9);
  Can0.enableFIFO();
  Can1.begin();
  Can1.setBaudRate(125000);
  Can1.setMaxMB(9);
  Can1.enableFIFO();
  Can2.begin();
  Can2.setBaudRate(500000);
  Can2.setMaxMB(9);
  Can2.enableFIFO();
  Can0.onReceive(0x3EA, gotFrame3EA);   //Register specific callback for ID 0x3EA on CAN0
  Can0.onReceive(gotFrame0);            //Register general callback for CAN0
  Can1.onReceive(gotFrame1);            //Register general callback for CAN1
}
//**************************************** MAIN LOOP ************************************************
void loop(){                                //note the empty loop here. All work is done via callback as frames come in - no need to poll for them
}

It does compile successfully, but I'm getting several warnings.

The 1st one is related to line 68 in my code:
Code:
warning: invalid conversion from 'int' to 'FLEXCAN_MAILBOX' [-fpermissive]
   68 |   Can0.onReceive(0x3EA, gotFrame3EA);   //Register specific callback for ID 0x3EA on CAN0
      |                  ^~~~~
      |                  |
      |                  int

I assume it's got to do with me trying to use the message ID in the 1st argument, where that is supposed to be a mailbox ID instead?

Then I also get 3 more warnings related to the library itself:

Code:
In file included from C:\Users\Stoop\AppData\Local\Arduino15\packages\teensy\hardware\avr\1.59.0\libraries\FlexCAN_T4/FlexCAN_T4.h:558,
                 from Z:\Pics\cars\23_Durango_RT\Canbus\Teensy\CanbusTeensy\CanbusTeensy.ino:2:
C:\Users\Stoop\AppData\Local\Arduino15\packages\teensy\hardware\avr\1.59.0\libraries\FlexCAN_T4/FlexCAN_T4.tpp:1050:59: note:   initializing argument 1 of 'void FlexCAN_T4<_bus, _rxSize, _txSize>::onReceive(const FLEXCAN_MAILBOX&, _MB_ptr) [with CAN_DEV_TABLE _bus = CAN1; FLEXCAN_RXQUEUE_TABLE _rxSize = RX_SIZE_256; FLEXCAN_TXQUEUE_TABLE _txSize = TX_SIZE_16; FLEXCAN_MAILBOX = FLEXCAN_MAILBOX; _MB_ptr = void (*)(const CAN_message_t&)]'
 1050 | FCTP_FUNC void FCTP_OPT::onReceive(const FLEXCAN_MAILBOX &mb_num, _MB_ptr handler) {
      |                                    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
C:\Users\Stoop\AppData\Local\Arduino15\packages\teensy\hardware\avr\1.59.0\libraries\FlexCAN_T4/FlexCAN_T4.tpp:1055:21: warning: array subscript 234 is above array bounds of 'void (* [64])(const CAN_message_t&)' [-Warray-bounds]
 1055 |   _mbHandlers[mb_num] = handler;
      |   ~~~~~~~~~~~~~~~~~~^
In file included from Z:\Pics\cars\23_Durango_RT\Canbus\Teensy\CanbusTeensy\CanbusTeensy.ino:2:
C:\Users\Stoop\AppData\Local\Arduino15\packages\teensy\hardware\avr\1.59.0\libraries\FlexCAN_T4/FlexCAN_T4.h:542:13: note: while referencing 'FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16>::_mbHandlers'
  542 |     _MB_ptr _mbHandlers[64]; /* individual mailbox handlers */
      |             ^~~~~~~~~~~

I appreciate any help with what I'm doing wrong here. I'm not new to Arduino, Teensy, ESP8266, ESP32, etc ... but I'm certainly no coder ... I just keep working at things until I get them right.
 
The onReceive() function can't be used to separate messages by ID, as you are trying to do. It can only be attached to mailboxes or the FIFO.

In setup(), attach your callback to the FIFO like this:
Code:
 Can0.enableFIFO();
 Can0.enableFIFOInterrupt();
 Can0.onReceive(gotFrame0);

then in gotFrame0() you can handle the different IDs accordingly:
Code:
void gotFrame0(const CAN_message_t &msg) {
    if (msg.id == 0x3EA) {
        // modify the byte in msg.buf[]
    }
    CAN1.write(msg)
}

This code hasn't been tested in any way, so don't take it too literally. I'm referencing the example included with FlexCAN_T4 called "CAN2.0_example_FIFO_with_interrupts.ino".

Until you get past that first error, I'd assume the others are due to the first. Good luck!
 
The onReceive() function can't be used to separate messages by ID, as you are trying to do. It can only be attached to mailboxes or the FIFO.

In setup(), attach your callback to the FIFO like this:
Code:
 Can0.enableFIFO();
 Can0.enableFIFOInterrupt();
 Can0.onReceive(gotFrame0);

then in gotFrame0() you can handle the different IDs accordingly:
Code:
void gotFrame0(const CAN_message_t &msg) {
    if (msg.id == 0x3EA) {
        // modify the byte in msg.buf[]
    }
    CAN1.write(msg)
}

This code hasn't been tested in any way, so don't take it too literally. I'm referencing the example included with FlexCAN_T4 called "CAN2.0_example_FIFO_with_interrupts.ino".

Until you get past that first error, I'd assume the others are due to the first. Good luck!

Perfect, thanks so much! This explains why I was getting the warnings/errors when trying the due_can approach with FlexCAN_T4.

I've made the adjustments, and it now compiles without warnings or errors. Just one point .... Apparently you can't modify the byte of the frame (msg), as I get an error that I am trying to modify a read only area

So to get this to work:

Code:
void gotFrame0(const CAN_message_t &msg) {
    if (msg.id == 0x3EA) {
        // modify the byte in msg.buf[]
    }
    CAN1.write(msg)
}

I have to copy the frame details into another variable (matrix?). I used "modifiedFrame" for the name...

Code:
void gotFrame0(const CAN_message_t &frame) {                //Function to handle messages received on CAN 0
if (frame.id == 0x3EA){                                     //If the message has ID 0x3EA, then ...
    CAN_message_t modifiedFrame = frame;                    //Make a copy of the original frame
    modifiedFrame.buf[3] = 0x29;                            //Modify Byte 3 of the copy with value 29 for SRT No, or 69 for SRT Yes
    Can1.write(modifiedFrame);                              //Send modified message to CAN 1
   }


I haven't tested this yet, as I wanted to get it compiling 1st, and have some sense of confidence before taking out the functional Arduino and installing the Teensy.

Question: Should I also use interrupt for the other can busses, even if I won't be modifying frames received on them?

Here is the code as it sits now, with only Can0 having the enableFIFOInterrupt() line.

Code:
// **************************** DEFINITIONS ********************************
#include <FlexCAN_T4.h>
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can0;
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> Can1;
FlexCAN_T4<CAN3, RX_SIZE_256, TX_SIZE_16> Can2;
// ************************** SERIAL PRINT FUNCTION *************************
void printFrame(CAN_message_t &frame, int filter) {
  if (filter == 1) Serial.print("RX0: ");                 //Messages received on Can0
    else if (filter == 2) Serial.print("RX1: ");          //Messages received on Can1
    else if (filter == 3) Serial.print("RX2: ");          //Messages recevied on Can2
    else if (filter == 4) Serial.print("TX1: ");          //Modified messages sending to Can1
    else if (filter == 5) Serial.print("FW0: ");          //Messages forwarding from Can1 to Can0
    else if (filter == 6) Serial.print("FW1: ");          //Messages forwarding from Can0 to Can1
    else if (filter == 7) Serial.print("DM0: ");          //Drive Mode messages sending to Can0
    else Serial.print("???");
  Serial.print(" ID: 0x");
  Serial.print(frame.id, HEX);
  Serial.print(" Len: ");
  Serial.print(frame.len);
  Serial.print(" Data: 0x");
  for (int count = 0; count < frame.len; count++) {
    Serial.print(frame.buf[count], HEX);
    Serial.print(" ");
  }
  Serial.print("\r\n");
}
//************************************** CALLBACK FUNCTIONS **************************************
void gotFrame0(const CAN_message_t &frame) {          //Function to handle messages received on Can0
  //printFrame(frame, 1);                             //uncomment line to print frames received on Can0
  if (frame.id == 0x3EA){                             //If the message has ID 0x3EA (Vehicle is SRT), then ...
    CAN_message_t modifiedFrame = frame;              //Make a copy of the original frame
    modifiedFrame.buf[3] = 0x69;                      //Modify Byte 3 of the copy with value 29 for SRT No, or 69 for SRT Yes
    Can1.write(modifiedFrame);                        //Send modified message to Can1
    //printFrame(modifiedFrame, 4);                   //uncomment line to print modified frames sending on Can1
  }
  else {
    Can1.write(frame);                                //Forward message received on Can0 to Can1
    //printFrame(modifiedFrame, 5);                   //uncomment line to print frames forwarding to Can1
  }
}
void gotFrame1(const CAN_message_t &frame) {          //Function to handle messages received on Can1
  //printFrame(frame, 2);                             //uncomment line to print frames received on Can1
  if (frame.id == 0x2D3){                             //If the message has ID 0x2Ds (Drive Mode), then ...
    /*****Send message A to Can2*****/                //Send message to Can2 to modify BCM  
    Can0.write(frame);                                //Forward drive mode message from Can1 to Can 0
    /*****Send message B to Can2*****/                //Send message to Can2 to undo previous modify
    //printFrame(frame, 7);                           //uncomment line to print drive mode frames sending on Can1 
  }
  else {
    Can0.write(frame);                                //Forward message received on Can1 to Can0
    //printFrame(frame, 5);                           //uncomment line to print frames forwarding to Can1    
  }
}
void gotFrame2(const CAN_message_t &frame) {          //Function to handle messages received on Can2
  //printFrame(frame, 3);                             //uncomment line to print frames that are going out
}
//**************************************** INITIALIZATION *******************************************
void setup()
{
  Serial.begin(115200); delay(400);
  Serial.println("Hello: Stoop's canbus MitM is starting...");
  Can0.begin();
  Can0.setBaudRate(125000);
  Can0.setMaxMB(9);
  Can0.enableFIFO();
  Can0.enableFIFOInterrupt();
  Can0.onReceive(gotFrame0);            //Register callback for Can0
  Can1.begin();
  Can1.setBaudRate(125000);
  Can1.setMaxMB(9);
  Can1.enableFIFO();
  Can1.onReceive(gotFrame1);            //Register callback for Can1
  Can2.begin();
  Can2.setBaudRate(500000);
  Can2.setMaxMB(9);
  Can2.enableFIFO();
  Can2.onReceive(gotFrame2);            //Register callback for Can2
}
//**************************************** MAIN LOOP ************************************************
void loop(){                                //note the empty loop here. All work is done via callback as frames come in - no need to poll for them
}
 
Last edited:
I'm glad that helped. Good catch on copying the frame in the callback.
Question: Should I also use interrupt for the other can busses, even if I won't be modifying frames received on them?
Yes. enableFIFOInterrupt() instructs FlexCAN to call the callbacks you specify with onReceive(). Without that, gotFrame1() and gotFrame2() will never be processed.
 
I'm glad that helped. Good catch on copying the frame in the callback.

Yes. enableFIFOInterrupt() instructs FlexCAN to call the callbacks you specify with onReceive(). Without that, gotFrame1() and gotFrame2() will never be processed.

Thanks again, I appreciate the info.

Do you know how I would go about sending a structured message from scratch?

For example, if I wanted to send the message "48 4E 39 69 E7 25 AB FD" with ID 0x3EA to Can2 ... I'm trying to figure out how to construct it.

would I do something like:

Code:
CAN_message_t newFrame;
newFrame.id = 0x3EA;
newFrame.buf[0] = 0x48;
newFrame.buf[1] = 0x4E;
newFrame.buf[2] = 0x39;
newFrame.buf[3] = 0x69;
newFrame.buf[4] = 0xE7;
newFrame.buf[5] = 0x25;
newFrame.buf[6] = 0xAB;
newFrame.buf[7] = 0xFD;
 
Thanks again, I appreciate the info.

Do you know how I would go about sending a structured message from scratch?

For example, if I wanted to send the message "48 4E 39 69 E7 25 AB FD" with ID 0x3EA to Can2 ... I'm trying to figure out how to construct it.

would I do something like:

Code:
CAN_message_t newFrame;
newFrame.id = 0x3EA;
newFrame.buf[0] = 0x48;
newFrame.buf[1] = 0x4E;
newFrame.buf[2] = 0x39;
newFrame.buf[3] = 0x69;
newFrame.buf[4] = 0xE7;
newFrame.buf[5] = 0x25;
newFrame.buf[6] = 0xAB;
newFrame.buf[7] = 0xFD;
Yep, that's the bare minimum to create a message. If you need to shorten the data length, you can use the msg.len property. If you don't specify the length, it defaults to 8 bytes.
 
Makes sense, thanks.

Do you happen to know if I need the "0x" when specifying the ID and Byte values?

I'm using 0x for manually specifying Byte values, but also in the "If" statements in the callbacks:

Code:
void gotFrame0(const CAN_message_t &frame) {          //Function to handle messages received on Can0
  //printFrame(frame, 1);                             //uncomment line to print frames received on Can0
  if (frame.id == 0x3EA){                             //If the message has ID 0x3EA (Vehicle is SRT), then ...
    CAN_message_t modifiedFrame = frame;              //Make a copy of the original frame
    modifiedFrame.buf[3] = 0x69;                      //Modify Byte 3 of the copy with value 29 for SRT No, or 69 for SRT Yes
    Can1.write(modifiedFrame);                        //Send modified message to Can1
    //printFrame(modifiedFrame, 4);                   //uncomment line to print modified frames sending on Can1
  }
  else {
    Can1.write(frame);                                //Forward message received on Can0 to Can1
    //printFrame(modifiedFrame, 5);                   //uncomment line to print frames forwarding to Can1
  }
}

Just wondering if that's erroneous and instead of "if (frame.id == 0x3EA)" and "modifiedFrame.buf[3] = 0x69;" I can omit the "0x" and just use "if (frame.id == 3EA)" and "modifiedFrame.buf[3] = 69;".


One other comment ... I believe I also need to use the FIFO argument in the onReceive statement for callback registration, correct?

Code:
  Can1.begin();
  Can1.setBaudRate(125000);
  Can1.setMaxMB(9);
  Can1.enableFIFO();
  Can1.enableFIFOInterrupt();
  Can1.onReceive(FIFO, gotFrame1);            //Register callback for Can1
 
Do you happen to know if I need the "0x" when specifying the ID and Byte values?
The 0x denotes hexadecimal notation, which is typically used for ID and Byte data. However, the decimal equivalent is equally valid. For example, ID=0x3EA is the same as if you wrote ID=1002. Therefore you can't simply drop the "0x" without converting to the decimal value. Hope that makes sense.

One other comment ... I believe I also need to use the FIFO argument in the onReceive statement for callback registration, correct?
You certainly can include the FIFO argument, but it is optional in the code you included. If that argument is omitted, the callback is used for all mailboxes and FIFO for that BUS.
In more complicated programs, one might want to specify different callbacks for FIFO and each configured mailbox, so that is an optional argument.
 
Makes perfect sense, thanks again.

Good to know on the FIFO handling. I do have plans to add more complex functionality later on, after I get the baseline up and running. It's the reason I'm swapping out the Arduino Due running due_can for the Teensy 4.0 running FlexCAN_T4; to be able to interface to both buses but also to add more complex functionality and not worry about processing power and speed/lag.

It's amazing what a 600mhz ARM Cortex-M7 processor makes possible in such a small form factor!

1709910251321.png


With such high processing power, I should be able to incorporate just about anything else I'd like ... For example, the vehicle has a hood scoop and I was thinking of adding RGB LEDs to it, and then have them dynamically change based on particular parameters (like drive mode selection for the color and RPM for the intensity, etc ...). Maybe adding convenience entry lighting with logic tied to the "door ajar" bus messaging. Also considering a 240x240 IPS display to integrate into the dash and have different parameters available for viewing (boost for example, since I'm installing a Whipple 3.0L supercharger on it), etc ...


Here's how my code sits right now. I have the "Tester Present" and "Extended Diagnostics Session" function calls commented out for now, as I continue to research how to structure those messages. But this code should allow me to replace the Arduino and retain the same functionality it is currently providing, as I develop the solution more.

Code:
// **************************** DEFINITIONS ********************************
#include <FlexCAN_T4.h>
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can0;
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> Can1;
FlexCAN_T4<CAN3, RX_SIZE_256, TX_SIZE_16> Can2;
// ************************** SERIAL PRINT FUNCTION *************************
void printFrame(CAN_message_t Pframe, int filter) {
  if (filter == 1) Serial.print("RX0: ");                 //Messages received on Can0
    else if (filter == 2) Serial.print("RX1: ");          //Messages received on Can1
    else if (filter == 3) Serial.print("RX2: ");          //Messages recevied on Can2
    else if (filter == 4) Serial.print("TX1: ");          //Modified messages sending to Can1
    else if (filter == 5) Serial.print("FW0: ");          //Messages forwarding from Can1 to Can0
    else if (filter == 6) Serial.print("FW1: ");          //Messages forwarding from Can0 to Can1
    else if (filter == 7) Serial.print("DM0: ");          //Drive Mode messages sending to Can0
    else if (filter == 8) Serial.print("DM2: ");          //Drive Mode messages detected on Can2
    else Serial.print("???");
  Serial.print(" ID: 0x");
  Serial.print(Pframe.id, HEX);
  Serial.print(" Len: ");
  Serial.print(Pframe.len);
  Serial.print(" Data: 0x");
  for (int count = 0; count < Pframe.len; count++) {
    Serial.print(Pframe.buf[count], HEX);
    Serial.print(" ");
  }
  Serial.print("\r\n");
}
//************************************** CALLBACK FUNCTIONS **************************************
void gotFrame0(const CAN_message_t &frame) {          //Function to handle messages received on Can0
  //printFrame(frame, 1);                             //uncomment line to print frames received on Can0
  if (frame.id == 0x3EA){                             //If the message has ID 0x3EA (Vehicle is SRT), then ...
    CAN_message_t modifiedFrame = frame;              //Make a copy of the original frame
    modifiedFrame.buf[3] = 0x69;                      //Modify Byte 3 of the copy with value 29 for SRT No, or 69 for SRT Yes
    Can1.write(modifiedFrame);                        //Send modified message to Can1
    //printFrame(modifiedFrame, 4);                   //uncomment line to print modified frames sending on Can1
  }
  else {
    Can1.write(frame);                                //Forward message received on Can0 to Can1
    //printFrame(modifiedFrame, 5);                   //uncomment line to print frames forwarding to Can1
  }
}
void gotFrame1(const CAN_message_t &frame) {          //Function to handle messages received on Can1
  //printFrame(frame, 2);                             //uncomment line to print frames received on Can1
  
  if (frame.id == 0x2D3){                             //If the message has ID 0x2D3 (Drive Mode), then ...
  //  sendTesterPresent();                              //Call function to send "Tester Present" message to BCM
  //  sendExtendedDiagnosticSession();                  //Call function to enable "Extended Diag Session" with the BCM
  //  Can2.write(BCM_msg_1);                            //Send message to Can2 to modify BCM  
    
    Can0.write(frame);                                //Forward drive mode message from Can1 to Can 0
    //printFrame(frame, 7);                           //uncomment line to print drive mode frames sending on Can0 
  //  sendTesterPresent();                              //Call function to send "Tester Present" message to BCM
  //  sendExtendedDiagnosticSession();                  //Call function to enable "Extended Diag Session" with the BCM
  //  Can2.write(BCM_msg_2);                            //Send message to Can2 to undo previous modify
  }
  else {
    Can0.write(frame);                                //Forward message received on Can1 to Can0
    //printFrame(frame, 5);                           //uncomment line to print frames forwarding to Can1   
  }
}
void gotFrame2(const CAN_message_t &frame) {          //Function to handle messages received on Can2
    if (frame.id == 0x2D3){                           //If the message has ID 0x2D3 (Drive Mode), then ...
      //printFrame(frame, 8);                         //uncomment line to print Drive Mode frames detected on Can2
    }
  //printFrame(frame, 3);                             //uncomment line to print frames that are going out
}
//**************************************** INITIALIZATION *******************************************
void setup()
{
  Serial.begin(115200); delay(400);
  Serial.println("Hello: Stoop's canbus MitM is starting...");
  
  Can0.begin();
  Can0.setBaudRate(125000);
  Can0.setMaxMB(9);
  Can0.enableFIFO();
  Can0.enableFIFOInterrupt();
  Can0.onReceive(FIFO, gotFrame0);            //Register callback for Can0
  Can0.mailboxStatus();
  Can1.begin();
  Can1.setBaudRate(125000);
  Can1.setMaxMB(9);
  Can1.enableFIFO();
  Can1.enableFIFOInterrupt();
  Can1.onReceive(FIFO, gotFrame1);            //Register callback for Can1  
  Can1.mailboxStatus();
  Can2.begin();
  Can2.setBaudRate(500000);
  Can2.setMaxMB(9);
  Can2.enableFIFO();
  Can2.enableFIFOInterrupt();
  Can2.onReceive(FIFO, gotFrame2);            //Register callback for Can2  
  Can2.mailboxStatus();
}
//**************************************** MAIN LOOP ************************************************
void loop(){                                //note the empty loop here. All work is done via callback as frames come in - no need to poll for them
}
 
So I've made a lot of progress on this project, and have it working fairly well now.

One additional thing I'd like to do is have the Teensy go into a "sleep" mode, or a low power idle mode, when there is no traffic on the canbuses for a given amount of time.

Do you happen to know if there is a function to accomplish this, or if there's a library available which will?
 
Glad you got it working.
The FlexCAN module on the T4 has the capability to go into "Stop Mode" which is a system low power mode, and "Self Wake" on CAN traffic. Unfortunately, this functionality isn't currently implemented in the FlexCAN_T4 library, nor any other library I'm aware of. Looking around the Snooze library, it looks like someone was able to use it, in a way... https://github.com/duff2013/Snooze/issues/87
 
Thanks guys, I appreciate the input.

As I've been working on the Teensy setup in the car, I've noticed the large 1062 chip is warm (slightly hot?) to the touch even when coming out to the car after it's been sitting 'off' for hours. I'm in Texas, so the ambient air will be much hotter come summer time, especially when the vehicle is parked in the sun when it's +105F out, so I'm thinking I should do something to reduce it as much as possible. Once the car is running and being driven, the AC will be running, so there's some mitigation there. But I don't want to risk the scenario where the Teensy won't power on due to heat until the interior cools down enough.

I ordered a Pi heatsink for it today, so will add that for some passive cooling.

I have the Teensy power and ground tapped into the harness of the vehicle module I am interfacing with, because it is fused and because I want the Teensy to be on the same circuit as the interfaced module.

I know my code is not efficient, but it's all callback driven. So if there's no traffic on the canbuses, then it should be sitting idle. I guess the heat is just from the 600mhz processor sitting idle for so long? It's not so hot I can't touch it, so maybe 90 - 100 *F?

If it's just the 600 Mhz processor idling which is causing the heat, maybe using the Snooze library to enter REDUCED_CPU_BLOCK mode (ie: run at 2 Mhz) while there is no CAN traffic would address this?

My main Loop() is empty right now, since everything is callback driven. But if I added something like the following to the loop, maybe it would accomplish the goal ... thoughts?

Code:
SnoozeBlock CAN_Sleep;

void Loop() {
   while ((Can0.available) && (Can1.available)) {
      REDUCED_CPU_BLOCK (CAN_Sleep) {
         while(1) {
            //Do something/nothing while in lower CPU mode
         }
      }
   }
}

I'm not sure if "while ((Can0.available) && (Can1.available))" is the right way to indicate "while no traffic exists on the can busses ..." ... not even sure if I can take this approach when using callbacks? Will the loop ever see traffic on the canbusses if every frame triggers a callback and is sent to the FIFO mailbox?


Below is the full code as it sits right now. Today I commented out the lines related to Can2 (ie: the FD Can 3 interface of the T4) because it turns out I don't need it for my final solution. But I haven't loaded it yet to see if it'll help at all with the heat.

Like I said above though, I realize it's not all that efficient with the use of so many if and else if statements ... Once I get all the messaging figured out I probably need to learn how to implement mapping tables instead ... but one step at a time :)

Code:
//0x2D3 = Button press messages from the integrated center stack (ICS), including drive mode hard buttons. One-time event, not continual broadcast
//        Use this to trigger in real-time the corresponding 0x3A0 UC5 drive mode change request message, when a hard button is pressed
//0x3A0 = Drive mode button presses from the uConnect 5 system. One time event, not continual broadcast
//0x3EA = VehConfig3 message parameters from BCM. Byte 3 contains "Vehicle is an SRT" setting. Continually broadcast
//        Must set this to "Yes" in the BCM, but change it's value to "No" in real-time for the ICS
//0x2FA = Byte 7 indicates the currently active drive mode enabled, except for ECO. Continually broadcasted value
//0x30A = Contains state of ECO drive mode and the ECO button LED control
//        Byte 4 value of 40 indicates ECO active
//        Byte 6 controls TnG ECO button LED. 4 = LED on and 0 = LED off
//        When "Vehicle is an SRT" is set to "Yes" in the BCM, flip Byte 6 to opposite value (0 to 4 / 4 to 0) due to LED logic being reversed
//0x306 = Expected response from BCM when an ICS drive mode is active.
//        Only updates when "Vehicle is an SRT" is set to "No" in the BCM.
//        Controls ICS drive mode button LEDs
//        Must create these messages in real-time and send to ICS. Triggered from drive mode indication within 0x2FA messages
//0x3B2 = Message ID which controls the SPORT LED on the TnG panel
//        Byte 1, Bit 0 needs to be on for the LED to light.
//0x302 = ECO Mode button press message from the uConnect 5 system. Byte 4 flips to 0x10 momentarily when the ECO soft button is pressed on the touch screen 
//
//
//                                                              _____
//                                                              |   |
//                                                              |BCM|
//                                                              |___|
//                                                        0x30A  V ^
//                                                        0x2FA  V ^  0x3A0
//                                                        0x3EA  V ^  0x302
//                                                        0x302  V ^
//                               0x2D3        0x302              V ^           0x302
//               _____           0x2D3 ______ 0x3A0           ___V_^___        0x3A0   _____
//               |   | > > > > > > > >|      | > > > > > > > >|       |< < < < < < < < |   |          
//               |ICS|      Can1      |Teensy|      Can0      |IHS BUS|                |UC5|
//               |___| < < < < < < < <|______|< < < < < < < < |_______| > > > > > > > >|___|
//                               0x3EA       0x3EA                                0x3EA  
//                               0x30A       0x30A                                0x30A
//                               0x306       0x2FA                                0x2FA
//                               0x3B2       0x3B2    
//                      
//
//
//Bitwise
//    OR operand will always turn the set bits to a '1' and uses | symbol (00101010 | 01000010 = 01101010)
//    XOR operand will flip the bit to the opposite value and uses ^ symbol (00101010 ^ 01000010 = 01101000)
//    AND NOT operand will always turn the set bits to a '0' and uses the &~ symbols (00101010 &~ 01000010 = 00101000)
//
// **************************** DEFINITIONS ********************************
#include <FlexCAN_T4.h>                               //Canbus include library for Teensy 4.0
#include <Bounce.h>                                   //https://www.pjrc.com/teensy/td_libs_Bounce.html
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can0;       //Define Can0 (for IHS-bus connection)
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> Can1;       //Define Can1 (for ICS connection)
//FlexCAN_T4<CAN3, RX_SIZE_256, TX_SIZE_16> Can2;       //Define Can2 (for C-bus connection, but not needed)
String ActiveDriveMode = "Auto";                      //String to store currently active drive mode
String LastDriveMode = "Auto";                        //String to store previous active drive mode. Used to "return to Auto" when ICS buttons are pushed
            
CAN_message_t VehConfig3Frame;                        //Frame variable to work with 0x3EA messages
CAN_message_t ICSExpectedResponseFrame;               //Frame variable to work with 0x306 messages
CAN_message_t UC5DriveModeFrame;                      //Frame variable to work with 0x3A0 messages
CAN_message_t ECOFrame;                               //Frame variable to work with 0x30A messages
CAN_message_t SportLEDFrame;                          //Frame variable to work with 0x305 messages
CAN_message_t UC5ECOButtonFrame;                      //Frame variable to work with 0x302 messages
CAN_message_t TestFrame;

// ************************** SERIAL PRINT FUNCTION *************************
void printFrame(CAN_message_t Pframe, int filter) {
  if (filter == 1) Serial.print("RX0: ");                 //Messages received on Can0
    else if (filter == 2) Serial.print("RX1: ");          //Messages received on Can1
    else if (filter == 3) Serial.print("RX2: ");          //Messages recevied on Can2
    else if (filter == 4) Serial.print("TX1: ");          //Modified messages sending to Can1
    else if (filter == 5) Serial.print("FW0: ");          //Messages forwarding from Can1 to Can0
    else if (filter == 6) Serial.print("FW1: ");          //Messages forwarding from Can0 to Can1
    else if (filter == 7) Serial.print("DM0: ");          //Drive Mode messages detected on Can0
    else if (filter == 8) Serial.print("DM2: ");          //Drive Mode messages detected on Can2
    else if (filter == 9) Serial.print("DM1: ");          //Drive Mode messages detected on Can1
    else Serial.print("???");
  Serial.print(" ID: 0x");
  Serial.print(Pframe.id, HEX);
  Serial.print(" Len: ");
  Serial.print(Pframe.len);
  Serial.print(" Data: 0x");
  for (int count = 0; count < Pframe.len; count++) {
    Serial.print(Pframe.buf[count], HEX);
    Serial.print(" ");
  }
  Serial.print("\r\n");
}

//************************************** CALLBACK FUNCTIONS **************************************
//Handling of frames received on the IHS-bus ==================================================================
void gotFrame0(const CAN_message_t &frame) {          //Function to handle messages received on Can0 (IHS)
  //printFrame(frame, 1);                             //uncomment line to print frames received on Can0
//-----------------------------------------------------------------------------------------------------------
//3EA Frame Handling (VehConfig 3 Settings)------------------------------------------------------------------
  if (frame.id == 0x3EA){                             //If the message has ID 0x3EA (contains "Vehicle is an SRT" setting), then ...
    VehConfig3Frame = frame;                          //Make a copy of the original frame
    VehConfig3Frame.buf[3] = frame.buf[3] &~ 0x40;    //Flip Bit 2 to a '0' to spoof "Vehicle is an SRT" to "No" for the ICS
    Can1.write(VehConfig3Frame);                      //Send modified message to Can1 (ICS)
    //printFrame(VehConfig3Frame, 4);                 //uncomment line to print modified frames sending on Can1
  }
  //-----------------------------------------------------------------------------------------------------------
  //305 Frame Handling (Sport Mode LED and Sport Drive Mode control)-------------------------------------------
    else if ((frame.id == 0x305) && (frame.buf[2] == 0x80)){    //If the message has ID 0x305 (UC5 Sport Mode Frame) and Sport is active, then ...
      if (ActiveDriveMode != "Sport") {                         //If Sport has been newly selected
        LastDriveMode = ActiveDriveMode;                        //Update last drive mode variable
        ActiveDriveMode = "Sport";                              //Set active drive mode to Sport
        //****** V MAY NEED TO MOVE THIS DOWN SO IT RUNS EVERY TIME INSTEAD OF ONLY ONCE V
        SportLEDFrame[1] = SportLEDframe.buf[1] | 0x01;         //Flip bit #0 to 1 to light the Sport Mode button LED
      }
      Can1.write(SportLEDFrame);                                //Write modified 0x3B2 frame to the ICS
    }
  //-----------------------------------------------------------------------------------------------------------
  //3B2 Frame Handling (Update ICS Panel Sport Button LED Control Frame variable)------------------------------
    else if ((frame.id == 0x3B2) && (ActiveDriveMode != "Sport") {  //If Frame ID = 3B2 and Sport Mode is not currently active, then...
      SportLEDFrame = frame;                                        //Copy the frame to the Sport LED frame variable
      Can1.write(SportLEDFrame);                                    //Write the 3B2 frame to the ICS (will turn sport button LED off)
    }
  //-----------------------------------------------------------------------------------------------------------
  //2FA Frame Handling (Currently active drive mode handling)--------------------------------------------------
    else if (frame.id == 0x2FA){                          //If the message has ID 0x2FA (BCM Broadcast of active Drive Mode), then ...
      if (frame.buf[7] == 0x2)  {                         //If "sport" mode is active 
        //Sport control within 305 & 3B2 Frame Handling   //Sport mode is unique and doesn't follow the same logic as the other drive modes
        ICSExpectedResponseFrame.buf[5] = 0x0;            //Set Byte 5 of ICS hearbeat (ID 0x306)  
        Can1.write(ICSExpectedResponseFrame);             //Send modified ICS heartbeat frame to Can1
      }
        else if (frame.buf[7] == 0x3)  {                  //If "track" mode is active
          if (ActiveDriveMode != "Track") {               //If Track has been newly selected
            LastDriveMode = ActiveDriveMode;              //Update last drive mode variable
            ActiveDriveMode = "Track";                    //Set active drive mode to Track
          }
          ICSExpectedResponseFrame.buf[5] = 0x4;          //Set Byte 5 of ICS heartbeat (ID 0x306) to light the Track button LED
          Can1.write(ICSExpectedResponseFrame);           //Send modified ICS heartbeat frame to Can1
      }
        else if (frame.buf[7] == 0xB)  {                  //If "custom" mode is active
          if (ActiveDriveMode != "Custom") {              //If Custom has been newly selected
            LastDriveMode = ActiveDriveMode;              //Update last drive mode variable
            ActiveDriveMode = "Custom";                   //Set active drive mode to Custom
          }
          //ICSExpectedResponseFrame.buf[5] = 0x0;          //Set Byte 5 of ICS heartbeat (ID 0x306)
          ICSExpectedResponseFrame.buf[5] = 0x11;          //****TEST to see if Tow and Snow LEDs will light up Set Byte 5 of ICS heartbeat (ID 0x306)
          Can1.write(ICSExpectedResponseFrame);           //Send modified ICS heartbeat frame to Can1
        }     
        else if (frame.buf[7] == 0x4) {                   //If "snow" mode is active
          if  (ActiveDriveMode != "Snow") {               //If Snow has been newly selected
            LastDriveMode = ActiveDriveMode;              //Update last drive mode variable
            ActiveDriveMode = "Snow";                     //Set active drive mode to Snow
          }
          ICSExpectedResponseFrame.buf[5] = 0x10;         //Set Byte 5 of ICS heartbeat (ID 0x306) to light the Snow button LED
          Can1.write(ICSExpectedResponseFrame);           //Send modified ICS heartbeat frame to Can1
        }           
        else if (frame.buf[7] == 0x7) {                   //If "tow" mode is active
          if (ActiveDriveMode != "Tow") {                 //If Tow has been newly selected
            LastDriveMode = ActiveDriveMode;              //Update last drive mode variable
            ActiveDriveMode = "Tow";                      //Set active drive mode to Tow
          }
          ICSExpectedResponseFrame.buf[5] = 0x1;          //Set Byte 5 of ICS heartbeat (ID 0x306) to light the Tow button LED
          Can1.write(ICSExpectedResponseFrame);           //Send modified ICS heartbeat frame to Can1
        }
        else if (frame.buf[7] == 0x1) {                   //If "auto" mode is active
          if (ActiveDriveMode != "Auto") {                //If Auto has been newly selected
            LastDriveMode = ActiveDriveMode;              //Update last drive mode variable
            ActiveDriveMode = "Auto";                     //Set active drive mode to Auto
          }
          ICSExpectedResponseFrame.buf[5] = 0x0;          //Set Byte 5 of ICS heartbeat (ID 0x306) to turn off all drive mode button LEDs
          Can1.write(ICSExpectedResponseFrame);           //Send modified ICS heartbeat frame to Can1
        }
        else {
          Can1.write(ICSExpectedResponseFrame);           //Forward 0x2FA messages which don't match a filter Can1
          //printFrame(modifiedFrame, 5);                 //uncomment line to print frames forwarding to Can1
        }
      Can1.write(frame);                                  //Write 2FA frame to Can1 (ICS)
    }
  //-----------------------------------------------------------------------------------------------------------
  //30A Frame Hanlding (ICS ECO Button LED handling)-----------------------------------------------------------
    else if (frame.id == 0x30A) {                         //If the message has ID 0x30A then ...           
      ECOFrame = frame;                                   //Copy 30A frame data to the ECOFrame variable
      ECOFrame.buf[6] = frame.buf[6] ^ 0x04;              //Flip bit 3 to the opposite value since ECO button LED logic is reversed when SRT = Yes in BCM
      Can1.write(ECOFrame);                               //Write modified 30A frame to Can1 (ICS)
    }
  //-----------------------------------------------------------------------------------------------------------
  //302 Frame Handling (Update UC5 ECO Button message variable)------------------------------------------------
    else if (frame.id == 0x302){                          //If the message has ID 0x302 (UC5 message for ECO button status), then ...
      UC5ECOButtonFrame = frame;                          //Update ECO frame with current values but don't send it since it will overwrite ECO button LED
    }
  //-----------------------------------------------------------------------------------------------------------
  //3A0 Frame Handling (Update UC5 button press message variable)----------------------------------------------
    else if (frame.id == 0x3A0){                          //If the message has ID 0x3A0 (UC5 Drive Mode Button Press), then ...
      UC5DriveModeFrame = frame;                          //Update UC5 button press frame with current values but don't send it
      Can1.write(frame);                                  //Forward 0x3A0 frame to Can 1 (ICS)
    }
  //-----------------------------------------------------------------------------------------------------------
  //306 Frame Handling (Update ICS heartbeat message variable)-------------------------------------------------
    else if (frame.id == 0x306){                          //If the message has ID 0x306 (BCM Response for ICS drive mode status), then ...
      ICSExpectedResponseFrame = frame;                   //Update ICS heartbeat frame with current values but don't send it
    }
  //-----------------------------------------------------------------------------------------------------------
  //All other frames handling (frames which do not match a previous filter)------------------------------------
    else {
      Can1.write(frame);                                  //If Frame doesn't match any ID filters, forward it to the ICS unmodified
    }
  //-----------------------------------------------------------------------------------------------------------
}
//Handling of frames received from the ICS ====================================================================
void gotFrame1(const CAN_message_t &frame) {          //Function to handle messages received on Can1
  //printFrame(frame, 2);                             //uncomment line to print frames received on Can1
  //2D3 Frames Handling (ICS button presses) ------------------------------------------------------------------
  if (frame.id == 0x2D3){                             //If the message has ID 0x2D3 (ICS Drive Mode buton press), then ...
    //printFrame(frame, 9);                           //uncomment line to print ICS button press frames received on Can1
    if (frame.buf[3] == 0x20) {                       //If the ICS Sport mode button was pushed
      if (ActiveDriveMode != "Sport"){                //If Sport mode is not already active, then ...
        UC5DriveModeFrame.buf[1] = 0x2;               //Set Byte 2 of the UC5 button press message (3A0) for Sport mode
        Can0.write(UC5DriveModeFrame);                //Send modified 3A0 frame to Can0
      }
      else {                                          //Else if Sport mode is already active, then ...
        UC5DriveModeFrame.buf[1] = 0x1;               //Set Byte 2 of the UC5 button press message (3A0) for Auto mode
        Can0.write(UC5DriveModeFrame);                //Send modified 3A0 frame to Can0
      }
    }
      else if (frame.buf[3] == 0x8)  {                //If the ICS Track mode button was pushed
        if (ActiveDriveMode != "Track"){              //If Track mode is not already active, then ...
          UC5DriveModeFrame.buf[1] = 0x3;             //Set Byte 2 of the UC5 button press message (3A0) for Track mode
          Can0.write(UC5DriveModeFrame);              //Send modified 3A0 frame to Can0
        }
        else {                                        //Else if Track mode is already active, then ...
          UC5DriveModeFrame.buf[1] = 0x1;             //Set Byte 2 of the UC5 button press message (3A0) for Auto mode
          Can0.write(UC5DriveModeFrame);              //Send modified 3A0 frame to Can0      
        }
      }
      else if (frame.buf[2] == 0x80)  {               //If the ICS Tow mode button was pushed
        if (ActiveDriveMode != "Tow"){                //If Tow mode is not already active, then ...
          UC5DriveModeFrame.buf[1] = 0x7;             //Set Byte 2 for Tow mode
          Can0.write(UC5DriveModeFrame);              //Send modified frame to Can0
        }
        else {                                        //Else if Tow mode is already active, then ...
          UC5DriveModeFrame.buf[1] = 0x1;             //Set Byte 2 of the UC5 button press message (3A0) for Auto mode
          Can0.write(UC5DriveModeFrame);              //Send modified 3A0 frame to Can0      
        }
      }
      else if (frame.buf[3] == 0x1)  {                //If the ICS Snow mode button was pushed
        if (ActiveDriveMode != "Snow"){               //If Snow mode is not already active, then ...
          UC5DriveModeFrame.buf[1] = 0x4;             //Set Byte 2 for Snow mode
          Can0.write(UC5DriveModeFrame);              //Send modified 3A0 frame to Can0
        }
        else {                                        //Else if Snow mode is already active, then ...
          UC5DriveModeFrame.buf[1] = 0x1;             //Set Byte 2 of the UC5 button press message (3A0) for Auto mode
          Can0.write(UC5DriveModeFrame);              //Send modified 3A0 frame to Can0      
        }
      }
      else if (frame.buf[7] == 0x2)  {                //If the ICS ECO mode button was pushed
        UC5ECOButtonFrame.buf[4] = 0x10;              //10 for UC5 ECO Button Push
        Can0.write(UC5ECOButtonFrame);                //Send modified 302 frame to Can0
      }         
    else {
      Can0.write(frame);                              //Forward 0x2D3 heartbeat messages received on Can1 to Can0
      //printFrame(frame, 5);                         //uncomment line to print frames forwarding to Can1
    }
    Can0.write(UC5DriveModeFrame);                    //Send modified 3A0 frame to Can0 a second time for consistency
    Can0.write(UC5DriveModeFrame);                    //Send modified 3A0 frame to Can0 a third time for consistency
  }
  //-----------------------------------------------------------------------------------------------------------
  //All other Can1 (ICS) frames handling (frames which do not match a previous filter)-------------------------
  else {                                              //If frame ID does not match a previous filter, then ...
      Can0.write(frame);                              //Forward message received on Can1 to Can0
      //printFrame(frame, 5);                         //uncomment line to print frames forwarding to Can1
    }
}
//Handling of frames received from the C-bus ------------------------------------------------------------------
//void gotFrame2(const CAN_message_t &frame) {          //Function to handle messages received on Can2
//}
//-------------------------------------------------------------------------------------------------------------
//**************************************** INITIALIZATION *******************************************
void setup()
{
  Serial.begin(115200); delay(1000);
  Serial.println("Hello: Stoop's canbus MitM is starting...");
  delay(1000);

  Can0.begin();
  Can0.setBaudRate(125000);
  Can0.setMaxMB(9);
  Can0.enableFIFO();
  Can0.enableFIFOInterrupt();
  Can0.onReceive(FIFO, gotFrame0);            //Register callback for Can0
  Can0.mailboxStatus();

  Can1.begin();
  Can1.setBaudRate(125000);
  Can1.setMaxMB(9);
  Can1.enableFIFO();
  Can1.enableFIFOInterrupt();
  Can1.onReceive(FIFO, gotFrame1);            //Register callback for Can1
  Can1.mailboxStatus();

//  Can2.begin();
//  Can2.setBaudRate(500000);
//  Can2.setMaxMB(9);
//  Can2.enableFIFO();
//  Can2.enableFIFOInterrupt();
//  Can2.onReceive(FIFO, gotFrame2);            //Register callback for Can2
//  Can2.mailboxStatus();

  ICSExpectedResponseFrame.id = 0x306;
  ICSExpectedResponseFrame.buf[0] = 0x0;
  ICSExpectedResponseFrame.buf[1] = 0x0;
  ICSExpectedResponseFrame.buf[2] = 0x0;
  ICSExpectedResponseFrame.buf[3] = 0x0;
  ICSExpectedResponseFrame.buf[4] = 0x0;
  ICSExpectedResponseFrame.buf[5] = 0x0;
  ICSExpectedResponseFrame.buf[6] = 0x28;
  ICSExpectedResponseFrame.buf[7] = 0x60;

  UC5DriveModeFrame.id = 0x3A0;
  UC5DriveModeFrame.buf[0] = 0x0;
  UC5DriveModeFrame.buf[1] = 0x1;
  UC5DriveModeFrame.buf[2] = 0x0;
  UC5DriveModeFrame.buf[3] = 0x0;
  UC5DriveModeFrame.buf[4] = 0x0;
  UC5DriveModeFrame.buf[5] = 0x0;
  UC5DriveModeFrame.buf[6] = 0x0;
  UC5DriveModeFrame.buf[7] = 0x0;

  UC5ECOButtonFrame.id = 0x302;
  UC5ECOButtonFrame.buf[0] = 0x0;
  UC5ECOButtonFrame.buf[1] = 0x2;
  UC5ECOButtonFrame.buf[2] = 0x0;
  UC5ECOButtonFrame.buf[3] = 0x0;
  UC5ECOButtonFrame.buf[4] = 0x8;              //8 for ECO on, 0 for ECO off, 10 for UC5 ECO Button Push
  UC5ECOButtonFrame.buf[5] = 0x0;
  UC5ECOButtonFrame.buf[6] = 0x0;
  UC5ECOButtonFrame.buf[7] = 0x0;

  ECOFrame.id = 0x30A;
  ECOFrame.buf[0] = 0x4;
  ECOFrame.buf[1] = 0x80;
  ECOFrame.buf[2] = 0x0;
  ECOFrame.buf[3] = 0x46;
  ECOFrame.buf[4] = 0xFC;
  ECOFrame.buf[5] = 0xE;
  ECOFrame.buf[6] = 0x4;
  ECOFrame.buf[7] = 0x25;

  SportLEDFrame.id = 0x305;
  SportLEDFrame.buf[0] = 0xFF;
  SportLEDFrame.buf[1] = 0x0;
  SportLEDFrame.buf[2] = 0x0;
  SportLEDFrame.buf[3] = 0x0;
  SportLEDFrame.buf[4] = 0x1;
  SportLEDFrame.buf[5] = 0x83;
  SportLEDFrame.buf[6] = 0x4;
  SportLEDFrame.buf[7] = 0x0;
}
//**************************************** MAIN LOOP ************************************************
void loop(){                                //All CAN work is done via callback as frames come in - no need to poll for them
}
 
Last edited:
I know my code is not efficient, but it's all callback driven. So if there's no traffic on the canbuses, then it should be sitting idle. I guess the heat is just from the 600mhz processor sitting idle for so long? It's not so hot I can't touch it, so maybe 90 - 100 *F?

Any reason you need to clock it at 600MHz? Unless you're using CAN FD the maximum data rate on a CAN bus is pretty low. To be fair, even with CAN FD it's still fairly low in comparison to what the Teensy processor can handle. You could probably drop the processor speed down to 150 MHz and not see any meaningful decrease in performance. That would both reduce the self heating of the CPU and increase the maximum temperature before you start running into timing issues.
 
Any reason you need to clock it at 600MHz? Unless you're using CAN FD the maximum data rate on a CAN bus is pretty low. To be fair, even with CAN FD it's still fairly low in comparison to what the Teensy processor can handle. You could probably drop the processor speed down to 150 MHz and not see any meaningful decrease in performance. That would both reduce the self heating of the CPU and increase the maximum temperature before you start running into timing issues.

Not that I know of ... what you suggest makes sense to me.

The canbus does have a ton of traffic on it, so I just need to make sure the clock is high enough to be able to process frames fast enough to not drop anything.

I do want to expand the functionality in the future, to control things like RGB lighting, maybe add an IPS display, rotary encoder for user interface, etc ... But I don;t think any of that would be a lot of overhead.

How can I go about changing the processor speed? Can I assume the canbus speed is kept independent of the processor speed, or is it a multiplier ... thus if I change from 600Mhz to 150Mhz, do I also need to adjust something else to ensure the canbus stays at the appropriate 125khz?
 
How can I go about changing the processor speed? Can I assume the canbus speed is kept independent of the processor speed, or is it a multiplier ... thus if I change from 600Mhz to 150Mhz, do I also need to adjust something else to ensure the canbus stays at the appropriate 125khz?
Assuming you're using the arduino IDE then in the tools menu there is an option to select the CPU speed. For other tool chains there should be a similar option somewhere.
Assuming you pick from the list given by the tools then everything else should be taken care of, the libraries calculate the correct dividers for a peripheral based on the core speed and the requested data rate.

If your CAN bus is only at 125k then you have nothing to worry about. You could probably even drop the speed down to 24 MHz.
 
Assuming you're using the arduino IDE then in the tools menu there is an option to select the CPU speed. For other tool chains there should be a similar option somewhere.
Assuming you pick from the list given by the tools then everything else should be taken care of, the libraries calculate the correct dividers for a peripheral based on the core speed and the requested data rate.

If your CAN bus is only at 125k then you have nothing to worry about. You could probably even drop the speed down to 24 MHz.

Perfect, thanks. So just choose the desired CPU speed in Arduino IDE and it'll compile to suit.

No need to do anything in the code?

1710866382038.png
 
If your vehicle uses an "Ignition" signal wire to wake up the control modules, you could also use this to control when the Teensy is powered, instead of relying on bus traffic. However, I know some newer vehicles have reduced the use of the IGN wire and just use CAN bus traffic to wake many of the modules. It would be a nice feature for the library, but that's definitely out of my depth to try and contribute.
While I think dropping the CPU clock speed like Andy suggested will take care of your heat concerns, I've found on some other projects that a bit of airflow does wonders at cooling, especially if the IC's get closer to their temp limits. If the teensy is mounted in a sealed project enclosure (which can look nice), the temp can climb pretty quickly.
 
If your vehicle uses an "Ignition" signal wire to wake up the control modules, you could also use this to control when the Teensy is powered, instead of relying on bus traffic. However, I know some newer vehicles have reduced the use of the IGN wire and just use CAN bus traffic to wake many of the modules. It would be a nice feature for the library, but that's definitely out of my depth to try and contribute.
While I think dropping the CPU clock speed like Andy suggested will take care of your heat concerns, I've found on some other projects that a bit of airflow does wonders at cooling, especially if the IC's get closer to their temp limits. If the teensy is mounted in a sealed project enclosure (which can look nice), the temp can climb pretty quickly.

Great point, thanks.

I could probably find a switched power source, but was hoping to keep the power tied to the module I am controlling (ie: The integrated center stack). I did reduce to 150 Mhz, and it definitely reduced the heat. It's not anywhere near what it was before I did this. The Pi heatsink will be delivered today as well, so I think I should be good to go in terms of heat now.

I would like to try and control the power anyway though, just so it's not running 24x7. I could see after years of operation (or maybe even just weeks or months) it might cause an issue. If the Teensy locks up for example, power cycling the vehicle won't unlock it. I would have to disconnect/reconnect the battery, or unplug/replug the Teensy.

I was thinking of installing a relay maybe, to trigger on switched vehicle power. Have the line side connected to the module power harness with the coil side connected to switched power.

In terms of the install, it's not in any enclosure, just tucked into a nice spot behind the driver's side dash end cap. It makes it very convenient to just pop off the end cap with no tools, and plug a USB cable in.

When I get to the point of being finished, I may adjust this though. Maybe more than just a zip tie, and may put a USB extension on it and route to somewhere easily accessible.

1710950873784.jpeg



1710950897312.jpeg
 
just so it's not running 24x7
A T4 running at 600MHz can make surprisingly quick work of a car battery if the car sits for long periods (like winter storage or while on vacation).

While switching the T4 on/off from a ignition signal is certainly the most straight forward, there other ways to accomplish this. For example TCAN1042, which can be put in low power standby, but upon seeing a certain pattern on bus, provides a falling edge on the RX pin. I think this could then be interfaced with the Snooze library.

If the Teensy locks up
Watchdog Timers (WDT) are made for this purpose. WDTs require the CPU to perform some "servicing" action to them, otherwise the timer expires and the WDT resets the microcontroller. This can be internal to the microcontroller or an external device. The T4 has built-in WDTs, which are easy to implement with the WDT_T4 library.

https://forum.pjrc.com/index.php?threads/wdt_t4-watchdog-library-for-teensy-4.59257/

I think using the Snooze-style sleep relies on the CPU to put itself to sleep and awaken, so if the CPU freezes, it could get stuck on.
 
I suspect that the Teensy watchdogs are separate from the processing unit so might not suffer from Teensy lock up.
They would be pretty useless if they did.

If you are concerned then use an external watchdog chip/device.

You have a Hazard Switch Sense which I would guess is not continuously in the Hazzard condition. You could consider using this for reset/wakeup.
 
Thanks again guys, I appreciate the input.

After adding the heatsink and also compiling at 150 Mhz, the Teensy is now staying cool and is barely warmer than ambient to the touch even after sitting for a long period of time in the closed vehicle. I will still look to tie the power to a switched source (either directly or through a relay), but for now I'm going back to getting the functionality correct.

I have a situation where I will need to send a series of CAN messages all in a row, with varying values for the 8 bytes. What I've been doing thus far is declaring the CAN message array variable at a global level, then pre-populating the message bytes in a setup function, and then in my code manipulating individual bytes (or bits at times) before sending.

Global definition:

Code:
//***************************** DEFINITIONS **************************************************************************
#include <FlexCAN_T4.h>                           //Canbus include library for Teensy 4.0
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can0;   //Define Can0 (for IHS-bus connection)
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> Can1;   //Define Can1 (for ICS connection)
FlexCAN_T4<CAN3, RX_SIZE_256, TX_SIZE_16> Can2;   //Define Can2 (for C-bus connection, but not needed)
String ActiveDriveMode = "Auto";                  //String to store currently active drive mode
String LastDriveMode = "Auto";                    //String to store previous active drive mode. Used to "return to Auto" when ICS buttons are pushed
int ECORepurpose = 1;                             //Variable to track if the ECO button has been repurposed for SRT instead
int SendNum = 3;                                  //# of times to send UC5 drive mode commands when an ICS button is pushed (for consistency/reliability)
CAN_message_t ICSExpectedResponseFrame;           //Frame variable to work with 0x306 messages
CAN_message_t ICSSendFrame;                       //Frame variable to work with 0x2D3 messages
CAN_message_t UC5DriveModeFrame;                  //Frame variable to work with 0x3A0 messages
CAN_message_t ECOFrame;                           //Frame variable to work with 0x30A messages
CAN_message_t SportLEDFrame;                      //Frame variable to work with 0x3B2 messages
CAN_message_t UC5ECOButtonFrame;                  //Frame variable to work with 0x302 messages
CAN_message_t SportSplashFrame;
CAN_message_t TestFrame;

Function call in setup:

Code:
void setup() {
  SetupDefaultFrames();                       //Call function to pre-populate frame bytes at start of program
}

Function to pre-populate with default values:

Code:
void SetupDefaultFrames() {
  ICSExpectedResponseFrame.id = 0x306;
  ICSExpectedResponseFrame.buf[0] = 0x0;
  ICSExpectedResponseFrame.buf[1] = 0x0;
  ICSExpectedResponseFrame.buf[2] = 0x0;
  ICSExpectedResponseFrame.buf[3] = 0x0;
  ICSExpectedResponseFrame.buf[4] = 0x0;
  ICSExpectedResponseFrame.buf[5] = 0x0;
  ICSExpectedResponseFrame.buf[6] = 0x28;
  ICSExpectedResponseFrame.buf[7] = 0x60;
  ICSSendFrame.id = 0x2D3;
  ICSSendFrame.buf[0] = 0x7;
  ICSSendFrame.buf[1] = 0xFC;
  ICSSendFrame.buf[2] = 0x0;
  ICSSendFrame.buf[3] = 0x0;
  ICSSendFrame.buf[4] = 0x0;
  ICSSendFrame.buf[5] = 0x0;
  ICSSendFrame.buf[6] = 0x0;
  ICSSendFrame.buf[7] = 0x0;
  UC5DriveModeFrame.id = 0x3A0;
  UC5DriveModeFrame.buf[0] = 0x0;
  UC5DriveModeFrame.buf[1] = 0x1;
  UC5DriveModeFrame.buf[2] = 0x0;
  UC5DriveModeFrame.buf[3] = 0x0;
  UC5DriveModeFrame.buf[4] = 0x0;
  UC5DriveModeFrame.buf[5] = 0x0;
  UC5DriveModeFrame.buf[6] = 0x0;
  UC5DriveModeFrame.buf[7] = 0x0;
  UC5ECOButtonFrame.id = 0x302;
  UC5ECOButtonFrame.buf[0] = 0x0;
  UC5ECOButtonFrame.buf[1] = 0x2;
  UC5ECOButtonFrame.buf[2] = 0x0;
  UC5ECOButtonFrame.buf[3] = 0x0;
  UC5ECOButtonFrame.buf[4] = 0x8;              //8 for ECO on, 0 for ECO off, 10 for UC5 ECO Button Push
  UC5ECOButtonFrame.buf[5] = 0x0;
  UC5ECOButtonFrame.buf[6] = 0x0;
  UC5ECOButtonFrame.buf[7] = 0x0;
  ECOFrame.id = 0x30A;
  ECOFrame.buf[0] = 0x4;
  ECOFrame.buf[1] = 0x80;
  ECOFrame.buf[2] = 0x0;
  ECOFrame.buf[3] = 0x46;
  ECOFrame.buf[4] = 0xFC;
  ECOFrame.buf[5] = 0xE;
  ECOFrame.buf[6] = 0x4;
  ECOFrame.buf[7] = 0x25;
  SportLEDFrame.id = 0x3B2;
  SportLEDFrame.buf[0] = 0x41;
  SportLEDFrame.buf[1] = 0x0;
  SportLEDFrame.buf[2] = 0xA2;
  SportLEDFrame.buf[3] = 0x0;
  SportLEDFrame.buf[4] = 0x0;
  SportLEDFrame.buf[5] = 0xFF;
  SportLEDFrame.buf[6] = 0xFF;
  SportLEDFrame.buf[7] = 0x3F;
  SportSplashFrame.id = 0x305;
  SportSplashFrame.buf[0] = 0xFF;
  SportSplashFrame.buf[1] = 0x0;
  SportSplashFrame.buf[2] = 0x0;
  SportSplashFrame.buf[3] = 0x0;
  SportSplashFrame.buf[4] = 0x1;
  SportSplashFrame.buf[5] = 0x83;
  SportSplashFrame.buf[6] = 0x4;
  SportSplashFrame.buf[7] = 0x0;
  TestFrame.id = 0x000;
  TestFrame.buf[0] = 0x0;
  TestFrame.buf[1] = 0x0;
  TestFrame.buf[2] = 0x0;
  TestFrame.buf[3] = 0x0;
  TestFrame.buf[4] = 0x0;
  TestFrame.buf[5] = 0x0;
  TestFrame.buf[6] = 0x0;
  TestFrame.buf[7] = 0x0;
}

What I want to test is a series of CAN ID 0x328 sending all in a row.

As an example, if I wanted to send the following 13 messages:

0x3286042054068065
0x32850202004A075
0x32840206C069065
0x32830202004D061
0x32820207306F06E
0x328102020053068
0x3280206F077020
0x328504304506C069
0x328403065020048
0x32830306F06E069
0x32820306702002D
0x32810302004304E
0x3280304E0000

Instead of creating 13 new CAN message variables, I'd like to declare these Bytes in an array and then loop a single message variable to populate with each row in the array and send.

I've been reading the Arduino reference material, and various forum posts, but it's not obvious to me how to accomplish this.

Let's say I want to use the TestFrame can message variable I already have declared to send, would the code be something like this?

Code:
uint16_t 328Matrix[12][7];
CAN_message_t TestFrame;

void setup() {
  SetupDefaultFrames();                       //Call function to pre-populate frame bytes at start of program
}

void SetupDefaultFrames() {

328Matrix[12][7] = { {60,42,00,54,00,68,00,65}
                     {50,02,00,20,00,4A,00,75}
                     {40,02,00,6C,00,69,00,65}
                     ... 
                     ...
                   }
}

With all 13 messages in the array of course ...

and then in my running program, do something like:

Code:
TestFrame.id = 0x328;
for (int j=0, J<13, J++) {
   for (int i=0, i<8, i++){
      TestFrame.buf[i] = 328Matrix.buf[j][i];
   }
   Can0.send (TestFrame);
}

I realize my declarations and syntax above are incorrect, as I get errors when compiling; but I can't seem to figure out the correct syntax to accomplish this.

I've spend a couple of hours digging through documentation adn forum posts, but I think my knowledge gap is still too large to figure it out :(

I know this isn't correct: uint16_t 328Matrix[12][7];

Should it be something like: CAN_message_t 328Matrix[12][7]; instead?
 
Surely the array sizes should be 13 and 8 instead of 12 and 7

And I think you would want a data type of uint8_t instead of uint16_t
 
Last edited:
Back
Top