FlexCAN_T4 - FlexCAN for Teensy 4

Thank you tonton81,
I am so stupid, I could not see the whole codes but stuck around CAN setting.
must correct basic logic coding part first before asking question.....
 
Hello tonton81,

We have been using Teensy 3.X boards for a while now with Collin80's FlexCan_Library. In his library there is a CANListener class that has a frameHandler function that can be declared in a user defined class so that the user's own library can have access to the received frames.

We have just started using a Teensy 4.0 on our setup and we had the need to switch to your library which from what I read here it is really well maintained!

In order to implement a similar behaviour to what we had before I think that we should use one of the ext_output functions. I have checked your TeensyCan library also but I can't seem to understand how should I implement this in my own class.

Below is the way we used to do it with Collin80's library:
Code:
class TSB_CAN : public CANListener 
{    
public:
    TSB_CAN();
    void receive_Message(CAN_message_t &frame);
    bool frameHandler(CAN_message_t &frame, int mailbox, uint8_t controller); //overrides the parent version so we can actually do something
};

bool TSB_CAN::frameHandler(CAN_message_t &frame, int mailbox, uint8_t controller)
{
		//printFrame(frame, mailbox);
		receive_Message(frame);
		return true;
}

void TSB_CAN::receive_Message(CAN_message_t &frame)
{
    switch(frame.id) {
	case 0x609:
    	    //save message payload
	break; 
    }
}

Then on the main sketch we did something like this:
Code:
TSB_CAN canMessageHandler;
void setup(void){
  Can0.begin(1000000); // Inicialise CAN BUS with 1 Mb/s speed
  Can0.attachObj(&canMessageHandler);
  canMessageHandler.attachGeneralHandler();
}

void loop(void)
{
  // If you want to print something that is avalible on the CAN Bus check the CAN_TSB.h to see the structer used.
  // As an example lets print the battery current
  Serial.println(canMessageHandler.device.variable); 
}
 
just add this to your library after including flexcan header:
Code:
void ext_output1(const CAN_message_t &msg) {
  //do whatever with message
}

and enable MB/FIFO interrupts in setup, the function will receive all frames, unless you use filters, then the mailbox/fifo will only stream your filtered IDs

If it's for a library, thats the way to go. If you are doing it from the sketch like above, either works. you can use onRecieve to set your sketch callback, but the background handler is always unused until you define it like i mentioned, either in sketch or library

don't forget, to differentiate which bus the frame came from, use msg.bus and it will return 1,2 or 3, depending on what you setup (CAN1, CAN2, CAN3)
 
What if I want that function to be part of my own C++ class so that I can do an Object Oriented Callback Interface? Is there any possibility to do so with this library?
 
Last edited:
sorry i dont understand how that works, but maybe i can look at how Collin did it and see if i can port it over
 
Can you try this patch? Originally it only supported 31 mailboxes max, as Teensy 3.x had 16 mailboxes, bit 31 was used for the global handler. Now it should support up to 64 mailboxes with a general handler.
Also if the mailbox handler was specified the generalhandler wouldn't fire, in case you did specify both ways, both will activate now.

Code:
[ATTACH]23426._xfImport[/ATTACH]

Serial monitor:
Code:
ID: FADE Data: 80 40 0 3 7 1 1 [COLOR="#FF0000"] <------------------- CANListener[/COLOR]

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 58671 ID: FADE Buffer: 80 40 0 3 7 1 1 [COLOR="#FF0000"] <------- FlexCAN_T4 callback[/COLOR]

ID: FADE Data: 81 0 1 0 9 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 5044 ID: FADE Buffer: 81 0 1 0 9 1 1 

ID: FADE Data: 80 80 7 4 0 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 53021 ID: FADE Buffer: 80 80 7 4 0 1 1 

ID: FADE Data: 80 40 0 A 0 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 7888 ID: FADE Buffer: 80 40 0 A 0 1 1 

ID: FADE Data: 80 80 0 A 0 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 36067 ID: FADE Buffer: 80 80 0 A 0 1 1 

ID: FADE Data: 80 80 2 0 8 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 2446 ID: FADE Buffer: 80 80 2 0 8 1 1 

ID: FADE Data: 80 40 9 0 1 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 23012 ID: FADE Buffer: 80 40 9 0 1 1 1 

ID: FADE Data: 81 0 8 2 0 1 1 

MB 99 OVERRUN: 0 LEN: 7 EXT: 1 TS: 35028 ID: FADE Buffer: 81 0 8 2 0 1 1
 
First of all thank you very much for you willingness to help! I can confirm that this works in a Teensy 3.5 with a Kvaser generating random CAN data. I have another Teensy 3.2 board with me but the transceiver on it is not working... When I'm able to test it with other boards I can let you know, but I'm pretty sure that if it works on 3.5 and 4.X (I suppose you test on that) it will work on the rest of them as well.

Can you explain what is the diference between using FIFO and Mailboxes? I think that with FIFO we can assume that the messages are in order. On the other hand with MB this is not guaranteed. Is this line of though right? I have tested this patched version with both and it worked with both also.

Will this patched version be added to the TeensyDuino?

On another note I noticed that the library allows to define CAN1 on Teensy 3.2 and 3.5, I think that the following code (FlexCAN_T4 line 53) should be made in 2 ifs one for 3.2 and 3.5 and another for the 3.6:
Code:
#if defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
  if ( _bus == CAN1 ) _CAN1 = this;
  if ( _bus == CAN0 ) _CAN0 = this;
#endif

Once again THAK YOU VERY MUCH!!
 
for the define, thats just a pointer, _CAN1 wont compile on T3.2 anyways

FIFO allows ordered receptions, mailboxes can receive in any order as long as a slot is empty. you can have FIFO and mailboxes combined for example, assign a mailbox to receive a filtered ID in it's own callback, so a separate callback will fire when your critical frame is received. with fifo alone you have only 1 callback essentially. In either case Teensy is fast enough to receive all critical frames without filters unless you are in non interrupt mode or your loop code is causing delays..
 
At least for me, with Paltformio if I declare
Code:
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> Can0;

it compiles for Teensy 3.2 and 3.5 but it obviously does not do anything.


Thank you very much for the explanation!
 
Indeed, it compiled in the arduino IDE as well I see, I just don't wanna add much code bloat. Going ahead after compilation should result in undefined behaviour though should the wrong one be used. I suppose a cleaner solution is to ifdef and give a warning if _bus is not identified in hardware rather than sprinkle more ifdef else's all over the place
 
You can try this?

Code:
[ATTACH]23429._xfImport[/ATTACH]


added checks:
Code:
#if defined(__MK20DX256__) || defined(__MK64FX512__)
  static_assert(_bus == CAN0, "Only CAN0 works on Teensy 3.2/3.5");
#endif
#if defined(__MK66FX1M0__)
  static_assert(_bus == CAN0 || _bus == CAN1, "Only CAN0 & CAN1 works on Teensy 3.6");
#endif
#if defined(__IMXRT1062__)
  static_assert(_bus == CAN1 || _bus == CAN2 || _bus == CAN3, "Only CAN1 & CAN2 & CAN3 works on Teensy 4.0/4.1");
#endif
 
It works as expected! Both on Teensy 3.2 and 3.5.

Compiler throws:
Only CAN0 works on Teensy 3.2/3.5
static_assert(_bus == CAN0, "Only CAN0 works on Teensy 3.2/3.5");
^

*** [.pio/build/teensy35/src/main.cpp.o] Error 1

By the way, trying to declare CAN2 on T3.6 although it gives an error it is not the same fancy error as in the other case. But for me it is fine, as long as we get an error it's fine. It just says that "CAN2 was not declared in this scope".
 
Yeah, that would require defining CAN2, CAN3, asserts should work as is:

Code:
typedef enum CAN_DEV_TABLE {
#if defined(__IMXRT1062__)
  [COLOR="#006400"]CAN0 = (uint32_t)0x0,[/COLOR]   [COLOR="#FF0000"]<------------- This was added to support the static_assert instead of a compiler crash saying it wasn't in scope for T4.x[/COLOR]
  CAN1 = (uint32_t)0x401D0000,
  CAN2 = (uint32_t)0x401D4000,
  CAN3 = (uint32_t)0x401D8000
#endif
#if defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
  CAN0 = (uint32_t)0x40024000,
  CAN1 = (uint32_t)0x400A4000,  [COLOR="#FF0000"]<-------- CAN2 could be added after here like above, like so[/COLOR] 
[COLOR="#008000"][B]  CAN2 = (uint32_t)0x0,
  CAN3 = (uint32_t)0x0,
[/B][/COLOR]#endif
} CAN_DEV_TABLE;

Just added em, try to break CANListener and if you can't I will update it on github :p
 
Last edited:
Thank you, tonton81,
Since I could not do a good job with FIFO, I went back to mail box stuff anyway.
So far I can send and receive OBD CAN message with extended 29 bit CAN communication.
Nevertheless, my goal is not getting PID parameters such as engine speed, engine coolant temperature, but longer messages which requires multi-frame (ISO-15765-2).
In order to handle multi-frame stuff, Teensy has to receive a multiple consecutive frames after sending flow control from Teensy.
Therefore I probably have to go back to FIFO again. I will read through FlexCAN_T4 library and try to understand how to use.

Anyway following code works with my vehicle.

Code:
// 29bit extended OBD CAN Sketch
//  get Service$01 PID$0C Engine Speed
//

#include <FlexCAN_T4.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ADC.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> can2;

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };


int engineCoolantTemp = 0; // Save the data here
int engineRPM = 0;// Save the data here


void setup(){
  while (!Serial && millis() < 5000);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64

    delay(200);
    
    Serial.println(F("SSD1306 allocation failed"));
    
    for(;;); // Don't proceed, loop forever
  }
  
  Serial.begin(115200);
  can2.begin();
  can2.setBaudRate(500000);
  //can2.setFIFOFilter(REJECT_ALL);
  //can2.setFIFOFilter(0, 0x18DAF111, EXT); // Extended CANID  Target address F1(Tester), Sender 11(ENG ECU)
  can2.setMB(MB10,TX,EXT); // Set mailbox as transmit
  can2.setMB(MB4,RX,EXT); // Set mailbox as receiving extended frames.
  can2.setMaxMB(16);
  can2.mailboxStatus(); // view current configuration
  
  //can2.enableFIFO();
  //can2.enableFIFOInterrupt();
  can2.enableMBInterrupt(MB4);
  can2.onReceive(canSniff);

}

const long loopDelay1 = 50; // Make a request every 500ms
unsigned long timeNow1 = 0;

void loop(){

can2.events();
 
  if (millis() > timeNow1 + loopDelay1)
    {
      timeNow1 = millis();
      canTx_OBD(); // Query data from CAN BUS via OBD
      //canTx_UDS(); // Query data from CAN BUS via UDS

    }
}


void canSniff (const CAN_message_t &msg){ // Global callback to catch any CAN frame coming in

   
      if (msg.buf[1] == 0x41){
        if (msg.buf[2] ==0x0C){

           // SID$01 PID$0C Engine Speed
           //engineCoolantTemp = msg.buf[3] - 40;           // Offset -40(degC)
           engineRPM = (msg.buf[3]*0x100 + msg.buf[4]) /4;
           Serial.println(" ");
           //Serial.printf("OBD: Engine Coolant Temp: %d c", engineCoolantTemp);
           Serial.printf("OBD: Engine Speed: %d rpm", engineRPM);

        }
      }
    
    
        display.setTextColor(WHITE);
        display.clearDisplay();

        display.setTextSize(1);      // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE); // Draw white text
        display.setCursor(0, 0);     // Start at top-left corner
        display.println(F("CAN_ID:"));
        display.setCursor(60,0);
        display.setTextColor(WHITE);
        display.print(msg.id, HEX);

    
        for ( uint8_t i = 0; i < 8; i++ ) {
           display.setCursor(15*i ,15);
           display.print(msg.buf[i],HEX);  Serial.print(" ");
        }

        display.setTextSize(2);      // Normal 1:1 pixel scale
        display.setCursor(10, 35);
       //display.print(engineCoolantTemp,DEC);
        display.print(engineRPM, DEC);
        display.setCursor(70, 35);
       //display.print("degC");
        display.print("RPM");
        display.display(); 

}


void canTx_OBD(){ // Request function to ask for Engine Coolant Temp via OBD request
  CAN_message_t msgTx, msgRx;
    msgTx.buf[0] = 0x02;     // Data Length 2byte
    msgTx.buf[1] = 0x01;     // Service$01
    msgTx.buf[2] = 0x0C;     // PID $05 Engine Coolant Temperature
    msgTx.buf[3] = 0x55;        // Padding
    msgTx.buf[4] = 0x55;
    msgTx.buf[5] = 0x55; 
    msgTx.buf[6] = 0x55;
    msgTx.buf[7] = 0x55;
    msgTx.len = 8;            // number of bytes in request
    //msgTx.flags.extended = 0; // 11 bit header, not 29 bit 
    msgTx.flags.extended = 1; // 29 bit header, not 11 bit 
    msgTx.flags.remote = 0;
    
    //msgTx.id = 0x7E0; // request header for OBD           // Physical Addressing Request to Engine ECU
    msgTx.id = 0x18DB33F1; // request header for OBD Functional Addressing request to all emission related ECUs.
    can2.write(msgTx);
    Serial.println("canTx_OBD REQ sent"); 
  }

 void canTx_UDS(){ // Request function to ask for Engine Coolant Temp via UDS request
  CAN_message_t msgTx, msgRx;
    msgTx.buf[0] = 0x03;      // Data Length
    msgTx.buf[1] = 0x22;      // SID$22 Request parameter by Data Identifier
    msgTx.buf[2] = 0xf4;      // 
    msgTx.buf[3] = 0x05;      // PID$05 is Engine Coolant Temperature
    msgTx.buf[4] = 0;
    msgTx.buf[5] = 0; 
    msgTx.buf[6] = 0; 
    msgTx.buf[7] = 0;
    msgTx.len = 8;            // number of bytes in request
    //msgTx.flags.extended = 0; // 11 bit header, not 29 bit
    msgTx.flags.extended = 1; // 29 bit header, not 11 bit
    
    msgTx.flags.remote = 0;
    //msgTx.id = 0x7E0; // request header for OBD
    msgTx.id = 0x18DB33F1; // request header for OBD Functional Addressing request to all emission related ECUs.
    can2.write(msgTx); 
    Serial.println("canTx_UDS REQ sent"); 
  }



void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}
 
So it does look like the SDHC basic write speed I've tried with test sketches is better than OK, but only if I keep the file open. The easy method is the too slow method, where you open a file, write, and close it on every write burst.

I would like to be able to get the SD file object to be global, so that I can write to it from various functions. I instantiated the object before the setup() function, and it surprisingly compiled, but it's not accessible by any function. If I instantiate the file in the setup function, which is kind of where it logically belongs, it's not accessible even in the loop(). Any hints on that?
 
Thank you, tonton81,
Since I could not do a good job with FIFO, I went back to mail box stuff anyway.
So far I can send and receive OBD CAN message with extended 29 bit CAN communication.
Nevertheless, my goal is not getting PID parameters such as engine speed, engine coolant temperature, but longer messages which requires multi-frame (ISO-15765-2).
In order to handle multi-frame stuff, Teensy has to receive a multiple consecutive frames after sending flow control from Teensy.
Therefore I probably have to go back to FIFO again. I will read through FlexCAN_T4 library and try to understand how to use.

Anyway following code works with my vehicle.

Code:
// 29bit extended OBD CAN Sketch
//  get Service$01 PID$0C Engine Speed
//

#include <FlexCAN_T4.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ADC.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> can2;

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };


int engineCoolantTemp = 0; // Save the data here
int engineRPM = 0;// Save the data here


void setup(){
  while (!Serial && millis() < 5000);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64

    delay(200);
    
    Serial.println(F("SSD1306 allocation failed"));
    
    for(;;); // Don't proceed, loop forever
  }
  
  Serial.begin(115200);
  can2.begin();
  can2.setBaudRate(500000);
  //can2.setFIFOFilter(REJECT_ALL);
  //can2.setFIFOFilter(0, 0x18DAF111, EXT); // Extended CANID  Target address F1(Tester), Sender 11(ENG ECU)
  can2.setMB(MB10,TX,EXT); // Set mailbox as transmit
  can2.setMB(MB4,RX,EXT); // Set mailbox as receiving extended frames.
  can2.setMaxMB(16);
  can2.mailboxStatus(); // view current configuration
  
  //can2.enableFIFO();
  //can2.enableFIFOInterrupt();
  can2.enableMBInterrupt(MB4);
  can2.onReceive(canSniff);

}

const long loopDelay1 = 50; // Make a request every 500ms
unsigned long timeNow1 = 0;

void loop(){

can2.events();
 
  if (millis() > timeNow1 + loopDelay1)
    {
      timeNow1 = millis();
      canTx_OBD(); // Query data from CAN BUS via OBD
      //canTx_UDS(); // Query data from CAN BUS via UDS

    }
}


void canSniff (const CAN_message_t &msg){ // Global callback to catch any CAN frame coming in

   
      if (msg.buf[1] == 0x41){
        if (msg.buf[2] ==0x0C){

           // SID$01 PID$0C Engine Speed
           //engineCoolantTemp = msg.buf[3] - 40;           // Offset -40(degC)
           engineRPM = (msg.buf[3]*0x100 + msg.buf[4]) /4;
           Serial.println(" ");
           //Serial.printf("OBD: Engine Coolant Temp: %d c", engineCoolantTemp);
           Serial.printf("OBD: Engine Speed: %d rpm", engineRPM);

        }
      }
    
    
        display.setTextColor(WHITE);
        display.clearDisplay();

        display.setTextSize(1);      // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE); // Draw white text
        display.setCursor(0, 0);     // Start at top-left corner
        display.println(F("CAN_ID:"));
        display.setCursor(60,0);
        display.setTextColor(WHITE);
        display.print(msg.id, HEX);

    
        for ( uint8_t i = 0; i < 8; i++ ) {
           display.setCursor(15*i ,15);
           display.print(msg.buf[i],HEX);  Serial.print(" ");
        }

        display.setTextSize(2);      // Normal 1:1 pixel scale
        display.setCursor(10, 35);
       //display.print(engineCoolantTemp,DEC);
        display.print(engineRPM, DEC);
        display.setCursor(70, 35);
       //display.print("degC");
        display.print("RPM");
        display.display(); 

}


void canTx_OBD(){ // Request function to ask for Engine Coolant Temp via OBD request
  CAN_message_t msgTx, msgRx;
    msgTx.buf[0] = 0x02;     // Data Length 2byte
    msgTx.buf[1] = 0x01;     // Service$01
    msgTx.buf[2] = 0x0C;     // PID $05 Engine Coolant Temperature
    msgTx.buf[3] = 0x55;        // Padding
    msgTx.buf[4] = 0x55;
    msgTx.buf[5] = 0x55; 
    msgTx.buf[6] = 0x55;
    msgTx.buf[7] = 0x55;
    msgTx.len = 8;            // number of bytes in request
    //msgTx.flags.extended = 0; // 11 bit header, not 29 bit 
    msgTx.flags.extended = 1; // 29 bit header, not 11 bit 
    msgTx.flags.remote = 0;
    
    //msgTx.id = 0x7E0; // request header for OBD           // Physical Addressing Request to Engine ECU
    msgTx.id = 0x18DB33F1; // request header for OBD Functional Addressing request to all emission related ECUs.
    can2.write(msgTx);
    Serial.println("canTx_OBD REQ sent"); 
  }

 void canTx_UDS(){ // Request function to ask for Engine Coolant Temp via UDS request
  CAN_message_t msgTx, msgRx;
    msgTx.buf[0] = 0x03;      // Data Length
    msgTx.buf[1] = 0x22;      // SID$22 Request parameter by Data Identifier
    msgTx.buf[2] = 0xf4;      // 
    msgTx.buf[3] = 0x05;      // PID$05 is Engine Coolant Temperature
    msgTx.buf[4] = 0;
    msgTx.buf[5] = 0; 
    msgTx.buf[6] = 0; 
    msgTx.buf[7] = 0;
    msgTx.len = 8;            // number of bytes in request
    //msgTx.flags.extended = 0; // 11 bit header, not 29 bit
    msgTx.flags.extended = 1; // 29 bit header, not 11 bit
    
    msgTx.flags.remote = 0;
    //msgTx.id = 0x7E0; // request header for OBD
    msgTx.id = 0x18DB33F1; // request header for OBD Functional Addressing request to all emission related ECUs.
    can2.write(msgTx); 
    Serial.println("canTx_UDS REQ sent"); 
  }



void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}
for ordered frames you need FIFO, otherwise you'll have to build your own frame re-assembly code for multiframe (you can build both ways, but FIFO would make the rebuilding easier, also remove events() from the loop().



for ordered frames you need FIFO, otherwise you'll have to build your own frame re-assembly code for multiframe (you can build both ways, but FIFO would make the rebuilding easier, also remove events() from the loop().
 
So it does look like the SDHC basic write speed I've tried with test sketches is better than OK, but only if I keep the file open. The easy method is the too slow method, where you open a file, write, and close it on every write burst.

I would like to be able to get the SD file object to be global, so that I can write to it from various functions. I instantiated the object before the setup() function, and it surprisingly compiled, but it's not accessible by any function. If I instantiate the file in the setup function, which is kind of where it logically belongs, it's not accessible even in the loop(). Any hints on that?

Sorry I figured it out. Declared only above setup() instantiated in setup(), wrote and closed in loop(). That seems to work for me.
 
Hi tonton81 - Couple of questions from the FIFO Interrupts example - loop code below.

1. How often does CAN.events() have to be called? I want to put some other code in loop() but it's not clear when things start to go south.
2. What's the purpose of the timeout routine?
3. Why isn't this thread a sticky?



void loop() {
Can0.events();

static uint32_t timeout = millis();
if ( millis() - timeout > 200 ) {
CAN_message_t msg;
msg.id = random(0x1,0x7FE);
for ( uint8_t i = 0; i < 8; i++ ) msg.buf = i + 1;
Can0.write(msg);
timeout = millis();
}
 
events() dequeues while the interrupt queues. so depending on your delays, if you let the queue build up to max you'll just be tossing frames after. you could also do a while(Can0.events()); and it'll dequeue everything in queue before continuing loop.

timeout means after 200ms the transmit repeats

if you dont want to rely on your loop() code if its got delays, remove the events() call and you will get the messages directly in callback without using the queue method
 
Hello,

Apologies if this has already been answered but I'm struggling to send or receive when the baud rate is set to 95,000 bps. I've tried all of the clock speeds using setClock (8,16,20,24,...) but still without success. Other speeds such as 33,333 and 500,000 work just fine. I really can't see what I'm doing wrong. Any help would be greatly appreciated. Many thanks!

Code:
#include <FlexCAN_T4.h>

FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> can2;  // can2 port
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> can1;  // can1 port 

int led = 13;
IntervalTimer timer;
uint8_t d=0;


void setup(void) {
  pinMode(led, OUTPUT);   
  digitalWrite(led,HIGH);
  Serial.begin(115200); delay(1000);
  Serial.println("Teensy 4.0 Triple CAN test");
  digitalWrite(led,LOW);
  
  can2.begin();
  can2.setClock(CLK_60MHz);
  can2.setBaudRate(95000);       // 95kbps data rate
  can2.enableFIFO();
  can2.enableFIFOInterrupt();
  can2.onReceive(FIFO, canSniff20);
  can2.mailboxStatus();
  
  can1.begin();
  can2.setClock(CLK_60MHz);
  can1.setBaudRate(95000);     // 95kbps data rate
  can1.enableFIFO();
  can1.enableFIFOInterrupt();
  can1.onReceive(FIFO, canSniff20);
  can1.mailboxStatus();

  timer.begin(sendframe, 500000); // Send frame every 500ms 
}

void sendframe()
{
  
  CAN_message_t msg2;
  msg2.id = 0x401;
  
  for ( uint8_t i = 0; i < 8; i++ ) {
    msg2.buf[i] = i + 1;
  }
  
  msg2.buf[0] = d++;
  msg2.seq = 1;
  can2.write(MB15, msg2); // write to can2

  msg2.id = 0x402;
  msg2.buf[1] = d++;
  can1.write(msg2);       // write to can1

}


void canSniff20(const CAN_message_t &msg) { // global callback
  Serial.print("T4: ");
  Serial.print("MB "); Serial.print(msg.mb);
  Serial.print(" OVERRUN: "); Serial.print(msg.flags.overrun);
  Serial.print(" BUS "); Serial.print(msg.bus);
  Serial.print(" LEN: "); Serial.print(msg.len);
  Serial.print(" EXT: "); Serial.print(msg.flags.extended);
  Serial.print(" REMOTE: "); Serial.print(msg.flags.remote);
  Serial.print(" TS: "); Serial.print(msg.timestamp);
  Serial.print(" ID: "); Serial.print(msg.id, HEX);
  Serial.print(" IDHIT: "); Serial.print(msg.idhit);
  Serial.print(" Buffer: ");
  for ( uint8_t i = 0; i < msg.len; i++ ) {
    Serial.print(msg.buf[i], HEX); Serial.print(" ");
  } Serial.println();
}



void loop() {
  
  can2.events();
  can1.events();
  
}
 
Try this:

Code:
  can1.setClock(CLK_60MHz);
  can1.setBaudRate(95238);

I made an ESP32 version of this CAN library and the closest match it shows on the ESP32 is 95238. So when I put that on teensy @ 60MHz peripheral clock, they're both talking to each other
 
Last edited:
Thank you so much tonton81! I would have never figured this out.

Try this:

Code:
  can1.setClock(CLK_60MHz);
  can1.setBaudRate(95238);

I made an ESP32 version of this CAN library and the closest match it shows on the ESP32 is 95238. So when I put that on teensy @ 60MHz peripheral clock, they're both talking to each other
 
Instead of using MailBox, I started using FIFO and it seems to be working.
Therefore I started to handle with multi-frame messages. Since my vehicle does not support VIN (Vehicle Identification Number), Calibration ID is selected for the beginning.
Calibration ID is Service$09 InfoType$04. Calibration ID consists of 16 ASCII characters and response message from ECU requires multi-frame because of the message length.
With the Vehicle Spy software ISO-15765-2 option usage, Teensy can receive multi-frame message, but with the actual vehicle Teensy cannot. Teensy can only receive First Frame response message and Teensy sends Flow Control but cannot receive Consecutive message from the ECU. My FIFO handling for the consecutive message may be wrong.


Code:
 /*********************************************************************************
 *   Teensy4.0 + MCP2562 CAN Transceiver + Adafruit OLED
 *   500kbps CAN communication speed
 *   2021 JAN 30
 *
 *   Service$09 InfoType$04 CALID Read
 *   
 *   
 ********************************************************************************/

#include <FlexCAN_T4.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ADC.h>
#include <ADC_util.h>
#include <Bounce.h>  // Bounce library makes button change detection easy


const int channel = 1;

int Req_msgTxid;
int val6, val7, val8, val9;
int Stat_FC=0;  // Flow Control request status
int disp_mode;
int seq_Num=0;
int CF_Num;
int CF_Max;
int sid09inftyp=0;
int f_sid09calid_multi;
String str_A;
String dtc_buf6pre;
String calid_buf1, calid_buf2, calid_buf3, calid_buf4, calid_buf5, calid_buf6, calid_buf7, calid_buf8;
String calid_buf9, calid_buf10, calid_buf11, calid_buf12, calid_buf13, calid_buf14, calid_buf15, calid_buf16, calid_buf17;

String cal_buf1, cal_buf2, cal_buf3, cal_buf4, cal_buf5, cal_buf6, cal_buf7, cal_buf8, cal_buf9, cal_buf10;
String cal_buf11,cal_buf12, cal_buf13, cal_buf14, cal_buf15, cal_buf16;  // CALID is maximum 16 ASCC characters.

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example
unsigned long curr, prev, next, interval;

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> can1;
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> can2;
CAN_message_t msg;

static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

void setup(void) {
  prev = 0;         // Initialize previous execution time
  interval = 1000;   // reset execution interval
  
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

 while (!Serial && millis() < 5000);
  Serial.begin(115200);
  can2.begin();
  can2.setBaudRate(500000);
  //can2.setMBFilter(REJECT_ALL);
  can2.setFIFOFilter(REJECT_ALL);
  can2.setFIFOFilter(0, 0x18DAF111, EXT); // Extended CANID  Target address F1(Tester), Sender 11(ENG ECU)
  can2.setMB(MB10,TX); // Set mailbox as transmit
  can2.setMaxMB(16);
  can2.mailboxStatus(); // view current configuration
  
  can2.enableFIFO();
  can2.enableFIFOInterrupt();
  //can2.enableMBInterrupt(MB4);
   
  can2.onReceive(FIFO, canSniff);
}

const long loopDelay1 =500;
unsigned long timeNow1 = 0;
unsigned long timeNow2 = 0;

void loop() {


  /*
   * Requesting Serivice Identifier routine
   */
  if (millis() > timeNow1 + loopDelay1) // Send the request every 100ms
    {
             timeNow1 = millis();
             if(Stat_FC ==0){
               can_SID09_TX();
            
             }

    }

   if(Stat_FC ==1){
            delay(10);
            can_FC_TX();
        }
        else if(Stat_FC ==2){
            
             // consider doing something
        }

    if ( can2.read(msg)) {
    
      Serial.print("CAN2 "); 
      Serial.print("MB: "); Serial.print(msg.mb);
      Serial.print("  ID: 0x"); Serial.print(msg.id, HEX );
      Serial.print("  EXT: "); Serial.print(msg.flags.extended );
      Serial.print("  LEN: "); Serial.print(msg.len);
      Serial.print(" DATA: ");
      for ( uint8_t i = 0; i < 8; i++ ) {
         Serial.print(msg.buf[i]); Serial.print(" ");
      }
      Serial.print("  TS: "); Serial.println(msg.timestamp);
   }
  
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }
  display.display();
}


// Response Message process routine 
// 
void canSniff (const CAN_message_t &msg){ // Global callback - prints out the received message in HEX + saves TW into a global variable
  if(msg.id == 0x18DAF111){
     Serial.println(" ");
     Serial.print("MB: ");
     Serial.print(msg.mb);
     Serial.print("  ID: 0x");
     Serial.print(msg.id, HEX);
     Serial.print("  EXT: ");
     Serial.print(msg.flags.extended);
     Serial.print("  LEN: ");
     Serial.print(msg.len);
     Serial.print(" DATA: ");
 
      for (uint8_t i = 0; i < 8; i++)
         {
            Serial.print(msg.buf[i], HEX);
            Serial.print(" ");
         }
      }
      if (msg.buf[0] > 0x0F & msg.buf[2] == 0x49){    // Service$09 Positive Response message  and First Frame.
         CF_Max = 2;
         f_sid09calid_multi = 1;   // SID$09 calid process
          
         if(msg.buf[3] == 4){   // Means Info Type == 02 calid. InfoType == 04 CALID.
            // ignore msg.buf[4]. because this buffer is always 0x01.Number of data item.
             calid_buf1 = msg.buf[5];
             calid_buf2 = msg.buf[6];
             calid_buf3 = msg.buf[7];  // calid buffer is fixed 17 byte. 
             Stat_FC = 1; 
           
         }
         
      }
      else if(f_sid09calid_multi ==1 & msg.buf[0] > 0x20 & Stat_FC ==2 ){     // Means CF: Consecutive Frame and Buf[0] is sequence number
        seq_Num = msg.buf[0]; //
             if(seq_Num == 0x21){   // Sequence Number is 1.
                calid_buf4  = msg.buf[1];
                calid_buf5  = msg.buf[2];
                calid_buf6  = msg.buf[3];
                calid_buf7  = msg.buf[4];
                calid_buf8  = msg.buf[5];
                calid_buf9  = msg.buf[6];
                calid_buf10 = msg.buf[7];
                CF_Num = 1;
              }
              if(seq_Num == 0x22 ){  // Sequence Number is 2.
                calid_buf11 = msg.buf[1];
                calid_buf12 = msg.buf[2];
                calid_buf13 = msg.buf[3];
                calid_buf14 = msg.buf[4];
                calid_buf15 = msg.buf[5];
                calid_buf16 = msg.buf[6];
                calid_buf17 = msg.buf[7];
                CF_Num = 2;
               }
              if( CF_Num == CF_Max){
                 if(Stat_FC == 2){
                    Stat_FC=0;
                    CF_Num = 0;
                    f_sid09calid_multi = 0;               
                 }
           
              }
    
        } 
  

  if(msg.id == 0x18DAF111){
    display.setTextColor(WHITE);
    display.clearDisplay();

    display.setTextSize(1);      // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE); // Draw white text
    display.setCursor(0, 0);     // Start at top-left corner
    display.println(F("CAN_ID:"));
    display.setCursor(60,0);
    display.setTextColor(WHITE);
    display.print(msg.id, HEX);
  }
    
  for ( uint8_t i = 0; i < 8; i++ ) {
     display.setCursor(15*i ,15);
     display.print(msg.buf[i],HEX);  Serial.print(" ");
  }
     
       display.display();

      display.setCursor(0, 30);
      display.print(calid_buf1);
      display.setCursor(6, 30);
      display.print(calid_buf2);
      display.setCursor(12, 30);
      display.print(calid_buf3);
      display.setCursor(18, 30);
      display.print(calid_buf4); 
      display.setCursor(24, 30);
      display.print(calid_buf5);
      display.setCursor(30, 30);
      display.print(calid_buf6); 
      display.setCursor(36, 30);
      display.print(calid_buf7); 
      display.setCursor(42, 30);
      display.print(calid_buf8);
      display.setCursor(48, 30);
      display.print(calid_buf9); 
      display.setCursor(54, 30);
      display.print(calid_buf10);
      display.setCursor(60, 30);
      display.print(calid_buf11); 
      display.setCursor(66, 30);
      display.print(calid_buf12);
      display.setCursor(72, 30);
      display.print(calid_buf13); 
      display.setCursor(78, 30);
      display.print(calid_buf14); 
      display.setCursor(84, 30);
      display.print(calid_buf15);
      display.setCursor(90, 30);
      display.print(calid_buf16); 
      //display.setCursor(96, 30);
      //display.print(calid_buf17); 
      
      display.display();   
  
}



void can_SID09_TX(){  // Sendo a request to get calid from ECU
    
    CAN_message_t msgTx, msgRx;
    msgTx.buf[0] = 0x02; 
    msgTx.buf[1] = 0x09; // SID$09 Request Info Type$02 calid
    msgTx.buf[2] = 0x04; // 02 is Info Type $02 CALID $04
    msgTx.buf[3] = 0x55; // 0x55 is padding
    msgTx.buf[4] = 0x55;
    msgTx.buf[5] = 0x55; 
    msgTx.buf[6] = 0x55;
    msgTx.buf[7] = 0x55;
    msgTx.len = 8; // number of bytes in request
    msgTx.flags.extended = 1; // 11 bit header, not 29 bit
    msgTx.flags.remote = 0;
    //msgTx.id = 0x7E0;   // requesting Service$09 use only 7E0 for testing Toyota
    //msgTx.id = 0x18DA11F1;  // Honda
    msgTx.id = 0x18DB33F1;

    can2.write(msgTx); 
}


void can_FC_TX(){ // Flow Control Command
  CAN_message_t msgTx, msgRx;
    msgTx.buf[0] = 0x30;     // Flow Control 
    msgTx.buf[1] = 0x00;     // Block Size
    msgTx.buf[2] = 0x00;     // STMin
    msgTx.buf[3] = 0x00;        // Padding
    msgTx.buf[4] = 0x00;
    msgTx.buf[5] = 0x00; 
    msgTx.buf[6] = 0x00;
    msgTx.buf[7] = 0x00;
    msgTx.len = 8;            // number of bytes in request
    //msgTx.flags.extended = 0; // 11 bit header, not 29 bit 
    msgTx.flags.extended = 1; // 29 bit header, not 11 bit 
    msgTx.flags.remote = 0;
    
    //msgTx.id = 0x7E0; // request header for OBD           // Physical Addressing Request to Engine ECU
    msgTx.id = 0x18DB33F1; // request header for OBD Functional Addressing request to all emission related ECUs.
    can2.write(msgTx);
    Stat_FC = 2;  // Flow Control sent
    
    //Serial.println("canTx_OBD FC sent"); 
  }
 
Back
Top