Hello everyone! Hope you are all doing well. This is going to be a long post, so buckle up.
My friend and I are working on a CAN bridge of sorts (I don't want to get into it too in-depth as we plan on selling it later, but I will post more code if it's necessary). We are using ESP32-S3's, and MCP2515 breakout boards (with 20Mhz crystals). We are using a custom PCB, and everything works (kinda, it misses messages- more on that below).
We are using coryjfowler's MCP_CAN library, and it has been working well so far. We set up interrupts to read the CAN messages, and we can read two full 250kbps bus (at 100% bus load each). We have PCAN boxes so that we can simulate CAN bus traffic (as well as read messages that we send onto the CAN bus).
The problem we are running into is sending messages back onto another bus. We send the messages to another ESP32-S3, it sends the messages into a sending queue (all the messages make it in the queue), and then to the SendMsgBuf function. There, it takes so long to send the message that it misses messages in the queue to send (the queue overflows). We could increase the queue size, but that's just delaying the inevitable. The MCP2515 breakout board has its own SPI bus, as well as an empty CAN bus (on the other end is the PCAN box where we can read the messages). We tested resistance across the lines and got 60 ohms like we should. However, we're not getting all the messages we should be sending.
We are using both cores, too. The receive interrupts are on core one, and the Transmit and Receive_Buffer (where it sends the CAN message) are on core zero. We originally had the Receive_Buffer send the message to a queue, with the can_send function (on core one, we also tried it on core zero to no effect) taking the message from that queue and sending it on the CAN bus, but it was still too slow. This is going to be a two-way communication device, by the way, so both of the ESP32-S3's need both the transmit and receive functions.
To get a sense of where we are now, see the rates below (we sent messages from the PCAN box to simulate messages, and calculated the rate by doing messages_received/messages_sent (both on the PCAN boxes).
At 100% bus load on one bus, we received 89% of messages.
At 75% bus load on one bus, we received 95% of messages.
At 75% bus load on two busses, we received 65% of messages.
We are aiming for a 95% receive rate for two busses each at 100% load. If we take out the SendMsgBuf (and add a memcpy so that it has something to do), that function is fast enough. That leads us to believe that the SendMsgBuf function is slow. We're not sure where, though. Are the TX buffers on the MCP2515 full? Is it stuck waiting for something on the CAN bus? Can it only send at a certain speed and we're exceeding that? Is there a faster function we can use? Can we put the sending function inside an interrupt like we did for the receive function (we thought about using them, but we couldn't figure out how to trigger it when there's a new message to send)? Would adding another thread (on the same core) of the Receive_Buffer (doing the same thing) increase speed? Some code snippets are below.
Struct definition and struct variable names
//Struct message for queues and sending/receiving messages
typedef struct struct_message {
int8_t can_chan = 0; //what channel the CAN message is on
unsigned long can_id; //the CAN ID of the message
byte can_data[8]; //the CAN data for the CAN message
byte extended; //a true/false if the ID is extended or not (mainly needed for sending/receiving a CAN message)
unsigned char length;
} struct_message;
// Create the structs for can 1 & 2
struct_message can1_incoming_message; //in terms of the CAN bus
struct_message can2_incoming_message; //in terms of the CAN bus
char can1_char[sizeof(can1_incoming_message)];
char can2_char[sizeof(can2_incoming_message)];
CAN receive ISR (this is mimicked for CAN 2)
// ISR for CAN 1 recieving and packaging to common queue
void IRAM_ATTR can1_can()
{
if(!digitalRead(CAN1_INT_PIN)) // If CAN1_INT pin is low, read receive buffer
{
CAN1.readMsgBuf(&can1_incoming_message.can_id, &can1_incoming_message.extended, &can1_incoming_message.length, can1_incoming_message.can_data); //read CAN message and store in struct
can1_incoming_message.can_chan = 1; //set the channel that the CAN message came from
memcpy(can1_char, &can1_incoming_message, sizeof(can1_incoming_message)); //convert struct to char array for transferring to other board
xQueueSend(can12_spisend_queue, &can1_char, (TickType_t)0); //send the message to the spi send queue for CAN 1&2
counter++; //troubleshooting - making sure we receive all messages
}
}
The receive buffer function
void rx_buffer(void *pvParameters) {
//Create structs for the outgoing messages, and RX char
struct_message empty_message;
char rx_messages[ARRAY_ELEMENTS][sizeof(empty_message)];
struct_message can1_outgoing_message;
struct_message can2_outgoing_message;
while(1) {
if(uxQueueMessagesWaiting(rx_buffer) > 0) { //check to see if any messages have been received
xQueueReceive(rx_buffer, &(rx_messages), (TickType_t)0); //read a message from the receive queue
for(int i = 0; i < ARRAY_ELEMENTS; i++) { //cycle through the incoming message
switch ((uint8_t)rx_messages[i][0]) //read the equivalent of the CAN channel to send to the proper queue
{
case 1: //If for CAN 1, memcpy to struct from the array, and then send to CAN 1 MCP board
//xQueueSend(can1_send_queue, rx_messages[i], (TickType_t)0); //send the incoming data to the SPI send queue
//portDISABLE_INTERRUPTS();
memcpy(&can1_outgoing_message, rx_messages[i], sizeof(can1_outgoing_message));
CAN1.sendMsgBuf(can1_outgoing_message.can_id, can1_outgoing_message.extended, can1_outgoing_message.length, can1_outgoing_message.can_data); //send the CAN message
//portENABLE_INTERRUPTS();
can1_rxcount ++;
break;
case 2: //If for CAN 2, memcpy to struct from the array, and then send to CAN 2 MCP board
//xQueueSend(can2_send_queue, rx_messages[i], (TickType_t)0); //send the incoming data to the CAN send queue
//portDISABLE_INTERRUPTS();
memcpy(&can2_outgoing_message, rx_messages[i], sizeof(can2_outgoing_message));
CAN2.sendMsgBuf(can2_outgoing_message.can_id, can2_outgoing_message.extended, can2_outgoing_message.length, can2_outgoing_message.can_data); //send the CAN message
//portENABLE_INTERRUPTS();
can2_rxcount ++;
break;
default:
//should only run if there is an empty message
break;
}
}
}
}
}
As you can see in this function, we have a lot commented out. We tried many different things, including disabling and then re-enabling interrupts, to no effect. The interrupts are operating on a completely different core. It is, however, the same SPI bus, which is why we originally had the CAN_send function on the same core. We cut that out and are now just sending from the receive_buffer function. If it stays here, we will uncomment the interrupt enable/disable commands so that there are no issues there. However, for right now, we are not receiving anything when we are sending (we are trying to get one way communication down first).
If you have any questions, feel free to ask and I'll do my best to answer them. Thank you in advance!
3 posts - 3 participants