diff --git a/components/driver/twai/twai.c b/components/driver/twai/twai.c index 6cfa5e34513..3dcc6bf58ce 100644 --- a/components/driver/twai/twai.c +++ b/components/driver/twai/twai.c @@ -231,8 +231,8 @@ static void twai_intr_handler_main(void *arg) //Note: This event will never occur if there is a periph reset event twai_handle_rx_buffer_frames(p_twai_obj, &task_woken, &alert_req); } - if (events & TWAI_HAL_EVENT_TX_BUFF_FREE) { - twai_handle_tx_buffer_frame(p_twai_obj, (events & TWAI_HAL_EVENT_TX_SUCCESS), &task_woken, &alert_req); + if (events & TWAI_HAL_EVENT_TX0_DONE) { + twai_handle_tx_buffer_frame(p_twai_obj, (events & TWAI_HAL_EVENT_TX0_SUCCESS), &task_woken, &alert_req); } //Handle events that only require alerting (i.e. no handler) diff --git a/components/esp_driver_twai/CMakeLists.txt b/components/esp_driver_twai/CMakeLists.txt index e4d24b813b2..c59921c987a 100644 --- a/components/esp_driver_twai/CMakeLists.txt +++ b/components/esp_driver_twai/CMakeLists.txt @@ -9,7 +9,7 @@ set(public_include "include") set(priv_req esp_driver_gpio esp_pm esp_timer) if(CONFIG_SOC_TWAI_SUPPORTED) - list(APPEND srcs "esp_twai_onchip.c") + list(APPEND srcs "esp_twai_onchip.c" "twai_frame_queue.c") endif() idf_component_register( diff --git a/components/esp_driver_twai/esp_twai_onchip.c b/components/esp_driver_twai/esp_twai_onchip.c index 92720e523c0..1ea5e841763 100644 --- a/components/esp_driver_twai/esp_twai_onchip.c +++ b/components/esp_driver_twai/esp_twai_onchip.c @@ -7,9 +7,10 @@ #include "esp_timer.h" #include "esp_twai.h" #include "esp_twai_onchip.h" +#include "twai_private.h" #include "esp_private/twai_interface.h" #include "esp_private/twai_utils.h" -#include "twai_private.h" +#include "esp_private/twai_frame_queue.h" #include "hal/twai_periph.h" #include "hal/twai_hal.h" #if SOC_HAS(TWAI_FD) @@ -46,7 +47,7 @@ typedef struct { twai_hal_context_t *hal; intr_handle_t intr_hdl; intr_handle_t timer_intr_hdl; - QueueHandle_t tx_mount_queue; + twai_frame_queue_t tx_queue; EventGroupHandle_t event_group; twai_clock_source_t curr_clk_src; uint32_t src_freq_hz; @@ -60,10 +61,11 @@ typedef struct { _Atomic twai_error_state_t state; twai_node_record_t history; + uint8_t tx_slot_num; atomic_bool hw_busy; atomic_bool rx_isr; - const twai_frame_t *p_curr_tx; + const twai_frame_t *p_curr_tx[TWAI_HAL_TX_BUFFER_SLOT_NUM]; twai_hal_frame_t rcv_buff; } twai_onchip_ctx_t; @@ -154,9 +156,9 @@ static void _node_release_io(twai_onchip_ctx_t *node) } } -static void _node_start_trans(twai_onchip_ctx_t *node) +static void _node_start_trans(twai_onchip_ctx_t *node, uint8_t buffer_idx) { - const twai_frame_t *frame = node->p_curr_tx; + const twai_frame_t *frame = node->p_curr_tx[buffer_idx]; twai_hal_context_t *hal = node->hal; twai_hal_frame_t hal_buf = {}; @@ -172,8 +174,40 @@ static void _node_start_trans(twai_onchip_ctx_t *node) }, }; twai_hal_format_frame(&hal_trans, &hal_buf); - //TODO: utilize all txt buffers - twai_hal_set_tx_buffer_and_transmit(hal, &hal_buf, 0); + twai_hal_set_tx_buffer_and_transmit(hal, &hal_buf, buffer_idx); +} + +static uint8_t _node_start_tx_batch_from_isr(twai_onchip_ctx_t *node, BaseType_t *yield_required) +{ + uint8_t tx_idx = 0; + // Note: hardware slot has default priority (like 0>1>2...), + // we should fill slot with same order to avoid data inverse + while (tx_idx < node->tx_slot_num) { // try best to fill all tx slots + const twai_frame_t *frame = NULL; + if (twai_frame_queue_pop_from_isr(node->tx_queue, &frame, (bool *)yield_required) != ESP_OK) { + break; + } + node->p_curr_tx[tx_idx] = frame; + _node_start_trans(node, tx_idx); + tx_idx++; + } + return tx_idx; +} + +static void _node_mark_tx_idle(twai_onchip_ctx_t *node) +{ + memset(node->p_curr_tx, 0, sizeof(node->p_curr_tx)); + atomic_store(&node->hw_busy, false); +} + +static inline bool _node_is_tx_all_done(twai_onchip_ctx_t *node) +{ + for (uint8_t tx_idx = 0; tx_idx < node->tx_slot_num; tx_idx++) { + if (node->p_curr_tx[tx_idx]) { + return false; + } + } + return true; } static void _node_isr_main(void *arg) @@ -221,11 +255,10 @@ static void _node_isr_main(void *arg) } // node recover from busoff, restart remain tx transaction if ((e_data.old_sta == TWAI_ERROR_BUS_OFF) && (e_data.new_sta == TWAI_ERROR_ACTIVE)) { - if (xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &do_yield)) { + if (_node_start_tx_batch_from_isr(twai_ctx, &do_yield)) { atomic_store(&twai_ctx->hw_busy, true); - _node_start_trans(twai_ctx); } else { - atomic_store(&twai_ctx->hw_busy, false); + _node_mark_tx_idle(twai_ctx); xEventGroupSetBitsFromISR(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT, &do_yield); } } @@ -257,21 +290,27 @@ static void _node_isr_main(void *arg) } // deal TX event - if (events & TWAI_HAL_EVENT_TX_BUFF_FREE) { - if (twai_ctx->cbs.on_tx_done) { - twai_tx_done_event_data_t tx_ev = { - .is_tx_success = (events & TWAI_HAL_EVENT_TX_SUCCESS), // find 'on_error_cb' if not success - .done_tx_frame = twai_ctx->p_curr_tx, - }; - do_yield |= twai_ctx->cbs.on_tx_done(&twai_ctx->api_base, &tx_ev, twai_ctx->user_data); + if (events & TWAI_HAL_EVENT_TX_DONE_MASK) { + uint32_t tx_done_events = (events & TWAI_HAL_EVENT_TX_DONE_MASK) >> __builtin_ctz(TWAI_HAL_EVENT_TX0_DONE); + while (tx_done_events) { + uint32_t slot_event = tx_done_events & -tx_done_events; // get the lowest event bit + uint8_t tx_idx = __builtin_ctz(slot_event) / 2; + assert((tx_idx < twai_ctx->tx_slot_num) && twai_ctx->p_curr_tx[tx_idx]); + if (twai_ctx->cbs.on_tx_done) { + twai_tx_done_event_data_t tx_ev = { + .is_tx_success = (events & TWAI_HAL_EVENT_TX_SUCC_SLOT(tx_idx)), // find 'on_error_cb' if not success + .done_tx_frame = twai_ctx->p_curr_tx[tx_idx], + }; + do_yield |= twai_ctx->cbs.on_tx_done(&twai_ctx->api_base, &tx_ev, twai_ctx->user_data); + } + twai_ctx->p_curr_tx[tx_idx] = NULL; + tx_done_events &= ~slot_event; } - // start a new TX - if ((atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF) && xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &do_yield)) { - _node_start_trans(twai_ctx); - } else { - atomic_store(&twai_ctx->hw_busy, false); - if (atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF) { - // only when node is not in busoff here, means tx is finished + + // start a new TX batch only when all hardware TX buffers from this batch are done + if (_node_is_tx_all_done(twai_ctx) && (atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF)) { + if (!_node_start_tx_batch_from_isr(twai_ctx, &do_yield)) { + _node_mark_tx_idle(twai_ctx); xEventGroupSetBitsFromISR(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT, &do_yield); } } @@ -306,9 +345,7 @@ static void _node_destroy(twai_onchip_ctx_t *twai_ctx) if (twai_ctx->timer_intr_hdl) { esp_intr_free(twai_ctx->timer_intr_hdl); } - if (twai_ctx->tx_mount_queue) { - vQueueDeleteWithCaps(twai_ctx->tx_mount_queue); - } + twai_frame_queue_del(twai_ctx->tx_queue); if (twai_ctx->event_group) { vEventGroupDeleteWithCaps(twai_ctx->event_group); } @@ -449,7 +486,11 @@ static esp_err_t _node_enable(twai_node_handle_t node) atomic_store(&twai_ctx->state, hw_state); // continuing the transaction if there be if (atomic_load(&twai_ctx->hw_busy) && hw_state != TWAI_ERROR_BUS_OFF) { - _node_start_trans(twai_ctx); + for (uint8_t tx_idx = 0; tx_idx < twai_ctx->tx_slot_num; tx_idx++) { + if (twai_ctx->p_curr_tx[tx_idx]) { + _node_start_trans(twai_ctx, tx_idx); + } + } } ESP_RETURN_ON_ERROR(esp_intr_enable(twai_ctx->intr_hdl), TAG, "enable interrupt failed"); return ESP_OK; @@ -540,7 +581,7 @@ static esp_err_t _node_get_status(twai_node_handle_t node, twai_node_status_t *s status_ret->state = atomic_load(&twai_ctx->state); status_ret->tx_error_count = twai_hal_get_tec(twai_ctx->hal); status_ret->rx_error_count = twai_hal_get_rec(twai_ctx->hal); - status_ret->tx_queue_remaining = uxQueueSpacesAvailable(twai_ctx->tx_mount_queue); + status_ret->tx_queue_remaining = twai_frame_queue_get_free_space(twai_ctx->tx_queue); } if (record_ret) { *record_ret = twai_ctx->history; @@ -564,48 +605,26 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra 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 bool false_var = false; if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) { - twai_ctx->p_curr_tx = frame; - _node_start_trans(twai_ctx); + twai_ctx->p_curr_tx[0] = frame; // here only has one frame, using slot 0 + _node_start_trans(twai_ctx, 0); } else { // 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"); - } + ESP_RETURN_ON_ERROR_ISR(twai_frame_queue_push_safe(twai_ctx->tx_queue, frame, frame->tx_queue_priority, 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)) { - 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); + if (twai_frame_queue_pop_safe(twai_ctx->tx_queue, &twai_ctx->p_curr_tx[0]) == ESP_OK) { + _node_start_trans(twai_ctx, 0); } else { // any reason here means frame already taken and maybe finished by fast hardware, so back `hw_busy` to false atomic_store(&twai_ctx->hw_busy, false); } } - - // Handle ISR yield if required - if (is_isr_context && yield_required) { - portYIELD_FROM_ISR(); - } } return ESP_OK; } @@ -616,9 +635,9 @@ static esp_err_t _node_wait_tx_all_done(twai_node_handle_t node, int timeout) TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout); ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off"); - // either hw_busy or tx_mount_queue is not empty, means tx is not finished + // either hw_busy or tx_queue is not empty, means tx is not finished // otherwise, hardware is idle, return immediately - if (atomic_load(&twai_ctx->hw_busy) || uxQueueMessagesWaiting(twai_ctx->tx_mount_queue)) { + if (atomic_load(&twai_ctx->hw_busy) || twai_frame_queue_get_count(twai_ctx->tx_queue)) { //wait for idle event bit but without clear it, every tasks block here can be waked up if (TWAI_IDLE_EVENT_BIT != xEventGroupWaitBits(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT, pdFALSE, pdFALSE, ticks_to_wait)) { return ESP_ERR_TIMEOUT; @@ -674,9 +693,11 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa // state is in bus_off before enabled atomic_store(&node->state, TWAI_ERROR_BUS_OFF); - node->tx_mount_queue = xQueueCreateWithCaps(node_config->tx_queue_depth, sizeof(twai_frame_t *), TWAI_MALLOC_CAPS); + if (!node_config->flags.enable_listen_only) { + ESP_GOTO_ON_ERROR(twai_frame_queue_new(&node->tx_queue, node_config->tx_queue_depth, TWAI_MALLOC_CAPS), err, TAG, "no_mem"); + } node->event_group = xEventGroupCreateWithCaps(TWAI_MALLOC_CAPS); - ESP_GOTO_ON_FALSE((node->tx_mount_queue && node->event_group) || node_config->flags.enable_listen_only, ESP_ERR_NO_MEM, err, TAG, "no_mem"); + ESP_GOTO_ON_FALSE(node->event_group, ESP_ERR_NO_MEM, err, TAG, "no_mem"); uint32_t intr_flags = TWAI_INTR_ALLOC_FLAGS; intr_flags |= (node_config->intr_priority > 0) ? BIT(node_config->intr_priority) : ESP_INTR_FLAG_LOWMED; _lock_acquire(&s_platform.intr_mutex); // lock to prevent twai_intr and timer_intr registered to different cpu then triggered at the same time @@ -757,6 +778,11 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa .enable_loopback = node_config->flags.enable_loopback, }; ESP_GOTO_ON_FALSE(twai_hal_init(node->hal, &hal_config), ESP_ERR_INVALID_STATE, err, TAG, "hardware not in reset state"); + node->tx_slot_num = twai_hal_get_tx_slot_num(node->hal); + if (node->tx_slot_num > TWAI_HAL_TX_BUFFER_SLOT_NUM) { + ESP_LOGW(TAG, "HW TX slot num (%d) is greater than supported, only using %d slots", node->tx_slot_num, TWAI_HAL_TX_BUFFER_SLOT_NUM); + node->tx_slot_num = TWAI_HAL_TX_BUFFER_SLOT_NUM; + } // Configure bus timing ESP_GOTO_ON_ERROR(_node_calc_set_bit_timing(&node->api_base, &node_config->bit_timing, &node_config->data_timing), err, TAG, "bitrate error"); // Configure GPIO diff --git a/components/esp_driver_twai/include/esp_private/twai_frame_queue.h b/components/esp_driver_twai/include/esp_private/twai_frame_queue.h new file mode 100644 index 00000000000..961b738b7d2 --- /dev/null +++ b/components/esp_driver_twai/include/esp_private/twai_frame_queue.h @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_twai_types.h" + +//////////////////////////////////////////////////////////////////// +// !! This queue is ONLY for TWAI driver internal use // +//////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct twai_frame_queue_s *twai_frame_queue_t; + +/** + * @brief Initialize a TWAI frame priority queue. + * + * Items are ordered by priority first. For equal priority values, items are popped in push order. + */ +esp_err_t twai_frame_queue_new(twai_frame_queue_t *queue, size_t capacity, uint32_t mem_caps); + +/** + * @brief Release resources used by a TWAI frame priority queue. + * + * @return + * - ESP_OK: Queue was released successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + */ +esp_err_t twai_frame_queue_del(twai_frame_queue_t queue); + +/** + * @brief Push an item with priority from task context. + * + * @return + * - ESP_OK: Item was queued successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_TIMEOUT: No free slot became available before timeout + */ +esp_err_t twai_frame_queue_push(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms); + +/** + * @brief Push an item with priority from ISR context. + * + * @return + * - ESP_OK: Item was queued successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_TIMEOUT: Queue is full + */ +esp_err_t twai_frame_queue_push_from_isr(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, bool *task_woken); + +/** + * @brief Push an item with priority from task or ISR context. + * + * @return + * - ESP_OK: Item was queued successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_TIMEOUT: Queue is full or no free slot became available before timeout + */ +esp_err_t twai_frame_queue_push_safe(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms); + +/** + * @brief Pop the highest-priority item from task context. + * + * @return + * - ESP_OK: Item was popped successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_NOT_FOUND: Queue is empty + */ +esp_err_t twai_frame_queue_pop(twai_frame_queue_t queue, const twai_frame_t **data); + +/** + * @brief Pop the highest-priority item from ISR context. + * + * @return + * - ESP_OK: Item was popped successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_NOT_FOUND: Queue is empty + */ +esp_err_t twai_frame_queue_pop_from_isr(twai_frame_queue_t queue, const twai_frame_t **data, bool *task_woken); + +/** + * @brief Pop the highest-priority item from task or ISR context. + * + * @return + * - ESP_OK: Item was popped successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_NOT_FOUND: Queue is empty + */ +esp_err_t twai_frame_queue_pop_safe(twai_frame_queue_t queue, const twai_frame_t **data); + +/** + * @brief Get the current number of queued items. + */ +size_t twai_frame_queue_get_count(twai_frame_queue_t queue); + +/** + * @brief Get the number of available queue slots. + */ +size_t twai_frame_queue_get_free_space(twai_frame_queue_t queue); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_twai/include/esp_twai_types.h b/components/esp_driver_twai/include/esp_twai_types.h index 8690153556b..f09d642e039 100644 --- a/components/esp_driver_twai/include/esp_twai_types.h +++ b/components/esp_driver_twai/include/esp_twai_types.h @@ -33,6 +33,7 @@ typedef struct { twai_frame_header_t header; /**< message attribute/metadata, exclude data buffer*/ uint8_t *buffer; /**< buffer address for tx and rx message data*/ size_t buffer_len; /**< buffer length of provided data buffer pointer, in bytes.*/ + uint8_t tx_queue_priority; /**< Frame priority, range [0, 255], a frame with higher priority value will be picked first from the queue */ } twai_frame_t; /** diff --git a/components/esp_driver_twai/linker.lf b/components/esp_driver_twai/linker.lf index 2fcbcef5929..08b6d79d4fd 100644 --- a/components/esp_driver_twai/linker.lf +++ b/components/esp_driver_twai/linker.lf @@ -6,10 +6,20 @@ entries: esp_twai_onchip: _node_isr_main (noflash) esp_twai_onchip: _node_start_trans (noflash) esp_twai_onchip: _node_parse_rx (noflash) + esp_twai_onchip: _node_start_tx_batch_from_isr (noflash) + esp_twai_onchip: _node_mark_tx_idle (noflash) + esp_twai_onchip: _node_is_tx_all_done (noflash) + twai_frame_queue: twai_frame_queue_pop_from_isr (noflash) if TWAI_IO_FUNC_IN_IRAM = y: esp_twai_onchip: _node_queue_tx (noflash) esp_twai: twai_node_transmit (noflash) + twai_frame_queue: twai_frame_queue_push (noflash) + twai_frame_queue: twai_frame_queue_push_from_isr (noflash) + twai_frame_queue: twai_frame_queue_push_safe (noflash) + twai_frame_queue: twai_frame_queue_pop (noflash) + twai_frame_queue: twai_frame_queue_pop_from_isr (noflash) + twai_frame_queue: twai_frame_queue_pop_safe (noflash) [mapping:twai_hal] archive: libesp_hal_twai.a diff --git a/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt b/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt index 24c3db7b0fb..a5f4b242223 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt +++ b/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt @@ -1,7 +1,7 @@ set(srcs "test_app_main.c") if(CONFIG_SOC_TWAI_SUPPORTED) - list(APPEND srcs "test_twai_common.cpp" "test_twai_network.cpp") + list(APPEND srcs "test_twai_common.cpp" "test_twai_network.cpp" "test_twai_queue.cpp") if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED) list(APPEND srcs "test_twai_sleep.c") endif() diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp index fa10bc71536..05d76c77bec 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp @@ -194,12 +194,12 @@ TEST_CASE("twai fd transmit time (loopback)", "[twai]") } uint64_t predict_time_ms = (uint64_t)trans_num * arb_bits * 1000 / node_config.bit_timing.bitrate; predict_time_ms += (uint64_t)trans_num * data_bits * 1000 / node_config.data_timing.bitrate; - predict_time_ms += (trans_num * 20) / 1000; // add about 20 us interrupt overhead per frame + predict_time_ms += (trans_num * 10) / 1000; // add about 10 us interrupt overhead per frame #if CONFIG_COMPILER_OPTIMIZATION_NONE - predict_time_ms += (trans_num * 10) / 1000; // non optimized slow code + predict_time_ms += (trans_num * 5) / 1000; // non optimized slow code #endif #if CONFIG_PM_DFS_INIT_AUTO - predict_time_ms += (trans_num * 35) / 1000; // slow cpu + predict_time_ms += (trans_num * 10) / 1000; // slow cpu #endif //waiting pkg receive finish diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_queue.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_queue.cpp new file mode 100644 index 00000000000..53b3030515d --- /dev/null +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_queue.cpp @@ -0,0 +1,282 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_twai_types.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "unity.h" +#include "esp_private/twai_frame_queue.h" + +typedef struct { + int value; + uint32_t priority; +} twai_frame_queue_test_item_t; + +static void test_twai_frame_queue_print_header(const char *title) +{ + printf("\n%s\n", title); + printf("+-------+----------+\n"); + printf("| value | priority |\n"); + printf("+-------+----------+\n"); +} + +static void test_twai_frame_queue_print_row(const twai_frame_queue_test_item_t *item) +{ + printf("| %5d | %8" PRIu32 " |\n", item->value, item->priority); +} + +static void test_twai_frame_queue_print_footer(void) +{ + printf("+-------+----------+\n"); +} + +static void test_twai_frame_queue_pop_in_order(twai_frame_queue_t queue, size_t expected_count, const char *title) +{ + const twai_frame_t *item = NULL; + const twai_frame_queue_test_item_t *last_item = NULL; + + test_twai_frame_queue_print_header(title); + for (size_t i = 0; i < expected_count; i++) { + TEST_ESP_OK(twai_frame_queue_pop(queue, &item)); + const twai_frame_queue_test_item_t *cur_item = (const twai_frame_queue_test_item_t *) item; + + test_twai_frame_queue_print_row(cur_item); + if (last_item) { + TEST_ASSERT_LESS_OR_EQUAL(last_item->priority, cur_item->priority); + if (cur_item->priority == last_item->priority) { + TEST_ASSERT_GREATER_THAN(last_item->value, cur_item->value); + } + } + + TEST_ASSERT_EQUAL(expected_count - i - 1, twai_frame_queue_get_count(queue)); + last_item = cur_item; + } + test_twai_frame_queue_print_footer(); +} + +TEST_CASE("test twai frame priority queue", "[twai]") +{ + twai_frame_queue_t test_q = NULL; + const twai_frame_t *item = NULL; + twai_frame_queue_test_item_t test_items[] = { + { 0, 1 }, { 1, 4 }, { 2, 2 }, { 3, 5 }, + { 4, 3 }, { 5, 5 }, { 6, 1 }, { 7, 4 }, + { 8, 2 }, { 9, 3 }, { 10, 5 }, { 11, 0 }, + { 12, 4 }, { 13, 2 }, { 14, 3 }, { 15, 1 }, + }; + twai_frame_queue_test_item_t overflow = { 16, 6 }; + const size_t capacity = sizeof(test_items) / sizeof(test_items[0]); + + TEST_ESP_OK(twai_frame_queue_new(&test_q, capacity, MALLOC_CAP_DEFAULT)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_print_header("Push all test data"); + for (size_t i = 0; i < capacity; i++) { + TEST_ESP_OK(twai_frame_queue_push(test_q, (const twai_frame_t *) &test_items[i], test_items[i].priority, 0)); + test_twai_frame_queue_print_row(&test_items[i]); + TEST_ASSERT_EQUAL(i + 1, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity - i - 1, twai_frame_queue_get_free_space(test_q)); + } + test_twai_frame_queue_print_footer(); + + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, twai_frame_queue_push(test_q, (const twai_frame_t *) &overflow, 6, 0)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_pop_in_order(test_q, capacity, "Pop all test data"); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, twai_frame_queue_pop(test_q, &item)); + + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + TEST_ESP_OK(twai_frame_queue_del(test_q)); +} + +TEST_CASE("test twai queue mixed pop and push", "[twai]") +{ + twai_frame_queue_t test_q = NULL; + const twai_frame_t *item = NULL; + twai_frame_queue_test_item_t test_items[] = { + { 0, 1 }, + { 1, 5 }, + { 2, 3 }, + { 3, 5 }, + { 4, 2 }, + { 5, 4 }, + { 6, 6 }, + { 7, 3 }, + { 8, 4 }, + }; + const size_t first_push_count = 6; + const size_t capacity = 8; + const size_t remaining_count = sizeof(test_items) / sizeof(test_items[0]) - 2; + + TEST_ESP_OK(twai_frame_queue_new(&test_q, capacity, MALLOC_CAP_DEFAULT)); + + test_twai_frame_queue_print_header("Initial push before refill"); + for (size_t i = 0; i < first_push_count; i++) { + TEST_ESP_OK(twai_frame_queue_push(test_q, (const twai_frame_t *) &test_items[i], test_items[i].priority, 0)); + test_twai_frame_queue_print_row(&test_items[i]); + } + test_twai_frame_queue_print_footer(); + TEST_ASSERT_EQUAL(first_push_count, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity - first_push_count, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_print_header("Pop before refill"); + TEST_ESP_OK(twai_frame_queue_pop(test_q, &item)); + test_twai_frame_queue_print_row((const twai_frame_queue_test_item_t *) item); + TEST_ASSERT_EQUAL_PTR(&test_items[1], item); + TEST_ESP_OK(twai_frame_queue_pop(test_q, &item)); + test_twai_frame_queue_print_row((const twai_frame_queue_test_item_t *) item); + TEST_ASSERT_EQUAL_PTR(&test_items[3], item); + test_twai_frame_queue_print_footer(); + TEST_ASSERT_EQUAL(4, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(4, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_print_header("Push after partial pop"); + for (size_t i = first_push_count; i < sizeof(test_items) / sizeof(test_items[0]); i++) { + TEST_ESP_OK(twai_frame_queue_push(test_q, (const twai_frame_t *) &test_items[i], test_items[i].priority, 0)); + test_twai_frame_queue_print_row(&test_items[i]); + } + test_twai_frame_queue_print_footer(); + TEST_ASSERT_EQUAL(remaining_count, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(1, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_pop_in_order(test_q, remaining_count, "Pop after refill"); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, twai_frame_queue_pop(test_q, &item)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + TEST_ESP_OK(twai_frame_queue_del(test_q)); +} + +typedef struct { + twai_frame_queue_t queue; + twai_frame_queue_test_item_t *items; + bool *seen; + size_t total; + atomic_int push_tasks_done; + atomic_int pop_count; + atomic_bool failed; +} twai_frame_queue_concurrent_ctx_t; + +typedef struct { + twai_frame_queue_concurrent_ctx_t *ctx; + size_t begin; + size_t end; +} twai_frame_queue_push_args_t; + +static void twai_frame_queue_push_task(void *arg) +{ + twai_frame_queue_push_args_t *args = (twai_frame_queue_push_args_t *) arg; + twai_frame_queue_concurrent_ctx_t *ctx = args->ctx; + + printf("%s started\n", pcTaskGetName(NULL)); + for (size_t i = args->begin; i < args->end; i++) { + if (atomic_load(&ctx->failed)) { + vTaskDelete(NULL); + } + TEST_ESP_OK(twai_frame_queue_push(ctx->queue, (const twai_frame_t *) &ctx->items[i], ctx->items[i].priority, portMAX_DELAY)); + vTaskDelay(1); + } + atomic_fetch_add(&ctx->push_tasks_done, 1); + vTaskDelete(NULL); +} + +static void twai_frame_queue_pop_task(void *arg) +{ + twai_frame_queue_concurrent_ctx_t *ctx = (twai_frame_queue_concurrent_ctx_t *) arg; + + printf("%s started\n", pcTaskGetName(NULL)); + const twai_frame_t *item = NULL; + + while (atomic_load(&ctx->pop_count) < (int) ctx->total) { + if (twai_frame_queue_pop(ctx->queue, &item) == ESP_OK) { + const twai_frame_queue_test_item_t *cur = (const twai_frame_queue_test_item_t *) item; + int value = cur->value; + + if (value < 0 || (size_t) value >= ctx->total || ctx->seen[value]) { + atomic_store(&ctx->failed, true); + vTaskDelete(NULL); + } + ctx->seen[value] = true; + atomic_fetch_add(&ctx->pop_count, 1); + } else if (atomic_load(&ctx->push_tasks_done) == 2 && twai_frame_queue_get_count(ctx->queue) == 0) { + break; + } else { + vTaskDelay(1); + } + } + vTaskDelete(NULL); +} + +TEST_CASE("test twai queue concurrent push and pop", "[twai]") +{ + twai_frame_queue_t test_q = NULL; + const size_t item_count = 500; + const size_t capacity = 4; + twai_frame_queue_test_item_t *test_items = (twai_frame_queue_test_item_t *) malloc(item_count * sizeof(twai_frame_queue_test_item_t)); + bool seen[item_count] = {}; + twai_frame_queue_concurrent_ctx_t ctx = {}; + twai_frame_queue_push_args_t push_args0 = {}; + twai_frame_queue_push_args_t push_args1 = {}; + + for (size_t i = 0; i < item_count; i++) { + test_items[i].value = (int) i; + test_items[i].priority = (uint32_t)((i * 7 + 3) % 6); + } + + ctx.items = test_items; + ctx.seen = seen; + ctx.total = item_count; + + push_args0.ctx = &ctx; + push_args0.begin = 0; + push_args0.end = item_count / 2; + + push_args1.ctx = &ctx; + push_args1.begin = item_count / 2; + push_args1.end = item_count; + + TEST_ESP_OK(twai_frame_queue_new(&test_q, capacity, MALLOC_CAP_DEFAULT)); + ctx.queue = test_q; + + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(twai_frame_queue_pop_task, "twai_q_pop", 4096, &ctx, 5, NULL)); + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(twai_frame_queue_push_task, "twai_q_push0", 4096, &push_args0, 4, NULL)); + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(twai_frame_queue_push_task, "twai_q_push1", 4096, &push_args1, 4, NULL)); + + TickType_t start = xTaskGetTickCount(); + while (atomic_load(&ctx.push_tasks_done) < 2 || atomic_load(&ctx.pop_count) < (int) item_count) { + TEST_ASSERT_FALSE(atomic_load(&ctx.failed)); + if ((xTaskGetTickCount() - start) > pdMS_TO_TICKS(5000)) { + TEST_FAIL_MESSAGE("concurrent push/pop timed out"); + } + vTaskDelay(1); + } + + TEST_ASSERT_FALSE(atomic_load(&ctx.failed)); + TEST_ASSERT_EQUAL(2, atomic_load(&ctx.push_tasks_done)); + printf("pop %d items\n", atomic_load(&ctx.pop_count)); + TEST_ASSERT_EQUAL((int) item_count, atomic_load(&ctx.pop_count)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + for (size_t i = 0; i < item_count; i++) { + TEST_ASSERT_TRUE(seen[i]); + } + + printf("test finished\n"); + free(test_items); + TEST_ESP_OK(twai_frame_queue_del(test_q)); + vTaskDelay(10); // wait for tasks to be deleted +} diff --git a/components/esp_driver_twai/twai_frame_queue.c b/components/esp_driver_twai/twai_frame_queue.c new file mode 100644 index 00000000000..be453601de4 --- /dev/null +++ b/components/esp_driver_twai/twai_frame_queue.c @@ -0,0 +1,277 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_attr.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_private/twai_frame_queue.h" + +/** + * @brief Priority queue item for TWAI frame pointers. + * + * The queue stores TWAI frame pointers and orders them by priority. + * Higher priority values are popped first; equal priorities keep FIFO order. + */ +typedef struct { + const twai_frame_t *data; /**< TWAI frame pointer stored in the queue */ + uint32_t priority; /**< Higher value is dequeued first */ + uint64_t seq; /**< Sequence number used to keep FIFO order for equal priority */ +} twai_frame_queue_item_t; + +/** + * @brief Priority queue used by the TWAI driver to schedule queued frames. + */ +struct twai_frame_queue_s { + portMUX_TYPE spinlock; /**< Protects heap storage in task and ISR context */ + SemaphoreHandle_t spaces_sem; /**< Counts available queue slots */ + twai_frame_queue_item_t *items; /**< Binary heap storage */ + size_t capacity; /**< Maximum number of items */ + size_t count; /**< Current number of queued items */ + uint64_t next_seq; /**< Next sequence number assigned on push */ +}; + +static inline IRAM_ATTR bool twai_frame_queue_item_greater(const twai_frame_queue_item_t *a, const twai_frame_queue_item_t *b) +{ + if (a->priority != b->priority) { + return a->priority > b->priority; + } + return a->seq < b->seq; +} + +static inline IRAM_ATTR void twai_frame_queue_swap_item(twai_frame_queue_item_t *a, twai_frame_queue_item_t *b) +{ + twai_frame_queue_item_t tmp = *a; + *a = *b; + *b = tmp; +} + +static IRAM_ATTR void twai_frame_queue_sift_up(twai_frame_queue_t queue, size_t index) +{ + while (index > 0) { + size_t parent = (index - 1) / 2; + if (!twai_frame_queue_item_greater(&queue->items[index], &queue->items[parent])) { + break; + } + twai_frame_queue_swap_item(&queue->items[index], &queue->items[parent]); + index = parent; + } +} + +static IRAM_ATTR void twai_frame_queue_sift_down(twai_frame_queue_t queue, size_t index) +{ + while (true) { + size_t left = index * 2 + 1; + size_t right = left + 1; + size_t highest = index; + + if ((left < queue->count) && twai_frame_queue_item_greater(&queue->items[left], &queue->items[highest])) { + highest = left; + } + if ((right < queue->count) && twai_frame_queue_item_greater(&queue->items[right], &queue->items[highest])) { + highest = right; + } + if (highest == index) { + break; + } + twai_frame_queue_swap_item(&queue->items[index], &queue->items[highest]); + index = highest; + } +} + +static inline IRAM_ATTR void twai_frame_queue_push_locked(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority) +{ + size_t index = queue->count++; + queue->items[index] = (twai_frame_queue_item_t) { + .data = data, + .priority = priority, + .seq = queue->next_seq++, + }; + twai_frame_queue_sift_up(queue, index); +} + +static inline IRAM_ATTR bool twai_frame_queue_pop_locked(twai_frame_queue_t queue, const twai_frame_t **data) +{ + if (queue->count == 0) { + return false; + } + + *data = queue->items[0].data; + queue->count--; + if (queue->count > 0) { + queue->items[0] = queue->items[queue->count]; + twai_frame_queue_sift_down(queue, 0); + } + return true; +} + +esp_err_t twai_frame_queue_new(twai_frame_queue_t *queue, size_t capacity, uint32_t mem_caps) +{ + if (!queue || !capacity) { + return ESP_ERR_INVALID_ARG; + } + + twai_frame_queue_t q_ctx = heap_caps_calloc(1, sizeof(struct twai_frame_queue_s), mem_caps); + if (!q_ctx) { + return ESP_ERR_NO_MEM; + } + q_ctx->items = heap_caps_calloc(capacity, sizeof(twai_frame_queue_item_t), mem_caps); + if (!q_ctx->items) { + heap_caps_free(q_ctx); + return ESP_ERR_NO_MEM; + } + q_ctx->spaces_sem = xSemaphoreCreateCountingWithCaps(capacity, capacity, mem_caps); + if (!q_ctx->spaces_sem) { + heap_caps_free(q_ctx->items); + heap_caps_free(q_ctx); + return ESP_ERR_NO_MEM; + } + q_ctx->spinlock = (portMUX_TYPE) portMUX_INITIALIZER_UNLOCKED; + q_ctx->capacity = capacity; + + *queue = q_ctx; + return ESP_OK; +} + +esp_err_t twai_frame_queue_del(twai_frame_queue_t queue) +{ + if (!queue) { + return ESP_ERR_INVALID_ARG; + } + if (queue->spaces_sem) { + vSemaphoreDeleteWithCaps(queue->spaces_sem); + } + if (queue->items) { + heap_caps_free(queue->items); + } + heap_caps_free(queue); + return ESP_OK; +} + +esp_err_t twai_frame_queue_push(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms) +{ + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + TickType_t ticks_to_wait = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + if (xSemaphoreTake(queue->spaces_sem, ticks_to_wait) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + portENTER_CRITICAL(&queue->spinlock); + twai_frame_queue_push_locked(queue, data, priority); + portEXIT_CRITICAL(&queue->spinlock); + return ESP_OK; +} + +esp_err_t twai_frame_queue_push_from_isr(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, bool *task_woken) +{ + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + if (xSemaphoreTakeFromISR(queue->spaces_sem, (BaseType_t *)task_woken) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + portENTER_CRITICAL_ISR(&queue->spinlock); + twai_frame_queue_push_locked(queue, data, priority); + portEXIT_CRITICAL_ISR(&queue->spinlock); + return ESP_OK; +} + +esp_err_t twai_frame_queue_push_safe(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms) +{ + if (xPortInIsrContext()) { + bool task_woken = false; + esp_err_t ret = twai_frame_queue_push_from_isr(queue, data, priority, &task_woken); + if (task_woken) { + portYIELD_FROM_ISR(); + } + return ret; + } + TickType_t ticks_to_wait = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return twai_frame_queue_push(queue, data, priority, ticks_to_wait); +} + +esp_err_t twai_frame_queue_pop(twai_frame_queue_t queue, const twai_frame_t **data) +{ + bool ret; + + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + portENTER_CRITICAL(&queue->spinlock); + ret = twai_frame_queue_pop_locked(queue, data); + portEXIT_CRITICAL(&queue->spinlock); + + if (ret) { + xSemaphoreGive(queue->spaces_sem); + } + return ret ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t twai_frame_queue_pop_from_isr(twai_frame_queue_t queue, const twai_frame_t **data, bool *task_woken) +{ + bool ret; + + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + portENTER_CRITICAL_ISR(&queue->spinlock); + ret = twai_frame_queue_pop_locked(queue, data); + portEXIT_CRITICAL_ISR(&queue->spinlock); + + if (ret) { + xSemaphoreGiveFromISR(queue->spaces_sem, (BaseType_t *)task_woken); + } + return ret ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t twai_frame_queue_pop_safe(twai_frame_queue_t queue, const twai_frame_t **data) +{ + if (xPortInIsrContext()) { + bool task_woken = false; + esp_err_t ret = twai_frame_queue_pop_from_isr(queue, data, &task_woken); + if (task_woken) { + portYIELD_FROM_ISR(); + } + return ret; + } + return twai_frame_queue_pop(queue, data); +} + +size_t twai_frame_queue_get_count(twai_frame_queue_t queue) +{ + size_t count; + + if (!queue || !queue->spaces_sem) { + return 0; + } + + portENTER_CRITICAL(&queue->spinlock); + count = queue->count; + portEXIT_CRITICAL(&queue->spinlock); + return count; +} + +size_t twai_frame_queue_get_free_space(twai_frame_queue_t queue) +{ + size_t count; + + if (!queue || !queue->spaces_sem) { + return 0; + } + + portENTER_CRITICAL(&queue->spinlock); + count = queue->count; + portEXIT_CRITICAL(&queue->spinlock); + return queue->capacity - count; +} diff --git a/components/esp_driver_twai/twai_private.h b/components/esp_driver_twai/twai_private.h index db4364163c3..ad6470af9e1 100644 --- a/components/esp_driver_twai/twai_private.h +++ b/components/esp_driver_twai/twai_private.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "sdkconfig.h" #if CONFIG_TWAI_ENABLE_DEBUG_LOG diff --git a/components/esp_hal_twai/esp32/include/hal/twai_ll.h b/components/esp_hal_twai/esp32/include/hal/twai_ll.h index 02bb17750db..b83d4cf8003 100644 --- a/components/esp_hal_twai/esp32/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -701,6 +701,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h b/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h index 5f52f7c9b9c..ba61a769617 100644 --- a/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -627,6 +627,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h b/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h index 3baad4e6f7e..62fcf6061d1 100644 --- a/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h @@ -48,6 +48,10 @@ #define TWAIFD_LL_TX_CMD_READY TWAIFD_TXCR // Set tx buffer to "Ready" state #define TWAIFD_LL_TX_CMD_ABORT TWAIFD_TXCA // Set tx buffer to "Aborted" state +#define TWAIFD_LL_TX_STATUS_SUCCESS 0x4 // TX buffer transmitted successfully +#define TWAIFD_LL_TX_STATUS_FAILED 0x6 // TX buffer transmission failed +#define TWAIFD_LL_TX_STATUS_ABORTED 0x7 // TX buffer transmission aborted + #define TWAIFD_LL_HW_CMD_RST_ERR_CNT TWAIFD_ERCRST // Error Counters Reset #define TWAIFD_LL_HW_CMD_RST_RX_CNT TWAIFD_RXFCRST // Clear RX bus traffic counter #define TWAIFD_LL_HW_CMD_RST_TX_CNT TWAIFD_TXFCRST // Clear TX bus traffic counter @@ -665,11 +669,10 @@ static inline uint32_t twaifd_ll_get_tx_buffer_total(twaifd_dev_t *hw) * @param buffer_idx Index of the TX buffer (0-7). * @return The status of the selected TX buffer. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_tx_buffer_status(twaifd_dev_t *hw, uint8_t buffer_idx) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid - uint32_t reg_val = hw->tx_status.val; - return reg_val & (TWAIFD_TX2S_V << (TWAIFD_TX2S_S * buffer_idx)); // Get status for buffer + return (hw->tx_status.val >> (TWAIFD_TX2S_S * buffer_idx)) & TWAIFD_TX2S_V; } /** @@ -700,7 +703,6 @@ static inline void twaifd_ll_set_tx_buffer_cmd(twaifd_dev_t *hw, uint8_t buffer_ */ static inline void twaifd_ll_set_tx_buffer_priority(twaifd_dev_t *hw, uint8_t buffer_idx, uint32_t priority) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid uint32_t reg_val = hw->tx_priority.val; reg_val &= ~(TWAIFD_TXT1P_V << (TWAIFD_TXT2P_S * buffer_idx)); // Clear old priority reg_val |= priority << (TWAIFD_TXT2P_S * buffer_idx); // Set new priority diff --git a/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h b/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h index bf5f7b71455..adc318e5261 100644 --- a/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -637,6 +637,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h b/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h index 701b5e7f913..4b868b55503 100644 --- a/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -615,6 +615,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h b/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h index 2e9001fd585..3d3ded42300 100644 --- a/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -615,6 +615,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h b/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h index 1874ae9fc6d..a3b3f757c7e 100644 --- a/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h @@ -48,6 +48,10 @@ #define TWAIFD_LL_TX_CMD_READY TWAIFD_TXCR // Set tx buffer to "Ready" state #define TWAIFD_LL_TX_CMD_ABORT TWAIFD_TXCA // Set tx buffer to "Aborted" state +#define TWAIFD_LL_TX_STATUS_SUCCESS 0x4 // TX buffer transmitted successfully +#define TWAIFD_LL_TX_STATUS_FAILED 0x6 // TX buffer transmission failed +#define TWAIFD_LL_TX_STATUS_ABORTED 0x7 // TX buffer transmission aborted + #define TWAIFD_LL_HW_CMD_RST_ERR_CNT TWAIFD_ERCRST // Error Counters Reset #define TWAIFD_LL_HW_CMD_RST_RX_CNT TWAIFD_RXFCRST // Clear RX bus traffic counter #define TWAIFD_LL_HW_CMD_RST_TX_CNT TWAIFD_TXFCRST // Clear TX bus traffic counter @@ -664,11 +668,10 @@ static inline uint32_t twaifd_ll_get_tx_buffer_total(twaifd_dev_t *hw) * @param buffer_idx Index of the TX buffer (0-7). * @return The status of the selected TX buffer. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_tx_buffer_status(twaifd_dev_t *hw, uint8_t buffer_idx) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid - uint32_t reg_val = hw->tx_status.val; - return reg_val & (TWAIFD_TX2S_V << (TWAIFD_TX2S_S * buffer_idx)); // Get status for buffer + return (hw->tx_status.val >> (TWAIFD_TX2S_S * buffer_idx)) & TWAIFD_TX2S_V; } /** @@ -699,7 +702,6 @@ static inline void twaifd_ll_set_tx_buffer_cmd(twaifd_dev_t *hw, uint8_t buffer_ */ static inline void twaifd_ll_set_tx_buffer_priority(twaifd_dev_t *hw, uint8_t buffer_idx, uint32_t priority) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid uint32_t reg_val = hw->tx_priority.val; reg_val &= ~(TWAIFD_TXT1P_V << (TWAIFD_TXT2P_S * buffer_idx)); // Clear old priority reg_val |= priority << (TWAIFD_TXT2P_S * buffer_idx); // Set new priority diff --git a/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h b/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h index 762cb9e4ac4..1a2ebb7e3f0 100644 --- a/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -680,6 +680,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h b/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h index abd1ffba065..d2f9d7f9ae1 100644 --- a/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -630,6 +630,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h b/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h index aad2bc7819a..5906369caba 100644 --- a/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -627,6 +627,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32s31/include/hal/twaifd_ll.h b/components/esp_hal_twai/esp32s31/include/hal/twaifd_ll.h index fff82a564be..79dc1f323fc 100644 --- a/components/esp_hal_twai/esp32s31/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32s31/include/hal/twaifd_ll.h @@ -46,6 +46,10 @@ #define TWAIFD_LL_TX_CMD_READY TWAIFD_TXCR // Set tx buffer to "Ready" state #define TWAIFD_LL_TX_CMD_ABORT TWAIFD_TXCA // Set tx buffer to "Aborted" state +#define TWAIFD_LL_TX_STATUS_SUCCESS 0x4 // TX buffer transmitted successfully +#define TWAIFD_LL_TX_STATUS_FAILED 0x6 // TX buffer transmission failed +#define TWAIFD_LL_TX_STATUS_ABORTED 0x7 // TX buffer transmission aborted + #define TWAIFD_LL_HW_CMD_RST_ERR_CNT TWAIFD_ERCRST // Error Counters Reset #define TWAIFD_LL_HW_CMD_RST_RX_CNT TWAIFD_RXFCRST // Clear RX bus traffic counter #define TWAIFD_LL_HW_CMD_RST_TX_CNT TWAIFD_TXFCRST // Clear TX bus traffic counter @@ -662,11 +666,10 @@ static inline uint32_t twaifd_ll_get_tx_buffer_total(twaifd_dev_t *hw) * @param buffer_idx Index of the TX buffer (0-7). * @return The status of the selected TX buffer. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_tx_buffer_status(twaifd_dev_t *hw, uint8_t buffer_idx) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid - uint32_t reg_val = hw->tx_status.val; - return reg_val & (TWAIFD_TX2S_V << (TWAIFD_TX2S_S * buffer_idx)); // Get status for buffer + return (hw->tx_status.val >> (TWAIFD_TX2S_S * buffer_idx)) & TWAIFD_TX2S_V; } /** @@ -697,7 +700,6 @@ static inline void twaifd_ll_set_tx_buffer_cmd(twaifd_dev_t *hw, uint8_t buffer_ */ static inline void twaifd_ll_set_tx_buffer_priority(twaifd_dev_t *hw, uint8_t buffer_idx, uint32_t priority) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid uint32_t reg_val = hw->tx_priority.val; reg_val &= ~(TWAIFD_TXT1P_V << (TWAIFD_TXT2P_S * buffer_idx)); // Clear old priority reg_val |= priority << (TWAIFD_TXT2P_S * buffer_idx); // Set new priority diff --git a/components/esp_hal_twai/include/hal/twai_hal.h b/components/esp_hal_twai/include/hal/twai_hal.h index 23bf080ab5a..5fc80557f1e 100644 --- a/components/esp_hal_twai/include/hal/twai_hal.h +++ b/components/esp_hal_twai/include/hal/twai_hal.h @@ -50,9 +50,29 @@ typedef union twai_ll_frame_buffer_t twai_hal_frame_t; #define TWAI_HAL_EVENT_BUS_ERR (1 << 7) #define TWAI_HAL_EVENT_ARB_LOST (1 << 8) #define TWAI_HAL_EVENT_RX_BUFF_FRAME (1 << 9) -#define TWAI_HAL_EVENT_TX_BUFF_FREE (1 << 10) -#define TWAI_HAL_EVENT_NEED_PERIPH_RESET (1 << 11) -#define TWAI_HAL_EVENT_TX_SUCCESS (1 << 12) +#define TWAI_HAL_EVENT_NEED_PERIPH_RESET (1 << 10) +#define TWAI_HAL_EVENT_TX0_DONE (1 << 11) +#define TWAI_HAL_EVENT_TX0_SUCCESS (1 << 12) +#define TWAI_HAL_EVENT_TX1_DONE (1 << 13) +#define TWAI_HAL_EVENT_TX1_SUCCESS (1 << 14) +#define TWAI_HAL_EVENT_TX2_DONE (1 << 15) +#define TWAI_HAL_EVENT_TX2_SUCCESS (1 << 16) +#define TWAI_HAL_EVENT_TX3_DONE (1 << 17) +#define TWAI_HAL_EVENT_TX3_SUCCESS (1 << 18) +#define TWAI_HAL_EVENT_TX4_DONE (1 << 19) +#define TWAI_HAL_EVENT_TX4_SUCCESS (1 << 20) +#define TWAI_HAL_EVENT_TX5_DONE (1 << 21) +#define TWAI_HAL_EVENT_TX5_SUCCESS (1 << 22) +#define TWAI_HAL_EVENT_TX6_DONE (1 << 23) +#define TWAI_HAL_EVENT_TX6_SUCCESS (1 << 24) +#define TWAI_HAL_EVENT_TX7_DONE (1 << 25) +#define TWAI_HAL_EVENT_TX7_SUCCESS (1 << 26) +#define TWAI_HAL_TX_BUFFER_SLOT_NUM 8 // support up to 8 TX slots in hal layer + +#define TWAI_HAL_EVENT_TX_DONE_MASK (TWAI_HAL_EVENT_TX0_DONE | TWAI_HAL_EVENT_TX1_DONE | TWAI_HAL_EVENT_TX2_DONE | TWAI_HAL_EVENT_TX3_DONE | \ + TWAI_HAL_EVENT_TX4_DONE | TWAI_HAL_EVENT_TX5_DONE | TWAI_HAL_EVENT_TX6_DONE | TWAI_HAL_EVENT_TX7_DONE) +#define TWAI_HAL_EVENT_TX_DONE_SLOT(buffer_idx) (TWAI_HAL_EVENT_TX0_DONE << ((buffer_idx) * 2)) +#define TWAI_HAL_EVENT_TX_SUCC_SLOT(buffer_idx) (TWAI_HAL_EVENT_TX0_SUCCESS << ((buffer_idx) * 2)) typedef struct { twai_soc_handle_t dev; // TWAI SOC layer handle (i.e. register base address) @@ -61,6 +81,7 @@ typedef struct { uint32_t timer_overflow_cnt; twai_error_flags_t errors; uint8_t sja1000_filter_id_type; // hardware don't check id type, check in software, 0:no_filter, 1: std_id_only, 2: ext_id_only + uint8_t tx_buffer_num; int8_t retry_cnt; bool enable_self_test; bool enable_loopback; @@ -296,6 +317,14 @@ static inline twai_error_flags_t twai_hal_get_err_flags(twai_hal_context_t *hal_ */ uint32_t twai_hal_get_rx_msg_count(twai_hal_context_t *hal_ctx); +/** + * @brief Get the number of TX buffers supported by the hardware + * + * @param hal_ctx Context of the HAL layer + * @return TX buffer count + */ +#define twai_hal_get_tx_slot_num(hal_ctx) (hal_ctx->tx_buffer_num) + /** * @brief TWAI hal transaction description type */ diff --git a/components/esp_hal_twai/twai_hal_v1.c b/components/esp_hal_twai/twai_hal_v1.c index 518f5a61c4c..8db40957686 100644 --- a/components/esp_hal_twai/twai_hal_v1.c +++ b/components/esp_hal_twai/twai_hal_v1.c @@ -50,6 +50,7 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) if (!twai_ll_is_in_reset_mode(hal_ctx->dev)) { //Must enter reset mode to write to config registers return false; } + hal_ctx->tx_buffer_num = twai_ll_get_tx_buffer_total(hal_ctx->dev); #if TWAI_LL_HAS_RX_FRAME_ISSUE || TWAI_LL_HAS_RX_FIFO_ISSUE hal_ctx->errata_ctx = (twai_hal_errata_ctx_t *)(hal_ctx + 1); //errata context is place at end of hal_ctx #endif @@ -238,10 +239,10 @@ static inline uint32_t twai_hal_decode_interrupt(twai_hal_context_t *hal_ctx) #else if (interrupts & TWAI_LL_INTR_TI) { #endif - TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX_BUFF_FREE); + TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX0_DONE); TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED); if (status & TWAI_LL_STATUS_TCS) { - TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX_SUCCESS); + TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX0_SUCCESS); } } //Error Passive Interrupt on transition from error active to passive or vice versa diff --git a/components/esp_hal_twai/twai_hal_v2.c b/components/esp_hal_twai/twai_hal_v2.c index cba5b646577..05e54ee5f7a 100644 --- a/components/esp_hal_twai/twai_hal_v2.c +++ b/components/esp_hal_twai/twai_hal_v2.c @@ -22,6 +22,7 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) hal_ctx->enable_listen_only = config->enable_listen_only; twaifd_ll_reset(hal_ctx->dev); + hal_ctx->tx_buffer_num = twaifd_ll_get_tx_buffer_total(hal_ctx->dev); twaifd_ll_enable_hw(hal_ctx->dev, false); //mode should be changed under disabled twaifd_ll_set_mode(hal_ctx->dev, config->enable_listen_only, config->enable_self_test, config->enable_loopback); twaifd_ll_set_tx_retrans_limit(hal_ctx->dev, config->retry_cnt); @@ -222,9 +223,20 @@ uint32_t twai_hal_get_events(twai_hal_context_t *hal_ctx) hal_ctx->timer_overflow_cnt ++; } if (int_stat & (TWAIFD_LL_INTR_TX_DONE)) { - hal_events |= TWAI_HAL_EVENT_TX_BUFF_FREE; - if (int_stat & TWAIFD_LL_INTR_TX_FRAME) { - hal_events |= TWAI_HAL_EVENT_TX_SUCCESS; + for (uint32_t i = 0; i < MIN(hal_ctx->tx_buffer_num, TWAI_HAL_TX_BUFFER_SLOT_NUM); i++) { + uint32_t tx_status = twaifd_ll_get_tx_buffer_status(hal_ctx->dev, i); + switch (tx_status) { + case TWAIFD_LL_TX_STATUS_SUCCESS: + hal_events |= TWAI_HAL_EVENT_TX_SUCC_SLOT(i); + __attribute__((fallthrough)); // success event must be a done event, just fallthrough + case TWAIFD_LL_TX_STATUS_FAILED: + case TWAIFD_LL_TX_STATUS_ABORTED: + hal_events |= TWAI_HAL_EVENT_TX_DONE_SLOT(i); + twaifd_ll_set_tx_buffer_cmd(hal_ctx->dev, i, TWAIFD_LL_TX_CMD_EMPTY); // clear buffer to empty state + break; + default: + break; + } } } if (int_stat & TWAIFD_LL_INTR_RX_NOT_EMPTY) { diff --git a/docs/en/api-reference/peripherals/twai.rst b/docs/en/api-reference/peripherals/twai.rst index 7bae2f93fa6..95e5329ea99 100644 --- a/docs/en/api-reference/peripherals/twai.rst +++ b/docs/en/api-reference/peripherals/twai.rst @@ -134,6 +134,7 @@ The :cpp:type:`twai_frame_t` message structure also includes other configuration - :cpp:member:`twai_frame_t::header::fdf`: Marks the frame as an FD format frame, supporting up to 64 bytes of data. - :cpp:member:`twai_frame_t::header::brs`: Enables use of a separate data-phase baud rate when transmitting. - :cpp:member:`twai_frame_t::header::esi`: For received frames, indicates the error state of the transmitting node. +- :cpp:member:`twai_frame_t::tx_queue_priority`: Local transmit queue priority. See `Transmit Queue Priority`_ for details. Receiving Messages ------------------ @@ -214,6 +215,13 @@ The TWAI driver supports transmitting messages from an Interrupt Service Routine .. 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. Similarly, the ``twai_frame_t`` structure and the memory pointed to by ``buffer`` must remain valid until the transmission is complete. You can get the completed frame by the :cpp:member:`twai_tx_done_event_data_t::done_tx_frame` pointer. +Transmit Queue Priority +----------------------- + +The TWAI driver supports local transmit queue prioritization through :cpp:member:`twai_frame_t::tx_queue_priority`. When multiple frames are pending in the driver's transmit queue, frames with a higher ``tx_queue_priority`` value are dequeued and started transmitting first. Frames with the same priority keep their enqueue order. + +This priority only affects the driver's local transmit queue. It is not transmitted on the TWAI bus and does not replace TWAI bus arbitration. If the controller has multiple hardware transmit buffers (for example, 4 hardware transmit buffers for esp32c5), the already cached frames will not be preempted by newly queued higher-priority frames. Once a frame reaches the bus, arbitration is still determined by the frame ID, where lower IDs have higher bus priority. + Bit Timing Customization ------------------------ diff --git a/docs/zh_CN/api-reference/peripherals/twai.rst b/docs/zh_CN/api-reference/peripherals/twai.rst index 30c4266471a..db519daa353 100644 --- a/docs/zh_CN/api-reference/peripherals/twai.rst +++ b/docs/zh_CN/api-reference/peripherals/twai.rst @@ -134,6 +134,7 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主 - :cpp:member:`twai_frame_t::header::fdf` 报文为 FD 格式,支持最大数据长度 64 字节。 - :cpp:member:`twai_frame_t::header::brs` 发送报文时在数据段使用独立的波特率。 - :cpp:member:`twai_frame_t::header::esi` 对于收到的报文,指示发送节点的错误状态。 +- :cpp:member:`twai_frame_t::tx_queue_priority` 本地发送队列优先级,详情请参阅 `发送队列优先级`_。 接收报文 -------- @@ -214,6 +215,13 @@ TWAI 驱动支持在中断服务程序 (ISR) 中发送报文。这对于需要 .. note:: 在 ISR 中调用 :cpp:func:`twai_node_transmit` 时,``timeout`` 参数将被忽略,函数不会阻塞。如果发送队列已满,函数将立即返回错误。应用程序需要自行处理队列已满的情况。同样,``twai_frame_t`` 及其 ``buffer`` 指向的内存必须在 **该传输** 完成之前保持有效。通过 :cpp:member:`twai_tx_done_event_data_t::done_tx_frame` 指针可得知该次完成的报文。 +发送队列优先级 +-------------- + +TWAI 驱动支持通过 :cpp:member:`twai_frame_t::tx_queue_priority` 设置本地发送队列优先级。当驱动发送队列中有多个待发送报文时,``tx_queue_priority`` 值更高的报文会优先出队开始发送。优先级相同的报文保持入队顺序发送。 + +该优先级只影响驱动的本地发送队列,不会被发送到 TWAI 总线上,也不会替代 TWAI 总线仲裁。若控制器有多个硬件发送缓存(例如 esp32c5 的 4 个硬件发送缓存),已经缓存的报文也不会被新入队的高优先级报文抢占。报文到达总线后,仲裁仍由帧 ID 决定,ID 越小,总线优先级越高。控制器已经开始发送的报文不会被新入队的高优先级报文抢占。 + 位时序自定义 ------------- diff --git a/examples/peripherals/twai/twai_network/README.md b/examples/peripherals/twai/twai_network/README.md index 4354ce25ee7..e795f3ba52f 100644 --- a/examples/peripherals/twai/twai_network/README.md +++ b/examples/peripherals/twai/twai_network/README.md @@ -17,6 +17,7 @@ This example demonstrates TWAI (Two-Wire Automotive Interface) network communica - Event-driven message handling with callbacks - Message filtering using acceptance filters in listen-only mode - Single/Burst data transmission and reception +- Local transmit queue prioritization for urgent frames - Real-time bus error and node status reporting ## Hardware Setup @@ -55,6 +56,7 @@ Navigate to: `Example Configuration` → Configure the following: |----|------|-----------|------|-------------| | 0x7FF | Heartbeat | 1 Hz | 8 bytes | Timestamp data | | 0x100 | Data | Every 10s | 1000 bytes | Test data (125 frames) | +| 0x080 | Emergency | During data burst | 0 bytes | High-priority frame inserted into the transmit queue | ## Building and Running @@ -72,9 +74,10 @@ idf.py set-target esp32 build flash monitor ``` ===================TWAI Sender Example Starting...=================== I (xxx) twai_sender: TWAI Sender started successfully -I (xxx) twai_sender: Sending messages on IDs: 0x100 (data), 0x7FF (heartbeat) +I (xxx) twai_sender: Sending messages with IDs: 0x100 (data), 0x7FF (heartbeat) I (xxx) twai_sender: Sending heartbeat message: 1234567890 I (xxx) twai_sender: Sending packet of 1000 bytes in 125 frames +I (xxx) twai_sender: Inserting Emergency message: 0x080 ``` ### Listen-Only Monitor @@ -102,6 +105,21 @@ Each program uses a buffer pool to handle incoming messages efficiently: - **Normal Mode** (Sender): Participates in bus communication, sends ACK frames - **Listen-Only Mode** (Monitor): Receives filtered messages without transmitting anything +### Transmit Queue Priority + +The sender inserts an emergency frame while a burst of data frames is pending in the transmit queue: + +```c +twai_frame_t emergency_frame = { + .header.id = TWAI_EMERGENCY_ID, + .tx_queue_priority = 10, +}; +``` + +The `tx_queue_priority` field controls the driver's local transmit queue. Frames with a higher priority value are dequeued before lower-priority frames, while frames with the same priority keep their enqueue order. This allows urgent frames, such as the emergency frame in this example, to be transmitted before queued burst data frames. + +The local queue priority is separate from TWAI bus arbitration. Once a frame is sent to the bus, arbitration is still determined by the frame ID, where lower IDs have higher bus priority. + ### Message Filtering The listen-only monitor uses hardware acceptance filters to receive only specific message IDs: @@ -133,6 +151,7 @@ Update the message ID definitions: ```c #define TWAI_DATA_ID 0x100 #define TWAI_HEARTBEAT_ID 0x7FF +#define TWAI_EMERGENCY_ID 0x080 ``` ## Use Cases diff --git a/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c b/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c index a8255752bc6..f397d1d7ad0 100644 --- a/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c +++ b/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,6 +22,7 @@ // Message IDs #define TWAI_DATA_ID 0x100 #define TWAI_HEARTBEAT_ID 0x7FF +#define TWAI_EMERGENCY_ID 0x080 #define TWAI_DATA_LEN 1000 static const char *TAG = "twai_sender"; @@ -108,6 +109,15 @@ void app_main(void) ESP_ERROR_CHECK(twai_node_transmit(sender_node, &data[i].frame, 500)); } + // Insert an emergency frame with high priority + // This frame will be transmitted before the queue remaining data frames + twai_frame_t emergency_frame = { + .header.id = TWAI_EMERGENCY_ID, + .tx_queue_priority = 10, + }; + ESP_LOGI(TAG, "Inserting Emergency message: 0x%03X", TWAI_EMERGENCY_ID); + ESP_ERROR_CHECK(twai_node_transmit(sender_node, &emergency_frame, 500)); + // Frames mounted, wait for all frames to be transmitted ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(sender_node, -1)); free(data);