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.

Wire up L to L and H to H on the bus. You do not need to connect Ground (but it is available)

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();
  }
}
Make sure whenever you load CAN read/write code you match the bus speed. Some examples are 500000 baud but we recommend 250000 baud! Whatever you use, all boards must have the same

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!

This guide was first published on Jan 25, 2021. It was last updated on Jan 25, 2021.

This page (Arduino CAN Examples) was last updated on Oct 15, 2021.

Text editor powered by tinymce.