diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index 16cebcc432..900db69d71 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -81,9 +81,9 @@ if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED) endif() # TWAI related source files -if(CONFIG_SOC_TWAI_SUPPORTED) +# TWAIFD is not supported by the legacy driver +if(CONFIG_SOC_TWAI_SUPPORTED AND NOT CONFIG_SOC_TWAI_SUPPORT_FD) list(APPEND srcs "twai/twai.c") - list(APPEND ldfragments "twai/linker.lf") endif() diff --git a/components/driver/test_apps/.build-test-rules.yml b/components/driver/test_apps/.build-test-rules.yml index 959c0663d6..e0eaa73687 100644 --- a/components/driver/test_apps/.build-test-rules.yml +++ b/components/driver/test_apps/.build-test-rules.yml @@ -82,7 +82,8 @@ components/driver/test_apps/touch_sensor_v2: components/driver/test_apps/twai: disable: - - if: SOC_TWAI_SUPPORTED != 1 + - if: SOC_TWAI_SUPPORTED != 1 or SOC_TWAI_SUPPORT_FD == 1 + reason: legacy driver doesn't support FD depends_filepatterns: - components/driver/twai/**/* depends_components: diff --git a/components/esp_driver_twai/CMakeLists.txt b/components/esp_driver_twai/CMakeLists.txt new file mode 100644 index 0000000000..2be8f239f3 --- /dev/null +++ b/components/esp_driver_twai/CMakeLists.txt @@ -0,0 +1,22 @@ +idf_build_get_property(target IDF_TARGET) + +if(${target} STREQUAL "linux") + return() # This component is not supported by the POSIX/Linux simulator +endif() + +set(srcs "esp_twai.c") +set(public_include "include") +set(priv_req esp_driver_gpio esp_pm) + +# Currently support only FD targets +if(CONFIG_SOC_TWAI_SUPPORT_FD) + list(APPEND srcs "onchip/esp_twai_onchip.c") + list(APPEND public_include "onchip/include") +endif() + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS ${public_include} + PRIV_REQUIRES ${priv_req} + LDFRAGMENTS "linker.lf" +) diff --git a/components/esp_driver_twai/Kconfig b/components/esp_driver_twai/Kconfig new file mode 100644 index 0000000000..36ac8440a0 --- /dev/null +++ b/components/esp_driver_twai/Kconfig @@ -0,0 +1,18 @@ +menu "ESP-Driver:TWAI Configurations" + depends on SOC_TWAI_SUPPORTED + + config TWAI_ISR_INTO_IRAM + bool "Place TWAI ISR in IRAM to reduce latency" + default n + help + Place ISR functions to IRAM to increase performance + + config TWAI_ISR_CACHE_SAFE + bool "Allow TWAI ISR to execute when cache is disabled" + select TWAI_ISR_INTO_IRAM + default n + help + Allow TWAI works under Cache disabled, to enabled this config, + all callbacks and user_ctx should also place in IRAM + +endmenu # TWAI Configuration diff --git a/components/esp_driver_twai/esp_twai.c b/components/esp_driver_twai/esp_twai.c new file mode 100644 index 0000000000..ed013ef574 --- /dev/null +++ b/components/esp_driver_twai/esp_twai.c @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_check.h" +#include "esp_twai.h" +#include "esp_private/twai_interface.h" +#include "esp_private/twai_priv.h" + +static const char *TAG = "esp_twai"; + +/** + * @brief Calculate twai timing param by giving bitrate and hardware limit. + * + * +---------------------------------------------------+ + * | bit time in time quanta (total_tq) | + * +--------------+----------+------------+------------+ + * | sync_seg | prop_seg | phase_seg1 | phase_seg2 | + * +--------------+----------+------------+------------+ + * | 1 | tseg1 | tseg2 | + * +--------------+----------+------------+------------+ + * | tseg2/2 ^ ^ + * sjw sample_point + */ +uint32_t twai_node_timing_calc_param(const uint32_t source_freq, const twai_timing_basic_config_t *in_param, const twai_timing_constraint_t *hw_limit, twai_timing_advanced_config_t *out_param) +{ + uint32_t total_div = (source_freq + in_param->bitrate / 2) / in_param->bitrate; + uint32_t pre_div = hw_limit->brp_min; + uint16_t tseg = 0; + for (; pre_div <= hw_limit->brp_max; pre_div ++) { + tseg = total_div / pre_div; + if (total_div != tseg * pre_div) { + continue; // no integer tseg + } + if ((tseg < (hw_limit->tseg1_max + hw_limit->tseg2_max)) && (tseg >= (hw_limit->tseg1_min + hw_limit->tseg2_min))) { + break; + } + } + if (pre_div > hw_limit->brp_max) { // no valid pre_div + return 0; + } + + uint16_t default_point = (in_param->bitrate >= 800000) ? 750 : ((in_param->bitrate >= 500000) ? 800 : 875); + uint16_t sample_point = in_param->sample_point ? in_param->sample_point : default_point; // default sample point based on bitrate if not configured + uint16_t tseg_1 = (tseg * sample_point) / 1000; + tseg_1 = MAX(hw_limit->tseg1_min, MIN(tseg_1, hw_limit->tseg1_max)); + uint16_t tseg_2 = tseg - tseg_1 - 1; + tseg_2 = MAX(hw_limit->tseg2_min, MIN(tseg_2, hw_limit->tseg2_max)); + tseg_1 = tseg - tseg_2 - 1; + uint16_t prop = tseg_1 / 2; // distribute tseg1 evenly between prop_seg and tseg_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; + out_param->tseg_2 = tseg_2; + out_param->sjw = MAX(1, MIN(tseg_2 >> 1, hw_limit->sjw_max)); + out_param->ssp_offset = (tseg * in_param->ssp_permill) / 1000; // ssp is optional, default 0 if not configured + + return source_freq / (pre_div * (prop + tseg_1 + tseg_2 + 1)); +} + +esp_err_t twai_node_enable(twai_node_handle_t node) +{ + ESP_RETURN_ON_FALSE(node, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null handle"); + ESP_RETURN_ON_FALSE(node->enable, ESP_ERR_NOT_SUPPORTED, TAG, "enable func null"); + + return node->enable(node); +} + +esp_err_t twai_node_disable(twai_node_handle_t node) +{ + ESP_RETURN_ON_FALSE(node, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null handle"); + ESP_RETURN_ON_FALSE(node->disable, ESP_ERR_NOT_SUPPORTED, TAG, "disable func null"); + + return node->disable(node); +} + +esp_err_t twai_node_delete(twai_node_handle_t node) +{ + ESP_RETURN_ON_FALSE(node, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null handle"); + ESP_RETURN_ON_FALSE(node->del, ESP_ERR_NOT_SUPPORTED, TAG, "delete func null"); + + return node->del(node); +} + +esp_err_t twai_node_config_mask_filter(twai_node_handle_t node, uint8_t filter_id, const twai_mask_filter_config_t *mask_cfg) +{ + ESP_RETURN_ON_FALSE(node && mask_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null"); + ESP_RETURN_ON_FALSE(node->config_mask_filter, ESP_ERR_NOT_SUPPORTED, TAG, "config_mask_filter func null"); + + return node->config_mask_filter(node, filter_id, mask_cfg); +} + +esp_err_t twai_node_config_range_filter(twai_node_handle_t node, uint8_t filter_id, const twai_range_filter_config_t *range_cfg) +{ + ESP_RETURN_ON_FALSE(node && range_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null"); + ESP_RETURN_ON_FALSE(node->config_range_filter, ESP_ERR_NOT_SUPPORTED, TAG, "config_range_filter func null"); + + return node->config_range_filter(node, filter_id, range_cfg); +} + +esp_err_t twai_node_reconfig_timing(twai_node_handle_t node, const twai_timing_advanced_config_t *bit_timing, const twai_timing_advanced_config_t *data_timing) +{ + ESP_RETURN_ON_FALSE(node && (bit_timing || data_timing), ESP_ERR_INVALID_ARG, TAG, "invalid argument: null"); + ESP_RETURN_ON_FALSE(node->timing_reconfig, ESP_ERR_NOT_SUPPORTED, TAG, "timing_reconfig func null"); + + return node->timing_reconfig(node, bit_timing, data_timing); +} + +esp_err_t twai_node_register_event_callbacks(twai_node_handle_t node, const twai_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(node && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null"); + ESP_RETURN_ON_FALSE(node->register_cbs, ESP_ERR_NOT_SUPPORTED, TAG, "register_cbs func null"); + + return node->register_cbs(node, cbs, user_data); +} + +esp_err_t twai_node_recover(twai_node_handle_t node) +{ + ESP_RETURN_ON_FALSE(node, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null handle"); + ESP_RETURN_ON_FALSE(node->recover, ESP_ERR_NOT_SUPPORTED, TAG, "recover func null"); + + return node->recover(node); +} + +esp_err_t twai_node_get_info(twai_node_handle_t node, twai_node_status_t *status_ret, twai_node_record_t *statistics_ret) +{ + ESP_RETURN_ON_FALSE(node, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null handle"); + ESP_RETURN_ON_FALSE(node->get_info, ESP_ERR_NOT_SUPPORTED, TAG, "get_info func null"); + + return node->get_info(node, status_ret, statistics_ret); +} + +esp_err_t twai_node_transmit(twai_node_handle_t node, const twai_frame_t *frame, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(node && frame, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null"); + ESP_RETURN_ON_FALSE(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit func null"); + + return node->transmit(node, frame, timeout_ms); +} + +esp_err_t twai_node_receive_from_isr(twai_node_handle_t node, twai_frame_header_t *header, uint8_t *rx_buffer, size_t buf_sz, size_t *received_len) +{ + ESP_RETURN_ON_FALSE_ISR(node && header, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null"); + ESP_RETURN_ON_FALSE_ISR(node->receive_isr, ESP_ERR_NOT_SUPPORTED, TAG, "receive func null"); + + return node->receive_isr(node, header, rx_buffer, buf_sz, received_len); +} diff --git a/components/esp_driver_twai/include/esp_private/twai_interface.h b/components/esp_driver_twai/include/esp_private/twai_interface.h new file mode 100644 index 0000000000..c9f20198fa --- /dev/null +++ b/components/esp_driver_twai/include/esp_private/twai_interface.h @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "esp_twai_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ESP TWAI driver API set definition + */ +struct twai_node_base { + /** + * @brief Enable the TWAI node + * + * @param[in] node Pointer to the TWAI node base + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Failed to enable the node + */ + esp_err_t (*enable)(struct twai_node_base *node); + + /** + * @brief Disable the TWAI node + * + * @param[in] node Pointer to the TWAI node base + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Failed to disable the node + */ + esp_err_t (*disable)(struct twai_node_base *node); + + /** + * @brief Delete the TWAI node and free its resources + * + * @param[in] node Pointer to the TWAI node base + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Failed to delete the node + */ + esp_err_t (*del)(struct twai_node_base *node); + + /** + * @brief Configure the mask filter of the TWAI node + * + * @param node Pointer to the TWAI node base + * @param filter_id Index of the filter to configure + * @param mask_cfg Pointer to the mask filter configuration + * @return ESP_OK on success, error code otherwise + */ + esp_err_t (*config_mask_filter)(struct twai_node_base *node, uint8_t filter_id, const twai_mask_filter_config_t *mask_cfg); + + /** + * @brief Configure the range filter of the TWAI node + * + * @param node Pointer to the TWAI node base + * @param filter_id Index of the filter to configure + * @param range_cfg Pointer to the range filter configuration + * @return ESP_OK on success, error code otherwise + */ + esp_err_t (*config_range_filter)(struct twai_node_base *node, uint8_t filter_id, const twai_range_filter_config_t *range_cfg); + + /** + * @brief Reconfigure the timing parameters for the TWAI node + * + * @param[in] node Pointer to the TWAI node base + * @param bit_timing Pointer to new twai cc(classic) or arbitration phase of twai fd timing configuration + * @param data_timing Pointer to new twai fd timing configuration + * @return esp_err_t + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid timing configuration + */ + esp_err_t (*timing_reconfig)(struct twai_node_base *node, const twai_timing_advanced_config_t *bit_timing, const twai_timing_advanced_config_t *data_timing); + + /** + * @brief Transmit a TWAI frame through the TWAI node + * + * @param[in] node Pointer to the TWAI node base + * @param[in] frame Pointer to the frame to transmit + * @param[in] timeout Timeout in milliseconds + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Failed to transmit frame + */ + esp_err_t (*transmit)(struct twai_node_base *node, const twai_frame_t *frame, int timeout); + + /** + * @brief Receive a TWAI frame through the ISR callback + * + * @param[in] node Pointer to the TWAI node base + * @param[out] header Where to store frame header + * @param[out] rx_buffer Where to store frame data + * @param[in] buf_sz Bytes length of rx_buffer + * @param[out] received_len Optional, the real data length of rx frame comes from 'header->dlc' + * @return esp_err_t + * - ESP_OK: Success + * - ESP_ERR_TIMEOUT: Reception timeout + */ + esp_err_t (*receive_isr)(struct twai_node_base *node, twai_frame_header_t *header, uint8_t *rx_buffer, size_t buf_sz, size_t *received_len); + + /** + * @brief Recover the TWAI node from a bus-off state + * + * @param[in] node Pointer to the TWAI node base + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Failed to recover node + */ + esp_err_t (*recover)(struct twai_node_base *node); + + /** + * @brief Register event callbacks for the TWAI node + * + * @param[in] node Pointer to the TWAI node base + * @param[in] cbs Pointer to the event callback configuration + * @param[in] user_data User data to pass to the callbacks + * @return esp_err_t + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid callback configuration + */ + esp_err_t (*register_cbs)(struct twai_node_base *node, const twai_event_callbacks_t *cbs, void *user_data); + + /** + * @brief Get the status and statistics of the TWAI node + * + * @param[in] node Pointer to the TWAI node base + * @param[out] status_ret Pointer to return the node status + * @param[out] record_ret Pointer to return the node statistics + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Failed to retrieve information + */ + esp_err_t (*get_info)(struct twai_node_base *node, twai_node_status_t *status_ret, twai_node_record_t *record_ret); +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_twai/include/esp_private/twai_priv.h b/components/esp_driver_twai/include/esp_private/twai_priv.h new file mode 100644 index 0000000000..0b49a66d89 --- /dev/null +++ b/components/esp_driver_twai/include/esp_private/twai_priv.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_twai_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief TWAI hardware-dependent bit-timing constant + * + * Used for calculating and checking bit-timing parameters + */ +typedef struct { + uint32_t brp_min; /* Bit-rate prescaler */ + uint32_t brp_max; + uint8_t tseg1_min; /* Time segment 1 = prop_seg + phase_seg1 */ + uint8_t tseg1_max; + uint8_t tseg2_min; /* Time segment 2 = phase_seg2 */ + uint8_t tseg2_max; + uint8_t sjw_max; /* Synchronisation jump width */ +} twai_timing_constraint_t; + +/** + * @brief Calculate TWAI timing parameters for a given source frequency and baud rate. + * + * This function computes the bit timing parameters (`brp`, `prop_seg`, `tseg_1`, `tseg_2`, `sjw`) + * based on the provided source clock frequency and desired baud rate. It also adjusts for the sample + * point ratio if specified. + * + * @param source_freq The source clock frequency in Hz. + * @param in_param Pointer to the input configuration, which includes baud rate and sample rate. + * @param hw_limit Pointer to const of hardware register limitation. + * @param out_param Pointer to the output structure where the calculated timing parameters will be stored. + * @return the actual hardware adopted baudrate. + */ +uint32_t twai_node_timing_calc_param(const uint32_t source_freq, const twai_timing_basic_config_t *in_param, const twai_timing_constraint_t *hw_limit, twai_timing_advanced_config_t *out_param); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_twai/include/esp_twai.h b/components/esp_driver_twai/include/esp_twai.h new file mode 100644 index 0000000000..757b6fc407 --- /dev/null +++ b/components/esp_driver_twai/include/esp_twai.h @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "esp_twai_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* -------------------------------------------------- Node Control -------------------------------------------------- */ + +/** + * @brief Enable the TWAI node + * + * @param node Handle to the TWAI node + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_enable(twai_node_handle_t node); + +/** + * @brief Disable the TWAI node + * + * @param node Handle to the TWAI node + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_disable(twai_node_handle_t node); + +/** + * @brief Init the recover process for TWAI node which in bus-off + * @note Follow `on_state_change` callback or `twai_node_get_info` to know recover finish or not + * + * @param node Handle to the TWAI node + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_recover(twai_node_handle_t node); + +/** + * @brief Delete the TWAI node and release resources + * + * @param node Handle to the TWAI node + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_delete(twai_node_handle_t node); + +/** + * @brief Register event callbacks for the TWAI node + * + * @param node Handle to the TWAI node + * @param cbs Pointer to a structure of event callback functions + * @param user_data User-defined data passed to callback functions + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_register_event_callbacks(twai_node_handle_t node, const twai_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Reconfigure the timing settings of the TWAI node + * + * @param node Handle to the TWAI node + * @param bit_timing Optional,pointer to new twai cc(classic) or arbitration phase of twai fd timing configuration + * @param data_timing Optional, pointer to new twai fd timing configuration + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_reconfig_timing(twai_node_handle_t node, const twai_timing_advanced_config_t *bit_timing, const twai_timing_advanced_config_t *data_timing); + +/** + * @brief Configure the mask filter of the TWAI node + * + * @param node Handle to the TWAI node + * @param filter_id Index of the filter to configure + * @param mask_cfg Pointer to the mask filter configuration + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_config_mask_filter(twai_node_handle_t node, uint8_t filter_id, const twai_mask_filter_config_t *mask_cfg); + +/** + * @brief Configure the range filter of the TWAI node + * + * @param node Handle to the TWAI node + * @param filter_id Index of the filter to configure + * @param range_cfg Pointer to the range filter configuration + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_config_range_filter(twai_node_handle_t node, uint8_t filter_id, const twai_range_filter_config_t *range_cfg); + +/** + * @brief Get information about the TWAI node + * + * @param node Handle to the TWAI node + * @param status_ret Pointer to store the current node status + * @param statistics_ret Pointer to store node statistics + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_get_info(twai_node_handle_t node, twai_node_status_t *status_ret, twai_node_record_t *statistics_ret); + +/* ----------------------------------------------- Node Communication ----------------------------------------------- */ + +/** + * @brief Transmit a TWAI frame + * + * @param[in] node Handle to the TWAI node + * @param[in] frame Pointer to the frame to transmit + * @param[in] timeout_ms Maximum wait time if the transmission queue is full (milliseconds), -1 to wait forever + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_transmit(twai_node_handle_t node, const twai_frame_t *frame, int timeout_ms); + +/** + * @brief Receive a TWAI frame from 'rx_done_cb' (ONLY FROM 'rx_done_cb') + * + * @param[in] node Handle to the TWAI node + * @param[out] header Where to store frame header + * @param[out] rx_buffer Where to store frame data + * @param[in] buf_sz Bytes length of rx_buffer + * @param[out] received_len Optional, the real data length of rx frame comes from 'header->dlc', null if don't care + * @return ESP_OK on success, error code otherwise + */ +esp_err_t twai_node_receive_from_isr(twai_node_handle_t node, twai_frame_header_t *header, uint8_t *rx_buffer, size_t buf_sz, size_t *received_len); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_twai/include/esp_twai_types.h b/components/esp_driver_twai/include/esp_twai_types.h new file mode 100644 index 0000000000..89a4802fe9 --- /dev/null +++ b/components/esp_driver_twai/include/esp_twai_types.h @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "hal/twai_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ESP TWAI controller handle + */ +typedef struct twai_node_base *twai_node_handle_t; + +/** + * @brief TWAI bitrate timing config basic (simple) mode + */ +typedef struct { + uint32_t bitrate; /**< Expected TWAI bus baud_rate/bitrate in bits/second */ + uint16_t sample_point; /**< Optional, sampling point in permill (1/1000) of the entire bit time */ + uint16_t ssp_permill; /**< Optional, secondary sample point(ssp) in permill (1/1000) of the entire bit time */ +} twai_timing_basic_config_t; + +/** + * @brief TWAI transaction frame param type + */ +typedef struct twai_frame_t { + twai_frame_header_t header; /**< message attribute/metadata, exclude data buffer*/ + uint8_t *buffer; /**< buffer address for tx and rx message data*/ + size_t buffer_len; /**< buffer length of provided data buffer pointer, in bytes.*/ +} twai_frame_t; + +/** + * @brief Configuration for TWAI mask filter + * + * @note Set both id and mask to `0` to receive ALL frames, both `0xFFFFFFFF` to receive NONE frames + */ +typedef struct { + uint32_t id; /**< Base ID for filtering */ + uint32_t mask; /**< Mask to determine the matching bits (1 = match bit, 0 = any bit) */ + struct { + uint32_t is_ext: 1; /**< True for extended ID filtering, false for standard ID */ + uint32_t no_classic: 1; /**< If true, Classic CAN frames are excluded (only CAN FD allowed) */ + uint32_t no_fd: 1; /**< If true, CAN FD frames are excluded (only Classic CAN allowed) */ + }; +} twai_mask_filter_config_t; + +/** + * @brief Range-based filter configuration structure + * + * @note Set both range_low and range_high to `0` to receive ALL frames, both `0xFFFFFFFF` to receive NONE frames + */ +typedef struct { + uint32_t range_low; /**< Lower bound of the filtering range */ + uint32_t range_high; /**< Upper bound of the filtering range */ + struct { + uint32_t is_ext: 1; /**< True for extended ID filtering, false for standard ID */ + uint32_t no_classic: 1; /**< If true, Classic CAN frames are excluded (only CAN FD allowed) */ + uint32_t no_fd: 1; /**< If true, CAN FD frames are excluded (only Classic CAN allowed) */ + }; +} twai_range_filter_config_t; + +/** + * @brief TWAI node's status + */ +typedef struct { + twai_error_state_t state; /**< Node's error state */ + uint16_t tx_error_count; /**< Node's TX error count */ + uint16_t rx_error_count; /**< Node's RX error count */ +} twai_node_status_t; + +/** + * @brief TWAI node's statistics/record type + * + * This structure contains some statistics regarding a node's communication on the TWAI bus + */ +typedef struct { + uint32_t bus_err_num; /**< Cumulative number (since `twai_node_enable()`) of bus errors */ +} twai_node_record_t; + +/** + * @brief TWAI "TX done" event data + */ +typedef struct { + twai_frame_t *done_trans_tx; +} twai_tx_done_event_data_t; + +/** + * @brief TWAI "RX done" event data + */ +typedef struct { +} twai_rx_done_event_data_t; + +/** + * @brief TWAI "state change" event data + */ +typedef struct { + twai_error_state_t old_sta; // Previous error state + twai_error_state_t new_sta; // New error state after the change +} twai_state_change_event_data_t; + +/** + * @brief TWAI Error Type Structure + */ +typedef union { + struct { + uint32_t arb_lost: 1; /**< Arbitration lost error (lost arbitration during transmission) */ + uint32_t bit_err: 1; /**< Bit error detected (dominant/recessive mismatch during arbitration or transmission) */ + uint32_t form_err: 1; /**< Form error detected (frame fixed-form bit violation) */ + uint32_t stuff_err: 1; /**< Stuff error detected (e.g. dominant error frame received) */ + uint32_t reserved: 28; /**< Reserved bits for future use, must be set to zero */ + }; + uint32_t val; /**< Integrated error code */ +} twai_error_code_t; + +/** + * @brief TWAI "error" event data + */ +typedef struct { + twai_error_code_t err_type; +} twai_error_event_data_t; + +/** + * @brief Group of supported TWAI callbacks + * + * @note All of these callbacks is invoked from ISR context. Thus, the implementation of the callback function must + * adhere to the ISR restrictions such as not calling any blocking APIs. + * + * @note Set the particular event callback's entry to NULL to unregister it if not required. + * @note When TWAI_ISR_CACHE_SAFE is enabled, the callbacks must be placed in IRAM. + */ +typedef struct { + /** + * @brief TWAI "TX done" event callback prototype + * + * @param handle TWAI node handle + * @param edata "TX done" event data (passed by the driver) + * @param user_ctx User data, passed from `twai_node_register_event_callbacks()` + * @return Whether a higher priority task has been unblocked by this function + */ + bool (*on_tx_done)(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx); + + /** + * @brief TWAI "RX done" event callback prototype + * + * @param handle TWAI node handle + * @param edata "RX done" event data (passed by the driver) + * @param user_ctx User data, passed from `twai_node_register_event_callbacks()` + * @return Whether a higher priority task has been unblocked by this function + */ + bool (*on_rx_done)(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx); + + /** + * @brief TWAI "state change" event callback prototype + * + * @param handle TWAI node handle + * @param edata "state change" event data (passed by the driver) + * @param user_ctx User data, passed from `twai_node_register_event_callbacks()` + * @return Whether a higher priority task has been unblocked by this function + */ + bool (*on_state_change)(twai_node_handle_t handle, const twai_state_change_event_data_t *edata, void *user_ctx); + + /** + * @brief TWAI "error" event callback prototype + * + * @param[in] handle TWAI node handle + * @param[in] edata "error" event data (passed by the driver) + * @param[in] user_ctx User data, passed from `twai_node_register_event_callbacks()` + * @return Whether a higher priority task has been unblocked by this function + */ + bool (*on_error)(twai_node_handle_t handle, const twai_error_event_data_t *edata, void *user_ctx); +} twai_event_callbacks_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_twai/linker.lf b/components/esp_driver_twai/linker.lf new file mode 100644 index 0000000000..33d64bf25e --- /dev/null +++ b/components/esp_driver_twai/linker.lf @@ -0,0 +1,8 @@ +[mapping:esp_twai] +archive: libesp_driver_twai.a +entries: + if TWAI_ISR_INTO_IRAM = y: + esp_twai: twai_node_receive_from_isr (no_flash) + esp_twai_onchip: _node_isr_main (no_flash) + esp_twai_onchip: _node_start_trans (no_flash) + esp_twai_onchip: _node_parse_rx (no_flash) diff --git a/components/esp_driver_twai/onchip/esp_twai_onchip.c b/components/esp_driver_twai/onchip/esp_twai_onchip.c new file mode 100644 index 0000000000..8a063c06e7 --- /dev/null +++ b/components/esp_driver_twai/onchip/esp_twai_onchip.c @@ -0,0 +1,645 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "soc/soc_caps.h" +#include "soc/twai_periph.h" +#include "soc/io_mux_reg.h" +#include "hal/twai_hal.h" +#include "freertos/FreeRTOS.h" +#include "esp_check.h" +#include "esp_pm.h" +#include "esp_heap_caps.h" +#include "esp_intr_alloc.h" +#include "esp_clk_tree.h" +#include "esp_twai.h" +#include "esp_twai_onchip.h" +#include "esp_private/twai_interface.h" +#include "esp_private/twai_priv.h" +#include "esp_private/gpio.h" +#include "esp_private/esp_gpio_reserve.h" +#include "esp_private/periph_ctrl.h" + +static const char *TAG = "twai"; + +#ifdef CONFIG_TWAI_ISR_INTO_IRAM +#define TWAI_MALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define TWAI_MALLOC_CAPS MALLOC_CAP_DEFAULT +#endif //CONFIG_TWAI_ISR_INTO_IRAM + +#if !SOC_RCC_IS_INDEPENDENT +#define TWAI_RCC_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define TWAI_RCC_ATOMIC() +#endif + +#if SOC_PERIPH_CLK_CTRL_SHARED +#define TWAI_PERI_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define TWAI_PERI_ATOMIC() +#endif + +#define DRIVER_DEFAULT_INTERRUPTS (TWAIFD_LL_INTR_TX_DONE |\ + TWAIFD_LL_INTR_TX_SUCCESS |\ + TWAIFD_LL_INTR_RX_NOT_EMPTY |\ + TWAIFD_LL_INTR_RX_FULL |\ + TWAIFD_LL_INTR_ERR_WARN |\ + TWAIFD_LL_INTR_BUS_ERR |\ + TWAIFD_LL_INTR_FSM_CHANGE |\ + TWAIFD_LL_INTR_ARBI_LOST |\ + TWAIFD_LL_INTR_DATA_OVERRUN) + +static void _twai_rcc_clock_ctrl(uint8_t ctrlr_id, bool enable) +{ + TWAI_RCC_ATOMIC() { + twaifd_ll_enable_bus_clock(ctrlr_id, enable); + twaifd_ll_reset_register(ctrlr_id); + } + TWAI_PERI_ATOMIC() { + twaifd_ll_enable_clock(ctrlr_id, enable); + } +} + +typedef struct { + struct twai_node_base api_base; + int ctrlr_id; + uint64_t gpio_reserved; + twai_hal_context_t hal; + intr_handle_t intr_hdl; + QueueHandle_t tx_mount_queue; + twai_clock_source_t curr_clk_src; + uint32_t valid_fd_timing; + twai_event_callbacks_t cbs; + void *user_data; + esp_pm_lock_handle_t pm_lock; + + _Atomic twai_error_state_t state; + uint16_t tx_error_count; + uint16_t rx_error_count; + twai_node_record_t history; + + atomic_bool hw_busy; + atomic_bool rx_isr; + const twai_frame_t *p_curr_tx; + twai_hal_frame_t rcv_buff; +} twai_onchip_ctx_t; + +typedef struct twai_platform_s { + _lock_t mutex; + twai_onchip_ctx_t *nodes[SOC_TWAI_CONTROLLER_NUM]; +} twai_platform_t; +static twai_platform_t s_platform; + +static int _ctrlr_acquire(twai_onchip_ctx_t *node) +{ + int ctrlr_id = -1; + _lock_acquire(&s_platform.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) { + // Assign to node object to the controller slot + s_platform.nodes[i] = node; + ctrlr_id = i; + break; + } + } + _lock_release(&s_platform.mutex); + + // Return the controller index or -1 + return ctrlr_id; +} + +static void _ctrlr_release(int ctrlr_id) +{ + _lock_acquire(&s_platform.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); +} + +static esp_err_t _node_config_io(twai_onchip_ctx_t *node, const twai_onchip_node_config_t *node_config) +{ + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(node_config->io_cfg.tx), ESP_ERR_INVALID_ARG, TAG, "Invalid tx gpio num"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(node_config->io_cfg.rx), ESP_ERR_INVALID_ARG, TAG, "Invalid rx gpio num"); + uint64_t reserve_mask = BIT64(node_config->io_cfg.tx); + + // Set RX pin + gpio_input_enable(node_config->io_cfg.rx); + gpio_set_pull_mode(node_config->io_cfg.rx, GPIO_PULLUP_ONLY); // pullup to avoid noise if no connection to transceiver + gpio_func_sel(node_config->io_cfg.rx, PIN_FUNC_GPIO); + esp_rom_gpio_connect_in_signal(node_config->io_cfg.rx, twai_controller_periph_signals.controllers[node->ctrlr_id].rx_sig, false); + + // Set TX pin + gpio_func_sel(node_config->io_cfg.tx, PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(node_config->io_cfg.tx, twai_controller_periph_signals.controllers[node->ctrlr_id].tx_sig, false, false); + + //Configure output clock pin (Optional) + if (GPIO_IS_VALID_OUTPUT_GPIO(node_config->io_cfg.quanta_clk_out)) { + reserve_mask |= BIT64(node_config->io_cfg.quanta_clk_out); + gpio_func_sel(node_config->io_cfg.quanta_clk_out, PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(node_config->io_cfg.quanta_clk_out, twai_controller_periph_signals.controllers[node->ctrlr_id].clk_out_sig, false, false); + } + + //Configure bus status pin (Optional) + if (GPIO_IS_VALID_OUTPUT_GPIO(node_config->io_cfg.bus_off_indicator)) { + reserve_mask |= BIT64(node_config->io_cfg.bus_off_indicator); + gpio_func_sel(node_config->io_cfg.bus_off_indicator, PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(node_config->io_cfg.bus_off_indicator, twai_controller_periph_signals.controllers[node->ctrlr_id].bus_off_sig, false, false); + } + + node->gpio_reserved = reserve_mask; + uint64_t busy_mask = esp_gpio_reserve(reserve_mask); + uint64_t conflict_mask = busy_mask & reserve_mask; + for (; conflict_mask > 0;) { + uint8_t pos = __builtin_ctzll(conflict_mask); + conflict_mask &= ~(1ULL << pos); + ESP_LOGW(TAG, "GPIO %d is not usable, maybe used by others", pos); + } + return ESP_OK; +} + +static void _node_release_io(twai_onchip_ctx_t *node) +{ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, twai_controller_periph_signals.controllers[node->ctrlr_id].rx_sig, false); + esp_gpio_revoke(node->gpio_reserved); + for (; node->gpio_reserved > 0;) { + uint8_t pos = __builtin_ctzll(node->gpio_reserved); + node->gpio_reserved &= ~(1ULL << pos); + gpio_output_disable(pos); + } +} + +static void _node_start_trans(twai_onchip_ctx_t *node) +{ + const twai_frame_header_t *format = &node->p_curr_tx->header; + twai_hal_context_t *hal = &node->hal; + twai_hal_frame_t tx_frame = {}; + + int final_dlc = (format->dlc) ? format->dlc : twaifd_len2dlc(node->p_curr_tx->buffer_len); + int data_len = (format->dlc) ? twaifd_dlc2len(format->dlc) : node->p_curr_tx->buffer_len; + twaifd_ll_format_frame_header(format, final_dlc, &tx_frame); + twaifd_ll_format_frame_data(node->p_curr_tx->buffer, data_len, &tx_frame); + //TODO: utilize all txt buffers + twaifd_ll_mount_tx_buffer(hal->dev, &tx_frame, 0); + twaifd_ll_set_tx_cmd(hal->dev, 0, TWAIFD_LL_TX_CMD_READY); +} + +static void _node_isr_main(void *arg) +{ + BaseType_t do_yield = pdFALSE; + twai_onchip_ctx_t *twai_ctx = arg; + uint32_t int_stat = twaifd_ll_get_intr_status(twai_ctx->hal.dev); + twai_ctx->tx_error_count = twaifd_ll_get_tec(twai_ctx->hal.dev); + twai_ctx->rx_error_count = twaifd_ll_get_rec(twai_ctx->hal.dev); + twaifd_ll_clr_intr_status(twai_ctx->hal.dev, int_stat); + + // deal ERR event + if (int_stat & (TWAIFD_LL_INTR_BUS_ERR | TWAIFD_LL_INTR_ARBI_LOST)) { + uint32_t err_reason = twaifd_ll_get_err_reason_code(twai_ctx->hal.dev); + twai_error_event_data_t e_data = {0}; + e_data.err_type.arb_lost = !!(int_stat & TWAIFD_LL_INTR_ARBI_LOST); + e_data.err_type.bit_err = (err_reason == TWAIFD_LL_ERR_BIT_ERR); + e_data.err_type.form_err = (err_reason == TWAIFD_LL_ERR_FRM_ERR); + e_data.err_type.stuff_err = (err_reason == TWAIFD_LL_ERR_STUF_ERR); + + twai_ctx->history.bus_err_num ++; + if (twai_ctx->cbs.on_error) { + do_yield |= twai_ctx->cbs.on_error(&twai_ctx->api_base, &e_data, twai_ctx->user_data); + } + } + + // deal FSM event + if (int_stat & (TWAIFD_LL_INTR_FSM_CHANGE | TWAIFD_LL_INTR_ERR_WARN)) { + twai_state_change_event_data_t e_data = {.old_sta = twai_ctx->state}; + e_data.new_sta = twaifd_ll_get_fault_state(twai_ctx->hal.dev); + uint32_t limit = twaifd_ll_get_err_warn_limit(twai_ctx->hal.dev); + if ((e_data.new_sta == TWAI_ERROR_ACTIVE) && ((twai_ctx->tx_error_count >= limit) || (twai_ctx->rx_error_count >= limit))) { + e_data.new_sta = TWAI_ERROR_WARNING; + } + atomic_store(&twai_ctx->state, e_data.new_sta); + if (twai_ctx->cbs.on_state_change) { + do_yield |= twai_ctx->cbs.on_state_change(&twai_ctx->api_base, &e_data, twai_ctx->user_data); + } + } + + // deal TX event + if (int_stat & (TWAIFD_LL_INTR_TX_DONE | TWAIFD_LL_INTR_TX_SUCCESS)) { + // only call tx_done_cb when tx without error, otherwise on_error_cb should triggered if it is registered + if (twai_ctx->cbs.on_tx_done && (int_stat & TWAIFD_LL_INTR_TX_DONE)) { + twai_tx_done_event_data_t tx_ev = { + .done_trans_tx = (twai_frame_t *)twai_ctx->p_curr_tx, + }; + do_yield |= twai_ctx->cbs.on_tx_done(&twai_ctx->api_base, &tx_ev, twai_ctx->user_data); + } + // start a new TX + if ((atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF) && xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &do_yield)) { + // Sanity check, must in `hw_busy` here, otherwise logic bug is somewhere + assert(twai_ctx->hw_busy); + _node_start_trans(twai_ctx); + } else { + atomic_store(&twai_ctx->hw_busy, false); + } + } + + // deal RX event + if (int_stat & TWAIFD_LL_INTR_RX_NOT_EMPTY) { + while (twaifd_ll_get_rx_frame_count(twai_ctx->hal.dev)) { + twaifd_ll_get_rx_frame(twai_ctx->hal.dev, &twai_ctx->rcv_buff); + if (twai_ctx->cbs.on_rx_done) { + atomic_store(&twai_ctx->rx_isr, true); + const twai_rx_done_event_data_t rx_ev = { + }; + do_yield |= twai_ctx->cbs.on_rx_done(&twai_ctx->api_base, &rx_ev, twai_ctx->user_data); + atomic_store(&twai_ctx->rx_isr, false); + } + } + // rx flag can only cleared after read to empty + twaifd_ll_clr_intr_status(twai_ctx->hal.dev, TWAIFD_LL_INTR_RX_NOT_EMPTY); + } + + if (do_yield) { + portYIELD_FROM_ISR(); + } +} + +static esp_err_t _node_delete(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, "delete node must when node stopped"); + + _node_release_io(twai_ctx); + _ctrlr_release(twai_ctx->ctrlr_id); + _twai_rcc_clock_ctrl(twai_ctx->ctrlr_id, false); +#if CONFIG_PM_ENABLE + if (twai_ctx->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(twai_ctx->pm_lock), TAG, "delete power manager failed"); + } +#endif //CONFIG_PM_ENABLE + esp_intr_free(twai_ctx->intr_hdl); + vQueueDeleteWithCaps(twai_ctx->tx_mount_queue); + free(twai_ctx); + return ESP_OK; +} + +static esp_err_t _node_register_callbacks(twai_node_handle_t node, const twai_event_callbacks_t *cbs, void *user_data) +{ + 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, "register callback must when node stopped"); +#if CONFIG_TWAI_ISR_CACHE_SAFE + ESP_RETURN_ON_FALSE(!cbs->on_tx_done || esp_ptr_in_iram(cbs->on_tx_done), ESP_ERR_INVALID_ARG, TAG, "on_tx_done callback not in IRAM"); + ESP_RETURN_ON_FALSE(!cbs->on_rx_done || esp_ptr_in_iram(cbs->on_rx_done), ESP_ERR_INVALID_ARG, TAG, "on_rx_done callback not in IRAM"); + ESP_RETURN_ON_FALSE(!cbs->on_state_change || esp_ptr_in_iram(cbs->on_state_change), ESP_ERR_INVALID_ARG, TAG, "on_state_change callback not in IRAM"); + ESP_RETURN_ON_FALSE(!cbs->on_error || esp_ptr_in_iram(cbs->on_error), ESP_ERR_INVALID_ARG, TAG, "on_error callback not in IRAM"); +#endif + memcpy(&twai_ctx->cbs, cbs, sizeof(twai_event_callbacks_t)); + twai_ctx->user_data = user_data; + 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) +{ + 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((timing->brp >= SOC_TWAI_BRP_MIN) && (timing->brp <= SOC_TWAI_BRP_MAX), 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_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) { + 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"); + + int ssp_offset = 0; + if (timing) { + twaifd_ll_set_nominal_bitrate(twai_ctx->hal.dev, timing); + if (timing->ssp_offset) { + ssp_offset = timing->ssp_offset; + } + } +#if SOC_TWAI_SUPPORT_FD + if (timing_fd) { + twai_ctx->valid_fd_timing = true; + twaifd_ll_set_fd_bitrate(twai_ctx->hal.dev, timing_fd); + if (timing_fd->ssp_offset) { + // prefer to config ssp by fd param + ssp_offset = timing_fd->ssp_offset; + } + } +#endif + + // config ssp, the hardware offset_val TWAIFD_LL_SSP_SRC_MEAS_OFFSET measured by clock_src freq + twaifd_ll_config_secondary_sample_point(twai_ctx->hal.dev, (ssp_offset ? TWAIFD_LL_SSP_SRC_MEAS_OFFSET : TWAIFD_LL_SSP_SRC_NO_SSP), ssp_offset); + if (new_clock_src != twai_ctx->curr_clk_src) { + twai_ctx->curr_clk_src = new_clock_src; + TWAI_PERI_ATOMIC() { + twaifd_ll_set_clock_source(twai_ctx->ctrlr_id, 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) +{ + ESP_RETURN_ON_FALSE(timing->bitrate, ESP_ERR_INVALID_ARG, TAG, "classic timing config is required"); +#if !SOC_TWAI_SUPPORT_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, timing_adv_fd = { .clk_src = root_clock_src, }; + twai_timing_constraint_t hw_const = { + .brp_min = SOC_TWAI_BRP_MIN, + .brp_max = SOC_TWAI_BRP_MAX, + .tseg1_min = TWAI_LL_TSEG1_MIN, + .tseg1_max = TWAI_LL_TSEG1_MAX, + .tseg2_min = TWAI_LL_TSEG2_MIN, + .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, "norm src %ld, param %ld %d %d %d %d %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); + 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_TWAI_SUPPORT_FD + if (timing_fd->bitrate) { + real_baud = twai_node_timing_calc_param(source_freq, timing_fd, &hw_const, &timing_adv_fd); + ESP_LOGD(TAG, "fd src %ld, param %ld %d %d %d %d %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); + 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; + } +#endif + ESP_RETURN_ON_ERROR(_node_set_bit_timing(node, &timing_adv, fd_cfg_ptr), TAG, "invalid timing param"); + return ESP_OK; +} + +/* -------------------------------------------------- Node Control -------------------------------------------------- */ + +static esp_err_t _node_enable(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 enabled"); + +#if CONFIG_PM_ENABLE + //Acquire pm_lock until _node_disable for potential receive + if (twai_ctx->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(twai_ctx->pm_lock), TAG, "acquire power manager failed"); + } +#endif //CONFIG_PM_ENABLE + twaifd_ll_enable_hw(twai_ctx->hal.dev, true); + twaifd_ll_waiting_state_change(twai_ctx->hal.dev); // waiting hardware ready before resume transaction + + // continuing the transaction if there be + if (atomic_load(&twai_ctx->hw_busy) && twaifd_ll_get_fault_state(twai_ctx->hal.dev) != TWAI_ERROR_BUS_OFF) { + _node_start_trans(twai_ctx); + } + // once interrupt enabled, state_change_intr will fire at the moment and update the `twai_ctx->state` + ESP_RETURN_ON_ERROR(esp_intr_enable(twai_ctx->intr_hdl), TAG, "enable interrupt failed"); + return ESP_OK; +} + +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"); + + esp_intr_disable(twai_ctx->intr_hdl); + atomic_store(&twai_ctx->state, TWAI_ERROR_BUS_OFF); + twaifd_ll_enable_hw(twai_ctx->hal.dev, false); + +#if CONFIG_PM_ENABLE + if (twai_ctx->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(twai_ctx->pm_lock), TAG, "release power manager failed"); + } +#endif //CONFIG_PM_ENABLE + return ESP_OK; +} + +static esp_err_t _node_config_mask_filter(twai_node_handle_t node, uint8_t filter_id, const twai_mask_filter_config_t *mask_cfg) +{ + twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); + ESP_RETURN_ON_FALSE(filter_id < SOC_TWAI_MASK_FILTER_NUM, ESP_ERR_INVALID_ARG, TAG, "Invalid mask filter id %d", filter_id); + ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) == TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "config filter must when node stopped"); + + bool full_open = (mask_cfg->mask == 0) && (mask_cfg->id == 0); + bool full_close = (mask_cfg->mask == UINT32_MAX) && (mask_cfg->id == UINT32_MAX); + bool cc_ext = full_open || (!full_close && mask_cfg->is_ext && !mask_cfg->no_classic); + bool fd_ext = full_open || (!full_close && mask_cfg->is_ext && !mask_cfg->no_fd); + bool cc_std = full_open || (!full_close && !mask_cfg->is_ext && !mask_cfg->no_classic); + bool fd_std = full_open || (!full_close && !mask_cfg->is_ext && !mask_cfg->no_fd); + twaifd_ll_filter_enable_basic_ext(twai_ctx->hal.dev, filter_id, false, cc_ext); + twaifd_ll_filter_enable_fd_ext(twai_ctx->hal.dev, filter_id, false, fd_ext); + twaifd_ll_filter_enable_basic_std(twai_ctx->hal.dev, filter_id, false, cc_std); + twaifd_ll_filter_enable_fd_std(twai_ctx->hal.dev, filter_id, false, fd_std); + twaifd_ll_filter_set_id_mask(twai_ctx->hal.dev, filter_id, mask_cfg->is_ext, mask_cfg->id, mask_cfg->mask); + return ESP_OK; +} + +#if SOC_TWAI_RANGE_FILTER_NUM +static esp_err_t _node_config_range_filter(twai_node_handle_t node, uint8_t filter_id, const twai_range_filter_config_t *range_cfg) +{ + twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); + ESP_RETURN_ON_FALSE(filter_id < SOC_TWAI_RANGE_FILTER_NUM, ESP_ERR_INVALID_ARG, TAG, "Invalid range filter id %d", filter_id); + ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) == TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "config filter must when node stopped"); + + bool full_open = (range_cfg->range_high == 0) && (range_cfg->range_low == 0); + bool full_close = (range_cfg->range_high == UINT32_MAX) && (range_cfg->range_low == UINT32_MAX); + bool cc_ext = full_open || (!full_close && range_cfg->is_ext && !range_cfg->no_classic); + bool fd_ext = full_open || (!full_close && range_cfg->is_ext && !range_cfg->no_fd); + bool cc_std = full_open || (!full_close && !range_cfg->is_ext && !range_cfg->no_classic); + bool fd_std = full_open || (!full_close && !range_cfg->is_ext && !range_cfg->no_fd); + twaifd_ll_filter_enable_basic_ext(twai_ctx->hal.dev, filter_id, true, cc_ext); + twaifd_ll_filter_enable_fd_ext(twai_ctx->hal.dev, filter_id, true, fd_ext); + twaifd_ll_filter_enable_basic_std(twai_ctx->hal.dev, filter_id, true, cc_std); + twaifd_ll_filter_enable_fd_std(twai_ctx->hal.dev, filter_id, true, fd_std); + twaifd_ll_filter_set_range(twai_ctx->hal.dev, 0, range_cfg->is_ext, range_cfg->range_high, range_cfg->range_low); + return ESP_OK; +} +#endif + +static esp_err_t _node_recover(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 not in bus off"); + + // After recover command, the hardware require 128 consecutive occurrences of 11 recessive bits received, so that it can be active again! + // Checking `twai_node_status_t::state` Or waiting `on_state_change` callback can know if recover is finish + twaifd_ll_set_operate_cmd(twai_ctx->hal.dev, TWAIFD_LL_HW_CMD_RST_ERR_CNT); + twai_ctx->history.bus_err_num = 0; + return ESP_OK; +} + +static esp_err_t _node_get_status(twai_node_handle_t node, twai_node_status_t *status_ret, twai_node_record_t *record_ret) +{ + twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); + + if (status_ret) { + status_ret->state = atomic_load(&twai_ctx->state); + status_ret->tx_error_count = twai_ctx->tx_error_count; + status_ret->rx_error_count = twai_ctx->rx_error_count; + } + if (record_ret) { + *record_ret = twai_ctx->history; + } + return ESP_OK; +} + +/* ----------------------------------------------- Node Communication ----------------------------------------------- */ + +static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *frame, int timeout) +{ + twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); + if (frame->header.dlc && frame->buffer_len) { + ESP_RETURN_ON_FALSE(frame->header.dlc == twaifd_len2dlc(frame->buffer_len), ESP_ERR_INVALID_ARG, TAG, "unmatched dlc and buffer_len"); + } + ESP_RETURN_ON_FALSE(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); + ESP_RETURN_ON_FALSE((!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(!twai_ctx->hal.enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only"); + ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off"); + TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout); + + bool false_var = false; + if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) { + twai_ctx->p_curr_tx = frame; + _node_start_trans(twai_ctx); + } else { + //options in following steps (in_queue->2nd_check->pop_queue) should exec ASAP + //within about 50us (minimum time for one msg), to ensure data safe + ESP_RETURN_ON_FALSE(xQueueSend(twai_ctx->tx_mount_queue, &frame, ticks_to_wait), ESP_ERR_TIMEOUT, TAG, "tx queue full"); + false_var = false; + if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) { + if (xQueueReceive(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, 0) != pdTRUE) { + assert(false && "should always get frame at this moment"); + } + _node_start_trans(twai_ctx); + } + } + return ESP_OK; +} + +static esp_err_t _node_parse_rx(twai_node_handle_t node, twai_frame_header_t *header, uint8_t *rx_buffer, size_t buf_len, size_t *ret_len) +{ + twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base); + ESP_RETURN_ON_FALSE_ISR(atomic_load(&twai_ctx->rx_isr), ESP_ERR_INVALID_STATE, TAG, "rx can only called in `rx_done` callback"); + + twaifd_ll_parse_frame_header(&twai_ctx->rcv_buff, header); + uint32_t frame_data_len = twaifd_dlc2len(header->dlc); + uint8_t final_len = (frame_data_len < buf_len) ? frame_data_len : buf_len; + twaifd_ll_parse_frame_data(&twai_ctx->rcv_buff, rx_buffer, final_len); + if (ret_len) { + *ret_len = frame_data_len; + } + return ESP_OK; +} + +/* --------------------------------- Public --------------------------------- */ +esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twai_node_handle_t *node_ret) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(node_config->tx_queue_depth > 0, ESP_ERR_INVALID_ARG, TAG, "tx_queue_depth at least 1"); + 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"); + + // Allocate TWAI node object memory + twai_onchip_ctx_t *node = heap_caps_calloc(1, sizeof(twai_onchip_ctx_t), TWAI_MALLOC_CAPS); + ESP_RETURN_ON_FALSE(node, ESP_ERR_NO_MEM, TAG, "No mem"); + // Acquire controller + int ctrlr_id = _ctrlr_acquire(node); + ESP_GOTO_ON_FALSE(ctrlr_id != -1, ESP_ERR_NOT_FOUND, ctrlr_err, TAG, "Controller not available"); + node->ctrlr_id = ctrlr_id; + + // state is in bus_off before enabled + atomic_store(&node->state, TWAI_ERROR_BUS_OFF); + node->tx_mount_queue = xQueueCreateWithCaps(node_config->tx_queue_depth, sizeof(twai_frame_t *), TWAI_MALLOC_CAPS); + ESP_GOTO_ON_FALSE(node->tx_mount_queue, ESP_ERR_NO_MEM, create_err, TAG, "no_mem"); + uint32_t intr_flags = node_config->intr_priority ? BIT(node_config->intr_priority) | ESP_INTR_FLAG_INTRDISABLED : ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_INTRDISABLED; +#if CONFIG_TWAI_ISR_CACHE_SAFE + intr_flags |= ESP_INTR_FLAG_IRAM; +#endif + ESP_GOTO_ON_ERROR(esp_intr_alloc(twai_controller_periph_signals.controllers[ctrlr_id].irq_id, intr_flags, _node_isr_main, (void *)node, &node->intr_hdl), + create_err, TAG, "Alloc interrupt 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 = DRIVER_DEFAULT_INTERRUPTS, + .enable_listen_only = node_config->flags.enable_listen_only, + }; + ESP_GOTO_ON_FALSE(twai_hal_init(&node->hal, &hal_config), ESP_ERR_INVALID_STATE, config_err, TAG, "hardware not in reset state"); + twaifd_ll_set_mode(node->hal.dev, node_config->flags.enable_listen_only, node_config->flags.enable_self_test, node_config->flags.enable_loopback); + twaifd_ll_set_tx_retrans_limit(node->hal.dev, node_config->fail_retry_cnt); + twaifd_ll_filter_block_rtr(node->hal.dev, node_config->flags.no_receive_rtr); + twaifd_ll_enable_filter_mode(node->hal.dev, true); // each filter still has independent enable control + twaifd_ll_enable_fd_mode(node->hal.dev, true); // fd frame still controlled by `header.fdf` + + // 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), config_err, TAG, "bitrate error"); + + // Configure GPIO + ESP_GOTO_ON_ERROR(_node_config_io(node, node_config), config_err, TAG, "gpio config failed"); +#if CONFIG_PM_ENABLE + ESP_GOTO_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, twai_controller_periph_signals.controllers[ctrlr_id].module_name, &node->pm_lock), config_err, TAG, "init power manager failed"); +#endif //CONFIG_PM_ENABLE + + node->api_base.enable = _node_enable; + node->api_base.disable = _node_disable; + node->api_base.del = _node_delete; + node->api_base.recover = _node_recover; + node->api_base.config_mask_filter = _node_config_mask_filter; + node->api_base.config_range_filter = _node_config_range_filter; + node->api_base.timing_reconfig = _node_set_bit_timing; + node->api_base.register_cbs = _node_register_callbacks; + node->api_base.transmit = _node_queue_tx; + node->api_base.receive_isr = _node_parse_rx; + node->api_base.get_info = _node_get_status; + + *node_ret = &node->api_base; + return ESP_OK; + +config_err: + if (node->intr_hdl) { + esp_intr_free(node->intr_hdl); + } + if (node->tx_mount_queue) { + vQueueDeleteWithCaps(node->tx_mount_queue); + } +create_err: + _ctrlr_release(ctrlr_id); +ctrlr_err: + free(node); + return ret; +} diff --git a/components/esp_driver_twai/onchip/include/esp_twai_onchip.h b/components/esp_driver_twai/onchip/include/esp_twai_onchip.h new file mode 100644 index 0000000000..286f838d7f --- /dev/null +++ b/components/esp_driver_twai/onchip/include/esp_twai_onchip.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "esp_twai_types.h" +#include "hal/gpio_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief TWAI on-chip node initialization configuration structure + */ +typedef struct { + struct { + gpio_num_t tx; /**< GPIO pin for twai TX */ + gpio_num_t rx; /**< GPIO pin for twai RX */ + gpio_num_t quanta_clk_out; /**< GPIO pin for quanta clock output, Set -1 to not use */ + gpio_num_t bus_off_indicator; /**< GPIO pin for bus-off indicator, Set -1 to not use */ + } io_cfg; /**< I/O configuration */ + 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 */ + 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] */ + struct { + uint32_t enable_self_test: 1; /**< Transmission does not require acknowledgment. Use this mode for self testing */ + uint32_t enable_loopback: 1; /**< The TWAI controller receive back frames what it send out */ + 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 */ + } flags; +} twai_onchip_node_config_t; + +/** + * @brief Allocate a TWAI hardware node by specific init config structure + * To delete/free the TWAI, call `twai_node_delete()` + * + * @param[in] node_config Init config structure + * @param[out] node_ret Return driver handle + * + * @return ESP_OK Allocate success + * ESP_ERR_NO_MEM No enough free memory + * ESP_ERR_NOT_FOUND No free hardware controller + * ESP_ERR_INVALID_ARG Config argument not available + * ESP_ERR_INVALID_STATE State error, including hardware state error and driver state error + * ESP_FAIL Other reasons + */ +esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twai_node_handle_t *node_ret); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/esp32c5/include/hal/twaifd_ll.h b/components/hal/esp32c5/include/hal/twaifd_ll.h index 6d8fab54c7..43c0bf3125 100644 --- a/components/hal/esp32c5/include/hal/twaifd_ll.h +++ b/components/hal/esp32c5/include/hal/twaifd_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,6 +26,14 @@ extern "C" { #define TWAIFD_LL_GET_HW(num) (((num) == 0) ? (&TWAI0) : (&TWAI1)) +#define TWAI_LL_TSEG1_MIN 0 +#define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_TSEG1_MAX TWAIFD_PH1 +#define TWAI_LL_TSEG2_MAX TWAIFD_PH2 +#define TWAI_LL_SJW_MAX TWAIFD_SJW + +#define TWAIFD_IDENTIFIER_BASE_S 18 // Start bit of std_id in IDENTIFIER_W of TX buffer or RX buffer + #define TWAIFD_LL_ERR_BIT_ERR 0x0 // Bit Error #define TWAIFD_LL_ERR_CRC_ERR 0x1 // CRC Error #define TWAIFD_LL_ERR_FRM_ERR 0x2 // Form Error @@ -44,7 +52,8 @@ extern "C" { #define TWAIFD_LL_HW_CMD_RST_RX_CNT TWAIFD_RXFCRST // Clear RX bus traffic counter #define TWAIFD_LL_HW_CMD_RST_TX_CNT TWAIFD_TXFCRST // Clear TX bus traffic counter -#define TWAIFD_LL_INTR_TX_DONE TWAIFD_TXI_INT_ST // Transmit Interrupt +#define TWAIFD_LL_INTR_TX_DONE TWAIFD_TXBHCI_INT_ST// Transmit finish (ok or error) +#define TWAIFD_LL_INTR_TX_SUCCESS TWAIFD_TXI_INT_ST // Transmit success without error #define TWAIFD_LL_INTR_RX_NOT_EMPTY TWAIFD_RBNEI_INT_ST // RX buffer not empty interrupt #define TWAIFD_LL_INTR_RX_FULL TWAIFD_RXFI_INT_ST // RX buffer full interrupt #define TWAIFD_LL_INTR_ERR_WARN TWAIFD_EWLI_INT_ST // Error Interrupt @@ -72,7 +81,7 @@ static inline void twaifd_ll_enable_bus_clock(uint8_t twai_id, bool enable) static inline void twaifd_ll_reset_register(uint8_t twai_id) { PCR.twai[twai_id].twai_conf.twai_rst_en = 1; - while (!PCR.twai[twai_id].twai_conf.twai_ready); + PCR.twai[twai_id].twai_conf.twai_rst_en = 0; } /** @@ -95,6 +104,9 @@ static inline void twaifd_ll_set_clock_source(uint8_t twai_id, twai_clock_source static inline void twaifd_ll_enable_clock(uint8_t twai_id, bool enable) { PCR.twai[twai_id].twai_func_clk_conf.twai_func_clk_en = enable; + if (enable) { + while (!PCR.twai[twai_id].twai_conf.twai_ready); + } } /** @@ -138,14 +150,15 @@ static inline void twaifd_ll_enable_hw(twaifd_dev_t *hw, bool enable) * @param hw Start address of the TWAI registers * @param modes Operating mode */ -static inline void twaifd_ll_set_mode(twaifd_dev_t *hw, const twai_mode_t modes) +static inline void twaifd_ll_set_mode(twaifd_dev_t *hw, bool listen_only, bool no_ack, bool loopback) { //mode should be changed under disabled HAL_ASSERT(hw->mode_settings.ena == 0); twaifd_mode_settings_reg_t opmode = {.val = hw->mode_settings.val}; - opmode.stm = (modes == TWAI_MODE_NO_ACK); - opmode.bmm = (modes == TWAI_MODE_LISTEN_ONLY); + opmode.stm = no_ack; + opmode.bmm = listen_only; + opmode.ilbp = loopback; hw->mode_settings.val = opmode.val; } @@ -172,17 +185,6 @@ static inline void twaifd_ll_enable_fd_mode(twaifd_dev_t *hw, bool ena) hw->mode_settings.fde = ena; } -/** - * @brief Enable or disable TX loopback - * - * @param hw Pointer to the TWAI-FD device hardware. - * @param ena Set to true to enable loopback, false to disable. - */ -static inline void twaifd_ll_enable_loopback(twaifd_dev_t *hw, bool ena) -{ - hw->mode_settings.ilbp = ena; -} - /** * @brief Enable or disable the RX fifo automatic increase when read to register * @@ -213,21 +215,11 @@ static inline void twaifd_ll_enable_filter_mode(twaifd_dev_t* hw, bool enable) * @param hw Pointer to hardware structure. * @param en True to drop, false to Receive to next filter */ -static inline void twaifd_ll_filter_drop_remote_frame(twaifd_dev_t* hw, bool en) +static inline void twaifd_ll_filter_block_rtr(twaifd_dev_t* hw, bool en) { hw->mode_settings.fdrf = en; } -/** - * @brief Get remote frame filtering behaviour. - * - * @param hw Pointer to hardware structure. - */ -static inline bool twaifd_ll_filter_is_drop_remote_frame(twaifd_dev_t* hw) -{ - return hw->mode_settings.fdrf; -} - /** * @brief Enable or disable the time-triggered transmission mode for the TWAI-FD peripheral. * @@ -272,6 +264,7 @@ static inline void twaifd_ll_enable_intr(twaifd_dev_t *hw, uint32_t intr_mask) * @param hw Pointer to the TWAI-FD device hardware. * @return The current interrupt status as a 32-bit value, used with `TWAIFD_LL_INTR_`. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_intr_status(twaifd_dev_t *hw) { return hw->int_stat.val; @@ -283,6 +276,7 @@ static inline uint32_t twaifd_ll_get_intr_status(twaifd_dev_t *hw) * @param hw Pointer to the TWAI-FD device hardware. * @param intr_mask The interrupt mask specifying which interrupts to clear. */ +__attribute__((always_inline)) static inline void twaifd_ll_clr_intr_status(twaifd_dev_t *hw, uint32_t intr_mask) { // this register is write to clear @@ -296,7 +290,7 @@ static inline void twaifd_ll_clr_intr_status(twaifd_dev_t *hw, uint32_t intr_mas * @param hw Start address of the TWAI registers * @param timing_param timing params */ -static inline void twaifd_ll_set_nominal_bit_rate(twaifd_dev_t *hw, const twai_timing_config_t *timing_param) +static inline void twaifd_ll_set_nominal_bitrate(twaifd_dev_t *hw, const twai_timing_advanced_config_t *timing_param) { twaifd_btr_reg_t reg_w = {.val = 0}; reg_w.brp = timing_param->brp; @@ -314,7 +308,7 @@ static inline void twaifd_ll_set_nominal_bit_rate(twaifd_dev_t *hw, const twai_t * @param hw Start address of the TWAI registers * @param timing_param_fd FD timing params */ -static inline void twaifd_ll_set_fd_bit_rate(twaifd_dev_t *hw, const twai_timing_config_t *timing_param_fd) +static inline void twaifd_ll_set_fd_bitrate(twaifd_dev_t *hw, const twai_timing_advanced_config_t *timing_param_fd) { twaifd_btr_fd_reg_t reg_w = {.val = 0}; reg_w.brp_fd = timing_param_fd->brp; @@ -331,7 +325,7 @@ static inline void twaifd_ll_set_fd_bit_rate(twaifd_dev_t *hw, const twai_timing * * @param hw Start address of the TWAI registers * @param ssp_src_code Secondary point mode config, see TWAIFD_LL_SSP_SRC_xxx. - * @param offset_val Secondary point offset based on Sync_Seg, in clock source freq. + * @param offset_val Secondary point offset based on Sync_Seg, in time quanta. */ static inline void twaifd_ll_config_secondary_sample_point(twaifd_dev_t *hw, uint8_t ssp_src_code, uint8_t offset_val) { @@ -463,15 +457,18 @@ static inline uint32_t twaifd_ll_get_tec(twaifd_dev_t *hw) * * @param hw Pointer to the TWAI FD hardware instance * @param filter_id The unique ID of the filter to configure + * @param is_range Setting for range filter or mask filter * @param en True to receive, False to drop */ -static inline void twaifd_ll_filter_enable_basic_std(twaifd_dev_t* hw, uint8_t filter_id, bool en) +static inline void twaifd_ll_filter_enable_basic_std(twaifd_dev_t* hw, uint8_t filter_id, bool is_range, bool en) { - HAL_ASSERT(filter_id < (SOC_TWAI_MASK_FILTER_NUM + SOC_TWAI_RANGE_FILTER_NUM)); + HAL_ASSERT(filter_id < (is_range ? SOC_TWAI_RANGE_FILTER_NUM : SOC_TWAI_MASK_FILTER_NUM)); + // The hw_filter_id of range_filter is indexed after mask_filter + uint8_t hw_filter_id = is_range ? filter_id + SOC_TWAI_MASK_FILTER_NUM : filter_id; if (en) { - hw->filter_control_filter_status.val |= TWAIFD_FANB << (filter_id * TWAIFD_FBNB_S); + hw->filter_control_filter_status.val |= TWAIFD_FANB << (hw_filter_id * TWAIFD_FBNB_S); } else { - hw->filter_control_filter_status.val &= ~(TWAIFD_FANB << (filter_id * TWAIFD_FBNB_S)); + hw->filter_control_filter_status.val &= ~(TWAIFD_FANB << (hw_filter_id * TWAIFD_FBNB_S)); } } @@ -480,15 +477,18 @@ static inline void twaifd_ll_filter_enable_basic_std(twaifd_dev_t* hw, uint8_t f * * @param hw Pointer to the TWAI FD hardware instance * @param filter_id The unique ID of the filter to configure + * @param is_range Setting for range filter or mask filter * @param en True to receive, False to drop */ -static inline void twaifd_ll_filter_enable_basic_ext(twaifd_dev_t* hw, uint8_t filter_id, bool en) +static inline void twaifd_ll_filter_enable_basic_ext(twaifd_dev_t* hw, uint8_t filter_id, bool is_range, bool en) { - HAL_ASSERT(filter_id < (SOC_TWAI_MASK_FILTER_NUM + SOC_TWAI_RANGE_FILTER_NUM)); + HAL_ASSERT(filter_id < (is_range ? SOC_TWAI_RANGE_FILTER_NUM : SOC_TWAI_MASK_FILTER_NUM)); + // The hw_filter_id of range_filter is indexed after mask_filter + uint8_t hw_filter_id = is_range ? filter_id + SOC_TWAI_MASK_FILTER_NUM : filter_id; if (en) { - hw->filter_control_filter_status.val |= TWAIFD_FANE << (filter_id * TWAIFD_FBNB_S); + hw->filter_control_filter_status.val |= TWAIFD_FANE << (hw_filter_id * TWAIFD_FBNB_S); } else { - hw->filter_control_filter_status.val &= ~(TWAIFD_FANE << (filter_id * TWAIFD_FBNB_S)); + hw->filter_control_filter_status.val &= ~(TWAIFD_FANE << (hw_filter_id * TWAIFD_FBNB_S)); } } @@ -497,15 +497,18 @@ static inline void twaifd_ll_filter_enable_basic_ext(twaifd_dev_t* hw, uint8_t f * * @param hw Pointer to the TWAI FD hardware instance * @param filter_id The unique ID of the filter to configure + * @param is_range Setting for range filter or mask filter * @param en True to receive, False to drop */ -static inline void twaifd_ll_filter_enable_fd_std(twaifd_dev_t* hw, uint8_t filter_id, bool en) +static inline void twaifd_ll_filter_enable_fd_std(twaifd_dev_t* hw, uint8_t filter_id, bool is_range, bool en) { - HAL_ASSERT(filter_id < (SOC_TWAI_MASK_FILTER_NUM + SOC_TWAI_RANGE_FILTER_NUM)); + HAL_ASSERT(filter_id < (is_range ? SOC_TWAI_RANGE_FILTER_NUM : SOC_TWAI_MASK_FILTER_NUM)); + // The hw_filter_id of range_filter is indexed after mask_filter + uint8_t hw_filter_id = is_range ? filter_id + SOC_TWAI_MASK_FILTER_NUM : filter_id; if (en) { - hw->filter_control_filter_status.val |= TWAIFD_FAFB << (filter_id * TWAIFD_FBNB_S); + hw->filter_control_filter_status.val |= TWAIFD_FAFB << (hw_filter_id * TWAIFD_FBNB_S); } else { - hw->filter_control_filter_status.val &= ~(TWAIFD_FAFB << (filter_id * TWAIFD_FBNB_S)); + hw->filter_control_filter_status.val &= ~(TWAIFD_FAFB << (hw_filter_id * TWAIFD_FBNB_S)); } } @@ -514,15 +517,18 @@ static inline void twaifd_ll_filter_enable_fd_std(twaifd_dev_t* hw, uint8_t filt * * @param hw Pointer to the TWAI FD hardware instance * @param filter_id The unique ID of the filter to configure + * @param is_range Setting for range filter or mask filter * @param en True to receive, False to drop */ -static inline void twaifd_ll_filter_enable_fd_ext(twaifd_dev_t* hw, uint8_t filter_id, bool en) +static inline void twaifd_ll_filter_enable_fd_ext(twaifd_dev_t* hw, uint8_t filter_id, bool is_range, bool en) { - HAL_ASSERT(filter_id < (SOC_TWAI_MASK_FILTER_NUM + SOC_TWAI_RANGE_FILTER_NUM)); + HAL_ASSERT(filter_id < (is_range ? SOC_TWAI_RANGE_FILTER_NUM : SOC_TWAI_MASK_FILTER_NUM)); + // The hw_filter_id of range_filter is indexed after mask_filter + uint8_t hw_filter_id = is_range ? filter_id + SOC_TWAI_MASK_FILTER_NUM : filter_id; if (en) { - hw->filter_control_filter_status.val |= TWAIFD_FAFE << (filter_id * TWAIFD_FBNB_S); + hw->filter_control_filter_status.val |= TWAIFD_FAFE << (hw_filter_id * TWAIFD_FBNB_S); } else { - hw->filter_control_filter_status.val &= ~(TWAIFD_FAFE << (filter_id * TWAIFD_FBNB_S)); + hw->filter_control_filter_status.val &= ~(TWAIFD_FAFE << (hw_filter_id * TWAIFD_FBNB_S)); } } @@ -530,48 +536,30 @@ static inline void twaifd_ll_filter_enable_fd_ext(twaifd_dev_t* hw, uint8_t filt * @brief Set Bit Acceptance Filter * @param hw Start address of the TWAI registers * @param filter_id Filter number id + * @param is_ext Filter for ext_id or std_id * @param code Acceptance Code * @param mask Acceptance Mask */ -static inline void twaifd_ll_filter_set_id_mask(twaifd_dev_t* hw, uint8_t filter_id, uint32_t code, uint32_t mask) +static inline void twaifd_ll_filter_set_id_mask(twaifd_dev_t* hw, uint8_t filter_id, bool is_ext, uint32_t code, uint32_t mask) { - hw->mask_filters[filter_id].filter_mask.bit_mask_val = mask; - hw->mask_filters[filter_id].filter_val.bit_val = code; + hw->mask_filters[filter_id].filter_mask.bit_mask_val = is_ext ? mask : (mask << TWAIFD_IDENTIFIER_BASE_S); + hw->mask_filters[filter_id].filter_val.bit_val = is_ext ? code : (code << TWAIFD_IDENTIFIER_BASE_S); } /** * @brief Set Range Acceptance Filter * @param hw Start address of the TWAI registers * @param filter_id Filter number id + * @param is_ext Filter for ext_id or std_id * @param high The id range high limit * @param low The id range low limit */ -static inline void twaifd_ll_filter_set_range(twaifd_dev_t* hw, uint8_t filter_id, uint32_t high, uint32_t low) +static inline void twaifd_ll_filter_set_range(twaifd_dev_t* hw, uint8_t filter_id, bool is_ext, uint32_t high, uint32_t low) { - hw->range_filters[filter_id].ran_low.bit_ran_low_val = low; - hw->range_filters[filter_id].ran_high.bit_ran_high_val = high; + hw->range_filters[filter_id].ran_low.bit_ran_low_val = is_ext ? low : (low << TWAIFD_IDENTIFIER_BASE_S); + hw->range_filters[filter_id].ran_high.bit_ran_high_val = is_ext ? high : (high << TWAIFD_IDENTIFIER_BASE_S); } -/** - * @brief Enable or disable bit or range frame filtering for a specific filter. - * - * @param hw Pointer to the TWAI-FD device hardware. - * @param filter_id The ID of the filter to configure (0-2 for bit filter, 3 for range filter). - * @param enable True to enable the filter, false to disable. - */ -static inline void twaifd_ll_filter_enable(twaifd_dev_t* hw, uint8_t filter_id, bool enable) -{ - HAL_ASSERT(filter_id < (SOC_TWAI_MASK_FILTER_NUM + SOC_TWAI_RANGE_FILTER_NUM)); - twaifd_filter_control_filter_status_reg_t reg_val = {.val = hw->filter_control_filter_status.val}; - - // enable or disable filter selection - if (enable) { - reg_val.val |= BIT(filter_id + TWAIFD_SFA_S); - } else { - reg_val.val &= ~BIT(filter_id + TWAIFD_SFA_S); - } - hw->filter_control_filter_status.val = reg_val.val; -} /* ------------------------- TX Buffer Registers ------------------------- */ /** @@ -610,6 +598,7 @@ static inline uint32_t twaifd_ll_get_tx_buffer_status(twaifd_dev_t *hw, uint8_t * @param buffer_idx * @param cmd The command want to set, see `TWAIFD_LL_TX_CMD_` */ +__attribute__((always_inline)) static inline void twaifd_ll_set_tx_cmd(twaifd_dev_t *hw, uint8_t buffer_idx, uint32_t cmd) { hw->tx_command_txtb_info.val = (cmd | BIT(buffer_idx + TWAIFD_TXB1_S)); @@ -640,6 +629,7 @@ static inline void twaifd_ll_set_tx_buffer_priority(twaifd_dev_t *hw, uint8_t bu * * @note Call twaifd_ll_format_frame_header() and twaifd_ll_format_frame_data() to format a frame */ +__attribute__((always_inline)) static inline void twaifd_ll_mount_tx_buffer(twaifd_dev_t *hw, twaifd_frame_buffer_t *tx_frame, uint8_t buffer_idx) { //Copy formatted frame into TX buffer @@ -667,6 +657,7 @@ static inline uint32_t twaifd_ll_get_rx_buffer_size(twaifd_dev_t *hw) * @param hw Pointer to the TWAI-FD device hardware. * @return Number of frames in the RX buffer. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_rx_frame_count(twaifd_dev_t *hw) { return hw->rx_status_rx_settings.rxfrc; @@ -691,6 +682,7 @@ static inline uint32_t twaifd_ll_is_rx_buffer_empty(twaifd_dev_t *hw) * * @note Call twaifd_ll_parse_frame_header() and twaifd_ll_parse_frame_data() to parse the formatted frame */ +__attribute__((always_inline)) static inline void twaifd_ll_get_rx_frame(twaifd_dev_t *hw, twaifd_frame_buffer_t *rx_frame) { // If rx_automatic_mode enabled, hw->rx_data.rx_data should 32bit access @@ -713,6 +705,7 @@ static inline void twaifd_ll_get_rx_frame(twaifd_dev_t *hw, twaifd_frame_buffer_ * @param[in] final_dlc data length code of frame. * @param[out] tx_frame Pointer to store formatted frame */ +__attribute__((always_inline)) static inline void twaifd_ll_format_frame_header(const twai_frame_header_t *header, uint8_t final_dlc, twaifd_frame_buffer_t *tx_frame) { HAL_ASSERT(final_dlc <= TWAIFD_FRAME_MAX_DLC); @@ -745,6 +738,7 @@ static inline void twaifd_ll_format_frame_header(const twai_frame_header_t *head * @param[in] len data length of data buffer. * @param[out] tx_frame Pointer to store formatted frame */ +__attribute__((always_inline)) static inline void twaifd_ll_format_frame_data(const uint8_t *buffer, uint32_t len, twaifd_frame_buffer_t *tx_frame) { HAL_ASSERT(len <= TWAIFD_FRAME_MAX_LEN); @@ -757,6 +751,7 @@ static inline void twaifd_ll_format_frame_data(const uint8_t *buffer, uint32_t l * @param[in] rx_frame Pointer to formatted frame * @param[out] p_frame_header Including DLC, ID, Format, etc. */ +__attribute__((always_inline)) static inline void twaifd_ll_parse_frame_header(const twaifd_frame_buffer_t *rx_frame, twai_frame_header_t *p_frame_header) { //Copy frame information @@ -786,6 +781,7 @@ static inline void twaifd_ll_parse_frame_header(const twaifd_frame_buffer_t *rx_ * @param[out] buffer Pointer to an 8 byte array to save data * @param[in] buffer_len_limit The buffer length limit, If less then frame data length, over length data will dropped */ +__attribute__((always_inline)) static inline void twaifd_ll_parse_frame_data(const twaifd_frame_buffer_t *rx_frame, uint8_t *buffer, int len_limit) { memcpy(buffer, rx_frame->data, len_limit); diff --git a/components/hal/include/hal/twai_hal.h b/components/hal/include/hal/twai_hal.h index 9492d3bf39..eca202f4c8 100644 --- a/components/hal/include/hal/twai_hal.h +++ b/components/hal/include/hal/twai_hal.h @@ -69,6 +69,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; + bool enable_listen_only; #if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT) twai_hal_frame_t tx_frame_save; twai_ll_reg_save_t reg_save; @@ -81,6 +82,8 @@ typedef struct { typedef struct { int controller_id; uint32_t clock_source_hz; + uint32_t intr_mask; + bool enable_listen_only; } twai_hal_config_t; /** diff --git a/components/hal/include/hal/twai_types.h b/components/hal/include/hal/twai_types.h index 9e29f8a461..232e611b7f 100644 --- a/components/hal/include/hal/twai_types.h +++ b/components/hal/include/hal/twai_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -20,8 +20,8 @@ extern "C" { #define TWAI_EXT_ID_MASK 0x1FFFFFFFU /* Mask of the ID fields in an extended frame */ /* TWAI payload length and DLC definitions */ -#define TWAI_FRAME_MAX_DLC 8 -#define TWAI_FRAME_MAX_LEN 8 +#define TWAI_FRAME_MAX_DLC 8 +#define TWAI_FRAME_MAX_LEN 8 /* TWAI FD payload length and DLC definitions */ #define TWAIFD_FRAME_MAX_DLC 15 @@ -31,21 +31,12 @@ extern "C" { * @brief TWAI error states */ typedef enum { - TWAI_ERROR_ACTIVE, /**< Error active state: TEC/REC < 96 */ - TWAI_ERROR_WARNING, /**< Error warning state: TEC/REC >= 96 and < 128 */ - TWAI_ERROR_PASSIVE, /**< Error passive state: TEC/REC >= 128 and < 256 */ - TWAI_ERROR_BUS_OFF, /**< Bus-off state: TEC >= 256 (node disconnected from bus) */ + TWAI_ERROR_ACTIVE, /**< Error active state: TEC/REC < 96 */ + TWAI_ERROR_WARNING, /**< Error warning state: TEC/REC >= 96 and < 128 */ + TWAI_ERROR_PASSIVE, /**< Error passive state: TEC/REC >= 128 and < 256 */ + TWAI_ERROR_BUS_OFF, /**< Bus-off state: TEC >= 256 (node offline) */ } twai_error_state_t; -/** - * @brief TWAI Controller operating modes - */ -typedef enum { - TWAI_MODE_NORMAL, /**< Normal operating mode where TWAI controller can send/receive/acknowledge messages */ - TWAI_MODE_NO_ACK, /**< Transmission does not require acknowledgment. Use this mode for self testing */ - TWAI_MODE_LISTEN_ONLY, /**< The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */ -} twai_mode_t; - /** * @brief TWAI group clock source * @note User should select the clock source based on the power and resolution requirement @@ -57,7 +48,7 @@ typedef int twai_clock_source_t; #endif /** - * @brief TWAI baud rate timing config advanced mode + * @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 { @@ -68,35 +59,33 @@ typedef struct { 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 */ - union { - bool en_multi_samp; /**< Enable multi-sampling for one bit to avoid noise and detect errors */ - bool triple_sampling; /**< Deprecated, in favor of `en_multi_samp` */ - }; + 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; + /** * @brief TWAI frame header/format struct type */ typedef struct { - union { - struct { - uint32_t ide:1; /**< Extended Frame Format (29bit ID) */ - uint32_t rtr:1; /**< Message is a Remote Frame */ - uint32_t fdf:1; /**< TWAI 2.0: Reserved, FD: FD Frames. */ - uint32_t brs:1; /**< TWAI 2.0: Reserved, FD: Bit Rate Shift. */ - uint32_t esi:1; /**< Transmit side error indicator for received frame */ - uint32_t loopback:1; /**< Temporary transmit as loop back for this trans, if setting `TWAI_MODE_LOOP_BACK`, all transmit is loop back */ - int8_t retrans_count; /**< Re-trans count on transfer fail, -1: infinite, 0: no re-trans, others: re-trans times. */ - uint32_t reserved:18; /**< Reserved */ - }; - uint32_t format_val; /**< Frame format/type integrate value */ + uint32_t id; /**< message arbitration identification */ + uint16_t dlc; /**< message data length code */ + struct { + uint32_t ide: 1; /**< Extended Frame Format (29bit ID) */ + uint32_t rtr: 1; /**< Message is a Remote Frame */ + uint32_t fdf: 1; /**< Message is FD format, allow max 64 byte of data */ + uint32_t brs: 1; /**< Transmit message with Bit Rate Shift. */ + uint32_t esi: 1; /**< Transmit side error indicator for received frame */ }; union { uint64_t timestamp; /**< Timestamp for received message */ uint64_t trigger_time; /**< Trigger time for transmitting message*/ }; - uint32_t id; /**< message arbitration identification */ - uint8_t dlc; /**< message data length code */ } twai_frame_header_t; #ifdef __cplusplus diff --git a/components/hal/include/hal/twai_types_deprecated.h b/components/hal/include/hal/twai_types_deprecated.h index 5ac883abf2..3dfe57d522 100644 --- a/components/hal/include/hal/twai_types_deprecated.h +++ b/components/hal/include/hal/twai_types_deprecated.h @@ -51,34 +51,34 @@ extern "C" { * @note The available bit rates are dependent on the chip target and ECO version. */ #if SOC_TWAI_BRP_MAX > 256 -#define TWAI_TIMING_CONFIG_1KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 20000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_5KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 100000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_10KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 200000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_1KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 20000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_5KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 100000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_10KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 200000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} #endif // SOC_TWAI_BRP_MAX > 256 #if (SOC_TWAI_BRP_MAX > 128) || (CONFIG_ESP32_REV_MIN_FULL >= 200) -#define TWAI_TIMING_CONFIG_12_5KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 312500, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_16KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 400000, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_20KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 400000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_12_5KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 312500, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_16KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 400000, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_20KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 400000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} #endif // (SOC_TWAI_BRP_MAX > 128) || (CONFIG_ESP32_REV_MIN_FULL >= 200) #if SOC_TWAI_CLK_SUPPORT_XTAL -#define TWAI_TIMING_CONFIG_25KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 500000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_25KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 500000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} #else // APB80M -#define TWAI_TIMING_CONFIG_25KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 625000, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_25KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 625000, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} #endif -#define TWAI_TIMING_CONFIG_50KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 1000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_100KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 2000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_125KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 2000000, .brp = 0, .prop_seg = 0, .tseg_1 = 11, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_250KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 4000000, .brp = 0, .prop_seg = 0, .tseg_1 = 11, .tseg_2 = 4, .sjw = 2, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_50KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 1000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_100KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 2000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_125KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 2000000, .brp = 0, .prop_seg = 0, .tseg_1 = 11, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_250KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 4000000, .brp = 0, .prop_seg = 0, .tseg_1 = 11, .tseg_2 = 4, .sjw = 2, .ssp_offset = 0, .triple_sampling = false} #if SOC_TWAI_CLK_SUPPORT_XTAL && CONFIG_XTAL_FREQ == 40 // TWAI_CLK_SRC_XTAL = 40M -#define TWAI_TIMING_CONFIG_500KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 10000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_800KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 20000000, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_1MBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 20000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_500KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 10000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_800KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 20000000, .brp = 0, .prop_seg = 0, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_1MBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 20000000, .brp = 0, .prop_seg = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .ssp_offset = 0, .triple_sampling = false} #else // 32M, 48M, APB80M -#define TWAI_TIMING_CONFIG_500KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 8000000, .brp = 0, .prop_seg = 0, .tseg_1 = 11, .tseg_2 = 4, .sjw = 2, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_800KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 8000000, .brp = 0, .prop_seg = 0, .tseg_1 = 6, .tseg_2 = 3, .sjw = 1, .en_multi_samp = false} -#define TWAI_TIMING_CONFIG_1MBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 8000000, .brp = 0, .prop_seg = 0, .tseg_1 = 5, .tseg_2 = 2, .sjw = 1, .en_multi_samp = false} +#define TWAI_TIMING_CONFIG_500KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 8000000, .brp = 0, .prop_seg = 0, .tseg_1 = 11, .tseg_2 = 4, .sjw = 2, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_800KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 8000000, .brp = 0, .prop_seg = 0, .tseg_1 = 6, .tseg_2 = 3, .sjw = 1, .ssp_offset = 0, .triple_sampling = false} +#define TWAI_TIMING_CONFIG_1MBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 8000000, .brp = 0, .prop_seg = 0, .tseg_1 = 5, .tseg_2 = 2, .sjw = 1, .ssp_offset = 0, .triple_sampling = false} #endif /** @@ -87,6 +87,15 @@ extern "C" { #define TWAI_FILTER_CONFIG_ACCEPT_ALL() {.acceptance_code = 0, .acceptance_mask = 0xFFFFFFFF, .single_filter = true} /** @endcond */ +/** + * @brief TWAI Controller operating modes + */ +typedef enum { + TWAI_MODE_NORMAL, /**< Normal operating mode where TWAI controller can send/receive/acknowledge messages */ + TWAI_MODE_NO_ACK, /**< Transmission does not require acknowledgment. Use this mode for self testing */ + TWAI_MODE_LISTEN_ONLY, /**< The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */ +} twai_mode_t; + /** * @brief Structure to store a TWAI message * diff --git a/components/hal/twai_hal.c b/components/hal/twai_hal.c index 25d0de61a8..1745280d7d 100644 --- a/components/hal/twai_hal.c +++ b/components/hal/twai_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +17,20 @@ /* ---------------------------- Init and Config ----------------------------- */ +#if SOC_TWAI_SUPPORT_FD +bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) +{ + hal_ctx->dev = TWAIFD_LL_GET_HW(config->controller_id); + hal_ctx->enable_listen_only = config->enable_listen_only; + + twaifd_ll_reset(hal_ctx->dev); + //mode should be changed under disabled + twaifd_ll_enable_hw(hal_ctx->dev, false); + twaifd_ll_enable_rxfifo_auto_incrase(hal_ctx->dev, true); + twaifd_ll_enable_intr(hal_ctx->dev, config->intr_mask); + return true; +} +#else bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) { //Initialize HAL context @@ -99,3 +113,4 @@ void twai_hal_stop(twai_hal_context_t *hal_ctx) //Any TX is immediately halted on entering reset mode TWAI_HAL_CLEAR_BITS(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED | TWAI_HAL_STATE_FLAG_RUNNING); } +#endif //SOC_TWAI_SUPPORT_FD diff --git a/components/hal/twai_hal_iram.c b/components/hal/twai_hal_iram.c index 76b532a20c..922701926e 100644 --- a/components/hal/twai_hal_iram.c +++ b/components/hal/twai_hal_iram.c @@ -16,7 +16,7 @@ #endif /* ----------------------------- Event Handling ----------------------------- */ - +#if !SOC_TWAI_SUPPORT_FD /** * Helper functions that can decode what events have been triggered based on * the values of the interrupt, status, TEC and REC registers. The HAL context's @@ -200,3 +200,4 @@ void twai_hal_set_tx_buffer_and_transmit(twai_hal_context_t *hal_ctx, twai_hal_f ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-overlapping-buffers") #endif //defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT) } +#endif // !SOC_TWAI_SUPPORT_FD diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index 48a50d29a6..680fa3f138 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -39,6 +39,10 @@ config SOC_MCPWM_SUPPORTED bool default y +config SOC_TWAI_SUPPORTED + bool + default y + config SOC_ETM_SUPPORTED bool default y diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index b6335da3de..077b6cdc4a 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -26,7 +26,7 @@ #define SOC_GPTIMER_SUPPORTED 1 #define SOC_PCNT_SUPPORTED 1 #define SOC_MCPWM_SUPPORTED 1 -// #define SOC_TWAI_SUPPORTED 1 // TODO: [ESP32C5] IDF-8691 +#define SOC_TWAI_SUPPORTED 1 #define SOC_ETM_SUPPORTED 1 #define SOC_PARLIO_SUPPORTED 1 #define SOC_ASYNC_MEMCPY_SUPPORTED 1 diff --git a/components/soc/esp32c5/twai_periph.c b/components/soc/esp32c5/twai_periph.c index 179116ddd2..7c3bbe6937 100644 --- a/components/soc/esp32c5/twai_periph.c +++ b/components/soc/esp32c5/twai_periph.c @@ -10,6 +10,7 @@ const twai_controller_signal_conn_t twai_controller_periph_signals = { .controllers = { [0] = { + .module_name = "TWAI0", .irq_id = ETS_TWAI0_INTR_SOURCE, .timer_irq_id = ETS_TWAI0_TIMER_INTR_SOURCE, .tx_sig = TWAI0_TX_IDX, @@ -19,6 +20,7 @@ const twai_controller_signal_conn_t twai_controller_periph_signals = { .stand_by_sig = TWAI0_STANDBY_IDX, }, [1] = { + .module_name = "TWAI1", .irq_id = ETS_TWAI1_INTR_SOURCE, .timer_irq_id = ETS_TWAI1_TIMER_INTR_SOURCE, .tx_sig = TWAI1_TX_IDX, diff --git a/components/soc/include/soc/twai_periph.h b/components/soc/include/soc/twai_periph.h index 8cb7682ebc..eac51da93e 100644 --- a/components/soc/include/soc/twai_periph.h +++ b/components/soc/include/soc/twai_periph.h @@ -23,6 +23,7 @@ extern "C" { typedef struct { struct { const periph_module_t module; // peripheral module + const char *module_name; // peripheral name const int irq_id; // interrupt source ID #if SOC_TWAI_SUPPORT_TIMESTAMP const int timer_irq_id; diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 6dacc50eb1..8af82ac318 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -481,7 +481,8 @@ examples/peripherals/touch_sensor/touch_sens_basic: examples/peripherals/twai/twai_alert_and_recovery: disable: - - if: SOC_TWAI_SUPPORTED != 1 + - if: SOC_TWAI_SUPPORTED != 1 or SOC_TWAI_SUPPORT_FD == 1 + reason: This example not support FD disable_test: - if: IDF_TARGET not in ["esp32"] temporary: true @@ -489,7 +490,8 @@ examples/peripherals/twai/twai_alert_and_recovery: examples/peripherals/twai/twai_network: disable: - - if: SOC_TWAI_SUPPORTED != 1 + - if: SOC_TWAI_SUPPORTED != 1 or SOC_TWAI_SUPPORT_FD == 1 + reason: This example not support FD disable_test: - if: 1 == 1 temporary: true @@ -497,7 +499,8 @@ examples/peripherals/twai/twai_network: examples/peripherals/twai/twai_self_test: disable: - - if: SOC_TWAI_SUPPORTED != 1 + - if: SOC_TWAI_SUPPORTED != 1 or SOC_TWAI_SUPPORT_FD == 1 + reason: This example not support FD disable_test: - if: IDF_TARGET not in ["esp32"] temporary: true