feat(driver_twai): support rx frame timestamp for all chips

Closes https://github.com/espressif/esp-idf/issues/4527
This commit is contained in:
wanckl
2026-01-16 16:33:37 +08:00
committed by Wan Lei
parent 68a0fc64e5
commit e4753c8019
18 changed files with 246 additions and 55 deletions
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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")
+65 -10
View File
@@ -4,6 +4,7 @@
* 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,10 +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;
@@ -73,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;
@@ -81,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) {
@@ -91,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;
@@ -99,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)
@@ -298,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);
}
@@ -410,6 +417,18 @@ static esp_err_t _node_calc_set_bit_timing(twai_node_handle_t node, const twai_t
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)
@@ -424,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
@@ -440,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);
@@ -606,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;
}
@@ -626,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);
@@ -634,9 +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");
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
@@ -647,9 +703,6 @@ 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
node->curr_clk_src = node_config->clk_src ? node_config->clk_src : TWAI_CLK_SRC_DEFAULT;
esp_clk_tree_src_get_freq_hz(node->curr_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &node->src_freq_hz);
// 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);
@@ -661,6 +714,7 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
.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,
@@ -692,6 +746,7 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
return ESP_OK;
err:
if (node) {
_lock_release(&s_platform.intr_mutex);
_node_destroy(node);
}
return ret;
@@ -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] */
@@ -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)
{
@@ -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));
}
}
@@ -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;
@@ -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;
+18 -2
View File
@@ -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
@@ -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
+2 -1
View File
@@ -376,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);
}
+32 -4
View File
@@ -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)
@@ -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) {
@@ -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
------------------------------
@@ -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 状态的影响。
停止和删除节点
--------------
@@ -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);
}
@@ -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 ".")
@@ -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;
@@ -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, "<controller>[,filter]",
twai_dump_args.controller_filter = arg_str1(NULL, NULL, "<controller> [filter]",
"Controller ID and optional filters");
twai_dump_args.stop = arg_lit0(NULL, "stop",
"Stop monitoring the specified controller");