From 2fb43820c229f2268d3fdf7770057d601906f570 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 11:59:46 +0800 Subject: [PATCH] driver_ng: implement new rmt driver The legacy driver can't handle the breaking change between esp chips very well. And it's not elegant to extend new feature like DMA, ETM. The new driver can return a opaque handle for each RMT channel. An obvious transaction concept was also introduced. TX and RX functionalities are splited out. --- components/driver/CMakeLists.txt | 2 +- components/driver/Kconfig | 16 + components/driver/deprecated/rmt_legacy.c | 1 - components/driver/include/driver/rmt_common.h | 83 ++ .../driver/include/driver/rmt_encoder.h | 138 +++ components/driver/include/driver/rmt_rx.h | 103 ++ components/driver/include/driver/rmt_tx.h | 174 +++ components/driver/include/driver/rmt_types.h | 71 ++ components/driver/rmt/rmt_common.c | 186 +++ components/driver/rmt/rmt_encoder.c | 301 +++++ components/driver/rmt/rmt_private.h | 194 +++ components/driver/rmt/rmt_rx.c | 645 ++++++++++ components/driver/rmt/rmt_tx.c | 1043 +++++++++++++++++ .../driver/test_apps/rmt/CMakeLists.txt | 18 + components/driver/test_apps/rmt/README.md | 2 + .../driver/test_apps/rmt/main/CMakeLists.txt | 8 + .../driver/test_apps/rmt/main/test_app_main.c | 52 + .../test_apps/rmt/main/test_rmt_common.c | 100 ++ .../driver/test_apps/rmt/main/test_rmt_rx.c | 202 ++++ .../driver/test_apps/rmt/main/test_rmt_tx.c | 607 ++++++++++ .../rmt/main/test_util_rmt_encoders.c | 214 ++++ .../rmt/main/test_util_rmt_encoders.h | 21 + components/driver/test_apps/rmt/pytest_rmt.py | 24 + .../test_apps/rmt/sdkconfig.ci.iram_safe | 7 + .../driver/test_apps/rmt/sdkconfig.ci.release | 5 + .../driver/test_apps/rmt/sdkconfig.defaults | 2 + components/hal/include/hal/rmt_hal.h | 7 + components/hal/include/hal/rmt_types.h | 2 +- components/hal/rmt_hal.c | 15 + .../esp32c3/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32c3/include/soc/soc_caps.h | 2 +- .../esp32h2/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32h2/include/soc/soc_caps.h | 2 +- .../esp32s2/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32s2/include/soc/soc_caps.h | 2 +- .../esp32s3/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32s3/include/soc/soc_caps.h | 2 +- 37 files changed, 4248 insertions(+), 11 deletions(-) create mode 100644 components/driver/include/driver/rmt_common.h create mode 100644 components/driver/include/driver/rmt_encoder.h create mode 100644 components/driver/include/driver/rmt_rx.h create mode 100644 components/driver/include/driver/rmt_tx.h create mode 100644 components/driver/include/driver/rmt_types.h create mode 100644 components/driver/rmt/rmt_common.c create mode 100644 components/driver/rmt/rmt_encoder.c create mode 100644 components/driver/rmt/rmt_private.h create mode 100644 components/driver/rmt/rmt_rx.c create mode 100644 components/driver/rmt/rmt_tx.c create mode 100644 components/driver/test_apps/rmt/CMakeLists.txt create mode 100644 components/driver/test_apps/rmt/README.md create mode 100644 components/driver/test_apps/rmt/main/CMakeLists.txt create mode 100644 components/driver/test_apps/rmt/main/test_app_main.c create mode 100644 components/driver/test_apps/rmt/main/test_rmt_common.c create mode 100644 components/driver/test_apps/rmt/main/test_rmt_rx.c create mode 100644 components/driver/test_apps/rmt/main/test_rmt_tx.c create mode 100644 components/driver/test_apps/rmt/main/test_util_rmt_encoders.c create mode 100644 components/driver/test_apps/rmt/main/test_util_rmt_encoders.h create mode 100644 components/driver/test_apps/rmt/pytest_rmt.py create mode 100644 components/driver/test_apps/rmt/sdkconfig.ci.iram_safe create mode 100644 components/driver/test_apps/rmt/sdkconfig.ci.release create mode 100644 components/driver/test_apps/rmt/sdkconfig.defaults diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index e64af28e18..51ea19c312 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -46,7 +46,7 @@ if(CONFIG_SOC_SIGMADELTA_SUPPORTED) endif() if(CONFIG_SOC_RMT_SUPPORTED) - list(APPEND srcs "rmt.c") + list(APPEND srcs "rmt/rmt_common.c" "rmt/rmt_encoder.c" "rmt/rmt_rx.c" "rmt/rmt_tx.c" "deprecated/rmt_legacy.c") endif() if(CONFIG_SOC_PCNT_SUPPORTED) diff --git a/components/driver/Kconfig b/components/driver/Kconfig index 366e5ff831..3a33772188 100644 --- a/components/driver/Kconfig +++ b/components/driver/Kconfig @@ -281,6 +281,15 @@ menu "Driver configurations" menu "RMT Configuration" depends on SOC_RMT_SUPPORTED + config RMT_ISR_IRAM_SAFE + bool "RMT ISR IRAM-Safe" + default n + select GDMA_ISR_IRAM_SAFE if SOC_RMT_SUPPORT_DMA # RMT basic functionality relies on GDMA callback + select GDMA_CTRL_FUNC_IN_IRAM if SOC_RMT_SUPPORT_DMA # RMT needs to restart the GDMA in the interrupt + help + Ensure the RMT interrupt is IRAM-Safe by allowing the interrupt handler to be + executable when the cache is disabled (e.g. SPI Flash write). + config RMT_SUPPRESS_DEPRECATE_WARN bool "Suppress legacy driver deprecated warning" default n @@ -288,6 +297,13 @@ menu "Driver configurations" Wether to suppress the deprecation warnings when using legacy rmt driver (driver/rmt.h). If you want to continue using the legacy driver, and don't want to see related deprecation warnings, you can enable this option. + + config RMT_ENABLE_DEBUG_LOG + bool "Enable debug log" + default n + help + Wether to enable the debug log message for RMT driver. + Note that, this option only controls the RMT driver log, won't affect other drivers. endmenu # RMT Configuration endmenu # Driver configurations diff --git a/components/driver/deprecated/rmt_legacy.c b/components/driver/deprecated/rmt_legacy.c index 6aa6a89b3f..e4659eb9e9 100644 --- a/components/driver/deprecated/rmt_legacy.c +++ b/components/driver/deprecated/rmt_legacy.c @@ -14,7 +14,6 @@ #include "driver/gpio.h" #include "esp_private/periph_ctrl.h" #include "driver/rmt_types_legacy.h" -#include "driver_ng.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" diff --git a/components/driver/include/driver/rmt_common.h b/components/driver/include/driver/rmt_common.h new file mode 100644 index 0000000000..a2223e9027 --- /dev/null +++ b/components/driver/include/driver/rmt_common.h @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief RMT carrier wave configuration (for either modulation or demodulation) + */ +typedef struct { + uint32_t frequency_hz; /*!< Carrier wave frequency, in Hz, 0 means disabling the carrier */ + float duty_cycle; /*!< Carrier wave duty cycle (0~100%) */ + struct { + uint32_t polarity_active_low: 1; /*!< Specify the polarity of carrier, by default it's modulated to base signal's high level */ + uint32_t always_on: 1; /*!< If set, the carrier can always exist even there's not transfer undergoing */ + } flags; +} rmt_carrier_config_t; + +/** + * @brief Delete an RMT channel + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @return + * - ESP_OK: Delete RMT channel successfully + * - ESP_ERR_INVALID_ARG: Delete RMT channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Delete RMT channel failed because it is still in working + * - ESP_FAIL: Delete RMT channel failed because of other error + */ +esp_err_t rmt_del_channel(rmt_channel_handle_t channel); + +/** + * @brief Apply modulation feature for TX channel or demodulation feature for RX channel + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @param[in] config Carrier configuration. Specially, a NULL config means to disable the carrier modulation or demodulation feature + * @return + * - ESP_OK: Apply carrier configuration successfully + * - ESP_ERR_INVALID_ARG: Apply carrier configuration failed because of invalid argument + * - ESP_FAIL: Apply carrier configuration failed because of other error + */ +esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config); + +/** + * @brief Enable the RMT channel + * + * @note This function will acquire a PM lock that might be installed during channel allocation + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @return + * - ESP_OK: Enable RMT channel successfully + * - ESP_ERR_INVALID_ARG: Enable RMT channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Enable RMT channel failed because it's enabled already + * - ESP_FAIL: Enable RMT channel failed because of other error + */ +esp_err_t rmt_enable(rmt_channel_handle_t channel); + +/** + * @brief Disable the RMT channel + * + * @note This function will release a PM lock that might be installed during channel allocation + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @return + * - ESP_OK: Disable RMT channel successfully + * - ESP_ERR_INVALID_ARG: Disable RMT channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Disable RMT channel failed because it's not enabled yet + * - ESP_FAIL: Disable RMT channel failed because of other error + */ +esp_err_t rmt_disable(rmt_channel_handle_t channel); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_encoder.h b/components/driver/include/driver/rmt_encoder.h new file mode 100644 index 0000000000..ccf2aa130e --- /dev/null +++ b/components/driver/include/driver/rmt_encoder.h @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "hal/rmt_types.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of RMT encoder + */ +typedef struct rmt_encoder_t rmt_encoder_t; + +/** + * @brief RMT encoding state + */ +typedef enum { + RMT_ENCODING_COMPLETE = (1 << 0), /*!< The encoding session is finished, the caller can continue with subsequent encoding */ + RMT_ENCODING_MEM_FULL = (1 << 1), /*!< The encoding artifact memory is full, the caller should return from current encoding session */ +} rmt_encode_state_t; + +/** + * @brief Interface of RMT encoder + */ +struct rmt_encoder_t { + /** + * @brief Encode the user data into RMT symbols and write into RMT memory + * + * @note The encoding function will also be called from an ISR context, thus the function must not call any blocking API. + * @note It's recommended to put this function implementation in the IRAM, to achieve a high performance and less interrupt latency. + * + * @param[in] encoder Encoder handle + * @param[in] tx_channel RMT TX channel handle, returned from `rmt_new_tx_channel()` + * @param[in] primary_data App data to be encoded into RMT symbols + * @param[in] data_size Size of primary_data, in bytes + * @param[out] ret_state Returned current encoder's state + * @return Number of RMT symbols that the primary data has been encoded into + */ + size_t (*encode)(rmt_encoder_t *encoder, rmt_channel_handle_t tx_channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state); + + /** + * @brief Reset encoding state + * + * @param[in] encoder Encoder handle + * @return + * - ESP_OK: reset encoder successfully + * - ESP_FAIL: reset encoder failed + */ + esp_err_t (*reset)(rmt_encoder_t *encoder); + + /** + * @brief Delete encoder object + * + * @param[in] encoder Encoder handle + * @return + * - ESP_OK: delete encoder successfully + * - ESP_FAIL: delete encoder failed + */ + esp_err_t (*del)(rmt_encoder_t *encoder); +}; + +/** + * @brief Bytes encoder configuration + */ +typedef struct { + rmt_symbol_word_t bit0; /*!< How to represent BIT0 in RMT symbol */ + rmt_symbol_word_t bit1; /*!< How to represent BIT1 in RMT symbol */ + struct { + uint32_t msb_first: 1; /*!< Whether to encode MSB bit first */ + } flags; +} rmt_bytes_encoder_config_t; + +/** + * @brief Copy encoder configuration + */ +typedef struct { +} rmt_copy_encoder_config_t; + +/** + * @brief Create RMT bytes encoder, which can encode byte stream into RMT symbols + * + * @param[in] config Bytes encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_OK: Create RMT bytes encoder successfully + * - ESP_ERR_INVALID_ARG: Create RMT bytes encoder failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT bytes encoder failed because out of memory + * - ESP_FAIL: Create RMT bytes encoder failed because of other error + */ +esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +/** + * @brief Create RMT copy encoder, which copies the given RMT symbols into RMT memory + * + * @param[in] config Copy encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_OK: Create RMT copy encoder successfully + * - ESP_ERR_INVALID_ARG: Create RMT copy encoder failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT copy encoder failed because out of memory + * - ESP_FAIL: Create RMT copy encoder failed because of other error + */ +esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +/** + * @brief Delete RMT encoder + * + * @param[in] encoder RMT encoder handle, created by e.g `rmt_new_bytes_encoder()` + * @return + * - ESP_OK: Delete RMT encoder successfully + * - ESP_ERR_INVALID_ARG: Delete RMT encoder failed because of invalid argument + * - ESP_FAIL: Delete RMT encoder failed because of other error + */ +esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder); + +/** + * @brief Reset RMT encoder + * + * @param[in] encoder RMT encoder handle, created by e.g `rmt_new_bytes_encoder()` + * @return + * - ESP_OK: Reset RMT encoder successfully + * - ESP_ERR_INVALID_ARG: Reset RMT encoder failed because of invalid argument + * - ESP_FAIL: Reset RMT encoder failed because of other error + */ +esp_err_t rmt_encoder_reset(rmt_encoder_handle_t encoder); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_rx.h b/components/driver/include/driver/rmt_rx.h new file mode 100644 index 0000000000..4ef83e67d3 --- /dev/null +++ b/components/driver/include/driver/rmt_rx.h @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/rmt_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Group of RMT RX callbacks + * @note The callbacks are all running under ISR environment + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. + */ +typedef struct { + rmt_rx_done_callback_t on_recv_done; /*!< Event callback, invoked when one RMT channel receiving transaction completes */ +} rmt_rx_event_callbacks_t; + +/** + * @brief RMT RX channel specific configuration + */ +typedef struct { + int gpio_num; /*!< GPIO number used by RMT RX channel. Set to -1 if unused */ + rmt_clock_source_t clk_src; /*!< Clock source of RMT RX channel, channels in the same group must use the same clock source */ + uint32_t resolution_hz; /*!< Channel clock resolution, in Hz */ + size_t mem_block_symbols; /*!< Size of memory block, in number of `rmt_symbol_word_t`, must be an even */ + struct { + uint32_t invert_in: 1; /*!< Whether to invert the incoming RMT channel signal */ + uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */ + uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */ + } flags; +} rmt_rx_channel_config_t; + +/** + * @brief RMT receive specific configuration + */ +typedef struct { + uint32_t signal_range_min_ns; /*!< A pulse whose width is smaller than this threshold will be treated as glitch and ignored */ + uint32_t signal_range_max_ns; /*!< RMT will stop receiving if one symbol level has kept more than `signal_range_max_ns` */ +} rmt_receive_config_t; + +/** + * @brief Create a RMT RX channel + * + * @param[in] config RX channel configurations + * @param[out] ret_chan Returned generic RMT channel handle + * @return + * - ESP_OK: Create RMT RX channel successfully + * - ESP_ERR_INVALID_ARG: Create RMT RX channel failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT RX channel failed because out of memory + * - ESP_ERR_NOT_FOUND: Create RMT RX channel failed because all RMT channels are used up and no more free one + * - ESP_ERR_NOT_SUPPORTED: Create RMT RX channel failed because some feature is not supported by hardware, e.g. DMA feature is not supported by hardware + * - ESP_FAIL: Create RMT RX channel failed because of other error + */ +esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan); + +/** + * @brief Initiate a receive job for RMT RX channel + * + * @note This function is non-blocking, it initiates a new receive job and then returns. + * User should check the received data from the `on_recv_done` callback that registered by `rmt_rx_register_event_callbacks()`. + * + * @param[in] rx_channel RMT RX channel that created by `rmt_new_rx_channel()` + * @param[in] buffer The buffer to store the received RMT symbols + * @param[in] buffer_size size of the `buffer`, in bytes + * @param[in] config Receive specific configurations + * @return + * - ESP_OK: Initiate receive job successfully + * - ESP_ERR_INVALID_ARG: Initiate receive job failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Initiate receive job failed because channel is not enabled + * - ESP_FAIL: Initiate receive job failed because of other error + */ +esp_err_t rmt_receive(rmt_channel_handle_t rx_channel, void *buffer, size_t buffer_size, const rmt_receive_config_t *config); + +/** + * @brief Set callbacks for RMT RX channel + * + * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. + * + * @param[in] rx_channel RMT generic channel that created by `rmt_new_rx_channel()` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + * - ESP_FAIL: Set event callbacks failed because of other error + */ +esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t rx_channel, const rmt_rx_event_callbacks_t *cbs, void *user_data); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_tx.h b/components/driver/include/driver/rmt_tx.h new file mode 100644 index 0000000000..058b9ad4ce --- /dev/null +++ b/components/driver/include/driver/rmt_tx.h @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/rmt_common.h" +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Group of RMT TX callbacks + * @note The callbacks are all running under ISR environment + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. + */ +typedef struct { + rmt_tx_done_callback_t on_trans_done; /*!< Event callback, invoked when transmission is finished */ +} rmt_tx_event_callbacks_t; + +/** + * @brief RMT TX channel specific configuration + */ +typedef struct { + int gpio_num; /*!< GPIO number used by RMT TX channel. Set to -1 if unused */ + rmt_clock_source_t clk_src; /*!< Clock source of RMT TX channel, channels in the same group must use the same clock source */ + uint32_t resolution_hz; /*!< Channel clock resolution, in Hz */ + size_t mem_block_symbols; /*!< Size of memory block, in number of `rmt_symbol_word_t`, must be an even */ + size_t trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background */ + struct { + uint32_t invert_out: 1; /*!< Whether to invert the RMT channel signal before output to GPIO pad */ + uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */ + uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */ + } flags; +} rmt_tx_channel_config_t; + +/** + * @brief RMT transmit specific configuration + */ +typedef struct { + int loop_count; /*!< Specify the times of transmission in a loop, -1 means transmitting in an infinite loop */ + struct { + uint32_t eot_level : 1; /*!< Set the output level for the "End Of Transmission" */ + } flags; +} rmt_transmit_config_t; + +/** + * @brief Synchronous manager configuration + */ +typedef struct { + const rmt_channel_handle_t *tx_channel_array; /*!< Array of TX channels that are about to be managed by a synchronous controller */ + size_t array_size; /*!< Size of the `tx_channel_array` */ +} rmt_sync_manager_config_t; + +/** + * @brief Create a RMT TX channel + * + * @param[in] config TX channel configurations + * @param[out] ret_chan Returned generic RMT channel handle + * @return + * - ESP_OK: Create RMT TX channel successfully + * - ESP_ERR_INVALID_ARG: Create RMT TX channel failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT TX channel failed because out of memory + * - ESP_ERR_NOT_FOUND: Create RMT TX channel failed because all RMT channels are used up and no more free one + * - ESP_ERR_NOT_SUPPORTED: Create RMT TX channel failed because some feature is not supported by hardware, e.g. DMA feature is not supported by hardware + * - ESP_FAIL: Create RMT TX channel failed because of other error + */ +esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan); + +/** + * @brief Transmit data by RMT TX channel + * + * @note This function will construct a transaction descriptor and push to a queue. The transaction will not start immediately until it's dispatched in the ISR. + * If there're too many transactions pending in the queue, this function will block until the queue has free space. + * @note The data to be transmitted will be encoded into RMT symbols by the specific `encoder`. + * + * @param[in] tx_channel RMT TX channel that created by `rmt_new_tx_channel()` + * @param[in] encoder RMT encoder that created by various factory APIs like `rmt_new_bytes_encoder()` + * @param[in] payload The raw data to be encoded into RMT symbols + * @param[in] payload_bytes Size of the `payload` in bytes + * @param[in] config Transmission specific configuration + * @return + * - ESP_OK: Transmit data successfully + * - ESP_ERR_INVALID_ARG: Transmit data failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Transmit data failed because channel is not enabled + * - ESP_ERR_NOT_SUPPORTED: Transmit data failed because some feature is not supported by hardware, e.g. unsupported loop count + * - ESP_FAIL: Transmit data failed because of other error + */ +esp_err_t rmt_transmit(rmt_channel_handle_t tx_channel, rmt_encoder_handle_t encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config); + +/** + * @brief Wait for all pending TX transactions done + * + * @note This function will block forever if the pending transaction can't be finished within a limited time (e.g. an infinite loop transaction). + * See also `rmt_disable()` for how to terminate a working channel. + * + * @param[in] tx_channel RMT TX channel that created by `rmt_new_tx_channel()` + * @param[in] timeout_ms Wait timeout, in ms. Specially, -1 means to wait forever. + * @return + * - ESP_OK: Flush transactions successfully + * - ESP_ERR_INVALID_ARG: Flush transactions failed because of invalid argument + * - ESP_ERR_TIMEOUT: Flush transactions failed because of timeout + * - ESP_FAIL: Flush transactions failed because of other error + */ +esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t tx_channel, int timeout_ms); + +/** + * @brief Set event callbacks for RMT TX channel + * + * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. + * + * @param[in] tx_channel RMT generic channel that created by `rmt_new_tx_channel()` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + * - ESP_FAIL: Set event callbacks failed because of other error + */ +esp_err_t rmt_tx_register_event_callbacks(rmt_channel_handle_t tx_channel, const rmt_tx_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Create a synchronization manager for multiple TX channels, so that the managed channel can start transmitting at the same time + * + * @note All the channels to be managed should be enabled by `rmt_enable()` before put them into sync manager. + * + * @param[in] config Synchronization manager configuration + * @param[out] ret_synchro Returned synchronization manager handle + * @return + * - ESP_OK: Create sync manager successfully + * - ESP_ERR_INVALID_ARG: Create sync manager failed because of invalid argument + * - ESP_ERR_NOT_SUPPORTED: Create sync manager failed because it is not supported by hardware + * - ESP_ERR_INVALID_STATE: Create sync manager failed because not all channels are enabled + * - ESP_ERR_NO_MEM: Create sync manager failed because out of memory + * - ESP_ERR_NOT_FOUND: Create sync manager failed because all sync controllers are used up and no more free one + * - ESP_FAIL: Create sync manager failed because of other error + */ +esp_err_t rmt_new_sync_manager(const rmt_sync_manager_config_t *config, rmt_sync_manager_handle_t *ret_synchro); + +/** + * @brief Delete synchronization manager + * + * @param[in] synchro Synchronization manager handle returned from `rmt_new_sync_manager()` + * @return + * - ESP_OK: Delete the synchronization manager successfully + * - ESP_ERR_INVALID_ARG: Delete the synchronization manager failed because of invalid argument + * - ESP_FAIL: Delete the synchronization manager failed because of other error + */ +esp_err_t rmt_del_sync_manager(rmt_sync_manager_handle_t synchro); + +/** + * @brief Reset synchronization manager + * + * @param[in] synchro Synchronization manager handle returned from `rmt_new_sync_manager()` + * @return + * - ESP_OK: Reset the synchronization manager successfully + * - ESP_ERR_INVALID_ARG: Reset the synchronization manager failed because of invalid argument + * - ESP_FAIL: Reset the synchronization manager failed because of other error + */ +esp_err_t rmt_sync_reset(rmt_sync_manager_handle_t synchro); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_types.h b/components/driver/include/driver/rmt_types.h new file mode 100644 index 0000000000..dd05b4cae0 --- /dev/null +++ b/components/driver/include/driver/rmt_types.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "hal/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of RMT channel handle + */ +typedef struct rmt_channel_t *rmt_channel_handle_t; + +/** + * @brief Type of RMT synchronization manager handle + */ +typedef struct rmt_sync_manager_t *rmt_sync_manager_handle_t; + +/** + * @brief Type of RMT encoder handle + */ +typedef struct rmt_encoder_t *rmt_encoder_handle_t; + +/** + * @brief Type of RMT TX done event data + */ +typedef struct { + size_t num_symbols; /*!< The number of transmitted RMT symbols (only one round is counted if it's a loop transmission) */ +} rmt_tx_done_event_data_t; + +/** + * @brief Prototype of RMT event callback + * @param[in] tx_chan RMT channel handle, created from `rmt_new_tx_channel()` + * @param[in] edata RMT event data + * @param[in] user_ctx User registered context, passed from `rmt_tx_register_event_callbacks()` + * + * @return Whether a high priority task has been waken up by this callback function + */ +typedef bool (*rmt_tx_done_callback_t)(rmt_channel_handle_t tx_chan, rmt_tx_done_event_data_t *edata, void *user_ctx); + +/** + * @brief Type of RMT RX done event data + */ +typedef struct { + rmt_symbol_word_t *received_symbols; /*!< Point to the received RMT symbols */ + size_t num_symbols; /*!< The number of received RMT symbols */ +} rmt_rx_done_event_data_t; + +/** + * @brief Prototype of RMT event callback + * + * @param[in] rx_chan RMT channel handle, created from `rmt_new_rx_channel()` + * @param[in] edata Point to RMT event data + * @param[in] user_ctx User registered context, passed from `rmt_rx_register_event_callbacks()` + * + * @return Whether a high priority task has been waken up by this function + */ +typedef bool (*rmt_rx_done_callback_t)(rmt_channel_handle_t rx_chan, rmt_rx_done_event_data_t *edata, void *user_ctx); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/rmt/rmt_common.c b/components/driver/rmt/rmt_common.c new file mode 100644 index 0000000000..9df192c4eb --- /dev/null +++ b/components/driver/rmt/rmt_common.c @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "rmt_private.h" +#include "clk_ctrl_os.h" +#include "soc/rtc.h" +#include "soc/rmt_periph.h" +#include "hal/rmt_ll.h" +#include "driver/gpio.h" +#include "esp_private/esp_clk.h" +#include "esp_private/periph_ctrl.h" + +static const char *TAG = "rmt"; + +typedef struct rmt_platform_t { + _lock_t mutex; // platform level mutex lock + rmt_group_t *groups[SOC_RMT_GROUPS]; // array of RMT group instances + int group_ref_counts[SOC_RMT_GROUPS]; // reference count used to protect group install/uninstall +} rmt_platform_t; + +static rmt_platform_t s_platform; // singleton platform + +rmt_group_t *rmt_acquire_group_handle(int group_id) +{ + bool new_group = false; + rmt_group_t *group = NULL; + + // prevent install rmt group concurrently + _lock_acquire(&s_platform.mutex); + if (!s_platform.groups[group_id]) { + group = heap_caps_calloc(1, sizeof(rmt_group_t), RMT_MEM_ALLOC_CAPS); + if (group) { + new_group = true; + s_platform.groups[group_id] = group; + group->group_id = group_id; + group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // initial occupy_mask: 1111...100...0 + group->occupy_mask = UINT32_MAX & ~((1 << SOC_RMT_CHANNELS_PER_GROUP) - 1); + // group clock won't be configured at this stage, it will be set when allocate the first channel + group->clk_src = RMT_CLK_SRC_NONE; + // enable APB access RMT registers + periph_module_enable(rmt_periph_signals.groups[group_id].module); + // hal layer initialize + rmt_hal_init(&group->hal); + } + } else { // group already install + group = s_platform.groups[group_id]; + } + if (group) { + // someone acquired the group handle means we have a new object that refer to this group + s_platform.group_ref_counts[group_id]++; + } + _lock_release(&s_platform.mutex); + + if (new_group) { + ESP_LOGD(TAG, "new group(%d) at %p, occupy=%x", group_id, group, group->occupy_mask); + } + return group; +} + +void rmt_release_group_handle(rmt_group_t *group) +{ + int group_id = group->group_id; + bool do_deinitialize = false; + + _lock_acquire(&s_platform.mutex); + s_platform.group_ref_counts[group_id]--; + if (s_platform.group_ref_counts[group_id] == 0) { + do_deinitialize = true; + s_platform.groups[group_id] = NULL; + // hal layer deinitialize + rmt_hal_deinit(&group->hal); + periph_module_disable(rmt_periph_signals.groups[group_id].module); + free(group); + } + _lock_release(&s_platform.mutex); + + if (do_deinitialize) { + ESP_LOGD(TAG, "del group(%d)", group_id); + } +} + +esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src) +{ + esp_err_t ret = ESP_OK; + rmt_group_t *group = chan->group; + int channel_id = chan->channel_id; + uint32_t periph_src_clk_hz = 0; + bool clock_selection_conflict = false; + // check if we need to update the group clock source, group clock source is shared by all channels + portENTER_CRITICAL(&group->spinlock); + if (group->clk_src == RMT_CLK_SRC_NONE) { + group->clk_src = clk_src; + } else { + clock_selection_conflict = (group->clk_src != clk_src); + } + portEXIT_CRITICAL(&group->spinlock); + ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG, + "group clock conflict, already is %d but attempt to %d", group->clk_src, clk_src); + + // [clk_tree] TODO: replace the following switch table by clk_tree API + switch (clk_src) { +#if SOC_RMT_SUPPORT_APB + case RMT_CLK_SRC_APB: + periph_src_clk_hz = esp_clk_apb_freq(); +#if CONFIG_PM_ENABLE + sprintf(chan->pm_lock_name, "rmt_%d_%d", group->group_id, channel_id); // e.g. rmt_0_0 + ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, chan->pm_lock_name, &chan->pm_lock); + ESP_RETURN_ON_ERROR(ret, TAG, "create APB_FREQ_MAX lock failed"); + ESP_LOGD(TAG, "install APB_FREQ_MAX lock for RMT channel (%d,%d)", group->group_id, channel_id); +#endif // CONFIG_PM_ENABLE +#endif // SOC_RMT_SUPPORT_APB + break; +#if SOC_RMT_SUPPORT_AHB + case RMT_CLK_SRC_AHB: + // TODO: decide which kind of PM lock we should use for such clock + periph_src_clk_hz = 48 * 1000 * 1000; + break; +#endif // SOC_RMT_SUPPORT_AHB +#if SOC_RMT_SUPPORT_XTAL + case RMT_CLK_SRC_XTAL: + periph_src_clk_hz = esp_clk_xtal_freq(); + break; +#endif // SOC_RMT_SUPPORT_XTAL +#if SOC_RMT_SUPPORT_REF_TICK + case RMT_CLK_SRC_APB_F1M: + periph_src_clk_hz = REF_CLK_FREQ; + break; +#endif // SOC_RMT_SUPPORT_REF_TICK +#if SOC_RMT_SUPPORT_RC_FAST + case RMT_CLK_SRC_RC_FAST: + periph_rtc_dig_clk8m_enable(); + periph_src_clk_hz = periph_rtc_dig_clk8m_get_freq(); + break; +#endif // SOC_RMT_SUPPORT_RC_FAST + default: + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not supported", clk_src); + break; + } + // no division for group clock source, to achieve highest resolution + rmt_ll_set_group_clock_src(group->hal.regs, channel_id, clk_src, 1, 1, 0); + group->resolution_hz = periph_src_clk_hz; + ESP_LOGD(TAG, "group clock resolution:%u", group->resolution_hz); + return ret; +} + +esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config) +{ + // specially, we allow config to be NULL, means to disable the carrier submodule + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return channel->set_carrier_action(channel, config); +} + +esp_err_t rmt_del_channel(rmt_channel_handle_t channel) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state"); + gpio_reset_pin(channel->gpio_num); + return channel->del(channel); +} + +esp_err_t rmt_enable(rmt_channel_handle_t channel) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state"); + return channel->enable(channel); +} + +esp_err_t rmt_disable(rmt_channel_handle_t channel) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); + return channel->disable(channel); +} diff --git a/components/driver/rmt/rmt_encoder.c b/components/driver/rmt/rmt_encoder.c new file mode 100644 index 0000000000..25d8b0e7ef --- /dev/null +++ b/components/driver/rmt/rmt_encoder.c @@ -0,0 +1,301 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "driver/rmt_encoder.h" +#include "rmt_private.h" + +static const char *TAG = "rmt"; + +typedef struct rmt_bytes_encoder_t { + rmt_encoder_t base; // encoder base class + size_t last_bit_index; // index of the encoding bit position in the encoding byte + size_t last_byte_index; // index of the encoding byte in the primary stream + rmt_symbol_word_t bit0; // bit zero representing + rmt_symbol_word_t bit1; // bit one representing + struct { + uint32_t msb_first: 1; // encode MSB firstly + } flags; +} rmt_bytes_encoder_t; + +typedef struct rmt_copy_encoder_t { + rmt_encoder_t base; // encoder base class + size_t last_symbol_index; // index of symbol position in the primary stream +} rmt_copy_encoder_t; + +static esp_err_t rmt_bytes_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base); + // reset index to zero + bytes_encoder->last_bit_index = 0; + bytes_encoder->last_byte_index = 0; + return ESP_OK; +} + +static inline uint8_t _bitwise_reverse(uint8_t n) +{ + n = ((n & 0xf0) >> 4) | ((n & 0x0f) << 4); + n = ((n & 0xcc) >> 2) | ((n & 0x33) << 2); + n = ((n & 0xaa) >> 1) | ((n & 0x55) << 1); + return n; +} + +static size_t IRAM_ATTR rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + const uint8_t *nd = (const uint8_t *)primary_data; + rmt_encode_state_t state = 0; + dma_descriptor_t *desc0 = NULL; + dma_descriptor_t *desc1 = NULL; + + size_t byte_index = bytes_encoder->last_byte_index; + size_t bit_index = bytes_encoder->last_bit_index; + // how many symbols will be generated by the encoder + size_t mem_want = (data_size - byte_index - 1) * 8 + (8 - bit_index); + // how many symbols we can save for this round + size_t mem_have = tx_chan->mem_end - tx_chan->mem_off; + // where to put the encoded symbols? DMA buffer or RMT HW memory + rmt_symbol_word_t *mem_to = channel->dma_chan ? channel->dma_mem_base : channel->hw_mem_base; + // how many symbols will be encoded in this round + size_t encode_len = MIN(mem_want, mem_have); + bool encoding_truncated = mem_have < mem_want; + bool encoding_space_free = mem_have > mem_want; + + if (channel->dma_chan) { + // mark the start descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc0 = &tx_chan->dma_nodes[0]; + } else { + desc0 = &tx_chan->dma_nodes[1]; + } + } + + size_t len = encode_len; + while (len > 0) { + // start from last time truncated encoding + uint8_t cur_byte = nd[byte_index]; + // bit-wise reverse + if (bytes_encoder->flags.msb_first) { + cur_byte = _bitwise_reverse(cur_byte); + } + while ((len > 0) && (bit_index < 8)) { + if (cur_byte & (1 << bit_index)) { + mem_to[tx_chan->mem_off++] = bytes_encoder->bit1; + } else { + mem_to[tx_chan->mem_off++] = bytes_encoder->bit0; + } + len--; + bit_index++; + } + if (bit_index >= 8) { + byte_index++; + bit_index = 0; + } + } + + if (channel->dma_chan) { + // mark the end descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc1 = &tx_chan->dma_nodes[0]; + } else { + desc1 = &tx_chan->dma_nodes[1]; + } + + // cross line, means desc0 has prepared with sufficient data buffer + if (desc0 != desc1) { + desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + } + + if (encoding_truncated) { + // this encoding has not finished yet, save the truncated position + bytes_encoder->last_bit_index = bit_index; + bytes_encoder->last_byte_index = byte_index; + } else { + // reset internal index if encoding session has finished + bytes_encoder->last_bit_index = 0; + bytes_encoder->last_byte_index = 0; + state |= RMT_ENCODING_COMPLETE; + } + + if (!encoding_space_free) { + // no more free memory, the caller should yield + state |= RMT_ENCODING_MEM_FULL; + } + + // reset offset pointer when exceeds maximum range + if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) { + if (channel->dma_chan) { + desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + tx_chan->mem_off = 0; + } + + *ret_state = state; + return encode_len; +} + +static esp_err_t rmt_copy_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base); + copy_encoder->last_symbol_index = 0; + return ESP_OK; +} + +static size_t IRAM_ATTR rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_symbol_word_t *symbols = (rmt_symbol_word_t *)primary_data; + rmt_encode_state_t state = 0; + dma_descriptor_t *desc0 = NULL; + dma_descriptor_t *desc1 = NULL; + + size_t symbol_index = copy_encoder->last_symbol_index; + // how many symbols will be copied by the encoder + size_t mem_want = (data_size / 4 - symbol_index); + // how many symbols we can save for this round + size_t mem_have = tx_chan->mem_end - tx_chan->mem_off; + // where to put the encoded symbols? DMA buffer or RMT HW memory + rmt_symbol_word_t *mem_to = channel->dma_chan ? channel->dma_mem_base : channel->hw_mem_base; + // how many symbols will be encoded in this round + size_t encode_len = MIN(mem_want, mem_have); + bool encoding_truncated = mem_have < mem_want; + bool encoding_space_free = mem_have > mem_want; + + if (channel->dma_chan) { + // mark the start descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc0 = &tx_chan->dma_nodes[0]; + } else { + desc0 = &tx_chan->dma_nodes[1]; + } + } + + size_t len = encode_len; + while (len > 0) { + mem_to[tx_chan->mem_off++] = symbols[symbol_index++]; + len--; + } + + if (channel->dma_chan) { + // mark the end descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc1 = &tx_chan->dma_nodes[0]; + } else { + desc1 = &tx_chan->dma_nodes[1]; + } + + // cross line, means desc0 has prepared with sufficient data buffer + if (desc0 != desc1) { + desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + } + + if (encoding_truncated) { + // this encoding has not finished yet, save the truncated position + copy_encoder->last_symbol_index = symbol_index; + } else { + // reset internal index if encoding session has finished + copy_encoder->last_symbol_index = 0; + state |= RMT_ENCODING_COMPLETE; + } + + if (!encoding_space_free) { + // no more free memory, the caller should yield + state |= RMT_ENCODING_MEM_FULL; + } + + // reset offset pointer when exceeds maximum range + if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) { + if (channel->dma_chan) { + desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + tx_chan->mem_off = 0; + } + + *ret_state = state; + return encode_len; +} + +static esp_err_t rmt_del_bytes_encoder(rmt_encoder_t *encoder) +{ + rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base); + free(bytes_encoder); + return ESP_OK; +} + +static esp_err_t rmt_del_copy_encoder(rmt_encoder_t *encoder) +{ + rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base); + free(copy_encoder); + return ESP_OK; +} + +esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + rmt_bytes_encoder_t *encoder = heap_caps_calloc(1, sizeof(rmt_bytes_encoder_t), RMT_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for bytes encoder"); + encoder->base.encode = rmt_encode_bytes; + encoder->base.del = rmt_del_bytes_encoder; + encoder->base.reset = rmt_bytes_encoder_reset; + encoder->bit0 = config->bit0; + encoder->bit1 = config->bit1; + encoder->flags.msb_first = config->flags.msb_first; + // return general encoder handle + *ret_encoder = &encoder->base; + ESP_LOGD(TAG, "new bytes encoder @%p", encoder); +err: + return ret; +} + +esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + rmt_copy_encoder_t *encoder = heap_caps_calloc(1, sizeof(rmt_copy_encoder_t), RMT_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for copy encoder"); + encoder->base.encode = rmt_encode_copy; + encoder->base.del = rmt_del_copy_encoder; + encoder->base.reset = rmt_copy_encoder_reset; + // return general encoder handle + *ret_encoder = &encoder->base; + ESP_LOGD(TAG, "new copy encoder @%p", encoder); +err: + return ret; +} + +esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder) +{ + ESP_RETURN_ON_FALSE(encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return encoder->del(encoder); +} + +esp_err_t rmt_encoder_reset(rmt_encoder_handle_t encoder) +{ + ESP_RETURN_ON_FALSE(encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return encoder->reset(encoder); +} diff --git a/components/driver/rmt/rmt_private.h b/components/driver/rmt/rmt_private.h new file mode 100644 index 0000000000..fb2fb0f1e3 --- /dev/null +++ b/components/driver/rmt/rmt_private.h @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_err.h" +#include "soc/soc_caps.h" +#include "hal/rmt_types.h" +#include "hal/rmt_hal.h" +#include "hal/dma_types.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "esp_pm.h" +#include "esp_attr.h" +#include "esp_private/gdma.h" +#include "driver/rmt_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_RMT_ISR_IRAM_SAFE +#define RMT_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define RMT_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +// RMT driver object is per-channel, the interrupt source is shared between channels +#if CONFIG_RMT_ISR_IRAM_SAFE +#define RMT_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM) +#else +#define RMT_INTR_ALLOC_FLAG ESP_INTR_FLAG_SHARED +#endif + +// Hopefully the channel offset won't change in other targets +#define RMT_TX_CHANNEL_OFFSET_IN_GROUP 0 +#define RMT_RX_CHANNEL_OFFSET_IN_GROUP (SOC_RMT_CHANNELS_PER_GROUP - SOC_RMT_TX_CANDIDATES_PER_GROUP) + +// DMA buffer size must align to `rmt_symbol_word_t` +#define RMT_DMA_DESC_BUF_MAX_SIZE (DMA_DESCRIPTOR_BUFFER_MAX_SIZE & ~(sizeof(rmt_symbol_word_t) - 1)) + +#define RMT_DMA_NODES_PING_PONG 2 // two nodes ping-pong +#define RMT_PM_LOCK_NAME_LEN_MAX 16 + +// RMTMEM address is declared in .peripherals.ld +extern rmt_symbol_word_t RMTMEM; + +typedef enum { + RMT_CHANNEL_DIRECTION_TX, + RMT_CHANNEL_DIRECTION_RX, +} rmt_channel_direction_t; + +typedef enum { + RMT_FSM_INIT, + RMT_FSM_ENABLE, +} rmt_fsm_t; + +enum { + RMT_TX_QUEUE_READY, + RMT_TX_QUEUE_PROGRESS, + RMT_TX_QUEUE_COMPLETE, + RMT_TX_QUEUE_MAX, +}; + +typedef struct rmt_group_t rmt_group_t; +typedef struct rmt_channel_t rmt_channel_t; +typedef struct rmt_tx_channel_t rmt_tx_channel_t; +typedef struct rmt_rx_channel_t rmt_rx_channel_t; +typedef struct rmt_sync_manager_t rmt_sync_manager_t; + +struct rmt_group_t { + int group_id; // group ID, index from 0 + portMUX_TYPE spinlock; // to protect per-group register level concurrent access + rmt_hal_context_t hal; // hal layer for each group + rmt_clock_source_t clk_src; // record the group clock source, group clock is shared by all channels + uint32_t resolution_hz; // resolution of group clock + uint32_t occupy_mask; // a set bit in the mask indicates the channel is not available + rmt_tx_channel_t *tx_channels[SOC_RMT_TX_CANDIDATES_PER_GROUP]; // array of RMT TX channels + rmt_rx_channel_t *rx_channels[SOC_RMT_RX_CANDIDATES_PER_GROUP]; // array of RMT RX channels + rmt_sync_manager_t *sync_manager; // sync manager, this can be extended into an array if there're more sync controllers in one RMT group +}; + +struct rmt_channel_t { + int channel_id; // channel ID, index from 0 + int gpio_num; // GPIO number used by RMT RX channel + uint32_t channel_mask; // mask of the memory blocks that occupied by the channel + size_t mem_block_num; // number of occupied RMT memory blocks + rmt_group_t *group; // which group the channel belongs to + portMUX_TYPE spinlock; // prevent channel resource accessing by user and interrupt concurrently + uint32_t resolution_hz; // channel clock resolution + intr_handle_t intr; // allocated interrupt handle for each channel + rmt_fsm_t fsm; // channel life cycle specific FSM + rmt_channel_direction_t direction; // channel direction + rmt_symbol_word_t *hw_mem_base; // base address of RMT channel hardware memory + rmt_symbol_word_t *dma_mem_base; // base address of RMT channel DMA buffer + gdma_channel_handle_t dma_chan; // DMA channel + esp_pm_lock_handle_t pm_lock; // power management lock +#if CONFIG_PM_ENABLE + char pm_lock_name[RMT_PM_LOCK_NAME_LEN_MAX]; // pm lock name +#endif + // RMT channel common interface + // The following IO functions will have per-implementation for TX and RX channel + esp_err_t (*del)(rmt_channel_t *channel); + esp_err_t (*set_carrier_action)(rmt_channel_t *channel, const rmt_carrier_config_t *config); + esp_err_t (*enable)(rmt_channel_t *channel); + esp_err_t (*disable)(rmt_channel_t *channel); +}; + +typedef struct { + rmt_encoder_handle_t encoder; // encode user payload into RMT symbols + const void *payload; // encoder payload + size_t payload_bytes; // payload size + int loop_count; // transaction can be continued in a loop for specific times + int remain_loop_count; // user required loop count may exceed hardware limitation, the driver will transfer them in batches + size_t transmitted_symbol_num; // track the number of transmitted symbols + struct { + uint32_t eot_level : 1; // Set the output level for the "End Of Transmission" + uint32_t encoding_done: 1; // Indicate whether the encoding has finished (not the encoding of transmission) + } flags; +} rmt_tx_trans_desc_t; + +struct rmt_tx_channel_t { + rmt_channel_t base; // channel base class + size_t mem_off; // runtime argument, indicating the next writing position in the RMT hardware memory + size_t mem_end; // runtime argument, incidating the end of current writing region + size_t ping_pong_symbols; // ping-pong size (half of the RMT channel memory) + size_t queue_size; // size of transaction queue + size_t num_trans_inflight; // indicates the number of transactions that are undergoing but not recycled to ready_queue + void *queues_storage; // storage of transaction queues + QueueHandle_t trans_queues[RMT_TX_QUEUE_MAX]; // transaction queues + StaticQueue_t trans_queue_structs[RMT_TX_QUEUE_MAX]; // memory to store the static structure for trans_queues + rmt_tx_trans_desc_t *cur_trans; // points to current transaction + void *user_data; // user context + rmt_tx_done_callback_t on_trans_done; // callback, invoked on trans done + dma_descriptor_t dma_nodes[RMT_DMA_NODES_PING_PONG]; // DMA descriptor nodes, make up a circular link list + rmt_tx_trans_desc_t trans_desc_pool[]; // tranfer descriptor pool +}; + +typedef struct { + void *buffer; // buffer for saving the received symbols + size_t buffer_size; // size of the buffer, in bytes + size_t received_symbol_num; // track the number of received symbols + size_t copy_dest_off; // tracking offset in the copy destination +} rmt_rx_trans_desc_t; + +struct rmt_rx_channel_t { + rmt_channel_t base; // channel base class + size_t mem_off; // starting offset to fetch the symbols in RMTMEM + size_t ping_pong_symbols; // ping-pong size (half of the RMT channel memory) + rmt_rx_done_callback_t on_recv_done; // callback, invoked on receive done + void *user_data; // user context + rmt_rx_trans_desc_t trans_desc; // transaction description + size_t num_dma_nodes; // number of DMA nodes, determined by how big the memory block that user configures + dma_descriptor_t dma_nodes[]; // DMA link nodes +}; + +/** + * @brief Acquire RMT group handle + * + * @param group_id Group ID + * @return RMT group handle + */ +rmt_group_t *rmt_acquire_group_handle(int group_id); + +/** + * @brief Release RMT group handle + * + * @param group RMT group handle, returned from `rmt_acquire_group_handle` + */ +void rmt_release_group_handle(rmt_group_t *group); + +/** + * @brief Set clock source for RMT peripheral + * + * @param chan RMT channel handle + * @param clk_src Clock source + * @return + * - ESP_OK: Set clock source successfully + * - ESP_ERR_NOT_SUPPORTED: Set clock source failed because the clk_src is not supported + * - ESP_ERR_INVALID_STATE: Set clock source failed because the clk_src is different from other RMT channel + * - ESP_FAIL: Set clock source failed because of other error + */ +esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/rmt/rmt_rx.c b/components/driver/rmt/rmt_rx.c new file mode 100644 index 0000000000..a4fc401cf4 --- /dev/null +++ b/components/driver/rmt/rmt_rx.c @@ -0,0 +1,645 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "esp_rom_gpio.h" +#include "soc/rmt_periph.h" +#include "soc/rtc.h" +#include "hal/rmt_ll.h" +#include "hal/gpio_hal.h" +#include "driver/gpio.h" +#include "driver/rmt_rx.h" +#include "rmt_private.h" + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +static const char *TAG = "rmt"; + +static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel); +static esp_err_t rmt_rx_demodulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config); +static esp_err_t rmt_rx_enable(rmt_channel_handle_t channel); +static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel); +static void rmt_rx_default_isr(void *args); + +#if SOC_RMT_SUPPORT_DMA +static bool rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data); + +static void rmt_rx_mount_dma_buffer(dma_descriptor_t *desc_array, size_t array_size, const void *buffer, size_t buffer_size) +{ + size_t prepared_length = 0; + uint8_t *data = (uint8_t *)buffer; + int dma_node_i = 0; + dma_descriptor_t *desc = NULL; + while (buffer_size > RMT_DMA_DESC_BUF_MAX_SIZE) { + desc = &desc_array[dma_node_i]; + desc->dw0.suc_eof = 0; + desc->dw0.size = RMT_DMA_DESC_BUF_MAX_SIZE; + desc->dw0.length = 0; + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->buffer = &data[prepared_length]; + desc->next = &desc_array[dma_node_i + 1]; + prepared_length += RMT_DMA_DESC_BUF_MAX_SIZE; + buffer_size -= RMT_DMA_DESC_BUF_MAX_SIZE; + dma_node_i++; + } + if (buffer_size) { + desc = &desc_array[dma_node_i]; + desc->dw0.suc_eof = 0; + desc->dw0.size = buffer_size; + desc->dw0.length = 0; + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->buffer = &data[prepared_length]; + prepared_length += buffer_size; + } + desc->next = NULL; // one-off DMA chain +} + +static esp_err_t rmt_rx_init_dma_link(rmt_rx_channel_t *rx_channel, const rmt_rx_channel_config_t *config) +{ + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_RX, + }; + ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &rx_channel->base.dma_chan), TAG, "allocate RX DMA channel failed"); + gdma_strategy_config_t gdma_strategy_conf = { + .auto_update_desc = true, + .owner_check = true, + }; + gdma_apply_strategy(rx_channel->base.dma_chan, &gdma_strategy_conf); + gdma_rx_event_callbacks_t cbs = { + .on_recv_eof = rmt_dma_rx_eof_cb, + }; + gdma_register_rx_event_callbacks(rx_channel->base.dma_chan, &cbs, rx_channel); + return ESP_OK; +} +#endif // SOC_RMT_SUPPORT_DMA + +static esp_err_t rmt_rx_register_to_group(rmt_rx_channel_t *rx_channel, const rmt_rx_channel_config_t *config) +{ + size_t mem_block_num = 0; + // start to search for a free channel + // a channel can take up its neighbour's memory block, so the neighbour channel won't work, we should skip these "invaded" ones + int channel_scan_start = RMT_RX_CHANNEL_OFFSET_IN_GROUP; + int channel_scan_end = RMT_RX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP; + if (config->flags.with_dma) { + // for DMA mode, the memory block number is always 1; for non-DMA mode, memory block number is configured by user + mem_block_num = 1; + // Only the last channel has the DMA capability + channel_scan_start = RMT_RX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP - 1; + rx_channel->ping_pong_symbols = 0; // with DMA, we don't need to do ping-pong + } else { + // one channel can occupy multiple memory blocks + mem_block_num = config->mem_block_symbols / SOC_RMT_MEM_WORDS_PER_CHANNEL; + if (mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL < config->mem_block_symbols) { + mem_block_num++; + } + rx_channel->ping_pong_symbols = mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL / 2; + } + rx_channel->base.mem_block_num = mem_block_num; + + // search free channel and then register to the group + // memory blocks used by one channel must be continuous + uint32_t channel_mask = (1 << mem_block_num) - 1; + rmt_group_t *group = NULL; + int channel_id = -1; + for (int i = 0; i < SOC_RMT_GROUPS; i++) { + group = rmt_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i); + portENTER_CRITICAL(&group->spinlock); + for (int j = channel_scan_start; j < channel_scan_end; j++) { + if (!(group->occupy_mask & (channel_mask << j))) { + group->occupy_mask |= (channel_mask << j); + // the channel ID should index from 0 + channel_id = j - RMT_RX_CHANNEL_OFFSET_IN_GROUP; + group->rx_channels[channel_id] = rx_channel; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (channel_id < 0) { + // didn't find a capable channel in the group, don't forget to release the group handle + rmt_release_group_handle(group); + group = NULL; + } else { + rx_channel->base.channel_id = channel_id; + rx_channel->base.channel_mask = channel_mask; + rx_channel->base.group = group; + break; + } + } + ESP_RETURN_ON_FALSE(channel_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free rx channels"); + return ESP_OK; +} + +static void rmt_rx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *group) +{ + portENTER_CRITICAL(&group->spinlock); + group->rx_channels[channel->channel_id] = NULL; + group->occupy_mask &= ~(channel->channel_mask << (channel->channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP)); + portEXIT_CRITICAL(&group->spinlock); + // channel has a reference on group, release it now + rmt_release_group_handle(group); +} + +static esp_err_t rmt_rx_destory(rmt_rx_channel_t *rx_channel) +{ + if (rx_channel->base.intr) { + ESP_RETURN_ON_ERROR(esp_intr_free(rx_channel->base.intr), TAG, "delete interrupt service failed"); + } + if (rx_channel->base.pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(rx_channel->base.pm_lock), TAG, "delete pm_lock failed"); + } +#if SOC_RMT_SUPPORT_DMA + if (rx_channel->base.dma_chan) { + ESP_RETURN_ON_ERROR(gdma_del_channel(rx_channel->base.dma_chan), TAG, "delete dma channel failed"); + } +#endif // SOC_RMT_SUPPORT_DMA + if (rx_channel->base.group) { + // de-register channel from RMT group + rmt_rx_unregister_from_group(&rx_channel->base, rx_channel->base.group); + } + free(rx_channel); + return ESP_OK; +} + +esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan) +{ +#if CONFIG_RMT_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + rmt_rx_channel_t *rx_channel = NULL; + ESP_GOTO_ON_FALSE(config && ret_chan && config->resolution_hz, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(config->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO number"); + ESP_GOTO_ON_FALSE((config->mem_block_symbols & 0x01) == 0 && config->mem_block_symbols >= SOC_RMT_MEM_WORDS_PER_CHANNEL, + ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols must be even and at least %d", SOC_RMT_MEM_WORDS_PER_CHANNEL); +#if !SOC_RMT_SUPPORT_DMA + ESP_GOTO_ON_FALSE(config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "DMA not supported"); +#endif // SOC_RMT_SUPPORT_DMA + + size_t num_dma_nodes = 0; + if (config->flags.with_dma) { + num_dma_nodes = config->mem_block_symbols * sizeof(rmt_symbol_word_t) / RMT_DMA_DESC_BUF_MAX_SIZE + 1; + } + // malloc channel memory + uint32_t mem_caps = RMT_MEM_ALLOC_CAPS; + if (config->flags.with_dma) { + // DMA descriptors must be placed in internal SRAM + mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; + } + rx_channel = heap_caps_calloc(1, sizeof(rmt_rx_channel_t) + num_dma_nodes * sizeof(dma_descriptor_t), mem_caps); + ESP_GOTO_ON_FALSE(rx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for rx channel"); + rx_channel->num_dma_nodes = num_dma_nodes; + // register the channel to group + ESP_GOTO_ON_ERROR(rmt_rx_register_to_group(rx_channel, config), err, TAG, "register channel failed"); + rmt_group_t *group = rx_channel->base.group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = rx_channel->base.channel_id; + int group_id = group->group_id; + // select the clock source + ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&rx_channel->base, config->clk_src), err, TAG, "set group clock failed"); + + // reset channel, make sure the RX engine is not working, and events are cleared + portENTER_CRITICAL(&group->spinlock); + rmt_hal_rx_channel_reset(&group->hal, channel_id); + portEXIT_CRITICAL(&group->spinlock); + + // When channel receives an end-maker, a DMA in_suc_eof interrupt will be generated + // So we don't rely on RMT interrupt any more, GDMA event callback is sufficient + if (config->flags.with_dma) { +#if SOC_RMT_SUPPORT_DMA + ESP_GOTO_ON_ERROR(rmt_rx_init_dma_link(rx_channel, config), err, TAG, "install rx DMA failed"); +#endif // SOC_RMT_SUPPORT_DMA + } else { + // RMT interrupt is mandatory if the channel doesn't use DMA + int isr_flags = RMT_INTR_ALLOC_FLAG; + ret = esp_intr_alloc_intrstatus(rmt_periph_signals.groups[group_id].irq, isr_flags, + (uint32_t)rmt_ll_get_interrupt_status_reg(hal->regs), + RMT_LL_EVENT_RX_MASK(channel_id), rmt_rx_default_isr, rx_channel, &rx_channel->base.intr); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install rx interrupt failed"); + } + + // set channel clock resolution + uint32_t real_div = group->resolution_hz / config->resolution_hz; + rmt_ll_rx_set_channel_clock_div(hal->regs, channel_id, real_div); + // resolution loss due to division, calculate the real resolution + rx_channel->base.resolution_hz = group->resolution_hz / real_div; + if (rx_channel->base.resolution_hz != config->resolution_hz) { + ESP_LOGW(TAG, "channel resolution loss, real=%u", rx_channel->base.resolution_hz); + } + + rmt_ll_rx_set_mem_blocks(hal->regs, channel_id, rx_channel->base.mem_block_num); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); +#if SOC_RMT_SUPPORT_RX_PINGPONG + rmt_ll_rx_set_limit(hal->regs, channel_id, rx_channel->ping_pong_symbols); + // always enable rx wrap, both DMA mode and ping-pong mode rely this feature + rmt_ll_rx_enable_wrap(hal->regs, channel_id, true); +#endif +#if SOC_RMT_SUPPORT_RX_DEMODULATION + // disable carrier demodulation by default, can reenable by `rmt_apply_carrier()` + rmt_ll_rx_enable_carrier_demodulation(hal->regs, channel_id, false); +#endif + + // GPIO Matrix/MUX configuration + rx_channel->base.gpio_num = config->gpio_num; + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + // also enable the input path is `io_loop_back` is on, this is useful for debug + .mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), + .pull_down_en = false, + .pull_up_en = true, + .pin_bit_mask = 1ULL << config->gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed"); + esp_rom_gpio_connect_in_signal(config->gpio_num, + rmt_periph_signals.groups[group_id].channels[channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP].rx_sig, + config->flags.invert_in); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_num], PIN_FUNC_GPIO); + + // initialize other members of rx channel + rx_channel->base.direction = RMT_CHANNEL_DIRECTION_RX; + rx_channel->base.fsm = RMT_FSM_INIT; + rx_channel->base.hw_mem_base = &RMTMEM + (channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP) * SOC_RMT_MEM_WORDS_PER_CHANNEL; + rx_channel->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // polymorphic methods + rx_channel->base.del = rmt_del_rx_channel; + rx_channel->base.set_carrier_action = rmt_rx_demodulate_carrier; + rx_channel->base.enable = rmt_rx_enable; + rx_channel->base.disable = rmt_rx_disable; + // return general channel handle + *ret_chan = &rx_channel->base; + ESP_LOGD(TAG, "new rx channel(%d,%d) at %p, gpio=%d, res=%uHz, hw_mem_base=%p, ping_pong_size=%d", + group_id, channel_id, rx_channel, config->gpio_num, rx_channel->base.resolution_hz, + rx_channel->base.hw_mem_base, rx_channel->ping_pong_symbols); + return ESP_OK; + +err: + if (rx_channel) { + rmt_rx_destory(rx_channel); + } + return ret; +} + +static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel) +{ + rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base); + rmt_group_t *group = channel->group; + int group_id = group->group_id; + int channel_id = channel->channel_id; + ESP_LOGD(TAG, "del rx channel(%d,%d)", group_id, channel_id); + // recycle memory resource + ESP_RETURN_ON_ERROR(rmt_rx_destory(rx_chan), TAG, "destory rx channel failed"); + return ESP_OK; +} + +esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t channel, const rmt_rx_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_RX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base); + +#if CONFIG_RMT_ISR_IRAM_SAFE + if (cbs->on_recv_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_recv_done), ESP_ERR_INVALID_ARG, TAG, "on_recv_done callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + + rx_chan->on_recv_done = cbs->on_recv_done; + rx_chan->user_data = user_data; + return ESP_OK; +} + +esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_size, const rmt_receive_config_t *config) +{ + ESP_RETURN_ON_FALSE(channel && buffer && buffer_size && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_RX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); + rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base); + + if (channel->dma_chan) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(buffer), ESP_ERR_INVALID_ARG, TAG, "buffer must locate in internal RAM for DMA use"); + } + if (channel->dma_chan) { + ESP_RETURN_ON_FALSE(buffer_size <= rx_chan->num_dma_nodes * RMT_DMA_DESC_BUF_MAX_SIZE, + ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity"); + } + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + // fill in the transaction descriptor + rmt_rx_trans_desc_t *t = &rx_chan->trans_desc; + t->buffer = buffer; + t->buffer_size = buffer_size; + t->received_symbol_num = 0; + t->copy_dest_off = 0; + + if (channel->dma_chan) { +#if SOC_RMT_SUPPORT_DMA + rmt_rx_mount_dma_buffer(rx_chan->dma_nodes, rx_chan->num_dma_nodes, buffer, buffer_size); + gdma_reset(channel->dma_chan); + gdma_start(channel->dma_chan, (intptr_t)rx_chan->dma_nodes); +#endif + } + + rx_chan->mem_off = 0; + portENTER_CRITICAL(&channel->spinlock); + // reset memory writer offset + rmt_ll_rx_reset_pointer(hal->regs, channel_id); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); + // set sampling parameters of incoming signals + rmt_ll_rx_set_filter_thres(hal->regs, channel_id, ((uint64_t)group->resolution_hz * config->signal_range_min_ns) / 1000000000UL); + rmt_ll_rx_enable_filter(hal->regs, channel_id, config->signal_range_min_ns != 0); + rmt_ll_rx_set_idle_thres(hal->regs, channel_id, ((uint64_t)channel->resolution_hz * config->signal_range_max_ns) / 1000000000UL); + // turn on RMT RX machine + rmt_ll_rx_enable(hal->regs, channel_id, true); + portEXIT_CRITICAL(&channel->spinlock); + return ESP_OK; +} + +static esp_err_t rmt_rx_demodulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config) +{ +#if !SOC_RMT_SUPPORT_RX_DEMODULATION + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "rx demodulation not supported"); +#else + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int group_id = group->group_id; + int channel_id = channel->channel_id; + uint32_t real_frequency = 0; + + if (config && config->frequency_hz) { + // carrier demodulation module works base on channel clock (this is different from TX carrier modulation mode) + uint32_t total_ticks = channel->resolution_hz / config->frequency_hz; // Note this division operation will lose precision + uint32_t high_ticks = total_ticks * config->duty_cycle; + uint32_t low_ticks = total_ticks - high_ticks; + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_set_carrier_level(hal->regs, channel_id, !config->flags.polarity_active_low); + rmt_ll_rx_set_carrier_high_low_ticks(hal->regs, channel_id, high_ticks, low_ticks); + portEXIT_CRITICAL(&channel->spinlock); + // save real carrier frequency + real_frequency = channel->resolution_hz / (high_ticks + low_ticks); + } + + // enable/disable carrier demodulation + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable_carrier_demodulation(hal->regs, channel_id, real_frequency > 0); + portEXIT_CRITICAL(&channel->spinlock); + + if (real_frequency > 0) { + ESP_LOGD(TAG, "enable carrier demodulation for channel(%d,%d), freq=%uHz", group_id, channel_id, real_frequency); + } else { + ESP_LOGD(TAG, "disable carrier demodulation for channel(%d, %d)", group_id, channel_id); + } + return ESP_OK; +#endif +} + +static esp_err_t rmt_rx_enable(rmt_channel_handle_t channel) +{ + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + // acquire power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(channel->pm_lock), TAG, "acquire pm_lock failed"); + } + if (channel->dma_chan) { +#if SOC_RMT_SUPPORT_DMA + // enable the DMA access mode + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable_dma(hal->regs, channel_id, true); + portEXIT_CRITICAL(&channel->spinlock); + + gdma_connect(channel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0)); +#endif // SOC_RMT_SUPPORT_DMA + } else { + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id), true); + portEXIT_CRITICAL(&group->spinlock); + } + channel->fsm = RMT_FSM_ENABLE; + return ESP_OK; +} + +static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel) +{ + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable(hal->regs, channel_id, false); + portEXIT_CRITICAL(&channel->spinlock); + + if (channel->dma_chan) { +#if SOC_RMT_SUPPORT_DMA + gdma_stop(channel->dma_chan); + gdma_disconnect(channel->dma_chan); + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable_dma(hal->regs, channel_id, false); + portEXIT_CRITICAL(&channel->spinlock); +#endif + } else { + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id), false); + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id)); + portEXIT_CRITICAL(&group->spinlock); + } + + // release power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(channel->pm_lock), TAG, "release pm_lock failed"); + } + channel->fsm = RMT_FSM_INIT; + return ESP_OK; +} + +static size_t IRAM_ATTR rmt_copy_symbols(rmt_symbol_word_t *symbol_stream, size_t symbol_num, void *buffer, size_t offset, size_t buffer_size) +{ + size_t mem_want = symbol_num * sizeof(rmt_symbol_word_t); + size_t mem_have = buffer_size - offset; + size_t copy_size = MIN(mem_want, mem_have); + // do memory copy + memcpy(buffer + offset, symbol_stream, copy_size); + return copy_size; +} + +static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan) +{ + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc; + bool need_yield = false; + + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_DONE(channel_id)); + + portENTER_CRITICAL_ISR(&channel->spinlock); + // disable the RX engine, it will be enabled again when next time user calls `rmt_receive()` + rmt_ll_rx_enable(hal->regs, channel_id, false); + uint32_t offset = rmt_ll_rx_get_memory_writer_offset(hal->regs, channel_id); + // sanity check + assert(offset > rx_chan->mem_off); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_SW); + // copy the symbols to user space + size_t stream_symbols = offset - rx_chan->mem_off; + size_t copy_size = rmt_copy_symbols(channel->hw_mem_base + rx_chan->mem_off, stream_symbols, + trans_desc->buffer, trans_desc->copy_dest_off, trans_desc->buffer_size); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); + portEXIT_CRITICAL_ISR(&channel->spinlock); + +#if !SOC_RMT_SUPPORT_RX_PINGPONG + // for chips doesn't support ping-pong RX, we should check whether the receiver has encountered with a long frame, + // whose length is longer than the channel capacity + if (rmt_ll_rx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_RX_ERROR(channel_id)) { + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_rx_reset_pointer(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); + // this clear operation can only take effect after we copy out the received data and reset the pointer + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_ERROR(channel_id)); + ESP_DRAM_LOGE(TAG, "hw buffer too small, received symbols truncated"); + } +#endif // !SOC_RMT_SUPPORT_RX_PINGPONG + + // check whether all symbols are copied + if (copy_size != stream_symbols * sizeof(rmt_symbol_word_t)) { + ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated"); + } + trans_desc->copy_dest_off += copy_size; + trans_desc->received_symbol_num += copy_size / sizeof(rmt_symbol_word_t); + // notify the user with receive RMT symbols + if (rx_chan->on_recv_done) { + rmt_rx_done_event_data_t edata = { + .received_symbols = trans_desc->buffer, + .num_symbols = trans_desc->received_symbol_num, + }; + if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) { + need_yield = true; + } + } + return need_yield; +} + +#if SOC_RMT_SUPPORT_RX_PINGPONG +static bool IRAM_ATTR rmt_isr_handle_rx_threshold(rmt_rx_channel_t *rx_chan) +{ + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc; + + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_THRES(channel_id)); + + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_SW); + // copy the symbols to user space + size_t copy_size = rmt_copy_symbols(channel->hw_mem_base + rx_chan->mem_off, rx_chan->ping_pong_symbols, + trans_desc->buffer, trans_desc->copy_dest_off, trans_desc->buffer_size); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); + portEXIT_CRITICAL_ISR(&channel->spinlock); + + // check whether all symbols are copied + if (copy_size != rx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) { + ESP_DRAM_LOGE(TAG, "received symbols truncated"); + } + trans_desc->copy_dest_off += copy_size; + trans_desc->received_symbol_num += copy_size / sizeof(rmt_symbol_word_t); + // update the hw memory offset, where stores the next RMT symbols to copy + rx_chan->mem_off = rx_chan->ping_pong_symbols - rx_chan->mem_off; + + return false; +} +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + +static void IRAM_ATTR rmt_rx_default_isr(void *args) +{ + rmt_rx_channel_t *rx_chan = (rmt_rx_channel_t *)args; + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + bool need_yield = false; + + uint32_t status = rmt_ll_rx_get_interrupt_status(hal->regs, channel_id); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + // RX threshold interrupt + if (status & RMT_LL_EVENT_RX_THRES(channel_id)) { + if (rmt_isr_handle_rx_threshold(rx_chan)) { + need_yield = true; + } + } +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + + // RX end interrupt + if (status & RMT_LL_EVENT_RX_DONE(channel_id)) { + if (rmt_isr_handle_rx_done(rx_chan)) { + need_yield = true; + } + } + + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +#if SOC_RMT_SUPPORT_DMA +static size_t IRAM_ATTR rmt_rx_get_received_symbol_num_from_dma(dma_descriptor_t *desc) +{ + size_t received_bytes = 0; + while (desc) { + received_bytes += desc->dw0.length; + desc = desc->next; + } + received_bytes = ALIGN_UP(received_bytes, sizeof(rmt_symbol_word_t)); + return received_bytes / sizeof(rmt_symbol_word_t); +} + +static bool IRAM_ATTR rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + bool need_yield = false; + rmt_rx_channel_t *rx_chan = (rmt_rx_channel_t *)user_data; + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc; + uint32_t channel_id = channel->channel_id; + + portENTER_CRITICAL_ISR(&channel->spinlock); + // disable the RX engine, it will be enabled again in the next `rmt_receive()` + rmt_ll_rx_enable(hal->regs, channel_id, false); + portEXIT_CRITICAL_ISR(&channel->spinlock); + + if (rx_chan->on_recv_done) { + rmt_rx_done_event_data_t edata = { + .received_symbols = trans_desc->buffer, + .num_symbols = rmt_rx_get_received_symbol_num_from_dma(rx_chan->dma_nodes), + }; + if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) { + need_yield = true; + } + } + return need_yield; +} +#endif // SOC_RMT_SUPPORT_DMA diff --git a/components/driver/rmt/rmt_tx.c b/components/driver/rmt/rmt_tx.c new file mode 100644 index 0000000000..679148af67 --- /dev/null +++ b/components/driver/rmt/rmt_tx.c @@ -0,0 +1,1043 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "esp_rom_gpio.h" +#include "soc/rmt_periph.h" +#include "soc/rtc.h" +#include "hal/rmt_ll.h" +#include "hal/gpio_hal.h" +#include "driver/gpio.h" +#include "driver/rmt_tx.h" +#include "rmt_private.h" + +static const char *TAG = "rmt"; + +struct rmt_sync_manager_t { + rmt_group_t *group; // which group the synchro belongs to + uint32_t channel_mask; // Mask of channels that are managed + size_t array_size; // Size of the `tx_channel_array` + rmt_channel_handle_t tx_channel_array[]; // Array of TX channels that are managed +}; + +static esp_err_t rmt_del_tx_channel(rmt_channel_handle_t channel); +static esp_err_t rmt_tx_modulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config); +static esp_err_t rmt_tx_enable(rmt_channel_handle_t channel); +static esp_err_t rmt_tx_disable(rmt_channel_handle_t channel); +static void rmt_tx_default_isr(void *args); + +#if SOC_RMT_SUPPORT_DMA +static bool rmt_dma_tx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data); + +static esp_err_t rmt_tx_init_dma_link(rmt_tx_channel_t *tx_channel, const rmt_tx_channel_config_t *config) +{ + rmt_symbol_word_t *dma_mem_base = heap_caps_calloc(1, sizeof(rmt_symbol_word_t) * config->mem_block_symbols, RMT_MEM_ALLOC_CAPS | MALLOC_CAP_DMA); + ESP_RETURN_ON_FALSE(dma_mem_base, ESP_ERR_NO_MEM, TAG, "no mem for tx DMA buffer"); + tx_channel->base.dma_mem_base = dma_mem_base; + for (int i = 0; i < RMT_DMA_NODES_PING_PONG; i++) { + // each descriptor shares half of the DMA buffer + tx_channel->dma_nodes[i].buffer = dma_mem_base + tx_channel->ping_pong_symbols * i; + tx_channel->dma_nodes[i].dw0.size = tx_channel->ping_pong_symbols * sizeof(rmt_symbol_word_t); + // the ownership will be switched to DMA in `rmt_tx_do_transaction()` + tx_channel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; + // each node can generate the DMA eof interrupt, and the driver will do a ping-pong trick in the eof callback + tx_channel->dma_nodes[i].dw0.suc_eof = 1; + } + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_TX, + }; + ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &tx_channel->base.dma_chan), TAG, "allocate TX DMA channel failed"); + gdma_strategy_config_t gdma_strategy_conf = { + .auto_update_desc = true, + .owner_check = true, + }; + gdma_apply_strategy(tx_channel->base.dma_chan, &gdma_strategy_conf); + gdma_tx_event_callbacks_t cbs = { + .on_trans_eof = rmt_dma_tx_eof_cb, + }; + gdma_register_tx_event_callbacks(tx_channel->base.dma_chan, &cbs, tx_channel); + return ESP_OK; +} +#endif // SOC_RMT_SUPPORT_DMA + +static esp_err_t rmt_tx_register_to_group(rmt_tx_channel_t *tx_channel, const rmt_tx_channel_config_t *config) +{ + size_t mem_block_num = 0; + // start to search for a free channel + // a channel can take up its neighbour's memory block, so the neighbour channel won't work, we should skip these "invaded" ones + int channel_scan_start = RMT_TX_CHANNEL_OFFSET_IN_GROUP; + int channel_scan_end = RMT_TX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_TX_CANDIDATES_PER_GROUP; + if (config->flags.with_dma) { + // for DMA mode, the memory block number is always 1; for non-DMA mode, memory block number is configured by user + mem_block_num = 1; + // Only the last channel has the DMA capability + channel_scan_start = RMT_TX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_TX_CANDIDATES_PER_GROUP - 1; + tx_channel->ping_pong_symbols = config->mem_block_symbols / 2; + } else { + // one channel can occupy multiple memory blocks + mem_block_num = config->mem_block_symbols / SOC_RMT_MEM_WORDS_PER_CHANNEL; + if (mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL < config->mem_block_symbols) { + mem_block_num++; + } + tx_channel->ping_pong_symbols = mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL / 2; + } + tx_channel->base.mem_block_num = mem_block_num; + + // search free channel and then register to the group + // memory blocks used by one channel must be continuous + uint32_t channel_mask = (1 << mem_block_num) - 1; + rmt_group_t *group = NULL; + int channel_id = -1; + for (int i = 0; i < SOC_RMT_GROUPS; i++) { + group = rmt_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i); + portENTER_CRITICAL(&group->spinlock); + for (int j = channel_scan_start; j < channel_scan_end; j++) { + if (!(group->occupy_mask & (channel_mask << j))) { + group->occupy_mask |= (channel_mask << j); + // the channel ID should index from 0 + channel_id = j - RMT_TX_CHANNEL_OFFSET_IN_GROUP; + group->tx_channels[channel_id] = tx_channel; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (channel_id < 0) { + // didn't find a capable channel in the group, don't forget to release the group handle + rmt_release_group_handle(group); + group = NULL; + } else { + tx_channel->base.channel_id = channel_id; + tx_channel->base.channel_mask = channel_mask; + tx_channel->base.group = group; + break; + } + } + ESP_RETURN_ON_FALSE(channel_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free tx channels"); + return ESP_OK; +} + +static void rmt_tx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *group) +{ + portENTER_CRITICAL(&group->spinlock); + group->tx_channels[channel->channel_id] = NULL; + group->occupy_mask &= ~(channel->channel_mask << (channel->channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP)); + portEXIT_CRITICAL(&group->spinlock); + // channel has a reference on group, release it now + rmt_release_group_handle(group); +} + +static esp_err_t rmt_tx_create_trans_queue(rmt_tx_channel_t *tx_channel, const rmt_tx_channel_config_t *config) +{ + tx_channel->queue_size = config->trans_queue_depth; + // the queue only saves transaction description pointers + tx_channel->queues_storage = heap_caps_calloc(config->trans_queue_depth * RMT_TX_QUEUE_MAX, sizeof(rmt_tx_trans_desc_t *), RMT_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(tx_channel->queues_storage, ESP_ERR_NO_MEM, TAG, "no mem for queue storage"); + rmt_tx_trans_desc_t **pp_trans_desc = (rmt_tx_trans_desc_t **)tx_channel->queues_storage; + for (int i = 0; i < RMT_TX_QUEUE_MAX; i++) { + tx_channel->trans_queues[i] = xQueueCreateStatic(config->trans_queue_depth, sizeof(rmt_tx_trans_desc_t *), + (uint8_t *)pp_trans_desc, &tx_channel->trans_queue_structs[i]); + pp_trans_desc += config->trans_queue_depth; + // sanity check + assert(tx_channel->trans_queues[i]); + } + // initialize the ready queue + rmt_tx_trans_desc_t *p_trans_desc = NULL; + for (int i = 0; i < config->trans_queue_depth; i++) { + p_trans_desc = &tx_channel->trans_desc_pool[i]; + ESP_RETURN_ON_FALSE(xQueueSend(tx_channel->trans_queues[RMT_TX_QUEUE_READY], &p_trans_desc, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + return ESP_OK; +} + +static esp_err_t rmt_tx_destory(rmt_tx_channel_t *tx_channel) +{ + if (tx_channel->base.intr) { + ESP_RETURN_ON_ERROR(esp_intr_free(tx_channel->base.intr), TAG, "delete interrupt service failed"); + } + if (tx_channel->base.pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(tx_channel->base.pm_lock), TAG, "delete pm_lock failed"); + } +#if SOC_RMT_SUPPORT_DMA + if (tx_channel->base.dma_chan) { + ESP_RETURN_ON_ERROR(gdma_del_channel(tx_channel->base.dma_chan), TAG, "delete dma channel failed"); + } +#endif // SOC_RMT_SUPPORT_DMA + for (int i = 0; i < RMT_TX_QUEUE_MAX; i++) { + if (tx_channel->trans_queues[i]) { + vQueueDelete(tx_channel->trans_queues[i]); + } + } + if (tx_channel->queues_storage) { + free(tx_channel->queues_storage); + } + if (tx_channel->base.dma_mem_base) { + free(tx_channel->base.dma_mem_base); + } + if (tx_channel->base.group) { + // de-register channel from RMT group + rmt_tx_unregister_from_group(&tx_channel->base, tx_channel->base.group); + } + free(tx_channel); + return ESP_OK; +} + +esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan) +{ +#if CONFIG_RMT_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + rmt_tx_channel_t *tx_channel = NULL; + ESP_GOTO_ON_FALSE(config && ret_chan && config->resolution_hz && config->trans_queue_depth, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(config->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO number"); + ESP_GOTO_ON_FALSE((config->mem_block_symbols & 0x01) == 0 && config->mem_block_symbols >= SOC_RMT_MEM_WORDS_PER_CHANNEL, + ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols must be even and at least %d", SOC_RMT_MEM_WORDS_PER_CHANNEL); +#if SOC_RMT_SUPPORT_DMA + // we only support 2 nodes ping-pong, if the configured memory block size needs more than two DMA descriptors, should treat it as invalid + ESP_GOTO_ON_FALSE(config->mem_block_symbols <= RMT_DMA_DESC_BUF_MAX_SIZE * RMT_DMA_NODES_PING_PONG, ESP_ERR_INVALID_ARG, err, TAG, + "mem_block_symbols can't exceed %d", RMT_DMA_DESC_BUF_MAX_SIZE * RMT_DMA_NODES_PING_PONG); +#else + ESP_GOTO_ON_FALSE(config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "DMA not supported"); +#endif + + // malloc channel memory + uint32_t mem_caps = RMT_MEM_ALLOC_CAPS; + if (config->flags.with_dma) { + // DMA descriptors must be placed in internal SRAM + mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; + } + tx_channel = heap_caps_calloc(1, sizeof(rmt_tx_channel_t) + sizeof(rmt_tx_trans_desc_t) * config->trans_queue_depth, mem_caps); + ESP_GOTO_ON_FALSE(tx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for tx channel"); + // create transaction queues + ESP_GOTO_ON_ERROR(rmt_tx_create_trans_queue(tx_channel, config), err, TAG, "install trans queues failed"); + // register the channel to group + ESP_GOTO_ON_ERROR(rmt_tx_register_to_group(tx_channel, config), err, TAG, "register channel failed"); + rmt_group_t *group = tx_channel->base.group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = tx_channel->base.channel_id; + int group_id = group->group_id; + // select the clock source + ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&tx_channel->base, config->clk_src), err, TAG, "set group clock failed"); + + // reset channel, make sure the TX engine is not working, and events are cleared + portENTER_CRITICAL(&group->spinlock); + rmt_hal_tx_channel_reset(&group->hal, channel_id); + portEXIT_CRITICAL(&group->spinlock); + + // install interrupt service + // interrupt is mandatory to run basic RMT transactions, so it's not lazy installed in `rmt_tx_register_event_callbacks()` + int isr_flags = RMT_INTR_ALLOC_FLAG; + ret = esp_intr_alloc_intrstatus(rmt_periph_signals.groups[group_id].irq, isr_flags, + (uint32_t)rmt_ll_get_interrupt_status_reg(hal->regs), + RMT_LL_EVENT_TX_MASK(channel_id), rmt_tx_default_isr, tx_channel, &tx_channel->base.intr); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install tx interrupt failed"); + // install DMA service +#if SOC_RMT_SUPPORT_DMA + if (config->flags.with_dma) { + ESP_GOTO_ON_ERROR(rmt_tx_init_dma_link(tx_channel, config), err, TAG, "install tx DMA failed"); + } +#endif + // set channel clock resolution + uint32_t real_div = group->resolution_hz / config->resolution_hz; + rmt_ll_tx_set_channel_clock_div(hal->regs, channel_id, real_div); + // resolution lost due to division, calculate the real resolution + tx_channel->base.resolution_hz = group->resolution_hz / real_div; + if (tx_channel->base.resolution_hz != config->resolution_hz) { + ESP_LOGW(TAG, "channel resolution loss, real=%u", tx_channel->base.resolution_hz); + } + + rmt_ll_tx_set_mem_blocks(hal->regs, channel_id, tx_channel->base.mem_block_num); + // set limit threshold, after transmit ping_pong_symbols size, an interrupt event would be generated + rmt_ll_tx_set_limit(hal->regs, channel_id, tx_channel->ping_pong_symbols); + // disable carrier modulation by default, can reenable by `rmt_apply_carrier()` + rmt_ll_tx_enable_carrier_modulation(hal->regs, channel_id, false); + // idle level is determind by eof encoder, not set to a fixed value + rmt_ll_tx_fix_idle_level(hal->regs, channel_id, 0, false); + // always enable tx wrap, both DMA mode and ping-pong mode rely this feature + rmt_ll_tx_enable_wrap(hal->regs, channel_id, true); + + // GPIO Matrix/MUX configuration + tx_channel->base.gpio_num = config->gpio_num; + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + // also enable the input path is `io_loop_back` is on, this is useful for debug + .mode = GPIO_MODE_OUTPUT | (config->flags.io_loop_back ? GPIO_MODE_INPUT : 0), + .pull_down_en = false, + .pull_up_en = true, + .pin_bit_mask = 1ULL << config->gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed"); + esp_rom_gpio_connect_out_signal(config->gpio_num, + rmt_periph_signals.groups[group_id].channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].tx_sig, + config->flags.invert_out, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_num], PIN_FUNC_GPIO); + + tx_channel->base.direction = RMT_CHANNEL_DIRECTION_TX; + tx_channel->base.fsm = RMT_FSM_INIT; + tx_channel->base.hw_mem_base = &RMTMEM + (channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP) * SOC_RMT_MEM_WORDS_PER_CHANNEL; + tx_channel->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // polymorphic methods + tx_channel->base.del = rmt_del_tx_channel; + tx_channel->base.set_carrier_action = rmt_tx_modulate_carrier; + tx_channel->base.enable = rmt_tx_enable; + tx_channel->base.disable = rmt_tx_disable; + // return general channel handle + *ret_chan = &tx_channel->base; + ESP_LOGD(TAG, "new tx channel(%d,%d) at %p, gpio=%d, res=%uHz, hw_mem_base=%p, dma_mem_base=%p, ping_pong_size=%zu, queue_depth=%zu", + group_id, channel_id, tx_channel, config->gpio_num, tx_channel->base.resolution_hz, + tx_channel->base.hw_mem_base, tx_channel->base.dma_mem_base, tx_channel->ping_pong_symbols, tx_channel->queue_size); + return ESP_OK; + +err: + if (tx_channel) { + rmt_tx_destory(tx_channel); + } + return ret; +} + +static esp_err_t rmt_del_tx_channel(rmt_channel_handle_t channel) +{ + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + int group_id = group->group_id; + int channel_id = channel->channel_id; + ESP_LOGD(TAG, "del tx channel(%d,%d)", group_id, channel_id); + // recycle memory resource + ESP_RETURN_ON_ERROR(rmt_tx_destory(tx_chan), TAG, "destory tx channel failed"); + return ESP_OK; +} + +esp_err_t rmt_new_sync_manager(const rmt_sync_manager_config_t *config, rmt_sync_manager_handle_t *ret_synchro) +{ + esp_err_t ret = ESP_OK; + rmt_sync_manager_t *synchro = NULL; +#if !SOC_RMT_SUPPORT_TX_SYNCHRO + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "sync manager not supported"); +#else + ESP_GOTO_ON_FALSE(config && ret_synchro && config->tx_channel_array && config->array_size, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + synchro = heap_caps_calloc(1, sizeof(rmt_sync_manager_t) + sizeof(rmt_channel_handle_t) * config->array_size, RMT_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(synchro, ESP_ERR_NO_MEM, err, TAG, "no mem for sync manager"); + for (size_t i = 0; i < config->array_size; i++) { + synchro->tx_channel_array[i] = config->tx_channel_array[i]; + } + synchro->array_size = config->array_size; + + int group_id = config->tx_channel_array[0]->group->group_id; + // acquire group handle, increase reference count + rmt_group_t *group = rmt_acquire_group_handle(group_id); + // sanity check + assert(group); + synchro->group = group; + // calculate the mask of the channels to be managed + uint32_t channel_mask = 0; + rmt_channel_handle_t channel = NULL; + for (size_t i = 0; i < config->array_size; i++) { + channel = config->tx_channel_array[i]; + ESP_GOTO_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, err, TAG, "sync manager supports TX channel only"); + ESP_GOTO_ON_FALSE(channel->group == group, ESP_ERR_INVALID_ARG, err, TAG, "channels to be managed should locate in the same group"); + ESP_GOTO_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, err, TAG, "channel should be started before creating sync manager"); + channel_mask |= 1 << channel->channel_id; + } + synchro->channel_mask = channel_mask; + + // search and register sync manager to group + bool new_synchro = false; + portENTER_CRITICAL(&group->spinlock); + if (group->sync_manager == NULL) { + group->sync_manager = synchro; + new_synchro = true; + } + portEXIT_CRITICAL(&group->spinlock); + ESP_GOTO_ON_FALSE(new_synchro, ESP_ERR_NOT_FOUND, err, TAG, "no free sync manager in the group"); + + // enable sync manager + portENTER_CRITICAL(&group->spinlock); + rmt_ll_tx_enable_sync(group->hal.regs, true); + rmt_ll_tx_sync_group_add_channels(group->hal.regs, channel_mask); + rmt_ll_tx_reset_channels_clock_div(group->hal.regs, channel_mask); + // ensure the reading cursor of each channel is pulled back to the starting line + for (size_t i = 0; i < config->array_size; i++) { + rmt_ll_tx_reset_pointer(group->hal.regs, config->tx_channel_array[i]->channel_id); + } + portEXIT_CRITICAL(&group->spinlock); + + + *ret_synchro = synchro; + ESP_LOGD(TAG, "new sync manager at %p, with channel mask:%02x", synchro, synchro->channel_mask); + return ESP_OK; +#endif // !SOC_RMT_SUPPORT_TX_SYNCHRO + +err: + if (synchro) { + if (synchro->group) { + rmt_release_group_handle(synchro->group); + } + free(synchro); + } + return ret; +} + +esp_err_t rmt_sync_reset(rmt_sync_manager_handle_t synchro) +{ +#if !SOC_RMT_SUPPORT_TX_SYNCHRO + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "sync manager not supported"); +#else + ESP_RETURN_ON_FALSE(synchro, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_group_t *group = synchro->group; + + portENTER_CRITICAL(&group->spinlock); + rmt_ll_tx_reset_channels_clock_div(group->hal.regs, synchro->channel_mask); + for (size_t i = 0; i < synchro->array_size; i++) { + rmt_ll_tx_reset_pointer(group->hal.regs, synchro->tx_channel_array[i]->channel_id); + } + portEXIT_CRITICAL(&group->spinlock); + + return ESP_OK; +#endif // !SOC_RMT_SUPPORT_TX_SYNCHRO +} + +esp_err_t rmt_del_sync_manager(rmt_sync_manager_handle_t synchro) +{ +#if !SOC_RMT_SUPPORT_TX_SYNCHRO + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "sync manager not supported"); +#else + ESP_RETURN_ON_FALSE(synchro, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_group_t *group = synchro->group; + int group_id = group->group_id; + + portENTER_CRITICAL(&group->spinlock); + group->sync_manager = NULL; + // disable sync manager + rmt_ll_tx_enable_sync(group->hal.regs, false); + rmt_ll_tx_sync_group_remove_channels(group->hal.regs, synchro->channel_mask); + portEXIT_CRITICAL(&group->spinlock); + + free(synchro); + ESP_LOGD(TAG, "del sync manager in group(%d)", group_id); + rmt_release_group_handle(group); + return ESP_OK; +#endif // !SOC_RMT_SUPPORT_TX_SYNCHRO +} + +esp_err_t rmt_tx_register_event_callbacks(rmt_channel_handle_t channel, const rmt_tx_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + +#if CONFIG_RMT_ISR_IRAM_SAFE + if (cbs->on_trans_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_trans_done callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + + tx_chan->on_trans_done = cbs->on_trans_done; + tx_chan->user_data = user_data; + return ESP_OK; +} + +esp_err_t rmt_transmit(rmt_channel_handle_t channel, rmt_encoder_t *encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config) +{ + ESP_RETURN_ON_FALSE(channel && encoder && payload && payload_bytes && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); +#if !SOC_RMT_SUPPORT_TX_LOOP_COUNT + ESP_RETURN_ON_FALSE(config->loop_count <= 0, ESP_ERR_NOT_SUPPORTED, TAG, "loop count is not supported"); +#endif // !SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_tx_trans_desc_t *t = NULL; + // acquire one transaction description from ready_queue or done_queue + if (tx_chan->num_trans_inflight < tx_chan->queue_size) { + xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_READY], &t, portMAX_DELAY); + } else { + xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &t, portMAX_DELAY); + tx_chan->num_trans_inflight--; + } + // fill in the transaction descriptor + memset(t, 0, sizeof(rmt_tx_trans_desc_t)); + t->encoder = encoder; + t->payload = payload; + t->payload_bytes = payload_bytes; + t->loop_count = config->loop_count; + t->remain_loop_count = t->loop_count; + t->flags.eot_level = config->flags.eot_level; + + // send the transaction descriptor to queue + if (xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &t, portMAX_DELAY) == pdTRUE) { + tx_chan->num_trans_inflight++; + } else { + // put the trans descriptor back to ready_queue + ESP_RETURN_ON_FALSE(xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_READY], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + + // we don't know which "transmission complete" event will be triggered, but must be one of them: trans_done, loop_done + // when we run at here, the interrupt status bit for tx_done or loop_end should already up (ensured by `rmt_tx_enable()`) + // that's why we can go into ISR as soon as we enable the interrupt bit + // in the ISR, we will fetch the transactions from trans_queue and start it + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id) | RMT_LL_EVENT_TX_LOOP_END(channel_id), true); + portEXIT_CRITICAL(&group->spinlock); + return ESP_OK; +} + +esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t channel, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + TickType_t wait_ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + // recycle all transaction that are on the fly + rmt_tx_trans_desc_t *t = NULL; + size_t num_trans_inflight = tx_chan->num_trans_inflight; + for (size_t i = 0; i < num_trans_inflight; i++) { + ESP_RETURN_ON_FALSE(xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &t, wait_ticks) == pdTRUE, + ESP_ERR_TIMEOUT, TAG, "flush timeout"); + ESP_RETURN_ON_FALSE(xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_READY], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + tx_chan->num_trans_inflight--; + } + return ESP_OK; +} + +static void IRAM_ATTR rmt_tx_mark_eof(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + int channel_id = channel->channel_id; + rmt_symbol_word_t *mem_to = channel->dma_chan ? channel->dma_mem_base : channel->hw_mem_base; + rmt_tx_trans_desc_t *cur_trans = tx_chan->cur_trans; + dma_descriptor_t *desc = NULL; + + // a RMT word whose duration is zero means a "stop" pattern + mem_to[tx_chan->mem_off++] = (rmt_symbol_word_t) { + .duration0 = 0, + .level0 = cur_trans->flags.eot_level, + .duration1 = 0, + .level1 = cur_trans->flags.eot_level, + }; + + size_t off = 0; + if (channel->dma_chan) { + if (tx_chan->mem_off <= tx_chan->ping_pong_symbols) { + desc = &tx_chan->dma_nodes[0]; + off = tx_chan->mem_off; + } else { + desc = &tx_chan->dma_nodes[1]; + off = tx_chan->mem_off - tx_chan->ping_pong_symbols; + } + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->dw0.length = off * sizeof(rmt_symbol_word_t); + // break down the DMA descriptor link + desc->next = NULL; + } else { + portENTER_CRITICAL_ISR(&group->spinlock); + // This is the end of a sequence of encoding sessions, disable the threshold interrupt as no more data will be put into RMT memory block + rmt_ll_enable_interrupt(group->hal.regs, RMT_LL_EVENT_TX_THRES(channel_id), false); + portEXIT_CRITICAL_ISR(&group->spinlock); + } +} + +static size_t IRAM_ATTR rmt_encode_check_result(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t) +{ + rmt_encode_state_t encode_state = 0; + rmt_encoder_handle_t encoder = t->encoder; + size_t encoded_symbols = encoder->encode(encoder, &tx_chan->base, t->payload, t->payload_bytes, &encode_state); + if (encode_state & RMT_ENCODING_COMPLETE) { + t->flags.encoding_done = true; + // inserting EOF symbol if there's extra space + if (!(encode_state & RMT_ENCODING_MEM_FULL)) { + rmt_tx_mark_eof(tx_chan); + encoded_symbols += 1; + } + } + + // for loop transaction, the memory block should accommodate all encoded RMT symbols + if (t->loop_count != 0) { + if (unlikely(encoded_symbols > tx_chan->base.mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL)) { + ESP_DRAM_LOGE(TAG, "encoding artifacts can't exceed hw memory block for loop transmission"); + } + } + + return encoded_symbols; +} + +static void IRAM_ATTR rmt_tx_do_transaction(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_reset(channel->dma_chan); + // chain the descritpros into a ring, and will break it in `rmt_encode_eof()` + for (int i = 0; i < RMT_DMA_NODES_PING_PONG; i++) { + tx_chan->dma_nodes[i].next = &tx_chan->dma_nodes[i + 1]; + tx_chan->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; + } + tx_chan->dma_nodes[1].next = &tx_chan->dma_nodes[0]; + } +#endif // SOC_RMT_SUPPORT_DMA + + // set transaction specific parameters + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_tx_reset_pointer(hal->regs, channel_id); // reset pointer for new transaction + rmt_ll_tx_enable_loop(hal->regs, channel_id, t->loop_count != 0); +#if SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + rmt_ll_tx_enable_loop_autostop(hal->regs, channel_id, true); +#endif // SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_tx_reset_loop_count(hal->regs, channel_id); + rmt_ll_tx_enable_loop_count(hal->regs, channel_id, t->loop_count > 0); + // transfer loops in batches + if (t->remain_loop_count > 0) { + uint32_t this_loop_count = MIN(t->remain_loop_count, RMT_LL_MAX_LOOP_COUNT_PER_BATCH); + rmt_ll_tx_set_loop_count(hal->regs, channel_id, this_loop_count); + t->remain_loop_count -= this_loop_count; + } +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + portEXIT_CRITICAL_ISR(&channel->spinlock); + + // enable/disable specific interrupts + portENTER_CRITICAL_ISR(&group->spinlock); +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), t->loop_count > 0); +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + // in DMA mode, DMA eof event plays the similar functionality to this threshold interrupt, so only enable it for non-DMA mode + if (!channel->dma_chan) { + // don't enable threshold interrupt with loop mode on + // threshold interrupt will be disabled in `rmt_encode_eof()` + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_THRES(channel_id), t->loop_count == 0); + // Threshold interrupt will be generated by accident, clear it before starting new transmission + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_THRES(channel_id)); + } + // don't generate trans done event for loop transmission + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), t->loop_count == 0); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // at the beginning of a new transaction, encoding memory offset should start from zero. + // It will increase in the encode function e.g. `rmt_encode_copy()` + tx_chan->mem_off = 0; + // use the full memory block for the beginning encoding session + tx_chan->mem_end = tx_chan->ping_pong_symbols * 2; + // perform the encoding session, return the number of encoded symbols + t->transmitted_symbol_num = rmt_encode_check_result(tx_chan, t); + // we're going to perform ping-pong operation, so the next encoding end position is the middle + tx_chan->mem_end = tx_chan->ping_pong_symbols; + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_start(channel->dma_chan, (intptr_t)tx_chan->dma_nodes); + // delay a while, wait for DMA data going to RMT memory block + esp_rom_delay_us(1); + } +#endif + // turn on the TX machine + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_tx_start(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); +} + +static esp_err_t rmt_tx_enable(rmt_channel_handle_t channel) +{ + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + // acquire power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(channel->pm_lock), TAG, "acquire pm_lock failed"); + } + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_reset_pointer(hal->regs, channel_id); + rmt_ll_tx_enable_loop(hal->regs, channel_id, false); +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_tx_reset_loop_count(hal->regs, channel_id); + rmt_ll_tx_enable_loop_count(hal->regs, channel_id, false); +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + // trigger a quick trans done event by sending a EOF symbol, no signal should appear on the GPIO + tx_chan->cur_trans = NULL; + channel->hw_mem_base[0].val = 0; + rmt_ll_tx_start(hal->regs, channel_id); + portEXIT_CRITICAL(&channel->spinlock); + + // wait the RMT interrupt line goes active, we won't go into the ISR handler until we enable the `RMT_LL_EVENT_TX_DONE` interrupt + while (!(rmt_ll_tx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_TX_DONE(channel_id))) {} +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + // enable the DMA access mode + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_enable_dma(hal->regs, channel_id, true); + portEXIT_CRITICAL(&channel->spinlock); + + gdma_connect(channel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0)); + } +#endif // SOC_RMT_SUPPORT_DMA + + channel->fsm = RMT_FSM_ENABLE; + + // enable channel interrupt, dispatch transactions in ISR (in case there're transaction descriptors in the queue, then we should start them) + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), true); + portEXIT_CRITICAL(&group->spinlock); + return ESP_OK; +} + +static esp_err_t rmt_tx_disable(rmt_channel_handle_t channel) +{ + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + portENTER_CRITICAL(&channel->spinlock); + // when this function called, the transaction might be middle-way, the output level when we stop the transmitter is nondeterministic, + // so we fix the idle level temporarily + rmt_ll_tx_fix_idle_level(hal->regs, channel->channel_id, tx_chan->cur_trans ? tx_chan->cur_trans->flags.eot_level : 0, true); + rmt_ll_tx_enable_loop(hal->regs, channel->channel_id, false); +#if SOC_RMT_SUPPORT_TX_ASYNC_STOP + rmt_ll_tx_stop(hal->regs, channel->channel_id); +#endif + portEXIT_CRITICAL(&channel->spinlock); + + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_MASK(channel_id), false); +#if !SOC_RMT_SUPPORT_TX_ASYNC_STOP + // we do a trick to stop the undergoing transmission + // stop interrupt, insert EOF marker to the RMT memory, polling the trans_done event + channel->hw_mem_base[0].val = 0; + while (!(rmt_ll_tx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_TX_DONE(channel_id))) {} +#endif + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_MASK(channel_id)); + portEXIT_CRITICAL(&group->spinlock); + + portENTER_CRITICAL(&channel->spinlock); + // restore the idle level selection, to be determind by eof symbol + rmt_ll_tx_fix_idle_level(hal->regs, channel_id, 0, false); + portEXIT_CRITICAL(&channel->spinlock); + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_stop(channel->dma_chan); + gdma_disconnect(channel->dma_chan); + + // disable DMA access mode + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_enable_dma(hal->regs, channel_id, false); + portEXIT_CRITICAL(&channel->spinlock); + } +#endif + // recycle the interrupted transaction + if (tx_chan->cur_trans) { + xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &tx_chan->cur_trans, portMAX_DELAY); + // reset corresponding encoder + rmt_encoder_reset(tx_chan->cur_trans->encoder); + } + tx_chan->cur_trans = NULL; + + // release power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(channel->pm_lock), TAG, "release pm_lock failed"); + } + + channel->fsm = RMT_FSM_INIT; + return ESP_OK; +} + +static esp_err_t rmt_tx_modulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config) +{ + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int group_id = group->group_id; + int channel_id = channel->channel_id; + uint32_t real_frequency = 0; + + if (config && config->frequency_hz) { + // carrier module works base on group clock + uint32_t total_ticks = group->resolution_hz / config->frequency_hz; // Note this division operation will lose precision + uint32_t high_ticks = total_ticks * config->duty_cycle; + uint32_t low_ticks = total_ticks - high_ticks; + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_set_carrier_level(hal->regs, channel_id, !config->flags.polarity_active_low); + rmt_ll_tx_set_carrier_high_low_ticks(hal->regs, channel_id, high_ticks, low_ticks); +#if SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY + rmt_ll_tx_enable_carrier_always_on(hal->regs, channel_id, config->flags.always_on); +#endif + portEXIT_CRITICAL(&channel->spinlock); + // save real carrier frequency + real_frequency = group->resolution_hz / total_ticks; + } + + // enable/disable carrier modulation + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_enable_carrier_modulation(hal->regs, channel_id, real_frequency > 0); + portEXIT_CRITICAL(&channel->spinlock); + + if (real_frequency > 0) { + ESP_LOGD(TAG, "enable carrier modulation for channel(%d,%d), freq=%uHz", group_id, channel_id, real_frequency); + } else { + ESP_LOGD(TAG, "disable carrier modulation for channel(%d,%d)", group_id, channel_id); + } + return ESP_OK; +} + +static bool IRAM_ATTR rmt_isr_handle_tx_threshold(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + + // continue pingpong transmission + rmt_tx_trans_desc_t *t = tx_chan->cur_trans; + size_t encoded_symbols = t->transmitted_symbol_num; + // encoding finished, only need to send the EOF symbol + if (t->flags.encoding_done) { + rmt_tx_mark_eof(tx_chan); + encoded_symbols += 1; + } else { + encoded_symbols += rmt_encode_check_result(tx_chan, t); + } + t->transmitted_symbol_num = encoded_symbols; + tx_chan->mem_end = tx_chan->ping_pong_symbols * 3 - tx_chan->mem_end; // mem_end equals to either ping_pong_symbols or ping_pong_symbols*2 + + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_THRES(channel_id)); + + return false; +} + +static bool IRAM_ATTR rmt_isr_handle_tx_done(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + BaseType_t awoken = pdFALSE; + rmt_tx_trans_desc_t *trans_desc = NULL; + bool need_yield = false; + + portENTER_CRITICAL_ISR(&group->spinlock); + // disable interrupt temporarily, re-enable it when there is transaction unhandled in the queue + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), false); + portEXIT_CRITICAL_ISR(&group->spinlock); + + trans_desc = tx_chan->cur_trans; + // process finished transaction + if (trans_desc) { + // don't care of the tx done event for any undergoing loop transaction + // mostly it's triggered when a loop transmission is undergoing and user calls `rmt_transmit()` where tx done interrupt is generated by accident + if (trans_desc->loop_count != 0) { + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id)); + return need_yield; + } + if (tx_chan->on_trans_done) { + rmt_tx_done_event_data_t edata = { + .num_symbols = trans_desc->transmitted_symbol_num, + }; + if (tx_chan->on_trans_done(channel, &edata, tx_chan->user_data)) { + need_yield = true; + } + } + // move transaction to done_queue + xQueueSendFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &trans_desc, &awoken); + if (awoken == pdTRUE) { + need_yield = true; + } + } + // fetch new transaction description from trans_queue + if (xQueueReceiveFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &trans_desc, &awoken) == pdTRUE) { + // update current transaction + tx_chan->cur_trans = trans_desc; + + portENTER_CRITICAL_ISR(&group->spinlock); + // only clear the trans done status when we're sure there still remains transaction to handle + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id)); + // enable interrupt again, because the new transaction can trigger another trans done event + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), trans_desc->loop_count == 0); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), trans_desc->loop_count > 0); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // begin a new transaction + rmt_tx_do_transaction(tx_chan, trans_desc); + } else { // No transactions left in the queue + // don't clear interrupt status, so when next time user push new transaction to the queue and call esp_intr_enable, + // we can go to this ISR handler again + tx_chan->cur_trans = NULL; + } + if (awoken == pdTRUE) { + need_yield = true; + } + + return need_yield; +} + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT +static bool IRAM_ATTR rmt_isr_handle_tx_loop_end(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + BaseType_t awoken = pdFALSE; + rmt_tx_trans_desc_t *trans_desc = NULL; + bool need_yield = false; + + trans_desc = tx_chan->cur_trans; + if (trans_desc) { +#if !SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + portENTER_CRITICAL_ISR(&channel->spinlock); + // This is a workaround for chips that don't support auto stop + // Although we stop the transaction immediately in ISR handler, it's still possible that some rmt symbols have sneaked out + rmt_ll_tx_stop(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); +#endif // SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + // continue unfinished loop transaction + if (trans_desc->remain_loop_count) { + uint32_t this_loop_count = MIN(trans_desc->remain_loop_count, RMT_LL_MAX_LOOP_COUNT_PER_BATCH); + trans_desc->remain_loop_count -= this_loop_count; + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id)); + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_tx_set_loop_count(hal->regs, channel_id, this_loop_count); + rmt_ll_tx_reset_pointer(hal->regs, channel_id); + // continue the loop transmission, don't need to fill the RMT symbols again, just restart the engine + rmt_ll_tx_start(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); + return need_yield; + } else { + if (tx_chan->on_trans_done) { + rmt_tx_done_event_data_t edata = { + .num_symbols = trans_desc->transmitted_symbol_num, + }; + if (tx_chan->on_trans_done(channel, &edata, tx_chan->user_data)) { + need_yield = true; + } + } + // move transaction to done_queue + xQueueSendFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &trans_desc, &awoken); + if (awoken == pdTRUE) { + need_yield = true; + } + } + } + // trans_done and loop_done should be considered as one "transmission complete" + // but sometimes the trans done event might also be triggered together with loop done event, by accident, so clear it first + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id)); + portENTER_CRITICAL_ISR(&group->spinlock); + // disable interrupt temporarily, re-enable it when there is transaction unhandled in the queue + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), false); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // fetch new transaction description from trans_queue + if (xQueueReceiveFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &trans_desc, &awoken) == pdTRUE) { + tx_chan->cur_trans = trans_desc; + // clear the loop end status when we're sure there still remains transaction to handle + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id)); + + portENTER_CRITICAL_ISR(&group->spinlock); + // enable interrupt again, because the new transaction can trigger new trans done event + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), trans_desc->loop_count == 0); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), trans_desc->loop_count > 0); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // begin a new transaction + rmt_tx_do_transaction(tx_chan, trans_desc); + } else { // No transactions left in the queue + // don't clear interrupt status, so when next time user push new transaction to the queue and call esp_intr_enable, + // we can go into ISR handler again + tx_chan->cur_trans = NULL; + } + if (awoken == pdTRUE) { + need_yield = true; + } + return need_yield; +} +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + +static void IRAM_ATTR rmt_tx_default_isr(void *args) +{ + rmt_tx_channel_t *tx_chan = (rmt_tx_channel_t *)args; + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + bool need_yield = false; + + uint32_t status = rmt_ll_tx_get_interrupt_status(hal->regs, channel_id); + + // Tx threshold interrupt + if (status & RMT_LL_EVENT_TX_THRES(channel_id)) { + if (rmt_isr_handle_tx_threshold(tx_chan)) { + need_yield = true; + } + } + + // Tx end interrupt + if (status & RMT_LL_EVENT_TX_DONE(channel_id)) { + if (rmt_isr_handle_tx_done(tx_chan)) { + need_yield = true; + } + } + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + // Tx loop end interrupt + if (status & RMT_LL_EVENT_TX_LOOP_END(channel_id)) { + if (rmt_isr_handle_tx_loop_end(tx_chan)) { + need_yield = true; + } + } +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +#if SOC_RMT_SUPPORT_DMA +static bool IRAM_ATTR rmt_dma_tx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + rmt_tx_channel_t *tx_chan = (rmt_tx_channel_t *)user_data; + dma_descriptor_t *eof_desc = (dma_descriptor_t *)event_data->tx_eof_desc_addr; + // if the DMA descriptor link is still a ring (i.e. hasn't broken down by `rmt_tx_mark_eof()`), then we treat it as a valid ping-pong event + if (eof_desc->next && eof_desc->next->next) { + // continue pingpong transmission + rmt_tx_trans_desc_t *t = tx_chan->cur_trans; + size_t encoded_symbols = t->transmitted_symbol_num; + if (t->flags.encoding_done) { + rmt_tx_mark_eof(tx_chan); + encoded_symbols += 1; + } else { + encoded_symbols += rmt_encode_check_result(tx_chan, t); + } + t->transmitted_symbol_num = encoded_symbols; + tx_chan->mem_end = tx_chan->ping_pong_symbols * 3 - tx_chan->mem_end; // mem_end equals to either ping_pong_symbols or ping_pong_symbols*2 + // tell DMA that we have a new descriptor attached + gdma_append(dma_chan); + } + return false; +} +#endif // SOC_RMT_SUPPORT_DMA diff --git a/components/driver/test_apps/rmt/CMakeLists.txt b/components/driver/test_apps/rmt/CMakeLists.txt new file mode 100644 index 0000000000..4523465b2f --- /dev/null +++ b/components/driver/test_apps/rmt/CMakeLists.txt @@ -0,0 +1,18 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(rmt_test) + +if(CONFIG_COMPILER_DUMP_RTL_FILES) + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/ + --elf-file ${CMAKE_BINARY_DIR}/rmt_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() diff --git a/components/driver/test_apps/rmt/README.md b/components/driver/test_apps/rmt/README.md new file mode 100644 index 0000000000..3f7a0a04f0 --- /dev/null +++ b/components/driver/test_apps/rmt/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/rmt/main/CMakeLists.txt b/components/driver/test_apps/rmt/main/CMakeLists.txt new file mode 100644 index 0000000000..84ab9ed8dd --- /dev/null +++ b/components/driver/test_apps/rmt/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(srcs "test_app_main.c" + "test_rmt_common.c" + "test_rmt_tx.c" + "test_rmt_rx.c" + "test_util_rmt_encoders.c") + +idf_component_register(SRCS "${srcs}" + WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/rmt/main/test_app_main.c b/components/driver/test_apps/rmt/main/test_app_main.c new file mode 100644 index 0000000000..4ccc872d4c --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_app_main.c @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in RMT driver, so we reserved this threadhold when checking memory leak +// A better way to check a potential memory leak is running a same case by twice, for the second time, the memory usage delta should be zero +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + // ____ __ __ _____ _____ _ + // | _ \| \/ |_ _| |_ _|__ ___| |_ + // | |_) | |\/| | | | | |/ _ \/ __| __| + // | _ <| | | | | | | | __/\__ \ |_ + // |_| \_\_| |_| |_| |_|\___||___/\__| + printf(" ____ __ __ _____ _____ _\r\n"); + printf("| _ \\| \\/ |_ _| |_ _|__ ___| |_\r\n"); + printf("| |_) | |\\/| | | | | |/ _ \\/ __| __|\r\n"); + printf("| _ <| | | | | | | | __/\\__ \\ |_\r\n"); + printf("|_| \\_\\_| |_| |_| |_|\\___||___/\\__|\r\n"); + unity_run_menu(); +} diff --git a/components/driver/test_apps/rmt/main/test_rmt_common.c b/components/driver/test_apps/rmt/main/test_rmt_common.c new file mode 100644 index 0000000000..1b6dedb866 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_rmt_common.c @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "soc/soc_caps.h" + +TEST_CASE("rmt_channel_install_uninstall", "[rmt]") +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .gpio_num = 0, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + .trans_queue_depth = 1, + }; + rmt_channel_handle_t tx_channels[SOC_RMT_TX_CANDIDATES_PER_GROUP] = {}; + rmt_rx_channel_config_t rx_channel_cfg = { + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .gpio_num = 2, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + }; + rmt_channel_handle_t rx_channels[SOC_RMT_RX_CANDIDATES_PER_GROUP] = {}; + + printf("install tx/rx channels, each channel takes one memory block\r\n"); + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[i])); + } + // alloc more when tx channels are exhausted should report error + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + } + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[i])); + } + // alloc more when rx channels are exhausted should report error + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_del_channel(rx_channels[i])); + } + + printf("install tx/rx channels, each channel takes two memory blocks\r\n"); + tx_channel_cfg.mem_block_symbols = 2 * SOC_RMT_MEM_WORDS_PER_CHANNEL; + rx_channel_cfg.mem_block_symbols = 2 * SOC_RMT_MEM_WORDS_PER_CHANNEL; + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[i])); + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + } + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[i])); + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_del_channel(rx_channels[i])); + } + + printf("install tx+rx channels, memory blocks exhaustive\r\n"); + tx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + tx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL * (SOC_RMT_CHANNELS_PER_GROUP - 2); + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[1])); + rx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[1])); + TEST_ESP_OK(rmt_del_channel(tx_channels[0])); + TEST_ESP_OK(rmt_del_channel(tx_channels[1])); + TEST_ESP_OK(rmt_del_channel(rx_channels[0])); + +#if SOC_RMT_SUPPORT_DMA + printf("install DMA channel + normal channel\r\n"); + tx_channel_cfg.mem_block_symbols = 4096; // DMA is aimed for transfer large amount of buffers + tx_channel_cfg.flags.with_dma = true; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + rx_channel_cfg.flags.with_dma = true; + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + tx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + tx_channel_cfg.flags.with_dma = false; + rx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + rx_channel_cfg.flags.with_dma = false; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[1])); + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[1])); + for (int i = 0; i < 2; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + TEST_ESP_OK(rmt_del_channel(rx_channels[i])); + } +#endif // SOC_RMT_SUPPORT_DMA +} diff --git a/components/driver/test_apps/rmt/main/test_rmt_rx.c b/components/driver/test_apps/rmt/main/test_rmt_rx.c new file mode 100644 index 0000000000..7528843fe7 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_rmt_rx.c @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "soc/soc_caps.h" +#include "test_util_rmt_encoders.h" + +#if CONFIG_RMT_ISR_IRAM_SAFE +#define TEST_RMT_CALLBACK_ATTR IRAM_ATTR +#else +#define TEST_RMT_CALLBACK_ATTR +#endif + +typedef struct { + TaskHandle_t task_to_notify; + size_t received_symbol_num; +} test_nec_rx_user_data_t; + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_rx_done_callback(rmt_channel_handle_t channel, rmt_rx_done_event_data_t *edata, void *user_data) +{ + BaseType_t high_task_wakeup = pdFALSE; + test_nec_rx_user_data_t *test_user_data = (test_nec_rx_user_data_t *)user_data; + rmt_symbol_word_t *remote_codes = edata->received_symbols; + esp_rom_printf("%u symbols decoded:\r\n", edata->num_symbols); + for (size_t i = 0; i < edata->num_symbols; i++) { + esp_rom_printf("{%d:%d},{%d:%d}\r\n", remote_codes[i].level0, remote_codes[i].duration0, remote_codes[i].level1, remote_codes[i].duration1); + } + vTaskNotifyGiveFromISR(test_user_data->task_to_notify, &high_task_wakeup); + test_user_data->received_symbol_num = edata->num_symbols; + return high_task_wakeup == pdTRUE; +} + +static void test_rmt_rx_nec_carrier(size_t mem_block_symbols, bool with_dma, rmt_clock_source_t clk_src) +{ + rmt_rx_channel_config_t rx_channel_cfg = { + .clk_src = clk_src, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = mem_block_symbols, + .gpio_num = 0, + .flags.with_dma = with_dma, + .flags.io_loop_back = true, // the GPIO will act like a loopback + }; + printf("install rx channel\r\n"); + rmt_channel_handle_t rx_channel = NULL; + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel)); + printf("register rx event callbacks\r\n"); + rmt_rx_event_callbacks_t cbs = { + .on_recv_done = test_rmt_rx_done_callback, + }; + test_nec_rx_user_data_t test_user_data = { + .task_to_notify = xTaskGetCurrentTaskHandle(), + }; + TEST_ESP_OK(rmt_rx_register_event_callbacks(rx_channel, &cbs, &test_user_data)); + + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = clk_src, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.io_loop_back = true, // TX channel and RX channel will bounded to the same GPIO + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + + printf("install nec protocol encoder\r\n"); + rmt_encoder_handle_t nec_encoder = NULL; + TEST_ESP_OK(test_rmt_new_nec_protocol_encoder(&nec_encoder)); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + printf("enable rx channel\r\n"); + TEST_ESP_OK(rmt_enable(rx_channel)); + + rmt_symbol_word_t remote_codes[128]; + + rmt_receive_config_t receive_config = { + .signal_range_min_ns = 1250, + .signal_range_max_ns = 12000000, + }; + + // ready to receive + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + // ready to receive + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send customized NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0xFF00, 0xFF00, 0xFF00, 0xFF00 + }, 8, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 66); +#else + // ready to receive + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send customized NEC frame without carrier\r\n"); + // the maximum symbols can receive is its memory block capacity + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00 + }, 10, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, mem_block_symbols); +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + +#if SOC_RMT_SUPPORT_RX_DEMODULATION + rmt_carrier_config_t carrier_cfg = { + .duty_cycle = 0.33, + .frequency_hz = 38000, + }; + printf("enable modulation for tx channel\r\n"); + TEST_ESP_OK(rmt_apply_carrier(tx_channel, &carrier_cfg)); + printf("enable demodulation for rx channel\r\n"); + // need to leave a tolerance for the carrier demodulation, can't set the carrier frequency exactly to 38KHz + // should reduce frequency to some extend + carrier_cfg.frequency_hz = 25000; + TEST_ESP_OK(rmt_apply_carrier(rx_channel, &carrier_cfg)); + + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame with carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send customized frame with carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0xFF00, 0xFF00, 0xFF00, 0xFF00 + }, 8, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 66); +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + + printf("disable modulation and demodulation for tx and rx channels\r\n"); + TEST_ESP_OK(rmt_apply_carrier(tx_channel, NULL)); + TEST_ESP_OK(rmt_apply_carrier(rx_channel, NULL)); +#endif // SOC_RMT_SUPPORT_RX_DEMODULATION + + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + printf("disable tx and rx channels\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + TEST_ESP_OK(rmt_disable(rx_channel)); + printf("delete channels and encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(rx_channel)); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(nec_encoder)); +} + +TEST_CASE("rmt_rx_nec_carrier_no_dma", "[rmt]") +{ + // test width different clock sources + rmt_clock_source_t clk_srcs[] = SOC_RMT_CLKS; + for (size_t i = 0; i < sizeof(clk_srcs) / sizeof(clk_srcs[0]); i++) { + test_rmt_rx_nec_carrier(SOC_RMT_MEM_WORDS_PER_CHANNEL, false, clk_srcs[i]); + } +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_rx_nec_carrier_with_dma", "[rmt]") +{ + test_rmt_rx_nec_carrier(128, true, RMT_CLK_SRC_DEFAULT); +} +#endif diff --git a/components/driver/test_apps/rmt/main/test_rmt_tx.c b/components/driver/test_apps/rmt/main/test_rmt_tx.c new file mode 100644 index 0000000000..676834f42b --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_rmt_tx.c @@ -0,0 +1,607 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/rmt_tx.h" +#include "esp_timer.h" +#include "soc/soc_caps.h" +#include "test_util_rmt_encoders.h" + +#if CONFIG_RMT_ISR_IRAM_SAFE +#define TEST_RMT_CALLBACK_ATTR IRAM_ATTR +#else +#define TEST_RMT_CALLBACK_ATTR +#endif + +static void test_rmt_channel_single_trans(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_single_led = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_single_led)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_single_led)); + + printf("single transmission: light up one RGB LED\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + TEST_ESP_OK(rmt_transmit(tx_channel_single_led, led_strip_encoder, (uint8_t[]) { + 0x00, 0x7F, 0xFF + }, 3, &transmit_config)); + // adding extra delay here for visualizing + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ESP_OK(rmt_transmit(tx_channel_single_led, led_strip_encoder, (uint8_t[]) { + 0xFF, 0x00, 0x7F + }, 3, &transmit_config)); + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ESP_OK(rmt_transmit(tx_channel_single_led, led_strip_encoder, (uint8_t[]) { + 0x7F, 0xFF, 0x00 + }, 3, &transmit_config)); + vTaskDelay(pdMS_TO_TICKS(500)); + + // can't delete channel if it's not in stop state + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, rmt_del_channel(tx_channel_single_led)); + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_single_led)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_single_led)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +} + +TEST_CASE("rmt_single_trans_no_dma", "[rmt]") +{ + test_rmt_channel_single_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_single_trans_with_dma", "[rmt]") +{ + test_rmt_channel_single_trans(512, true); +} +#endif + +static void test_rmt_ping_pong_trans(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_multi_leds = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_multi_leds)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + + // Mutiple LEDs (ping-pong in the background) + printf("ping pong transmission: light up 100 RGB LEDs\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; +#define TEST_LED_NUM 100 + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + // color: Material Design Green-A200 (#69F0AE) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0xF0; + leds_grb[i + 1] = 0x69; + leds_grb[i + 2] = 0xAE; + } + printf("start transmission and stop immediately, only a few LEDs are light up\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + // this second transmission will stay in the queue and shouldn't be dispatched until we restart the tx channel later + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + esp_rom_delay_us(100); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + vTaskDelay(pdTICKS_TO_MS(500)); + + printf("enable tx channel again\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + // adding extra delay here for visualizing + vTaskDelay(pdTICKS_TO_MS(500)); + // color: Material Design Pink-A200 (#FF4081) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0x40; + leds_grb[i + 1] = 0xFF; + leds_grb[i + 2] = 0x81; + } + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + vTaskDelay(pdTICKS_TO_MS(500)); + // color: Material Design Orange-900 (#E65100) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0x51; + leds_grb[i + 1] = 0xE6; + leds_grb[i + 2] = 0x00; + } + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + vTaskDelay(pdTICKS_TO_MS(500)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_multi_leds)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +#undef TEST_LED_NUM +} + +TEST_CASE("rmt_ping_pong_trans_no_dma", "[rmt]") +{ + test_rmt_ping_pong_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_ping_pong_trans_with_dma", "[rmt]") +{ + test_rmt_ping_pong_trans(1024, true); +} +#endif + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_tx_done_cb_check_event_data(rmt_channel_handle_t channel, rmt_tx_done_event_data_t *edata, void *user_data) +{ + uint32_t *p_expected_encoded_size = (uint32_t *)user_data; + TEST_ASSERT_EQUAL(*p_expected_encoded_size, edata->num_symbols); + return false; +} + +static void test_rmt_trans_done_event(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 1, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_multi_leds = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_multi_leds)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + + printf("register trans done event callback\r\n"); + rmt_tx_event_callbacks_t cbs = { + .on_trans_done = test_rmt_tx_done_cb_check_event_data, + }; + uint32_t expected_encoded_size = 0; + TEST_ESP_OK(rmt_tx_register_event_callbacks(tx_channel_multi_leds, &cbs, &expected_encoded_size)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + + printf("transmit dynamic number of LEDs\r\n"); +#define TEST_LED_NUM 40 + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + // color: Material Design Purple-800 (6A1B9A) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0x1B; + leds_grb[i + 1] = 0x6A; + leds_grb[i + 2] = 0x9A; + } + for (int i = 1; i <= TEST_LED_NUM; i++) { + expected_encoded_size = 2 + i * 24; // 2 = 1 reset symbol + 1 eof symbol, 24 = 8*3(RGB) + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, i * 3, &transmit_config)); + // wait for the transmission finished and recycled + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel_multi_leds, -1)); + } + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_multi_leds)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +#undef TEST_LED_NUM +} + +TEST_CASE("rmt_trans_done_event_callback_no_dma", "[rmt]") +{ + test_rmt_trans_done_event(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_trans_done_event_callback_with_dma", "[rmt]") +{ + test_rmt_trans_done_event(332, true); +} +#endif + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_loop_done_cb_check_event_data(rmt_channel_handle_t channel, rmt_tx_done_event_data_t *edata, void *user_data) +{ + uint32_t *p_expected_encoded_size = (uint32_t *)user_data; + TEST_ASSERT_EQUAL(*p_expected_encoded_size, edata->num_symbols); + return false; +} + +static void test_rmt_loop_trans(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_multi_leds = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_multi_leds)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + + printf("register loop done event callback\r\n"); + rmt_tx_event_callbacks_t cbs = { + .on_trans_done = test_rmt_loop_done_cb_check_event_data, + }; + uint32_t expected_encoded_size = 0; + TEST_ESP_OK(rmt_tx_register_event_callbacks(tx_channel_multi_leds, &cbs, &expected_encoded_size)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + + printf("loop transmission: light up RGB LEDs in a loop\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 5, + }; +#define TEST_LED_NUM 3 + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + for (int i = 0; i < TEST_LED_NUM * 3; i++) { + leds_grb[i] = 0x10 + i; + } + expected_encoded_size = 2 + 24 * TEST_LED_NUM; + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + vTaskDelay(pdTICKS_TO_MS(100)); + + printf("wait for loop transactions done\r\n"); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel_multi_leds, -1)); + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_multi_leds)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +#undef TEST_LED_NUM +} + +TEST_CASE("rmt_loop_trans_no_dma", "[rmt]") +{ + test_rmt_loop_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL * 2, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_loop_trans_with_dma", "[rmt]") +{ + test_rmt_loop_trans(128, true); +} +#endif // SOC_RMT_SUPPORT_DMA +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + +TEST_CASE("rmt_infinite_loop_trans", "[rmt]") +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .gpio_num = 2, + .trans_queue_depth = 3, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + printf("install step motor encoder\r\n"); + // stepper encoder is as simple as a copy encoder + rmt_encoder_t *copy_encoder = NULL; + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, ©_encoder)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + rmt_transmit_config_t transmit_config = { + .loop_count = -1, // infinite loop transmission + }; + + printf("infinite loop transmission: keep spinning stepper motor\r\n"); + uint32_t step_motor_frequency_hz = 1000; // 1KHz + uint32_t rmt_raw_symbol_duration = 1000000 / step_motor_frequency_hz / 2; + // 1KHz PWM, Period: 1ms + rmt_symbol_word_t stepper_motor_rmt_symbol = { + .level0 = 0, + .duration0 = rmt_raw_symbol_duration, + .level1 = 1, + .duration1 = rmt_raw_symbol_duration, + }; + TEST_ESP_OK(rmt_transmit(tx_channel, copy_encoder, &stepper_motor_rmt_symbol, sizeof(stepper_motor_rmt_symbol), &transmit_config)); + // not trans done event should be triggered + TEST_ESP_ERR(ESP_ERR_TIMEOUT, rmt_tx_wait_all_done(tx_channel, 500)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + // the flush operation should return immediately, as there's not pending transactions and the TX machine has stopped + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, 0)); + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + printf("enable tx channel again\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + printf("finite loop transmission: spinning stepper motor with various number of loops\r\n"); +#define TEST_RMT_LOOPS 5 + uint32_t pwm_freq[TEST_RMT_LOOPS] = {}; + rmt_symbol_word_t pwm_rmt_symbols[TEST_RMT_LOOPS] = {}; + for (int i = 0; i < TEST_RMT_LOOPS; i++) { + transmit_config.loop_count = 100 * i; + pwm_freq[i] = 1000 * (i + 1); + uint32_t pwm_symbol_duration = 1000000 / pwm_freq[i] / 2; + // 1KHz PWM, Period: 1ms + pwm_rmt_symbols[i] = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = pwm_symbol_duration, + .level1 = 1, + .duration1 = pwm_symbol_duration, + }; + TEST_ESP_OK(rmt_transmit(tx_channel, copy_encoder, &pwm_rmt_symbols[i], sizeof(rmt_symbol_word_t), &transmit_config)); + } + + printf("wait for loop transactions done\r\n"); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); // wait forever + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); +#undef TEST_RMT_LOOPS +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + + printf("remove tx channel and motor encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(copy_encoder)); +} + +static void test_rmt_tx_nec_carrier(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = mem_block_symbols, + .gpio_num = 2, + .trans_queue_depth = 4, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + printf("install nec protocol encoder\r\n"); + rmt_encoder_handle_t nec_encoder = NULL; + TEST_ESP_OK(test_rmt_new_nec_protocol_encoder(&nec_encoder)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + printf("transmit nec frame without carrier\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("transmit nec frame with carrier\r\n"); + rmt_carrier_config_t carrier_cfg = { + .duty_cycle = 0.33, + .frequency_hz = 38000, + }; + TEST_ESP_OK(rmt_apply_carrier(tx_channel, &carrier_cfg)); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("remove carrier\r\n"); + TEST_ESP_OK(rmt_apply_carrier(tx_channel, NULL)); + + printf("transmit nec frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + printf("remove tx channel and nec encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(nec_encoder)); +} + +TEST_CASE("rmt_tx_nec_carrier_no_dma", "[rmt]") +{ + test_rmt_tx_nec_carrier(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_tx_nec_carrier_with_dma", "[rmt]") +{ + test_rmt_tx_nec_carrier(128, true); +} +#endif + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_tx_done_cb_record_time(rmt_channel_handle_t channel, rmt_tx_done_event_data_t *edata, void *user_data) +{ + int64_t *record_time = (int64_t *)user_data; + *record_time = esp_timer_get_time(); + return false; +} + +static void test_rmt_multi_channels_trans(size_t channel0_mem_block_symbols, size_t channel1_mem_block_symbols, bool channel0_with_dma, bool channel1_with_dma) +{ +#define TEST_RMT_CHANS 2 +#define TEST_LED_NUM 24 + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + }; + printf("install tx channels\r\n"); + rmt_channel_handle_t tx_channels[TEST_RMT_CHANS] = {NULL}; + int gpio_nums[TEST_RMT_CHANS] = {0, 2}; + size_t mem_blk_syms[TEST_RMT_CHANS] = {channel0_mem_block_symbols, channel1_mem_block_symbols}; + bool dma_flags[TEST_RMT_CHANS] = {channel0_with_dma, channel1_with_dma}; + for (int i = 0; i < TEST_RMT_CHANS; i++) { + tx_channel_cfg.gpio_num = gpio_nums[i]; + tx_channel_cfg.mem_block_symbols = mem_blk_syms[i]; + tx_channel_cfg.flags.with_dma = dma_flags[i]; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[i])); + } + + printf("install led strip encoders\r\n"); + rmt_encoder_handle_t led_strip_encoders[TEST_RMT_CHANS] = {NULL}; + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoders[i])); + } + + printf("register tx event callback\r\n"); + rmt_tx_event_callbacks_t cbs = { + .on_trans_done = test_rmt_tx_done_cb_record_time + }; + int64_t record_stop_time[TEST_RMT_CHANS] = {}; + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_register_event_callbacks(tx_channels[i], &cbs, &record_stop_time[i])); + } + + printf("enable tx channels\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_enable(tx_channels[i])); + } + + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + // color: Material Design Green-A200 (#69F0AE) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0xF0; + leds_grb[i + 1] = 0x69; + leds_grb[i + 2] = 0xAE; + } + + printf("transmit without synchronization\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + // the channels should work independently, without synchronization + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_transmit(tx_channels[i], led_strip_encoders[i], leds_grb, TEST_LED_NUM * 3, &transmit_config)); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channels[i], -1)); + } + printf("stop time (no sync):\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + printf("\t%lld\r\n", record_stop_time[i]); + } + // without synchronization, there will be obvious time shift + TEST_ASSERT((record_stop_time[1] - record_stop_time[0]) < 100); + + printf("install sync manager\r\n"); + rmt_sync_manager_handle_t synchro = NULL; + rmt_sync_manager_config_t synchro_config = { + .tx_channel_array = tx_channels, + .array_size = TEST_RMT_CHANS, + }; +#if SOC_RMT_SUPPORT_TX_SYNCHRO + TEST_ESP_OK(rmt_new_sync_manager(&synchro_config, &synchro)); +#else + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, rmt_new_sync_manager(&synchro_config, &synchro)); +#endif // SOC_RMT_SUPPORT_TX_SYNCHRO + +#if SOC_RMT_SUPPORT_TX_SYNCHRO + printf("transmit with synchronization\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_transmit(tx_channels[i], led_strip_encoders[i], leds_grb, TEST_LED_NUM * 3, &transmit_config)); + // manually introduce the delay, to show the managed channels are indeed in sync + vTaskDelay(pdMS_TO_TICKS(10)); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channels[i], -1)); + } + printf("stop time (with sync):\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + printf("\t%lld\r\n", record_stop_time[i]); + } + // because of synchronization, the managed channels will stop at the same time + // but call of `esp_timer_get_time` won't happen at the same time, so there still be time drift, very small + TEST_ASSERT((record_stop_time[1] - record_stop_time[0]) < 10); + + printf("reset sync manager\r\n"); + TEST_ESP_OK(rmt_sync_reset(synchro)); + printf("transmit with synchronization again\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_transmit(tx_channels[i], led_strip_encoders[i], leds_grb, TEST_LED_NUM * 3, &transmit_config)); + // manually introduce the delay, ensure the channels get synchronization + vTaskDelay(pdMS_TO_TICKS(10)); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channels[i], -1)); + } + printf("stop time (with sync):\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + printf("\t%lld\r\n", record_stop_time[i]); + } + TEST_ASSERT((record_stop_time[1] - record_stop_time[0]) < 10); + + printf("delete sync manager\r\n"); + TEST_ESP_OK(rmt_del_sync_manager(synchro)); +#endif // SOC_RMT_SUPPORT_TX_SYNCHRO + + printf("disable tx channels\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_disable(tx_channels[i])); + } + printf("delete channels and encoders\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_del_encoder(led_strip_encoders[i])); + } +#undef TEST_LED_NUM +#undef TEST_RMT_CHANS +} + +TEST_CASE("rmt_multi_channels_trans_no_dma", "[rmt]") +{ + test_rmt_multi_channels_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL, SOC_RMT_MEM_WORDS_PER_CHANNEL, false, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_multi_channels_trans_with_dma", "[rmt]") +{ + test_rmt_multi_channels_trans(1024, SOC_RMT_MEM_WORDS_PER_CHANNEL, true, false); +} +#endif diff --git a/components/driver/test_apps/rmt/main/test_util_rmt_encoders.c b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.c new file mode 100644 index 0000000000..e99bad5d53 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.c @@ -0,0 +1,214 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "unity.h" +#include "driver/rmt_encoder.h" + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + switch (led_encoder->state) { + case 0: + encoded_symbols += led_encoder->bytes_encoder->encode(led_encoder->bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: + encoded_symbols += led_encoder->copy_encoder->encode(led_encoder->copy_encoder, channel, &led_encoder->reset_code, sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + state |= RMT_ENCODING_COMPLETE; + led_encoder->state = 0; // back to the initial encoding session + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = 0; + return ESP_OK; +} + +esp_err_t test_rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder) +{ + rmt_led_strip_encoder_t *led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + // different led strip might have its own timing requirements, following parameter is for WS2812 + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 3, // T0H=0.3us + .level1 = 0, + .duration1 = 9, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 9, // T1H=0.9us + .level1 = 0, + .duration1 = 3, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + TEST_ESP_OK(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder)); + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder)); + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = 250, + .level1 = 0, + .duration1 = 250, + }; // reset duration defaults to 50us + *ret_encoder = &led_encoder->base; + return ESP_OK; +} + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *copy_encoder; + rmt_encoder_t *bytes_encoder; + int state; +} rmt_nec_protocol_encoder_t; + +static size_t rmt_encode_nec_protocol(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_nec_protocol_encoder_t *nec_encoder = __containerof(encoder, rmt_nec_protocol_encoder_t, base); + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + const rmt_symbol_word_t nec_leading_symbol = { + .level0 = 1, + .duration0 = 9000, + .level1 = 0, + .duration1 = 4500 + }; + const rmt_symbol_word_t nec_ending_symbol = { + .level0 = 1, + .duration0 = 560, + .level1 = 0, + .duration1 = 0x7FFF + }; + switch (nec_encoder->state) { + case 0: + encoded_symbols += nec_encoder->copy_encoder->encode(nec_encoder->copy_encoder, channel, &nec_leading_symbol, sizeof(nec_leading_symbol), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 1; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 1: + encoded_symbols += nec_encoder->bytes_encoder->encode(nec_encoder->bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 2; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 2: + encoded_symbols += nec_encoder->copy_encoder->encode(nec_encoder->copy_encoder, channel, &nec_ending_symbol, sizeof(nec_ending_symbol), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + state |= RMT_ENCODING_COMPLETE; + nec_encoder->state = 0; // back to the initial encoding session + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_nec_protocol_encoder(rmt_encoder_t *encoder) +{ + rmt_nec_protocol_encoder_t *nec_encoder = __containerof(encoder, rmt_nec_protocol_encoder_t, base); + rmt_del_encoder(nec_encoder->copy_encoder); + rmt_del_encoder(nec_encoder->bytes_encoder); + free(nec_encoder); + return ESP_OK; +} + +static esp_err_t rmt_nec_protocol_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_nec_protocol_encoder_t *nec_encoder = __containerof(encoder, rmt_nec_protocol_encoder_t, base); + rmt_encoder_reset(nec_encoder->copy_encoder); + rmt_encoder_reset(nec_encoder->bytes_encoder); + nec_encoder->state = 0; + return ESP_OK; +} + +esp_err_t test_rmt_new_nec_protocol_encoder(rmt_encoder_handle_t *ret_encoder) +{ + rmt_nec_protocol_encoder_t *nec_encoder = calloc(1, sizeof(rmt_nec_protocol_encoder_t)); + nec_encoder->base.encode = rmt_encode_nec_protocol; + nec_encoder->base.del = rmt_del_nec_protocol_encoder; + nec_encoder->base.reset = rmt_nec_protocol_encoder_reset; + + // different IR protocol might have its own timing requirements, following parameter is for NEC + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 560, // T0H=560us + .level1 = 0, + .duration1 = 560, // T0L=560us + }, + .bit1 = { + .level0 = 1, + .duration0 = 560, // T1H=560us + .level1 = 0, + .duration1 = 1690, // T1L=1690us + }, + }; + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, &nec_encoder->copy_encoder)); + TEST_ESP_OK(rmt_new_bytes_encoder(&bytes_encoder_config, &nec_encoder->bytes_encoder)); + + *ret_encoder = &nec_encoder->base; + return ESP_OK; +} diff --git a/components/driver/test_apps/rmt/main/test_util_rmt_encoders.h b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.h new file mode 100644 index 0000000000..a325c92325 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t test_rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder); + +esp_err_t test_rmt_new_nec_protocol_encoder(rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/test_apps/rmt/pytest_rmt.py b/components/driver/test_apps/rmt/pytest_rmt.py new file mode 100644 index 0000000000..441f669120 --- /dev/null +++ b/components/driver/test_apps/rmt/pytest_rmt.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'iram_safe', + 'release', + ], + indirect=True, +) +def test_rmt(dut: Dut) -> None: + dut.expect('Press ENTER to see the list of tests') + dut.write('*') + dut.expect_unity_test_output() diff --git a/components/driver/test_apps/rmt/sdkconfig.ci.iram_safe b/components/driver/test_apps/rmt/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..9621665f0d --- /dev/null +++ b/components/driver/test_apps/rmt/sdkconfig.ci.iram_safe @@ -0,0 +1,7 @@ +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_RMT_ISR_IRAM_SAFE=y + +# silent the error check, as the error string are stored in rodata, causing RTL check failure +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_HAL_ASSERTION_SILIENT=y diff --git a/components/driver/test_apps/rmt/sdkconfig.ci.release b/components/driver/test_apps/rmt/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/rmt/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/driver/test_apps/rmt/sdkconfig.defaults b/components/driver/test_apps/rmt/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/rmt/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/hal/include/hal/rmt_hal.h b/components/hal/include/hal/rmt_hal.h index a591f90800..ef7a077857 100644 --- a/components/hal/include/hal/rmt_hal.h +++ b/components/hal/include/hal/rmt_hal.h @@ -34,6 +34,13 @@ typedef struct { */ void rmt_hal_init(rmt_hal_context_t *hal); +/** + * @brief Deinitialize the RMT HAL driver + * + * @param hal: RMT HAL context + */ +void rmt_hal_deinit(rmt_hal_context_t *hal); + /** * @brief Reset RMT TX Channel * diff --git a/components/hal/include/hal/rmt_types.h b/components/hal/include/hal/rmt_types.h index cbe689096d..f3c9a6f7ba 100644 --- a/components/hal/include/hal/rmt_types.h +++ b/components/hal/include/hal/rmt_types.h @@ -28,7 +28,7 @@ typedef union { unsigned int duration1 : 15; /*!< Duration of level1 */ unsigned int level1 : 1; /*!< Level of the second part */ }; - unsigned int val; /*!< Equivelent unsigned value for the RMT symbol */ + unsigned int val; /*!< Equivalent unsigned value for the RMT symbol */ } rmt_symbol_word_t; #ifdef __cplusplus diff --git a/components/hal/rmt_hal.c b/components/hal/rmt_hal.c index 5bff27d4ed..d83e5952da 100644 --- a/components/hal/rmt_hal.c +++ b/components/hal/rmt_hal.c @@ -10,6 +10,21 @@ void rmt_hal_init(rmt_hal_context_t *hal) { hal->regs = &RMT; + rmt_ll_power_down_mem(hal->regs, false); // turn on RMTMEM power domain + rmt_ll_enable_mem_access_nonfifo(hal->regs, true); // APB access the RMTMEM in nonfifo mode + rmt_ll_enable_interrupt(hal->regs, UINT32_MAX, false); // disable all interupt events + rmt_ll_clear_interrupt_status(hal->regs, UINT32_MAX); // clear all pending events +#if SOC_RMT_SUPPORT_TX_SYNCHRO + rmt_ll_tx_clear_sync_group(hal->regs); +#endif // SOC_RMT_SUPPORT_TX_SYNCHRO +} + +void rmt_hal_deinit(rmt_hal_context_t *hal) +{ + rmt_ll_enable_interrupt(hal->regs, UINT32_MAX, false); // disable all interupt events + rmt_ll_clear_interrupt_status(hal->regs, UINT32_MAX); // clear all pending events + rmt_ll_power_down_mem(hal->regs, true); // turn off RMTMEM power domain + hal->regs = NULL; } void rmt_hal_tx_channel_reset(rmt_hal_context_t *hal, uint32_t channel) diff --git a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in index e3d7f2e7d0..f6c147368b 100644 --- a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in @@ -411,7 +411,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32c3/include/soc/soc_caps.h b/components/soc/esp32c3/include/soc/soc_caps.h index 0ebf71f4d8..198c5015a8 100644 --- a/components/soc/esp32c3/include/soc/soc_caps.h +++ b/components/soc/esp32c3/include/soc/soc_caps.h @@ -195,7 +195,7 @@ #define SOC_RMT_SUPPORT_TX_ASYNC_STOP 1 /*!< Support stop transmission asynchronously */ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmit specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_XTAL 1 /*!< Support set XTAL clock as the RMT clock source */ #define SOC_RMT_SUPPORT_APB 1 /*!< Support set APB as the RMT clock source */ #define SOC_RMT_SUPPORT_RC_FAST 1 /*!< Support set RC_FAST clock as the RMT clock source */ diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index 38f0158102..06422606d7 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -403,7 +403,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index 284bc2b6a0..41e0a55fde 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -206,7 +206,7 @@ #define SOC_RMT_SUPPORT_TX_ASYNC_STOP 1 /*!< Support stop transmission asynchronously */ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmit specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_XTAL 1 /*!< Support set XTAL clock as the RMT clock source */ #define SOC_RMT_SUPPORT_AHB 1 /*!< Support set AHB clock as the RMT clock source */ #define SOC_RMT_SUPPORT_RC_FAST 1 /*!< Support set RC_FAST clock as the RMT clock source */ diff --git a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in index af274424af..0f6d879339 100644 --- a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in @@ -451,7 +451,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index a48d92f693..0509d88a07 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -213,7 +213,7 @@ #define SOC_RMT_SUPPORT_TX_ASYNC_STOP 1 /*!< Support stop transmission asynchronously */ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmiting specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_REF_TICK 1 /*!< Support set REF_TICK as the RMT clock source */ #define SOC_RMT_SUPPORT_APB 1 /*!< Support set APB as the RMT clock source */ #define SOC_RMT_CHANNEL_CLK_INDEPENDENT 1 /*!< Can select different source clock for each channel */ diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index 12b2d2c90f..107ffc6699 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -503,7 +503,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32s3/include/soc/soc_caps.h b/components/soc/esp32s3/include/soc/soc_caps.h index 1e4ac04d72..6520239cea 100644 --- a/components/soc/esp32s3/include/soc/soc_caps.h +++ b/components/soc/esp32s3/include/soc/soc_caps.h @@ -205,7 +205,7 @@ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmit specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP 1 /*!< Hardware support of auto-stop in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_XTAL 1 /*!< Support set XTAL clock as the RMT clock source */ #define SOC_RMT_SUPPORT_RC_FAST 1 /*!< Support set RC_FAST clock as the RMT clock source */ #define SOC_RMT_SUPPORT_APB 1 /*!< Support set APB as the RMT clock source */