To use the built in CAN hardware, we have adapted ('forked') the most common CAN Arduino library and made changes so that it works seamlessly on a SAME51
The new library lives on github at https://github.com/adafruit/arduino-CAN
Other than having to enable the transceiver and boost supply, you can use existing code for the CAN library 'as is'!
Install the Arduino Library
Right now the new library is not in the library manager. You will have to install it manually To do so, download the zip of the library from https://github.com/adafruit/arduino-CAN/archive/master.zip
In the Arduino IDE under the Sketch menu, select Include Library and Add .ZIP Library, then select the zip you just downloaded
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:
// Copyright (c) Sandeep Mistry. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include <CAN.h> void setup() { Serial.begin(9600); while (!Serial); 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); } } 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
// Copyright (c) Sandeep Mistry. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include <CAN.h> void setup() { Serial.begin(9600); while (!Serial); 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); } } 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 sender 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 Arduino 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
// Copyright (c) ladyada. Public domain #include <CAN.h> #include <Adafruit_NeoPixel.h> Adafruit_NeoPixel strip(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); #define MY_PACKET_ID 0xAF uint32_t timestamp; void setup() { Serial.begin(9600); 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); } 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!