feat(driver_twai): update error recover example using new driver

Closes https://github.com/espressif/esp-idf/issues/8461
This commit is contained in:
wanckl
2025-07-02 20:24:14 +08:00
parent 6440f9d5d7
commit 959557d00a
15 changed files with 257 additions and 308 deletions

View File

@@ -10,6 +10,6 @@ endif()
idf_component_register( idf_component_register(
SRCS ${srcs} SRCS ${srcs}
PRIV_REQUIRES esp_driver_twai esp_timer esp_driver_uart esp_psram PRIV_REQUIRES esp_driver_twai esp_timer esp_driver_uart esp_psram esp_driver_gpio
WHOLE_ARCHIVE WHOLE_ARCHIVE
) )

View File

@@ -16,6 +16,8 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "esp_twai.h" #include "esp_twai.h"
#include "esp_twai_onchip.h" #include "esp_twai_onchip.h"
#include "soc/twai_periph.h"
#include "esp_private/gpio.h"
#include "driver/uart.h" // for baudrate detection #include "driver/uart.h" // for baudrate detection
#define TEST_TX_GPIO 4 #define TEST_TX_GPIO 4
@@ -485,3 +487,55 @@ TEST_CASE("twai driver cache safe (loopback)", "[twai]")
TEST_ESP_OK(twai_node_delete(node_hdl)); TEST_ESP_OK(twai_node_delete(node_hdl));
} }
#endif //CONFIG_TWAI_ISR_CACHE_SAFE #endif //CONFIG_TWAI_ISR_CACHE_SAFE
TEST_CASE("twai bus off recovery (loopback)", "[twai]")
{
twai_node_handle_t node_hdl;
twai_onchip_node_config_t node_config = {
.io_cfg.tx = TEST_TX_GPIO,
.io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver
.bit_timing.bitrate = 50000, //slow bitrate to ensure soft error trigger
.tx_queue_depth = 1,
.flags.enable_self_test = true,
};
TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl));
TEST_ESP_OK(twai_node_enable(node_hdl));
twai_node_status_t node_status;
twai_frame_t tx_frame = {
.buffer = (uint8_t *)"hello\n",
.buffer_len = 6,
};
// send frames and trigger error, must become bus off before 50 frames
while ((node_status.state != TWAI_ERROR_BUS_OFF) && (tx_frame.header.id < 50)) {
printf("sending frame %ld\n", tx_frame.header.id ++);
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame, 500));
if (tx_frame.header.id > 3) { // trigger error after 3 frames
printf("trigger bit_error now!\n");
esp_rom_delay_us(30 * (1000000 / node_config.bit_timing.bitrate)); // trigger error at 30 bits after frame start
gpio_matrix_output(TEST_TX_GPIO, twai_periph_signals[0].tx_sig, true, false);
esp_rom_delay_us(2 * (1000000 / node_config.bit_timing.bitrate)); // trigger error for 2 bits
gpio_matrix_output(TEST_TX_GPIO, twai_periph_signals[0].tx_sig, false, false);
}
vTaskDelay(pdMS_TO_TICKS(100)); // some time for hardware report errors
twai_node_get_info(node_hdl, &node_status, NULL);
}
// recover node
TEST_ASSERT_EQUAL(TWAI_ERROR_BUS_OFF, node_status.state);
printf("node offline, start recover ...\n");
TEST_ESP_OK(twai_node_recover(node_hdl));
// wait node recovered
while (node_status.state != TWAI_ERROR_ACTIVE) {
printf("waiting ...\n");
vTaskDelay(pdMS_TO_TICKS(1000));
twai_node_get_info(node_hdl, &node_status, NULL);
}
printf("node recovered! continue\n");
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame, 500));
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
}

View File

@@ -203,7 +203,8 @@ static inline uint32_t twai_hal_decode_interrupt(twai_hal_context_t *hal_ctx)
if (interrupts & TWAI_LL_INTR_EI) { if (interrupts & TWAI_LL_INTR_EI) {
if (status & TWAI_LL_STATUS_BS) { //Currently in BUS OFF state if (status & TWAI_LL_STATUS_BS) { //Currently in BUS OFF state
if (status & TWAI_LL_STATUS_ES) { //EWL is exceeded, thus must have entered BUS OFF if (status & TWAI_LL_STATUS_ES) { //EWL is exceeded, thus must have entered BUS OFF
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_OFF); //The last error which trigger bus_off, hardware no longer fire TWAI_HAL_EVENT_BUS_ERR, but error reason still need to be read/clear and report
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_OFF | TWAI_HAL_EVENT_BUS_ERR);
TWAI_HAL_SET_BITS(state_flags, TWAI_HAL_STATE_FLAG_BUS_OFF); TWAI_HAL_SET_BITS(state_flags, TWAI_HAL_STATE_FLAG_BUS_OFF);
//Any TX would have been halted by entering bus off. Reset its flag //Any TX would have been halted by entering bus off. Reset its flag
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_RUNNING | TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED); TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_RUNNING | TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);

View File

@@ -372,6 +372,7 @@ Application Examples
.. list:: .. list::
- :example:`peripherals/twai/twai_utils` demonstrates how to use the TWAI (Two-Wire Automotive Interface) APIs to create a command-line interface for TWAI bus communication, supporting frame transmission/reception, filtering, monitoring, and both classic and FD formats for testing and debugging TWAI networks. - :example:`peripherals/twai/twai_utils` demonstrates how to use the TWAI (Two-Wire Automotive Interface) APIs to create a command-line interface for TWAI bus communication, supporting frame transmission/reception, filtering, monitoring, and both classic and FD formats for testing and debugging TWAI networks.
- :example:`peripherals/twai/twai_error_recovery` demonstrates how to recover nodes from the bus-off state and resume communication, as well as bus error reporting, node state changes, and other event information.
API Reference API Reference
------------- -------------

View File

@@ -372,6 +372,7 @@ TWAI控制器能够检测由于总线干扰产生的/损坏的不符合帧格式
.. list:: .. list::
- :example:`peripherals/twai/twai_utils` 演示了如何使用 TWAITwo-Wire Automotive Interface双线汽车接口API 创建一个命令行工具,用于 TWAI 总线通信,支持帧的发送/接收、过滤、监控,以及经典和 FD 格式,以便测试和调试 TWAI 网络。 - :example:`peripherals/twai/twai_utils` 演示了如何使用 TWAITwo-Wire Automotive Interface双线汽车接口API 创建一个命令行工具,用于 TWAI 总线通信,支持帧的发送/接收、过滤、监控,以及经典和 FD 格式,以便测试和调试 TWAI 网络。
- :example:`peripherals/twai/twai_error_recovery` 演示了总线错误上报,节点状态变化等事件信息,以及如何从离线状态恢复节点并重新进行通信。
API 参考 API 参考
-------- --------

View File

@@ -503,14 +503,11 @@ examples/peripherals/touch_sensor/touch_sens_sleep:
depends_components: depends_components:
- esp_driver_touch_sens - esp_driver_touch_sens
examples/peripherals/twai/twai_alert_and_recovery: examples/peripherals/twai/twai_error_recovery:
disable: disable:
- if: SOC_TWAI_SUPPORTED != 1 or SOC_TWAI_SUPPORT_FD == 1 - if: SOC_TWAI_SUPPORTED != 1
reason: This example not support FD depends_components:
disable_test: - esp_driver_twai
- if: IDF_TARGET not in ["esp32"]
temporary: true
reason: lack of runners
examples/peripherals/twai/twai_network: examples/peripherals/twai/twai_network:
disable: disable:

View File

@@ -1,94 +0,0 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | --------- | -------- | -------- | -------- |
# TWAI Alert and Recovery Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to use the alert and bus recovery features of the TWAI driver. The alert feature allows the TWAI driver to notify the application layer of certain TWAI driver or bus events. The bus recovery feature is used to recover the TWAI driver after it has entered the Bus-Off state. See the TWAI driver reference for more details.
## How to use example
### Hardware Required
This example requires only a single target (e.g., an ESP32 or ESP32-S2). The target must be connected to an external transceiver (e.g., a SN65HVD23X transceiver). This connection usually consists of a TX and an RX signal.
Note: If you don't have an external transceiver, this example can still be run by simply connecting the TX GPIO and RX GPIO with a jumper.
### Configure the project
* Set the target of the build (where `{IDF_TARGET}` stands for the target chip such as `esp32`, `esp32c3`).
* Then run `menuconfig` to configure the example.
```sh
idf.py set-target {IDF_TARGET}
idf.py menuconfig
```
* Under `Example Configuration`, configure the pin assignments using the options `TX GPIO Number` and `RX GPIO Number` according to how the target was connected to the transceiver. By default, `TX GPIO Number` and `RX GPIO Number` are set to the following values:
* On the ESP32, `TX GPIO Number` and `RX GPIO Number` default to `21` and `22` respectively
* On other chips, `TX GPIO Number` and `RX GPIO Number` default to `0` and `2` respectively
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```sh
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (330) TWAI Alert and Recovery: Driver installed
I (340) TWAI Alert and Recovery: Driver started
I (340) TWAI Alert and Recovery: Starting transmissions
W (350) TWAI Alert and Recovery: Trigger TX errors in 3
W (1350) TWAI Alert and Recovery: Trigger TX errors in 2
W (2350) TWAI Alert and Recovery: Trigger TX errors in 1
I (3350) TWAI Alert and Recovery: Trigger errors
I (3650) TWAI Alert and Recovery: Surpassed Error Warning Limit
I (3650) TWAI Alert and Recovery: Entered Error Passive state
I (4300) TWAI Alert and Recovery: Bus Off state
W (4300) TWAI Alert and Recovery: Initiate bus recovery in 3
W (5300) TWAI Alert and Recovery: Initiate bus recovery in 2
W (6300) TWAI Alert and Recovery: Initiate bus recovery in 1
I (7300) TWAI Alert and Recovery: Initiate bus recovery
I (7350) TWAI Alert and Recovery: Bus Recovered
I (7350) TWAI Alert and Recovery: Driver uninstalled
```
## Troubleshooting
```text
I (3350) TWAI Alert and Recovery: Trigger errors
```
If the example does not progress pass triggering errors, check that the target is correctly connected to the transceiver.
```text
I (3350) TWAI Alert and Recovery: Trigger errors
I (3650) TWAI Alert and Recovery: Surpassed Error Warning Limit
I (3650) TWAI Alert and Recovery: Entered Error Passive state
```
If the example is able to trigger errors but does not enter the bus off state (i.e., stays in the error passive state), check that the triggering of the bit error is properly set to the examples operating bit rate. By default, the example runs at a bit rate of 25kbits/sec, and the bit error should be triggered after the arbitration phase of each transmitted message.
## Example Breakdown
The TWAI Alert and Recovery Example will do the following...
1. Initialize the TWAI driver in No Acknowledgement mode (so that another node is not required).
2. Create a transmit task to handle message transmission, and a control task to handle alerts.
3. Control task starts the TWAI driver, then reconfigures the alerts to trigger when the error passive or bus off state is entered. The control task then waits for those alerts.
4. The transmit repeatedly transmits single shot messages (i.e., message won't be retried if an error occurs).
5. When a message is being transmitted, the transmit task will purposely invert the TX pin to trigger a bit error. **Note that the triggering of the bit error is timed to occur after the arbitration phase of the transmitted message**.
6. The triggering of a bit error on each transmitted message eventually puts the TWAI driver into the Bus-Off state.
7. Control tasks detects the Bus-Off state via an alert, and triggers the Bus-Off recovery process after a short delay. Alerts are also reconfigured to trigger on the completion of Bus-Off recovery.
8. Once the Bus-Off recovery completion alert is detected by the control task, the TWAI driver is stopped and uninstalled.

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS "twai_alert_and_recovery_example_main.c"
REQUIRES driver
INCLUDE_DIRS ".")

View File

@@ -1,23 +0,0 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EXAMPLE_TX_GPIO_NUM
int "TX GPIO number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 21 if IDF_TARGET_ESP32
default 0
help
This option selects the GPIO pin used for the TX signal. Connect the
TX signal to your transceiver.
config EXAMPLE_RX_GPIO_NUM
int "RX GPIO number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
default 22 if IDF_TARGET_ESP32
default 2
help
This option selects the GPIO pin used for the RX signal. Connect the
RX signal to your transceiver.
endmenu

View File

@@ -1,165 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
/*
* The following example demonstrates how to use the alert and bus recovery
* features of the TWAI driver. The example will do the following:
* 1) Install and start the TWAI driver
* 2) Have the TX task periodically broadcast messages expecting no ACK
* 3) Reconfigure alerts to detect bus-off state
* 4) Trigger bus errors by inverting TX GPIO
* 5) Initiate bus-off recovery and wait for completion
* 6) Uninstall TWAI driver
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/twai.h"
#include "esp_rom_gpio.h"
#include "esp_rom_sys.h"
#include "soc/twai_periph.h" // For GPIO matrix signal index
/* --------------------- Definitions and static variables ------------------ */
//Example Configuration
#define TX_GPIO_NUM CONFIG_EXAMPLE_TX_GPIO_NUM
#define RX_GPIO_NUM CONFIG_EXAMPLE_RX_GPIO_NUM
#define TX_TASK_PRIO 9
#define CTRL_TASK_PRIO 10
#define ERR_DELAY_US 800 //Approximate time for arbitration phase at 25KBPS
#define ERR_PERIOD_US 80 //Approximate time for two bits at 25KBPS
#define EXAMPLE_TAG "TWAI Alert and Recovery"
static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
static const twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, TWAI_MODE_NO_ACK);
static const twai_message_t tx_msg = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 0, // Not single shot
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = 0,
.data_length_code = 0,
.data = {0},
};
static SemaphoreHandle_t tx_task_sem;
static SemaphoreHandle_t ctrl_task_sem;
static bool trigger_tx_error = false;
/* --------------------------- Tasks and Functions -------------------------- */
extern const twai_signal_conn_t twai_periph_signals[SOC_TWAI_CONTROLLER_NUM];
static void invert_tx_bits(bool enable)
{
if (enable) {
//Inverts output of TX to trigger errors
esp_rom_gpio_connect_out_signal(TX_GPIO_NUM, twai_periph_signals[0].tx_sig, true, false);
} else {
//Returns TX to default settings
esp_rom_gpio_connect_out_signal(TX_GPIO_NUM, twai_periph_signals[0].tx_sig, false, false);
}
}
static void tx_task(void *arg)
{
xSemaphoreTake(tx_task_sem, portMAX_DELAY);
while (1) {
if (twai_transmit(&tx_msg, 0) == ESP_ERR_INVALID_STATE) {
break; //Exit TX task when bus-off state is reached
}
if (trigger_tx_error) {
//Trigger a bit error in transmission by inverting GPIO
esp_rom_delay_us(ERR_DELAY_US); //Wait until arbitration phase is over
invert_tx_bits(true); //Trigger bit error for a few bits
esp_rom_delay_us(ERR_PERIOD_US);
invert_tx_bits(false);
}
vTaskDelay(pdMS_TO_TICKS(50));
}
vTaskDelete(NULL);
}
static void ctrl_task(void *arg)
{
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(EXAMPLE_TAG, "Driver started");
ESP_LOGI(EXAMPLE_TAG, "Starting transmissions");
xSemaphoreGive(tx_task_sem); //Start transmit task
//Prepare to trigger errors, reconfigure alerts to detect change in error state
twai_reconfigure_alerts(TWAI_ALERT_ABOVE_ERR_WARN | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_OFF, NULL);
for (int i = 3; i > 0; i--) {
ESP_LOGW(EXAMPLE_TAG, "Trigger TX errors in %d", i);
vTaskDelay(pdMS_TO_TICKS(1000));
}
ESP_LOGI(EXAMPLE_TAG, "Trigger errors");
trigger_tx_error = true;
while (1) {
uint32_t alerts;
twai_read_alerts(&alerts, portMAX_DELAY);
if (alerts & TWAI_ALERT_ABOVE_ERR_WARN) {
ESP_LOGI(EXAMPLE_TAG, "Surpassed Error Warning Limit");
}
if (alerts & TWAI_ALERT_ERR_PASS) {
ESP_LOGI(EXAMPLE_TAG, "Entered Error Passive state");
}
if (alerts & TWAI_ALERT_BUS_OFF) {
ESP_LOGI(EXAMPLE_TAG, "Bus Off state");
//Prepare to initiate bus recovery, reconfigure alerts to detect bus recovery completion
twai_reconfigure_alerts(TWAI_ALERT_BUS_RECOVERED, NULL);
for (int i = 3; i > 0; i--) {
ESP_LOGW(EXAMPLE_TAG, "Initiate bus recovery in %d", i);
vTaskDelay(pdMS_TO_TICKS(1000));
}
twai_initiate_recovery(); //Needs 128 occurrences of bus free signal
ESP_LOGI(EXAMPLE_TAG, "Initiate bus recovery");
}
if (alerts & TWAI_ALERT_BUS_RECOVERED) {
//Bus recovery was successful, exit control task to uninstall driver
ESP_LOGI(EXAMPLE_TAG, "Bus Recovered");
break;
}
}
//No need call twai_stop(), bus recovery will return to stopped state
xSemaphoreGive(ctrl_task_sem);
vTaskDelete(NULL);
}
void app_main(void)
{
tx_task_sem = xSemaphoreCreateBinary();
ctrl_task_sem = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(tx_task, "TWAI_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(ctrl_task, "TWAI_ctrl", 4096, NULL, CTRL_TASK_PRIO, NULL, tskNO_AFFINITY);
//Install TWAI driver
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, & f_config));
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
xSemaphoreGive(ctrl_task_sem); //Start control task
vTaskDelay(pdMS_TO_TICKS(100));
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); //Wait for completion
//Uninstall TWAI driver
ESP_ERROR_CHECK(twai_driver_uninstall());
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
//Cleanup
vSemaphoreDelete(tx_task_sem);
vSemaphoreDelete(ctrl_task_sem);
}

View File

@@ -1,12 +0,0 @@
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.twai_transceiver
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_twai_alert_recovery_example(dut: Dut) -> None:
dut.expect_exact('TWAI Alert and Recovery: Driver installed')
dut.expect_exact('TWAI Alert and Recovery: Driver uninstalled')

View File

@@ -5,4 +5,5 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on. # "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON) idf_build_set_property(MINIMAL_BUILD ON)
project(can_alert_and_recovery_example)
project(twai_recovery_example)

View File

@@ -0,0 +1,84 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- |
# TWAI Bus-Off Recovery Example
This example demonstrates how to recover a TWAI node from a Bus-Off error condition and resume communication. The recovery is triggered by physically inducing bus errors and handled using the ESP TWAI on-chip driver with callback support.
## How It Works
1. Initialize and start the TWAI node using esp_twai_onchip.
2. Transmit a few TWAI frames in normal operation.
3. **Manually** trigger bit errors by either:
- Briefly disconnecting the TX or RX connection
- Briefly short-circuiting the TWAI H and TWAI L lines
4. When the node enters the Bus-Off state, an error recovery process will automatically initiated.
5. Once recovered, resume normal transmission.
## Hardware Requirements
- An ESP32xx chip with TWAI peripheral support
- External TWAI transceiver (e.g., TJA1050, MCP2551, or similar)
- TWAI bus setup with proper termination resistors
## Pin Configuration
Modify TX_GPIO_NUM and RX_GPIO_NUM in the `twai_recovery_main.c` if needed. Connect these pins to your TWAI transceiver's TX and RX pins respectively.
```c
#define TX_GPIO_NUM GPIO_NUM_4
#define RX_GPIO_NUM GPIO_NUM_5
```
## Manual Error Triggering
To trigger bus errors and test the recovery mechanism, you can use either of these methods:
1. **Connection Disruption**: Briefly disconnect the TX or RX wire between the ESP32 and the TWAI transceiver while frames are being transmitted.
2. **Bus Short Circuit**: Briefly short-circuit the TWAI H and TWAI L lines on the bus. This simulates a bus fault condition.
⚠️ **Important**: The disconnection or short-circuit should be brief enough to trigger bus error messages in the log output. Too long may cause permanent damage to the transceiver or affect other nodes on the bus.
## Build and Flash
```sh
idf.py set-target esp32 build
idf.py -p PORT flash monitor
```
## Example Output
```
install twai success
node started
sending frame 0
sending frame 1
...
sending frame 4
// Manually trigger error here (disconnect TX/RX or short H/L)
bus error: 0x2
state changed: error_active -> error_warning
...
sending frame 9
// Trigger error again
bus error: 0x2
state changed: error_passive -> bus_off
node offline, start recover ...
waiting ... 0
...
state changed: bus_off -> error_active
node recovered! continue
sending frame 0
```
## Troubleshooting
If the example doesn't enter the `error_warning`/`error_passive`/`bus_off` states after manual error triggering:
1. **Check Hardware Connections**: Ensure the TWAI transceiver is properly connected and powered.
2. **Verify Bus Termination**: Make sure the TWAI bus has proper 120Ω termination resistors at both ends.
3. **Timing of Error Injection**: The manual error should be triggered while frames are actively being transmitted.
4. **Transceiver Type**: Different TWAI transceivers may have different fault detection sensitivities.
If errors are not being detected:
- Try holding the disconnection/short-circuit for a slightly longer duration
- Ensure the error is introduced during active frame transmission
- Check that the TWAI H and TWAI L lines are properly connected to the transceiver

View File

@@ -0,0 +1,5 @@
idf_component_register(
SRCS "twai_recovery_main.c"
REQUIRES esp_driver_twai
INCLUDE_DIRS "."
)

View File

@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief This example shows how to recover twai node from bus_off and continue communication
*
* 1) Install and start the TWAI driver
* 2) Sending some frames
* 3) Manually trigger bit_error by disconnecting the TX / RX or short-circuiting the H & L
* 4) Initiate bus-off recovery and wait for completion through `twai_node_get_info`
* 5) Back to step (2)
*/
#include "esp_twai.h"
#include "esp_log.h"
#include "esp_twai_onchip.h"
#include "freertos/FreeRTOS.h"
//////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////// Please update the following configuration according to your HardWare spec /////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
#define TX_GPIO_NUM GPIO_NUM_4
#define RX_GPIO_NUM GPIO_NUM_5 // Using same pin to test without transceiver
#define TWAI_BITRATE 100000 // use low bitrate to ensure soft trigger error is effective
static const char *TAG = "twai_recovery";
// bus errors
static bool example_error_cb(twai_node_handle_t handle, const twai_error_event_data_t *edata, void *user_ctx)
{
ESP_EARLY_LOGI(TAG, "bus error: 0x%lx", edata->err_flags.val);
return false;
}
// node state
static bool example_state_change_cb(twai_node_handle_t handle, const twai_state_change_event_data_t *edata, void *user_ctx)
{
const char *twai_state_name[] = {"error_active", "error_warning", "error_passive", "bus_off"};
ESP_EARLY_LOGI(TAG, "state changed: %s -> %s", twai_state_name[edata->old_sta], twai_state_name[edata->new_sta]);
return false;
}
void app_main(void)
{
twai_node_handle_t node_hdl;
twai_onchip_node_config_t node_config = {
.io_cfg.tx = TX_GPIO_NUM,
.io_cfg.rx = RX_GPIO_NUM,
.bit_timing.bitrate = TWAI_BITRATE,
.tx_queue_depth = 1,
.flags.enable_self_test = true,
};
ESP_ERROR_CHECK(twai_new_node_onchip(&node_config, &node_hdl));
ESP_LOGI(TAG, "install twai success, bitrate: %ld, bittime: %d us", node_config.bit_timing.bitrate, (1000000 / TWAI_BITRATE));
// register callbacks
twai_event_callbacks_t user_cbs = {
.on_error = example_error_cb,
.on_state_change = example_state_change_cb,
};
ESP_ERROR_CHECK(twai_node_register_event_callbacks(node_hdl, &user_cbs, NULL));
ESP_ERROR_CHECK(twai_node_enable(node_hdl));
ESP_LOGI(TAG, "node started");
twai_frame_t tx_frame = {
.buffer = (uint8_t *)"hello\n",
.buffer_len = 6,
};
for (uint8_t i = 0; i < 100; i++) {
twai_node_status_t node_status;
twai_node_get_info(node_hdl, &node_status, NULL);
if (node_status.state == TWAI_ERROR_BUS_OFF) {
i = 0;
ESP_LOGI(TAG, "node offline, start recover ...");
ESP_ERROR_CHECK(twai_node_recover(node_hdl));
for (uint8_t i = 0; i < 100; i++) {
ESP_LOGI(TAG, "waiting ... %d", i);
vTaskDelay(pdMS_TO_TICKS(1000));
twai_node_get_info(node_hdl, &node_status, NULL);
if (node_status.state == TWAI_ERROR_ACTIVE) {
ESP_LOGI(TAG, "node recovered! continue");
break;
}
}
} else {
ESP_LOGI(TAG, "sending frame %d", i);
tx_frame.header.id = i;
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_frame, 500));
}
vTaskDelay(pdMS_TO_TICKS(500));
}
ESP_ERROR_CHECK(twai_node_disable(node_hdl));
ESP_ERROR_CHECK(twai_node_delete(node_hdl));
}