samm_flynn
Active member
Hi everyone,
First time posting here. I apologize in advance if I have broken any rules unknowingly.
I’m using a Teensy 4.1 with two CAN transceivers for a project.
CAN0 TX is connected to pin 22, RX to pin 23,
while CAN1 TX is on pin 1 and RX on pin 0.
Both are set to 500 kbps using the FlexCAN_T4 library.
Every second, CAN0 sends an 8-byte message. When CAN1 receives it, it triggers the canSniff1 function, which immediately sends a response back to CAN0. When CAN0 receives this reply, it logs the timing information. The goal is to measure how long it takes for messages to be received and processed.
The write times are really good, around 5 microseconds, but the reception time is much slower than I expected, around 250 ± 50 microseconds. My actual motor driver responds within 50 microseconds, and I confirmed that using an oscilloscope. I also tested an MCP2515, which took around 350 microseconds to transmit but showed receive times between 70 and 100 microseconds.
My main question , Is this normal receive timing for the FlexCAN_T4 library, or am I doing something wrong(my money is on I am doing something wrong)? Is there a way to optimize this and reduce the receive latency? I also don’t fully understand how FIFO vs. mailboxes impact performance in this library. My setup is copied from a GitHub example.
Here is an how I intend to use the code -
Any advice on optimizing CAN receive times or improving the way I handle messages would be greatly appreciated. Also, if anyone has insights into the best way to set up FIFO or mailboxes for low-latency CAN reception, I’d love to learn more. Thanks in advance.
Please feel free to ask for more clarification if required.
First time posting here. I apologize in advance if I have broken any rules unknowingly.
I’m using a Teensy 4.1 with two CAN transceivers for a project.
CAN0 TX is connected to pin 22, RX to pin 23,
while CAN1 TX is on pin 1 and RX on pin 0.
Both are set to 500 kbps using the FlexCAN_T4 library.
C-like:
#include <FlexCAN_T4.h>
#include <motctrl_prot.h>
#include <cstring>
#include <IntervalTimer.h>
FlexCAN_T4<CAN1, RX_SIZE_8, TX_SIZE_8> Can0;
FlexCAN_T4<CAN2, RX_SIZE_8, TX_SIZE_8> Can1;
IntervalTimer sendTimer;
volatile uint32_t can0WriteTime = 0;
volatile uint32_t can1WriteTime = 0;
void canSniff0(const CAN_message_t &msg) {
uint32_t delaySinceCan1Write = micros() - can1WriteTime;
Serial.print("CAN0 received after CAN1.write: ");
Serial.print(delaySinceCan1Write);
Serial.println(" us");
}
void canSniff1(const CAN_message_t &msg) {
uint32_t delaySinceCan0Write = micros() - can0WriteTime;
Serial.print("CAN1 received after CAN0.write: ");
Serial.print(delaySinceCan0Write);
Serial.println(" us");
uint32_t start = micros();
CAN_message_t reply;
reply.id = 0x0;
reply.len = 8;
uint8_t buf[8] = {0};
MCReqStartMotor(buf);
memcpy(reply.buf, buf, sizeof(buf));
Can1.write(reply);
uint32_t duration = micros() - start;
Serial.print("CAN1 write duration: ");
Serial.print(duration);
Serial.println(" us");
can1WriteTime = start;
}
void sendCANMessages() {
uint32_t start = micros();
CAN_message_t msg;
msg.id = 0x1;
msg.len = 8;
uint8_t buf[8] = {0};
MCReqStopMotor(buf);
memcpy(msg.buf, buf, sizeof(buf));
Can0.write(msg);
uint32_t duration = micros() - start;
Serial.print("CAN0 write duration: ");
Serial.print(duration);
Serial.println(" us");
can0WriteTime = start;
}
void setup() {
Serial.begin(115200);
delay(1000);
Can0.begin();
Can0.setBaudRate(500000);
Can0.setMaxMB(16);
Can0.enableFIFO();
Can0.enableFIFOInterrupt();
Can0.onReceive(canSniff0);
Can1.begin();
Can1.setBaudRate(500000);
Can1.setMaxMB(16);
Can1.enableFIFO();
Can1.enableFIFOInterrupt();
Can1.onReceive(canSniff1);
sendTimer.begin(sendCANMessages, 1000*1000);// 1s = 1 millon microseconds
}
void loop() {
Can0.events();
Can1.events();
}
The write times are really good, around 5 microseconds, but the reception time is much slower than I expected, around 250 ± 50 microseconds. My actual motor driver responds within 50 microseconds, and I confirmed that using an oscilloscope. I also tested an MCP2515, which took around 350 microseconds to transmit but showed receive times between 70 and 100 microseconds.
My main question , Is this normal receive timing for the FlexCAN_T4 library, or am I doing something wrong(my money is on I am doing something wrong)? Is there a way to optimize this and reduce the receive latency? I also don’t fully understand how FIFO vs. mailboxes impact performance in this library. My setup is copied from a GitHub example.
Here is an how I intend to use the code -
C-like:
#include <FlexCAN_T4.h>
#include <math.h>
#include <motctrl_prot.h>
#include <IntervalTimer.h>
const int LED_PIN = 13;
const uint32_t CAN_ID = 0x01; // Motor controller CAN ID
const int update_interval_us = 1000; // 1 ms update rate
IntervalTimer motor_timer;
volatile bool armed = false;
volatile bool send_flag = false;
volatile int8_t temp = 0;
volatile float theta = 0;
volatile float omega = 0;
volatile float torque = 0;
volatile int canmsg_count = 0;
volatile float cmd_vel = 0.0; // Command velocity (rad/s)
String inputString = "";
bool commandReceived = false;
FlexCAN_T4<CAN2, RX_SIZE_8, TX_SIZE_8> Can0;
volatile uint32_t t_send = 0;
volatile uint32_t t_recv = 0;
volatile uint8_t lastCmdType = 0;
void canRxHandler(const CAN_message_t &msg);
void sendCmd();
void processCommand(String command);
void startMotor();
void stopMotor();
void setVelocity(float radPerSec);
typedef enum
{
MOTCTRL_CMD_START_MOTOR = 0x91,
MOTCTRL_CMD_STOP_MOTOR = 0x92,
MOTCTRL_CMD_TORQUE_CONTROL = 0x93,
MOTCTRL_CMD_SPEED_CONTROL = 0x94,
MOTCTRL_CMD_POSITION_CONTROL = 0x95,
MOTCTRL_CMD_STOP_CONTROL = 0x97,
} MOTCTRL_CMD;
void setup()
{
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
Serial.begin(115200);
while (!Serial)
{
delay(1000);
}
Serial.println("<Ready>");
digitalWrite(LED_PIN, LOW);
Can0.begin();
Can0.setBaudRate(500000);
Can0.enableFIFO();
Can0.enableFIFOInterrupt();
Can0.onReceive(canRxHandler);
Serial.println("CAN Initialization Complete.");
delay(1000);
stopMotor();
motor_timer.begin(sendCmd, update_interval_us);
}
void loop()
{
Can0.events();
}
void serialEvent()
{
while (Serial.available() > 0)
{
char receivedChar = Serial.read();
if (receivedChar == '\n')
{
processCommand(inputString);
inputString = "";
}
else
{
inputString += receivedChar;
}
}
}
void processCommand(String command)
{
command.trim();
if (command.equalsIgnoreCase("arm"))
{
Serial.println("arm");
startMotor();
commandReceived = true;
}
else if (command.equalsIgnoreCase("stop"))
{
Serial.println("stop");
stopMotor();
commandReceived = true;
}
else if (command.startsWith("vel:"))
{
String floatPart = command.substring(4);
cmd_vel = floatPart.toFloat();
Serial.print("CMD_VEL : ");
Serial.println(cmd_vel, 6);
commandReceived = true;
}
else
{
Serial.println("Invalid command. Please enter 'arm', 'stop', or 'vel:<value>'.");
}
}
void startMotor()
{
t_send = micros();
send_flag = true;
uint8_t buf[8];
MCReqStartMotor(buf);
lastCmdType = MOTCTRL_CMD_START_MOTOR;
CAN_message_t msg;
msg.id = CAN_ID;
msg.len = MOTCTRL_FRAME_SIZE;
memcpy(msg.buf, buf, MOTCTRL_FRAME_SIZE);
Can0.write(msg);
Serial.println("<Start Request>");
Serial.print("Start-> DT:");
Serial.println(micros() - t_send);
}
void stopMotor()
{
t_send = micros();
send_flag = false;
uint8_t buf[8];
MCReqStopMotor(buf);
lastCmdType = MOTCTRL_CMD_STOP_MOTOR;
CAN_message_t msg;
msg.id = CAN_ID;
msg.len = MOTCTRL_FRAME_SIZE;
memcpy(msg.buf, buf, MOTCTRL_FRAME_SIZE);
Can0.write(msg);
Serial.println("<Stop Request>");
Serial.print("Stop-> DT:");
Serial.println(micros() - t_send);
}
void setVelocity(float radPerSec)
{
t_send = micros();
uint8_t CMD_buf[8];
MCReqSpeedControl(CMD_buf, radPerSec * (30.0 / M_PI), 0);
lastCmdType = MOTCTRL_CMD_SPEED_CONTROL;
CAN_message_t msg;
msg.id = CAN_ID;
msg.len = MOTCTRL_FRAME_SIZE;
memcpy(msg.buf, CMD_buf, MOTCTRL_FRAME_SIZE);
Can0.write(msg);
Serial.print("CMD_VEL-> DT:");
Serial.println(micros() - t_send);
}
void canRxHandler(const CAN_message_t &msg)
{
canmsg_count++;
uint8_t buf[8];
memcpy(buf, msg.buf, msg.len);
if (buf[1] == MOTCTRL_RES_SUCCESS)
{
switch (buf[0])
{
case MOTCTRL_CMD_SPEED_CONTROL:
{
float p, s, t;
int8_t tmp;
MCResSpeedControl(buf, &tmp, &p, &s, &t);
theta = p;
omega = s;
torque = t;
temp = tmp;
if (canmsg_count % 10 == 0)
{
Serial.print(" ID: 0x0");
Serial.print(msg.id, HEX);
Serial.print(". ω : ");
Serial.println(fabs(omega), 6);
if (t_send != 0 && lastCmdType == MOTCTRL_CMD_SPEED_CONTROL)
{
t_recv = micros();
uint32_t latency = t_recv - t_send;
Serial.print("Round-trip latency for speed control: ");
Serial.print(latency);
Serial.println(" us");
t_send = 0;
}
}
break;
}
case MOTCTRL_CMD_START_MOTOR:
{
armed = true;
digitalWrite(LED_PIN, HIGH);
Serial.print(" ID: 0x0");
Serial.print(msg.id, HEX);
Serial.println(" . <ENABLED>");
if (t_send != 0 && lastCmdType == MOTCTRL_CMD_START_MOTOR)
{
t_recv = micros();
uint32_t latency = t_recv - t_send;
Serial.print("Round-trip latency for start motor: ");
Serial.print(latency);
Serial.println(" us");
t_send = 0;
}
break;
}
case MOTCTRL_CMD_STOP_MOTOR:
{
armed = false;
digitalWrite(LED_PIN, LOW);
Serial.print(" ID: 0x0");
Serial.print(msg.id, HEX);
Serial.println(" . <DISABLED>");
if (t_send != 0 && lastCmdType == MOTCTRL_CMD_STOP_MOTOR)
{
t_recv = micros();
uint32_t latency = t_recv - t_send;
Serial.print("Round-trip latency for stop motor: ");
Serial.print(latency);
Serial.println(" us");
t_send = 0;
}
break;
}
default:
Serial.println("UNKNOWN_CMD");
break;
}
}
else
{
Serial.println("FAIL");
}
}
void sendCmd()
{
if (send_flag && armed)
{
setVelocity(cmd_vel);
}
}
Any advice on optimizing CAN receive times or improving the way I handle messages would be greatly appreciated. Also, if anyone has insights into the best way to set up FIFO or mailboxes for low-latency CAN reception, I’d love to learn more. Thanks in advance.
Please feel free to ask for more clarification if required.