The Adafruit_CAN library provides support for native CAN peripherals, like the one in the ATSAME51 processor on the Feather M4 CAN Express.
Install the Arduino Library
The Adafruit_CAN library can be installed via the Arduino Library Manager. Find it by searching for "Adafruit CAN". Verify the library by name in the results and then click Install.
You'll also need the NeoPixel library.
When uploading code make sure you have Feather M4 CAN (SAME51) selected, not the Feather M4 Express!
Transmission & Reception Examples
Our first examples will transmit packets from one board to another. In this example we're using two CAN Feather boards but of course you can replace either side with a different CAN-enabled platform.
On one Feather load the transmitter example:
/* * Adafruit Feather M4 CAN Sender Example */ #include <CANSAME5x.h> CANSAME5x CAN; void setup() { Serial.begin(115200); while (!Serial) delay(10); Serial.println("CAN Sender"); pinMode(PIN_CAN_STANDBY, OUTPUT); digitalWrite(PIN_CAN_STANDBY, false); // turn off STANDBY pinMode(PIN_CAN_BOOSTEN, OUTPUT); digitalWrite(PIN_CAN_BOOSTEN, true); // turn on booster // start the CAN bus at 250 kbps if (!CAN.begin(250000)) { Serial.println("Starting CAN failed!"); while (1) delay(10); } Serial.println("Starting CAN!"); } void loop() { // send packet: id is 11 bits, packet can contain up to 8 bytes of data Serial.print("Sending packet ... "); CAN.beginPacket(0x12); CAN.write('h'); CAN.write('e'); CAN.write('l'); CAN.write('l'); CAN.write('o'); CAN.endPacket(); Serial.println("done"); delay(1000); // send extended packet: id is 29 bits, packet can contain up to 8 bytes of data Serial.print("Sending extended packet ... "); CAN.beginExtendedPacket(0xabcdef); CAN.write('w'); CAN.write('o'); CAN.write('r'); CAN.write('l'); CAN.write('d'); CAN.endPacket(); Serial.println("done"); delay(1000); }
On the other Feather load the receiver example
/* * Adafruit Feather M4 CAN Receiver Example */ #include <CANSAME5x.h> CANSAME5x CAN; void setup() { Serial.begin(115200); while (!Serial) delay(10); Serial.println("CAN Receiver"); pinMode(PIN_CAN_STANDBY, OUTPUT); digitalWrite(PIN_CAN_STANDBY, false); // turn off STANDBY pinMode(PIN_CAN_BOOSTEN, OUTPUT); digitalWrite(PIN_CAN_BOOSTEN, true); // turn on booster // start the CAN bus at 250 kbps if (!CAN.begin(250000)) { Serial.println("Starting CAN failed!"); while (1) delay(10); } Serial.println("Starting CAN!"); } void loop() { // try to parse packet int packetSize = CAN.parsePacket(); if (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(); } }
Now open the Serial console for both CAN Feathers (you may need to run Arduino two times, or use another serial port monitor so you can see both)
On the transmitter you'll see:
On the receiver you'll see the data getting received!
If not, check that L and H wires are both solidly connected
For more details on the Adafruit CAN library usage, check the documentation
Bi-Directional Communication Demo
OK now you can send and receive, but you'd like to do both? No problem, try this example!
On the two boards wired together, connect a potentiometer to each board, with the middle pin going to A5
and one side to ground and the other side to 3V
Upload the same code to both boards
/* * Adafruit Feather M4 CAN Transceiver Example */ #include <CANSAME5x.h> #include <Adafruit_NeoPixel.h> CANSAME5x CAN; Adafruit_NeoPixel strip(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); #define MY_PACKET_ID 0xAF uint32_t timestamp; void setup() { Serial.begin(115200); //while (!Serial) delay(10); Serial.println("CAN NeoPixel Potentiometer RX/TX demo"); pinMode(PIN_CAN_STANDBY, OUTPUT); digitalWrite(PIN_CAN_STANDBY, false); // turn off STANDBY pinMode(PIN_CAN_BOOSTEN, OUTPUT); digitalWrite(PIN_CAN_BOOSTEN, true); // turn on booster strip.begin(); strip.setBrightness(50); // start the CAN bus at 250 kbps if (!CAN.begin(250000)) { Serial.println("Starting CAN failed!"); while (1) delay(10); } timestamp = millis(); } void loop() { // every 100 ms send out a packet if ((millis() - timestamp) > 100) { uint16_t pot = analogRead(A5); // send a packet with the potentiometer value Serial.print("Sending packet with value "); Serial.print(pot); CAN.beginPacket(MY_PACKET_ID); CAN.write(pot >> 8); CAN.write(pot & 0xFF); CAN.endPacket(); Serial.println("...sent!"); timestamp = millis(); } // try to parse any incoming packet int packetSize = CAN.parsePacket(); if (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); uint8_t receivedData[packetSize]; for (int i=0; i<packetSize; i++) { receivedData[i] = CAN.read(); Serial.print("0x"); Serial.print(receivedData[i], HEX); Serial.print(", "); } Serial.println(); uint16_t value = (uint16_t)receivedData[0] << 8 | receivedData[1]; strip.setPixelColor(0, Wheel(value / 4)); strip.show(); } Serial.println(); } } uint32_t Wheel(byte WheelPos) { WheelPos = 255 - WheelPos; if(WheelPos < 85) { return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); } if(WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); }
Ten times a second, each board sends out the analog reading from A5
as two bytes of data. If you want to have more than two boards, you can give each board a unique ID by editing #define MY_PACKET_ID 0xAF
to change it to a different byte value
uint16_t pot = analogRead(A5); //... CAN.beginPacket(MY_PACKET_ID); CAN.write(pot >> 8); CAN.write(pot & 0xFF); CAN.endPacket();
We also check to read any packets, there are a few different types of CAN packets (not covered in this guide) so we make sure its a non-return-request type...
// try to parse any incoming packet int packetSize = CAN.parsePacket(); if (packetSize) { // received a packet Serial.print("Received "); //... 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);
We then read the data from the CAN buffer, convert the first two bytes back into a 16-bit number. That number is going to range from 0 to 1023 (because the analog input signal from the sender is 10-bit). We divide it by 4 so it ranges from 0-255 and then use our color Wheel function to convert into a rainbow color.
Note we do very little checking to verify the packet data and length, this is a very barebones example!
uint8_t receivedData[packetSize]; for (int i=0; i<packetSize; i++) { receivedData[i] = CAN.read(); Serial.print("0x"); Serial.print(receivedData[i], HEX); Serial.print(", "); } Serial.println(); uint16_t value = (uint16_t)receivedData[0] << 8 | receivedData[1]; strip.setPixelColor(0, Wheel(value / 4)); strip.show(); }
Twist the knob on one Feather breadboard to see the NeoPixel on the other Feather change colors!
Page last edited January 22, 2025
Text editor powered by tinymce.