Merge branch 'features/twai_send_isr' into 'master'

features(twai): Added support for calling twai_node_transmit() from ISR contexts

See merge request espressif/esp-idf!41383
This commit is contained in:
morris
2025-08-25 15:19:18 +08:00
8 changed files with 246 additions and 18 deletions

View File

@@ -8,6 +8,13 @@ menu "ESP-Driver:TWAI Configurations"
help
Place the TWAI ISR in to IRAM to reduce latency and increase performance
config TWAI_IO_FUNC_IN_IRAM
bool "Place TWAI I/O functions in IRAM"
select TWAI_OBJ_CACHE_SAFE
default n
help
Place certain TWAI I/O functions (like twai_transmit) in IRAM to reduce latency
config TWAI_ISR_CACHE_SAFE
bool "Allow TWAI ISR execute when cache disabled" if !SPI_FLASH_AUTO_SUSPEND
select TWAI_ISR_IN_IRAM

View File

@@ -136,8 +136,8 @@ esp_err_t twai_node_get_info(twai_node_handle_t node, twai_node_status_t *status
esp_err_t twai_node_transmit(twai_node_handle_t node, const twai_frame_t *frame, int timeout_ms)
{
ESP_RETURN_ON_FALSE(node && frame, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null");
ESP_RETURN_ON_FALSE(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit is not supported");
ESP_RETURN_ON_FALSE_ISR(node && frame, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null");
ESP_RETURN_ON_FALSE_ISR(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit is not supported");
return node->transmit(node, frame, timeout_ms);
}

View File

@@ -582,15 +582,15 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
{
twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base);
if (frame->header.dlc && frame->buffer_len) {
ESP_RETURN_ON_FALSE(frame->header.dlc == twaifd_len2dlc(frame->buffer_len), ESP_ERR_INVALID_ARG, TAG, "unmatched dlc and buffer_len");
ESP_RETURN_ON_FALSE_ISR(frame->header.dlc == twaifd_len2dlc(frame->buffer_len), ESP_ERR_INVALID_ARG, TAG, "unmatched dlc and buffer_len");
}
#if !SOC_TWAI_SUPPORT_FD
ESP_RETURN_ON_FALSE(!frame->header.fdf || frame->buffer_len <= TWAI_FRAME_MAX_LEN, ESP_ERR_INVALID_ARG, TAG, "fdf flag or buffer_len not supported");
ESP_RETURN_ON_FALSE_ISR(!frame->header.fdf || frame->buffer_len <= TWAI_FRAME_MAX_LEN, ESP_ERR_INVALID_ARG, TAG, "fdf flag or buffer_len not supported");
#endif
ESP_RETURN_ON_FALSE(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld)", frame->buffer_len);
ESP_RETURN_ON_FALSE((!frame->header.brs) || (twai_ctx->valid_fd_timing), ESP_ERR_INVALID_ARG, TAG, "brs can't be used without config data_timing");
ESP_RETURN_ON_FALSE(!twai_ctx->hal->enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only");
ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off");
ESP_RETURN_ON_FALSE_ISR(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld)", frame->buffer_len);
ESP_RETURN_ON_FALSE_ISR((!frame->header.brs) || (twai_ctx->valid_fd_timing), ESP_ERR_INVALID_ARG, TAG, "brs can't be used without config data_timing");
ESP_RETURN_ON_FALSE_ISR(!twai_ctx->hal->enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only");
ESP_RETURN_ON_FALSE_ISR(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off");
TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout);
xEventGroupClearBits(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT); //going to send, clear the idle event
@@ -599,15 +599,38 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
twai_ctx->p_curr_tx = frame;
_node_start_trans(twai_ctx);
} else {
//options in following steps (in_queue->2nd_check->pop_queue) should exec ASAP
//within about 50us (minimum time for one msg), to ensure data safe
// Hardware busy, need to queue the frame
BaseType_t is_isr_context = xPortInIsrContext();
BaseType_t yield_required = pdFALSE;
if (is_isr_context) {
// In ISR context - use ISR-safe queue operations
ESP_RETURN_ON_FALSE_ISR(xQueueSendFromISR(twai_ctx->tx_mount_queue, &frame, &yield_required), ESP_ERR_TIMEOUT, TAG, "tx queue full");
} else {
// In task context - use normal queue operations
ESP_RETURN_ON_FALSE(xQueueSend(twai_ctx->tx_mount_queue, &frame, ticks_to_wait), ESP_ERR_TIMEOUT, TAG, "tx queue full");
}
// Second chance check for hardware availability
false_var = false;
if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) {
if (xQueueReceive(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, 0) != pdTRUE) {
BaseType_t dequeue_result;
if (is_isr_context) {
dequeue_result = xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &yield_required);
} else {
dequeue_result = xQueueReceive(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, 0);
}
if (dequeue_result == pdTRUE) {
_node_start_trans(twai_ctx);
} else {
assert(false && "should always get frame at this moment");
}
_node_start_trans(twai_ctx);
}
// Handle ISR yield if required
if (is_isr_context && yield_required) {
portYIELD_FROM_ISR();
}
}
return ESP_OK;

View File

@@ -7,6 +7,10 @@ entries:
esp_twai_onchip: _node_start_trans (noflash)
esp_twai_onchip: _node_parse_rx (noflash)
if TWAI_IO_FUNC_IN_IRAM = y:
esp_twai_onchip: _node_queue_tx (noflash)
esp_twai: twai_node_transmit (noflash)
[mapping:twai_hal]
archive: libhal.a
entries:

View File

@@ -48,6 +48,8 @@ TEST_CASE("twai install uninstall (loopback)", "[twai]")
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 1000000;
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
node_config.flags.enable_self_test = true;
@@ -126,7 +128,7 @@ static void test_twai_baudrate_correctness(twai_clock_source_t clk_src, uint32_t
TEST_ESP_OK(uart_detect_bitrate_start(UART_NUM_1, &detect_config));
twai_frame_t tx_frame = {};
tx_frame.header.id = 0x55555;
tx_frame.header.id = 0x15555555;
tx_frame.header.dlc = 8;
tx_frame.header.ide = true;
tx_frame.buffer = (uint8_t []) {
@@ -183,6 +185,8 @@ TEST_CASE("twai transmit stop resume (loopback)", "[twai]")
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 200000;
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
node_config.flags.enable_self_test = true;
@@ -280,6 +284,8 @@ TEST_CASE("twai mask filter (loopback)", "[twai]")
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 1000000;
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
node_config.flags.enable_self_test = true;
@@ -363,6 +369,8 @@ TEST_CASE("twai dual 16bit mask filter (loopback)", "[twai]")
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 1000000;
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
node_config.flags.enable_self_test = true;
@@ -436,6 +444,8 @@ TEST_CASE("twai driver cache safe (loopback)", "[twai]")
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 50000; //slow bitrate to ensure cache disabled before tx_queue finish
node_config.tx_queue_depth = TEST_FRAME_NUM;
node_config.flags.enable_loopback = true;
@@ -577,3 +587,130 @@ TEST_CASE("twai tx_wait_all_done thread safe", "[twai]")
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
}
// Test data for ISR send functionality
typedef struct {
twai_node_handle_t node;
uint32_t rx_count;
uint32_t tx_isr_send_count;
uint32_t rx_isr_send_count;
bool test_completed;
} isr_send_test_ctx_t;
static IRAM_ATTR bool test_tx_isr_send_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
{
isr_send_test_ctx_t *ctx = (isr_send_test_ctx_t *)user_ctx;
// Test sending from TX ISR context
if (ctx->tx_isr_send_count < 3) {
twai_frame_t isr_frame = {};
isr_frame.header.id = 0x200 + ctx->tx_isr_send_count;
isr_frame.header.dlc = 1;
isr_frame.buffer = (uint8_t*)(&ctx->tx_isr_send_count);
isr_frame.buffer_len = 1;
esp_err_t err = twai_node_transmit(handle, &isr_frame, 0); // timeout must be 0 in ISR
if (err == ESP_OK) {
ctx->tx_isr_send_count++;
}
}
return false;
}
static IRAM_ATTR bool test_rx_isr_send_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
isr_send_test_ctx_t *ctx = (isr_send_test_ctx_t *)user_ctx;
twai_frame_t rx_frame = {};
uint8_t buffer[8];
rx_frame.buffer = buffer;
rx_frame.buffer_len = sizeof(buffer);
if (ESP_OK == twai_node_receive_from_isr(handle, &rx_frame)) {
ctx->rx_count++;
// Test sending from RX ISR context (response pattern)
if ((rx_frame.header.id >= 0x100) && (rx_frame.header.id < 0x103) && (ctx->rx_isr_send_count < 3)) {
twai_frame_t response_frame = {};
response_frame.header.id = 0x300 + ctx->rx_isr_send_count;
response_frame.header.dlc = 1;
response_frame.buffer = (uint8_t*)(&ctx->rx_isr_send_count);
response_frame.buffer_len = 1;
esp_err_t err = twai_node_transmit(handle, &response_frame, 0); // timeout must be 0 in ISR
if (err == ESP_OK) {
ctx->rx_isr_send_count++;
}
}
// Mark test completed when we receive expected frames
if (ctx->rx_count >= 9) { // 3 initial + 3 tx_isr + 3 rx_isr responses
ctx->test_completed = true;
}
}
return false;
}
TEST_CASE("twai send from ISR context (loopback)", "[twai]")
{
isr_send_test_ctx_t test_ctx = {};
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 500000;
node_config.tx_queue_depth = 10; // Larger queue for ISR sends
node_config.flags.enable_self_test = true;
node_config.flags.enable_loopback = true;
TEST_ESP_OK(twai_new_node_onchip(&node_config, &test_ctx.node));
twai_event_callbacks_t user_cbs = {};
user_cbs.on_rx_done = test_rx_isr_send_cb;
user_cbs.on_tx_done = test_tx_isr_send_cb;
TEST_ESP_OK(twai_node_register_event_callbacks(test_ctx.node, &user_cbs, &test_ctx));
TEST_ESP_OK(twai_node_enable(test_ctx.node));
printf("Testing ISR context sending...\n");
// Send initial frames to trigger RX ISR responses
for (int i = 0; i < 3; i++) {
twai_frame_t trigger_frame = {};
trigger_frame.header.id = 0x100 + i;
trigger_frame.header.dlc = 1;
trigger_frame.buffer = (uint8_t[]) {
(uint8_t)i
};
trigger_frame.buffer_len = 1;
TEST_ESP_OK(twai_node_transmit(test_ctx.node, &trigger_frame, 500));
printf("Sent trigger frame 0x%" PRIx32 "\n", trigger_frame.header.id);
vTaskDelay(pdMS_TO_TICKS(50)); // Allow ISR processing
}
// Wait for test completion
int timeout_count = 0;
while (!test_ctx.test_completed && timeout_count < 100) {
vTaskDelay(pdMS_TO_TICKS(10));
timeout_count++;
}
printf("Test results:\n");
printf(" RX count: %" PRIu32 "\n", test_ctx.rx_count);
printf(" TX ISR sends: %" PRIu32 "\n", test_ctx.tx_isr_send_count);
printf(" RX ISR sends: %" PRIu32 "\n", test_ctx.rx_isr_send_count);
printf(" Test completed: %s\n", test_ctx.test_completed ? "YES" : "NO");
// Verify test results
TEST_ASSERT_TRUE(test_ctx.test_completed);
TEST_ASSERT_EQUAL_UINT32(3, test_ctx.tx_isr_send_count); // 3 sends from TX ISR
TEST_ASSERT_EQUAL_UINT32(3, test_ctx.rx_isr_send_count); // 3 sends from RX ISR
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(9, test_ctx.rx_count); // At least 9 received frames
TEST_ESP_OK(twai_node_disable(test_ctx.node));
TEST_ESP_OK(twai_node_delete(test_ctx.node));
printf("ISR send test passed!\n");
}

View File

@@ -1,6 +1,7 @@
CONFIG_COMPILER_DUMP_RTL_FILES=y
CONFIG_TWAI_ISR_CACHE_SAFE=y
CONFIG_FREERTOS_IN_IRAM=y
CONFIG_TWAI_IO_FUNC_IN_IRAM=y
CONFIG_COMPILER_OPTIMIZATION_NONE=y
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y

View File

@@ -122,7 +122,7 @@ To reduce performance overhead caused by memory copying, the TWAI driver uses po
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // Timeout = 0: returns immediately if queue is full
ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(node_hdl, -1)); // Wait for transmission to finish
In this example, :cpp:member:`twai_frame_t::header::id` specifies the ID of the message as 0x01. Message IDs are typically used to indicate the type of message in an application and also play a role in bus arbitration during transmission—lower values indicate higher priority on the bus. :cpp:member:`twai_frame_t::buffer` points to the memory address where the data to be transmitted is stored, and :cpp:member:`twai_frame_t::buffer_len` specifies the length of that data.
In this example, :cpp:member:`twai_frame_t::header::id` specifies the ID of the message as 0x01. Message IDs are typically used to indicate the type of message in an application and also play a role in bus arbitration during transmission—lower values indicate higher priority on the bus. :cpp:member:`twai_frame_t::buffer` points to the memory address where the data to be transmitted is stored, and :cpp:member:`twai_frame_t::buffer_len` specifies the length of that data. The :cpp:func:`twai_node_transmit` function is thread-safe and can also be called from an ISR. When called from an ISR, the ``timeout`` parameter is ignored, and the function will not block.
Note that :cpp:member:`twai_frame_t::header::dlc` can also specify the length of the data in the frame. The DLC (Data Length Code) is mapped to the actual data length as defined in ISO 11898-1. You can use either :cpp:func:`twaifd_dlc2len` or :cpp:func:`twaifd_len2dlc` for conversion. If both dlc and buffer_len are non-zero, they must represent the same length.
@@ -181,6 +181,32 @@ After understanding the basic usage, you can further explore more advanced capab
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
:align: center
Transmit from ISR
^^^^^^^^^^^^^^^^^
The TWAI driver supports transmitting messages from an Interrupt Service Routine (ISR). This is particularly useful for applications requiring low-latency responses or periodic transmissions triggered by hardware timers. For example, you can trigger a new transmission from within the ``on_tx_done`` callback, which is executed in an ISR context.
.. code:: c
static bool twai_tx_done_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
{
// A frame has been successfully transmitted. Queue another one.
// The frame and its data buffer must be valid until transmission is complete.
static const uint8_t data_buffer[] = {1, 2, 3, 4};
static const twai_frame_t tx_frame = {
.header.id = 0x2,
.buffer = (uint8_t *)data_buffer,
.buffer_len = sizeof(data_buffer),
};
// The `twai_node_transmit` is safe to be called in an ISR context
twai_node_transmit(handle, &tx_frame, 0);
return false;
}
.. note::
When calling :cpp:func:`twai_node_transmit` from an ISR, the ``timeout`` parameter is ignored, and the function will not block. If the transmit queue is full, the function will return immediately with an error. It is the application's responsibility to handle cases where the queue is full.
Bit Timing Customization
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -318,7 +344,9 @@ The driver guarantees thread safety for all public TWAI APIs. You can safely cal
Performance
^^^^^^^^^^^
To improve the real-time performance of interrupt handling, the driver provides the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option. When enabled, the TWAI ISR (Interrupt Service Routine) is placed in internal RAM, reducing latency caused by instruction fetching from Flash.
To improve the real-time performance of interrupt handling, the driver provides the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option. When enabled, the TWAI ISR (Interrupt Service Routine) and receive operations are placed in internal RAM, reducing latency caused by instruction fetching from Flash.
For applications that require high-performance transmit operations, the driver provides the :ref:`CONFIG_TWAI_IO_FUNC_IN_IRAM` option to place transmit functions in IRAM. This is particularly beneficial for time-critical applications that frequently call :cpp:func:`twai_node_transmit` from user tasks.
.. note::

View File

@@ -122,7 +122,7 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // 超时为0队列满则直接返回超时
ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(node_hdl, -1)); // 等待发送完成
其中 :cpp:member:`twai_frame_t::header::id` 指示了该文的 ID 为 0x01。报文的 ID 通常用于表示报文在应用中的类型,并在发送过程中起到总线竞争仲裁的作用,其数值越小,在总线上的优先级越高。:cpp:member:`twai_frame_t::buffer` 则指向要发送数据所在的内存地址,并由 :cpp:member:`twai_frame_t::buffer_len` 给出数据长度。
其中 :cpp:member:`twai_frame_t::header::id` 指示了该文的 ID 为 0x01。报文的 ID 通常用于表示报文在应用中的类型,并在发送过程中起到总线竞争仲裁的作用,其数值越小,在总线上的优先级越高。:cpp:member:`twai_frame_t::buffer` 则指向要发送数据所在的内存地址,并由 :cpp:member:`twai_frame_t::buffer_len` 给出数据长度。:cpp:func:`twai_node_transmit` 函数是线程安全的,并且也可以在 ISR 中调用。当从 ISR 调用时,``timeout`` 参数将被忽略,函数不会阻塞。
需要注意的是 :cpp:member:`twai_frame_t::header::dlc` 同样可以指定一个数据帧中数据的长度dlc(data length code) 与具体长度的对应兼容 ISO11898-1 规定。可使用 :cpp:func:`twaifd_dlc2len` / :cpp:func:`twaifd_len2dlc` 进行转换,选择其一即可,如果 dlc 和 buffer_len 都不为 0 ,那他们所代表的长度必须一致。
@@ -181,6 +181,32 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
:align: center
在 ISR 中发送
^^^^^^^^^^^^^
TWAI 驱动支持在中断服务程序 (ISR) 中发送报文。这对于需要低延迟响应或由硬件定时器触发的周期性传输的应用特别有用。例如,你可以在 ``on_tx_done`` 回调中触发一次新的传输,该回调在 ISR 上下文中执行。
.. code:: c
static bool twai_tx_done_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
{
// 一帧已成功发送。排队另一帧。
// 帧及其数据缓冲区必须在传输完成之前保持有效。
static const uint8_t data_buffer[] = {1, 2, 3, 4};
static const twai_frame_t tx_frame = {
.header.id = 0x2,
.buffer = (uint8_t *)data_buffer,
.buffer_len = sizeof(data_buffer),
};
// `twai_node_transmit` 在 ISR 上下文中调用是安全的
twai_node_transmit(handle, &tx_frame, 0);
return false;
}
.. note::
在 ISR 中调用 :cpp:func:`twai_node_transmit` 时,``timeout`` 参数将被忽略,函数不会阻塞。如果发送队列已满,函数将立即返回错误。应用程序需要自行处理队列已满的情况。
位时序自定义
^^^^^^^^^^^^^
@@ -318,7 +344,9 @@ TWAI控制器能够检测由于总线干扰产生的/损坏的不符合帧格式
关于性能
^^^^^^^^
为了提升中断处理的实时响应能力, 驱动提供了 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项。启用该选项后,中断处理程序将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令带来的延迟。
为了提升中断处理的实时响应能力, 驱动提供了 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项。启用该选项后,中断处理程序和接收操作将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令带来的延迟。
对于需要高性能发送操作的应用,驱动还提供了 :ref:`CONFIG_TWAI_IO_FUNC_IN_IRAM` 选项,用于将发送函数放置在 IRAM 中。这对于在用户任务中频繁调用 :cpp:func:`twai_node_transmit` 的时间关键应用特别有效。
.. note::