[CAN 03] - Communicating with an ESP32 Using SocketCAN

Exchanging CAN frames between SocketCAN and an ESP32

Previous Posts:

Code: [GitHub]

Hardware

This example connects an Ubuntu computer to an ESP32 through a USB-to-CAN adapter and an external CAN transceiver. The original ESP32 includes a CAN-compatible controller, now documented by Espressif as the Two-Wire Automotive Interface (TWAI), but it does not include the physical-layer transceiver. Other microcontrollers may require both an external CAN controller, such as the MCP2515, and a transceiver.

Left: ESP32. Middle: TJA1050 module. Right: USB-to-CAN adapter.
Connecting the ESP32, transceiver, and USB-to-CAN adapter.

Connect the ESP32 to a CAN Transceiver

Connect the ESP32’s TWAI TX and RX signals to the transceiver’s logic-side TXD and RXD pins. Connect CAN-H and CAN-L only to the differential bus; they must never be connected directly to ESP32 GPIO pins.

This setup uses GPIO 17 for TX and GPIO 16 for RX. Other pins can be selected through the ESP32 GPIO matrix, but TX must be output-capable and the library configuration must match the wiring.

The TJA1050 is a 5 V transceiver. Many inexpensive modules expose a 5 V logic-high level on RXD, while ESP32 GPIO is 3.3 V. Check the exact module schematic and transceiver datasheet before connecting it. A 3.3 V-compatible transceiver, or appropriate level shifting, is the safer choice.

I used the Arduino IDE with the Arduino-CAN library, which provides a compact high-level API. Espressif’s current TWAI driver is a better reference when you need explicit timing, error-state, or recovery control.

Bit-rate note: An open Arduino-CAN issue reports a half-rate result on one ESP32/RTOS configuration. This is not a universal ESP32 rule. Start with the intended rate—for example, CAN.begin(500E3) for 500 kbit/s—and verify it with a known-good node or analyzer. Do not double the configured rate blindly. If the measured rate is wrong, check the exact board clock, ESP32 revision, library version, and bit-timing implementation.

Flash the linked example sketch to the ESP32. If the IDE reports that it cannot open /dev/ttyUSB0, add the user to the serial-device group:

sudo adduser <username> dialout

Replace <username> with the current user, then log out and back in so the new group membership takes effect. A temporary chmod can be useful for diagnosis, but a group or udev rule is the better persistent solution.

The example prints received CAN frames to the serial monitor and transmits the payload hello once per second.

On a normal CAN bus, at least one other active node must acknowledge a transmitted frame. If the transceiver, wiring, termination, or peer node is missing, transmission may fail, retry repeatedly, or block in CAN.endPacket(), depending on the library.

Reading CAN from Terminal

Assuming the USB adapter uses candleLight firmware, configure can0 for 500 kbit/s and bring it up:

sudo ip link set can0 up type can bitrate 500000

Open a new terminal and run:

candump can0
CAN frames received from the ESP32 with `candump can0`.

candump displays the payload as hexadecimal bytes. For an ASCII payload:

68 65 6C 6C 6F => hello

Writing CAN from Terminal

Use cansend <interface> <CAN-ID>#<payload> to transmit a Classic CAN data frame. The following command sends hello with base identifier 0x123:

cansend can0 123#68656C6C6F

If the bus is wired and terminated correctly and another node acknowledges the frame, the ESP32 serial monitor should display the received payload. Repeated transmit failures can eventually fill a host or device TX queue, so check the interface error counters rather than assuming that a successful application write means the frame reached the bus.

CAN payload displayed in the Arduino IDE serial monitor.

Generate Test Traffic

On an isolated test bus, cangen can generate synthetic frames:

cangen can0

This command transmits continuously until it is stopped. Do not run it on a live robot or vehicle network: random identifiers and payloads can trigger unintended device behavior or saturate the bus.

Test traffic displayed in the Arduino IDE serial monitor.

The frames are not incorrectly encoded; the example receiver simply interprets every byte as an ASCII character. To inspect arbitrary binary payloads, replace Serial.print((char)CAN.read()); with Serial.print(CAN.read(), HEX);.

References