This repository adds TWAI/CAN(USER_C_MODULES) support to MicroPython for the ESP32 family. Use user_module, that draft probe. But works. From: micropython/micropython#12331
Read this section if you want to include the ESP32 TWAI/CAN support to MicroPython from scratch. To do that follow these steps:
- Note: The steps below now also work for MicroPython "esp32", "1.25" with ESP-IDF v5.4.1
-
Clone the MicroPython repository:
git clone --recursive https://github.com/micropython/micropython.git
-
Clone this repository:
git clone https://github.com/vostraga/micropython-esp32-twai.git
Copy the files and folders inside the root folder where
micropython
. -
Directory structure should look like this:
/ ├── micropython/ # MicroPython repository │ └── port/ │ └── esp32/ # build point │ └── cmodules/ └── micropython-esp32-twai/ ├── src/ # First version of CAN module └── src_can_v2/ # Second version with improvements
# Set environment variables
export IDF_TOOLS_PATH="$HOME/tools/esp_idf_v5.4.1/esp_tool" IDF_PATH="$HOME/tools/esp/v5.4.1/esp-idf"
# Create directories
mkdir -p "$HOME/Documents/dev_iot/opt/upy/tools/esp_idf_v5.4.1/esp_tool"
cd "$HOME/Documents/dev_iot/opt/upy/tools/esp_idf_v5.4.1"
# Clone ESP-IDF repository
git clone -j8 -b v5.4.1 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
# Install and export
./install.sh
. ./export.sh
export IDF_TOOLS_PATH="$HOME/upy/tools/esp_idf_v5.4.1/esp_tool" IDF_PATH="$HOME/tools/esp_idf_v5.4.1/esp-idf" && . "$IDF_PATH/export.sh"
Read official documentation for complete instructions. Basic steps:
# Go to micropython repository
cd micropython
# Build mpy-cross
make -C mpy-cross
# Go to ESP32 port
cd ports/esp32
# Delete lock files if present
rm *.lock
# Update submodules
make submodules
For example, to build for ESP32-S3 with octal PSRAM (go to build point):
idf.py -D MICROPY_BOARD=ESP32_GENERIC_S3 -D MICROPY_BOARD_VARIANT=SPIRAM_OCT -D USER_C_MODULES="../../../../cmodules/micropython-esp32-twai/src_can_v2/micropython.cmake" -B build_ESP32_GENERIC_S3_SPIRAM_OCT build
For testing, connect pin 4 and pin 5 together for loopback mode.
MicroPython v1.25.0 on 2025-05-09; PicoW_S3 with ESP32-S3
Type "help()" for more information.
>>>
>>> import CAN
>>> dev = CAN(0, extframe=False, tx=5, rx=4, mode=CAN.LOOPBACK, bitrate=50000, auto_restart=False)
CAN: TIMING
CAN: timing brp=0
CAN: timing tseg_1=15
CAN: timing tseg_2=4
CAN: timing sjw=3
CAN: timing triple_sampling=0
CAN: BRP_MIN=2, BRP_MAX=16384
CAN: bitrate 50000kb
CAN: Mode 1
CAN: Loopback flag 1
>>> dev
CAN(tx=5, rx=4, bitrate=50000, mode=NO_ACK, loopback=1, extframe=0)
import asyncio
import CAN
dev = CAN(0, extframe=False, tx=5, rx=4, mode=CAN.LOOPBACK, bitrate=50000, auto_restart=False)
6ED8
# - identifier of can packet (int)
# - extended packet (bool)
# - rtr packet (bool)
# - data frame (0..8 bytes)
async def reader():
while True:
if dev.any():
data = dev.recv()
print(f"RECEIVED: id:{hex(data[0])}, ex:{data[1]}, rtr:{data[2]}, data:{data[3]}")
await asyncio.sleep(0.01)
async def sender():
counter = 0
while True:
# Send a message once per second
msg_id = 0x123 # CAN message identifier
# Use list of bytes instead of bytes object
msg_data = [counter & 0xFF, (counter >> 8) & 0xFF]
# Correct parameter order: data first, then ID
dev.send(msg_data, msg_id) # data, id
print(f"SENT: id:{hex(msg_id)}, data:{msg_data}")
counter += 1
await asyncio.sleep(1) # Send message once per second
async def main():
# Start both tasks concurrently
read_task = asyncio.create_task(reader())
send_task = asyncio.create_task(sender())
# Wait for both tasks (this will run forever)
await asyncio.gather(read_task, send_task)
# Run the example
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
SENT: id:0x123, data:[0, 0]
RECEIVED: id:0x123, ex:False, rtr:False, data:b'\x00\x00'
SENT: id:0x123, data:[1, 0]
RECEIVED: id:0x123, ex:False, rtr:False, data:b'\x01\x00'
SENT: id:0x123, data:[2, 0]
RECEIVED: id:0x123, ex:False, rtr:False, data:b'\x02\x00'
SENT: id:0x123, data:[3, 0]
RECEIVED: id:0x123, ex:False, rtr:False, data:b'\x03\x00'
SENT: id:0x123, data:[4, 0]
RECEIVED: id:0x123, ex:False, rtr:False, data:b'\x04\x00'