fix(twai): Enhance ISR handling for TX queue operations

This commit is contained in:
Yuan Yu
2025-08-22 14:33:49 +08:00
parent c994c2203d
commit 96118438da
6 changed files with 187 additions and 15 deletions

View File

@@ -8,6 +8,13 @@ menu "ESP-Driver:TWAI Configurations"
help help
Place the TWAI ISR in to IRAM to reduce latency and increase performance 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 config TWAI_ISR_CACHE_SAFE
bool "Allow TWAI ISR execute when cache disabled" if !SPI_FLASH_AUTO_SUSPEND bool "Allow TWAI ISR execute when cache disabled" if !SPI_FLASH_AUTO_SUSPEND
select TWAI_ISR_IN_IRAM 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_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_ISR(node && frame, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null");
ESP_RETURN_ON_FALSE(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit func null"); ESP_RETURN_ON_FALSE_ISR(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit is not supported");
return node->transmit(node, frame, timeout_ms); return node->transmit(node, frame, timeout_ms);
} }

View File

@@ -569,15 +569,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); twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base);
if (frame->header.dlc && frame->buffer_len) { 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 #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 #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_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((!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((!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_ISR(!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(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); TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout);
bool false_var = false; bool false_var = false;
@@ -585,15 +585,38 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
twai_ctx->p_curr_tx = frame; twai_ctx->p_curr_tx = frame;
_node_start_trans(twai_ctx); _node_start_trans(twai_ctx);
} else { } else {
//options in following steps (in_queue->2nd_check->pop_queue) should exec ASAP // Hardware busy, need to queue the frame
//within about 50us (minimum time for one msg), to ensure data safe BaseType_t is_isr_context = xPortInIsrContext();
ESP_RETURN_ON_FALSE(xQueueSend(twai_ctx->tx_mount_queue, &frame, ticks_to_wait), ESP_ERR_TIMEOUT, TAG, "tx queue full"); 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; false_var = false;
if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) { 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"); 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; return ESP_OK;

View File

@@ -7,6 +7,10 @@ entries:
esp_twai_onchip: _node_start_trans (noflash) esp_twai_onchip: _node_start_trans (noflash)
esp_twai_onchip: _node_parse_rx (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] [mapping:twai_hal]
archive: libhal.a archive: libhal.a
entries: entries:

View File

@@ -44,6 +44,8 @@ TEST_CASE("twai install uninstall (loopback)", "[twai]")
twai_onchip_node_config_t node_config = { twai_onchip_node_config_t node_config = {
.io_cfg.tx = TEST_TX_GPIO, .io_cfg.tx = TEST_TX_GPIO,
.io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver .io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver
.io_cfg.quanta_clk_out = GPIO_NUM_NC,
.io_cfg.bus_off_indicator = GPIO_NUM_NC,
.bit_timing.bitrate = 1000000, .bit_timing.bitrate = 1000000,
.data_timing.bitrate = 1000000, .data_timing.bitrate = 1000000,
.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH, .tx_queue_depth = TEST_TWAI_QUEUE_DEPTH,
@@ -128,7 +130,7 @@ static void test_twai_baudrate_correctness(twai_clock_source_t clk_src, uint32_t
.buffer = (uint8_t []){0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, .buffer = (uint8_t []){0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55},
.buffer_len = 8, .buffer_len = 8,
.header = { .header = {
.id = 0x55555, .id = 0x15555555,
.ide = true, .ide = true,
.dlc = 8, .dlc = 8,
} }
@@ -182,6 +184,8 @@ TEST_CASE("twai transmit stop resume (loopback)", "[twai]")
twai_onchip_node_config_t node_config = { twai_onchip_node_config_t node_config = {
.io_cfg.tx = TEST_TX_GPIO, .io_cfg.tx = TEST_TX_GPIO,
.io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver .io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver
.io_cfg.quanta_clk_out = GPIO_NUM_NC,
.io_cfg.bus_off_indicator = GPIO_NUM_NC,
.bit_timing.bitrate = 200000, .bit_timing.bitrate = 200000,
.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH, .tx_queue_depth = TEST_TWAI_QUEUE_DEPTH,
.flags.enable_loopback = true, .flags.enable_loopback = true,
@@ -282,6 +286,8 @@ TEST_CASE("twai mask filter (loopback)", "[twai]")
twai_onchip_node_config_t node_config = { twai_onchip_node_config_t node_config = {
.io_cfg.tx = TEST_TX_GPIO, .io_cfg.tx = TEST_TX_GPIO,
.io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver .io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver
.io_cfg.quanta_clk_out = GPIO_NUM_NC,
.io_cfg.bus_off_indicator = GPIO_NUM_NC,
.bit_timing.bitrate = 1000000, .bit_timing.bitrate = 1000000,
.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH, .tx_queue_depth = TEST_TWAI_QUEUE_DEPTH,
.flags.enable_loopback = true, .flags.enable_loopback = true,
@@ -366,6 +372,8 @@ TEST_CASE("twai dual 16bit mask filter (loopback)", "[twai]")
twai_onchip_node_config_t node_config = { twai_onchip_node_config_t node_config = {
.io_cfg.tx = TEST_TX_GPIO, .io_cfg.tx = TEST_TX_GPIO,
.io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver .io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver
.io_cfg.quanta_clk_out = GPIO_NUM_NC,
.io_cfg.bus_off_indicator = GPIO_NUM_NC,
.bit_timing.bitrate = 1000000, .bit_timing.bitrate = 1000000,
.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH, .tx_queue_depth = TEST_TWAI_QUEUE_DEPTH,
.flags.enable_loopback = true, .flags.enable_loopback = true,
@@ -440,6 +448,8 @@ TEST_CASE("twai driver cache safe (loopback)", "[twai]")
twai_onchip_node_config_t node_config = { twai_onchip_node_config_t node_config = {
.io_cfg.tx = TEST_TX_GPIO, .io_cfg.tx = TEST_TX_GPIO,
.io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver .io_cfg.rx = TEST_TX_GPIO, // Using same pin for test without transceiver
.io_cfg.quanta_clk_out = GPIO_NUM_NC,
.io_cfg.bus_off_indicator = GPIO_NUM_NC,
.bit_timing.bitrate = 50000, //slow bitrate to ensure cache disabled before tx_queue finish .bit_timing.bitrate = 50000, //slow bitrate to ensure cache disabled before tx_queue finish
.tx_queue_depth = TEST_FRAME_NUM, .tx_queue_depth = TEST_FRAME_NUM,
.flags.enable_loopback = true, .flags.enable_loopback = true,
@@ -485,3 +495,131 @@ 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 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) {
static 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)) {
static twai_frame_t response_frame;
response_frame = (twai_frame_t) {};
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,6 @@
CONFIG_COMPILER_DUMP_RTL_FILES=y CONFIG_COMPILER_DUMP_RTL_FILES=y
CONFIG_TWAI_ISR_CACHE_SAFE=y CONFIG_TWAI_ISR_CACHE_SAFE=y
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y CONFIG_TWAI_IO_FUNC_IN_IRAM=y
CONFIG_COMPILER_OPTIMIZATION_NONE=y CONFIG_COMPILER_OPTIMIZATION_NONE=y
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y