ACANFD_SAME
A CAN FD / Classic CAN Arduino library for SAME51/SAME54 boards, with automatic BSP-driven CAN pin detection and validation.
ACANFD_SAME is my CAN FD / Classic CAN Arduino library for SAME5x, SAME51 and SAME54 microcontrollers. It supports both CAN0 and CAN1 controllers found on these parts and is the CAN driver I use underneath CANMessageSignal and is what MicroCAN-FD relies on.
It is based on Pierre Molinaro’s excellent ACANFD_FeatherM4CAN library, which is specific to the Adafruit Feather M4 CAN Express but I extended it towards broader SAME5x board support with automatic pin handling. ACANFD_SAME is therefore not board specific and works with the Feather M4 CAN as well as MicroCAN-FD and any other board that uses the ATSAME5x/SAME5x family of microcontrollers. I have tested it and it works on the SAM E54 XPLAINED PRO evaluation board and should work on any of the Microchip evaluation boards that use the SAME51/54 MCUs.
TL;DR: ACANFD_SAME is designed to work across SAME5x-based boards, supports both channels, CAN 2.0A/B and CAN FD. It validates the actual MCU pin mapping and removes much of the repetitive setup involved in bringing up the SAME5x MCAN peripherals such as message RAM layout, filters, FIFOs and CAN frame handling every time.
Why I built it
I needed a CAN library that worked cleanly for MicroCAN-FD, but I did not want it to be a one-board-only driver. The SAME51/SAME54 is a great chip with powerful CAN controllers and I have plans to use it on many boards in the future and I don’t want to have a separate library for each.
Therefore I set about getting the library to work across different Arduino board packages without hard-coding one set of pins or hoping everyone else has the same variant file.
That matters because CAN TX/RX names in an Arduino BSP are not always as obvious as they look. A macro called PIN_CAN1_TX does not guarantee that the underlying MCU pin is actually connected to the CAN1 peripheral. On the Adafruit Feather M4 CAN Express, for example, the onboard transceiver is connected to the real CAN1 pins, but the BSP exposes those pins as PIN_CAN_TX / PIN_CAN_RX, while PIN_CAN1_TX / PIN_CAN1_RX resolve to the real CAN0 pins.
I wanted the library to validate the actual MCU port and pin before configuring the peripheral. If the selected pins do not belong to the requested CAN controller, the library should reject them clearly instead of silently configuring the wrong thing.
What it supports
- CAN0 and CAN1 where the selected SAME5x device and board expose them.
- Classic CAN 2.0 data frames and remote frames.
- CAN FD frames up to 64 data bytes.
- CAN FD bit-rate switching.
- Standard and extended identifiers.
- RX FIFO0 and RX FIFO1.
- Standard and extended hardware filters.
- Interrupt-driven receive and transmit completion handling.
- Optional transmit acknowledge storage.
- Internal loopback, external loopback, normal CAN FD mode and bus monitoring mode.
- Automatic CAN TX/RX pin mux configuration from the selected Arduino BSP.
- Optional
setPins()override for unusual boards or manual testing.
The examples cover single-channel CAN0 and CAN1 use, dual-channel transmit, Classic CAN 2.0, CAN FD loopback, intensive payload tests, standard and extended filters, callbacks, FIFO0/FIFO1 routing, transceiver delay compensation and a simple pass-through example that modifies selected messages between two channels.
The board support problem
For a normal SAME51/SAME54 board package, the BSP should define explicit controller-numbered names such as:
#define PIN_CAN0_TX (...)
#define PIN_CAN0_RX (...)
#define PIN_CAN1_TX (...)
#define PIN_CAN1_RX (...)
The library then looks those Arduino pin numbers up in g_APinDescription[], checks the real MCU port and pin, and validates them against the legal CAN pin mux combinations.
For the SAM D5x/E5x / SAME51 / SAME54 parts, the recognised combinations are:
| Controller | TX pin | RX pin | Peripheral function |
|---|---|---|---|
| CAN0 | PA22 | PA23 | I |
| CAN0 | PA24 | PA25 | I |
| CAN1 | PB12 | PB13 | H |
| CAN1 | PB14 | PB15 | H |
The important bit is that the library does not blindly trust the macro name. The actual MCU pin decides whether the pins are valid for can0 or can1.
If automatic detection is not suitable, then you can explicitly set the Arduino TX/RX pins in the sketch before calling beginFD():
can1.setPins(PIN_CAN_TX, PIN_CAN_RX);
const uint32_t errorCode = can1.beginFD(settings);
Those explicit pins are still validated. If they do not belong to the requested controller, beginFD() returns kInvalidCANPinMux.
The code is primarily targeted at SAM D5x/E5x, SAME51 and SAME54 devices. There are SAME70 MCAN entries in the pin validation table, but I am not presenting that as full SAME70 support.
Typical sketch shape
The library expects the sketch to allocate CAN message RAM before including ACANFD_SAME.h. The value is in 32-bit words, not bytes.
#define CAN0_MESSAGE_RAM_SIZE (0)
#define CAN1_MESSAGE_RAM_SIZE (1728)
#include <ACANFD_SAME.h>
Using 0 disables that CAN module, so its pins can be used for something else. If the allocation is too small for the settings, filters and FIFOs requested by the sketch, beginFD() returns an error. The examples print messageRamRequiredMinimumSize() so the allocation can be tuned rather than guessed.
A simple CAN1 setup looks like this:
ACANFD_SAME_Settings settings(
ACANFD_SAME_Settings::CLOCK_48MHz,
500 * 1000,
DataBitRateFactor::x1
);
settings.mModuleMode = ACANFD_SAME_Settings::NORMAL_FD;
const uint32_t errorCode = can1.beginFD(settings);
CANFDMessage is used for both Classic CAN and CAN FD frames. A Classic CAN frame is just a CANFDMessage with type = CANFDMessage::CAN_DATA and a length up to 8 bytes. A CAN FD frame can use payload lengths up to 64 bytes, with or without bit-rate switching.
Filters, FIFOs and callbacks
The library supports both standard and extended filters, and lets a sketch route accepted frames to FIFO0 or FIFO1.
ACANFD_SAME::StandardFilters filters;
filters.addSingle(0x123, ACANFD_SAME_FilterAction::FIFO0);
filters.addClassic(0x200, 0x700, ACANFD_SAME_FilterAction::FIFO1);
const uint32_t errorCode = can1.beginFD(settings, filters);
For callback-driven sketches, received messages can be dispatched from loop():
can1.dispatchReceivedMessage();
That is useful when the firmware only cares about known IDs and the application code should react to those IDs directly. Polling FIFO0 and FIFO1 is still available when that is a better fit.
Enable / Standby Pins
ACANFD_SAME configures the MCU CAN TX/RX pin muxes. It does not drive any enable/standby pins, because that is board-specific hardware behaviour. This library is intended to be as board agnostic as possible; some boards may have the enable/standby pins tied in hardware, while others may be connected to the MCU. Therefore it is up to the user to control these pins in their sketch.
For example, MicroCAN-FD uses MCU controlled standby pins for the transceivers, and the sketch handles those pins before starting CAN. The Adafruit Feather M4 CAN Express has its own standby and boost-enable pins. So the separation is deliberate: the library owns the CAN controller setup, while the board or application code owns transceiver enable, standby, silent mode or boost control.
Where it fits in my projects
ACANFD_SAME is the foundation layer. It handles the SAME5x CAN controllers, bit timing, message RAM, FIFOs, filters, transmit queues and pin mux validation.
CANMessageSignal sits above it and lets firmware define messages and signals in a DBC-style way. That means application code can work with values like RPM, speed, lamp state or temperature rather than constantly packing and unpacking bytes by hand.
MicroCAN-FD uses both: ACANFD_SAME provides the CAN hardware access, while CANMessageSignal makes translator, emulator and gateway firmware much easier to write and maintain.
Current state
The library is released as open source and is currently at v1.0.0. It has been tested with my MicroCAN-FD hardware, the Adafruit Feather M4 CAN Express and Microchip ATSAMEx development boards including SAME54-class hardware.