mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
driver_ng: implement new rmt driver
The legacy driver can't handle the breaking change between esp chips very well. And it's not elegant to extend new feature like DMA, ETM. The new driver can return a opaque handle for each RMT channel. An obvious transaction concept was also introduced. TX and RX functionalities are splited out.
This commit is contained in:
parent
df474e74a1
commit
2fb43820c2
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
83
components/driver/include/driver/rmt_common.h
Normal file
83
components/driver/include/driver/rmt_common.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
138
components/driver/include/driver/rmt_encoder.h
Normal file
138
components/driver/include/driver/rmt_encoder.h
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
103
components/driver/include/driver/rmt_rx.h
Normal file
103
components/driver/include/driver/rmt_rx.h
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#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
|
174
components/driver/include/driver/rmt_tx.h
Normal file
174
components/driver/include/driver/rmt_tx.h
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#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
|
71
components/driver/include/driver/rmt_types.h
Normal file
71
components/driver/include/driver/rmt_types.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#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
|
186
components/driver/rmt/rmt_common.c
Normal file
186
components/driver/rmt/rmt_common.c
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/lock.h>
|
||||
#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);
|
||||
}
|
301
components/driver/rmt/rmt_encoder.c
Normal file
301
components/driver/rmt/rmt_encoder.c
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#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);
|
||||
}
|
194
components/driver/rmt/rmt_private.h
Normal file
194
components/driver/rmt/rmt_private.h
Normal file
@ -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 <target>.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
|
645
components/driver/rmt/rmt_rx.c
Normal file
645
components/driver/rmt/rmt_rx.c
Normal file
@ -0,0 +1,645 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#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
|
1043
components/driver/rmt/rmt_tx.c
Normal file
1043
components/driver/rmt/rmt_tx.c
Normal file
File diff suppressed because it is too large
Load Diff
18
components/driver/test_apps/rmt/CMakeLists.txt
Normal file
18
components/driver/test_apps/rmt/CMakeLists.txt
Normal file
@ -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()
|
2
components/driver/test_apps/rmt/README.md
Normal file
2
components/driver/test_apps/rmt/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- |
|
8
components/driver/test_apps/rmt/main/CMakeLists.txt
Normal file
8
components/driver/test_apps/rmt/main/CMakeLists.txt
Normal file
@ -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)
|
52
components/driver/test_apps/rmt/main/test_app_main.c
Normal file
52
components/driver/test_apps/rmt/main/test_app_main.c
Normal file
@ -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();
|
||||
}
|
100
components/driver/test_apps/rmt/main/test_rmt_common.c
Normal file
100
components/driver/test_apps/rmt/main/test_rmt_common.c
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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
|
||||
}
|
202
components/driver/test_apps/rmt/main/test_rmt_rx.c
Normal file
202
components/driver/test_apps/rmt/main/test_rmt_rx.c
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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
|
607
components/driver/test_apps/rmt/main/test_rmt_tx.c
Normal file
607
components/driver/test_apps/rmt/main/test_rmt_tx.c
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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
|
214
components/driver/test_apps/rmt/main/test_util_rmt_encoders.c
Normal file
214
components/driver/test_apps/rmt/main/test_util_rmt_encoders.c
Normal file
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#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;
|
||||
}
|
@ -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
|
24
components/driver/test_apps/rmt/pytest_rmt.py
Normal file
24
components/driver/test_apps/rmt/pytest_rmt.py
Normal file
@ -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()
|
7
components/driver/test_apps/rmt/sdkconfig.ci.iram_safe
Normal file
7
components/driver/test_apps/rmt/sdkconfig.ci.iram_safe
Normal file
@ -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
|
5
components/driver/test_apps/rmt/sdkconfig.ci.release
Normal file
5
components/driver/test_apps/rmt/sdkconfig.ci.release
Normal file
@ -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
|
2
components/driver/test_apps/rmt/sdkconfig.defaults
Normal file
2
components/driver/test_apps/rmt/sdkconfig.defaults
Normal file
@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user