# TWAI (CAN 2.0) Component
[](https://components.espressif.com/components/espp/twai)
The `Twai` class provides an idiomatic C++ interface to the ESP TWAI (Two-Wire
Automotive Interface, i.e. CAN 2.0) peripheral. It wraps the modern node-based
ESP-IDF driver (`esp_driver_twai`) and takes care of creating an on-chip TWAI
node, registering the driver's ISR event callbacks, and marshaling received
frames (and optional error / state-change events) from ISR context into a
task-context callback via an internal FreeRTOS queue and an `espp::Task`.
Because the driver's `on_rx_done` / `on_error` / `on_state_change` callbacks run
in ISR context, the `Twai` class copies each event into a FreeRTOS queue from
the ISR (`xQueueSendFromISR`) and drains that queue from an internal task. Your
`on_receive`, `on_error`, and `on_state_change` callbacks are therefore always
invoked from task context - never from the ISR - so they may safely call
blocking or non-IRAM-safe APIs. No lock is held while your callback runs.
The class supports the classic CAN 2.0 frame format (up to 8 data bytes).
CAN-FD (up to 64 data bytes and bit-rate switching) is a possible future
extension - the underlying driver and frame types support it, but this wrapper
intentionally keeps to classic CAN for a small, clean surface.
## Modes
* `Mode::NORMAL` - transmit, receive, and acknowledge frames. Requires a CAN
transceiver (e.g. SN65HVD230, TJA1050, MCP2551) and at least one other node on
the bus to acknowledge frames.
* `Mode::LISTEN_ONLY` - only monitor the bus; never transmit or acknowledge.
Useful for passive bus monitoring / sniffing.
* `Mode::LOOPBACK` - internal loopback + self-test: the controller receives back
the frames it transmits and does not require an acknowledgement. This lets the
node run with **no transceiver** and **no other node**, which is what the
example uses.
## Transmit result
`transmit(message, ec)` waits for the controller to finish with the frame and
reports how it went through its `std::error_code` output parameter:
* `std::errc::io_error` — the controller gave up on the frame after the
configured retry limit (`Config::tx_retry_count`; 0 means one attempt) was
exhausted by a missing acknowledgement, a bit error, or arbitration lost
(`Config::on_error` carries the reason). This is what a bus with no other
node, a missing transceiver or a bit-rate mismatch looks like.
* `std::errc::timed_out` — with `tx_retry_count = -1` the controller keeps
retransmitting (standard CAN behaviour) and nothing acknowledged the frame
within the timeout.
A frame for which `transmit()` returns `true` was acknowledged by another node
in `Mode::NORMAL` (in `Mode::LOOPBACK` the self-test flag waives the
acknowledgement, so it only means the frame went out). The default single
attempt suits time-critical data that must not be delivered late; set
`tx_retry_count` to a retry count, or -1, when a frame must get through.
## Pending transmissions: `flush()` and `abort_pending()`
`transmit()` is synchronous, so once it returns nothing of yours is normally
left with the controller. The exception is a frame whose completion wait timed
out: with `tx_retry_count = -1` and nothing acknowledging, the controller keeps
retransmitting it, and the driver keeps reading the frame from its TX ISR. The
ESP-IDF driver offers no abort for that: disabling the node only pauses the
transmission and re-enabling it resumes it. `espp::Twai` therefore gives you:
* `flush(ec, timeout_ms)` — wait until the controller has no pending
transmission (TX queue empty, nothing in progress). Use it before a stop that
must not be followed by anything older.
* `abort_pending(ec)` — drop the pending transmission(s). The only way the
driver allows is to delete the node and create it again (same configuration,
callbacks and filter; the receive task and everything already received are
untouched), which is what it does.
* `Config::auto_abort_on_timeout` (default `true`) — what a `transmit()` that
timed out waiting for completion does with its frame. On, it is dropped with
`abort_pending()` so the next `transmit()` proceeds at once. Off, it stays
with the controller (it may still go out when the bus comes back) and the
next `transmit()` first waits for it, up to its own timeout, failing with
`std::errc::timed_out` while it is still pending; `flush()` waits for it
explicitly and `abort_pending()` drops it.
## Hardware / wiring (NORMAL mode)
To talk to a real CAN bus you need a 3.3V CAN transceiver between the ESP TWAI
TX/RX pins and the bus:
```
ESP32 GPIO(tx) ---> CTX \
SN65HVD230 ==> CANH / CANL (120R terminated bus)
ESP32 GPIO(rx) <--- CRX /
```
Terminate the bus with 120Ω resistors at both physical ends. Typical baud rates
are 125 kbit/s, 250 kbit/s, 500 kbit/s (common in automotive), and 1 Mbit/s.
## Example
The [example](./example) uses `Mode::LOOPBACK` so it runs on a bare devkit with
no transceiver: it transmits a few frames, receives them back via the
`on_receive` callback, and asserts the round trip.
dd207f27d573f6a3bedae360227d5dc5862d614f
idf.py add-dependency "espp/twai^1.3.5"