diff --git a/components/driver/test_apps/legacy_twai/pytest_twai.py b/components/driver/test_apps/legacy_twai/pytest_twai.py index 04eef1b5b38..b8103449311 100644 --- a/components/driver/test_apps/legacy_twai/pytest_twai.py +++ b/components/driver/test_apps/legacy_twai/pytest_twai.py @@ -97,22 +97,24 @@ def fixture_create_socket_can() -> Bus: 'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target'] ) def test_legacy_twai_listen_only(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - # TEST_CASE("twai_listen_only", "[twai]") - dut.write('"twai_listen_only"') + # TEST_CASE("twai_listen_only", "[twai]") + dut.write('"twai_listen_only"') - # wait the DUT to start listening - time.sleep(0.1) + # wait the DUT to start listening + time.sleep(0.1) - message = Message( - arbitration_id=0x123, - is_extended_id=False, - data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], - ) - socket_can.send(message, timeout=0.2) - dut.expect_unity_test_output() - esp_enter_flash_mode(dut) + message = Message( + arbitration_id=0x123, + is_extended_id=False, + data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + ) + socket_can.send(message, timeout=0.2) + dut.expect_unity_test_output() + finally: + esp_enter_flash_mode(dut) @pytest.mark.twai_adapter @@ -127,30 +129,31 @@ def test_legacy_twai_listen_only(dut: Dut, socket_can: Bus) -> None: 'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target'] ) def test_legacy_twai_remote_request(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - # TEST_CASE("twai_remote_request", "[twai]") - dut.write('"twai_remote_request"') + # TEST_CASE("twai_remote_request", "[twai]") + dut.write('"twai_remote_request"') - deadline = time.time() + 2.0 - req = None - while time.time() < deadline: - req = socket_can.recv(timeout=0.2) - if req is not None and req.is_remote_frame: - break + deadline = time.time() + 2.0 + req = None + while time.time() < deadline: + req = socket_can.recv(timeout=0.2) + if req is not None and req.is_remote_frame: + break - if req is None: - raise Exception('Remote frame not received') + if req is None: + raise Exception('Remote frame not received') + logging.info(f'Received message: {req}') - logging.info(f'Received message: {req}') + reply = Message( + arbitration_id=req.arbitration_id, + is_extended_id=req.is_extended_id, + data=[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80], + ) + socket_can.send(reply, timeout=0.2) + print('send', reply) - reply = Message( - arbitration_id=req.arbitration_id, - is_extended_id=req.is_extended_id, - data=[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80], - ) - socket_can.send(reply, timeout=0.2) - print('send', reply) - - dut.expect_unity_test_output() - esp_enter_flash_mode(dut) + dut.expect_unity_test_output() + finally: + esp_enter_flash_mode(dut) diff --git a/components/driver/twai/twai.c b/components/driver/twai/twai.c index a82e03b4518..6cb6e55ee44 100644 --- a/components/driver/twai/twai.c +++ b/components/driver/twai/twai.c @@ -818,7 +818,8 @@ esp_err_t twai_receive_v2(twai_handle_t handle, twai_message_t *message, TickTyp message->extd = header.ide; message->rtr = header.rtr; //Set remaining bytes of data to 0 - memset(message->data + message->data_length_code, 0, TWAI_FRAME_MAX_LEN - message->data_length_code); + uint8_t data_length = (header.dlc > TWAI_FRAME_MAX_LEN) ? TWAI_FRAME_MAX_LEN : header.dlc; + memset(message->data + data_length, 0, TWAI_FRAME_MAX_LEN - data_length); return ESP_OK; } diff --git a/components/esp_driver_twai/esp_twai_onchip.c b/components/esp_driver_twai/esp_twai_onchip.c index d68df27c430..cc3a43e0ff8 100644 --- a/components/esp_driver_twai/esp_twai_onchip.c +++ b/components/esp_driver_twai/esp_twai_onchip.c @@ -77,6 +77,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; esp_pm_lock_handle_t pm_lock; @@ -623,7 +624,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"); @@ -700,6 +701,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 SOC_TWAI_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); @@ -711,6 +717,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 @@ -791,6 +798,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); diff --git a/components/esp_driver_twai/include/esp_twai_onchip.h b/components/esp_driver_twai/include/esp_twai_onchip.h index b1aa9de8da4..bde7443a25b 100644 --- a/components/esp_driver_twai/include/esp_twai_onchip.h +++ b/components/esp_driver_twai/include/esp_twai_onchip.h @@ -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; /**< The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */ 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; diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.c b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.c index c8bba930424..68d6905c9ec 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.c +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.c @@ -932,3 +932,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 !SOC_TWAI_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)); +} diff --git a/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py b/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py index 80da4434c5c..6fc9f095ae1 100644 --- a/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py +++ b/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py @@ -88,51 +88,52 @@ def fixture_create_socket_can() -> Bus: @pytest.mark.parametrize('config', ['release'], indirect=True) @idf_parametrize('target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1'), indirect=['target']) def test_driver_twai_listen_only(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - dut.write('"twai_listen_only"') + dut.write('"twai_listen_only"') + # wait the DUT to finish initialize + time.sleep(0.1) - # wait the DUT to finish initialize - time.sleep(0.1) - - message = Message( - arbitration_id=0x6688, - is_extended_id=True, - data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], - ) - print('USB Socket CAN Send:', message, 'Return:', socket_can.send(message)) - dut.expect_unity_test_output(timeout=10) - esp_enter_flash_mode(dut) + message = Message( + arbitration_id=0x6688, + is_extended_id=True, + data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + ) + print('USB Socket CAN Send:', message, 'Return:', socket_can.send(message)) + dut.expect_unity_test_output(timeout=10) + finally: + esp_enter_flash_mode(dut) @pytest.mark.twai_adapter @pytest.mark.parametrize('config', ['release'], indirect=True) @idf_parametrize('target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1'), indirect=['target']) def test_driver_twai_remote_request(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - dut.write('"twai_remote_request"') + dut.write('"twai_remote_request"') + print('Waiting remote frame ...') + deadline = time.time() + 5.0 + req = None + while time.time() < deadline: + req = socket_can.recv(timeout=0.2) + if req is not None and req.is_remote_frame: + break - print('Waiting remote frame ...') - deadline = time.time() + 5.0 - req = None - while time.time() < deadline: - req = socket_can.recv(timeout=0.2) - if req is not None and req.is_remote_frame: - break + if req is None: + raise Exception('Remote frame not received') + print(f'USB Socket CAN Received: {req}') - if req is None: - raise Exception('Remote frame not received') + reply = Message( + arbitration_id=req.arbitration_id, + is_extended_id=req.is_extended_id, + data=[0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10], + ) + socket_can.send(reply, timeout=0.2) + print('USB Socket CAN Replied:', reply) - print(f'USB Socket CAN Received: {req}') - - reply = Message( - arbitration_id=req.arbitration_id, - is_extended_id=req.is_extended_id, - data=[0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10], - ) - socket_can.send(reply, timeout=0.2) - print('USB Socket CAN Replied:', reply) - - dut.expect_unity_test_output(timeout=10) - esp_enter_flash_mode(dut) + dut.expect_unity_test_output(timeout=10) + finally: + esp_enter_flash_mode(dut) diff --git a/components/hal/include/hal/twai_hal.h b/components/hal/include/hal/twai_hal.h index 36bbf1c52f8..98edcd0b42f 100644 --- a/components/hal/include/hal/twai_hal.h +++ b/components/hal/include/hal/twai_hal.h @@ -108,6 +108,7 @@ typedef struct { bool enable_self_test; bool enable_loopback; bool enable_listen_only; + bool enable_time_trigger_tx; } twai_hal_config_t; /** diff --git a/components/hal/include/hal/twai_types.h b/components/hal/include/hal/twai_types.h index 34e4e0930b6..f525be2a6ee 100644 --- a/components/hal/include/hal/twai_types.h +++ b/components/hal/include/hal/twai_types.h @@ -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; diff --git a/components/hal/twai_hal_v2.c b/components/hal/twai_hal_v2.c index 1ec21d7f181..c3a0a707ece 100644 --- a/components/hal/twai_hal_v2.c +++ b/components/hal/twai_hal_v2.c @@ -26,6 +26,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_block_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_rxfifo_auto_incrase(hal_ctx->dev, true); diff --git a/docs/en/api-reference/peripherals/twai.rst b/docs/en/api-reference/peripherals/twai.rst index 236080b7cc8..cadef906148 100644 --- a/docs/en/api-reference/peripherals/twai.rst +++ b/docs/en/api-reference/peripherals/twai.rst @@ -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 ------------------------------ diff --git a/docs/zh_CN/api-reference/peripherals/twai.rst b/docs/zh_CN/api-reference/peripherals/twai.rst index 987a9f05665..448210e244d 100644 --- a/docs/zh_CN/api-reference/peripherals/twai.rst +++ b/docs/zh_CN/api-reference/peripherals/twai.rst @@ -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` 对队列重新排序。因此,后提交但触发时间更早的报文不会越过先提交的报文。 + 停止和删除节点 -------------- diff --git a/examples/peripherals/twai/twai_network/pytest_twai_network.py b/examples/peripherals/twai/twai_network/pytest_twai_network.py index 76c2f0c086a..bda19d6775f 100644 --- a/examples/peripherals/twai/twai_network/pytest_twai_network.py +++ b/examples/peripherals/twai/twai_network/pytest_twai_network.py @@ -3,11 +3,13 @@ import os import os.path import subprocess +import time from typing import Tuple import pytest from can import Bus from can import Message +from pytest_embedded import Dut from pytest_embedded_idf import IdfDut can_env = os.getenv('CAN_PORT', 'can0') @@ -26,6 +28,16 @@ def fixture_create_socket_can() -> Bus: subprocess.run(stop_command, shell=True, capture_output=True, text=True) +def esp_enter_flash_mode(dut: Dut) -> None: + ser = dut.serial.proc + ser.setRTS(True) # EN Low + time.sleep(0.5) + ser.setDTR(True) # GPIO0 Low + ser.setRTS(False) # EN High + dut.expect('waiting for download', timeout=2) + ser.setDTR(False) # Back RTS/DTR to 1/1 to avoid affect to esptool + + @pytest.mark.twai_adapter @pytest.mark.parametrize('count', [2], indirect=True) @pytest.mark.timeout(120) @@ -57,45 +69,50 @@ def test_twai_network_multi(dut: Tuple[IdfDut, IdfDut], socket_can: Bus) -> None - dut[1]: twai_sender """ - # Print chip information for debugging - print(f'===> Pytest testing with chips: {dut[0].app.target} (listener), {dut[1].app.target} (sender)') + try: + # Print chip information for debugging + print(f'===> Pytest testing with chips: {dut[0].app.target} (listener), {dut[1].app.target} (sender)') - # Initialize listener node first - dut[0].expect('===================TWAI Listen Only Example Starting...===================') - dut[0].expect('TWAI start listening...') + # Initialize listener node first + dut[0].expect('===================TWAI Listen Only Example Starting...===================') + dut[0].expect('TWAI start listening...') - # Initialize sender node and start communication - dut[1].expect('===================TWAI Sender Example Starting...===================') - dut[1].expect('TWAI Sender started successfully') + # Initialize sender node and start communication + dut[1].expect('===================TWAI Sender Example Starting...===================') + dut[1].expect('TWAI Sender started successfully') - # Verify communication is working - # Wait for sender to send messages - dut[1].expect('Sending heartbeat message:', timeout=10) + # Verify communication is working + # Wait for sender to send messages + dut[1].expect('Sending heartbeat message:', timeout=10) - # Check that listener is receiving data - dut[0].expect('RX:', timeout=15) # Listener should see filtered messages + # Check that listener is receiving data + dut[0].expect('RX:', timeout=15) # Listener should see filtered messages - # Check if socket receive any messages - socket_rcv_cnt = 0 - for i in range(100): - msg = socket_can.recv(timeout=1) - if msg is not None: - socket_rcv_cnt += 1 - print(f'Socket receive {socket_rcv_cnt} messages') - assert socket_rcv_cnt > 50, 'Socket NO messages' + # Check if socket receive any messages + socket_rcv_cnt = 0 + for i in range(100): + msg = socket_can.recv(timeout=1) + if msg is not None: + socket_rcv_cnt += 1 + print(f'Socket receive {socket_rcv_cnt} messages') + assert socket_rcv_cnt > 50, 'Socket NO messages' - # Wait a bit more to ensure stable communication - dut[1].expect('Sending packet of', timeout=10) - dut[0].expect('RX:', timeout=10) + # Wait a bit more to ensure stable communication + dut[1].expect('Sending packet of', timeout=10) + dut[0].expect('RX:', timeout=10) - # Check if esp32 receive messages from usb can - message = Message( - arbitration_id=0x10A, - is_extended_id=False, - data=b'Hi ESP32', - ) - print('USB CAN Send:', message) - socket_can.send(message, timeout=0.2) - dut[0].expect_exact('10a [8] 48 69 20 45 53 50 33 32', timeout=10) # ASCII: Hi ESP32 + # Check if esp32 receive messages from usb can + message = Message( + arbitration_id=0x10A, + is_extended_id=False, + data=b'Hi ESP32', + ) + print('USB CAN Send:', message) + socket_can.send(message, timeout=0.2) + dut[0].expect_exact('10a [8] 48 69 20 45 53 50 33 32', timeout=10) # ASCII: Hi ESP32 - print('===> TWAI network communication test completed successfully') + print('===> TWAI network communication test completed successfully') + + finally: + esp_enter_flash_mode(dut[0]) + esp_enter_flash_mode(dut[1]) diff --git a/pytest.ini b/pytest.ini index b4cc5ea234c..842e3a399ef 100644 --- a/pytest.ini +++ b/pytest.ini @@ -123,7 +123,6 @@ env_markers = sdio_multidev_32_c61: Test sdio multi board, esp32+esp32c61 sdio_multidev_p4_c5: Test sdio multi board, esp32p4+esp32c5 usj_device: Test usb_serial_jtag and usb_serial_jtag is used as serial only (not console) - twai_std: twai runner with all twai supported targets connect to usb-can adapter twai_adapter: runner with multiple twai_std lp_i2s: lp_i2s runner tested with hp_i2s ram_app: ram_app runners