It's easy to use the CAN Pal with CircuitPython and the built-in canio module. This module allows you to easily write Python code that lets you utilize your board's onboard CAN peripheral with the CAN Pal transceiver.
Note that not all chips have a CAN peripheral! Some that we know do have it are the ESP32/ESP32-S2/ESP32-S3 (note that ESP32 calls this interface TWAI not CAN) series of chips, SAME51, STM32F405, and Teensy 4.
CircuitPython Microcontroller Wiring
First, wire up a CAN Pal to your board exactly as shown below. You're going to do this twice so you have two CAN pals identically connected to two microcontrollers.
Here's an example of wiring a QT Py ESP32-S2 to the CAN Pal on a breadboard with 0.100" pitch headers:
- QT Py 3.3V to CAN Pal VCC (red wire)
- QT Py GND to CAN Pal GND (black wire)
- QT Py RX to CAN Pal RX (green wire)
- QT Py TX to CAN Pal TX (blue wire)
Then, connect the two CAN Pal CAN Bus connections together via the 3.5 mm terminal blocks:
- CAN Pal 0 L to CAN Pal 1 L (yellow wire)
- CAN Pal 0 GND to CAN Pal 1 GND (black wire)
- CAN Pal 0 H to CAN Pal 1 H (pink wire)
CircuitPython Usage
To use with CircuitPython, you need to update code.py with the example script. There are no additional libraries needed since the code is utilizing core modules.
In the examples below, click the Download Project Bundle button below to download the the code.py file in a zip file. Extract the contents of the zip file, and copy the code.py file to your CIRCUITPY drive.

can = canio.CAN(rx=board.RX, tx=board.TX, baudrate=250_000, auto_restart=True)
Then, upload the code to one of the QT Py ESP32-S2's.
# 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
can = canio.CAN(rx=board.RX, tx=board.TX, baudrate=250_000, auto_restart=True)
Then, upload the code to the remaining QT Py ESP32-S2.
# 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}")
In the REPL, you'll be able to see the message count and timestamp as they are sent and received. For the QT Py ESP32-S2 running the Sender example, the REPL will look like this:
For the QT Py ESP32-S2 running the Listener example, the REPL will look like this:
Going Further
For more information on using CAN Bus with CircuitPython, check out the CAN Bus with CircuitPython: Using the canio module Learn Guide.
Page last edited January 21, 2025
Text editor powered by tinymce.