Linux supports CAN networks. I'm using a CAN-enabled Raspberry Pi to test some examples. |
Design and code are not my own. This exercise is to investigate the Linux way of doing CAN.
The code I'm using is Craig Peacock's CAN example. For the hardware, see the box below.
A common Raspberry Pi doesn't have a CAN bus. But it can be added as a hat (e.g: PiCAN2), or you can build your own. I'm using a SmartEdge IIOT GatewaySmartEdge IIOT Gateway . It's a Raspberry Pi 3 Compute based industrial Linux box. It has a CAN physical layer - a real CAN bus driver. And all drivers pre-installed. See AVNET SmartEdge IIOT Gateway: Use the Isolated CAN. The result of the 3 options above are exactly the same. The PiCAN2, the diy example and the SmartEdge solution are the same.
If you don't have CAN hardware, you can use Linux' virtual CAN driver vcan. That will turn your Linux device (Pi, BB, ....) into a self-contained virtual CAN network (like using 127.0.0.1 in a network context or Oracle's bequath database connection). You'll then receive the same data you send out. |
Goal: Listen to a CAN bus and only pick up messages I'm interested in
A CAN bus in a a car can (sic) be busy. A lot of information and commands are on there continuously.
And every node in the network receives all that data. It isn't routed.
You could try and parse the interesting messages out in your software. It works but is compute- and I/O-intensive.
Luckily, CAN hardware modules typically have a method to filter out desired message scope.
Let's pretend that our Linux module is a rear view camera.
When the car is put in reverse, the camera should switch on, and the video signal has to be overlaid on the dashboard display.
Putting the car in reverse generates a known message on the CAN bus.
So we can either listen to the CAN bus, and react if we recognise that the message is "car in reverse".
Or we can program our Linux module, so that we only get messages that are related to "car in reverse" events.
And that's the goal. Setting up that filter so that our Linux OS is freed of the burden of listening and filtering.
Filter and Mask
The solution that's used in CAN peripherals is to support sets of filters and masks.
A CAN message starts with an ID, then a count of the payload bytes, then the payload.
The filter and mask work on the ID.
- the filter defines what messages to listen for.
- the mask defines what bits in the filter have to match, and which ones can be ignored.
Let's pretend that all messages related to "car in reverse" are in the range 0x550 -> 0x55F.
We can set our filter as 0x550 and mask to 0xFF0.
In binary:
0b010101010000
0b111111110000
The CAN peripheral only watches for IDs where the relevant bits match the filter.
0b01010101****
Any message with ID between 0b010101010000 and 0b010101011111 will be passed to Linux.
That's what we wanted. Any message in the range 0x550 -> 0x55F.
The code looks like this:
struct can_filter rfilter[1]; rfilter[0].can_id = 0x550; rfilter[0].can_mask = 0xFF0; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter)); nbytes = read(s, &frame, sizeof(struct can_frame)); if (nbytes < 0) { perror("Read"); return 1; } printf("0x%03X [%d] ",frame.can_id, frame.can_dlc); for (i = 0; i < frame.can_dlc; i++) printf("%02X ",frame.data[i]);
The code will be sitting and waiting on line 07 until a message within range arrives.
A message passed through the filter. Click to enlarge.
Multiple Filters
There will be cases where you want to react on several ranges or IDs. The number of filters is dependent on the CAN IC and the Linux driver/
The MCP2515 that's used in the majority of Raspberry Pi and Arduino designs, can handle six filters and two masks.
I haven't checked what the Linux driver does with that. Let's leave that as an exercise for the reader.
As indicated by Craig (see a link to his blog at the end of this article), the current Linux driver doesn't use the IC's filtering capabilities and performs filtering in the driver code.
Here's Craig's same example, modified for two sets of filters:
struct can_filter rfilter[2]; rfilter[0].can_id = 0x550; rfilter[0].can_mask = 0xFF0; rfilter[1].can_id = 0x200; rfilter[1].can_mask = 0x700;
The rest of the code is identical.
It will still listen to the 0x550 -> 0x55F range.
In addition, it will also send the payload to Linux when messages in this range appear on the bus:
Filter 0x200 and mask to 0x700.
In binary:
0b001000000000
0b011100000000
The CAN peripheral only watches for IDs where the relevant bits match the filter.
0b*010********
You'll receive any message that matches that filter.
0x200 matches. But also e.g. 0xAFF.
It may seem an odd combination, but in a typical context, CAN message IDs are chosen/grouped based on this kind of capability.
Read Craig's blog: https://www.beyondlogic.org/category/can-controller-area-network/.
Top Comments