TWAI (CAN 2.0) APIs
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 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.
The example uses Mode::LOOPBACK (internal loopback + self-test) so it runs on
a bare devkit with no CAN transceiver and no other node on the bus. To talk to a
real bus, use Mode::NORMAL with a 3.3V CAN transceiver (e.g. SN65HVD230)
wired to the TX/RX GPIOs and at least one other node present to acknowledge
frames.
API Reference
Header File
Classes
-
class Twai : public espp::BaseComponent
A class to interface with the ESP TWAI (CAN 2.0) peripheral.
This class wraps the modern node-based ESP-IDF TWAI driver (
esp_driver_twai) in an idiomatic C++ interface. It creates an on-chip TWAI node, registers ISR event callbacks, and marshals received frames (and optional error / state-change events) from ISR context into a task-context callback using an internal FreeRTOS queue and anespp::Task.All user callbacks (on_receive, on_error, on_state_change) are invoked from the internal task context - never from the ISR - so they may safely call blocking / non-IRAM-safe APIs. No lock is held while a user callback is invoked.
The class supports the classic CAN 2.0 frame format (up to 8 data bytes). CAN-FD (up to 64 data bytes, 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.
TWAI (loopback) Example
// Count the frames we receive back, so we can assert the round-trip. static std::atomic<int> num_received{0}; // The receive callback runs in task context (not ISR). It is invoked for // every frame the node receives - in loopback mode that is every frame we // transmit. auto on_receive = [&](const espp::Twai::Message &msg) { num_received++; logger.info("RX: {}", msg); }; auto on_state_change = [&](const espp::Twai::StateChange &sc) { logger.warn("State change: {} -> {}", static_cast<int>(sc.old_state), static_cast<int>(sc.new_state)); }; // NOTE: We use Mode::LOOPBACK (internal loopback + self-test) so this example // runs on a bare devkit with NO CAN transceiver and NO other node on the bus. // To talk to a real bus, use Mode::NORMAL with a transceiver (e.g. // SN65HVD230) wired to the tx/rx GPIOs, and make sure at least one other node // is present to acknowledge frames. espp::Twai twai({ .tx_gpio = 5, // GPIO5 (change to match your board / transceiver) .rx_gpio = 4, // GPIO4 (change to match your board / transceiver) .baudrate = 500000, .mode = espp::Twai::Mode::LOOPBACK, .tx_queue_depth = 5, .on_receive = on_receive, .on_state_change = on_state_change, .log_level = espp::Logger::Verbosity::INFO, }); std::error_code ec; if (!twai.initialize(ec)) { logger.error("Failed to initialize TWAI: {}", ec.message()); return; } // Transmit a few frames; in loopback mode each is received back via on_receive. static constexpr int num_to_send = 5; for (int i = 0; i < num_to_send; i++) { espp::Twai::Message msg; msg.id = 0x100 + i; msg.extended = false; msg.rtr = false; msg.dlc = 4; msg.data = {static_cast<uint8_t>(i), 0xDE, 0xAD, 0xBE, 0, 0, 0, 0}; if (twai.transmit(msg, ec)) { logger.info("TX: {}", msg); } else { logger.error("Failed to transmit frame {}: {}", i, ec.message()); } std::this_thread::sleep_for(50ms); } // give the RX task a moment to drain the queue std::this_thread::sleep_for(200ms); logger.info("Sent {} frames, received {} frames", num_to_send, num_received.load()); // print out the node status / statistics twai_node_status_t status; twai_node_record_t record; if (twai.get_info(status, record, ec)) { logger.info("Node state={}, tx_err={}, rx_err={}, bus_err_num={}", static_cast<int>(status.state), status.tx_error_count, status.rx_error_count, record.bus_err_num); } // assert the round trip if (num_received.load() == num_to_send) { logger.info("SUCCESS: all {} frames were received back in loopback mode", num_to_send); } else { logger.error("FAILURE: expected {} frames, got {}", num_to_send, num_received.load()); }
Public Types
-
enum class Mode
The operating mode of the TWAI node.
Values:
-
enumerator NORMAL
Normal mode: transmit, receive, and acknowledge frames on the bus. Requires a transceiver and at least one other acknowledging node.
-
enumerator LISTEN_ONLY
Listen-only mode: the node only monitors the bus and never transmits or acknowledges. Useful for passive bus monitoring / sniffing.
-
enumerator LOOPBACK
Loopback self-test mode: the controller receives back the frames it transmits and does not require acknowledgement. This lets the node run with no transceiver and no other node on the bus (used by the example).
-
enumerator NORMAL
-
typedef std::function<void(const StateChange&)> state_change_callback_fn
State-change callback type.
-
typedef std::function<void(twai_error_flags_t)> error_callback_fn
Error callback type.
Public Functions
-
inline explicit Twai(const Config &config)
Construct a new Twai object.
Note
This does not touch the hardware; call initialize() to create the node and start the receive task.
- Parameters:
config – The configuration for the TWAI node.
-
inline ~Twai()
Destructor. Stops the receive task and disables + deletes the node.
-
inline bool initialize(std::error_code &ec)
Initialize the TWAI node.
Creates the on-chip node, registers ISR event callbacks, applies the optional acceptance filter, creates the internal event queue and receive task, and (if Config::auto_start is true) enables the node.
- Parameters:
ec – The error code, set if initialization fails.
- Returns:
True on success, false on failure.
-
inline bool start(std::error_code &ec)
Enable (start) the TWAI node so it participates on the bus.
- Parameters:
ec – The error code, set if the node could not be enabled.
- Returns:
True on success, false otherwise.
-
inline bool stop(std::error_code &ec)
Disable (stop) the TWAI node.
- Parameters:
ec – The error code, set if the node could not be disabled.
- Returns:
True on success, false otherwise.
-
inline bool transmit(const Message &message, std::error_code &ec, int timeout_ms = DEFAULT_TX_TIMEOUT_MS)
Transmit a CAN message.
- Parameters:
message – The message to transmit.
ec – The error code, set if the transmission could not be queued.
timeout_ms – Max time (ms) to wait if the TX queue is full (-1 = forever).
- Returns:
True if the message was queued for transmission, false otherwise.
-
inline bool recover(std::error_code &ec)
Begin bus-off recovery.
Starts the recovery process for a node in the bus-off state. Use the on_state_change callback or get_status() to know when recovery has finished.
- Parameters:
ec – The error code, set if recovery could not be started.
- Returns:
True if recovery was started, false otherwise.
-
inline bool get_status(twai_node_status_t &status, std::error_code &ec)
Get the current node status (error state, error counters, TX queue space).
- Parameters:
status – The status structure to fill in.
ec – The error code, set on failure.
- Returns:
True on success, false otherwise.
-
inline bool get_statistics(twai_node_record_t &record, std::error_code &ec)
Get the node statistics (cumulative bus error count).
- Parameters:
record – The statistics structure to fill in.
ec – The error code, set on failure.
- Returns:
True on success, false otherwise.
-
inline bool get_info(twai_node_status_t &status, twai_node_record_t &record, std::error_code &ec)
Get both the node status and statistics.
- Parameters:
status – The status structure to fill in.
record – The statistics structure to fill in.
ec – The error code, set on failure.
- Returns:
True on success, false otherwise.
-
inline bool is_enabled() const
Whether the node is currently enabled (started).
- Returns:
True if the node is enabled, false otherwise.
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
Public Static Attributes
-
static constexpr size_t MAX_DATA_LEN = 8
Maximum number of data bytes in a classic CAN 2.0 frame.
-
static constexpr int DEFAULT_TX_TIMEOUT_MS = 100
Default timeout (ms) used when transmitting a frame.
-
struct Config
Configuration for the TWAI node.
Public Members
-
int tx_gpio = {-1}
GPIO number for TWAI TX. Must be set (validated).
-
int rx_gpio = {-1}
GPIO number for TWAI RX. Must be set (validated).
-
uint32_t baudrate = {500000}
Bus baud rate / bit rate in bits/second (e.g. 500000).
-
size_t tx_queue_depth = {5}
Depth of the hardware transmit queue.
-
receive_callback_fn on_receive = {nullptr}
Called (in task context) for each received frame.
-
error_callback_fn on_error = {nullptr}
Optional: called (in task context) on a bus error.
-
state_change_callback_fn on_state_change{nullptr}
Optional: called (in task context) on an error-state change.
-
bool auto_start = {true}
If true, the node is enabled at the end of initialize().
-
size_t rx_queue_size = {16}
Size (number of events) of the internal ISR->task event queue.
- Task::BaseConfig task_config {.name = "Twai Task",.stack_size_bytes = 4096,.priority = 10,}
Configuration for the internal receive task.
-
int tx_gpio = {-1}
-
struct Filter
An acceptance (hardware) filter for received frames.
A frame is accepted if
(received_id & mask) == (id & mask). A mask bit of 1 means the corresponding ID bit must match; a mask bit of 0 means “don’t care”. Thereforeid= 0,mask= 0 accepts all frames.
-
struct Message
A classic CAN 2.0 message / frame.
Public Functions
-
inline twai_frame_t to_twai_frame() const
Convert this Message into a driver
twai_frame_tfor transmission.Note
The returned frame’s
bufferpoints into this Message’sdataarray, so the Message must outlive the returned frame (which it does for the duration of a synchronous transmit call).- Returns:
A
twai_frame_tdescribing this message.
Public Members
-
uint32_t id = {0}
Arbitration ID (11-bit standard, or 29-bit if
extendedis true).
-
bool extended = {false}
True for an extended (29-bit) ID, false for standard (11-bit) ID.
-
bool rtr = {false}
True if this is a Remote Transmission Request frame (no data).
-
uint8_t dlc = {0}
Data Length Code / number of valid data bytes (0-8).
-
std::array<uint8_t, MAX_DATA_LEN> data = {}
Frame payload (only the first
dlcbytes valid).
-
inline twai_frame_t to_twai_frame() const
-
struct StateChange
Data passed to the state-change callback.
-
enum class Mode