feat(driver_twai): fd hardware support time trigger trans

This commit is contained in:
wanckl
2026-06-10 17:32:20 +08:00
parent a415ba1fcf
commit 5912fe2107
8 changed files with 149 additions and 3 deletions

View File

@@ -53,6 +53,7 @@ typedef struct {
uint32_t src_freq_hz;
uint32_t timestamp_freq_hz;
uint32_t valid_fd_timing;
bool enable_scheduled_tx;
twai_event_callbacks_t cbs;
void *user_data;
#ifdef CONFIG_PM_ENABLE
@@ -601,7 +602,7 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
ESP_RETURN_ON_FALSE_ISR(!frame->header.fdf || frame->buffer_len <= TWAI_FRAME_MAX_LEN, ESP_ERR_INVALID_ARG, TAG, "fdf flag or buffer_len not supported");
#endif
ESP_RETURN_ON_FALSE_ISR((frame->header.dlc <= TWAIFD_FRAME_MAX_DLC) && \
(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN)), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld)", frame->buffer_len);
(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN)), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld, dlc %d)", frame->buffer_len, frame->header.dlc);
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");
@@ -678,6 +679,11 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
ESP_RETURN_ON_FALSE(!node_config->intr_priority || (BIT(node_config->intr_priority) & ESP_INTR_FLAG_LOWMED), ESP_ERR_INVALID_ARG, TAG, "Invalid intr_priority level");
#if !SOC_TWAI_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(!node_config->flags.sleep_allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "sleep retention is not supported on this target");
#endif
#if TWAI_LL_SUPPORT(TIMESTAMP)
ESP_RETURN_ON_FALSE(!node_config->flags.enable_scheduled_tx || node_config->timestamp_resolution_hz, ESP_ERR_INVALID_ARG, TAG, "enable_scheduled_tx requires timestamp_resolution_hz");
#else
ESP_RETURN_ON_FALSE(!node_config->flags.enable_scheduled_tx, ESP_ERR_NOT_SUPPORTED, TAG, "enable_scheduled_tx is not supported on this chip");
#endif
// Allocate TWAI node from internal memory because it contains atomic variable
twai_onchip_ctx_t *node = heap_caps_calloc(1, sizeof(twai_onchip_ctx_t) + twai_hal_get_mem_requirment(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
@@ -689,6 +695,7 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
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;
node->enable_scheduled_tx = node_config->flags.enable_scheduled_tx;
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
@@ -776,6 +783,7 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
.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,
.enable_time_trigger_tx = node->enable_scheduled_tx,
};
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);

View File

@@ -26,7 +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 */
uint32_t timestamp_resolution_hz; /**< Timebase frequency (in Hz), used for RX frame timestamps and scheduled TX trigger times, 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] */
@@ -36,6 +36,8 @@ typedef struct {
uint32_t enable_listen_only: 1; /**< No transmissions or acknowledgements. The controller only monitors the bus without participating */
uint32_t no_receive_rtr: 1; /**< Don't receive remote frames */
uint32_t sleep_allow_pd: 1; /**< Allow power down during sleep to save power, driver will backup/restore the TWAI registers to guarantee the peripheral features. */
uint32_t enable_scheduled_tx: 1; /**< Schedule TX mode, if enabled, the tx frame will actually send until `twai_frame_t::header.trigger_time` is reached,
Feature depends on hardware support, and `timestamp_resolution_hz` must be set. */
} flags; /**< Misc configuration flags */
} twai_onchip_node_config_t;

View File

@@ -923,3 +923,79 @@ TEST_CASE("twai rx timestamp", "[twai]")
TEST_ESP_OK(twai_node_delete(node_hdl));
}
}
TEST_CASE("twai schedule transmit", "[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;
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 = 100000;
node_config.tx_queue_depth = 10;
node_config.flags.enable_loopback = true;
node_config.flags.enable_self_test = true;
node_config.flags.enable_scheduled_tx = true;
printf("Testing schedule feature check\n");
#if !TWAI_LL_SUPPORT(TIMESTAMP)
TEST_ESP_ERR(twai_new_node_onchip(&node_config, &node_hdl), ESP_ERR_NOT_SUPPORTED);
return;
#endif
TEST_ESP_ERR(twai_new_node_onchip(&node_config, &node_hdl), ESP_ERR_INVALID_ARG);
node_config.timestamp_resolution_hz = 1000000;
TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl));
twai_frame_t rx_frame = {};
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[6] = {};
uint64_t time_now = esp_timer_get_time();
printf("time_now = %llu\n", time_now);
printf("Testing schedule frame 1 blocks immediate frame 2\n");
tx_frame[0].header.id = 1;
tx_frame[0].header.trigger_time = time_now + 1000000;
tx_frame[1].header.id = 2;
tx_frame[1].header.trigger_time = 0; // set 0 to send immediately but will be blocked by 1st frame
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame[0], 100));
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame[1], 100));
TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, -1));
// should receive 2nd frame in same time
TEST_ASSERT_EQUAL(tx_frame[1].header.id, rx_frame.header.id);
TEST_ASSERT_INT32_WITHIN(1000000 / 100, 1000000, rx_frame.header.timestamp - time_now);
printf("\nTesting schedule time sequence\n");
time_now = esp_timer_get_time();
tx_frame[0].header.trigger_time = time_now + 1000000;
for (int i = 1; i < 6; i++) {
tx_frame[i].header.id = i;
tx_frame[i].header.trigger_time = tx_frame[i - 1].header.trigger_time + i * 1000000;
printf("Schedule frame %d after %lld s\n", i, (tx_frame[i].header.trigger_time - time_now) / 1000000);
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame[i], 0));
}
printf("\nWaiting for checking result\n");
uint64_t last_time = rx_frame.header.timestamp;
for (int i = 0; i < 5; i++) {
time_now = esp_timer_get_time();
int second_cnt = 0;
while (rx_frame.header.timestamp == last_time) {
vTaskDelay(1);
second_cnt++;
if (second_cnt % 1000 == 0) { // print time every 1 second
esp_rom_printf("%d ", second_cnt / 1000);
}
}
last_time = rx_frame.header.timestamp;
printf("\nFrame %d received after %d s\n", i, second_cnt / 1000);
TEST_ASSERT_INT32_WITHIN(1000000 / 100, second_cnt * 1000, rx_frame.header.timestamp - time_now);
}
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
}

View File

@@ -101,6 +101,7 @@ typedef struct {
bool enable_self_test;
bool enable_loopback;
bool enable_listen_only;
bool enable_time_trigger_tx;
} twai_hal_config_t;
/**

View File

@@ -122,7 +122,7 @@ typedef struct {
};
union {
uint64_t timestamp; /**< Timestamp for received message */
uint64_t trigger_time; /**< Trigger time for transmitting message*/
uint64_t trigger_time; /**< Trigger time for transmitting message in scheduled TX mode */
};
} twai_frame_header_t;

View File

@@ -27,6 +27,7 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config)
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);
twaifd_ll_filter_drop_rtr(hal_ctx->dev, config->no_receive_rtr);
twaifd_ll_enable_time_trig_trans_mode(hal_ctx->dev, config->enable_time_trigger_tx);
twaifd_ll_enable_filter_mode(hal_ctx->dev, true); // each filter still has independent enable control
twaifd_ll_enable_fd_mode(hal_ctx->dev, true); // fd frame still controlled by `header.fdf`
twaifd_ll_enable_bus_off_tx_fail_mode(hal_ctx->dev, true); // all buffers go to "TX failed" state upon bus-off

View File

@@ -90,6 +90,7 @@ Below are additional configuration fields of the :cpp:type:`twai_onchip_node_con
- :cpp:member:`twai_onchip_node_config_t::flags::enable_loopback`: Enables loopback mode. The node will receive its own transmitted messages (subject to filter configuration), while also transmitting them to the bus.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_listen_only`: Configures the node in listen-only mode. In this mode, the node only receives and does not transmit any dominant bits, including ACK and error frames.
- :cpp:member:`twai_onchip_node_config_t::flags::no_receive_rtr`: When using filters, determines whether remote frames matching the ID pattern should be filtered out.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_scheduled_tx`: Enables scheduled transmission. This option requires a non-zero :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz`.
The :cpp:func:`twai_node_enable` function starts the TWAI controller. Once enabled, the controller is connected to the bus and can transmit messages. It also generates events upon receiving messages from other nodes on the bus or when bus errors are detected.
@@ -176,6 +177,34 @@ The TWAI driver supports creating a 64-bit timestamp for each successfully recei
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.
.. only:: SOC_TWAI_FD_SUPPORTED
Scheduled Transmission
----------------------
The {IDF_TARGET_NAME} TWAI supports schedule a transmitted frame by trigger time. Enable :cpp:member:`twai_onchip_node_config_t::flags::enable_scheduled_tx` and set :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz` when creating the node, then fill :cpp:member:`twai_frame_t::header::trigger_time` before calling :cpp:func:`twai_node_transmit`. The trigger time uses the same timebase as received frame timestamps.
.. code:: c
twai_onchip_node_config_t node_config = {
.io_cfg.tx = 4,
.io_cfg.rx = 5,
.bit_timing.bitrate = 500000,
.timestamp_resolution_hz = 1000, // 1 tick = 1 ms
.tx_queue_depth = 4,
.flags.enable_scheduled_tx = true,
};
twai_frame_t tx_msg = {
.header.id = 0x10,
.header.trigger_time = 2000, // transmit when node timestamp reaches 2000 ticks
};
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0));
.. note::
If the frame's trigger time has already been reached when the frame is ready to transmit, the driver starts transmitting it immediately. When multiple scheduled frames are queued, the driver processes them in software submission order. Frames are not reordered by :cpp:member:`twai_frame_t::header::trigger_time`, so a later-submitted frame with an earlier trigger time cannot overtake frames submitted before it.
Stopping and Deleting the Node
------------------------------

View File

@@ -90,6 +90,7 @@ TWAI 是一种适用于汽车和工业应用的高可靠性的多主机实时串
- :cpp:member:`twai_onchip_node_config_t::flags::enable_loopback` 使能自收发模式,节点会收到自己发送的报文(如果配置了过滤器则还需要符合过滤规则),同时也会发送到总线。
- :cpp:member:`twai_onchip_node_config_t::flags::enable_listen_only` 配置为监听模式,节点只接收,不发送任何显性位,包括 ACK 和错误帧。
- :cpp:member:`twai_onchip_node_config_t::flags::no_receive_rtr` 使用过滤器时是否同时过滤掉符合 ID 规则的远程帧。
- :cpp:member:`twai_onchip_node_config_t::flags::enable_scheduled_tx` 使能定时发送。该功能必须配置非零的 :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz`
函数 :cpp:func:`twai_node_enable` 将启动 TWAI 控制器,此时 TWAI 控制器就连接到了总线,可以向总线发送报文。如果收到了总线上其他节点发送的报文,或者检测到了总线错误,也将产生相应事件。
@@ -176,6 +177,34 @@ TWAI 驱动支持为每个成功接收的报文创建一个 64 位的时间戳
节点时间继承自系统时间,即时间起点同为芯片上电启动时开始计时,期间不受驱动停止/启动/BUS_OFF 状态的影响。
.. only:: SOC_TWAI_FD_SUPPORTED
定时发送
--------
{IDF_TARGET_NAME} TWAI 支持定时发送报文。创建节点时使能 :cpp:member:`twai_onchip_node_config_t::flags::enable_scheduled_tx` 并设置 :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz`,然后在调用 :cpp:func:`twai_node_transmit` 前填写 :cpp:member:`twai_frame_t::header::trigger_time`。触发时间使用与接收报文 timestamp 相同的时间基准。
.. code:: c
twai_onchip_node_config_t node_config = {
.io_cfg.tx = 4,
.io_cfg.rx = 5,
.bit_timing.bitrate = 500000,
.timestamp_resolution_hz = 1000, // 1 tick = 1 ms
.tx_queue_depth = 4,
.flags.enable_scheduled_tx = true,
};
twai_frame_t tx_msg = {
.header.id = 0x10,
.header.trigger_time = 2000, // 当节点 timestamp 到达 2000 ticks 时发送
};
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0));
.. note::
如果报文准备发送时,其触发时间已经到达或已经过去,驱动会立即开始发送该报文。多个定时报文同时排队时,驱动按软件提交顺序处理,不会按照 :cpp:member:`twai_frame_t::header::trigger_time` 对队列重新排序。因此,后提交但触发时间更早的报文不会越过先提交的报文。
停止和删除节点
--------------