diff --git a/components/driver/twai/twai.c b/components/driver/twai/twai.c index 141abe966f..11a821451c 100644 --- a/components/driver/twai/twai.c +++ b/components/driver/twai/twai.c @@ -817,7 +817,7 @@ esp_err_t twai_receive_v2(twai_handle_t handle, twai_message_t *message, TickTyp //Decode frame twai_frame_header_t header = {0}; - twai_hal_parse_frame(&rx_frame, &header, message->data, TWAI_FRAME_MAX_LEN); + twai_hal_parse_frame(p_twai_obj->hal, &rx_frame, &header, message->data, TWAI_FRAME_MAX_LEN); message->identifier = header.id; message->data_length_code = header.dlc; message->extd = header.ide; diff --git a/components/esp_driver_twai/CMakeLists.txt b/components/esp_driver_twai/CMakeLists.txt index bf4cf0ca48..e4d24b813b 100644 --- a/components/esp_driver_twai/CMakeLists.txt +++ b/components/esp_driver_twai/CMakeLists.txt @@ -6,7 +6,7 @@ endif() set(srcs "esp_twai.c") set(public_include "include") -set(priv_req esp_driver_gpio esp_pm) +set(priv_req esp_driver_gpio esp_pm esp_timer) if(CONFIG_SOC_TWAI_SUPPORTED) list(APPEND srcs "esp_twai_onchip.c") diff --git a/components/esp_driver_twai/esp_twai.c b/components/esp_driver_twai/esp_twai.c index f680a2ddcb..9d2a086b4a 100644 --- a/components/esp_driver_twai/esp_twai.c +++ b/components/esp_driver_twai/esp_twai.c @@ -50,7 +50,6 @@ uint32_t twai_node_timing_calc_param(const uint32_t source_freq, const twai_timi uint16_t prop = MAX(1, tseg_1 / 4); // prop_seg is usually shorter than tseg_1 and at least 1 tseg_1 -= prop; - out_param->quanta_resolution_hz = 0; // going to deprecated IDF-12725 out_param->brp = pre_div; out_param->prop_seg = prop; out_param->tseg_1 = tseg_1; diff --git a/components/esp_driver_twai/esp_twai_onchip.c b/components/esp_driver_twai/esp_twai_onchip.c index 5311c5953f..aa63ef6273 100644 --- a/components/esp_driver_twai/esp_twai_onchip.c +++ b/components/esp_driver_twai/esp_twai_onchip.c @@ -1,9 +1,10 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include "esp_timer.h" #include "esp_twai.h" #include "esp_twai_onchip.h" #include "esp_private/twai_interface.h" @@ -52,9 +53,12 @@ typedef struct { uint64_t gpio_reserved; twai_hal_context_t *hal; intr_handle_t intr_hdl; + intr_handle_t timer_intr_hdl; QueueHandle_t tx_mount_queue; EventGroupHandle_t event_group; twai_clock_source_t curr_clk_src; + uint32_t src_freq_hz; + uint32_t timestamp_freq_hz; uint32_t valid_fd_timing; twai_event_callbacks_t cbs; void *user_data; @@ -72,7 +76,8 @@ typedef struct { } twai_onchip_ctx_t; typedef struct twai_platform_s { - _lock_t mutex; + _lock_t ctrlr_mutex; + _lock_t intr_mutex; twai_onchip_ctx_t *nodes[SOC_TWAI_CONTROLLER_NUM]; } twai_platform_t; static twai_platform_t s_platform; @@ -80,7 +85,7 @@ static twai_platform_t s_platform; static int _ctrlr_acquire(twai_onchip_ctx_t *node) { int ctrlr_id = -1; - _lock_acquire(&s_platform.mutex); + _lock_acquire(&s_platform.ctrlr_mutex); // Check if there is a controller available for use for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) { if (s_platform.nodes[i] == NULL) { @@ -90,7 +95,7 @@ static int _ctrlr_acquire(twai_onchip_ctx_t *node) break; } } - _lock_release(&s_platform.mutex); + _lock_release(&s_platform.ctrlr_mutex); // Return the controller index or -1 return ctrlr_id; @@ -98,11 +103,11 @@ static int _ctrlr_acquire(twai_onchip_ctx_t *node) static void _ctrlr_release(int ctrlr_id) { - _lock_acquire(&s_platform.mutex); + _lock_acquire(&s_platform.ctrlr_mutex); assert(s_platform.nodes[ctrlr_id]); // Clear the node object from the controller slot s_platform.nodes[ctrlr_id] = NULL; - _lock_release(&s_platform.mutex); + _lock_release(&s_platform.ctrlr_mutex); } static esp_err_t _node_config_io(twai_onchip_ctx_t *node, const twai_onchip_node_config_t *node_config) @@ -297,6 +302,9 @@ static void _node_destroy(twai_onchip_ctx_t *twai_ctx) if (twai_ctx->intr_hdl) { esp_intr_free(twai_ctx->intr_hdl); } + 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); } @@ -338,39 +346,13 @@ static esp_err_t _node_register_callbacks(twai_node_handle_t node, const twai_ev return ESP_OK; } -static esp_err_t _node_check_timing_valid(twai_onchip_ctx_t *twai_ctx, const twai_timing_advanced_config_t *timing, uint32_t source_freq) +static esp_err_t _node_check_timing_valid(twai_onchip_ctx_t *twai_ctx, const twai_timing_advanced_config_t *timing) { - if (!timing) { - return ESP_OK; - } - ESP_RETURN_ON_FALSE(!timing->quanta_resolution_hz, ESP_ERR_INVALID_ARG, TAG, "quanta_resolution_hz is not supported"); //TODO: IDF-12725 - ESP_RETURN_ON_FALSE(twai_hal_check_brp_validation(twai_ctx->hal, timing->brp), ESP_ERR_INVALID_ARG, TAG, "invalid brp"); - ESP_RETURN_ON_FALSE((timing->tseg_1 >= TWAI_LL_TSEG1_MIN) && (timing->tseg_1 <= TWAI_LL_TSEG1_MAX), ESP_ERR_INVALID_ARG, TAG, "invalid tseg1"); - ESP_RETURN_ON_FALSE((timing->tseg_2 >= TWAI_LL_TSEG2_MIN) && (timing->tseg_2 <= TWAI_LL_TSEG2_MAX), ESP_ERR_INVALID_ARG, TAG, "invalid tseg_2"); - ESP_RETURN_ON_FALSE((timing->sjw >= 1) && (timing->sjw <= TWAI_LL_SJW_MAX), ESP_ERR_INVALID_ARG, TAG, "invalid swj"); - return ESP_OK; -} - -static esp_err_t _node_set_clock_source(twai_node_handle_t node, twai_clock_source_t clock_src) -{ - twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); - if (clock_src != twai_ctx->curr_clk_src) { - // Order of operations is important here. - // First enable and switch to the new clock source, then disable the old one. - // To ensure the clock to controller is continuous. - ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src(clock_src, true), TAG, "enable clock source failed"); - _twai_rcc_clock_sel(twai_ctx->ctrlr_id, clock_src); - if (twai_ctx->curr_clk_src) { - // Disable previous clock source - esp_err_t err = esp_clk_tree_enable_src(twai_ctx->curr_clk_src, false); - if (err != ESP_OK) { - ESP_LOGE(TAG, "disable previous clock source failed, err: %d", err); - esp_clk_tree_enable_src(clock_src, false); - return err; - } - } - twai_ctx->curr_clk_src = clock_src; - ESP_LOGD(TAG, "set clock source to %d", clock_src); + if (timing) { + ESP_RETURN_ON_FALSE(twai_hal_check_brp_validation(twai_ctx->hal, timing->brp), ESP_ERR_INVALID_ARG, TAG, "invalid brp"); + ESP_RETURN_ON_FALSE((timing->tseg_1 >= TWAI_LL_TSEG1_MIN) && (timing->tseg_1 <= TWAI_LL_TSEG1_MAX), ESP_ERR_INVALID_ARG, TAG, "invalid tseg1"); + ESP_RETURN_ON_FALSE((timing->tseg_2 >= TWAI_LL_TSEG2_MIN) && (timing->tseg_2 <= TWAI_LL_TSEG2_MAX), ESP_ERR_INVALID_ARG, TAG, "invalid tseg_2"); + ESP_RETURN_ON_FALSE((timing->sjw >= 1) && (timing->sjw <= TWAI_LL_SJW_MAX), ESP_ERR_INVALID_ARG, TAG, "invalid swj"); } return ESP_OK; } @@ -378,23 +360,9 @@ static esp_err_t _node_set_clock_source(twai_node_handle_t node, twai_clock_sour static esp_err_t _node_set_bit_timing(twai_node_handle_t node, const twai_timing_advanced_config_t *timing, const twai_timing_advanced_config_t *timing_fd) { twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); - twai_clock_source_t new_clock_src = twai_ctx->curr_clk_src; ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) == TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "config timing must when node stopped"); - if (timing && timing_fd) { - ESP_RETURN_ON_FALSE(timing->clk_src == timing_fd->clk_src, ESP_ERR_INVALID_ARG, TAG, "clk_src of 2 configs must same"); - new_clock_src = timing->clk_src ? timing->clk_src : TWAI_CLK_SRC_DEFAULT; - } else { - if (timing) { - ESP_RETURN_ON_FALSE(!twai_ctx->valid_fd_timing || !timing->clk_src || (timing->clk_src == TWAI_CLK_SRC_DEFAULT), ESP_ERR_INVALID_ARG, TAG, "don't change clk_src in single config"); - new_clock_src = timing->clk_src ? timing->clk_src : TWAI_CLK_SRC_DEFAULT; - } else { - ESP_RETURN_ON_FALSE(!timing_fd->clk_src || (timing_fd->clk_src == TWAI_CLK_SRC_DEFAULT), ESP_ERR_INVALID_ARG, TAG, "don't change clk_src in single config"); - } - } - uint32_t source_freq = 0; - ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(new_clock_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &source_freq), TAG, "clock src error, can't get freq"); - ESP_RETURN_ON_ERROR(_node_check_timing_valid(twai_ctx, timing, source_freq), TAG, "invalid param"); - ESP_RETURN_ON_ERROR(_node_check_timing_valid(twai_ctx, timing_fd, source_freq), TAG, "invalid fd param"); + ESP_RETURN_ON_ERROR(_node_check_timing_valid(twai_ctx, timing), TAG, "invalid param"); + ESP_RETURN_ON_ERROR(_node_check_timing_valid(twai_ctx, timing_fd), TAG, "invalid fd param"); if (timing) { twai_hal_configure_timing(twai_ctx->hal, timing); @@ -405,22 +373,17 @@ static esp_err_t _node_set_bit_timing(twai_node_handle_t node, const twai_timing twai_hal_configure_timing_fd(twai_ctx->hal, timing_fd); } #endif - - return _node_set_clock_source(node, new_clock_src); + return ESP_OK; } -static esp_err_t _node_calc_set_bit_timing(twai_node_handle_t node, twai_clock_source_t clk_src, const twai_timing_basic_config_t *timing, const twai_timing_basic_config_t *timing_fd) +static esp_err_t _node_calc_set_bit_timing(twai_node_handle_t node, const twai_timing_basic_config_t *timing, const twai_timing_basic_config_t *timing_fd) { + twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); ESP_RETURN_ON_FALSE(timing->bitrate, ESP_ERR_INVALID_ARG, TAG, "classic timing config is required"); #if !SOC_HAS(TWAI_FD) ESP_RETURN_ON_FALSE((!timing_fd->bitrate) || (timing_fd->bitrate == timing->bitrate), ESP_ERR_INVALID_ARG, TAG, "FD stage bitrate is not supported"); #endif - twai_clock_source_t root_clock_src = clk_src ? clk_src : TWAI_CLK_SRC_DEFAULT; - uint32_t source_freq = 0; - ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(root_clock_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &source_freq), TAG, "can't get clock source freq"); - twai_timing_advanced_config_t timing_adv = { .clk_src = root_clock_src, }; - twai_timing_advanced_config_t *fd_cfg_ptr = NULL; twai_timing_constraint_t hw_const = { .brp_min = TWAI_LL_BRP_MIN, .brp_max = TWAI_LL_BRP_MAX, @@ -430,28 +393,42 @@ static esp_err_t _node_calc_set_bit_timing(twai_node_handle_t node, twai_clock_s .tseg2_max = TWAI_LL_TSEG2_MAX, .sjw_max = TWAI_LL_SJW_MAX, }; - uint32_t real_baud = twai_node_timing_calc_param(source_freq, timing, &hw_const, &timing_adv); - ESP_LOGD(TAG, "timing: src %ld brp %ld prop %d seg1 %d seg2 %d sjw %d ssp %d", source_freq, timing_adv.brp, timing_adv.prop_seg, timing_adv.tseg_1, timing_adv.tseg_2, timing_adv.sjw, timing_adv.ssp_offset); + + twai_timing_advanced_config_t timing_adv = {}, *timing_fd_ptr = NULL; + uint32_t real_baud = twai_node_timing_calc_param(twai_ctx->src_freq_hz, timing, &hw_const, &timing_adv); + ESP_LOGD(TAG, "timing: src %ld brp %ld prop %d seg1 %d seg2 %d sjw %d ssp %d", twai_ctx->src_freq_hz, timing_adv.brp, timing_adv.prop_seg, timing_adv.tseg_1, timing_adv.tseg_2, timing_adv.sjw, timing_adv.ssp_offset); ESP_RETURN_ON_FALSE(real_baud, ESP_ERR_INVALID_ARG, TAG, "bitrate can't achieve!"); if (timing->bitrate != real_baud) { ESP_LOGW(TAG, "bitrate precision loss, adjust from %ld to %ld", timing->bitrate, real_baud); } #if SOC_HAS(TWAI_FD) - twai_timing_advanced_config_t timing_adv_fd = { .clk_src = root_clock_src, }; + twai_timing_advanced_config_t timing_adv_fd = {}; if (timing_fd->bitrate) { - real_baud = twai_node_timing_calc_param(source_freq, timing_fd, &hw_const, &timing_adv_fd); - ESP_LOGD(TAG, "timing_fd: src %ld brp %ld prop %d seg1 %d seg2 %d sjw %d ssp %d", source_freq, timing_adv_fd.brp, timing_adv_fd.prop_seg, timing_adv_fd.tseg_1, timing_adv_fd.tseg_2, timing_adv_fd.sjw, timing_adv_fd.ssp_offset); + real_baud = twai_node_timing_calc_param(twai_ctx->src_freq_hz, timing_fd, &hw_const, &timing_adv_fd); + ESP_LOGD(TAG, "timing_fd: src %ld brp %ld prop %d seg1 %d seg2 %d sjw %d ssp %d", twai_ctx->src_freq_hz, timing_adv_fd.brp, timing_adv_fd.prop_seg, timing_adv_fd.tseg_1, timing_adv_fd.tseg_2, timing_adv_fd.sjw, timing_adv_fd.ssp_offset); ESP_RETURN_ON_FALSE(real_baud, ESP_ERR_INVALID_ARG, TAG, "bitrate can't achieve!"); if (timing_fd->bitrate != real_baud) { ESP_LOGW(TAG, "bitrate precision loss, adjust from %ld to %ld", timing_fd->bitrate, real_baud); } - fd_cfg_ptr = &timing_adv_fd; + timing_fd_ptr = &timing_adv_fd; } #endif - ESP_RETURN_ON_ERROR(_node_set_bit_timing(node, &timing_adv, fd_cfg_ptr), TAG, "invalid timing param, bitrate can't achieve!"); + ESP_RETURN_ON_ERROR(_node_set_bit_timing(node, &timing_adv, timing_fd_ptr), TAG, "invalid timing param, bitrate can't achieve!"); return ESP_OK; } +//convert microseconds to timestamp units +__attribute__((always_inline)) +static inline uint64_t _time_us_to_timestamp(uint64_t time_us, uint32_t resolution) +{ + if (resolution > 1000000) { + return time_us * (resolution / 1000000); + } else if (resolution > 0) { + return time_us / (1000000 / resolution); + } + return 0; +} + /* -------------------------------------------------- Node Control -------------------------------------------------- */ static esp_err_t _node_enable(twai_node_handle_t node) @@ -466,7 +443,12 @@ static esp_err_t _node_enable(twai_node_handle_t node) } #endif //CONFIG_PM_ENABLE twai_hal_start(twai_ctx->hal); - +#if TWAI_LL_SUPPORT(TIMESTAMP) + if (twai_ctx->timestamp_freq_hz) { + twai_hal_timer_start_with(twai_ctx->hal, _time_us_to_timestamp(esp_timer_get_time(), twai_ctx->timestamp_freq_hz)); + ESP_RETURN_ON_ERROR(esp_intr_enable(twai_ctx->timer_intr_hdl), TAG, "enable timer interrupt failed"); + } +#endif twai_error_state_t hw_state = twai_hal_get_err_state(twai_ctx->hal); atomic_store(&twai_ctx->state, hw_state); // continuing the transaction if there be @@ -482,6 +464,12 @@ static esp_err_t _node_disable(twai_node_handle_t node) twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node already disabled"); +#if TWAI_LL_SUPPORT(TIMESTAMP) + if (twai_ctx->timestamp_freq_hz) { + twai_hal_timer_stop(twai_ctx->hal); + ESP_RETURN_ON_ERROR(esp_intr_disable(twai_ctx->timer_intr_hdl), TAG, "disable timer interrupt failed"); + } +#endif ESP_RETURN_ON_ERROR(esp_intr_disable(twai_ctx->intr_hdl), TAG, "disable interrupt failed"); atomic_store(&twai_ctx->state, TWAI_ERROR_BUS_OFF); twai_hal_stop(twai_ctx->hal); @@ -648,7 +636,11 @@ static esp_err_t _node_parse_rx(twai_node_handle_t node, twai_frame_t *rx_frame) ESP_RETURN_ON_FALSE_ISR(atomic_load(&twai_ctx->rx_isr), ESP_ERR_INVALID_STATE, TAG, "rx can only called in `rx_done` callback"); assert(xPortInIsrContext() && "should always in rx_done callback"); - twai_hal_parse_frame(&twai_ctx->rcv_buff, &rx_frame->header, rx_frame->buffer, rx_frame->buffer_len); + twai_hal_parse_frame(twai_ctx->hal, &twai_ctx->rcv_buff, &rx_frame->header, rx_frame->buffer, rx_frame->buffer_len); + if (twai_ctx->timestamp_freq_hz && !rx_frame->header.timestamp) { + // if timestamp not updated by hardware, use the esp_timer timestamp to calculate the timestamp + rx_frame->header.timestamp = _time_us_to_timestamp(esp_timer_get_time(), twai_ctx->timestamp_freq_hz); + } return ESP_OK; } @@ -668,6 +660,8 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa ESP_GOTO_ON_FALSE(ctrlr_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "Controller not available"); node->ctrlr_id = ctrlr_id; node->hal = (twai_hal_context_t *)(node + 1); //hal context is place at end of driver context + node->curr_clk_src = node_config->clk_src ? node_config->clk_src : TWAI_CLK_SRC_DEFAULT; + ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(node->curr_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &node->src_freq_hz), err, TAG, "get clock source frequency failed"); // state is in bus_off before enabled atomic_store(&node->state, TWAI_ERROR_BUS_OFF); @@ -676,28 +670,29 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa 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"); 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 ESP_GOTO_ON_ERROR(esp_intr_alloc(twai_periph_signals[ctrlr_id].irq_id, intr_flags, _node_isr_main, (void *)node, &node->intr_hdl), err, TAG, "Alloc interrupt failed"); - // Set default clock source first - ESP_RETURN_ON_ERROR(_node_set_clock_source(&node->api_base, TWAI_CLK_SRC_DEFAULT), TAG, "enable default clock source failed"); - // Enable bus clock and reset controller - _twai_rcc_clock_ctrl(ctrlr_id, true); - // Initialize HAL and configure register defaults. - twai_hal_config_t hal_config = { - .controller_id = node->ctrlr_id, - .intr_mask = TWAI_LL_DRIVER_INTERRUPTS, - .retry_cnt = node_config->fail_retry_cnt, - .no_receive_rtr = node_config->flags.no_receive_rtr, - .enable_listen_only = node_config->flags.enable_listen_only, - .enable_self_test = node_config->flags.enable_self_test, - .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"); - // Configure bus timing - ESP_GOTO_ON_ERROR(_node_calc_set_bit_timing(&node->api_base, node_config->clk_src, &node_config->bit_timing, &node_config->data_timing), err, TAG, "bitrate error"); - // Configure GPIO - ESP_GOTO_ON_ERROR(_node_config_io(node, node_config), err, TAG, "gpio config failed"); + if (node_config->timestamp_resolution_hz) { +#if TWAI_LL_SUPPORT(TIMESTAMP) + ESP_GOTO_ON_FALSE((node_config->timestamp_resolution_hz >= (node->src_freq_hz / TWAI_LL_TIMER_DIV_MAX)) && (node_config->timestamp_resolution_hz <= node->src_freq_hz), \ + ESP_ERR_INVALID_ARG, err, TAG, "Timestamp resolution range [%d, %d]", node->src_freq_hz / TWAI_LL_TIMER_DIV_MAX, node->src_freq_hz); + uint32_t real_timer_freq = node->src_freq_hz / (node->src_freq_hz / node_config->timestamp_resolution_hz); + if (real_timer_freq != node_config->timestamp_resolution_hz) { + ESP_LOGW(TAG, "timestamp resolution loss, adjust to %dHz", real_timer_freq); + } + // deal timer interrupt in same `_node_isr_main` handler and check timer event first + // to avoid race condition if two hardware interrupts are triggered at the same time + ESP_GOTO_ON_ERROR(esp_intr_alloc(twai_periph_signals[ctrlr_id].timer_irq_id, intr_flags, _node_isr_main, (void *)node, &node->timer_intr_hdl), + err, TAG, "Alloc timer interrupt failed"); +#else + ESP_GOTO_ON_FALSE(node_config->timestamp_resolution_hz <= 1000000, ESP_ERR_INVALID_ARG, err, TAG, "Timestamp resolution is at most 1MHz"); +#endif + node->timestamp_freq_hz = node_config->timestamp_resolution_hz; + } + _lock_release(&s_platform.intr_mutex); + #if CONFIG_PM_ENABLE #if TWAI_LL_SUPPORT(APB_CLK) // DFS can change APB frequency. So add lock to prevent sleep and APB freq from changing @@ -708,6 +703,30 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa #endif //TWAI_LL_SUPPORT(APB_CLK) #endif //CONFIG_PM_ENABLE + // Set clock source, enable bus clock and reset controller + ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src(node->curr_clk_src, true), TAG, "enable clock source failed"); + ESP_LOGD(TAG, "set clock source to %d, freq: %ld Hz", node->curr_clk_src, node->src_freq_hz); + _twai_rcc_clock_sel(node->ctrlr_id, node->curr_clk_src); + _twai_rcc_clock_ctrl(ctrlr_id, true); + + // Initialize HAL and configure register defaults. + twai_hal_config_t hal_config = { + .controller_id = node->ctrlr_id, + .intr_mask = TWAI_LL_DRIVER_INTERRUPTS, + .clock_source_hz = node->src_freq_hz, + .timer_freq = node->timestamp_freq_hz, + .retry_cnt = node_config->fail_retry_cnt, + .no_receive_rtr = node_config->flags.no_receive_rtr, + .enable_listen_only = node_config->flags.enable_listen_only, + .enable_self_test = node_config->flags.enable_self_test, + .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"); + // 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 + ESP_GOTO_ON_ERROR(_node_config_io(node, node_config), err, TAG, "gpio config failed"); + node->api_base.enable = _node_enable; node->api_base.disable = _node_disable; node->api_base.del = _node_delete; @@ -725,9 +744,9 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa *node_ret = &node->api_base; return ESP_OK; - err: if (node) { + _lock_release(&s_platform.intr_mutex); _node_destroy(node); } return ret; diff --git a/components/esp_driver_twai/include/esp_twai_onchip.h b/components/esp_driver_twai/include/esp_twai_onchip.h index 643cc3177e..84fc8bd50b 100644 --- a/components/esp_driver_twai/include/esp_twai_onchip.h +++ b/components/esp_driver_twai/include/esp_twai_onchip.h @@ -26,6 +26,7 @@ typedef struct { twai_clock_source_t clk_src; /**< Optional, clock source, remain 0 to using TWAI_CLK_SRC_DEFAULT by default */ twai_timing_basic_config_t bit_timing; /**< Timing configuration for classic twai and FD arbitration stage */ twai_timing_basic_config_t data_timing; /**< Optional, timing configuration for FD data stage */ + uint32_t timestamp_resolution_hz; /**< Timebase frequency (in Hz), used for recording the timestamp of RX frame, set 0 to disable the timestamp feature */ int8_t fail_retry_cnt; /**< Hardware retry limit if failed, range [-1:15], -1 for re-trans forever */ uint32_t tx_queue_depth; /**< Depth of the transmit queue */ int intr_priority; /**< Interrupt priority, [0:3] */ diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_app_main.c b/components/esp_driver_twai/test_apps/test_twai/main/test_app_main.c index ee70625960..f66637a53c 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_app_main.c +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_app_main.c @@ -10,7 +10,7 @@ #include "esp_heap_caps.h" // lazy install of mutex and pm_lock occupied memorys -#define LEAKS (300) +#define LEAKS (400) void setUp(void) { diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp index 86fb1b720f..2db5a9bc3e 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp @@ -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 */ @@ -12,6 +12,7 @@ #include "test_utils.h" #include "esp_attr.h" #include "esp_log.h" +#include "esp_timer.h" #include "esp_heap_caps.h" #include "esp_clk_tree.h" #include "freertos/FreeRTOS.h" @@ -745,7 +746,7 @@ static IRAM_ATTR bool test_dlc_range_cb(twai_node_handle_t handle, const twai_rx { twai_frame_t *rx_frame = (twai_frame_t *)user_ctx; if (ESP_OK == twai_node_receive_from_isr(handle, rx_frame)) { - esp_rom_printf(DRAM_STR("RX len %d %s\n"), rx_frame->header.dlc, rx_frame->buffer); + ESP_EARLY_LOGI("RX", "timestamp %llu frame %x [%d] %s", rx_frame->header.timestamp, rx_frame->header.id, rx_frame->header.dlc, rx_frame->buffer); } return false; } @@ -791,6 +792,7 @@ TEST_CASE("twai dlc range test", "[twai]") TEST_ASSERT_EQUAL(len % 9, rx_frame.header.dlc); memset(rx_buffer, 0, sizeof(rx_buffer)); } + TEST_ASSERT_EQUAL(0, rx_frame.header.timestamp); // timestamp should be 0 if not enabled tx_frame.buffer_len = 9; tx_frame.header.dlc = 0; @@ -803,3 +805,72 @@ TEST_CASE("twai dlc range test", "[twai]") TEST_ESP_OK(twai_node_disable(node_hdl)); TEST_ESP_OK(twai_node_delete(node_hdl)); } + +#define MS_TO_TWAI_TICK(time_ms, resolution) ((time_ms) * (resolution / 1000)) +TEST_CASE("twai rx timestamp", "[twai]") +{ + twai_node_handle_t node_hdl; + 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 = 800000; + node_config.tx_queue_depth = TEST_FRAME_NUM; + node_config.flags.enable_loopback = true; + node_config.flags.enable_self_test = true; + + bool hw_timer = false; +#if TWAI_LL_SUPPORT(TIMESTAMP) + hw_timer = true; +#endif + for (uint32_t resolution = 1000; resolution <= 10000000; resolution *= 100) { + node_config.timestamp_resolution_hz = resolution; + printf("\nTesting resolution %ld\n", resolution); + if (((resolution < 2000) && hw_timer) || ((resolution > 1000000) && !hw_timer)) { + TEST_ESP_ERR(twai_new_node_onchip(&node_config, &node_hdl), ESP_ERR_INVALID_ARG); + continue; + } + TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl)); + + uint8_t rx_buffer[TWAI_FRAME_MAX_LEN] = {0}; + twai_frame_t rx_frame = {}; + rx_frame.buffer = rx_buffer; + rx_frame.buffer_len = sizeof(rx_buffer); + + twai_event_callbacks_t user_cbs = {}; + user_cbs.on_rx_done = test_dlc_range_cb; + TEST_ESP_OK(twai_node_register_event_callbacks(node_hdl, &user_cbs, &rx_frame)); + TEST_ESP_OK(twai_node_enable(node_hdl)); + + twai_frame_t tx_frame = {}; + tx_frame.buffer = (uint8_t *)"hi time"; + tx_frame.buffer_len = strlen((const char *)tx_frame.buffer); + uint64_t time_now, time_last = MS_TO_TWAI_TICK(esp_timer_get_time() / 1000, resolution); + for (int i = 1; i < 10; i++) { + tx_frame.header.id = i; + printf("\nwaiting %dms (%ld ticks) ...\n", i * 100, MS_TO_TWAI_TICK(i * 100, resolution)); + esp_rom_delay_us(i * 100 * 1000); + TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame, 100)); + TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, 100)); + + time_now = MS_TO_TWAI_TICK(esp_timer_get_time() / 1000, resolution); + printf("esp tick now %llu, diff %u\n", time_now, abs(time_now - rx_frame.header.timestamp)); + TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), time_now, rx_frame.header.timestamp); + TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), rx_frame.header.timestamp - time_last, MS_TO_TWAI_TICK(i * 100, resolution)); + time_last = rx_frame.header.timestamp; + } + + printf("\n==============================================\n"); + printf("Test timestamp still alive during node disable/enable (%ld ticks)\n", MS_TO_TWAI_TICK(1000, resolution)); + TEST_ESP_OK(twai_node_disable(node_hdl)); + esp_rom_delay_us(1000 * 1000); + TEST_ESP_OK(twai_node_enable(node_hdl)); + TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame, 100)); + TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, 100)); + TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), rx_frame.header.timestamp - time_last, MS_TO_TWAI_TICK(1000, resolution)); + + TEST_ESP_OK(twai_node_disable(node_hdl)); + TEST_ESP_OK(twai_node_delete(node_hdl)); + } +} 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 ae57172b29..6fdc2ecc86 100644 --- a/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,12 +18,13 @@ #define TWAIFD_LL_GET_HW(num) (((num) == 0) ? (&TWAI0) : (&TWAI1)) #define TWAI_LL_BRP_MIN 1 -#define TWAI_LL_BRP_MAX 255 #define TWAI_LL_TSEG1_MIN 0 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_BRP_MAX TWAIFD_BRP #define TWAI_LL_TSEG1_MAX TWAIFD_PH1 #define TWAI_LL_TSEG2_MAX TWAIFD_PH2 #define TWAI_LL_SJW_MAX TWAIFD_SJW +#define TWAI_LL_TIMER_DIV_MAX TWAIFD_TIMER_STEP #define TWAIFD_IDENTIFIER_BASE_S 18 // Start bit of std_id in IDENTIFIER_W of TX buffer or RX buffer @@ -966,6 +967,7 @@ static inline void twaifd_ll_timer_enable(twaifd_dev_t *hw, bool enable) * @param hw Pointer to the TWAI-FD device hardware. * @return Bit width of the timer. */ +__attribute__((always_inline)) static inline uint8_t twaifd_ll_timer_get_bitwidth(twaifd_dev_t *hw) { return hw->err_capt_retr_ctr_alc_ts_info.ts_bits + 1; @@ -973,6 +975,7 @@ static inline uint8_t twaifd_ll_timer_get_bitwidth(twaifd_dev_t *hw) /** * @brief Get the current timer count. + * @note The frame time is recorded by hardware, so this function is not required, can be used for independent test * * @param hw Pointer to the TWAI-FD device hardware. * @return Current timer count as a 64-bit value. @@ -984,16 +987,16 @@ static inline uint64_t twaifd_ll_timer_get_count(twaifd_dev_t *hw) } /** - * @brief Set the timer step value. + * @brief Set the timer clock divider value. * * @note This is to determine the resolution of the timer. We can also treat it as a prescaler. * * @param hw Pointer to the TWAI-FD device hardware. - * @param step Step value to set (actual step = step - 1). + * @param div Clock divider value to set. */ -static inline void twaifd_ll_timer_set_step(twaifd_dev_t *hw, uint32_t step) +static inline void twaifd_ll_timer_set_clkdiv(twaifd_dev_t *hw, uint32_t div) { - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->timer_cfg, timer_step, (step - 1)); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->timer_cfg, timer_step, (div - 1)); } /** @@ -1019,7 +1022,7 @@ static inline void twaifd_ll_timer_clr_count(twaifd_dev_t *hw, bool clear) } /** - * @brief Set the timer preload value. + * @brief Set the timer preload value when overflow. * * @param hw Pointer to the TWAI-FD device hardware. * @param load_value 64-bit load value. @@ -1075,6 +1078,7 @@ static inline void twaifd_ll_timer_enable_intr(twaifd_dev_t *hw, uint32_t mask, * @param hw Pointer to the TWAI-FD device hardware. * @return Current interrupt status. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_timer_get_intr_status(twaifd_dev_t *hw, uint32_t mask) { return hw->timer_int_st.val & mask; @@ -1085,6 +1089,7 @@ static inline uint32_t twaifd_ll_timer_get_intr_status(twaifd_dev_t *hw, uint32_ * * @param hw Pointer to the TWAI-FD device hardware. */ +__attribute__((always_inline)) static inline void twaifd_ll_timer_clr_intr_status(twaifd_dev_t *hw, uint32_t mask) { hw->timer_int_clr.val = mask; 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 fbb9c74280..4adbd01488 100644 --- a/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32h4/include/hal/twaifd_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 */ @@ -18,12 +18,13 @@ #define TWAIFD_LL_GET_HW(num) (((num) == 0) ? (&TWAI0) : NULL) #define TWAI_LL_BRP_MIN 1 -#define TWAI_LL_BRP_MAX 255 #define TWAI_LL_TSEG1_MIN 0 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_BRP_MAX TWAIFD_BRP #define TWAI_LL_TSEG1_MAX TWAIFD_PH1 #define TWAI_LL_TSEG2_MAX TWAIFD_PH2 #define TWAI_LL_SJW_MAX TWAIFD_SJW +#define TWAI_LL_TIMER_DIV_MAX TWAIFD_TIMER_STEP #define TWAIFD_IDENTIFIER_BASE_S 18 // Start bit of std_id in IDENTIFIER_W of TX buffer or RX buffer @@ -965,6 +966,7 @@ static inline void twaifd_ll_timer_enable(twaifd_dev_t *hw, bool enable) * @param hw Pointer to the TWAI-FD device hardware. * @return Bit width of the timer. */ +__attribute__((always_inline)) static inline uint8_t twaifd_ll_timer_get_bitwidth(twaifd_dev_t *hw) { return hw->err_capt_retr_ctr_alc_ts_info.ts_bits + 1; @@ -972,6 +974,7 @@ static inline uint8_t twaifd_ll_timer_get_bitwidth(twaifd_dev_t *hw) /** * @brief Get the current timer count. + * @note The frame time is recorded by hardware, so this function is not required, can be used for independent test * * @param hw Pointer to the TWAI-FD device hardware. * @return Current timer count as a 64-bit value. @@ -983,16 +986,16 @@ static inline uint64_t twaifd_ll_timer_get_count(twaifd_dev_t *hw) } /** - * @brief Set the timer step value. + * @brief Set the timer clock divider value. * * @note This is to determine the resolution of the timer. We can also treat it as a prescaler. * * @param hw Pointer to the TWAI-FD device hardware. - * @param step Step value to set (actual step = step - 1). + * @param div Clock divider value to set. */ -static inline void twaifd_ll_timer_set_step(twaifd_dev_t *hw, uint32_t step) +static inline void twaifd_ll_timer_set_clkdiv(twaifd_dev_t *hw, uint32_t div) { - HAL_FORCE_MODIFY_U32_REG_FIELD(hw->timer_cfg, timer_step, (step - 1)); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->timer_cfg, timer_step, (div - 1)); } /** @@ -1018,7 +1021,7 @@ static inline void twaifd_ll_timer_clr_count(twaifd_dev_t *hw, bool clear) } /** - * @brief Set the timer preload value. + * @brief Set the timer preload value when overflow. * * @param hw Pointer to the TWAI-FD device hardware. * @param load_value 64-bit load value. @@ -1074,6 +1077,7 @@ static inline void twaifd_ll_timer_enable_intr(twaifd_dev_t *hw, uint32_t mask, * @param hw Pointer to the TWAI-FD device hardware. * @return Current interrupt status. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_timer_get_intr_status(twaifd_dev_t *hw, uint32_t mask) { return hw->timer_int_st.val & mask; @@ -1084,6 +1088,7 @@ static inline uint32_t twaifd_ll_timer_get_intr_status(twaifd_dev_t *hw, uint32_ * * @param hw Pointer to the TWAI-FD device hardware. */ +__attribute__((always_inline)) static inline void twaifd_ll_timer_clr_intr_status(twaifd_dev_t *hw, uint32_t mask) { hw->timer_int_clr.val = mask; diff --git a/components/esp_hal_twai/include/hal/twai_hal.h b/components/esp_hal_twai/include/hal/twai_hal.h index d3a26148fd..8216731fd0 100644 --- a/components/esp_hal_twai/include/hal/twai_hal.h +++ b/components/esp_hal_twai/include/hal/twai_hal.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 */ @@ -58,6 +58,7 @@ typedef struct { twai_soc_handle_t dev; // TWAI SOC layer handle (i.e. register base address) uint32_t state_flags; uint32_t clock_source_hz; + 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 int8_t retry_cnt; @@ -72,6 +73,7 @@ typedef struct { typedef struct { int controller_id; uint32_t clock_source_hz; + uint32_t timer_freq; uint32_t intr_mask; int8_t retry_cnt; bool no_receive_rtr; @@ -191,6 +193,19 @@ void twai_hal_start(twai_hal_context_t *hal_ctx); */ void twai_hal_stop(twai_hal_context_t *hal_ctx); +/** + * @brief Start the TWAI timer with a start value + * + * @param hal_ctx Context of the HAL layer + * @param preload_value Preload value for the timer + */ +void twai_hal_timer_start_with(twai_hal_context_t *hal_ctx, uint64_t preload_value); + +/** + * @brief Stop the TWAI timer + */ +void twai_hal_timer_stop(twai_hal_context_t *hal_ctx); + /** * @brief Start bus recovery * @@ -310,10 +325,11 @@ void twai_hal_format_frame(const twai_hal_trans_desc_t *trans_desc, twai_hal_fra * This function takes a TWAI frame (in the format of the RX frame buffer) and * parses it to a TWAI message (containing ID, DLC, data and flags). * + * @param hal_ctx Context of the HAL layer * @param frame Pointer to frame structure * @param message Pointer to empty message structure */ -void twai_hal_parse_frame(const twai_hal_frame_t *frame, twai_frame_header_t *header, uint8_t *buffer, uint8_t buffer_len); +void twai_hal_parse_frame(twai_hal_context_t *hal_ctx, twai_hal_frame_t *frame, twai_frame_header_t *header, uint8_t *buffer, uint8_t buffer_len); /** * @brief Copy a frame into the TX buffer and transmit diff --git a/components/esp_hal_twai/include/hal/twai_periph.h b/components/esp_hal_twai/include/hal/twai_periph.h index 55d3d81e8e..faa5256684 100644 --- a/components/esp_hal_twai/include/hal/twai_periph.h +++ b/components/esp_hal_twai/include/hal/twai_periph.h @@ -25,9 +25,7 @@ extern "C" { typedef struct { const char *module_name; // peripheral name const int irq_id; // interrupt source ID -#if TWAI_LL_SUPPORT(TIMESTAMP) const int timer_irq_id; // time base interrupt source ID -#endif const int tx_sig; // TX signal ID in GPIO matrix const int rx_sig; // RX signal ID in GPIO matrix const int clk_out_sig; // CLK_OUT signal ID in GPIO matrix diff --git a/components/esp_hal_twai/include/hal/twai_types.h b/components/esp_hal_twai/include/hal/twai_types.h index 488f4b8197..f2892f2d74 100644 --- a/components/esp_hal_twai/include/hal/twai_types.h +++ b/components/esp_hal_twai/include/hal/twai_types.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 */ @@ -63,26 +63,16 @@ typedef int twai_clock_source_t; #endif /** - * @brief TWAI bitrate timing config advanced mode - * @note Setting one of `quanta_resolution_hz` and `brp` is enough, otherwise, `brp` is not used. + * @brief TWAI bitrate timing advanced config structure */ typedef struct { - twai_clock_source_t clk_src; /**< Optional, clock source, remain 0 to using TWAI_CLK_SRC_DEFAULT by default */ - uint32_t quanta_resolution_hz; /**< The resolution of one timing quanta, in Hz. If setting, brp will be ignored */ - uint32_t brp; /**< Bit rate pre-divider, f(clk_src) / brp = quanta_resolution_hz, f(clk_src) can be obtained using esp_clk_tree_src_get_freq_hz(clk_src,,)*/ + uint32_t brp; /**< Bitrate pre-divider, which decides the quanta time */ uint8_t prop_seg; /**< Prop_seg length, in quanta time */ uint8_t tseg_1; /**< Seg_1 length, in quanta time */ uint8_t tseg_2; /**< Seg_2 length, in quanta time */ uint8_t sjw; /**< Sync jump width, in quanta time */ uint8_t ssp_offset; /**< Secondary sample point offset refet to Sync seg, in quanta time, set 0 to disable ssp */ - bool triple_sampling; /**< Deprecated, in favor of `ssp_offset` */ -} twai_timing_config_t; - -/** - * @brief TWAI bitrate timing config advanced mode for esp_driver_twai - * @note `quanta_resolution_hz` is not supported in this driver - */ -typedef twai_timing_config_t twai_timing_advanced_config_t; +} twai_timing_advanced_config_t; /** * @brief Configuration for TWAI mask filter diff --git a/components/esp_hal_twai/include/hal/twai_types_deprecated.h b/components/esp_hal_twai/include/hal/twai_types_deprecated.h index 7695cd71e3..7901eccee1 100644 --- a/components/esp_hal_twai/include/hal/twai_types_deprecated.h +++ b/components/esp_hal_twai/include/hal/twai_types_deprecated.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 */ @@ -84,6 +84,32 @@ extern "C" { #define TWAI_FILTER_CONFIG_ACCEPT_ALL() {.acceptance_code = 0, .acceptance_mask = 0xFFFFFFFF, .single_filter = true} /** @endcond */ +/** + * @brief TWAI group clock source + * @note User should select the clock source based on the power and resolution requirement + */ +#if SOC_TWAI_SUPPORTED +typedef soc_periph_twai_clk_src_t twai_clock_source_t; +#else +typedef int twai_clock_source_t; +#endif + +/** + * @brief TWAI bitrate timing config advanced mode + * @note Setting one of `quanta_resolution_hz` and `brp` is enough, otherwise, `brp` is not used. + */ +typedef struct { + twai_clock_source_t clk_src; /**< Optional, clock source, remain 0 to using TWAI_CLK_SRC_DEFAULT by default */ + uint32_t quanta_resolution_hz; /**< The resolution of one timing quanta, in Hz. If setting, brp will be ignored */ + uint32_t brp; /**< Bit rate pre-divider, f(clk_src) / brp = quanta_resolution_hz, f(clk_src) can be obtained using esp_clk_tree_src_get_freq_hz(clk_src,,)*/ + uint8_t prop_seg; /**< Prop_seg length, in quanta time */ + uint8_t tseg_1; /**< Seg_1 length, in quanta time */ + uint8_t tseg_2; /**< Seg_2 length, in quanta time */ + uint8_t sjw; /**< Sync jump width, in quanta time */ + uint8_t ssp_offset; /**< Secondary sample point offset refet to Sync seg, in quanta time, set 0 to disable ssp */ + bool triple_sampling; /**< Deprecated, in favor of `ssp_offset` */ +} twai_timing_config_t; + /** * @brief TWAI Controller operating modes */ diff --git a/components/esp_hal_twai/twai_hal_v1.c b/components/esp_hal_twai/twai_hal_v1.c index 952ae75af2..dc69a8ac8f 100644 --- a/components/esp_hal_twai/twai_hal_v1.c +++ b/components/esp_hal_twai/twai_hal_v1.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 */ @@ -98,15 +98,8 @@ bool twai_hal_check_brp_validation(twai_hal_context_t *hal_ctx, uint32_t brp) void twai_hal_configure_timing(twai_hal_context_t *hal_ctx, const twai_timing_advanced_config_t *t_config) { - uint32_t brp = t_config->brp; - // both quanta_resolution_hz and brp can affect the baud rate - // but a non-zero quanta_resolution_hz takes higher priority - if (t_config->quanta_resolution_hz) { - brp = hal_ctx->clock_source_hz / t_config->quanta_resolution_hz; - } - //Configure bus timing - twai_ll_set_bus_timing(hal_ctx->dev, brp, t_config->sjw, t_config->tseg_1 + t_config->prop_seg, t_config->tseg_2, !!t_config->ssp_offset); - twai_ll_set_clkout(hal_ctx->dev, brp); + twai_ll_set_bus_timing(hal_ctx->dev, t_config->brp, t_config->sjw, t_config->tseg_1 + t_config->prop_seg, t_config->tseg_2, !!t_config->ssp_offset); + twai_ll_set_clkout(hal_ctx->dev, t_config->brp); } void twai_hal_configure_mask_filter(twai_hal_context_t *hal_ctx, uint8_t filter_id, const twai_mask_filter_config_t *f_config) @@ -383,9 +376,10 @@ void twai_hal_format_frame(const twai_hal_trans_desc_t *trans_desc, twai_hal_fra twai_ll_format_frame_buffer(header->id, final_dlc, trans_desc->frame.buffer, msg_flags.flags, frame); } -void twai_hal_parse_frame(const twai_hal_frame_t *frame, twai_frame_header_t *header, uint8_t *buffer, uint8_t buffer_len) +void twai_hal_parse_frame(twai_hal_context_t *hal_ctx, twai_hal_frame_t *frame, twai_frame_header_t *header, uint8_t *buffer, uint8_t buffer_len) { twai_ll_parse_frame_header((const twai_ll_frame_buffer_t *)frame, header); + header->timestamp = 0; // hardware timestamp is not supported in v1 if (!header->rtr) { twai_ll_parse_frame_data((const twai_ll_frame_buffer_t *)frame, buffer, buffer_len); } diff --git a/components/esp_hal_twai/twai_hal_v2.c b/components/esp_hal_twai/twai_hal_v2.c index b477fcd732..62ff17e6f6 100644 --- a/components/esp_hal_twai/twai_hal_v2.c +++ b/components/esp_hal_twai/twai_hal_v2.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 */ @@ -32,6 +32,10 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) twaifd_ll_enable_rxfifo_auto_increase(hal_ctx->dev, true); twaifd_ll_ts_set_sample_point(hal_ctx->dev, TWAIFD_LL_TS_POINT_EOF); twaifd_ll_enable_intr(hal_ctx->dev, config->intr_mask); + if (config->timer_freq) { + twaifd_ll_timer_set_clkdiv(hal_ctx->dev, config->clock_source_hz / config->timer_freq); + twaifd_ll_timer_enable_intr(hal_ctx->dev, 1, true); + } return true; } @@ -40,7 +44,6 @@ void twai_hal_deinit(twai_hal_context_t *hal_ctx) twaifd_ll_set_operate_cmd(hal_ctx->dev, TWAIFD_LL_HW_CMD_RST_TX_CNT); twaifd_ll_set_operate_cmd(hal_ctx->dev, TWAIFD_LL_HW_CMD_RST_RX_CNT); memset(hal_ctx, 0, sizeof(twai_hal_context_t)); - hal_ctx->dev = NULL; } bool twai_hal_check_brp_validation(twai_hal_context_t *hal_ctx, uint32_t brp) @@ -48,7 +51,7 @@ bool twai_hal_check_brp_validation(twai_hal_context_t *hal_ctx, uint32_t brp) return twaifd_ll_check_brp_validation(brp); } -void twai_hal_configure_timing(twai_hal_context_t *hal_ctx, const twai_timing_config_t *t_config) +void twai_hal_configure_timing(twai_hal_context_t *hal_ctx, const twai_timing_advanced_config_t *t_config) { twaifd_ll_set_nominal_bitrate(hal_ctx->dev, t_config); if (t_config->ssp_offset) { @@ -57,7 +60,7 @@ void twai_hal_configure_timing(twai_hal_context_t *hal_ctx, const twai_timing_co } } -void twai_hal_configure_timing_fd(twai_hal_context_t *hal_ctx, const twai_timing_config_t *t_config_fd) +void twai_hal_configure_timing_fd(twai_hal_context_t *hal_ctx, const twai_timing_advanced_config_t *t_config_fd) { twaifd_ll_set_fd_bitrate(hal_ctx->dev, t_config_fd); if (t_config_fd->ssp_offset) { @@ -106,6 +109,20 @@ void twai_hal_stop(twai_hal_context_t *hal_ctx) twaifd_ll_enable_hw(hal_ctx->dev, false); } +void twai_hal_timer_start_with(twai_hal_context_t *hal_ctx, uint64_t preload_value) +{ + hal_ctx->timer_overflow_cnt = preload_value >> 32; + twaifd_ll_timer_set_preload_value(hal_ctx->dev, preload_value); + twaifd_ll_timer_apply_preload_value(hal_ctx->dev); + twaifd_ll_timer_set_preload_value(hal_ctx->dev, 0); // set load value back to 0 + twaifd_ll_timer_enable(hal_ctx->dev, true); +} + +void twai_hal_timer_stop(twai_hal_context_t *hal_ctx) +{ + twaifd_ll_timer_enable(hal_ctx->dev, false); +} + twai_error_state_t twai_hal_get_err_state(twai_hal_context_t *hal_ctx) { return twaifd_ll_get_fault_state(hal_ctx->dev); @@ -126,7 +143,7 @@ uint16_t twai_hal_get_rec(twai_hal_context_t *hal_ctx) return twaifd_ll_get_rec((hal_ctx)->dev); } -// /* ------------------------------------ IRAM Content ------------------------------------ */ +/* ------------------------------------ IRAM Content ------------------------------------ */ void twai_hal_format_frame(const twai_hal_trans_desc_t *trans_desc, twai_hal_frame_t *frame) { @@ -140,8 +157,11 @@ void twai_hal_format_frame(const twai_hal_trans_desc_t *trans_desc, twai_hal_fra } } -void twai_hal_parse_frame(const twai_hal_frame_t *frame, twai_frame_header_t *header, uint8_t *buffer, uint8_t buffer_len) +void twai_hal_parse_frame(twai_hal_context_t *hal_ctx, twai_hal_frame_t *frame, twai_frame_header_t *header, uint8_t *buffer, uint8_t buffer_len) { + // compensate the timer overflow before parsing it + frame->timestamp_high = hal_ctx->timer_overflow_cnt; + twaifd_ll_parse_frame_header(frame, header); if (!header->rtr) { int frame_data_len = twaifd_dlc2len(header->dlc); @@ -166,6 +186,8 @@ uint32_t twai_hal_get_rx_msg_count(twai_hal_context_t *hal_ctx) bool twai_hal_read_rx_fifo(twai_hal_context_t *hal_ctx, twai_hal_frame_t *rx_frame) { twaifd_ll_get_rx_frame(hal_ctx->dev, rx_frame); + // 'not_empty' interrupt can only be cleared after reading the frame + twaifd_ll_clr_intr_status(hal_ctx->dev, TWAIFD_LL_INTR_RX_NOT_EMPTY); return true; } @@ -173,10 +195,16 @@ uint32_t twai_hal_get_events(twai_hal_context_t *hal_ctx) { uint32_t hal_events = 0; uint32_t int_stat = twaifd_ll_get_intr_status(hal_ctx->dev); + uint32_t timer_int_stat = twaifd_ll_timer_get_intr_status(hal_ctx->dev, 0xffffffff); uint32_t tec = twaifd_ll_get_tec(hal_ctx->dev); uint32_t rec = twaifd_ll_get_rec(hal_ctx->dev); twaifd_ll_clr_intr_status(hal_ctx->dev, int_stat); + // check timer event before other events, to avoid race condition that receive frame right on timer overflow + if (timer_int_stat) { + twaifd_ll_timer_clr_intr_status(hal_ctx->dev, timer_int_stat); + 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) { diff --git a/docs/en/api-reference/peripherals/twai.rst b/docs/en/api-reference/peripherals/twai.rst index 88816d588d..6c7bed53c7 100644 --- a/docs/en/api-reference/peripherals/twai.rst +++ b/docs/en/api-reference/peripherals/twai.rst @@ -168,6 +168,13 @@ Receiving messages inside the callback: Similarly, since the driver uses pointers for message passing, you must configure the pointer :cpp:member:`twai_frame_t::buffer` and its memory length :cpp:member:`twai_frame_t::buffer_len` before receiving. +Frame Timestamp +--------------- + +The TWAI driver supports creating a 64-bit timestamp for each successfully received frame, enabling this feature by configuring the :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz` field when creating the node. The timestamp is stored in the :cpp:member:`twai_frame_t::header::timestamp` field of the received frame. + +The node time inherits from the system time, i.e. the time starts from the power-on of the chip, and is not affected by the stop/restart/BUS_OFF state during the node's lifetime. + Stopping and Deleting the Node ------------------------------ diff --git a/docs/zh_CN/api-reference/peripherals/twai.rst b/docs/zh_CN/api-reference/peripherals/twai.rst index dabcf9d500..9ee4876301 100644 --- a/docs/zh_CN/api-reference/peripherals/twai.rst +++ b/docs/zh_CN/api-reference/peripherals/twai.rst @@ -168,6 +168,13 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主 同样,驱动使用指针进行传递,因此需要在接收前配置 :cpp:member:`twai_frame_t::buffer` 的指针及其内存长度 :cpp:member:`twai_frame_t::buffer_len` +报文时间戳 +---------- + +TWAI 驱动支持为每个成功接收的报文创建一个 64 位的时间戳,在创建节点时配置 :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz` 字段即可启用该功能,时间戳保存在接收报文的 :cpp:member:`twai_frame_t::header::timestamp` 字段中。 + +节点时间继承自系统时间,即时间起点同为芯片上电启动时开始计时,期间不受驱动停止/启动/BUS_OFF 状态的影响。 + 停止和删除节点 -------------- diff --git a/examples/peripherals/twai/twai_network/twai_listen_only/main/twai_listen_only.c b/examples/peripherals/twai/twai_network/twai_listen_only/main/twai_listen_only.c index 1851281f18..a0f12ffdd4 100644 --- a/examples/peripherals/twai/twai_network/twai_listen_only/main/twai_listen_only.c +++ b/examples/peripherals/twai/twai_network/twai_listen_only/main/twai_listen_only.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 */ @@ -98,6 +98,7 @@ void app_main(void) .bus_off_indicator = GPIO_NUM_NC, }, .bit_timing.bitrate = TWAI_BITRATE, + .timestamp_resolution_hz = 1000000, .flags.enable_listen_only = true, }; @@ -130,8 +131,10 @@ void app_main(void) while (1) { if (xSemaphoreTake(twai_listener_ctx.rx_result_semaphore, portMAX_DELAY) == pdTRUE) { twai_frame_t *frame = &twai_listener_ctx.rx_pool[twai_listener_ctx.read_idx].frame; - ESP_LOGI(TAG, "RX: %x [%d] %x %x %x %x %x %x %x %x", \ - frame->header.id, frame->header.dlc, frame->buffer[0], frame->buffer[1], frame->buffer[2], frame->buffer[3], frame->buffer[4], frame->buffer[5], frame->buffer[6], frame->buffer[7]); + ESP_LOGI(TAG, "RX: timestamp %llu, %x [%d] %x %x %x %x %x %x %x %x", \ + frame->header.timestamp, frame->header.id, frame->header.dlc, \ + frame->buffer[0], frame->buffer[1], frame->buffer[2], frame->buffer[3], \ + frame->buffer[4], frame->buffer[5], frame->buffer[6], frame->buffer[7]); twai_listener_ctx.read_idx = (twai_listener_ctx.read_idx + 1) % POLL_DEPTH; xSemaphoreGive(twai_listener_ctx.free_pool_semaphore); } diff --git a/examples/peripherals/twai/twai_utils/main/CMakeLists.txt b/examples/peripherals/twai/twai_utils/main/CMakeLists.txt index 01368784f5..6eb34161ca 100644 --- a/examples/peripherals/twai/twai_utils/main/CMakeLists.txt +++ b/examples/peripherals/twai/twai_utils/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRCS "cmd_twai_dump.c" "cmd_twai_send.c" "cmd_twai_core.c" "cmd_twai.c" "twai_utils_main.c" "twai_utils_parser.c" - REQUIRES esp_driver_twai esp_timer esp_driver_gpio console + REQUIRES esp_driver_twai esp_driver_gpio console INCLUDE_DIRS ".") diff --git a/examples/peripherals/twai/twai_utils/main/cmd_twai_core.c b/examples/peripherals/twai/twai_utils/main/cmd_twai_core.c index dcd1c9f309..934cb004ee 100644 --- a/examples/peripherals/twai/twai_utils/main/cmd_twai_core.c +++ b/examples/peripherals/twai/twai_utils/main/cmd_twai_core.c @@ -304,6 +304,9 @@ static int twai_init_handler(int argc, char **argv) #endif ESP_LOGI(TAG, "FD bitrate set to %" PRIu32, ctx->driver_config.data_timing.bitrate); + /* Always configure timestamp frequency to 1MHz */ + ctx->driver_config.timestamp_resolution_hz = 1000000; + /* Start TWAI controller */ controller->node_handle = twai_start(controller); ret = (controller->node_handle != NULL) ? ESP_OK : ESP_FAIL; diff --git a/examples/peripherals/twai/twai_utils/main/cmd_twai_dump.c b/examples/peripherals/twai/twai_utils/main/cmd_twai_dump.c index 6da21a3aca..771ba46769 100644 --- a/examples/peripherals/twai/twai_utils/main/cmd_twai_dump.c +++ b/examples/peripherals/twai/twai_utils/main/cmd_twai_dump.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: Unlicense OR CC0-1.0 */ @@ -17,7 +17,6 @@ #include "esp_twai.h" #include "esp_twai_onchip.h" #include "cmd_twai_internal.h" -#include "esp_timer.h" #include "esp_check.h" #include "twai_utils_parser.h" @@ -27,7 +26,6 @@ */ typedef struct { twai_frame_t frame; /**< TWAI frame with embedded buffer */ - int64_t timestamp_us; /**< Frame timestamp in microseconds */ uint8_t buffer[TWAI_FRAME_BUFFER_SIZE]; /**< Frame data buffer (supports both TWAI and TWAI-FD) */ } rx_queue_item_t; @@ -178,11 +176,9 @@ static IRAM_ATTR bool twai_dump_rx_done_cb(twai_node_handle_t handle, const twai item.frame.buffer_len = sizeof(item.buffer); if (ESP_OK == twai_node_receive_from_isr(handle, &item.frame)) { - item.timestamp_us = esp_timer_get_time(); - /* Non-blocking queue send with explicit error handling */ if (xQueueSendFromISR(controller->dump_ctx.rx_queue, &item, &higher_priority_task_woken) != pdTRUE) { - /* Queue full - frame dropped silently to maintain ISR performance */ + ESP_EARLY_LOGW(TAG, "app queue full"); } } @@ -208,7 +204,7 @@ static void dump_task(void *parameter) if (xQueueReceive(dump_ctx->rx_queue, &item, pdMS_TO_TICKS(CONFIG_EXAMPLE_DUMP_TASK_TIMEOUT_MS)) == pdPASS) { item.frame.buffer = item.buffer; // point to the new buffer - format_twaidump_frame(dump_ctx->timestamp_mode, &item.frame, item.timestamp_us, + format_twaidump_frame(dump_ctx->timestamp_mode, &item.frame, item.frame.header.timestamp, dump_ctx->start_time_us, &dump_ctx->last_frame_time_us, controller_id, output_line, sizeof(output_line)); printf("%s", output_line); @@ -440,11 +436,6 @@ static int twai_dump_handler(int argc, char **argv) } } - /* Initialize timestamp base time */ - int64_t current_time = esp_timer_get_time(); - controller->dump_ctx.start_time_us = current_time; - controller->dump_ctx.last_frame_time_us = current_time; - /* Start dump task and create resources */ ret = twai_dump_start_controller(controller); ESP_RETURN_ON_FALSE(ret == ESP_OK, ret, TAG, "Failed to start dump task"); @@ -512,7 +503,7 @@ void register_twai_dump_commands(void) } /* Register command */ - twai_dump_args.controller_filter = arg_str1(NULL, NULL, "[,filter]", + twai_dump_args.controller_filter = arg_str1(NULL, NULL, " [filter]", "Controller ID and optional filters"); twai_dump_args.stop = arg_lit0(NULL, "stop", "Stop monitoring the specified controller");