A follow up project with the Arduino MKR CAN Shield. In this post, I try to offload as much effort as possible to the CAN IC, and reduce the load on the Arduino microcontroller. |
CAN Receive with Interrupts
The CAN controller, the Microchip MCP2515Microchip MCP2515, has several features that handle traffic management in hardware.
The first one I review here, the read interrupt, takes care that the controller doesn't have to check if data is arriving.
Instead of looping and polling all the time, the Arduino can spend its precious resources on other activities, or can go to a low power mode.
When traffic arrives, the CAN controller sends a signal on its trigger pin.
That pin is watched by the Arduino (the CAN library from Sandeep Mistry takes care of that).
My task, as user of the library, is to:
- inform that I want to use the interrupt, and
- provide an interrupt service handler.
The interrupt is activated by adding this piece of code:
// register the receive callback CAN.onReceive(onReceive);
The interrupt function will be called when data arrives on the bus:
void onReceive(int packetSize) { // received a packet Serial.print("Received "); if (CAN.packetExtended()) { Serial.print("extended "); } if (CAN.packetRtr()) { // Remote transmission request, packet contains no data Serial.print("RTR "); } Serial.print("packet with id 0x"); Serial.print(CAN.packetId(), HEX); if (CAN.packetRtr()) { Serial.print(" and requested length "); Serial.println(CAN.packetDlc()); } else { Serial.print(" and length "); Serial.println(packetSize); // only print packet data for non-RTR packets while (CAN.available()) { Serial.print((char)CAN.read()); } Serial.println(); } Serial.println(); }
This isn't my code. It's the CANReceiverCallback example from the library I'm using.
In that example, the loop() method does nothing. That's because it doesn't have to do anything. All is handled by the interrupt.
void loop() { // do nothing }
This is an excellent way to prove that the mechanism works.
In a real project, you may want to minimise the logic in the interrupt handler, and handle the majority of the processing in the loop().
That, in combination with sleeping whenever possible, can result in a well-behaving, low power design.
Add Hardware Filtering
The CAN controller has an additional hardware feature. It can filter inbound messages and only pass them to the Arduino if they match the filter's scope.
Together with the interrupt, this is a powerful combination.
Ther microcontroller just has to read the data. It already knows that that data is intended for it.
In the first section, the microcontroller received an interrupt for each message appearing on the bus. It had to check the message ID of each data chunk and decide if it was of interest.
If the message wasn't intended for our design, we spent precious clock ticks to find that out. And if we were in low power mode, we woke up for nothing.
In this example with filters, the hardware capabilities of the CAN controller are put to optimal use. The controller only has to spend clock ticks if the message is relevant.
The change to the code is minimal. Add this snippet before the interrupt handler registration:
// Set ID Filter if (!CAN.filter(0x01)) { Serial.println("Setting CAN ID filter failed!"); while (1); }
The screen capture above shows the project in action.
I used the Microchip CAN AnalyzerMicrochip CAN Analyzer to inject messages on the bus.
What you see on the image is the result of shooting two in-scope messages. Not shown, but tested by me, is that messages with a different ID are nicely ignored.