Chapter 151:
TWAI (Two-Wire Automotive Interface) Introduction
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the fundamental principles of the CAN (Controller Area Network) bus.
- Explain the role and significance of TWAI (Two-Wire Automotive Interface) on ESP32 devices.
- Describe the physical layer characteristics of a CAN bus.
- Identify the basic structure of a CAN message frame.
- Explain the concept of message arbitration in CAN.
- Recognize the importance of error detection and handling in CAN.
- Perform a basic TWAI peripheral initialization and self-test on an ESP32.
1. Introduction
Welcome to the world of in-vehicle and industrial networking! The Controller Area Network (CAN) bus is a robust and reliable serial communication protocol designed to allow microcontrollers and devices to communicate with each other’s applications without a host computer. Originally developed for automotive applications to reduce wiring harnesses, its resilience and efficiency have led to its widespread adoption in various other fields, including industrial automation, medical equipment, and maritime electronics.
On Espressif’s ESP32 series of microcontrollers, the CAN protocol is implemented through a peripheral known as TWAI (Two-Wire Automotive Interface). The TWAI module is compatible with the CAN 2.0B specification, supporting both standard (11-bit) and extended (29-bit) identifiers. This chapter will introduce you to the core concepts of CAN communication and how the TWAI peripheral on the ESP32 facilitates this. Understanding these fundamentals is crucial before diving into configuring the TWAI driver and developing complex CAN-based applications in subsequent chapters.
Think of a car’s electronic systems: the engine control unit (ECU), anti-lock braking system (ABS), airbags, and even the power windows need to exchange information. Instead of having a dedicated wire for every signal between every component (which would be a nightmare of complexity and weight!), CAN bus provides a shared communication pathway. Multiple devices, called nodes, connect to this common bus and broadcast messages. Each message has an identifier that determines its priority and content type, rather than a specific destination address. All nodes receive all messages, but they only process the ones relevant to them based on these identifiers.
This chapter will serve as your entry point into this fascinating and widely used communication protocol, specifically within the ESP32 ecosystem.
2. Theory
2.1. What is CAN Bus?
The Controller Area Network (CAN) is a message-based protocol, meaning that messages are not sent from one specific node to another based on addresses, but rather they are broadcast onto the bus with a specific Message Identifier (ID). Nodes on the bus can then choose to “listen” for messages with specific IDs that are relevant to their function.
Key characteristics of CAN include:
Characteristic | Description |
---|---|
Multi-master | Any node on the bus can initiate communication (transmit a message) when the bus is free. There is no central bus controller. |
Message Prioritization | Message Identifiers (IDs) determine the priority of the message. Lower numerical ID values have higher priority on the bus. |
Error Detection and Fault Confinement | Robust mechanisms are built into the protocol to detect errors in transmission (e.g., CRC, bit monitoring, stuffing errors) and to prevent a faulty node from continuously disrupting the bus (fault confinement states: error active, error passive, bus-off). |
Differential Signaling | Uses two wires for communication (CAN_H and CAN_L), providing high noise immunity. Logic states are represented by the voltage difference between these two lines. |
Fixed Message Length (Classical CAN) | Data payloads are relatively small, up to 8 bytes for Classical CAN (CAN 2.0A/B). This makes it suitable for control commands and sensor data rather than large file transfers. |
Broadcast Communication | Messages are broadcast onto the bus with a specific Message ID. Nodes choose to listen for messages with IDs relevant to their function, rather than messages being addressed to a specific node. |
Non-Destructive Arbitration | If multiple nodes transmit simultaneously, a bitwise arbitration process based on the message ID occurs. The message with the highest priority (lowest ID) wins arbitration without being corrupted. |
2.2. Physical Layer
The CAN physical layer typically uses two wires: CAN High (CAN_H) and CAN Low (CAN_L). It employs differential signaling:
- Recessive State (Logic ‘1’): When the bus is idle or transmitting a recessive bit, both CAN_H and CAN_L are typically at around 2.5V (relative to ground). The differential voltage between them is close to 0V.
- Dominant State (Logic ‘0’): When a node transmits a dominant bit, it drives CAN_H towards a higher voltage (e.g., 3.5V) and CAN_L towards a lower voltage (e.g., 1.5V). This creates a significant differential voltage (e.g., 2V).
The dominant state (0) will always overwrite a recessive state (1). This is fundamental to the arbitration process.
Termination: A CAN bus is a transmission line and requires termination resistors at both ends of the bus to prevent signal reflections. Typically, a 120 Ohm resistor is placed at each end of the main bus trunk.
2.3. CAN Message Frames
There are several types of frames in CAN, but the most common is the Data Frame, used to transmit data. Other types include Remote Frames (to request data), Error Frames (signaling an error), and Overload Frames (indicating a node is busy). We will focus on the Data Frame.
A Standard CAN Data Frame (CAN 2.0A, 11-bit ID) consists of the following fields:
- Start of Frame (SOF): A single dominant bit (0) that marks the beginning of a message. All nodes synchronize to this.
- Arbitration Field:
- Identifier (ID): 11 bits for Standard CAN. This determines the message’s priority. Lower values mean higher priority.
- RTR (Remote Transmission Request): 1 bit. Dominant (0) for Data Frame, Recessive (1) for Remote Frame.
- Control Field:
- IDE (Identifier Extension): 1 bit. Dominant (0) for Standard CAN (11-bit ID). Recessive (1) if it’s an Extended CAN frame (29-bit ID).
- r0 (Reserved bit): 1 bit, must be dominant (0).
- DLC (Data Length Code): 4 bits. Indicates the number of data bytes in the Data Field (0 to 8 bytes).
- Data Field: 0 to 8 bytes of actual data being transmitted. The length is specified by the DLC.
- CRC Field (Cyclic Redundancy Check): 15 bits for error detection, followed by a 1-bit CRC delimiter (recessive).
- ACK Field (Acknowledge): 2 bits.
- ACK Slot: Transmitted as recessive by the sender. Any receiving node that correctly receives the message overwrites this slot with a dominant bit.
- ACK Delimiter: 1 bit, must be recessive.
- End of Frame (EOF): 7 recessive bits.
- Interframe Space (IFS): Minimum of 3 recessive bits separating consecutive frames.
Extended CAN Frames (CAN 2.0B): These use a 29-bit identifier. The Arbitration Field is modified:
- The first 11 bits are the Base ID.
- The RTR bit is followed by the SRR (Substitute Remote Request) bit, which is recessive.
- The IDE bit is recessive, indicating an extended frame.
- An additional 18 bits for the Extended ID follow.
- The RTR bit in the extended format is after the full 29-bit identifier.
Standard vs Extended CAN Frame Comparison:
Feature / Field | Standard CAN (CAN 2.0A) | Extended CAN (CAN 2.0B) | Notes |
---|---|---|---|
Identifier Length | 11 bits | 29 bits (11-bit Base ID + 18-bit Extended ID) | Primary difference affecting message prioritization and capacity. |
SOF (Start of Frame) | 1 dominant bit | 1 dominant bit | Identical. |
Arbitration Field Structure | 11-bit ID + 1-bit RTR | 11-bit Base ID + 1-bit SRR + 1-bit IDE (recessive) + 18-bit Extended ID + 1-bit RTR | SRR (Substitute Remote Request) is recessive. IDE bit is recessive (1) to signify an extended frame. RTR is positioned after the full 29-bit ID. |
IDE (Identifier Extension bit) | Located in Control Field, dominant (0) | Located in Arbitration Field (after SRR), recessive (1) | Crucial for differentiating frame types. |
Control Field | IDE (0), r0 (reserved, 0), DLC (4 bits) – Total 6 bits | r1 (reserved, 0), r0 (reserved, 0), DLC (4 bits) – Total 6 bits | The IDE bit from standard frame is effectively moved and repurposed in the extended frame’s arbitration field. Some diagrams may vary in naming reserved bits. |
RTR (Remote Transmission Request bit) | Part of Arbitration Field (after 11-bit ID) | Part of Arbitration Field (after 29-bit ID) | Dominant (0) for Data Frame, Recessive (1) for Remote Frame. |
Data Field | 0-8 bytes (specified by DLC) | 0-8 bytes (specified by DLC) | Identical. |
CRC Field | 15-bit CRC + 1-bit CRC Delimiter | 15-bit CRC + 1-bit CRC Delimiter | Identical. |
ACK Field | 1-bit ACK Slot + 1-bit ACK Delimiter | 1-bit ACK Slot + 1-bit ACK Delimiter | Identical. |
EOF (End of Frame) | 7 recessive bits | 7 recessive bits | Identical. |
Interframe Space (IFS) | ≥3 recessive bits | ≥3 recessive bits | Identical. |
2.4. Message Arbitration (CSMA/CD+AMP / CSMA/NBA)
CAN uses a Carrier Sense Multiple Access/Collision Detection with Arbitration on Message Priority (CSMA/CD+AMP), also known as CSMA with Non-Destructive Bitwise Arbitration (CSMA/NBA).
- Carrier Sense (CS): Before transmitting, a node checks if the bus is idle (recessive state for a specific duration).
- Multiple Access (MA): Multiple nodes can attempt to transmit simultaneously if they sense the bus is idle.
- Non-Destructive Bitwise Arbitration (NBA): This is the clever part. If multiple nodes start transmitting at the same time, they monitor the bus while sending their message ID bit by bit, starting from the most significant bit.
- If a node transmits a recessive bit (1) but sees a dominant bit (0) on the bus, it means another node with a higher priority (lower ID value) is also transmitting.
- That node immediately stops transmitting (loses arbitration) and becomes a receiver for the winning message.
- The node transmitting the dominant bit continues, as its ‘0’ overwrites the ‘1’.
- This process continues for the entire ID field. The node with the numerically lowest ID will win arbitration and transmit its message without corruption or delay. The “collision” is resolved gracefully.
graph TD %% Mermaid Diagram for CAN Message Arbitration %% Styles for nodes based on provided color scheme classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef endSuccess fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef endFail fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; A0("<b>Start: Bus Idle</b><br>Multiple nodes may attempt to transmit"):::primary A1("Nodes Sense Bus Idle (CS)"):::process A0 --> A1 A2("Node A (ID: 0x123 = <b>00100100011</b>...)<br>Node B (ID: 0x12A = <b>00100101010</b>...)<br>Both start transmitting SOF, then ID bit by bit"):::process A1 --> A2 subgraph "Arbitration - Bit by Bit Comparison (MSB first)" direction LR B1("Bit 1 (MSB):<br>Node A sends <b>0</b> (dominant)<br>Node B sends <b>0</b> (dominant)<br>Bus sees <b>0</b>. Both continue."):::process B2("Bit 2:<br>Node A sends <b>0</b> (dominant)<br>Node B sends <b>0</b> (dominant)<br>Bus sees <b>0</b>. Both continue."):::process B3("Bit 3:<br>Node A sends <b>1</b> (recessive)<br>Node B sends <b>1</b> (recessive)<br>Bus sees <b>1</b>. Both continue."):::process B4("Bit 4:<br>Node A sends <b>0</b> (dominant)<br>Node B sends <b>0</b> (dominant)<br>Bus sees <b>0</b>. Both continue."):::process B5("Bit 5:<br>Node A sends <b>0</b> (dominant)<br>Node B sends <b>0</b> (dominant)<br>Bus sees <b>0</b>. Both continue."):::process B6("Bit 6:<br>Node A sends <b>1</b> (recessive)<br>Node B sends <b>1</b> (recessive)<br>Bus sees <b>1</b>. Both continue."):::process B7("Bit 7 (<b>CRITICAL POINT</b>):<br>Node A sends <b>0</b> (dominant)<br>Node B sends <b>1</b> (recessive)<br>Bus sees <b>0</b> (Node A's dominant bit overwrites Node B's recessive bit)"):::decision B1 --> B2 --> B3 --> B4 --> B5 --> B6 --> B7 end A2 --> B1 C1{"Node B Transmitted Recessive (1),<br>but Sees Dominant (0) on Bus?"}:::decision B7 --> C1 C1 --"Yes (Mismatch)"--> D1("Node B Loses Arbitration!<br>Stops transmitting ID.<br>Switches to receiver mode for Node A's message."):::endFail C1 --"No (Node A: Transmitted 0, Saw 0)"--> D2("Node A Wins Arbitration!<br>Continues transmitting its ID and the rest of the message.<br>Its dominant bit matched the bus state."):::endSuccess D2 --> E1("Node A completes transmission of its message (ID 0x123)."):::primary D1 --> E2("Node B waits for bus to be idle again before re-attempting transmission of its message (ID 0x12A)."):::process E1 --> F1("<b>End: Message with Higher Priority (lower ID) Transmitted</b>"):::endSuccess %% Styling for path clarity linkStyle default interpolate basis
2.5. Bit Stuffing
To ensure enough edges for synchronization, CAN uses bit stuffing. If five consecutive bits of the same polarity (all 0s or all 1s) occur in the SOF, Arbitration, Control, Data, or CRC fields, the transmitter automatically inserts a complementary bit into the bitstream. The receiver automatically removes these stuffed bits. Error frames and EOF are not stuffed.
2.6. Error Detection and Handling
CAN has several mechanisms for error detection:
- Bit Monitoring: Transmitters monitor the bus and compare the transmitted bit with the bit observed on the bus. A mismatch (outside the arbitration field or ACK slot) indicates a Bit Error.
- Stuff Error: If more than 5 consecutive bits of the same polarity are detected (where stuffing should have occurred), a Stuff Error is flagged.
- CRC Error: Receivers calculate the CRC of the received message and compare it with the transmitted CRC. A mismatch indicates a CRC Error.
- Form Error: If certain fixed-format bit fields (like CRC delimiter, ACK delimiter, EOF) contain illegal values (dominant bits where recessive is expected), a Form Error is signaled.
- Acknowledgement Error (ACK Error): If the transmitter does not detect a dominant bit in the ACK slot, an ACK Error is flagged (meaning no node acknowledged the message).
When a node detects an error, it transmits an Error Frame. An Error Frame consists of 6 to 12 dominant bits (Error Flag) followed by 8 recessive bits (Error Delimiter). This violates the bit stuffing rule and is immediately recognized by all other nodes, which then also may send Error Frames. The original message is discarded, and the transmitter usually attempts to retransmit.
CAN controllers also feature fault confinement. They maintain error counters (Transmit Error Counter – TEC, Receive Error Counter – REC). Based on the values of these counters, a node can transition through different states:
- Error Active: Normal operation, participates in communication, sends Active Error Flags (dominant) upon error detection.
- Error Passive: Node has detected many errors. It still participates but sends Passive Error Flags (recessive) which don’t disrupt other nodes as much. It also has to wait longer before retransmitting.
- Bus-Off: Node has detected excessive errors (TEC > 255). It is no longer allowed to influence the bus (transmit messages or error flags) until it undergoes a recovery procedure.
This robust error handling makes CAN very reliable.
2.7. Bit Timing and Synchronization (Brief Overview)
The bit rate on a CAN bus can range from a few kbit/s up to 1 Mbit/s (for Classical CAN). All nodes on the bus must operate at the same nominal bit rate.
Each bit is divided into several time segments called “quanta”:
- Synchronization Segment (Sync_Seg): Used to synchronize with the bus. An edge is expected here.
- Propagation Time Segment (Prop_Seg): Compensates for physical signal delays in the network.
- Phase Buffer Segment 1 (Phase_Seg1): Used to compensate for phase errors. Can be lengthened during resynchronization.
- Phase Buffer Segment 2 (Phase_Seg2): Also used to compensate for phase errors. Can be shortened during resynchronization.The Sample Point is the point in time where the bus level is read to determine if it’s dominant or recessive. It is usually at the end of Phase_Seg1.The Synchronization Jump Width (SJW) defines the maximum amount Phase_Seg1 can be lengthened or Phase_Seg2 shortened for resynchronization.
These timing parameters (Prop_Seg
, Phase_Seg1
, Phase_Seg2
, SJW
) are configured in the CAN controller (TWAI peripheral on ESP32) to achieve the desired bit rate and to ensure reliable communication across the network. Detailed configuration will be covered in the next chapter.
3. Practical Example: Basic TWAI Initialization and Self-Test (Loopback)
This example demonstrates how to initialize the TWAI driver on an ESP32, configure it for a self-test (loopback) mode, transmit a message, and then receive it. Loopback mode allows testing the TWAI peripheral without needing an external CAN bus or another CAN node. The transmitted message is internally routed back to the receiver of the same controller.
graph TD %% Mermaid Flowchart for TWAI Initialization and Self-Test %% Styles classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef io fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46; classDef error fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; A[Start TWAI Loopback Example]:::startEnd A --> B["1- Configure General Settings\ntwai_general_config_t\nSet GPIOs, Mode (SELF_TEST)"]:::process B --> C["2- Configure Timing Settings\ntwai_timing_config_t\ne.g., TWAI_TIMING_CONFIG_125KBITS()"]:::process C --> D["3- Configure Filter Settings\ntwai_filter_config_t\ne.g., TWAI_FILTER_CONFIG_ACCEPT_ALL()"]:::process D --> E["4- Install TWAI Driver\ntwai_driver_install()"]:::process E --> F{Driver Installed Successfully?}:::decision F -- Yes --> G["5- Start TWAI Driver\ntwai_start()"]:::process F -- No --> F_Error[Log Error & Exit]:::error G --> H{Driver Started Successfully?}:::decision H -- Yes --> I["6- Prepare TX Message\ntwai_message_t\nSet ID, DLC, Data"]:::process H -- No --> H_Error[Log Error, Uninstall Driver and Exit]:::error I --> J["7- Transmit Message\ntwai_transmit()"]:::io J --> K{Transmit Successful?}:::decision K -- Yes --> L["8- Attempt to Receive Message\ntwai_receive()"]:::io K -- No --> K_Error[Log TX Error]:::error K_Error --> L L --> M{Receive Successful?}:::decision M -- Yes --> N[Log Received Message\nVerify against TX message]:::io N --> N_Verify{Messages Match?}:::decision N_Verify -- Yes --> N_Success[Log Loopback Success!]:::io N_Verify -- No --> N_Mismatch[Log Loopback Mismatch]:::error N_Success --> P N_Mismatch --> P M -- Timeout --> M_Timeout[Log RX Timeout]:::error M -- Other Error --> M_Error[Log RX Error]:::error M_Timeout --> P M_Error --> P K_Error --> P P["9- Stop TWAI Driver\ntwai_stop()"]:::process P --> Q{Stop Successful?}:::decision Q -- Yes --> R["10- Uninstall TWAI Driver\ntwai_driver_uninstall()"]:::process Q -- No --> Q_Error[Log Stop Error]:::error Q_Error --> R R --> S{Uninstall Successful?}:::decision S -- Yes --> T[End TWAI Loopback Example]:::startEnd S -- No --> S_Error[Log Uninstall Error]:::error S_Error --> T F_Error --> StopPoint[Stop Program]:::startEnd H_Error --> StopPoint
Prerequisites:
- ESP-IDF v5.x installed and configured with VS Code.
- An ESP32 development board (any variant with a TWAI controller).
- Basic knowledge of creating, building, and flashing ESP-IDF projects.
Code Snippet:
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/twai.h"
#include "esp_log.h"
static const char *TAG = "TWAI_LOOPBACK_EXAMPLE";
// Define GPIO pins for TWAI.
// For self-test/loopback mode, these pins are configured but not strictly connected to an external bus.
// However, they should not conflict with other used pins.
// Check your ESP32 variant's datasheet for default/recommended TWAI pins if you plan to use an external bus later.
#define TWAI_TX_GPIO_NUM CONFIG_EXAMPLE_TWAI_TX_GPIO // Set via menuconfig, e.g., GPIO21
#define TWAI_RX_GPIO_NUM CONFIG_EXAMPLE_TWAI_RX_GPIO // Set via menuconfig, e.g., GPIO22
// Example Kconfig options to add to your project's Kconfig file or sdkconfig.defaults:
// CONFIG_EXAMPLE_TWAI_TX_GPIO=21
// CONFIG_EXAMPLE_TWAI_RX_GPIO=22
void app_main(void)
{
ESP_LOGI(TAG, "Starting TWAI Loopback Example...");
// 1. Configure TWAI general settings
// Set mode to NO_ACK. This is recommended for self-test mode.
// In NO_ACK mode, the TWAI controller does not expect an acknowledgment signal for transmitted messages.
// For loopback, the controller itself will "ack" its own message internally if not in NO_ACK mode,
// but NO_ACK simplifies the self-test scenario.
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TWAI_TX_GPIO_NUM, TWAI_RX_GPIO_NUM, TWAI_MODE_NO_ACK);
// Modify operating mode for self-test (loopback)
// Other modes include: TWAI_MODE_NORMAL, TWAI_MODE_LISTEN_ONLY
g_config.mode = TWAI_MODE_NO_ACK; // Using NO_ACK for simplicity in loopback
// For a true internal loopback where TX is internally connected to RX,
// some controllers might require TWAI_MODE_NORMAL and will self-acknowledge.
// However, ESP-IDF examples often use TWAI_MODE_SELF_TEST or TWAI_MODE_NO_ACK for loopback.
// Let's use TWAI_MODE_SELF_TEST which is explicitly for loopback.
g_config.mode = TWAI_MODE_SELF_TEST;
// 2. Configure TWAI timing.
// Common baud rates: 125kbps, 250kbps, 500kbps, 1Mbps.
// For this example, let's use 125kbps.
// Use `TWAI_TIMING_CONFIG_125KBITS()` or similar predefined macros.
// These macros configure brp, tseg1, tseg2, sjw for common baud rates assuming a specific clock source (usually APB clock).
// For ESP32, APB_CLK is typically 80MHz.
// TWAI_TIMING_CONFIG_125KBITS() -> {.brp = 64, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
// Calculation: Bit time = (brp / APB_CLK) * (1 + tseg_1 + tseg_2)
// Nominal Bit Rate = 1 / Bit time
// For 125kbps with APB @ 80MHz:
// (1 + TSEG1 + TSEG2) = APB_CLK / (BRP * BAUD_RATE)
// (1 + 15 + 4) = 20 quanta.
// Bit Rate = (80 MHz / 64) / 20 = 1.25 MHz / 20 = 62.5 kHz * 2 = 125 kbps. Wait, this math is off.
// Correct formula for bit time quanta: (1 (Sync_Seg) + TSEG1 + TSEG2)
// Baud rate = Clock Speed / (BRP * (1 + TSEG1 + TSEG2))
// 125000 = 80000000 / (BRP * (1 + TSEG1 + TSEG2))
// If BRP = 32, (1+TSEG1+TSEG2) = 80000000 / (32 * 125000) = 80000000 / 4000000 = 20.
// So, if BRP=32, TSEG1+TSEG2 = 19. Example: TSEG1=15, TSEG2=4. SJW=3.
// The predefined macros are generally reliable.
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_125KBITS();
// For ESP32-C3/S3/C6/H2, the clock source might differ or have different prescaler behavior.
// The predefined macros are generally updated for these targets.
// Check `soc/soc_caps.h` for `TWAI_BRP_MAX` and clock details if customizing.
// For ESP32, APB_CLK_FREQ is 80MHz.
// For ESP32-S2, APB_CLK_FREQ is 80MHz.
// For ESP32-S3, APB_CLK_FREQ is 80MHz.
// For ESP32-C3, APB_CLK_FREQ can be 80MHz or XTAL_CLK. Default is usually APB.
// For ESP32-C6, APB_CLK_FREQ is 80MHz.
// For ESP32-H2, APB_CLK_FREQ is 32MHz. The predefined timing macros account for this.
// Example: TWAI_TIMING_CONFIG_125KBITS() for ESP32-H2 might use BRP = 16 if APB is 32MHz.
// (1 + 15 + 4) = 20. Baud rate = (32MHz / 16) / 20 = 2MHz / 20 = 100kbps. This is not 125kbps.
// Let's re-check ESP-IDF source for TWAI_TIMING_CONFIG_125KBITS for ESP32-H2.
// esp-idf/components/driver/twai/twai.c
// For ESP32H2_ANA_CLK_XTAL_D2 (XTAL/2, e.g. 32MHz/2 = 16MHz)
// #define TWAI_TIMING_CONFIG_125KBITS() {.clk_src = TWAI_CLK_SRC_XTAL_D2, .brp = 8, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false}
// Baud rate = (16MHz / 8) / (1+15+4) = 2MHz / 20 = 100kbps. Still not 125kbps.
// This indicates that for some chips, you might need to use `TWAI_CLK_SRC_APB` or custom calculate.
// For simplicity and broad compatibility for a first example, let's assume APB clock is the default and works for most.
// If issues arise, specific clock configuration might be needed per variant.
// The example will use the default clock source implied by the macro.
// For ESP32, S2, S3, C3, C6, APB (80MHz) is a safe bet for the default source for these macros.
// For ESP32-H2, it's more complex; the macro might select XTAL_D2. If you need 125kbps precisely on H2, custom timing or ensuring APB is used and available at a suitable frequency is needed.
// Let's stick to the macro for now, as it's intended for ease of use.
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// ESP-IDF 5.x introduced clock source selection in timing config
#if CONFIG_IDF_TARGET_ESP32H2
// For ESP32-H2, APB clock is 32MHz.
// To get 125kbps: 32MHz / (BRP * Quanta) = 125kHz
// BRP * Quanta = 32000 / 125 = 256
// If Quanta = 16 (e.g., 1+10+5), BRP = 256 / 16 = 16
// So, for H2:
t_config.clk_src = TWAI_CLK_SRC_DEFAULT; // Or specifically TWAI_CLK_SRC_APB if available and configured.
// TWAI_CLK_SRC_DEFAULT should pick an appropriate source.
// Let's use the macro and trust it.
#else // For other common targets like ESP32, ESP32-S3, ESP32-C3, ESP32-C6 (APB typically 80MHz)
// The default clock source for these is typically APB @ 80MHz.
// The TWAI_TIMING_CONFIG_xKBITS() macros are usually tuned for this.
// No explicit clk_src needed if TWAI_CLK_SRC_DEFAULT (which is APB for these) is fine.
#endif
#endif
// 3. Configure TWAI filter.
// For loopback/self-test, we can accept all messages.
// `TWAI_FILTER_CONFIG_ACCEPT_ALL()`
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// 4. Install TWAI driver
ESP_LOGI(TAG, "Installing TWAI driver...");
if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
ESP_LOGI(TAG, "Driver installed");
} else {
ESP_LOGE(TAG, "Failed to install driver");
return;
}
// 5. Start TWAI driver
ESP_LOGI(TAG, "Starting TWAI driver...");
if (twai_start() == ESP_OK) {
ESP_LOGI(TAG, "Driver started");
} else {
ESP_LOGE(TAG, "Failed to start driver");
twai_driver_uninstall(); // Clean up
return;
}
// 6. Prepare a message to transmit
twai_message_t tx_message;
tx_message.identifier = 0x123; // 11-bit ID
tx_message.extd = 0; // Standard frame (not extended)
tx_message.rtr = 0; // Data frame (not remote frame)
tx_message.data_length_code = 4; // Send 4 bytes of data
tx_message.data[0] = 'P';
tx_message.data[1] = 'I';
tx_message.data[2] = 'N';
tx_message.data[3] = 'G';
// Other data bytes will be ignored due to DLC=4
ESP_LOGI(TAG, "Prepared message: ID=0x%03lX, DLC=%d, Data=[%c%c%c%c]",
tx_message.identifier, tx_message.data_length_code,
tx_message.data[0], tx_message.data[1], tx_message.data[2], tx_message.data[3]);
// 7. Transmit the message
ESP_LOGI(TAG, "Transmitting message...");
esp_err_t tx_result = twai_transmit(&tx_message, pdMS_TO_TICKS(1000)); // Wait up to 1 second for TX queue space
if (tx_result == ESP_OK) {
ESP_LOGI(TAG, "Message transmitted successfully!");
} else {
ESP_LOGE(TAG, "Failed to transmit message, error: %s", esp_err_to_name(tx_result));
// Common errors: ESP_ERR_TIMEOUT if TX queue is full or bus issues (less likely in self-test)
// ESP_FAIL if driver not started or invalid args.
}
// 8. Receive the message (should be the one we just sent due to loopback)
twai_message_t rx_message;
ESP_LOGI(TAG, "Attempting to receive message...");
// Wait up to 1 second to receive a message
esp_err_t rx_result = twai_receive(&rx_message, pdMS_TO_TICKS(1000));
if (rx_result == ESP_OK) {
ESP_LOGI(TAG, "Message received successfully!");
ESP_LOGI(TAG, "Received ID: 0x%03lX, Extd: %d, RTR: %d, DLC: %d",
rx_message.identifier, rx_message.extd, rx_message.rtr, rx_message.data_length_code);
// Print data if DLC > 0
if (rx_message.data_length_code > 0) {
printf("Received Data: ");
for (int i = 0; i < rx_message.data_length_code; i++) {
printf("0x%02X (%c) ", rx_message.data[i], rx_message.data[i]);
}
printf("\n");
}
// Verify if it's the same message
if (rx_message.identifier == tx_message.identifier &&
rx_message.data_length_code == tx_message.data_length_code &&
rx_message.data[0] == tx_message.data[0] &&
rx_message.data[1] == tx_message.data[1] &&
rx_message.data[2] == tx_message.data[2] &&
rx_message.data[3] == tx_message.data[3]) {
ESP_LOGI(TAG, "Loopback successful: Transmitted and received messages match.");
} else {
ESP_LOGW(TAG, "Loopback mismatch or unexpected message received.");
}
} else if (rx_result == ESP_ERR_TIMEOUT) {
ESP_LOGW(TAG, "Timeout waiting for message reception. Loopback might have failed or timing issue.");
} else {
ESP_LOGE(TAG, "Failed to receive message, error: %s", esp_err_to_name(rx_result));
}
// 9. Stop TWAI driver
ESP_LOGI(TAG, "Stopping TWAI driver...");
if (twai_stop() == ESP_OK) {
ESP_LOGI(TAG, "Driver stopped");
} else {
ESP_LOGE(TAG, "Failed to stop driver");
}
// 10. Uninstall TWAI driver
ESP_LOGI(TAG, "Uninstalling TWAI driver...");
if (twai_driver_uninstall() == ESP_OK) {
ESP_LOGI(TAG, "Driver uninstalled");
} else {
ESP_LOGE(TAG, "Failed to uninstall driver");
}
ESP_LOGI(TAG, "TWAI Loopback Example Finished.");
}
KConfig entries:
You’ll need to add configuration options for the GPIO pins. Create or modify Kconfig.projbuild
in your project’s main
directory:
menu "TWAI Loopback Example Configuration"
config EXAMPLE_TWAI_TX_GPIO
int "TWAI TX GPIO Number"
default 21
help
GPIO pin number for TWAI transmit (TX).
Refer to your ESP32 variant's datasheet for suitable pins.
config EXAMPLE_TWAI_RX_GPIO
int "TWAI RX GPIO Number"
default 22
help
GPIO pin number for TWAI receive (RX).
Refer to your ESP32 variant's datasheet for suitable pins.
endmenu
Ensure these defaults (GPIO 21 for TX, GPIO 22 for RX) are suitable for your specific ESP32 board. For some boards, these might be used by other peripherals (like I2C for a display). Always check your board’s schematic. For example, on an ESP32-WROVER-KIT, GPIO21/22 are typically used for I2C. You might use GPIO4 (TX) and GPIO5 (RX) if they are free.
Build Instructions:
- Save the C code as
twai_loopback_main.c
(or similar) in your project’smain
directory. - Ensure your
main/CMakeLists.txt
includes this source file:idf_component_register(SRCS "twai_loopback_main.c" INCLUDE_DIRS ".")
- If you added KConfig entries, ensure they are processed. Usually, no extra step is needed if
Kconfig.projbuild
is in themain
directory. - Open the ESP-IDF Terminal in VS Code.
- Run
idf.py menuconfig
(or use the VS Code extension’s menuconfig GUI).- Navigate to “TWAI Loopback Example Configuration”.
- Verify or set the correct GPIO pins for TWAI TX and RX for your board.
- Save and exit menuconfig.
- Build the project:
idf.py build
.
Run/Flash/Observe Steps:
- Connect your ESP32 board to your computer.
- Flash the project:
idf.py -p (PORT) flash
(replace(PORT)
with your ESP32’s serial port, e.g.,/dev/ttyUSB0
orCOM3
). You can also use the flash button in the VS Code extension. - Monitor the output:
idf.py -p (PORT) monitor
(or use the monitor button in VS Code). - Observe: You should see log messages indicating:
- Driver installation and start.
- Message preparation and transmission.
- Message reception.
- Verification of the received message against the transmitted one.
- Driver stop and uninstall.
- A successful run will show “Loopback successful: Transmitted and received messages match.”
Expected Output Snippet:
...
I (XXX) TWAI_LOOPBACK_EXAMPLE: Starting TWAI Loopback Example...
I (XXX) TWAI_LOOPBACK_EXAMPLE: Installing TWAI driver...
I (XXX) TWAI: Driver installed
I (XXX) TWAI_LOOPBACK_EXAMPLE: Starting TWAI driver...
I (XXX) TWAI: Driver started
I (XXX) TWAI_LOOPBACK_EXAMPLE: Prepared message: ID=0x123, DLC=4, Data=[PING]
I (XXX) TWAI_LOOPBACK_EXAMPLE: Transmitting message...
I (XXX) TWAI: Transmitted message ID: 0x123
I (XXX) TWAI_LOOPBACK_EXAMPLE: Message transmitted successfully!
I (XXX) TWAI_LOOPBACK_EXAMPLE: Attempting to receive message...
I (XXX) TWAI: Received message ID: 0x123 DLC: 4
I (XXX) TWAI_LOOPBACK_EXAMPLE: Message received successfully!
I (XXX) TWAI_LOOPBACK_EXAMPLE: Received ID: 0x123, Extd: 0, RTR: 0, DLC: 4
Received Data: 0x50 (P) 0x49 (I) 0x4E (N) 0x47 (G)
I (XXX) TWAI_LOOPBACK_EXAMPLE: Loopback successful: Transmitted and received messages match.
I (XXX) TWAI_LOOPBACK_EXAMPLE: Stopping TWAI driver...
I (XXX) TWAI: Driver stopped
I (XXX) TWAI_LOOPBACK_EXAMPLE: Uninstalling TWAI driver...
I (XXX) TWAI: Driver uninstalled
I (XXX) TWAI_LOOPBACK_EXAMPLE: TWAI Loopback Example Finished.
...
Tip: In
TWAI_MODE_SELF_TEST
, the TWAI controller internally connects its transmitter to its receiver. This is ideal for testing the peripheral and software logic without external hardware. The actual GPIO pins are still configured and should ideally be left unconnected or in a state that doesn’t interfere, though for self-test, their external state is less critical than inTWAI_MODE_NORMAL
.
4. Variant Notes
The TWAI (CAN) controller is available on a wide range of ESP32 variants:
ESP32 Variant | TWAI/CAN Controller(s) | Typical APB Clock (for Baud Rate) | CAN FD Support | Notes |
---|---|---|---|---|
ESP32 (Original) | 1 | 80 MHz | No | Classic CAN 2.0B supported. |
ESP32-S2 | 1 | 80 MHz | No | Classic CAN 2.0B supported. |
ESP32-S3 | 1 | 80 MHz | Yes | Supports Classic CAN and CAN FD. |
ESP32-C3 | 1 | 80 MHz (typically, check TRM for clock options) | No (check specific revisions, generally no) | Classic CAN 2.0B supported. Peripheral sometimes named “CAN controller”. |
ESP32-C6 | 1 | 80 MHz (typically, check TRM) | Yes | Supports Classic CAN and CAN FD. |
ESP32-H2 | 1 | 32 MHz (APB), may use XTAL/2 (e.g., 16MHz) for TWAI | Yes | Supports Classic CAN and CAN FD. Timing macros account for different clock. |
Key Considerations:
- GPIO Pins: While the TWAI peripheral is present, the specific GPIO pins that can be used for TWAI_TX and TWAI_RX can vary or might have specific recommendations. Always consult the datasheet for your specific ESP32 variant and module. Use the GPIO matrix for flexibility, but be mindful of signal integrity for higher CAN speeds.
- Clock Source for Baud Rate Generation: The accuracy of the CAN baud rate depends on the stability and frequency of the clock source provided to the TWAI peripheral (usually APB clock or a dedicated PLL/XTAL derived clock). The predefined timing macros in
driver/twai.h
are a good starting point. For custom baud rates or on variants with different clocking schemes (like ESP32-H2), you might need to calculate timing parameters (BRP, TSEG1, TSEG2, SJW) manually and potentially specify the clock source. - CAN FD (Flexible Data-Rate): Classical CAN, as discussed in this chapter, supports up to 1 Mbit/s and 8 data bytes. Some newer ESP32 variants might have CAN controllers that also support CAN FD, which allows for higher data rates and larger message payloads. This introductory chapter focuses on Classical CAN. CAN FD capabilities will be discussed in a later chapter (Chapter 157). The ESP32, ESP32-S2, and original ESP32-C3 generally do not support CAN FD. Newer revisions or specific variants like ESP32-S3, ESP32-C6, and ESP32-H2 do have CAN FD support. The TWAI driver in ESP-IDF has distinct APIs for CAN FD if the hardware supports it.
For this introductory chapter, the basic TWAI functionality for Classical CAN is largely consistent across the supported variants. The primary difference to be aware of for basic setup is potential variations in clocking that affect baud rate calculations, which the ESP-IDF macros aim to abstract.
5. Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Incorrect GPIO Pin Assignment | TWAI peripheral fails to initialize; twai_driver_install() returns error; no communication; board may behave erratically if critical pins (e.g., strapping, JTAG, flash SPI) are used. |
|
Missing or Incorrect Bus Termination (for external CAN bus) | Communication is unreliable, intermittent, or fails completely; frequent error frames on the bus; messages corrupted. Loopback/Self-Test mode will still work. |
|
Mismatched Baud Rates or Timing Parameters | No communication between nodes, or only one-way communication; frequent error frames; nodes may go into error passive or bus-off state. |
|
Forgetting to Start the TWAI Driver | Calls to twai_transmit() or twai_receive() fail (e.g., return ESP_ERR_INVALID_STATE ); no messages sent or received. |
|
Incorrect Message ID / Filter Configuration | Node does not receive expected messages, even if they are on the bus; twai_receive() times out. |
|
External Transceiver Issues (when not in self-test mode) | No communication on the external bus; ESP32 logic seems fine in loopback but fails with transceiver; transceiver gets hot. |
|
Loopback Test Fails Unexpectedly | Messages transmitted in TWAI_MODE_SELF_TEST are not received, or received data is corrupted. |
|
Warning: When moving from self-test mode to an actual CAN bus with external transceivers (like a TJA1050 or MCP2551), ensure your ESP32’s TWAI_TX and TWAI_RX pins are connected correctly to the transceiver’s TXD and RXD pins, respectively. The transceiver then connects to the CAN_H and CAN_L bus lines. The ESP32’s GPIOs operate at 3.3V logic levels, while CAN bus signaling is differential and at different voltage levels, handled by the transceiver.
6. Exercises
- Identify CAN Frame Fields:
- Given a hexadecimal representation of a short CAN bus transmission (e.g.,
0x1A 0xBC 0xDE 0xF0 ...
), try to identify where the SOF, Identifier, DLC, Data, and CRC fields might be, assuming it’s a Standard Data Frame. (This is conceptual, as raw bus data also includes stuff bits, error frames, etc.). - What is the purpose of the RTR bit in the arbitration field?
- Given a hexadecimal representation of a short CAN bus transmission (e.g.,
- Explain Arbitration:
- Node A wants to transmit a message with ID
0x345
. - Node B wants to transmit a message with ID
0x310
. - Both nodes start transmitting at the exact same time.
- Explain, bit by bit for the first few differing bits of the ID, which node wins arbitration and why. What happens to the node that loses arbitration?
- Node A wants to transmit a message with ID
- Research CAN Baud Rates and Bit Timing:
- Research three common CAN bus baud rates used in automotive or industrial applications.
- For one of these baud rates (e.g., 500 kbps), and assuming an 80MHz clock for the TWAI peripheral, try to find or calculate a valid set of BRP, TSEG1, and TSEG2 values. What is the significance of the Sample Point, and where should it ideally be located within a bit time? (Hint: CANopen recommendations often suggest around 87.5%).
- Modify the Loopback Example:
- Take the provided loopback example code.
- Modify it to send a message with an Extended Identifier (29-bit ID), for example,
0x1ABCD123
. - Change the data payload to be 8 bytes long and contain your initials followed by a short counter (e.g.,
{'A', 'B', 'C', 0x01, 0x02, 0x03, 0x04, 0x05}
). - Verify that the received message matches the new extended ID and data. Remember to set
tx_message.extd = 1;
.
7. Summary
- TWAI (Two-Wire Automotive Interface) is the ESP32’s peripheral for CAN (Controller Area Network) communication, compatible with CAN 2.0B.
- CAN is a robust, message-based, multi-master serial bus protocol widely used in automotive and industrial applications.
- The Physical Layer uses differential signaling (CAN_H, CAN_L) for noise immunity and requires 120 Ohm termination resistors at bus ends.
- CAN Messages (Frames) are broadcast with an Identifier (ID) that determines priority and content, not a destination address. Standard frames use 11-bit IDs, Extended frames use 29-bit IDs. Data payload is up to 8 bytes.
- Arbitration is non-destructive and bitwise: nodes with numerically lower IDs win bus access without message corruption. Dominant bits (0) override recessive bits (1).
- Error Detection is comprehensive (Bit, Stuff, CRC, Form, ACK errors) and leads to Error Frames. Fault confinement mechanisms (Error Active, Error Passive, Bus-Off states) ensure bus stability.
- Bit Stuffing ensures sufficient signal edges for synchronization.
- ESP-IDF TWAI Driver provides APIs to configure, install, start, stop the driver, and to transmit/receive messages. Functions like
twai_driver_install()
,twai_start()
,twai_transmit()
,twai_receive()
are fundamental. - Self-Test (Loopback) Mode (
TWAI_MODE_SELF_TEST
) is useful for initial development and testing without external hardware. - All ESP32 variants discussed (ESP32, S2, S3, C3, C6, H2) feature a TWAI/CAN controller, though clocking details for baud rate generation might vary slightly.
8. Further Reading
- ESP-IDF TWAI Driver Documentation:
- CAN Specification (Bosch):
- The original CAN specification documents from Bosch provide the deepest insights. Searching for “Bosch CAN Specification 2.0” will yield relevant PDFs.
- CAN Bus Information Sites:
- CiA (CAN in Automation): A leading organization for CAN standards and information. They have many introductory and technical articles.
- Kvaser’s CAN Protocol Tour: Good overview of CAN principles.
- Wikipedia – CAN bus: General information and links.
This chapter has laid the groundwork for understanding TWAI and CAN. The following chapters will build upon this by detailing TWAI configuration, message transmission and reception techniques, filtering, error handling, and more advanced topics.