mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-03 02:20:57 +02:00
fix(twai): Enhance ISR handling for TX queue operations
This commit is contained in:
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
ESP_RETURN_ON_FALSE(xQueueSend(twai_ctx->tx_mount_queue, &frame, ticks_to_wait), ESP_ERR_TIMEOUT, TAG, "tx queue full");
|
||||
// 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;
|
||||
|
@@ -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:
|
||||
|
@@ -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");
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user