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 */