espp/twai

1.3.5

Latest
uploaded 4 hours ago
TWAI (CAN 2.0) peripheral wrapper for ESP-IDF using the esp_driver_twai node API

Readme

# TWAI (CAN 2.0) Component

[![Badge](https://components.espressif.com/components/espp/twai/badge.svg)](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.

Links

Supports all targets

Maintainer

  • William Emfinger <waemfinger@gmail.com>
To add this component to your project, run:

idf.py add-dependency "espp/twai^1.3.5"

download archive

Stats

  • Archive size
    Archive size ~ 33.12 KB
  • Downloaded in total
    Downloaded in total 30 times
  • Weekly Downloads Weekly Downloads (All Versions)
  • Downloaded this version
    This version: 0 times

Badge

espp/twai version: 1.3.5
|