mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(driver_twai): fd hardware support time trigger trans
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -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` 对队列重新排序。因此,后提交但触发时间更早的报文不会越过先提交的报文。
|
||||
|
||||
停止和删除节点
|
||||
--------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user