This demo shows how to set up one Feather M4 CAN as a sender and another as a receiver. They will print messages on the serial terminal (REPL) to show what is going on.

Before trying this demo, make sure you have the right version of CircuitPython, that import canio succeeds, and that you've wired the two Feathers together as shown on the Wiring page.

First, we'll set up the listening (receiving) node. Put the text below in that device's code.py and open up the serial terminal. When the program restarts, it will display "No messsage received within timeout" until our second device is up and running.

# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import struct

import board
import canio
import digitalio

# If the CAN transceiver has a standby pin, bring it out of standby mode
if hasattr(board, 'CAN_STANDBY'):
    standby = digitalio.DigitalInOut(board.CAN_STANDBY)
    standby.switch_to_output(False)

# If the CAN transceiver is powered by a boost converter, turn on its supply
if hasattr(board, 'BOOST_ENABLE'):
    boost_enable = digitalio.DigitalInOut(board.BOOST_ENABLE)
    boost_enable.switch_to_output(True)

# Use this line if your board has dedicated CAN pins. (Feather M4 CAN and Feather STM32F405)
can = canio.CAN(rx=board.CAN_RX, tx=board.CAN_TX, baudrate=250_000, auto_restart=True)
# On ESP32S2 most pins can be used for CAN.  Uncomment the following line to use IO5 and IO6
#can = canio.CAN(rx=board.IO6, tx=board.IO5, baudrate=250_000, auto_restart=True)
listener = can.listen(matches=[canio.Match(0x408)], timeout=.9)

old_bus_state = None
old_count = -1

while True:
    bus_state = can.state
    if bus_state != old_bus_state:
        print(f"Bus state changed to {bus_state}")
        old_bus_state = bus_state

    message = listener.receive()
    if message is None:
        print("No messsage received within timeout")
        continue

    data = message.data
    if len(data) != 8:
        print(f"Unusual message length {len(data)}")
        continue

    count, now_ms = struct.unpack("<II", data)
    gap = count - old_count
    old_count = count
    print(f"received message: count={count} now_ms={now_ms}")
    if gap != 1:
        print(f"gap: {gap}")

Next, set up the transmitting (sending) node. Put the text below in that device's code.py and open up a second serial terminal. Once both programs are running, you should see the sender and receiver printing the same information.

# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import struct
import time

import board
import canio
import digitalio

# If the CAN transceiver has a standby pin, bring it out of standby mode
if hasattr(board, 'CAN_STANDBY'):
    standby = digitalio.DigitalInOut(board.CAN_STANDBY)
    standby.switch_to_output(False)

# If the CAN transceiver is powered by a boost converter, turn on its supply
if hasattr(board, 'BOOST_ENABLE'):
    boost_enable = digitalio.DigitalInOut(board.BOOST_ENABLE)
    boost_enable.switch_to_output(True)

# Use this line if your board has dedicated CAN pins. (Feather M4 CAN and Feather STM32F405)
can = canio.CAN(rx=board.CAN_RX, tx=board.CAN_TX, baudrate=250_000, auto_restart=True)
# On ESP32S2 most pins can be used for CAN.  Uncomment the following line to use IO5 and IO6
#can = canio.CAN(rx=board.IO6, tx=board.IO5, baudrate=250_000, auto_restart=True)

old_bus_state = None
count = 0

while True:
    bus_state = can.state
    if bus_state != old_bus_state:
        print(f"Bus state changed to {bus_state}")
        old_bus_state = bus_state

    now_ms = (time.monotonic_ns() // 1_000_000) & 0xffffffff
    print(f"Sending message: count={count} now_ms={now_ms}")

    message = canio.Message(id=0x408, data=struct.pack("<II", count, now_ms))
    can.send(message)

    time.sleep(.5)
    count += 1

In this case, the data being transmitted is a packet counter which starts at 0 and counts up 1 for each transmitted packet; and a timestamp which counts up by 1000 each second. These are sent as 4 byte values, for a total of 8 bytes—the maximum for a packet on the CAN bus.

If there are problems affecting the bus (such as a disconnected wire) then various error information will also be displayed. However, after fixing the wiring the devices should automatically recover and begin communicating again.

Typical output from sender:

code.py output:
Bus state changed to canio.BusState.ERROR_ACTIVE
Sending message: count=0 now_ms=372429
Sending message: count=1 now_ms=372929
Sending message: count=2 now_ms=373429
Sending message: count=3 now_ms=373929

Typical output from receiver:

code.py output:
Bus state changed to canio.BusState.ERROR_ACTIVE
received message: count=0 now_ms=372429
received message: count=1 now_ms=372929
received message: count=2 now_ms=373429
received message: count=3 now_ms=373929

This guide was first published on Nov 11, 2020. It was last updated on Nov 11, 2020.

This page (Send and Receive) was last updated on Mar 16, 2023.

Text editor powered by tinymce.