Optimal CAN processing strategy

Hi,

I’m using a SAME70Q21 to (among other things) interface with an ADM3053 CAN transceiver. I have already written some basic firmware which allows me to send and receive CAN packets, and have incorporated that into my existing FreeRTOS package.

At present, I use the ‘new message received’ interrupt, which is handled by an interrupt routine when a new message is appended to the CAN FIFO buffer. This works fine in my limited bench testing, but I’m concerned that generating an interrupt, and processing each individual message one at a time, will cause issues in real-world, high bandwidth applications.

As far I can see, there are 4 high-level strategies I could adopt:

  1. Use the ‘new message’ interrupt for every received message, and process each received message as and when it is received. As per the above, this is the current approach.

  2. Ensure the CAN FIFO buffer is sufficiently large, and use a FreeRTOS task to poll (set to a suitable frequency) the FIFO buffer for new messages, and process the received messages in batches.

  3. A hybrid of the above two; trigger an interrupt on each ‘new message’ received in the CAN FIFO, but only increment a counter until a sufficient batch of messages has been received (or potentially a suitable amount of time has elapsed), and then process the batch using a ‘FromISR’ function (e.g. xQueueSendFromISR).

  4. Instead of incrementing a counter with the ‘new message’ interrupt, instead use the CAN FIFO watermark interrupt, to deliver the same functionality without wasting cycles on each individual interrupt. This makes sense to me, but having already played with the watermark approach, I don’t think I fully understand the process/workflow.

I understand this isn’t necessarily a FreeRTOS-specific question, but I think it relates to general peripheral interrupt and processing strategy, so hopefully it can become a useful resource on the forum.

Thanks!

1 Like

First decision you need to make, is the current hardware FIFO big enough to handle the possible backlog in processing. If it is, then you can just use it and trigger the processing software when a message arrives. IF the processing is very fast, this might be done inside the ISR, if longer, it activates a task (‘very fast’ being evaluated in the context of needed response in other parts of the system)

If the hardware FIFO isn’t big enough, than you need to copy messages from that FIFO into some larger buffering schema, like a Queue. Note that once you have gotten into the ISR, it really doesn’t make sense to delay fetching the message, you aren’t saving anything. It might make sense to hold off waking up the task to process the data, in which case you can’t just put the data into a FreeRTOS queue, but need to build your own buffer, and only signal the task when there is enough to want to do the processing. If you can delay the interrupt until there is enough to be worth processing, that could make sense to use to cut down the ISR overhead.

I have built serial drivers based on that last sort of method, you limit ISR overhead by only getting an interrupt when the input buffer has gotten sufficient characters or some characters and a gap in characters (this requires hardware that supports this), and only then do you go into the ISR to empty the buffer and send the data to the task processing it, which saves some processing time.

The best answer will always be ‘it depends’ because it does. What is the hardware capable of doing? How much work does it take to process the message, and can the software keep up with the hardware or not? How much latency am I allowed in responding to a message?

These answer affect what sort of strategy you need to adopt.

We use a FreeRTOS task to process messages from the FIFO until it is empty. Then we have the ISR call TaskNotifyGiveFromISR to wake it up when a new message arrives. So it’s similar to your #1. We do it this way to get low latency between a message arriving and being processed.

1 Like