FlexCAN_T4 - FlexCAN for Teensy 4

Which CAN transceiver are you using ? How is it connected to the Teensy ?

Post a photo of your setup.
 
Which CAN transceiver are you using ? How is it connected to the Teensy ?

Post a photo of your setup.

Hello skpang,

I'm using standard mcp2551 as you can see in my schematic below, I've done a pcb to connect everything.
I've been using this same schematic for years without problems but I was used to ST and other libraries.
Still, dont know if this is a hardware thing or library issue.

https://ibb.co/K6mbMXB
 
The MCP2551 is 5v supply with 5v IO. The Teensy 3.6 is not 5v tolerant on the input. You may have damaged the input pin.

I would use the MCP2562 with 5v supply and 3.3v IO.
 
The MCP2551 is 5v supply with 5v IO. The Teensy 3.6 is not 5v tolerant on the input. You may have damaged the input pin.

I would use the MCP2562 with 5v supply and 3.3v IO.

OK, that could be a problem, but the input is not damaged because after a power cycle on teensy everything seems to work fine.
Can the input be clamped by internal diodes or protected and only a power cycle solves this?
 
Hello,

I use flexcan_t4 library for the first time and I figure that when I start the CAN, there are messages on Serial0 with a teensy 4.0.

My code :
Code:
  Can0.begin();
  Can0.setBaudRate(CANSpeed);
  Can0.setMaxMB(16);
  Can0.enableFIFO();
  Can0.enableFIFOInterrupt();
  Can0.onReceive(canSniff);
  Can0.mailboxStatus();
  return;

What is outputted to serial :
Code:
FIFO Enabled --> Interrupt Enabled
	FIFO Filters in use: 8
	Remaining Mailboxes: 8
		MB8 code: TX_INACTIVE
		MB9 code: TX_INACTIVE
		MB10 code: TX_INACTIVE
		MB11 code: TX_INACTIVE
		MB12 code: TX_INACTIVE
		MB13 code: TX_INACTIVE
		MB14 code: TX_INACTIVE
		MB15 code: TX_INACTIVE

How can I disable library messages ? I use Serial for communication with another devices and can't have any "unknown" messages.

Thank you,
Manu
 
Hi everybody,
I hardly try to make my CAN works on a bus of 5 units. The first and the last have terminaison resistor of 12O ohm. Tranceivers : MCP2551 5 volt
I tried to use CAN2 on teensy 4.1 but there is no signal on CTX2.
Here my code :
Code:
#include <FlexCAN_T4.h>
#include <Arduino.h>
#include <Bounce.h>
#include <RGB_LED.h>
#include "music.h"
#include "config.h"

void canCallback(const CAN_message_t &msg);

void canSend(uint8_t data);

void parseCAN(CAN_message_t msg);

FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> can;
CAN_message_t in_msg;
Bounce ir_detect(DETEC_PIN, 100);
RGB_LED rgb_led(RED_PIN, GREEN_PIN, BLUE_PIN);
#ifdef ARCHE_MASTER
Metro timer = Metro(INTERVAL_PONG);
Metro pong = Metro(INC_LED_PONG);
bool pong_state = true;
uint8_t led_inc = 0;
#endif

void setup() {
    pinMode(DETEC_PIN, INPUT);
#if DBG
    Serial.begin(115200);
    while (!Serial) { delay(10); }
#endif
    DBG_PRINT("Arche : ");
    DBG_PRINTLN(ARCHE_NO);
    can.begin();
    can.setBaudRate(500000);
    can.setMaxMB(16);
    can.enableFIFO();
    can.enableFIFOInterrupt();
    can.onReceive(canCallback);
    can.mailboxStatus();

    //music_init();
    rgb_led.set(0, 0, 0);
#ifdef ARCHE_MASTER
    timer.reset();
#endif
}

void loop() {
    can.events();
    rgb_led.run();
    if (can.read(in_msg)) {
        DBG_PRINTLN("CAN RX");
        parseCAN(in_msg);
    }
    delay(20);
}

void canCallback(const CAN_message_t &msg) {
    parseCAN(msg);
}

void canSend(uint8_t data) {
    CAN_message_t send_msg;
    send_msg.id = ARCHE_NO;
    send_msg.id = 0x00;
    send_msg.len = CAN_FRAME_LEN;
    send_msg.buf[0] = START_CAN_FRAME;
    send_msg.buf[1] = data;
    send_msg.buf[2] = STOP_CAN_FRAME;
    can.write(send_msg);
    //can.setMB(MB8, TX, STD);
}

void parseCAN(CAN_message_t msg) {
    DBG_PRINTF("CAN RX ID : %d | Len : %d\n", msg.id, msg.len);
    DBG_PRINT("Data : ");
#if DBG
    for (int i = 0; i < msg.len; i++) {
        DBG_PRINT(msg.buf[i]);
    }
#endif
    DBG_PRINTLN();

#ifdef ARCHE_MASTER
    if ((msg.id > 0x01) && (msg.id < 0x06) && (msg.len == CAN_FRAME_LEN)) {
        if (msg.buf[0] == START_CAN_FRAME && msg.buf[2] == STOP_CAN_FRAME) {
            if (msg.buf[1] == DETECT_CAN_FRAME) {
                DBG_PRINTLN("DETECT");
                timer.reset();
                pong_state = false;
            } else {
                DBG_PRINTLN("Wrong CAN frame");
            }
        }
    }
#else
    if ((msg.id > 0x00) && (msg.id < 0x06) && (msg.len == CAN_FRAME_LEN)) {
        if (msg.buf[0] == START_CAN_FRAME && msg.buf[2] == STOP_CAN_FRAME) {
            if (msg.buf[1] == DETECT_CAN_FRAME) {
                rgb_led.set(0, 0, 0);
            } else if ((msg.buf[1] & 0xD0) == PONG_CAN_FRAME) {
#if ARCHE_NO == 2
                if (msg.buf[1] == 0xD1) {
                    rgb_led.set(LED_COLOR);
                } else if (msg.buf[1] == 0xD2) {
                    rgb_led.set(0, 0, 0);
                }
#elif ARCHE_NO == 3
                if (msg.buf[1] == 0xD2) {
                rgb_led.set(LED_COLOR);
                }
            else if (msg.buf[1] == 0xD3) {
                rgb_led.set(0,0,0);
            }
#elif ARCHE_NO == 4
            if (msg.buf[1] == 0xD3) {
                rgb_led.set(LED_COLOR);
                }
            else if (msg.buf[1] == 0xD4) {
                rgb_led.set(0,0,0);
            }
#elif ARCHE_NO == 5
            if (msg.buf[1] == 0xD4) {
                rgb_led.set(LED_COLOR);
                }
            else if (msg.buf[1] == 0xD0) {
                rgb_led.set(0,0,0);
            }
#endif

            } else {
                DBG_PRINTLN("Wrong CAN frame");
            }
        }
    }
#endif
}

And the serial monitor :
Code:
FIFO Enabled
Mailboxes:
MB8 code: TX_INACTIVE
MB9 code: TX_INACTIVE
MB10 code: TX_INACTIVE
MB11 code: TX_INACTIVE
MB12 code: TX_INACTIVE
MB13 code: TX_INACTIVE
MB14 code: TX_INACTIVE
MB15 code: TX_INACTIVE
Mailboxes are empty after sending something.
If anyone can help me I would be amazing.
Thanks in advance
Axoul
 
Howdy, In the process of porting my code tested on a UNO coded for polling to a teensy 4.0 using FIFO with interrupts. Everything connects, writes, and reads fine however the frames are not ordered as seen in the logs below. I'm expecting Oil and Coolant temp to be cycled in order. I’m using UDS over canbus whereby data requests is similar to OBD ie: request/receive. Filtering is working as expected. The polling speed on either the UNO or Teensy is constant 30ms which is governed by the OBD gateway in the car.

I have read a few posts suggesting to use message sequencing (msg.seq=1), setMaxMB(9) to setup only 1 transmit MB or removing .events in the loop but have had no joy finding the correct combination. Obviously I can just move back to polling but most of the threads that I have read suggest to use interrupts with the teensy due to the processing speed.

Below is a snippet of the pertinent code elements and the associated serial monitor outputs. Any guidance or recommendations appreciated.

Code:

Code:
#include <FlexCAN_T4.h>
#include <EasyNextionLibrary.h>
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> can1;

#define OUTPUT_TERMINAL true                                                            			// set to false not to output to serial terminal and before production compile
#define OUTPUT_TERMINAL_SERIAL if(OUTPUT_TERMINAL)Serial

CAN_message_t msg;

void canSniff(const CAN_message_t &msg) {
  #if OUTPUT_TERMINAL == true 
    Serial.print("MB "); Serial.print(msg.mb);
    Serial.print(" OVERRUN: "); Serial.print(msg.flags.overrun);
    Serial.print(" LEN: "); Serial.print(msg.len);
    Serial.print(" EXT: "); Serial.print(msg.flags.extended);
    //Serial.print(" TS: "); Serial.print(msg.timestamp);
    Serial.print(" ID: "); Serial.print(msg.id, HEX);
    Serial.print(" Buffer: ");

    for ( uint8_t i = 0; i < msg.len; i++ )                                                         // print the data
    { 
        Serial.print(" ");
        if (msg.buf[i] < 16)                                                                    	   // Pad with 0 for HEX values less than 0x10
        {
            Serial.print("0");
        } 
        Serial.print(msg.buf[i], HEX);
    }
    if (msg.len == 6)                                                                           	   // Padding out with 00 for null byte fields so that debug output table looks nice
    { 
        Serial.print(" ");
        Serial.print("00");
    }
    Serial.print(" ");
  #endif
  unsigned int sensorID = (msg.buf[3] << 8) | msg.buf[4];               							          
  switch(sensorID){
    case 0x5822:                                                         				  // HEX 5822 - Oil Temperature in C
        {
        unsigned char OilTemperature = msg.buf[5];
        OilTemperature = OilTemperature - 60;
        #if OUTPUT_TERMINAL == true 
            Serial.print("OilTemperature:\t\t");
            Serial.print(OilTemperature);
        #endif    
        }                    
    break; 
    case 0x4300:                                                         				 // HEX 4300 - Coolant Temperature in C
        {
        unsigned char CoolantTemperature = msg.buf[5];
        CoolantTemperature = CoolantTemperature * 0.75 - 48;
        #if OUTPUT_TERMINAL == true 
            Serial.print("CoolantTemperature:\t");
            Serial.print(CoolantTemperature);
        #endif 
        }                    
    break;
  }
  Serial.print("\tTS: "); Serial.print(millis());
  Serial.print("\tTS: "); Serial.println(msg.timestamp);
}

void setup(void) {
  #if OUTPUT_TERMINAL == true
    Serial.begin(250000); delay(6000);                                              		  // Delay to allow PlatformIO console monitor time to start. Otherwise miss the initial maiboxstatus
  #endif

  can1.begin();
  can1.setBaudRate(500000);
  can1.setMaxMB(16);
  //can1.setMaxMB(9);
  //can1.setMaxMB(32);
  can1.enableFIFO();
  can1.enableFIFOInterrupt();
  
  can1.setFIFOFilter(REJECT_ALL);
  can1.setFIFOFilter(0, 0x612, STD); 
  can1.onReceive(canSniff);
  #if OUTPUT_TERMINAL == true
    can1.mailboxStatus();
  #endif
}

void rOilTemperature(){
    //unsigned char stmp[8] = {0x12,0x03,0x22,0x58,0x22,0x00,0x00,0x00}; 
    msg.buf[0] = 0x12; msg.buf[1] = 0x03; msg.buf[2] = 0x22; msg.buf[3] = 0x58; msg.buf[4] = 0x22; msg.buf[5] = 0x00; msg.buf[6] = 0x00; msg.buf[7] = 0x00;
    can1.write(msg);                                                
}

void rCoolantTemperature(){
    //unsigned char stmp[8] = {0x12,0x03,0x22,0x43,0x00,0x00,0x00,0x00};
    msg.buf[0] = 0x12; msg.buf[1] = 0x03; msg.buf[2] = 0x22; msg.buf[3] = 0x43; msg.buf[4] = 0x00; msg.buf[5] = 0x00; msg.buf[6] = 0x00; msg.buf[7] = 0x00;
    can1.write(msg);                                              
}

void loop() {
  can1.events();
  //msg.seq = 1; 
  msg.id = 0x6F1; msg.len = 8;
  rCoolantTemperature();
  rOilTemperature(); 
}

setMaxMB(16)

Code:
FIFO Enabled --> Interrupt Enabled
        FIFO Filters in use: 8
        Remaining Mailboxes: 8
                MB8 code: TX_INACTIVE
                MB9 code: TX_INACTIVE
                MB10 code: TX_INACTIVE
                MB11 code: TX_INACTIVE
                MB12 code: TX_INACTIVE
                MB13 code: TX_INACTIVE
                MB14 code: TX_INACTIVE
                MB15 code: TX_INACTIVE
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 45635 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16267
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 60608 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:          28      TS: 16297
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 9926  ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16327
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 24994 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16357
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 39848 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:          28      TS: 16387
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 54940 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16417
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 4710  ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16448
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 19326 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16477
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 34656 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16508
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 49272 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16537
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 64578 ID: 612 Buffer:  F1 04 62 43 00 65 00 CoolantTemperature:      27      TS: 16568
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 13777 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:          28      TS: 16597
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 TS: 28988 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:          28      TS: 16628


setMaxMB(9)

Code:
FIFO Enabled --> Interrupt Enabled
        FIFO Filters in use: 8
        Remaining Mailboxes: 1
                MB8 code: TX_INACTIVE
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:            28      TS: 8309
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 43 00 66 00 CoolantTemperature:        28      TS: 8332
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 43 00 66 00 CoolantTemperature:        28      TS: 8361
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 43 00 66 00 CoolantTemperature:        28      TS: 8391
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:            28      TS: 8421
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:            28      TS: 8452
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 43 00 66 00 CoolantTemperature:        28      TS: 8482
MB 99 OVERRUN: 0 LEN: 6 EXT: 0 ID: 612 Buffer:  F1 04 62 58 22 58 00 OilTemperature:            28      TS: 8511
 
Last edited:
you shouldn't need to strip mailboxes down to one using msg.seq = 1, it will only output to the absolute first mailbox. also you don't need events() in loop if you want direct firing interrupts, but that's optional. As far as I see your loop is flooding the bus and the ECU is responding whatever is available in memory of the burst. At this point since you're transmitting too fast to the ECU, the ECU will block some requests until it finishes with the response, which will flow back to you in a seemingly unordered state, but in reality it's the order the ECU prepared the response during a request while invalidating the excessive frames.usually you need at least a 10ms gap between ECU requests, I don't see any delays in your loop(), add a delay(15) somewhere in your loop and try it again

once it works, replace delay with millis() or elapsedmillis.

yes the 30ms is consistant with the gateway's responses, but your transmission speeds are too fast for the ECU/gateway to handle when you are constantly sending them in a forever loop, if it responds at 30, send at 45ms intervals for example, in some cases ECU responses may come quicker if it's not being flooded or talked to in awhile
 
Last edited:
you shouldn't need to strip mailboxes down to one using msg.seq = 1, it will only output to the absolute first mailbox. also you don't need events() in loop if you want direct firing interrupts, but that's optional. As far as I see your loop is flooding the bus and the ECU is responding whatever is available in memory of the burst. At this point since you're transmitting too fast to the ECU, the ECU will block some requests until it finishes with the response, which will flow back to you in a seemingly unordered state, but in reality it's the order the ECU prepared the response during a request while invalidating the excessive frames.usually you need at least a 10ms gap between ECU requests, I don't see any delays in your loop(), add a delay(15) somewhere in your loop and try it again

once it works, replace delay with millis() or elapsedmillis.

yes the 30ms is consistant with the gateway's responses, but your transmission speeds are too fast for the ECU/gateway to handle when you are constantly sending them in a forever loop, if it responds at 30, send at 45ms intervals for example, in some cases ECU responses may come quicker if it's not being flooded or talked to in awhile

Thanks for the tips. I checked my UNO code and the reason it works in the correct sequenced order is because I had a check in their waiting for the frame to hit the buffer before processing. As suggested for testing purposes I had to add a 30ms delay on each sensor request which fixed the sequencing. Anything lower no dice.

I found this article which explained the reasons to use millis() vs delay LINK which all makes sense however I don't see an elegant way of incorporating this into my sketch. Its hard to explain but I will try.

The output is to a nextion display that has multiple pages. Each page has a different set of sensors with some that overlap with other pages. When sending data to the display I'm only sending sensor data that is displayed on that page. There are about 50 sensors in total. When using the polling method I can execute each sensor function call sequentially within the loop and the parsing function will wait till the frame exists in the buffer before doing the calculations. In essence the "wait" is controlled by the parsing function. Using interrupts the "wait" needs to be controlled within the Loop function to control when the sensor function should request the data ie pre vs post. This is where I'm stuck in the logic on how to only request the data 30ms apart while keeping the requests ordered per page.

Hope that makes sense. Any pseudo wisdom appreciated.
 
what i always do (my own preference of course) is setup a scoped millis() near the action you want to do, so in the loop i would put:

Code:
void loop() {
  static uint32_t coolant_timer = millis();
  if ( millis() - coolant_timer >= 30 ) {
    rCoolantTemperature():
    coolant_timer = millis();
  }
}

this will run your function every 30ms

If your ECU can process both frames at same time and send them back in same order (provided it's not flooded with requests), then you can put both functions in there, if not, you could tell the scope to run a next command after 30 ms


Code:
void loop() {
  static uint32_t coolant_timer = millis();
  static int send_switch = 0;
  if ( millis() - coolant_timer >= 30 ) {
    if ( send_switch > 1 ) send_switch = 0;
    if ( send_switch == 0 ) rCoolantTemperature():
    if ( send_switch == 1 ) rOilTemperature();
    send_switch++;
    coolant_timer = millis();
  }
}
 
This works great! I'm streaming data on several vehicles now, and I want to start sending OBD2 PID requests. I saw the example for writes. Should I use this as a basis? What configurations do I need to worry about?

Example from git below. I assume those write options are either/or?

void loop() {
static uint32_t sendTimer = millis();
if ( millis() - sendTimer > 1000 ) {
uint8_t buf[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 };
const char b[] = "01413AAAAABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
ISOTP_data config;
config.id = 0x666;
config.flags.extended = 0; /* standard frame */
config.separation_time = 10; /* time between back-to-back frames in millisec */
tp.write(config, buf, sizeof(buf));
tp.write(config, b, sizeof(b));
sendTimer = millis();
}
}
 
well thats for isotp.

for pid requests you can just use the following (edited as needed)

Code:
    CAN_message_t msg;
    msg.id = random(0x1,0x7FE);
    for ( uint8_t i = 0; i < 8; i++ ) msg.buf[i] = i + 1;
    Can0.write(msg);
 
Thank you. What an amazing service you are providing. Teensy is an amazing "grown up" Arduino option, and your library is an amazing add to that for car dudes. I couldn't imagine a better development platform.
 
@tony could you help me write a simple sketch with the isotp addon?

Today I am using MBs and three different callbacks (each per ID)
I would like to send the following frame:
0x07, 0x22, 0xF4, 0x33, 0xF4, 0x0F, 0xF4, 0x3C
Response would be
0x10, 0x0D,0x62, 0xF4, 0x33, 0x00, 0xF4, 0x0F, 0x00, 0xF4, 0x3C, 0x00, 0x00, 0x00, 0x00

At the moment when I receive the fist frame I send a flow control, then get the rest of the payload.
How would I manage this in isotp? I recall there is no support for mailboxes and I would have to check the ID in the general isotp callback - is this still the case?
Is there no way to assign an isotp callback per mailbox?
 
Ive seen the example, but what’s not clear is do I have to send the flow control to the ECU or does the library do that when it receives the first frame?
 
ahh i see. the way the library handles isotp between teensies is it sends the complete isotp frames. So for reception that is not complete, an isotp first frame is discarded if overridden as needed from the queue, and never fired to the user.

basically if you send a VIN request using Can0.write(msg) normal frame, yes you need to send a flow frame. The library just rebuilds the ECU frames it doesnt do the requests. if you dont send the flow control the first frame sits in queue to do nothing, or be overridden as queue space permits

for teensy to teensy or teensy to esp32, they send the complete payload based on the example format, and since the payload is complete, after the rebuild is successful the user callback is fired with it

so yes for the ecu you need to send the flow control, teensy just captures full payloads, and rebuilds and fire as needed

the isotp data in the example is the flow control just for the payload teensy is sending to send at those specific rates
 
So perhaps I could register a regular callback to check if the frame starts with 0x10, then send a flow control,
And I would receive the full payload in the isotp callback? Would that be possible?
 
Back
Top