Technology stack:
Processing (Java), Arduino IDE/Teensy4.0 with the FlexCAN_T4 library
Issue:
Teensy sends a burst of data over Serial to Processing. All good with that. When a Processing code sends a small amount of data (14 bytes) to Teensy4.0 at the same time, Teensy gets the correct data (according to our second hardware COM monitor). But, the data which Teensy sends to Processing gets received corrupted on the Processing side. If I do not send any data from Processing to Teensy, all works fine, and the correct data from Teensy comes to Processing at the maximum speed. It looks like the incoming data from Processing interrupts the outcoming data from Teensy in its Tx Serial buffer, i.e. a part of previous Teensy data is sent, then a new data is added to the Tx Serial buffer, and the rest of the previous messages shifts to the end of Tx Serial buffer. This way, I get a constant shift in the data flow. It seems I need to somehow tell Teensy to complete its current transmission, stop any new transmission, receive the data from Processing over Serial, and resume transmission over Serial to Processing. It seems like both Teensy and Processing prioritize serial reception over transmission. Based on what I observed by using a second FTDI hardware serial, the data is not corrupted when put in the Tx Serial buffer. It gets corrupted specifically at the moment of transmission at low level.
The Teensy code is attached. If you can point me out what is wrong or have any ideas, I would appreciate your comment.
Processing (Java), Arduino IDE/Teensy4.0 with the FlexCAN_T4 library
Issue:
Teensy sends a burst of data over Serial to Processing. All good with that. When a Processing code sends a small amount of data (14 bytes) to Teensy4.0 at the same time, Teensy gets the correct data (according to our second hardware COM monitor). But, the data which Teensy sends to Processing gets received corrupted on the Processing side. If I do not send any data from Processing to Teensy, all works fine, and the correct data from Teensy comes to Processing at the maximum speed. It looks like the incoming data from Processing interrupts the outcoming data from Teensy in its Tx Serial buffer, i.e. a part of previous Teensy data is sent, then a new data is added to the Tx Serial buffer, and the rest of the previous messages shifts to the end of Tx Serial buffer. This way, I get a constant shift in the data flow. It seems I need to somehow tell Teensy to complete its current transmission, stop any new transmission, receive the data from Processing over Serial, and resume transmission over Serial to Processing. It seems like both Teensy and Processing prioritize serial reception over transmission. Based on what I observed by using a second FTDI hardware serial, the data is not corrupted when put in the Tx Serial buffer. It gets corrupted specifically at the moment of transmission at low level.
The Teensy code is attached. If you can point me out what is wrong or have any ideas, I would appreciate your comment.
C-like:
#include <FlexCAN_T4.h>
/* ============= defines ============= */
#define CAN_PAYLOAD_SIZE_BYTES 8 // the maximum payload length
#define NUM_RX_MB_CAN1 5 // number of Rx mailboxes (ID pairs) for CAN1
#define NUM_RX_MB_CAN2 5
#define PAYLOAD_START_INDEX 4
#define ID_START_INDEX 0
#define BUS_INDEX 12
#define PAYLOAD_LEN_INDEX 13
#define SERIAL_BAUDRATE 500000
#define CAN1_BAUDRATE 500000
#define CAN2_BAUDRATE 50000
#define CAN1_FILTERS_ON
#define CAN2_FILTERS_ON
#define HardwareSerial Serial3
unsigned char serialRxBuf[500];
/* ============= data types ============= */
#pragma pack(1) // instruct the compiler to avoid padding
struct CanPacket
{
uint32_t id; // 4 bytes [0 1 2 3]
uint8_t payload[CAN_PAYLOAD_SIZE_BYTES]; // 8 bytes [4 5 6 7 8 9 10 11]
uint8_t bus; // 1 byte [12]
uint8_t payload_length; // 1 byte [13]
};
#pragma pack(1)
union CanPacketByte
{
struct CanPacket packet;
unsigned char binary[sizeof(CanPacket)];
};
/* ============= variables ============= */
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> can1; // CAN2.0: can1 port (500kbps data rate): 256-byte receive buffer / 16 byte transmit buffer
FlexCAN_T4<CAN2, RX_SIZE_256, TX_SIZE_16> can2; // CAN2.0: can2 port (50kbps data rate)
CAN_message_t msg; // a structure from the FlexCAN_T4 library representing a CAN message (used to send and receive CAN messages)
long int cntrMsgSerialPrint = 0; // keep track of the sequence number of the messages printed
/* ============= function declarations ============= */
void canSniff20(const CAN_message_t &msg);
int readHardwareSerial(union CanPacketByte& p);
void virtualSerialPrintCanMsg(uint32_t id, uint8_t CAN_Msg_Length, const uint8_t buf_1[8], uint8_t bus);
/* ============= initial settings ============= */
void setup() {
// hardware serial (DEV)
HardwareSerial.begin(SERIAL_BAUDRATE);
HardwareSerial.addMemoryForRead(serialRxBuf, 500);
HardwareSerial.attachRts(18); // Serial RTS (request to send), flow control
HardwareSerial.attachCts(19); // Serial CTS (clear to send), flow control
/* configure CAN 1 */
can1.begin();
can1.setBaudRate(CAN1_BAUDRATE);
can1.mailboxStatus(); // check the status of the mailbox before configuration
can1.setMaxMB(NUM_RX_MB_CAN1 + 1); // +1 for Tx
// initialize Tx mailboxes
can1.setMB((FLEXCAN_MAILBOX)0, TX, EXT);
// initialize Rx mailboxes
can1.setMB((FLEXCAN_MAILBOX)1, RX, STD);
can1.setMB((FLEXCAN_MAILBOX)2, RX, EXT);
can1.setMB((FLEXCAN_MAILBOX)3, RX, EXT);
can1.setMB((FLEXCAN_MAILBOX)4, RX, EXT);
can1.setMB((FLEXCAN_MAILBOX)5, RX, EXT);
#ifdef CAN1_FILTERS_ON
can1.setMBFilter(REJECT_ALL); // block all data before setting filters
#endif
can1.enableMBInterrupts(); // make all mailboxes interrupt enabled
can1.onReceive(canSniff20); // allows all MBs to be received in the supplied callback
/* configure filters */
#ifdef CAN1_FILTERS_ON
can1.enhanceFilter(MB1); // prevent the frames "bleeding through" filters (to avoid limitations of the lib)
can1.enhanceFilter(MB2);
can1.enhanceFilter(MB3);
can1.enhanceFilter(MB4);
can1.enhanceFilter(MB5);
#endif
#ifdef CAN1_FILTERS_ON
can1.setMBFilter(MB1, 0x4E0, 0x620);
can1.setMBFilter(MB2, 0x18DA28F1, 0x18DAF128);
can1.setMBFilter(MB3, 0x18DA40F1, 0x18DAF140);
can1.setMBFilter(MB4, 0x18DA10F1, 0x18DAF110);
can1.setMBFilter(MB5, 0x18DB33F1, 0x18DBF133);
#endif
can1.mailboxStatus(); // get the updated settings of mailboxes
/* configure CAN 2 */
can2.begin();
can2.setBaudRate(CAN2_BAUDRATE);
can2.mailboxStatus(); // get default initialization of mailboxes
can2.setMaxMB(NUM_RX_MB_CAN2 + 1); // +1 for Tx
// initialize Tx mailboxes
can2.setMB((FLEXCAN_MAILBOX)0, TX, EXT);
// initialize Rx mailboxes
can2.setMB((FLEXCAN_MAILBOX)1, RX, EXT);
can2.setMB((FLEXCAN_MAILBOX)2, RX, EXT);
can2.setMB((FLEXCAN_MAILBOX)3, RX, EXT);
can2.setMB((FLEXCAN_MAILBOX)4, RX, EXT);
can2.setMB((FLEXCAN_MAILBOX)5, RX, EXT);
#ifdef CAN2_FILTERS_ON
can2.setMBFilter(REJECT_ALL); // block all data before setting filters
#endif
can2.enableMBInterrupts(); // make all mailboxes interrupt enabled
can2.onReceive(canSniff20); // allows all MBs to be received in the supplied callback
/* configure filters */
#ifdef CAN2_FILTERS_ON
can2.enhanceFilter(MB1);
can2.enhanceFilter(MB2);
can2.enhanceFilter(MB3);
can2.enhanceFilter(MB4);
can2.enhanceFilter(MB5);
#endif
#ifdef CAN2_FILTERS_ON
can2.setMBFilter(MB1, 0x18DA98F1, 0x18DAF198);
can2.setMBFilter(MB2, 0x18DA60F1, 0x18DAF160);
can2.setMBFilter(MB3, 0x18DAF1C0, 0x18DAC0F1);
can2.setMBFilter(MB4, 0x18DAA1F1, 0x18DAF1A1);
can2.setMBFilter(MB5, 0x18DAF140, 0x18DA40F1);
#endif
can2.mailboxStatus(); // get the updated settings of mailboxes
delay(10000); // ms
}
/* ============= main loop ============= */
void loop() {
// In interrupt mode (without events() being used), the message is fired directly to callback without using queues.
// In interrupt mode (with events() being used), the ISR sends the mailbox frame directly to the queue where it can be dequeued to the callback via events() in loop().
// In polling mode, message is read directly from mailbox and nothing is queued. Failing to read the messages will cause message overruns if the mailboxes are full.
CanPacketByte cpb;
memset(&cpb, 0, sizeof(cpb));
int serialLen = readHardwareSerial(cpb);
// if we received a packet of correct length
if (serialLen == sizeof(CanPacket)) {
// if the size of payload is correct
if (cpb.packet.payload_length <= CAN_PAYLOAD_SIZE_BYTES) {
// prepare a CAN message
for (uint8_t i = 0; i < cpb.packet.payload_length; i++) {
msg.buf[i] = cpb.packet.payload[i];
}
msg.len = cpb.packet.payload_length;
msg.id = cpb.packet.id;
msg.flags.extended = 1;
// write to a CAN bus
if(cpb.packet.bus == 1) can1.write(msg);
else can2.write(msg);
// delay(1);
}
}
// in interrupt mode, dequeue to the callback (push received interrupt frames from the queue to the callback)
can1.events();
can2.events();
}
/* ============= function definition ============= */
/*
* @brief: a global callback invoked by the FlexCAN library when a CAN message is received.
* @params:
* msg - the received CAN message
*/
void canSniff20(const CAN_message_t &msg) {
CanPacket cp;
memset(&cp, 0, sizeof(cp));
cp.id = msg.id;
cp.payload_length = msg.len;
cp.bus = msg.bus;
for (int i = 0; i < cp.payload_length; i++) {
cp.payload[i] = msg.buf[i];
}
HardwareSerial.write( (byte*)&cp, sizeof(CanPacket) ); // send the packet
HardwareSerial.flush(); // wait for any transmitted data still in buffers to actually transmit (returns immediately if no data is waiting in a buffer to transmit)
}
/*
* @brief: reads a CAN packet (represented as a union of bytes) from the serial port of the Arduino board
* @params:
* p - represent a CAN packet in two different formats: as a struct of separate fields, or as an array of bytes
*/
int readHardwareSerial(union CanPacketByte& p)
{
// if a partial packet received, skip
if (HardwareSerial.available() < (int)sizeof(CanPacket)) return 0;
size_t nByte = 0;
while (HardwareSerial.available() > 0) {
char rc = HardwareSerial.read();
p.binary[nByte++] = rc;
if (nByte >= sizeof(CanPacket)) {
// Log error here
break;
}
}
return (int)nByte;
}