Merge branch 'feature/rmt_driver_ng' into 'master'

 Introduce a brand new driver for RMT

Closes IDF-2081, IDF-3855, IDF-4363, IDFGH-3160, and IDFGH-7275

See merge request espressif/esp-idf!15618
This commit is contained in:
morris 2022-05-07 20:18:33 +08:00
commit 1b1553c853
153 changed files with 7850 additions and 3007 deletions

View File

@ -31,6 +31,16 @@ example_test_pytest_esp32_generic:
TARGET: ESP32
ENV_MARKER: generic
example_test_pytest_esp32_ir_transceiver:
extends:
- .pytest_examples_dir_template
- .rules:test:example_test-esp32
needs:
- build_pytest_examples_esp32
variables:
TARGET: ESP32
ENV_MARKER: ir_transceiver
example_test_pytest_esp32s2_generic:
extends:
- .pytest_examples_dir_template
@ -462,12 +472,6 @@ example_test_011:
variables:
SETUP_TOOLS: "1"
example_test_012:
extends: .example_test_esp32_template
tags:
- ESP32
- Example_RMT_IR_PROTOCOLS
example_test_013:
extends: .example_test_esp32_template
tags:

View File

@ -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)

View File

@ -279,4 +279,31 @@ menu "Driver configurations"
Note that, this option only controls the PCNT driver log, won't affect other drivers.
endmenu # PCNT Configuration
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
help
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

View File

@ -6,281 +6,22 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "soc/soc_caps.h"
#include "soc/clk_tree_defs.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "hal/rmt_types.h"
#include "driver/rmt_types_legacy.h"
#if !CONFIG_RMT_SUPPRESS_DEPRECATE_WARN
#warning "The legacy RMT driver is deprecated, please use driver/rmt_tx.h and/or driver/rmt_rx.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define RMT_CHANNEL_FLAGS_AWARE_DFS (1 << 0) /*!< Channel can work during APB clock scaling */
#define RMT_CHANNEL_FLAGS_INVERT_SIG (1 << 1) /*!< Invert RMT signal */
/** @cond */
#define RMT_CHANNEL_FLAGS_ALWAYS_ON RMT_CHANNEL_FLAGS_AWARE_DFS /*!< Deprecated name, defined here for compatibility */
/** @endcond */
/**
* @brief Define memory space of each RMT channel (in words = 4 bytes)
*
*/
#define RMT_MEM_ITEM_NUM SOC_RMT_MEM_WORDS_PER_CHANNEL
/**
* @brief Definition of RMT item
*/
typedef struct {
union {
struct {
uint32_t duration0 : 15; /*!< Duration of level0 */
uint32_t level0 : 1; /*!< Level of the first part */
uint32_t duration1 : 15; /*!< Duration of level1 */
uint32_t level1 : 1; /*!< Level of the second part */
};
uint32_t val; /*!< Equivalent unsigned value for the RMT item */
};
} rmt_item32_t;
/**
* @brief RMT hardware memory layout
*/
typedef struct {
struct {
volatile rmt_item32_t data32[SOC_RMT_MEM_WORDS_PER_CHANNEL];
} chan[SOC_RMT_CHANNELS_PER_GROUP];
} rmt_mem_t;
/**
* @brief RMT channel ID
*
*/
typedef enum {
RMT_CHANNEL_0, /*!< RMT channel number 0 */
RMT_CHANNEL_1, /*!< RMT channel number 1 */
RMT_CHANNEL_2, /*!< RMT channel number 2 */
RMT_CHANNEL_3, /*!< RMT channel number 3 */
#if SOC_RMT_CHANNELS_PER_GROUP > 4
RMT_CHANNEL_4, /*!< RMT channel number 4 */
RMT_CHANNEL_5, /*!< RMT channel number 5 */
RMT_CHANNEL_6, /*!< RMT channel number 6 */
RMT_CHANNEL_7, /*!< RMT channel number 7 */
#endif
RMT_CHANNEL_MAX /*!< Number of RMT channels */
} rmt_channel_t;
/**
* @brief RMT Internal Memory Owner
*
*/
typedef enum {
RMT_MEM_OWNER_TX, /*!< RMT RX mode, RMT transmitter owns the memory block*/
RMT_MEM_OWNER_RX, /*!< RMT RX mode, RMT receiver owns the memory block*/
RMT_MEM_OWNER_MAX,
} rmt_mem_owner_t;
/**
* @brief Clock Source of RMT Channel
*
*/
typedef soc_periph_rmt_clk_src_legacy_t rmt_source_clk_t;
/**
* @brief RMT Data Mode
*
* @note We highly recommended to use MEM mode not FIFO mode since there will be some gotcha in FIFO mode.
*
*/
typedef enum {
RMT_DATA_MODE_FIFO, /*<! RMT memory access in FIFO mode */
RMT_DATA_MODE_MEM, /*<! RMT memory access in memory mode */
RMT_DATA_MODE_MAX,
} rmt_data_mode_t;
/**
* @brief RMT Channel Working Mode (TX or RX)
*
*/
typedef enum {
RMT_MODE_TX, /*!< RMT TX mode */
RMT_MODE_RX, /*!< RMT RX mode */
RMT_MODE_MAX
} rmt_mode_t;
/**
* @brief RMT Idle Level
*
*/
typedef enum {
RMT_IDLE_LEVEL_LOW, /*!< RMT TX idle level: low Level */
RMT_IDLE_LEVEL_HIGH, /*!< RMT TX idle level: high Level */
RMT_IDLE_LEVEL_MAX,
} rmt_idle_level_t;
/**
* @brief RMT Carrier Level
*
*/
typedef enum {
RMT_CARRIER_LEVEL_LOW, /*!< RMT carrier wave is modulated for low Level output */
RMT_CARRIER_LEVEL_HIGH, /*!< RMT carrier wave is modulated for high Level output */
RMT_CARRIER_LEVEL_MAX
} rmt_carrier_level_t;
/**
* @brief RMT Channel Status
*
*/
typedef enum {
RMT_CHANNEL_UNINIT, /*!< RMT channel uninitialized */
RMT_CHANNEL_IDLE, /*!< RMT channel status idle */
RMT_CHANNEL_BUSY, /*!< RMT channel status busy */
} rmt_channel_status_t;
/**
* @brief Data struct of RMT channel status
*/
typedef struct {
rmt_channel_status_t status[RMT_CHANNEL_MAX]; /*!< Store the current status of each channel */
} rmt_channel_status_result_t;
/**
* @brief Data struct of RMT TX configure parameters
*/
typedef struct {
uint32_t carrier_freq_hz; /*!< RMT carrier frequency */
rmt_carrier_level_t carrier_level; /*!< Level of the RMT output, when the carrier is applied */
rmt_idle_level_t idle_level; /*!< RMT idle level */
uint8_t carrier_duty_percent; /*!< RMT carrier duty (%) */
#if SOC_RMT_SUPPORT_TX_LOOP_COUNT
uint32_t loop_count; /*!< Maximum loop count */
#endif
bool carrier_en; /*!< RMT carrier enable */
bool loop_en; /*!< Enable sending RMT items in a loop */
bool idle_output_en; /*!< RMT idle level output enable */
} rmt_tx_config_t;
/**
* @brief Data struct of RMT RX configure parameters
*/
typedef struct {
uint16_t idle_threshold; /*!< RMT RX idle threshold */
uint8_t filter_ticks_thresh; /*!< RMT filter tick number */
bool filter_en; /*!< RMT receiver filter enable */
#if SOC_RMT_SUPPORT_RX_DEMODULATION
bool rm_carrier; /*!< RMT receiver remove carrier enable */
uint32_t carrier_freq_hz; /*!< RMT carrier frequency */
uint8_t carrier_duty_percent; /*!< RMT carrier duty (%) */
rmt_carrier_level_t carrier_level; /*!< The level to remove the carrier */
#endif
} rmt_rx_config_t;
/**
* @brief Data struct of RMT configure parameters
*/
typedef struct {
rmt_mode_t rmt_mode; /*!< RMT mode: transmitter or receiver */
rmt_channel_t channel; /*!< RMT channel */
gpio_num_t gpio_num; /*!< RMT GPIO number */
uint8_t clk_div; /*!< RMT channel counter divider */
uint8_t mem_block_num; /*!< RMT memory block number */
uint32_t flags; /*!< RMT channel extra configurations, OR'd with RMT_CHANNEL_FLAGS_[*] */
union {
rmt_tx_config_t tx_config; /*!< RMT TX parameter */
rmt_rx_config_t rx_config; /*!< RMT RX parameter */
};
} rmt_config_t;
/**
* @brief Default configuration for Tx channel
*
*/
#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id) \
{ \
.rmt_mode = RMT_MODE_TX, \
.channel = channel_id, \
.gpio_num = gpio, \
.clk_div = 80, \
.mem_block_num = 1, \
.flags = 0, \
.tx_config = { \
.carrier_freq_hz = 38000, \
.carrier_level = RMT_CARRIER_LEVEL_HIGH, \
.idle_level = RMT_IDLE_LEVEL_LOW, \
.carrier_duty_percent = 33, \
.carrier_en = false, \
.loop_en = false, \
.idle_output_en = true, \
} \
}
/**
* @brief Default configuration for RX channel
*
*/
#define RMT_DEFAULT_CONFIG_RX(gpio, channel_id) \
{ \
.rmt_mode = RMT_MODE_RX, \
.channel = channel_id, \
.gpio_num = gpio, \
.clk_div = 80, \
.mem_block_num = 1, \
.flags = 0, \
.rx_config = { \
.idle_threshold = 12000, \
.filter_ticks_thresh = 100, \
.filter_en = true, \
} \
}
/**
* @brief RMT interrupt handle
*
*/
typedef intr_handle_t rmt_isr_handle_t;
/**
* @brief Type of RMT Tx End callback function
*
*/
typedef void (*rmt_tx_end_fn_t)(rmt_channel_t channel, void *arg);
/**
* @brief Structure encapsulating a RMT TX end callback
*/
typedef struct {
rmt_tx_end_fn_t function; /*!< Function which is called on RMT TX end */
void *arg; /*!< Optional argument passed to function */
} rmt_tx_end_callback_t;
/**
* @brief User callback function to convert uint8_t type data to rmt format(rmt_item32_t).
*
* This function may be called from an ISR, so, the code should be short and efficient.
*
* @param src Pointer to the buffer storing the raw data that needs to be converted to rmt format.
* @param[out] dest Pointer to the buffer storing the rmt format data.
* @param src_size The raw data size.
* @param wanted_num The number of rmt format data that wanted to get.
* @param[out] translated_size The size of the raw data that has been converted to rmt format,
* it should return 0 if no data is converted in user callback.
* @param[out] item_num The number of the rmt format data that actually converted to,
* it can be less than wanted_num if there is not enough raw data, but cannot exceed wanted_num.
* it should return 0 if no data was converted.
*
* @note
* In fact, item_num should be a multiple of translated_size, e.g. :
* When we convert each byte of uint8_t type data to rmt format data,
* the relation between item_num and translated_size should be `item_num = translated_size*8`.
*/
typedef void (*sample_to_rmt_t)(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num);
/**
* @brief Set RMT clock divider, channel clock is divided from source clock.
*

View File

@ -0,0 +1,279 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "soc/soc_caps.h"
#include "soc/clk_tree_defs.h"
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RMT_CHANNEL_FLAGS_AWARE_DFS (1 << 0) /*!< Channel can work during APB clock scaling */
#define RMT_CHANNEL_FLAGS_INVERT_SIG (1 << 1) /*!< Invert RMT signal */
#define RMT_CHANNEL_FLAGS_ALWAYS_ON _Pragma ("GCC warning \"'RMT_CHANNEL_FLAGS_ALWAYS_ON' macro is deprecated\"") RMT_CHANNEL_FLAGS_AWARE_DFS
/**
* @brief Define memory space of each RMT channel (in words = 4 bytes)
*
*/
#define RMT_MEM_ITEM_NUM SOC_RMT_MEM_WORDS_PER_CHANNEL
/**
* @brief Definition of RMT item
*/
typedef struct {
union {
struct {
uint32_t duration0 : 15; /*!< Duration of level0 */
uint32_t level0 : 1; /*!< Level of the first part */
uint32_t duration1 : 15; /*!< Duration of level1 */
uint32_t level1 : 1; /*!< Level of the second part */
};
uint32_t val; /*!< Equivalent unsigned value for the RMT item */
};
} rmt_item32_t;
/**
* @brief RMT hardware memory layout
*/
typedef struct {
struct {
volatile rmt_item32_t data32[SOC_RMT_MEM_WORDS_PER_CHANNEL];
} chan[SOC_RMT_CHANNELS_PER_GROUP];
} rmt_mem_t;
/**
* @brief RMT channel ID
*
*/
typedef enum {
RMT_CHANNEL_0, /*!< RMT channel number 0 */
RMT_CHANNEL_1, /*!< RMT channel number 1 */
RMT_CHANNEL_2, /*!< RMT channel number 2 */
RMT_CHANNEL_3, /*!< RMT channel number 3 */
#if SOC_RMT_CHANNELS_PER_GROUP > 4
RMT_CHANNEL_4, /*!< RMT channel number 4 */
RMT_CHANNEL_5, /*!< RMT channel number 5 */
RMT_CHANNEL_6, /*!< RMT channel number 6 */
RMT_CHANNEL_7, /*!< RMT channel number 7 */
#endif
RMT_CHANNEL_MAX /*!< Number of RMT channels */
} rmt_channel_t;
/**
* @brief RMT Internal Memory Owner
*
*/
typedef enum {
RMT_MEM_OWNER_TX, /*!< RMT RX mode, RMT transmitter owns the memory block*/
RMT_MEM_OWNER_RX, /*!< RMT RX mode, RMT receiver owns the memory block*/
RMT_MEM_OWNER_MAX,
} rmt_mem_owner_t;
/**
* @brief Clock Source of RMT Channel
*
*/
typedef soc_periph_rmt_clk_src_legacy_t rmt_source_clk_t;
/**
* @brief RMT Data Mode
*
* @note We highly recommended to use MEM mode not FIFO mode since there will be some gotcha in FIFO mode.
*
*/
typedef enum {
RMT_DATA_MODE_FIFO, /*<! RMT memory access in FIFO mode */
RMT_DATA_MODE_MEM, /*<! RMT memory access in memory mode */
RMT_DATA_MODE_MAX,
} rmt_data_mode_t;
/**
* @brief RMT Channel Working Mode (TX or RX)
*
*/
typedef enum {
RMT_MODE_TX, /*!< RMT TX mode */
RMT_MODE_RX, /*!< RMT RX mode */
RMT_MODE_MAX
} rmt_mode_t;
/**
* @brief RMT Idle Level
*
*/
typedef enum {
RMT_IDLE_LEVEL_LOW, /*!< RMT TX idle level: low Level */
RMT_IDLE_LEVEL_HIGH, /*!< RMT TX idle level: high Level */
RMT_IDLE_LEVEL_MAX,
} rmt_idle_level_t;
/**
* @brief RMT Carrier Level
*
*/
typedef enum {
RMT_CARRIER_LEVEL_LOW, /*!< RMT carrier wave is modulated for low Level output */
RMT_CARRIER_LEVEL_HIGH, /*!< RMT carrier wave is modulated for high Level output */
RMT_CARRIER_LEVEL_MAX
} rmt_carrier_level_t;
/**
* @brief RMT Channel Status
*
*/
typedef enum {
RMT_CHANNEL_UNINIT, /*!< RMT channel uninitialized */
RMT_CHANNEL_IDLE, /*!< RMT channel status idle */
RMT_CHANNEL_BUSY, /*!< RMT channel status busy */
} rmt_channel_status_t;
/**
* @brief Data struct of RMT channel status
*/
typedef struct {
rmt_channel_status_t status[RMT_CHANNEL_MAX]; /*!< Store the current status of each channel */
} rmt_channel_status_result_t;
/**
* @brief Data struct of RMT TX configure parameters
*/
typedef struct {
uint32_t carrier_freq_hz; /*!< RMT carrier frequency */
rmt_carrier_level_t carrier_level; /*!< Level of the RMT output, when the carrier is applied */
rmt_idle_level_t idle_level; /*!< RMT idle level */
uint8_t carrier_duty_percent; /*!< RMT carrier duty (%) */
#if SOC_RMT_SUPPORT_TX_LOOP_COUNT
uint32_t loop_count; /*!< Maximum loop count */
#endif
bool carrier_en; /*!< RMT carrier enable */
bool loop_en; /*!< Enable sending RMT items in a loop */
bool idle_output_en; /*!< RMT idle level output enable */
} rmt_tx_config_t;
/**
* @brief Data struct of RMT RX configure parameters
*/
typedef struct {
uint16_t idle_threshold; /*!< RMT RX idle threshold */
uint8_t filter_ticks_thresh; /*!< RMT filter tick number */
bool filter_en; /*!< RMT receiver filter enable */
#if SOC_RMT_SUPPORT_RX_DEMODULATION
bool rm_carrier; /*!< RMT receiver remove carrier enable */
uint32_t carrier_freq_hz; /*!< RMT carrier frequency */
uint8_t carrier_duty_percent; /*!< RMT carrier duty (%) */
rmt_carrier_level_t carrier_level; /*!< The level to remove the carrier */
#endif
} rmt_rx_config_t;
/**
* @brief Data struct of RMT configure parameters
*/
typedef struct {
rmt_mode_t rmt_mode; /*!< RMT mode: transmitter or receiver */
rmt_channel_t channel; /*!< RMT channel */
gpio_num_t gpio_num; /*!< RMT GPIO number */
uint8_t clk_div; /*!< RMT channel counter divider */
uint8_t mem_block_num; /*!< RMT memory block number */
uint32_t flags; /*!< RMT channel extra configurations, OR'd with RMT_CHANNEL_FLAGS_[*] */
union {
rmt_tx_config_t tx_config; /*!< RMT TX parameter */
rmt_rx_config_t rx_config; /*!< RMT RX parameter */
};
} rmt_config_t;
/**
* @brief Default configuration for Tx channel
*
*/
#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id) \
{ \
.rmt_mode = RMT_MODE_TX, \
.channel = channel_id, \
.gpio_num = gpio, \
.clk_div = 80, \
.mem_block_num = 1, \
.flags = 0, \
.tx_config = { \
.carrier_freq_hz = 38000, \
.carrier_level = RMT_CARRIER_LEVEL_HIGH, \
.idle_level = RMT_IDLE_LEVEL_LOW, \
.carrier_duty_percent = 33, \
.carrier_en = false, \
.loop_en = false, \
.idle_output_en = true, \
} \
}
/**
* @brief Default configuration for RX channel
*
*/
#define RMT_DEFAULT_CONFIG_RX(gpio, channel_id) \
{ \
.rmt_mode = RMT_MODE_RX, \
.channel = channel_id, \
.gpio_num = gpio, \
.clk_div = 80, \
.mem_block_num = 1, \
.flags = 0, \
.rx_config = { \
.idle_threshold = 12000, \
.filter_ticks_thresh = 100, \
.filter_en = true, \
} \
}
/**
* @brief RMT interrupt handle
*
*/
typedef intr_handle_t rmt_isr_handle_t;
/**
* @brief Type of RMT Tx End callback function
*
*/
typedef void (*rmt_tx_end_fn_t)(rmt_channel_t channel, void *arg);
/**
* @brief Structure encapsulating a RMT TX end callback
*/
typedef struct {
rmt_tx_end_fn_t function; /*!< Function which is called on RMT TX end */
void *arg; /*!< Optional argument passed to function */
} rmt_tx_end_callback_t;
/**
* @brief User callback function to convert uint8_t type data to rmt format(rmt_item32_t).
*
* This function may be called from an ISR, so, the code should be short and efficient.
*
* @param src Pointer to the buffer storing the raw data that needs to be converted to rmt format.
* @param[out] dest Pointer to the buffer storing the rmt format data.
* @param src_size The raw data size.
* @param wanted_num The number of rmt format data that wanted to get.
* @param[out] translated_size The size of the raw data that has been converted to rmt format,
* it should return 0 if no data is converted in user callback.
* @param[out] item_num The number of the rmt format data that actually converted to,
* it can be less than wanted_num if there is not enough raw data, but cannot exceed wanted_num.
* it should return 0 if no data was converted.
*
* @note
* In fact, item_num should be a multiple of translated_size, e.g. :
* When we convert each byte of uint8_t type data to rmt format data,
* the relation between item_num and translated_size should be `item_num = translated_size*8`.
*/
typedef void (*sample_to_rmt_t)(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num);
#ifdef __cplusplus
}
#endif

View File

@ -13,7 +13,7 @@
#include "esp_check.h"
#include "driver/gpio.h"
#include "esp_private/periph_ctrl.h"
#include "driver/rmt.h"
#include "driver/rmt_types_legacy.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
@ -44,7 +44,7 @@
#define RMT_TRANSLATOR_UNINIT_STR "RMT translator not init"
#define RMT_PARAM_ERR_STR "RMT param error"
static const char *TAG = "rmt";
static const char *TAG = "rmt(legacy)";
// Spinlock for protecting concurrent register-level access only
#define RMT_ENTER_CRITICAL() portENTER_CRITICAL_SAFE(&(rmt_contex.rmt_spinlock))
@ -414,7 +414,7 @@ esp_err_t rmt_set_source_clk(rmt_channel_t channel, rmt_source_clk_t base_clk)
{
ESP_RETURN_ON_FALSE(channel < RMT_CHANNEL_MAX, ESP_ERR_INVALID_ARG, TAG, RMT_CHANNEL_ERROR_STR);
RMT_ENTER_CRITICAL();
// `rmt_clock_source_t` and `rmt_source_clk_t` are binary competible, as the underlying enum entries come from the same `soc_module_clk_t`
// `rmt_clock_source_t` and `rmt_source_clk_t` are binary compatible, as the underlying enum entries come from the same `soc_module_clk_t`
rmt_ll_set_group_clock_src(rmt_contex.hal.regs, channel, (rmt_clock_source_t)base_clk, 1, 0, 0);
RMT_EXIT_CRITICAL();
return ESP_OK;
@ -424,7 +424,7 @@ esp_err_t rmt_get_source_clk(rmt_channel_t channel, rmt_source_clk_t *src_clk)
{
ESP_RETURN_ON_FALSE(channel < RMT_CHANNEL_MAX, ESP_ERR_INVALID_ARG, TAG, RMT_CHANNEL_ERROR_STR);
RMT_ENTER_CRITICAL();
// `rmt_clock_source_t` and `rmt_source_clk_t` are binary competible, as the underlying enum entries come from the same `soc_module_clk_t`
// `rmt_clock_source_t` and `rmt_source_clk_t` are binary compatible, as the underlying enum entries come from the same `soc_module_clk_t`
*src_clk = (rmt_source_clk_t)rmt_ll_get_group_clock_src(rmt_contex.hal.regs, channel);
RMT_EXIT_CRITICAL();
return ESP_OK;
@ -768,16 +768,19 @@ static void IRAM_ATTR rmt_driver_isr_default(void *arg)
}
const rmt_item32_t *pdata = p_rmt->tx_data;
size_t len_rem = p_rmt->tx_len_rem;
rmt_idle_level_t idle_level = rmt_ll_tx_get_idle_level(hal->regs, channel);
rmt_item32_t stop_data = (rmt_item32_t) {
.level0 = idle_level,
.duration0 = 0,
};
if (len_rem >= p_rmt->tx_sub_len) {
rmt_fill_memory(channel, pdata, p_rmt->tx_sub_len, p_rmt->tx_offset);
p_rmt->tx_data += p_rmt->tx_sub_len;
p_rmt->tx_len_rem -= p_rmt->tx_sub_len;
} else if (len_rem == 0) {
rmt_item32_t stop_data = {0};
rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_offset);
} else {
rmt_fill_memory(channel, pdata, len_rem, p_rmt->tx_offset);
rmt_item32_t stop_data = {0};
rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_offset + len_rem);
p_rmt->tx_data += len_rem;
p_rmt->tx_len_rem -= len_rem;
@ -1112,7 +1115,11 @@ esp_err_t rmt_write_items(rmt_channel_t channel, const rmt_item32_t *rmt_item, i
p_rmt->tx_sub_len = item_sub_len;
} else {
rmt_fill_memory(channel, rmt_item, len_rem, 0);
rmt_item32_t stop_data = {0};
rmt_idle_level_t idle_level = rmt_ll_tx_get_idle_level(rmt_contex.hal.regs, channel);
rmt_item32_t stop_data = (rmt_item32_t) {
.level0 = idle_level,
.duration0 = 0,
};
rmt_fill_memory(channel, &stop_data, 1, len_rem);
p_rmt->tx_len_rem = 0;
}
@ -1251,7 +1258,11 @@ esp_err_t rmt_write_sample(rmt_channel_t channel, const uint8_t *src, size_t src
p_rmt->tx_sub_len = item_sub_len;
p_rmt->translator = true;
} else {
rmt_item32_t stop_data = {0};
rmt_idle_level_t idle_level = rmt_ll_tx_get_idle_level(rmt_contex.hal.regs, channel);
rmt_item32_t stop_data = (rmt_item32_t) {
.level0 = idle_level,
.duration0 = 0,
};
rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_len_rem);
p_rmt->tx_len_rem = 0;
p_rmt->sample_cur = NULL;
@ -1355,3 +1366,19 @@ esp_err_t rmt_enable_tx_loop_autostop(rmt_channel_t channel, bool en)
return ESP_OK;
}
#endif
/**
* @brief This function will be called during start up, to check that this legacy RMT driver is not running along with the new driver
*/
__attribute__((constructor))
static void check_rmt_legacy_driver_conflict(void)
{
// This function was declared as weak here. The new RMT driver has one implementation.
// So if the new RMT driver is not linked in, then `rmt_acquire_group_handle()` should be NULL at runtime.
extern __attribute__((weak)) void *rmt_acquire_group_handle(int group_id);
if ((void *)rmt_acquire_group_handle != NULL) {
ESP_EARLY_LOGE(TAG, "CONFLICT! driver_ng is not allowed to be used with the legacy driver");
abort();
}
ESP_EARLY_LOGW(TAG, "legacy driver is deprecated, please migrate to `driver/rmt_tx.h` and/or `driver/rmt_rx.h`");
}

View File

@ -161,7 +161,7 @@ static void check_legacy_temp_sensor_driver_conflict(void)
// This function was declared as weak here. temperature_sensor driver has one implementation.
// So if temperature_sensor driver is not linked in, then `temperature_sensor_install()` should be NULL at runtime.
extern __attribute__((weak)) esp_err_t temperature_sensor_install(const void *tsens_config, void **ret_tsens);
if (temperature_sensor_install != NULL) {
if ((void *)temperature_sensor_install != NULL) {
ESP_EARLY_LOGE(TAG, "CONFLICT! driver_ng is not allowed to be used with the legacy driver");
abort();
}

View 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

View 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

View 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

View 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

View 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

View File

@ -293,7 +293,7 @@ esp_err_t pcnt_unit_enable(pcnt_unit_handle_t unit)
esp_err_t pcnt_unit_disable(pcnt_unit_handle_t unit)
{
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(unit->fsm = PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not in enable state");
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not in enable state");
// disable interrupt service
if (unit->intr) {

View 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);
}

View 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);
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
idf_component_register(SRC_DIRS . param_test touch_sensor_test adc_dma_test dac_dma_test
PRIV_INCLUDE_DIRS include param_test/include touch_sensor_test/include
PRIV_REQUIRES cmock test_utils driver nvs_flash esp_serial_slave_link
infrared_tools esp_adc_cal esp_timer)
esp_adc_cal esp_timer)

View File

@ -0,0 +1,5 @@
# 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(legacy_rmt_test)

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
| ----------------- | ----- | -------- | -------- | -------- |

View File

@ -1,8 +1,3 @@
if(IDF_TARGET STREQUAL "esp32c2")
# ESP32-C2 does't support rmt
return()
endif()
set(component_srcs "src/ir_builder_rmt_nec.c"
"src/ir_builder_rmt_rc5.c"
"src/ir_parser_rmt_nec.c"
@ -10,6 +5,4 @@ set(component_srcs "src/ir_builder_rmt_nec.c"
idf_component_register(SRCS "${component_srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS ""
PRIV_REQUIRES "driver"
REQUIRES "")
PRIV_REQUIRES "driver")

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Timings for NEC protocol
*
*/
#define NEC_LEADING_CODE_HIGH_US (9000)
#define NEC_LEADING_CODE_LOW_US (4500)
#define NEC_PAYLOAD_ONE_HIGH_US (560)
#define NEC_PAYLOAD_ONE_LOW_US (1690)
#define NEC_PAYLOAD_ZERO_HIGH_US (560)
#define NEC_PAYLOAD_ZERO_LOW_US (560)
#define NEC_REPEAT_CODE_HIGH_US (9000)
#define NEC_REPEAT_CODE_LOW_US (2250)
#define NEC_ENDING_CODE_HIGH_US (560)
/**
* @brief Timings for RC5 protocol
*
*/
#define RC5_PULSE_DURATION_US (889)
#ifdef __cplusplus
}
#endif

View File

@ -1,16 +1,8 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.#include <stdlib.h>
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/cdefs.h>
#include "esp_log.h"
#include "ir_tools.h"

View File

@ -1,16 +1,8 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <sys/cdefs.h>
#include "esp_log.h"

View File

@ -1,16 +1,8 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <sys/cdefs.h>
#include "esp_log.h"

View File

@ -1,16 +1,8 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <sys/cdefs.h>
#include "esp_log.h"

View File

@ -0,0 +1,5 @@
set(srcs "test_app_main.c"
"test_legacy_rmt.c")
idf_component_register(SRCS ${srcs}
WHOLE_ARCHIVE)

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 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"
#define TEST_MEMORY_LEAK_THRESHOLD (-600)
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)
{
unity_run_menu();
}

View File

@ -3,7 +3,6 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
// RMT driver unit test is based on extended NEC protocol
#include <stdio.h>
#include <string.h>
@ -14,10 +13,8 @@
#include "freertos/task.h"
#include "esp_log.h"
#include "unity.h"
#include "test_utils.h"
#include "esp_rom_gpio.h"
#if SOC_RMT_SUPPORTED
#include "ir_tools.h"
#include "driver/rmt.h"
@ -32,6 +29,7 @@
#define RMT_TESTBENCH_FLAGS_LOOP_ON (1<<2)
static const char *TAG = "RMT.test";
static ir_builder_t *s_ir_builder = NULL;
static ir_parser_t *s_ir_parser = NULL;
@ -598,5 +596,3 @@ TEST_CASE("RMT TX loop", "[rmt]")
rmt_clean_testbench(tx_channel, rx_channel);
}
#endif
#endif // SOC_RMT_SUPPORTED

View File

@ -0,0 +1,19 @@
# 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', [
'release',
], indirect=True)
def test_legacy_rmt(dut: Dut) -> None:
dut.expect('Press ENTER to see the list of tests')
dut.write('*')
dut.expect_unity_test_output(timeout=120)

View 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

View File

@ -0,0 +1,3 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT=n
CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y

View 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()

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
| ----------------- | ----- | -------- | -------- | -------- |

View 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)

View 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();
}

View 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
}

View 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

View 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(&copy_encoder_config, &copy_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

View 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(&copy_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(&copy_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;
}

View File

@ -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

View 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()

View 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

View 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

View File

@ -0,0 +1,2 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT=n

View File

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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

@ -0,0 +1,24 @@
blockdiag rmt_encoder_chain {
orientation = portrait;
default_fontsize = 14;
node_width = 160;
node_height = 40;
span_width = 80;
span_height = 40;
new [label = "New", shape = "beginpoint"];
init [label = "Init"];
encoder_a [label = "Encoder A"];
encoder_b [label = "Encoder B"];
other_encoder [shape = "dots"];
yield [label = "Yield", shape = "endpoint"];
new -> init [folded];
init -> encoder_a [thick];
encoder_a <-> yield [folded, thick, color = "green", label = "full"];
encoder_a -> encoder_b [thick];
encoder_b <-> yield [color = "green", thick, label = "full"];
encoder_b -> other_encoder [style = "dotted"];
encoder_a, encoder_b -> init [thick, color = red, label = "reset"];
other_encoder -> init [thick, color = red, label = "finish"];
}

45
docs/_static/diagrams/rmt/rmt_rx.diag vendored Normal file
View File

@ -0,0 +1,45 @@
blockdiag rmt_rx {
node_width = 80;
node_height = 60;
default_group_color = lightgrey;
o -> p -> a;
q -> r;
r -- s;
s -> p [folded];
a -> b [label=GPIO];
b -> c -> d;
e -- f;
f -> b [folded];
o [style=none, label="", background="../../../_static/rmt-waveform-modulated.png"]
p [shape=endpoint, label="demod"]
q [label="Remove\nCarrier"]
r [style=none, label="", background="../../../_static/rmt-carrier.png"]
s [shape=none, label=""]
a [style=none, label="", background="../../../_static/rmt-waveform.png"]
b [label=Filter]
c [label="Edge\nDetect"]
d [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."]
e [style=none, width=60, height=40, label="Enable\nFilter"]
f [shape=none, label=""]
group {
label = "Optional"
q,r,o,p;
}
group {
label = "Input"
a,e;
}
group {
label = "RMT Receiver"
b,c;
}
group {
label = "Output"
d;
}
}

View File

@ -0,0 +1,16 @@
packetdiag rmt_symbols {
colwidth = 32
node_width = 16
node_height = 24
default_fontsize = 12
0-14: Period (15)
15: L
16-30: Period (15)
31: L
32-95: ... [colheight=2]
96-110: Period (15)
111: L
112-126: Period (15)
127: L
}

33
docs/_static/diagrams/rmt/rmt_tx.diag vendored Normal file
View File

@ -0,0 +1,33 @@
blockdiag rmt_tx {
node_width = 80;
node_height = 60;
default_group_color = lightgrey;
a -> b -> c -> d;
e -> f -> g -- h;
d -> o [label=GPIO];
h -> d [folded];
a [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."]
b [label="Waveform\nGenerator"]
c [style=none, label="", background="../../../_static/rmt-waveform.png"]
d [shape=beginpoint, label="mod"]
e [style=none, width=60, height=40, label="Enable\nCarrier"]
f [label="Carrier\nGenerator"]
g [style=none, label="", background="../../../_static/rmt-carrier.png"]
h [shape=none]
o [style=none, label="", background="../../../_static/rmt-waveform-modulated.png"]
group {
label = "Input"
a,e;
}
group {
label = "RMT Transmitter"
b,f,c,g,d,h;
}
group {
label = "Output"
o;
}
}

BIN
docs/_static/ir_nec.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/_static/rmt_tx_sync.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -67,7 +67,6 @@ INPUT = \
$(PROJECT_PATH)/components/driver/include/driver/i2c.h \
$(PROJECT_PATH)/components/driver/include/driver/i2s.h \
$(PROJECT_PATH)/components/driver/include/driver/ledc.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt.h \
$(PROJECT_PATH)/components/driver/include/driver/rtc_io.h \
$(PROJECT_PATH)/components/driver/include/driver/sdio_slave.h \
$(PROJECT_PATH)/components/driver/include/driver/sdmmc_host.h \
@ -76,8 +75,8 @@ INPUT = \
$(PROJECT_PATH)/components/driver/include/driver/sigmadelta.h \
$(PROJECT_PATH)/components/driver/include/driver/spi_common.h \
$(PROJECT_PATH)/components/driver/include/driver/spi_master.h \
$(PROJECT_PATH)/components/driver/include/driver/spi_slave_hd.h \
$(PROJECT_PATH)/components/driver/include/driver/spi_slave.h \
$(PROJECT_PATH)/components/driver/include/driver/spi_slave_hd.h \
$(PROJECT_PATH)/components/driver/include/driver/touch_sensor_common.h \
$(PROJECT_PATH)/components/driver/include/driver/twai.h \
$(PROJECT_PATH)/components/driver/include/driver/uart.h \
@ -87,13 +86,13 @@ INPUT = \
$(PROJECT_PATH)/components/esp_common/include/esp_check.h \
$(PROJECT_PATH)/components/esp_common/include/esp_err.h \
$(PROJECT_PATH)/components/esp_common/include/esp_idf_version.h \
$(PROJECT_PATH)/components/esp_eth/include/esp_eth.h \
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_com.h \
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_mac.h \
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_netif_glue.h \
$(PROJECT_PATH)/components/esp_eth/include/esp_eth_phy.h \
$(PROJECT_PATH)/components/esp_eth/include/esp_eth.h \
$(PROJECT_PATH)/components/esp_event/include/esp_event_base.h \
$(PROJECT_PATH)/components/esp_event/include/esp_event.h \
$(PROJECT_PATH)/components/esp_event/include/esp_event_base.h \
$(PROJECT_PATH)/components/esp_http_client/include/esp_http_client.h \
$(PROJECT_PATH)/components/esp_http_server/include/esp_http_server.h \
$(PROJECT_PATH)/components/esp_https_ota/include/esp_https_ota.h \
@ -112,19 +111,19 @@ INPUT = \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_panel_vendor.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_types.h \
$(PROJECT_PATH)/components/esp_local_ctrl/include/esp_local_ctrl.h \
$(PROJECT_PATH)/components/esp_netif/include/esp_netif.h \
$(PROJECT_PATH)/components/esp_netif/include/esp_netif_ip_addr.h \
$(PROJECT_PATH)/components/esp_netif/include/esp_netif_net_stack.h \
$(PROJECT_PATH)/components/esp_netif/include/esp_netif_types.h \
$(PROJECT_PATH)/components/esp_netif/include/esp_netif.h \
$(PROJECT_PATH)/components/esp_netif/include/esp_vfs_l2tap.h \
$(PROJECT_PATH)/components/esp_phy/include/esp_phy_init.h \
$(PROJECT_PATH)/components/esp_pm/include/$(IDF_TARGET)/pm.h \
$(PROJECT_PATH)/components/esp_pm/include/esp_pm.h \
$(PROJECT_PATH)/components/esp_ringbuf/include/freertos/ringbuf.h \
$(PROJECT_PATH)/components/esp_rom/include/esp_rom_sys.h \
$(PROJECT_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h \
$(PROJECT_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio.h \
$(PROJECT_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h \
$(PROJECT_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h \
$(PROJECT_PATH)/components/esp_system/include/esp_expression_with_stack.h \
$(PROJECT_PATH)/components/esp_system/include/esp_freertos_hooks.h \
$(PROJECT_PATH)/components/esp_system/include/esp_int_wdt.h \
@ -134,9 +133,9 @@ INPUT = \
$(PROJECT_PATH)/components/esp_wifi/include/esp_mesh.h \
$(PROJECT_PATH)/components/esp_wifi/include/esp_now.h \
$(PROJECT_PATH)/components/esp_wifi/include/esp_smartconfig.h \
$(PROJECT_PATH)/components/esp_wifi/include/esp_wifi.h \
$(PROJECT_PATH)/components/esp_wifi/include/esp_wifi_default.h \
$(PROJECT_PATH)/components/esp_wifi/include/esp_wifi_types.h \
$(PROJECT_PATH)/components/esp_wifi/include/esp_wifi.h \
$(PROJECT_PATH)/components/esp-tls/esp_tls_errors.h \
$(PROJECT_PATH)/components/esp-tls/esp_tls.h \
$(PROJECT_PATH)/components/fatfs/diskio/diskio_impl.h \
@ -159,7 +158,6 @@ INPUT = \
$(PROJECT_PATH)/components/hal/include/hal/i2s_types.h \
$(PROJECT_PATH)/components/hal/include/hal/lcd_types.h \
$(PROJECT_PATH)/components/hal/include/hal/ledc_types.h \
$(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \
$(PROJECT_PATH)/components/hal/include/hal/rtc_io_types.h \
$(PROJECT_PATH)/components/hal/include/hal/sdio_slave_types.h \
$(PROJECT_PATH)/components/hal/include/hal/sigmadelta_types.h \
@ -169,25 +167,25 @@ INPUT = \
$(PROJECT_PATH)/components/hal/include/hal/touch_sensor_types.h \
$(PROJECT_PATH)/components/hal/include/hal/twai_types.h \
$(PROJECT_PATH)/components/hal/include/hal/uart_types.h \
$(PROJECT_PATH)/components/heap/include/esp_heap_caps_init.h \
$(PROJECT_PATH)/components/heap/include/esp_heap_caps.h \
$(PROJECT_PATH)/components/heap/include/esp_heap_caps_init.h \
$(PROJECT_PATH)/components/heap/include/esp_heap_trace.h \
$(PROJECT_PATH)/components/heap/include/multi_heap.h \
$(PROJECT_PATH)/components/ieee802154/include/esp_ieee802154_types.h \
$(PROJECT_PATH)/components/ieee802154/include/esp_ieee802154.h \
$(PROJECT_PATH)/components/ieee802154/include/esp_ieee802154_types.h \
$(PROJECT_PATH)/components/log/include/esp_log.h \
$(PROJECT_PATH)/components/lwip/include/apps/esp_sntp.h \
$(PROJECT_PATH)/components/lwip/include/apps/ping/ping_sock.h \
$(PROJECT_PATH)/components/mbedtls/esp_crt_bundle/include/esp_crt_bundle.h \
$(PROJECT_PATH)/components/mdns/include/mdns.h \
$(PROJECT_PATH)/components/mqtt/esp-mqtt/include/mqtt_client.h \
$(PROJECT_PATH)/components/nvs_flash/include/nvs_flash.h \
$(PROJECT_PATH)/components/nvs_flash/include/nvs.h \
$(PROJECT_PATH)/components/nvs_flash/include/nvs_flash.h \
$(PROJECT_PATH)/components/openthread/include/esp_openthread.h \
$(PROJECT_PATH)/components/openthread/include/esp_openthread_border_router.h \
$(PROJECT_PATH)/components/openthread/include/esp_openthread_lock.h \
$(PROJECT_PATH)/components/openthread/include/esp_openthread_netif_glue.h \
$(PROJECT_PATH)/components/openthread/include/esp_openthread_types.h \
$(PROJECT_PATH)/components/openthread/include/esp_openthread.h \
$(PROJECT_PATH)/components/perfmon/include/xtensa_perfmon_access.h \
$(PROJECT_PATH)/components/perfmon/include/xtensa_perfmon_apis.h \
$(PROJECT_PATH)/components/perfmon/include/xtensa_perfmon_masks.h \
@ -204,13 +202,13 @@ INPUT = \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/clk_tree_defs.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/soc_caps.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/uart_channel.h \
$(PROJECT_PATH)/components/spi_flash/include/esp_flash_spi_init.h \
$(PROJECT_PATH)/components/spi_flash/include/esp_flash.h \
$(PROJECT_PATH)/components/spi_flash/include/esp_flash_spi_init.h \
$(PROJECT_PATH)/components/spi_flash/include/esp_partition.h \
$(PROJECT_PATH)/components/spi_flash/include/esp_spi_flash.h \
$(PROJECT_PATH)/components/spiffs/include/esp_spiffs.h \
$(PROJECT_PATH)/components/tinyusb/additions/include/tinyusb_types.h \
$(PROJECT_PATH)/components/tinyusb/additions/include/tinyusb.h \
$(PROJECT_PATH)/components/tinyusb/additions/include/tinyusb_types.h \
$(PROJECT_PATH)/components/tinyusb/additions/include/tusb_cdc_acm.h \
$(PROJECT_PATH)/components/tinyusb/additions/include/tusb_config.h \
$(PROJECT_PATH)/components/tinyusb/additions/include/tusb_console.h \
@ -219,10 +217,10 @@ INPUT = \
$(PROJECT_PATH)/components/ulp/ulp_common/include/ulp_common.h \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/ulp_fsm_common.h \
$(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv.h \
$(PROJECT_PATH)/components/vfs/include/esp_vfs.h \
$(PROJECT_PATH)/components/vfs/include/esp_vfs_dev.h \
$(PROJECT_PATH)/components/vfs/include/esp_vfs_eventfd.h \
$(PROJECT_PATH)/components/vfs/include/esp_vfs_semihost.h \
$(PROJECT_PATH)/components/vfs/include/esp_vfs.h \
$(PROJECT_PATH)/components/wear_levelling/include/wear_levelling.h \
$(PROJECT_PATH)/components/wifi_provisioning/include/wifi_provisioning/manager.h \
$(PROJECT_PATH)/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h \

View File

@ -1,15 +1,21 @@
INPUT += \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/dac_channel.h \
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/dac.h \
$(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_rx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_tx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_types.h \
$(PROJECT_PATH)/components/esp_hw_support/include/esp_himem.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h \
$(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
$(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/dac_channel.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/rtc_io_channel.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
$(PROJECT_PATH)/components/esp_hw_support/include/esp_himem.h \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \
$(PROJECT_PATH)/components/ulp/ulp_common/include/$(IDF_TARGET)/ulp_common_defs.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \

View File

@ -1,4 +1,10 @@
INPUT += \
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_rx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_tx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/temperature_sensor.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32c3/esp_ds.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32c3/esp_hmac.h
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32c3/esp_hmac.h \
$(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \

View File

@ -1,4 +1,10 @@
INPUT += \
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_rx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_tx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/temperature_sensor.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32h2/esp_ds.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32h2/esp_hmac.h
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32h2/esp_hmac.h \
$(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \

View File

@ -1,21 +1,27 @@
INPUT += \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/dac_channel.h \
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/dac.h \
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/rtc_io_channel.h \
$(PROJECT_PATH)/components/driver/include/driver/temperature_sensor.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_rx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_tx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/temperature_sensor.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32s2/esp_ds.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32s2/esp_hmac.h \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \
$(PROJECT_PATH)/components/ulp/ulp_common/include/$(IDF_TARGET)/ulp_common_defs.h \
$(PROJECT_PATH)/components/touch_element/include/touch_element/touch_element.h \
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
$(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/dac_channel.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/rtc_io_channel.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
$(PROJECT_PATH)/components/touch_element/include/touch_element/touch_button.h \
$(PROJECT_PATH)/components/touch_element/include/touch_element/touch_slider.h \
$(PROJECT_PATH)/components/touch_element/include/touch_element/touch_element.h \
$(PROJECT_PATH)/components/touch_element/include/touch_element/touch_matrix.h \
$(PROJECT_PATH)/components/touch_element/include/touch_element/touch_slider.h \
$(PROJECT_PATH)/components/ulp/ulp_common/include/$(IDF_TARGET)/ulp_common_defs.h \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_host.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_types_ch9.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_types_stack.h
$(PROJECT_PATH)/components/usb/include/usb/usb_types_stack.h \

View File

@ -1,18 +1,24 @@
INPUT += \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/$(IDF_TARGET)/esp_hmac.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/$(IDF_TARGET)/esp_ds.h \
$(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
$(PROJECT_PATH)/components/driver/include/driver/temperature_sensor.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_rx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_tx.h \
$(PROJECT_PATH)/components/driver/include/driver/rmt_types.h \
$(PROJECT_PATH)/components/driver/include/driver/temperature_sensor.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/$(IDF_TARGET)/esp_ds.h \
$(PROJECT_PATH)/components/esp_hw_support/include/soc/$(IDF_TARGET)/esp_hmac.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h \
$(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
$(PROJECT_PATH)/components/hal/include/hal/rmt_types.h \
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
$(PROJECT_PATH)/components/ulp/ulp_common/include/$(IDF_TARGET)/ulp_common_defs.h \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_host.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_types_ch9.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_types_stack.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc.h \
$(PROJECT_PATH)/components/esp_system/include/esp_ipc_isr.h

View File

@ -1,324 +1,536 @@
Remote Control (RMT)
====================
Remote Control Transceiver (RMT)
================================
The RMT (Remote Control) module driver can be used to send and receive infrared remote control signals. Due to flexibility of RMT module, the driver can also be used to generate or receive many other types of signals.
Introduction
------------
The signal, which consists of a series of pulses, is generated by RMT's transmitter based on a list of values. The values define the pulse duration and a binary level, see below. The transmitter can also provide a carrier and modulate it with provided pulses.
The RMT (Remote Control Transceiver) peripheral was designed to act as an infrared transceiver. However, due to the flexibility of its data format, the functionality of RMT can be extended to a versatile and general purpose transceiver. From the perspective of network layering, the RMT hardware contains both physical and data link layer. The physical layer defines the communication media and bit signal representation. The data link layer defines the format of an RMT frame. The minimal data unit in the frame is called **RMT symbol**, which is represented by :cpp:type:`rmt_symbol_word_t` in the driver.
.. blockdiag::
:scale: 100
{IDF_TARGET_NAME} contains multiple channels in the RMT peripheral. [1]_ Each channel can be configured as either transmitter or receiver, independently.
Typically, the RMT peripheral can be used in the following scenarios:
- Transmit or receive infrared signals, with any IR protocols, e.g. NEC
- General purpose sequence generator
- Transmit signals in a hardware controlled loop, with finite or infinite number of times
- Multi-channel simultaneous transmission
- Modulate the carrier to the output signal or demodulate the carrier from the input signal
Layout of RMT Symbols
^^^^^^^^^^^^^^^^^^^^^
The RMT hardware defines data in its own pattern -- the **RMT symbol**. Each symbol consists of two pairs of two values. The first value in a pair describes the signal duration in RMT ticks and is 15 bits long. The second provides the signal level (high or low) and is contained in a single bit, as shown below:
.. packetdiag:: /../_static/diagrams/rmt/rmt_symbols.diag
:caption: Structure of RMT symbols (L - signal level)
:align: center
RMT Transmitter Overview
^^^^^^^^^^^^^^^^^^^^^^^^
The data path and control path of an RMT TX channel is illustrated in the figure below:
.. blockdiag:: /../_static/diagrams/rmt/rmt_tx.diag
:caption: RMT Transmitter Overview
:align: center
blockdiag rmt_tx {
The driver will encode user's data into RMT data format, then the RMT transmitter can generate the waveforms according to the encoding artifacts. It is also possible to modulate a high frequency carrier signal before being routed to a GPIO pad.
node_width = 80;
node_height = 60;
default_group_color = lightgrey;
RMT Receiver Overview
^^^^^^^^^^^^^^^^^^^^^
a -> b -> c -> d;
e -> f -> g -- h;
d -> o [label=GPIO];
h -> d [folded];
The data path and control path of an RMT RX channel is illustrated in the figure below:
a [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."]
b [label="Waveform\nGenerator"]
c [style=none, label="", background="../../../_static/rmt-waveform.png"]
d [shape=beginpoint, label="mod"]
e [style=none, width=60, height=40, label="Carrier\nenable"]
f [label="Carrier\nGenerator"]
g [style=none, label="", background="../../../_static/rmt-carrier.png"]
h [shape=none]
o [style=none, label="", background="../../../_static/rmt-waveform-modulated.png"]
group {
label = Input
a,e;
}
group {
label = "RMT Transmitter"
b,f,c,g,d,h;
}
group {
label = Output
o;
}
}
The reverse operation is performed by the receiver, where a series of pulses is decoded into a list of values containing the pulse duration and binary level. A filter may be applied to remove high frequency noise from the input signal.
.. blockdiag::
:scale: 90
.. blockdiag:: /../_static/diagrams/rmt/rmt_rx.diag
:caption: RMT Receiver Overview
:align: center
blockdiag rmt_rx {
The RMT receiver can sample incoming signals into RMT data format, and store the data in memory. It's feasible to tell the receiver the basic characteristics of the incoming signal, so that the signal's stop condition can be recognized, and signal glitches and noise can be filtered out. The RMT peripheral also supports demodulating the high frequency carrier from the base signal.
node_width = 80;
node_height = 60;
default_group_color = lightgrey;
Functional Overview
-------------------
a -> b [label=GPIO];
b -> c -> d;
e -- f;
f -> b [folded];
Description of the RMT functionality is divided into the following sections:
a [style=none, label="", background="../../../_static/rmt-waveform.png"]
b [label=Filter]
c [label="Edge\nDetect"]
d [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."]
e [style=none, width=60, height=40, label="Filter\nenable"]
f [shape=none, label=""]
- `Resource Allocation <#resource-allocation>`__ - covers how to allocate RMT channels with properly set of configurations. It also covers how to recycle the resources when they finished working.
- `Carrier Modulation and Demodulation <#carrier-modulation-and-demodulation>`__ - describes how to modulate carrier for TX channel and demodulate carrier for RX channel.
- `Register Event Callbacks <#register-event-callbacks>`__ - covers how to hook user specific code to RMT channel specific events.
- `Enable and Disable channel <#enable-and-disable-channel>`__ - shows how to enable and disable the RMT channel.
- `Initiate TX Transaction <#initiate-tx-transaction>`__ - describes the steps to initiate a transaction for TX channel.
- `Initiate RX Transaction <#initiate-rx-transaction>`__ - describes the steps to initiate a transaction for RX channel.
- `Multiple Channels Simultaneous Transmission <#multiple-channels-simultaneous-transmission>`__ - describes how to collect multiple channels into a sync group and start transaction at the same time.
- `RMT Encoder <#rmt-encoder>`__ - focuses on how to write a customized encoder in a combination way, with the help of the primitive encoders provided by the driver.
- `Power Management <#power-management>`__ - describes how different source clock will affect power consumption.
- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the RMT interrupt work better along with a disabled cache.
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can bring different effects to the driver.
group {
label = Input
a,e;
}
group {
label = "RMT Receiver"
b,c;
}
group {
label = Output
d;
}
}
Resource Allocation
^^^^^^^^^^^^^^^^^^^
There couple of typical steps to setup and operate the RMT and they are discussed in the following sections:
Both RMT TX and RX channels are represented by :cpp:type:`rmt_channel_handle_t` in the driver. The available channels are managed in a resource pool, which will hand out a free channel on request.
1. `Configure Driver`_
2. `Transmit Data`_ or `Receive Data`_
3. `Change Operation Parameters`_
4. `Use Interrupts`_
Install RMT TX Channel
~~~~~~~~~~~~~~~~~~~~~~
.. only:: esp32
To install an RMT TX channel, there's a configuration structure that needs to be given in advance: :cpp:type:`rmt_tx_channel_config_t`:
The RMT has eight channels numbered from zero to seven. Each channel is able to independently transmit or receive data. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`.
- :cpp:member:`rmt_tx_channel_config_t::gpio_num` sets the GPIO number used by the transmitter.
- :cpp:member:`rmt_tx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock will also be used by other channels, which means user should ensure this configuration is same when allocating other channels, regardless of TX or RX. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section.
- :cpp:member:`rmt_tx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` sets the size of the dedicated memory block or DMA buffer that is used to store RMT encoding artifacts.
- :cpp:member:`rmt_tx_channel_config_t::trans_queue_depth` sets the depth of internal transaction queue, the deeper the queue, the more transactions can be prepared in the backlog.
- :cpp:member:`rmt_tx_channel_config_t::invert_out` is used to decide whether to invert the RMT signal before sending it to the GPIO pad.
- :cpp:member:`rmt_tx_channel_config_t::with_dma` is used to indicate if the channel needs a DMA backend. A channel with DMA attached can offload the CPU by a lot. However, DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_tx_channel_config_t::io_loop_back` is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. Meanwhile, if both TX and RX channels are bound to the same GPIO, then monitoring of the data transmission line can be realized.
.. only:: esp32s2
Once the :cpp:type:`rmt_tx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_tx_channel` to allocate and initialize a TX channel. This function will return an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g. DMA backend) is not supported by hardware, it will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
The RMT has four channels numbered from zero to three. Each channel is able to independently transmit or receive data. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`.
.. code:: c
.. only:: esp32c3
rmt_channel_handle_t tx_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = 0, // GPIO number
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
.resolution_hz = 1 * 1000 * 1000, // 1MHz tick resolution, i.e. 1 tick = 1us
.trans_queue_depth = 4, // set the number of transactions that can pend in the background
.flags.invert_out = false, // don't invert output signal
.flags.with_dma = false, // don't need DMA backend
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan));
The RMT has four channels numbered from zero to three. The first half (i.e. Channel 0 ~ 1) channels can only be configured for transmitting, and the other half (i.e. Channel 2 ~ 3) channels can only be configured for receiving. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`.
Install RMT RX Channel
~~~~~~~~~~~~~~~~~~~~~~
.. only:: esp32s3
To install an RMT RX channel, there's a configuration structure that needs to be given in advance: :cpp:type:`rmt_rx_channel_config_t`:
The RMT has eight channels numbered from zero to seven. The first half (i.e. Channel 0 ~ 3) channels can only be configured for transmitting, and the other half (i.e. Channel 4 ~ 7) channels can only be configured for receiving. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`.
- :cpp:member:`rmt_rx_channel_config_t::gpio_num` sets the GPIO number used by the receiver.
- :cpp:member:`rmt_rx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock will also be used by other channels, which means user should ensure this configuration is same when allocating other channels, regardless of TX or RX. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section.
- :cpp:member:`rmt_rx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols` sets the size of the dedicated memory block or DMA buffer that used to store RMT encoding artifacts.
- :cpp:member:`rmt_rx_channel_config_t::invert_in` is used to decide whether to invert the input signals before they going into RMT receiver. The inversion is done by GPIO matrix instead of by the RMT peripheral.
- :cpp:member:`rmt_rx_channel_config_t::with_dma` is used to indicate if the channel needs a DMA backend. A channel with DMA attached can offload the CPU by a lot. However, DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_rx_channel_config_t::io_loop_back` is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. Meanwhile, if both TX and RX channels are bound to the same GPIO, then monitoring of the data transmission line can be realized.
Configure Driver
----------------
Once the :cpp:type:`rmt_rx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_rx_channel` to allocate and initialize a RX channel. This function will return an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g. DMA backend) is not supported by hardware, it will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
There are several parameters that define how particular channel operates. Most of these parameters are configured by setting specific members of :cpp:type:`rmt_config_t` structure. Some of the parameters are common to both transmit or receive mode, and some are mode specific. They are all discussed below.
.. code:: c
rmt_channel_handle_t rx_chan = NULL;
rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.resolution_hz = 1 * 1000 * 1000, // 1MHz tick resolution, i.e. 1 tick = 1us
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
.gpio_num = 2, // GPIO number
.flags.invert_in = false, // don't invert input signal
.flags.with_dma = false, // don't need DMA backend
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan));
Common Parameters
^^^^^^^^^^^^^^^^^
Uninstall RMT Channel
~~~~~~~~~~~~~~~~~~~~~
* The **channel** to be configured, select one from the :cpp:type:`rmt_channel_t` enumerator.
* The RMT **operation mode** - whether this channel is used to transmit or receive data, selected by setting a **rmt_mode** members to one of the values from :cpp:type:`rmt_mode_t`.
* What is the **pin number** to transmit or receive RMT signals, selected by setting **gpio_num**.
* How many **memory blocks** will be used by the channel, set with **mem_block_num**.
* Extra miscellaneous parameters for the channel can be set in the **flags**.
If a previously installed RMT channel is no longer needed, it's recommended to recycle the resources by calling :cpp:func:`rmt_del_channel`, which in return allows the underlying hardware to be usable for other purposes.
* When **RMT_CHANNEL_FLAGS_AWARE_DFS** is set, RMT channel will take REF_TICK or XTAL as source clock. The benefit is, RMT channel can continue work even when APB clock is changing. See :doc:`power_management <../system/power_management>` for more information.
* When **RMT_CHANNEL_FLAGS_INVERT_SIG** is set, the driver will invert the RMT signal sending to or receiving from the channel. It just works like an external inverter connected to the GPIO of certain RMT channel.
Carrier Modulation and Demodulation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* A **clock divider**, that will determine the range of pulse length generated by the RMT transmitter or discriminated by the receiver. Selected by setting **clk_div** to a value within [1 .. 255] range. The RMT source clock is typically APB CLK, 80Mhz by default. But when **RMT_CHANNEL_FLAGS_AWARE_DFS** is set in **flags**, RMT source clock is changed to REF_TICK or XTAL.
The RMT transmitter can generate a carrier wave and modulate it onto the base signal. Compared to the base signal, the carrier frequency is usually high. In addition, user can only set the frequency and duty cycle for the carrier. The RMT receiver can demodulate the carrier from the incoming signal. Note that, carrier modulation and demodulation is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before configuring the carrier, or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
Carrier related configurations lie in :cpp:type:`rmt_carrier_config_t`:
- :cpp:member:`rmt_carrier_config_t::frequency_hz` sets the carrier frequency, in Hz.
- :cpp:member:`rmt_carrier_config_t::duty_cycle` sets the carrier duty cycle.
- :cpp:member:`rmt_carrier_config_t::polarity_active_low` sets the carrier polarity, i.e. on which level the carrier is applied.
- :cpp:member:`rmt_carrier_config_t::always_on` sets whether to output the carrier even when the data transmission has finished. This configuration is only valid for TX channel.
.. note::
The period of a square wave after the clock divider is called a 'tick'. The length of the pulses generated by the RMT transmitter or discriminated by the receiver is configured in number of 'ticks'.
For RX channel, we shouldn't set the carrier frequency exactly to the theoretical value. It's recommended to leave a tolerance for the carrier frequency. For example, in the snippet below, we set the frequency to 25KHz, instead of the 38KHz that configured on the TX side. The reason is that reflection and refraction will occur when a signal travels through the air, leading to the a distortion on the receiver side.
There are also couple of specific parameters that should be set up depending if selected channel is configured in `Transmit Mode`_ or `Receive Mode`_:
.. code:: c
rmt_carrier_config_t tx_carrier_cfg = {
.duty_cycle = 0.33, // duty cycle 33%
.frequency_hz = 38000, // 38KHz
.flags.polarity_active_low = false, // carrier should modulated to high level
};
// modulate carrier to TX channel
ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan, &tx_carrier_cfg));
Transmit Mode
^^^^^^^^^^^^^
rmt_carrier_config_t rx_carrier_cfg = {
.duty_cycle = 0.33, // duty cycle 33%
.frequency_hz = 25000, // 25KHz carrier, should be smaller than transmitter's carrier frequency
.flags.polarity_active_low = false, // the carrier is modulated to high level
};
// demodulate carrier from RX channel
ESP_ERROR_CHECK(rmt_apply_carrier(rx_chan, &rx_carrier_cfg));
When configuring channel in transmit mode, set **tx_config** and the following members of :cpp:type:`rmt_tx_config_t`:
.. list::
* Transmit the currently configured data items in a loop - **loop_en**
* Enable the RMT carrier signal - **carrier_en**
* Frequency of the carrier in Hz - **carrier_freq_hz**
* Duty cycle of the carrier signal in percent (%) - **carrier_duty_percent**
* Level of the RMT output, when the carrier is applied - **carrier_level**
* Enable the RMT output if idle - **idle_output_en**
* Set the signal level on the RMT output if idle - **idle_level**
:SOC_RMT_SUPPORT_TX_LOOP_COUNT: * Specify maximum number of transmissions in a loop - **loop_count**
Receive Mode
^^^^^^^^^^^^
In receive mode, set **rx_config** and the following members of :cpp:type:`rmt_rx_config_t`:
.. list::
* Enable a filter on the input of the RMT receiver - **filter_en**
* A threshold of the filter, set in the number of ticks - **filter_ticks_thresh**. Pulses shorter than this setting will be filtered out. Note, that the range of entered tick values is [0..255].
* A pulse length threshold that will turn the RMT receiver idle, set in number of ticks - **idle_threshold**. The receiver will ignore pulses longer than this setting.
:SOC_RMT_SUPPORT_RX_DEMODULATION: * Enable the RMT carrier demodulation - **carrier_rm**
:SOC_RMT_SUPPORT_RX_DEMODULATION: * Frequency of the carrier in Hz - **carrier_freq_hz**
:SOC_RMT_SUPPORT_RX_DEMODULATION: * Duty cycle of the carrier signal in percent (%) - **carrier_duty_percent**
:SOC_RMT_SUPPORT_RX_DEMODULATION: * Level of the RMT input, where the carrier is modulated to - **carrier_level**
Finalize Configuration
^^^^^^^^^^^^^^^^^^^^^^
Once the :cpp:type:`rmt_config_t` structure is populated with parameters, it should be then invoked with :cpp:func:`rmt_config` to make the configuration effective.
The last configuration step is installation of the driver in memory by calling :cpp:func:`rmt_driver_install`. If `rx_buf_size` parameter of this function is > 0, then a ring buffer for incoming data will be allocated. A default ISR handler will be installed, see a note in `Use Interrupts`_.
Now, depending on how the channel is configured, we are ready to either `Transmit Data`_ or `Receive Data`_. This is described in next two sections.
Transmit Data
-------------
Before being able to transmit some RMT pulses, we need to define the pulse pattern. The minimum pattern recognized by the RMT controller, later called an 'item', is provided in a structure :cpp:type:`rmt_item32_t`. Each item consists of two pairs of two values. The first value in a pair describes the signal duration in ticks and is 15 bits long, the second provides the signal level (high or low) and is contained in a single bit. A block of couple of items and the structure of an item is presented below.
.. packetdiag::
:caption: Structure of RMT items (L - signal level)
:align: center
packetdiag rmt_items {
colwidth = 32
node_width = 10
node_height = 24
default_fontsize = 12
0-14: Period (15)
15: L
16-30: Period (15)
31: L
32-95: ... [colheight=2]
96-110: Period (15)
111: L
112-126: Period (15)
127: L
}
For a simple example how to define a block of items see :example:`peripherals/rmt/morse_code`.
The items are provided to the RMT controller by calling function :cpp:func:`rmt_write_items`. This function also automatically triggers start of transmission. It may be called to wait for transmission completion or exit just after transmission start. In such case you can wait for the transmission end by calling :cpp:func:`rmt_wait_tx_done`. This function does not limit the number of data items to transmit. It is using an interrupt to successively copy the new data chunks to RMT's internal memory as previously provided data are sent out.
Another way to provide data for transmission is by calling :cpp:func:`rmt_fill_tx_items`. In this case transmission is not started automatically. To control the transmission process use :cpp:func:`rmt_tx_start` and :cpp:func:`rmt_tx_stop`. The number of items to sent is restricted by the size of memory blocks allocated in the RMT controller's internal memory, see :cpp:func:`rmt_set_mem_block_num`.
Receive Data
------------
.. only:: esp32
.. warning::
RMT RX channel can't receive packet whose items are larger than its memory block size. If you set the memory block number to 1, then this RX channel can't receive packet with more than 64 items. This is a hardware limitation.
.. only:: esp32
Before starting the receiver we need some storage for incoming items. The RMT controller has 512 x 32-bits of internal RAM shared between all eight channels.
.. only:: esp32s2
Before starting the receiver we need some storage for incoming items. The RMT controller has 256 x 32-bits of internal RAM shared between all four channels.
.. only:: esp32c3
Before starting the receiver we need some storage for incoming items. The RMT controller has 192 x 32-bits of internal RAM shared between all four channels.
.. only:: esp32s3
Before starting the receiver we need some storage for incoming items. The RMT controller has 384 x 32-bits of internal RAM shared between all eight channels.
In typical scenarios it is not enough as an ultimate storage for all incoming (and outgoing) items. Therefore this API supports retrieval of incoming items on the fly to save them in a ring buffer of a size defined by the user. The size is provided when calling :cpp:func:`rmt_driver_install` discussed above. To get a handle to this buffer call :cpp:func:`rmt_get_ringbuf_handle`.
With the above steps complete we can start the receiver by calling :cpp:func:`rmt_rx_start` and then move to checking what's inside the buffer. To do so, you can use common FreeRTOS functions that interact with the ring buffer. Please see an example how to do it in :example:`peripherals/rmt/ir_protocols`.
To stop the receiver, call :cpp:func:`rmt_rx_stop`.
Change Operation Parameters
---------------------------
Previously described function :cpp:func:`rmt_config` provides a convenient way to set several configuration parameters in one shot. This is usually done on application start. Then, when the application is running, the API provides an alternate way to update individual parameters by calling dedicated functions. Each function refers to the specific RMT channel provided as the first input parameter. Most of the functions have `_get_` counterpart to read back the currently configured value.
Parameters Common to Transmit and Receive Mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Selection of a GPIO pin number on the input or output of the RMT - :cpp:func:`rmt_set_gpio`
* Number of memory blocks allocated for the incoming or outgoing data - :cpp:func:`rmt_set_mem_pd`
* Setting of the clock divider - :cpp:func:`rmt_set_clk_div`
* Selection of the clock source, note that currently one clock source is supported, the APB clock which is 80Mhz - :cpp:func:`rmt_set_source_clk`
Transmit Mode Parameters
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
* Enable or disable the loop back mode for the transmitter - :cpp:func:`rmt_set_tx_loop_mode`
* Binary level on the output to apply the carrier - :cpp:func:`rmt_set_tx_carrier`, selected from :cpp:type:`rmt_carrier_level_t`
* Determines the binary level on the output when transmitter is idle - :cpp:func:`rmt_set_idle_level()`, selected from :cpp:type:`rmt_idle_level_t`
When an RMT channel finishes transmitting or receiving, a specific event will be generated and notify the CPU by interrupt. If you have some function that needs to be called when those events occurred, you can hook your function to the ISR (Interrupt Service Routine) by calling :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` for TX and RX channel respectively. Since the registered callback functions are called in the interrupt context, user should ensure the callback function doesn't attempt to block (e.g. by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The callback function has a boolean return value, to tell the caller whether a high priority task is woke up by it.
.. only:: SOC_RMT_SUPPORT_TX_LOOP_COUNT
TX channel supported event callbacks are listed in the :cpp:type:`rmt_tx_event_callbacks_t`:
* Enable or disable loop count feature to automatically transmit items for N iterations, then trigger an ISR callback - :cpp:func:`rmt_set_tx_loop_count`
* Enable automatically stopping when the number of iterations matches the set loop count. Note this is not reliable for target that doesn't support `SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP`. - :cpp:func:`rmt_enable_tx_loop_autostop`
- :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` sets a callback function for trans done event. The function prototype is declared in :cpp:type:`rmt_tx_done_callback_t`.
RX channel supported event callbacks are listed in the :cpp:type:`rmt_rx_event_callbacks_t`:
Receive Mode Parameters
- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` sets a callback function for receive complete event. The function prototype is declared in :cpp:type:`rmt_rx_done_callback_t`.
User can save own context in :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to each callback function.
In the callback function, users can fetch the event specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is only valid for the duration of the callback.
The TX done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`:
- :cpp:member:`rmt_tx_done_event_data_t::num_symbols` tells the number of transmitted RMT symbols. This also reflects the size of encoding artifacts.
The RX complete event data is defined in :cpp:type:`rmt_rx_done_event_data_t`:
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. User shouldn't free this receive buffer before the callback returns.
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` tells the number of received RMT symbols. This value won't be bigger than ``buffer_size`` parameter of :cpp:func:`rmt_receive` function. If the ``buffer_size`` is not sufficient to accommodate all the received RMT symbols, the driver will truncate it.
Enable and Disable channel
^^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`rmt_enable` must be called in advanced before transmitting or receiving RMT symbols. For transmitters, enabling a channel will enable a specific interrupt and prepare the hardware to dispatch transactions. For RX channels, enabling a channel will enable an interrupt, but the receiver is not started during this time, as it has no idea about the characteristics of the incoming signals. The receiver will be started in :cpp:func:`rmt_receive`.
:cpp:func:`rmt_disable` does the opposite work by disabling the interrupt and clearing pending status. The transmitter and receiver will be disabled as well.
.. code:: c
ESP_ERROR_CHECK(rmt_enable(tx_chan));
ESP_ERROR_CHECK(rmt_enable(rx_chan));
Initiate TX Transaction
^^^^^^^^^^^^^^^^^^^^^^^
* The filter setting - :cpp:func:`rmt_set_rx_filter`
* The receiver threshold setting - :cpp:func:`rmt_set_rx_idle_thresh`
* Whether the transmitter or receiver is entitled to access RMT's memory - :cpp:func:`rmt_set_memory_owner`, selection is from :cpp:type:`rmt_mem_owner_t`.
RMT is a special communication peripheral as it's unable to transmit raw byte streams like SPI and I2C. RMT can only send data in its own format :cpp:type:`rmt_symbol_word_t`. However, the hardware doesn't help to convert the user data into RMT symbols, this can only be done in software --- by the so-called **RMT Encoder**. The encoder is responsible for encoding user data into RMT symbols and then write to RMT memory block or DMA buffer. For how to create an RMT encoder, please refer to `RMT Encoder <#rmt-encoder>`__.
Once we got an encoder, we can initiate a TX transaction by calling :cpp:func:`rmt_transmit`. This function takes several positional parameters like channel handle, encoder handle, payload buffer. Besides that, we also need to provide a transmission specific configuration in :cpp:type:`rmt_transmit_config_t`:
Use Interrupts
--------------
Registering of an interrupt handler for the RMT controller is done be calling :cpp:func:`rmt_isr_register`.
- :cpp:member:`rmt_transmit_config_t::loop_count` sets the number of transmission loop. After the transmitter finished one round of transmission, it can restart the same transmission again if this value is not set to zero. As the loop is controlled by hardware, the RMT channel can be used to generate many periodic sequences at the cost of a very little CPU intervention. Specially, setting :cpp:member:`rmt_transmit_config_t::loop_count` to `-1` means an infinite loop transmission. In this situation, the channel won't stop until manually call of :cpp:func:`rmt_disable`. And the trans done event won't be generated as well. If :cpp:member:`rmt_transmit_config_t::loop_count` is set to a positive number, the trans done event won't be generated until target number of loop transmission have finished. Note that, the **loop transmit** feature is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you configure this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_transmit_config_t::eot_level` sets the output level when the transmitter finishes working or stops working by calling :cpp:func:`rmt_disable`.
.. note::
When calling :cpp:func:`rmt_driver_install` to use the system RMT driver, a default ISR is being installed. In such a case you cannot register a generic ISR handler with :cpp:func:`rmt_isr_register`.
There's a limitation in the transmission size if the :cpp:member:`rmt_transmit_config_t::loop_count` is set to non-zero (i.e. to enable the loop feature). The encoded RMT symbols should not exceed the capacity of RMT hardware memory block size. Or you might see error message like ``encoding artifacts can't exceed hw memory block for loop transmission``. If you have to start a large transaction by loop, you can try either:
The RMT controller triggers interrupts on four specific events describes below. To enable interrupts on these events, the following functions are provided:
- Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`. This approach doesn't work if the DMA backend is also enabled.
- Customize an encoder and construct a forever loop in the encoding function. See also `RMT Encoder <#rmt-encoder>`__.
* The RMT receiver has finished receiving a signal - :cpp:func:`rmt_set_rx_intr_en`
* The RMT transmitter has finished transmitting the signal - :cpp:func:`rmt_set_tx_intr_en`
* The number of events the transmitter has sent matches a threshold value :cpp:func:`rmt_set_tx_thr_intr_en`
* Ownership to the RMT memory block has been violated - :cpp:func:`rmt_set_err_intr_en`
Internally, :cpp:func:`rmt_transmit` will construct a transaction descriptor and send to a job queue, which will be dispatched in the ISR. So it is possible that the transaction is not started yet when :cpp:func:`rmt_transmit` returns. To ensure all pending transaction to complete, user can use :cpp:func:`rmt_tx_wait_all_done`.
When servicing an interrupt within an ISR, the interrupt need to explicitly cleared. To do so, set specific bits described as ``RMT.int_clr.val.chN_event_name`` and defined as a ``volatile struct`` in :component_file:`soc/{IDF_TARGET_PATH_NAME}/include/soc/rmt_struct.h`, where N is the RMT channel number [0, n] and the ``event_name`` is one of four events described above.
Multiple Channels Simultaneous Transmission
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you do not need an ISR anymore, you can deregister it by calling a function :cpp:func:`rmt_isr_deregister`.
In some real-time control applications, we don't want any time drift in between when startup multiple TX channels. For example, to make two robotic arms move simultaneously. The RMT driver can help to manage this by creating a so-called **Sync Manager**. The sync manager is represented by :cpp:type:`rmt_sync_manager_handle_t` in the driver. The procedure of RMT sync transmission is shown as follows:
.. warning::
.. figure:: /../_static/rmt_tx_sync.png
:align: center
:alt: RMT TX Sync
It's not recommended for users to register an interrupt handler in their applications. RMT driver is highly dependent on interrupt, especially when doing transaction in a ping-pong way, so the driver itself has registered a default handler called ``rmt_driver_isr_default``.
Instead, if what you want is to get a notification when transaction is done, go ahead with :cpp:func:`rmt_register_tx_end_callback`.
RMT TX Sync
Install RMT Sync Manager
~~~~~~~~~~~~~~~~~~~~~~~~
Uninstall Driver
----------------
To create a sync manager, user needs to tell which channels are going to be managed in the :cpp:type:`rmt_sync_manager_config_t`:
If the RMT driver has been installed with :cpp:func:`rmt_driver_install` for some specific period of time and then not required, the driver may be removed to free allocated resources by calling :cpp:func:`rmt_driver_uninstall`.
- :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` points to the array of TX channels to be managed.
- :cpp:member:`rmt_sync_manager_config_t::array_size` sets the number of channels to be managed.
:cpp:func:`rmt_new_sync_manager` can return a manager handle on success. This function could also fail due to various errors such as invalid arguments, etc. Specially, when the sync manager has been installed before, and there're no hardware resources to create another manager, this function will report :c:macro:`ESP_ERR_NOT_FOUND` error. In addition, if the sync manager is not supported by the hardware, it will report :c:macro:`ESP_ERR_NOT_SUPPORTED` error. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before using the sync manager feature.
Start Transmission Simultaneously
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For any managed TX channel, it won't start the machine until all the channels in the :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` are called with :cpp:func:`rmt_transmit`. Before that, the channel is just put in a waiting state. Different channel usually take different time to finish the job if the transaction is different, which results in a loss of sync. So user needs to call :cpp:func:`rmt_sync_reset` to pull the channels back to the starting line again before restarting a simultaneous transmission.
Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable the channels to initiate transactions independently afterwards.
.. code:: c
rmt_channel_handle_t tx_channels[2] = {NULL}; // declare two channels
int tx_gpio_number[2] = {0, 2};
// install channels one by one
for (int i = 0; i < 2; i++) {
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = tx_gpio_number[i], // GPIO number
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
.resolution_hz = 1 * 1000 * 1000, // 1MHz resolution
.trans_queue_depth = 1, // set the number of transactions that can pend in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_channels[i]));
}
// install sync manager
rmt_sync_manager_handle_t synchro = NULL;
rmt_sync_manager_config_t synchro_config = {
.tx_channel_array = tx_channels,
.array_size = sizeof(tx_channels) / sizeof(tx_channels[0]),
};
ESP_ERROR_CHECK(rmt_new_sync_manager(&synchro_config, &synchro));
ESP_ERROR_CHECK(rmt_transmit(tx_channels[0], led_strip_encoders[0], led_data, led_num * 3, &transmit_config));
// tx_channels[0] won't start transmission until call of `rmt_transmit()` for tx_channels[1] returns
ESP_ERROR_CHECK(rmt_transmit(tx_channels[1], led_strip_encoders[1], led_data, led_num * 3, &transmit_config));
Initiate RX Transaction
^^^^^^^^^^^^^^^^^^^^^^^
As also discussed in the `Enable and Disable channel <#enable-and-disable-channel>`__, the RX channel still doesn't get ready to receive RMT symbols even user calls :cpp:func:`rmt_enable`. User needs to specify the basic characteristics of the incoming signals in :cpp:type:`rmt_receive_config_t`:
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` specifies the minimal valid pulse duration (either high or low level). A pulse whose width is smaller than this value will be treated as glitch and ignored by the hardware.
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` specifies the maximum valid pulse duration (either high or low level). A pulse whose width is bigger than this value will be treated as **Stop Signal**, and the receiver will generate receive complete event immediately.
The RMT receiver will start the RX machine after user calls :cpp:func:`rmt_receive` with the provided configuration above. Note that, this configuration is transaction specific, which means, to start a new round of reception, user needs to sets the :cpp:type:`rmt_receive_config_t` again. The receiver saves the incoming signals into its internal memory block or DMA buffer, in the format of :cpp:type:`rmt_symbol_word_t`.
.. only:: SOC_RMT_SUPPORT_RX_PINGPONG
Due to the limited size of memory block, the RMT receiver will notify the driver to copy away the accumulated symbols in a ping-pong way.
.. only:: not SOC_RMT_SUPPORT_RX_PINGPONG
Due to the limited size of memory block, the RMT receiver can only save short frames whose length is not longer than the memory block capacity. Long frames will be truncated by the hardware, and the driver will report an error message: ``hw buffer too small, received symbols truncated``.
The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer size is not sufficient, the receiver can continue to work but later incoming symbols will be dropped and report an error message: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, user shouldn't recycle the buffer before the receiver finished or stopped working.
The receiver will be stopped by the driver when it finishes working (i.e. received a signal whose duration is bigger than :cpp:member:`rmt_receive_config_t::signal_range_max_ns`). User needs to call :cpp:func:`rmt_receive` again to restart the receiver, is necessary. User can get the received data in the :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback. See also `Register Event Callbacks <#register-event-callbacks>`__ for more information.
.. code:: c
static bool example_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;
TaskHandle_t task_to_notify = (TaskHandle_t)user_data;
// send the received RMT symbols to the parser task
xTaskNotifyFromISR(task_to_notify, (uint32_t)edata, eSetValueWithOverwrite, &high_task_wakeup);
// return whether any task is woken up
return high_task_wakeup == pdTRUE;
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = example_rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel, &cbs, cur_task));
// the following timing requirement is based on NEC protocol
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560us, 1250ns < 560us, valid signal won't be treated as noise
.signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000us, 12000000ns > 9000us, the receive won't stop early
};
rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame
// ready to receive
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
// wait for RX done signal
rmt_rx_done_event_data_t *rx_data = NULL;
xTaskNotifyWait(0x00, ULONG_MAX, (uint32_t *)&rx_data, portMAX_DELAY);
// parse the receive symbols
example_parse_nec_frame(rx_data->received_symbols, rx_data->num_symbols);
RMT Encoder
^^^^^^^^^^^
An RMT encoder is part of the RMT TX transaction, whose responsibility is to generate and write the correct RMT symbols into hardware memory (or DMA buffer) at specific time. There're some special restrictions for an encoding function:
- An encoding function might be called for several times within a single transaction. This is because the target RMT memory block can't accommodate all the artifacts at once. We have to use the memory in a **ping-pong** way, thus the encoding session is divided into multiple parts. This requires the encoder to be **stateful**.
- The encoding function is running in the ISR context. To speed up the encoding session, it's high recommend to put the encoding function into IRAM. This can also avoid the cache miss during encoding.
To help get started with RMT driver faster, some commonly used encoders are provided out-of-the box. They can either work alone or chained together into a new encoder. See also `Composite Pattern <https://en.wikipedia.org/wiki/Composite_pattern>`__ for the principle behind. The driver has defined the encoder interface in :cpp:type:`rmt_encoder_t`, it contains the following functions:
- :cpp:member:`rmt_encoder_t::encode` is the fundamental function of an encoder. This is where the encoding session happens. Please note, the :cpp:member:`rmt_encoder_t::encode` function might be called for multiple times within a single transaction. The encode function should return the state of current encoding session. The supported states are listed in the :cpp:type:`rmt_encode_state_t`. If the result contains :cpp:enumerator:`RMT_ENCODING_COMPLETE`, it means the current encoder has finished work. If the result contains :cpp:enumerator:`RMT_ENCODING_MEM_FULL`, we need to yield from current session, as there's no space to save more encoding artifacts.
- :cpp:member:`rmt_encoder_t::reset` should reset the encoder state back to initial. The RMT encoder is stateful, if RMT transmitter stopped manually without its corresponding encoder being reset, then the following encoding session can be wrong. This function is also called implicitly in :cpp:func:`rmt_disable`.
- :cpp:member:`rmt_encoder_t::del` function should free the resources allocated by the encoder.
Copy Encoder
~~~~~~~~~~~~
A copy encoder is created by calling :cpp:func:`rmt_new_copy_encoder`. Copy encoder's main functionality is to copy the RMT symbols from user space into the driver layer. It's usually used to encode const data (i.e. data won't change at runtime after initialization), for example, the leading code in the IR protocol.
A configuration structure :cpp:type:`rmt_copy_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_copy_encoder`. Currently, this configuration is reserved for future expansion.
Bytes Encoder
~~~~~~~~~~~~~
A bytes encoder is created by calling :cpp:func:`rmt_new_bytes_encoder`. Bytes encoder's main functionality is to convert the user space byte stream into RMT symbols dynamically. It's usually used to encode dynamic data, for example, the address and command fields in the IR protocol.
A configuration structure :cpp:type:`rmt_bytes_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_bytes_encoder`:
- :cpp:member:`rmt_bytes_encoder_config_t::bit0` and :cpp:member:`rmt_bytes_encoder_config_t::bit1` are necessary to tell to the encoder how to represent bit zero and bit one in the format of :cpp:type:`rmt_symbol_word_t`.
- :cpp:member:`rmt_bytes_encoder_config_t::msb_first` sets the encoding order for of byte. If it is set to true, the encoder will encode the **Most Significant Bit** first. Otherwise, it will encode the **Least Significant Bit** first.
Besides the primitive encoders provided by the driver, user can implement his own encoder by chaining the existing encoders together. A common encoder chain is shown as follows:
.. blockdiag:: /../_static/diagrams/rmt/rmt_encoder_chain.diag
:caption: RMT Encoder Chain
:align: center
Customize RMT Encoder for NEC Protocol
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this section, we will demonstrate on how to write an NEC encoder. The NEC IR protocol uses pulse distance encoding of the message bits. Each pulse burst is `562.5µs` in length, logical bits are transmitted as follows. It is worth mentioning, the bytes of data bits are sent least significant bit first.
- Logical ``0``: a `562.5µs` pulse burst followed by a `562.5µs` space, with a total transmit time of `1.125ms`
- Logical ``1``: a `562.5µs` pulse burst followed by a `1.6875ms` space, with a total transmit time of `2.25ms`
When a key is pressed on the remote controller, the message transmitted consists of the following, in order:
.. figure:: /../_static/ir_nec.png
:align: center
:alt: IR NEC Frame
IR NEC Frame
- `9ms` leading pulse burst (also called the "AGC pulse")
- `4.5ms` space
- 8-bit address for the receiving device
- 8-bit logical inverse of the address
- 8-bit command
- 8-bit logical inverse of the command
- a final `562.5µs` pulse burst to signify the end of message transmission
Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in the same order, for example:
.. code:: c
// IR NEC scan code representation
typedef struct {
uint16_t address;
uint16_t command;
} ir_nec_scan_code_t;
// construct a encoder by combining primitive encoders
typedef struct {
rmt_encoder_t base; // the base "class", declares the standard encoder interface
rmt_encoder_t *copy_encoder; // use the copy_encoder to encode the leading and ending pulse
rmt_encoder_t *bytes_encoder; // use the bytes_encoder to encode the address and command data
rmt_symbol_word_t nec_leading_symbol; // NEC leading code with RMT representation
rmt_symbol_word_t nec_ending_symbol; // NEC ending code with RMT representation
int state; // record the current encoding state (i.e. we're in which encoding phase)
} rmt_ir_nec_encoder_t;
static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base);
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data;
rmt_encoder_handle_t copy_encoder = nec_encoder->copy_encoder;
rmt_encoder_handle_t bytes_encoder = nec_encoder->bytes_encoder;
switch (nec_encoder->state) {
case 0: // send leading code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_leading_symbol,
sizeof(rmt_symbol_word_t), &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: // send address
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->address, sizeof(uint16_t), &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: // send command
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->command, sizeof(uint16_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 3; // 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 3: // send ending code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_ending_symbol,
sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE; // telling the caller the NEC encoding has 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
}
}
out:
*ret_state = state;
return encoded_symbols;
}
A full sample code can be found in :example:`peripherals/rmt/ir_nec_transceiver`. In the above snippet, we use a ``switch-case`` plus several ``goto`` statements to implement a `state machine <https://en.wikipedia.org/wiki/Finite-state_machine>`__ . With this pattern, user can construct a lot more complex IR protocols.
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the resolution of RMT internal counter.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever user creates an RMT channel that has selected :cpp:enumerator:`RMT_CLK_SRC_APB` as the clock source, the driver will guarantee that the power management lock is acquired after the channel enabled by :cpp:func:`rmt_enable`. Likewise, the driver releases the lock after :cpp:func:`rmt_disable` is called for the same channel. This also reveals that the :cpp:func:`rmt_enable` and :cpp:func:`rmt_disable` should appear in pairs.
If the channel clock source is selected to others like :cpp:enumerator:`RMT_CLK_SRC_XTAL`, then the driver won't install power management lock for it, which is more suitable for a low power application as long as the source clock can still provide sufficient resolution.
IRAM Safe
^^^^^^^^^
By default, the RMT interrupt will be deferred when the Cache is disabled for reasons like writing/erasing the main Flash. Thus the transaction done interrupt will not get executed in time, which is not expected in a real-time application. What's worse, when the RMT transaction relies on **ping-pong** interrupt to successively encode or copy RMT symbols, such delayed response can lead to an unpredictable result.
There's a Kconfig option :ref:`CONFIG_RMT_ISR_IRAM_SAFE` that will:
1. Enable the interrupt being serviced even when cache is disabled
2. Place all functions that used by the ISR into IRAM [2]_
3. Place driver object into DRAM (in case it's mapped to PSRAM by accident)
This Kconfig option will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`rmt_new_tx_channel`, :cpp:func:`rmt_new_rx_channel` and :cpp:func:`rmt_new_sync_manager` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
Other functions that take the :cpp:type:`rmt_channel_handle_t` and :cpp:type:`rmt_sync_manager_handle_t` as the first positional parameter, are not thread safe. which means the user should avoid calling them from multiple tasks.
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_RMT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see also `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` is used to enabled the debug log at the cost of increased firmware binary size.
Application Examples
--------------------
* Using RMT to send morse code: :example:`peripherals/rmt/morse_code`.
* Using RMT to drive RGB LED strip: :example:`peripherals/rmt/led_strip`.
* NEC remote control TX and RX example: :example:`peripherals/rmt/ir_protocols`.
* Musical buzzer example: :example:`peripherals/rmt/musical_buzzer`.
* RMT based RGB LED strip customized encoder: :example:`peripherals/rmt/led_strip`
* RMT IR NEC protocol encoding and decoding: :example:`peripherals/rmt/ir_nec_transceiver`
* RMT transactions in queue: :example:`peripherals/rmt/musical_buzzer`
* RMT based stepper motor with S-curve algorithm: : :example:`peripherals/rmt/stepper_motor`
* RMT infinite loop for driving DShot ESC: :example:`peripherals/rmt/dshot_esc`
API Reference
-------------
.. include-build-file:: inc/rmt.inc
.. include-build-file:: inc/rmt_types.inc
.. include-build-file:: inc/rmt_tx.inc
.. include-build-file:: inc/rmt_rx.inc
.. include-build-file:: inc/rmt_common.inc
.. include-build-file:: inc/rmt_encoder.inc
.. include-build-file:: inc/components/driver/include/driver/rmt_types.inc
.. include-build-file:: inc/components/hal/include/hal/rmt_types.inc
.. [1]
Different ESP chip series might have different number of RMT channels. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] for details. The driver won't forbid you from applying for more RMT channels, but it will return error when there's no hardware resources available. Please always check the return value when doing `Resource Allocation <#resource-allocation>`__.
.. [2]
Callback function (e.g. :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done`) and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves.

View File

@ -128,9 +128,58 @@ I2C
RMT Driver
----------
RMT driver has been redesigned (see :doc:`RMT transceiver <../api-reference/peripherals/rmt>`), which aims to unify and extend the usage of RMT peripheral. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/rmt.h``. However, by default, including ``driver/rmt.h`` will bring a build warning like `The legacy RMT driver is deprecated, please use driver/rmt_tx.h and/or driver/rmt_rx.h`. The warning can be suppressed by the Kconfig option :ref:`CONFIG_RMT_SUPPRESS_DEPRECATE_WARN`.
The major breaking changes in concept and usage are listed as follows:
Breaking Changes in Concepts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``rmt_channel_t`` which used to identify the hardware channel are removed from user space. In the new driver, RMT channel is represented by :cpp:type:`rmt_channel_handle_t`. The channel is dynamic allocated by the driver, instead of designated by user.
- ``rmt_item32_t`` is replaced by :cpp:type:`rmt_symbol_word_t`, which avoids a nested union inside a struct.
- ``rmt_mem_t`` is removed, as we don't allow users to access RMT memory block (a.k.an RMTMEM) directly. Direct access to RMTMEM doesn't make sense but make mistakes, especially when the RMT channel also connected with a DMA channel.
- ``rmt_mem_owner_t`` is removed, as the ownership is controller by driver, not by user anymore.
- ``rmt_source_clk_t`` is replaced by :cpp:type:`rmt_clock_source_t`, note they're not binary compatible.
- ``rmt_data_mode_t`` is removed, the RMT memory access mode is configured to always use Non-FIFO and DMA mode.
- ``rmt_mode_t`` is removed, as the driver has stand alone install functions for TX and RX channels.
- ``rmt_idle_level_t`` is removed, setting IDLE level for TX channel is available in :cpp:member:`rmt_transmit_config_t::eot_level`.
- ``rmt_carrier_level_t`` is removed, setting carrier polarity is available in :cpp:member:`rmt_carrier_config_t::polarity_active_low`.
- ``rmt_channel_status_t`` and ``rmt_channel_status_result_t`` are removed, they're not used anywhere.
- transmitting by RMT channel doesn't expect user to prepare the RMT symbols, instead, user needs to provide an RMT Encoder to tell the driver how to convert user data into RMT symbols.
Breaking Changes in Usage
~~~~~~~~~~~~~~~~~~~~~~~~~
- Channel installation has been separated for TX and RX channels into :cpp:func:`rmt_new_tx_channel` and :cpp:func:`rmt_new_rx_channel`.
- ``rmt_set_clk_div`` and ``rmt_get_clk_div`` are removed. Channel clock configuration can only be done during channel installation.
- ``rmt_set_rx_idle_thresh`` and ``rmt_get_rx_idle_thresh`` are removed. In the new driver, the RX channel IDLE threshold is redesigned into a new concept :cpp:member:`rmt_receive_config_t::signal_range_max_ns`.
- ``rmt_set_mem_block_num`` and ``rmt_get_mem_block_num`` are removed. In the new driver, the memory block number is determined by :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` and :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols`.
- ``rmt_set_tx_carrier`` is removed, the new driver uses :cpp:func:`rmt_apply_carrier` to set carrier behavior.
- ``rmt_set_mem_pd`` and ``rmt_get_mem_pd`` are removed. The memory power is managed by the driver automatically.
- ``rmt_memory_rw_rst``, ``rmt_tx_memory_reset`` and ``rmt_rx_memory_reset`` are removed. Memory reset is managed by the driver automatically.
- ``rmt_tx_start`` and ``rmt_rx_start`` are merged into a single function :cpp:func:`rmt_enable`, for both TX and RX channels.
- ``rmt_tx_stop`` and ``rmt_rx_stop`` are merged into a single function :cpp:func:`rmt_disable`, for both TX and RX channels.
- ``rmt_set_memory_owner`` and ``rmt_get_memory_owner`` are removed. RMT memory owner guard is added automatically by the driver.
- ``rmt_set_tx_loop_mode`` and ``rmt_get_tx_loop_mode`` are removed. In the new driver, the loop mode is configured in :cpp:member:`rmt_transmit_config_t::loop_count`.
- ``rmt_set_source_clk`` and ``rmt_get_source_clk`` are removed. Configuring clock source is only possible during channel installation by :cpp:member:`rmt_tx_channel_config_t::clk_src` and :cpp:member:`rmt_rx_channel_config_t::clk_src`.
- ``rmt_set_rx_filter`` is removed. In the new driver, the filter threshold is redesigned into a new concept :cpp:member:`rmt_receive_config_t::signal_range_min_ns`.
- ``rmt_set_idle_level`` and ``rmt_get_idle_level`` are removed. Setting IDLE level for TX channel is available in :cpp:member:`rmt_transmit_config_t::eot_level`.
- ``rmt_set_rx_intr_en``, ``rmt_set_err_intr_en``, ``rmt_set_tx_intr_en``, ``rmt_set_tx_thr_intr_en`` and ``rmt_set_rx_thr_intr_en`` are removed. The new driver doesn't allow user to turn on/off interrupt from user space. Instead, it provides callback functions.
- ``rmt_set_gpio`` and ``rmt_set_pin`` are removed. The new driver doesn't support to switch GPIO dynamically at runtime.
- ``rmt_config`` is removed. In the new driver, basic configuration is done during the channel installation stage.
- ``rmt_isr_register`` and ``rmt_isr_deregister`` are removed, the interrupt is allocated by the driver itself.
- ``rmt_driver_install`` is replaced by :cpp:func:`rmt_new_tx_channel` and :cpp:func:`rmt_new_rx_channel`.
- ``rmt_driver_uninstall`` is replaced by :cpp:func:`rmt_del_channel`.
- ``rmt_fill_tx_items``, ``rmt_write_items`` and ``rmt_write_sample`` are removed. In the new driver, user needs to provide an encoder to "translate" the user data into RMT symbols.
- ``rmt_get_counter_clock`` is removed, as the channel clock resolution is configured by user from :cpp:member:`rmt_tx_channel_config_t::resolution_hz`.
- ``rmt_wait_tx_done`` is replaced by :cpp:func:`rmt_tx_wait_all_done`.
- ``rmt_translator_init``, ``rmt_translator_set_context`` and ``rmt_translator_get_context`` are removed. In the new driver, the translator has been replaced by the RMT encoder.
- ``rmt_get_ringbuf_handle`` is removed. The new driver doesn't use Ringbuffer to save RMT symbols. Instead, the incoming data are saved to the user provided buffer directly. The user buffer can even be mounted to DMA link internally.
- ``rmt_register_tx_end_callback`` is replaced by :cpp:func:`rmt_tx_register_event_callbacks`, where user can register :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` event callback.
- ``rmt_set_intr_enable_mask`` and ``rmt_clr_intr_enable_mask`` are removed, as the interrupt is handled by the driver, user doesn't need to take care of it.
- ``rmt_set_pin`` is removed, as ``rmt_set_gpio`` can do the same thing.
- ``rmt_memory_rw_rst`` is removed, user can use ``rmt_tx_memory_reset`` and ``rmt_rx_memory_reset`` for TX and RX channel respectively.
- ``rmt_add_channel_to_group`` and ``rmt_remove_channel_from_group`` are replaced by RMT sync manager. Please refer to :cpp:func:`rmt_new_sync_manager`.
- ``rmt_set_tx_loop_count`` is removed. The loop count in the new driver is configured in :cpp:member:`rmt_transmit_config_t::loop_count`.
- ``rmt_enable_tx_loop_autostop`` is removed. In the new driver, TX loop auto stop is always enabled if available, it's not configurable anymore.
LCD
---

View File

@ -70,6 +70,8 @@ If type alias or template alias:
void() esp_spp_cb_t (esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
----^
rmt_encoder.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/peripherals/rmt:line.
Declaration is '.. cpp:type:: struct rmt_encoder_t rmt_encoder_t'.
spi_master.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/peripherals/spi_master:line.
Declaration is '.. cpp:type:: struct spi_transaction_t spi_transaction_t'.
spi_slave.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/peripherals/spi_slave:line.

View File

@ -1,4 +1,3 @@
idf_component_register(SRCS "led_strip_rmt_ws2812.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES "driver"
)
idf_component_register(SRCS "src/led_strip_rmt_dev.c" "src/led_strip_rmt_encoder.c"
INCLUDE_DIRS "include" "interface"
PRIV_REQUIRES "driver")

View File

@ -1,6 +1,6 @@
# LED Strip Component
This directory contains an implementation for addressable LEDs using the RMT peripheral.
This directory contains an implementation for addressable LEDs by different peripherals. Currently only RMT is supported as the led strip backend.
It's compatible with:
@ -9,7 +9,6 @@ It's compatible with:
This component is used as part of the following ESP-IDF examples:
- [Blink Example](../../get-started/blink).
- [LED Strip Example](../../peripherals/rmt/led_strip).
To learn more about how to use this component, please check API Documentation from header file [led_strip.h](./include/led_strip.h).

View File

@ -1,146 +1,94 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
/**
* @brief LED strip handle
*/
typedef struct led_strip_t *led_strip_handle_t;
/**
* @brief LED Strip Type
*
*/
typedef struct led_strip_s led_strip_t;
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief LED Strip Device Type
*
*/
typedef void *led_strip_dev_t;
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t led_strip_refresh(led_strip_handle_t strip);
/**
* @brief Declare of LED Strip Type
*
*/
struct led_strip_s {
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param timeout_ms: timeout value for refreshing task
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_ERR_TIMEOUT: Refresh failed because of timeout
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param timeout_ms: timeout value for clearing task
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_ERR_TIMEOUT: Clear LEDs failed because of timeout
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t (*del)(led_strip_t *strip);
};
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t led_strip_clear(led_strip_handle_t strip);
/**
* @brief LED Strip Configuration Type
*
*/
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t led_strip_del(led_strip_handle_t strip);
/**
* @brief LED Strip Configuration
*/
typedef struct {
uint32_t max_leds; /*!< Maximum LEDs in a single strip */
led_strip_dev_t dev; /*!< LED strip device (e.g. RMT channel, PWM channel, etc) */
uint32_t strip_gpio_num; /*!< GPIO number that used by LED strip */
uint32_t max_leds; /*!< Maximum LEDs in a single strip */
} led_strip_config_t;
/**
* @brief Default configuration for LED strip
* @brief Create LED strip based on RMT TX channel
*
*/
#define LED_STRIP_DEFAULT_CONFIG(number, dev_hdl) \
{ \
.max_leds = number, \
.dev = dev_hdl, \
}
/**
* @brief Install a new ws2812 driver (based on RMT peripheral)
*
* @param config: LED strip configuration
* @return
* LED strip instance or NULL
*/
led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config);
/**
* @brief Init the RMT peripheral and LED strip configuration.
*
* @param[in] channel: RMT peripheral channel number.
* @param[in] gpio: GPIO number for the RMT data output.
* @param[in] led_num: number of addressable LEDs.
* @param config LED strip specific configuration
* @param ret_strip Returned LED strip handle
* @return
* LED strip instance or NULL
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
led_strip_t * led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num);
/**
* @brief Denit the RMT peripheral.
*
* @param[in] strip: LED strip
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t led_strip_denit(led_strip_t *strip);
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *config, led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}

View File

@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */
/**
* @brief LED strip interface definition
*/
struct led_strip_t {
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param timeout_ms: timeout value for refreshing task
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t (*refresh)(led_strip_t *strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param timeout_ms: timeout value for clearing task
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t (*clear)(led_strip_t *strip);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t (*del)(led_strip_t *strip);
};
#ifdef __cplusplus
}
#endif

View File

@ -1,216 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_attr.h"
#include "led_strip.h"
#include "driver/rmt.h"
#define RMT_TX_CHANNEL RMT_CHANNEL_0
static const char *TAG = "ws2812";
#define STRIP_CHECK(a, str, goto_tag, ret_value, ...) \
do \
{ \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
ret = ret_value; \
goto goto_tag; \
} \
} while (0)
#define WS2812_T0H_NS (350)
#define WS2812_T0L_NS (1000)
#define WS2812_T1H_NS (1000)
#define WS2812_T1L_NS (350)
#define WS2812_RESET_US (280)
static uint32_t ws2812_t0h_ticks = 0;
static uint32_t ws2812_t1h_ticks = 0;
static uint32_t ws2812_t0l_ticks = 0;
static uint32_t ws2812_t1l_ticks = 0;
static uint32_t ws2812_reset_ticks = 0;
typedef struct {
led_strip_t parent;
rmt_channel_t rmt_channel;
uint32_t strip_len;
uint8_t buffer[0];
} ws2812_t;
/**
* @brief Conver RGB data to RMT format.
*
* @note For WS2812, R,G,B each contains 256 different choices (i.e. uint8_t)
*
* @param[in] src: source data, to converted to RMT format
* @param[in] dest: place where to store the convert result
* @param[in] src_size: size of source data
* @param[in] wanted_num: number of RMT items that want to get
* @param[out] translated_size: number of source data that got converted
* @param[out] item_num: number of RMT items which are converted from source data
*/
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
size_t wanted_num, size_t *translated_size, size_t *item_num)
{
if (src == NULL || dest == NULL) {
*translated_size = 0;
*item_num = 0;
return;
}
const rmt_item32_t bit0 = {{{ ws2812_t0h_ticks, 1, ws2812_t0l_ticks, 0 }}}; //Logical 0
const rmt_item32_t bit1 = {{{ ws2812_t1h_ticks, 1, ws2812_t1l_ticks, 0 }}}; //Logical 1
const rmt_item32_t reset = {{{ ws2812_reset_ticks, 0, 0, 0 }}}; // Reset signal
const rmt_item32_t nop = {{{ 0, 0, 0, 0 }}}; // Null signal
size_t size = 0;
size_t num = 0;
uint8_t *psrc = (uint8_t *)src;
rmt_item32_t *pdest = dest;
while (size < (src_size - 1) && num < wanted_num) {
// MSB first, so we decremented `i` from 7 to 0.
// Note that `i` must be signed (e.g. `int`) for this to work!
for (int i = 7; i >= 0; i--) {
(pdest++)->val = (*psrc & (1 << i)) ? bit1.val : bit0.val;
}
num += 8;
size++;
psrc++;
}
// Send reset signal (LOW for `WS2812_RESET_US` microseconds)
// The check of the size is still needed, because `src_size` could be 0.
// (Note that the check could be replaced by `src_size > 0`,
// but it is more clear this way.)
if (size < src_size && num < wanted_num) {
(pdest++)->val = reset.val; // The first bit is the reset signal
// we just sent the first bit, so we start the loop with i = 1
for (int i = 1; i < 8; i++) { // complete the byte with 7 NOP signals
(pdest++)->val = nop.val;
}
num += 8;
size++;
// we don't need to increment `psrc`, because we no longer use it
}
*translated_size = size;
*item_num = num;
}
static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
esp_err_t ret = ESP_OK;
ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
STRIP_CHECK(index < ws2812->strip_len, "index out of the maximum number of leds", err, ESP_ERR_INVALID_ARG);
uint32_t start = index * 3;
// In the order of GRB
ws2812->buffer[start + 0] = green & 0xFF;
ws2812->buffer[start + 1] = red & 0xFF;
ws2812->buffer[start + 2] = blue & 0xFF;
return ESP_OK;
err:
return ret;
}
static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms)
{
esp_err_t ret = ESP_OK;
ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
STRIP_CHECK(rmt_write_sample(ws2812->rmt_channel, ws2812->buffer, ws2812->strip_len * 3 + 1, true) == ESP_OK,
"transmit RMT samples failed", err, ESP_FAIL);
return rmt_wait_tx_done(ws2812->rmt_channel, pdMS_TO_TICKS(timeout_ms));
err:
return ret;
}
static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms)
{
ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
// Write zero to turn off all leds
memset(ws2812->buffer, 0, ws2812->strip_len * 3);
return ws2812_refresh(strip, timeout_ms);
}
static esp_err_t ws2812_del(led_strip_t *strip)
{
ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
free(ws2812);
return ESP_OK;
}
led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config)
{
led_strip_t *ret = NULL;
STRIP_CHECK(config, "configuration can't be null", err, NULL);
// 24 bits per led + reset
uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3 + 1;
ws2812_t *ws2812 = calloc(1, ws2812_size);
STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL);
uint32_t counter_clk_hz = 0;
STRIP_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev, &counter_clk_hz) == ESP_OK,
"get rmt counter clock failed", err, NULL);
// ns -> ticks
float ratio = (float)counter_clk_hz / 1e9;
ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
ws2812_reset_ticks = (uint32_t)(ratio * WS2812_RESET_US * 1000);
// set ws2812 to rmt adapter
rmt_translator_init((rmt_channel_t)config->dev, ws2812_rmt_adapter);
ws2812->rmt_channel = (rmt_channel_t)config->dev;
ws2812->strip_len = config->max_leds;
ws2812->parent.set_pixel = ws2812_set_pixel;
ws2812->parent.refresh = ws2812_refresh;
ws2812->parent.clear = ws2812_clear;
ws2812->parent.del = ws2812_del;
return &ws2812->parent;
err:
return ret;
}
led_strip_t * led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num)
{
static led_strip_t *pStrip;
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, channel);
// set counter clock to 40MHz
config.clk_div = 2;
ESP_ERROR_CHECK(rmt_config(&config));
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
// install ws2812 driver
led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(led_num, (led_strip_dev_t)config.channel);
pStrip = led_strip_new_rmt_ws2812(&strip_config);
if ( !pStrip ) {
ESP_LOGE(TAG, "install WS2812 driver failed");
return NULL;
}
// Clear LED strip (turn off all LEDs)
ESP_ERROR_CHECK(pStrip->clear(pStrip, 100));
return pStrip;
}
esp_err_t led_strip_denit(led_strip_t *strip)
{
ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
ESP_ERROR_CHECK(rmt_driver_uninstall(ws2812->rmt_channel));
return strip->del(strip);
}

View File

@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt_tx.h"
#include "led_strip.h"
#include "led_strip_interface.h"
#include "led_strip_rmt_encoder.h"
#define LED_SRIP_RMT_RESOLUTION 10000000 // 10MHz resolution
static const char *TAG = "led_strip_rmt";
typedef struct {
led_strip_t base;
rmt_channel_handle_t rmt_chan;
rmt_encoder_handle_t strip_encoder;
uint32_t strip_len;
uint8_t pixel_buf[];
} led_strip_rmt_obj;
static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
uint32_t start = index * 3;
// In thr order of GRB, as LED strip like WS2812 sends out pixels in this order
rmt_strip->pixel_buf[start + 0] = green & 0xFF;
rmt_strip->pixel_buf[start + 1] = red & 0xFF;
rmt_strip->pixel_buf[start + 2] = blue & 0xFF;
return ESP_OK;
}
static esp_err_t led_strip_rmt_refresh(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
rmt_transmit_config_t tx_conf = {
.loop_count = 0,
};
ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf,
rmt_strip->strip_len * 3, &tx_conf), TAG, "transmit pixels by RMT failed");
ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed");
return ESP_OK;
}
static esp_err_t led_strip_rmt_clear(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
// Write zero to turn off all leds
memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * 3);
return led_strip_rmt_refresh(strip);
}
static esp_err_t led_strip_rmt_del(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed");
free(rmt_strip);
return ESP_OK;
}
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + config->max_leds * 3);
ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip");
rmt_tx_channel_config_t rmt_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.gpio_num = config->strip_gpio_num,
.mem_block_symbols = 64,
.resolution_hz = LED_SRIP_RMT_RESOLUTION,
.trans_queue_depth = 4,
};
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
led_strip_encoder_config_t strip_encoder_conf = {
.resolution = LED_SRIP_RMT_RESOLUTION,
};
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
ESP_GOTO_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), err, TAG, "enable RMT channel failed");
rmt_strip->strip_len = config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
if (rmt_strip->rmt_chan) {
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder) {
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_refresh(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->refresh(strip);
}
esp_err_t led_strip_clear(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->clear(strip);
}
esp_err_t led_strip_del(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->del(strip);
}

View File

@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "led_strip_rmt_encoder.h"
static const char *TAG = "led_encoder";
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_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(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: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
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 rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
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 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}

View File

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of led strip encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
} led_strip_encoder_config_t;
/**
* @brief Create RMT encoder for encoding LED strip pixels into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -29,14 +29,12 @@ See [Development Boards](https://www.espressif.com/en/products/devkits) for more
### Configure the Project
Open the project configuration menu (`idf.py menuconfig`).
Open the project configuration menu (`idf.py menuconfig`).
In the `Example Configuration` menu:
* Select the LED type in the `Blink LED type` option.
* Use `GPIO` for regular LED blink.
* Use `RMT` for addressable LED blink.
* Use `RMT Channel` to select the RMT peripheral channel.
* Set the GPIO number used for the signal in the `Blink GPIO number` option.
* Set the blinking period in the `Blink period in ms` option.
@ -50,7 +48,7 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
## Example Output
As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `pStrip_a->set_pixel(pStrip_a, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the `blink.c` file.
As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `led_strip_set_pixel(pStrip_a, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the `blink.c` file.
```
I (315) example: Example configured to blink addressable LED!

View File

@ -13,18 +13,6 @@ menu "Example Configuration"
bool "RMT - Addressable LED"
endchoice
config BLINK_LED_RMT_CHANNEL
depends on BLINK_LED_RMT
int "RMT Channel"
range 0 7
default 0
help
Set the RMT peripheral channel.
ESP32 RMT channel from 0 to 7
ESP32-S2 RMT channel from 0 to 3
ESP32-S3 RMT channel from 0 to 3
ESP32-C3 RMT channel from 0 to 1
config BLINK_GPIO
int "Blink GPIO number"
range 0 48

View File

@ -24,19 +24,20 @@ static const char *TAG = "example";
static uint8_t s_led_state = 0;
#ifdef CONFIG_BLINK_LED_RMT
static led_strip_t *pStrip_a;
static led_strip_handle_t led_strip;
static void blink_led(void)
{
/* If the addressable LED is enabled */
if (s_led_state) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
pStrip_a->set_pixel(pStrip_a, 0, 16, 16, 16);
led_strip_set_pixel(led_strip, 0, 16, 16, 16);
/* Refresh the strip to send data */
pStrip_a->refresh(pStrip_a, 100);
led_strip_refresh(led_strip);
} else {
/* Set all LED off to clear all pixels */
pStrip_a->clear(pStrip_a, 50);
led_strip_clear(led_strip);
}
}
@ -44,9 +45,13 @@ static void configure_led(void)
{
ESP_LOGI(TAG, "Example configured to blink addressable LED!");
/* LED strip initialization with the GPIO and pixels number*/
pStrip_a = led_strip_init(CONFIG_BLINK_LED_RMT_CHANNEL, BLINK_GPIO, 1);
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO,
.max_leds = 1, // at least one LED on board
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &led_strip));
/* Set all LED off to clear all pixels */
pStrip_a->clear(pStrip_a, 50);
led_strip_clear(led_strip);
}
#elif CONFIG_BLINK_LED_GPIO

View File

@ -1 +0,0 @@
#

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(morse_code)
project(dshot_esc)

View File

@ -0,0 +1,60 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# RMT Infinite Loop Transmit Example -- Dshot ESC (Electronic Speed Controller)
(See the README.md file in the upper level 'examples' directory for more information about examples.)
RMT TX channel can transmit symbols in an infinite loop, where the loop is totally controlled by the hardware. This feature is useful for scenarios where a device needs continuous stimulus.
The [DShot](https://github.com/betaflight/betaflight/wiki/Dshot) is a digital protocol between flight controller (FC) and ESC, which is more resistant to electrical noise than traditional analog protocols. The DShot protocol requires the FC to encode throttle information into pulses with various durations and send out the pulses periodically. This is what an RMT TX channel can perfectly do.
## How to Use Example
### Hardware Required
* A development board with any supported Espressif SOC chip (see `Supported Targets` table above)
* A USB cable for Power supply and programming
* An ESC that supports DShot protocol (this example will take the **DShot300** as an example)
Connection :
```
BLDC DShot ESC 12V GND
+--------+ +---------------+ | | ESP
| | | | | | +----------------------+
| U +-----+ U P+ +----+ | | |
| | | | | | |
| V +-----+ V P- +--------+ | |
| | | | | |
| W +-----+ W SIG +----------+ DSHOT_ESC_GPIO_NUM |
| | | GND +----------+ GND |
+--------+ +---------------+ +----------------------+
```
The GPIO number used in this example can be changed according to your board, by the macro `DSHOT_ESC_GPIO_NUM` defined in the [source file](main/dshot_esc_example_main.c).
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Console Output
```
I (0) cpu_start: Starting scheduler on APP CPU.
I (182) example: Create RMT TX channel
I (182) gpio: GPIO[43]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (182) example: Install Dshot ESC encoder
I (182) example: Start RMT TX channel
I (182) example: Start ESC by sending zero throttle for a while...
I (3182) example: Set throttle to 1000, no telemetry
```
The BLDC motor will beep when the ESC receives a burst of initialization pulses. And then starts high-speed rotation at the throttle set in the code.
## Troubleshooting
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "dshot_esc_example_main.c" "dshot_esc_encoder.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,165 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "dshot_esc_encoder.h"
static const char *TAG = "dshot_encoder";
/**
* @brief Type of Dshot ESC frame
*/
typedef union {
struct {
uint16_t crc: 4; /*!< CRC checksum */
uint16_t telemetry: 1; /*!< Telemetry request */
uint16_t throttle: 11; /*!< Throttle value */
};
uint16_t val;
} dshot_esc_frame_t;
#ifndef __cplusplus
_Static_assert(sizeof(dshot_esc_frame_t) == 0x02, "Invalid size of dshot_esc_frame_t structure");
#endif
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
rmt_symbol_word_t dshot_delay_symbol;
int state;
} rmt_dshot_esc_encoder_t;
static void make_dshot_frame(dshot_esc_frame_t *frame, uint16_t throttle, bool telemetry)
{
frame->throttle = throttle;
frame->telemetry = telemetry;
uint16_t val = frame->val;
uint8_t crc = ((val ^ (val >> 4) ^ (val >> 8)) & 0xF0) >> 4;;
frame->crc = crc;
val = frame->val;
// change the endian
frame->val = ((val & 0xFF) << 8) | ((val & 0xFF00) >> 8);
}
static size_t rmt_encode_dshot_esc(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = dshot_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = dshot_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
// convert user data into dshot frame
dshot_esc_throttle_t *throttle = (dshot_esc_throttle_t *)primary_data;
dshot_esc_frame_t frame = {};
make_dshot_frame(&frame, throttle->throttle, throttle->telemetry_req);
switch (dshot_encoder->state) {
case 0: // send the dshot frame
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &frame, sizeof(frame), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
dshot_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 += copy_encoder->encode(copy_encoder, channel, &dshot_encoder->dshot_delay_symbol,
sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
state |= RMT_ENCODING_COMPLETE;
dshot_encoder->state = 0; // 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
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_dshot_encoder(rmt_encoder_t *encoder)
{
rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base);
rmt_del_encoder(dshot_encoder->bytes_encoder);
rmt_del_encoder(dshot_encoder->copy_encoder);
free(dshot_encoder);
return ESP_OK;
}
static esp_err_t rmt_dshot_encoder_reset(rmt_encoder_t *encoder)
{
rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base);
rmt_encoder_reset(dshot_encoder->bytes_encoder);
rmt_encoder_reset(dshot_encoder->copy_encoder);
dshot_encoder->state = 0;
return ESP_OK;
}
esp_err_t rmt_new_dshot_esc_encoder(const dshot_esc_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_dshot_esc_encoder_t *dshot_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
dshot_encoder = calloc(1, sizeof(rmt_dshot_esc_encoder_t));
ESP_GOTO_ON_FALSE(dshot_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for musical score encoder");
dshot_encoder->base.encode = rmt_encode_dshot_esc;
dshot_encoder->base.del = rmt_del_dshot_encoder;
dshot_encoder->base.reset = rmt_dshot_encoder_reset;
uint32_t delay_ticks = config->resolution / 1e6 * config->post_delay_us;
rmt_symbol_word_t dshot_delay_symbol = {
.level0 = 0,
.duration0 = delay_ticks / 2,
.level1 = 0,
.duration1 = delay_ticks / 2,
};
dshot_encoder->dshot_delay_symbol = dshot_delay_symbol;
// different dshot protocol have its own timing requirements,
float period_ticks = (float)config->resolution / config->baud_rate;
// 1 and 0 is represented by a 74.850% and 37.425% duty cycle respectively
unsigned int t1h_ticks = (unsigned int)(period_ticks * 0.7485);
unsigned int t1l_ticks = (unsigned int)(period_ticks - t1h_ticks);
unsigned int t0h_ticks = (unsigned int)(period_ticks * 0.37425);
unsigned int t0l_ticks = (unsigned int)(period_ticks - t0h_ticks);
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {
.level0 = 1,
.duration0 = t0h_ticks,
.level1 = 0,
.duration1 = t0l_ticks,
},
.bit1 = {
.level0 = 1,
.duration0 = t1h_ticks,
.level1 = 0,
.duration1 = t1l_ticks,
},
.flags.msb_first = 1,
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &dshot_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &dshot_encoder->copy_encoder), err, TAG, "create copy encoder failed");
*ret_encoder = &dshot_encoder->base;
return ESP_OK;
err:
if (dshot_encoder) {
if (dshot_encoder->bytes_encoder) {
rmt_del_encoder(dshot_encoder->bytes_encoder);
}
if (dshot_encoder->copy_encoder) {
rmt_del_encoder(dshot_encoder->copy_encoder);
}
free(dshot_encoder);
}
return ret;
}

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Throttle representation in DShot protocol
*/
typedef struct {
uint16_t throttle; /*!< Throttle value */
bool telemetry_req; /*!< Telemetry request */
} dshot_esc_throttle_t;
/**
* @brief Type of Dshot ESC encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
uint32_t baud_rate; /*!< Dshot protocol runs at several different baud rates, e.g. DSHOT300 = 300k baud rate */
uint32_t post_delay_us; /*!< Delay time after one Dshot frame, in microseconds */
} dshot_esc_encoder_config_t;
/**
* @brief Create RMT encoder for encoding Dshot ESC frame into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating Dshot ESC encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_dshot_esc_encoder(const dshot_esc_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h"
#include "dshot_esc_encoder.h"
#define DSHOT_ESC_RESOLUTION_HZ 40000000 // 40MHz resolution, DSHot protocol needs a relative high resolution
#define DSHOT_ESC_GPIO_NUM 0
static const char *TAG = "example";
void app_main(void)
{
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_channel_handle_t esc_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select a clock that can provide needed resolution
.gpio_num = DSHOT_ESC_GPIO_NUM,
.mem_block_symbols = 64,
.resolution_hz = DSHOT_ESC_RESOLUTION_HZ,
.trans_queue_depth = 10, // set the number of transactions that can be pending in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &esc_chan));
ESP_LOGI(TAG, "Install Dshot ESC encoder");
rmt_encoder_handle_t dshot_encoder = NULL;
dshot_esc_encoder_config_t encoder_config = {
.resolution = DSHOT_ESC_RESOLUTION_HZ,
.baud_rate = 300000, // DSHOT300 protocol
.post_delay_us = 50, // extra delay between each frame
};
ESP_ERROR_CHECK(rmt_new_dshot_esc_encoder(&encoder_config, &dshot_encoder));
ESP_LOGI(TAG, "Enable RMT TX channel");
ESP_ERROR_CHECK(rmt_enable(esc_chan));
rmt_transmit_config_t tx_config = {
.loop_count = -1, // infinite loop
};
dshot_esc_throttle_t throttle = {
.throttle = 0,
.telemetry_req = false, // telemetry is not supported in this example
};
ESP_LOGI(TAG, "Start ESC by sending zero throttle for a while...");
ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config));
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "Increase throttle, no telemetry");
for (uint16_t thro = 100; thro < 1000; thro += 10) {
throttle.throttle = thro;
ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config));
// the previous loop transfer is till undergoing, we need to stop it and restart,
// so that the new throttle can be updated on the output
ESP_ERROR_CHECK(rmt_disable(esc_chan));
ESP_ERROR_CHECK(rmt_enable(esc_chan));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

View File

@ -0,0 +1,18 @@
# 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
def test_dshot_esc_example(dut: Dut) -> None:
dut.expect_exact('example: Create RMT TX channel')
dut.expect_exact('example: Install Dshot ESC encoder')
dut.expect_exact('example: Enable RMT TX channel')
dut.expect_exact('example: Start ESC by sending zero throttle for a while...')
dut.expect_exact('example: Increase throttle, no telemetry')

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ir_protocols)
project(ir_nec_transceiver)

View File

@ -0,0 +1,110 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
| ----------------- | ----- | -------- | -------- | -------- |
# IR NEC Encoding and Decoding Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
[NEC](https://www.sbprojects.net/knowledge/ir/nec.php) is a common use IR protocol, this example creates a TX channel and sends out the IR NEC signals periodically. The signal is modulated with a 38KHz carrier. The example also creates an RX channel, to receive and parse the IR NEC signals into scan codes.
## How to Use Example
### Hardware Required
* A development board with supported SoC mentioned in the above `Supported Targets` table
* An USB cable for power supply and programming
* A 5mm infrared LED (e.g. IR333C) used to transmit encoded IR signals
* An infrared receiver module (e.g. IRM-3638T), which integrates a demodulator and AGC circuit
### Hardware Connection
```
IR Receiver (IRM-3638T) ESP Board IR Transmitter (IR333C)
+--------------------------+ +----------------------+ +---------------------------+
| RX+-------+IR_RX_GPIO IR_TX_GPIO+--------------+TX |
| | | | | |
| 3V3+-------+3V3 5V+--------------+VCC |
| | | | | |
| GND+-------+GND GND+--------------+GND |
+--------------------------+ +----------------------+ +---------------------------+
```
The TX and RX GPIO number used by this example can be changed in [the source file](main/ir_nec_transceiver_main.c) via `EXAMPLE_IR_TX_GPIO_NUM` and `EXAMPLE_IR_RX_GPIO_NUM`.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
Run this example, you might see the following output log:
```
...
I (0) cpu_start: Starting scheduler on APP CPU.
I (306) example: create RMT RX channel
I (306) gpio: GPIO[19]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (316) example: register RX done callback
I (316) example: create RMT TX channel
I (326) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (336) example: modulate carrier to TX channel
I (336) example: install IR NEC encoder
I (346) example: start RMT TX and RX channel
NEC frame start---
{0:9020},{1:4461}
{0:577},{1:577}
{0:577},{1:576}
{0:552},{1:601}
{0:552},{1:601}
{0:577},{1:576}
{0:578},{1:575}
{0:583},{1:572}
{0:579},{1:574}
{0:576},{1:1648}
{0:553},{1:1673}
{0:579},{1:1647}
{0:580},{1:1645}
{0:577},{1:1649}
{0:554},{1:1673}
{0:578},{1:1648}
{0:553},{1:1673}
{0:555},{1:1671}
{0:578},{1:577}
{0:553},{1:1673}
{0:554},{1:1671}
{0:555},{1:601}
{0:580},{1:574}
{0:551},{1:603}
{0:580},{1:574}
{0:553},{1:601}
{0:553},{1:1672}
{0:554},{1:602}
{0:552},{1:603}
{0:579},{1:1646}
{0:554},{1:1672}
{0:555},{1:1672}
{0:580},{1:1646}
{0:555},{1:0}
---NEC frame end: Address=FF00, Command=F20D
NEC frame start---
{0:9024},{1:2213}
{0:583},{1:0}
---NEC frame end: Address=FF00, Command=F20D, repeat
NEC frame start---
{0:584},{1:0}
---NEC frame end: Unknown NEC frame
...
```
In the example's main loop, the RX channel waits for any NEC frames, if nothing received within 1 second, the TX channel will send out a predefined NEC frame (address=0x0440, command=0x3003).
## Troubleshooting
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "ir_nec_transceiver_main.c" "ir_nec_encoder.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "ir_nec_encoder.h"
static const char *TAG = "nec_encoder";
typedef struct {
rmt_encoder_t base; // the base "class", declares the standard encoder interface
rmt_encoder_t *copy_encoder; // use the copy_encoder to encode the leading and ending pulse
rmt_encoder_t *bytes_encoder; // use the bytes_encoder to encode the address and command data
rmt_symbol_word_t nec_leading_symbol; // NEC leading code with RMT representation
rmt_symbol_word_t nec_ending_symbol; // NEC ending code with RMT representation
int state;
} rmt_ir_nec_encoder_t;
static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base);
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data;
rmt_encoder_handle_t copy_encoder = nec_encoder->copy_encoder;
rmt_encoder_handle_t bytes_encoder = nec_encoder->bytes_encoder;
switch (nec_encoder->state) {
case 0: // send leading code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_leading_symbol,
sizeof(rmt_symbol_word_t), &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: // send address
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->address, sizeof(uint16_t), &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: // send command
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->command, sizeof(uint16_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 3; // 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 3: // send ending code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_ending_symbol,
sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
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_ir_nec_encoder(rmt_encoder_t *encoder)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_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_ir_nec_encoder_reset(rmt_encoder_t *encoder)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_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 rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_ir_nec_encoder_t *nec_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
nec_encoder = calloc(1, sizeof(rmt_ir_nec_encoder_t));
ESP_GOTO_ON_FALSE(nec_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for ir nec encoder");
nec_encoder->base.encode = rmt_encode_ir_nec;
nec_encoder->base.del = rmt_del_ir_nec_encoder;
nec_encoder->base.reset = rmt_ir_nec_encoder_reset;
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &nec_encoder->copy_encoder), err, TAG, "create copy encoder failed");
// construct the leading code and ending code with RMT symbol format
nec_encoder->nec_leading_symbol = (rmt_symbol_word_t) {
.level0 = 1,
.duration0 = 9000ULL * config->resolution / 1000000,
.level1 = 0,
.duration1 = 4500ULL * config->resolution / 1000000,
};
nec_encoder->nec_ending_symbol = (rmt_symbol_word_t) {
.level0 = 1,
.duration0 = 560 * config->resolution / 1000000,
.level1 = 0,
.duration1 = 0x7FFF,
};
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {
.level0 = 1,
.duration0 = 560 * config->resolution / 1000000, // T0H=560us
.level1 = 0,
.duration1 = 560 * config->resolution / 1000000, // T0L=560us
},
.bit1 = {
.level0 = 1,
.duration0 = 560 * config->resolution / 1000000, // T1H=560us
.level1 = 0,
.duration1 = 1690 * config->resolution / 1000000, // T1L=1690us
},
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &nec_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
*ret_encoder = &nec_encoder->base;
return ESP_OK;
err:
if (nec_encoder) {
if (nec_encoder->bytes_encoder) {
rmt_del_encoder(nec_encoder->bytes_encoder);
}
if (nec_encoder->copy_encoder) {
rmt_del_encoder(nec_encoder->copy_encoder);
}
free(nec_encoder);
}
return ret;
}

View File

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief IR NEC scan code representation
*/
typedef struct {
uint16_t address;
uint16_t command;
} ir_nec_scan_code_t;
/**
* @brief Type of IR NEC encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
} ir_nec_encoder_config_t;
/**
* @brief Create RMT encoder for encoding IR NEC frame into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating IR NEC encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,233 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "ir_nec_encoder.h"
#define EXAMPLE_IR_RESOLUTION_HZ 1000000 // 1MHz resolution, 1 tick = 1us
#define EXAMPLE_IR_TX_GPIO_NUM 18
#define EXAMPLE_IR_RX_GPIO_NUM 19
#define EXAMPLE_IR_NEC_DECODE_MARGIN 200 // Tolerance for parsing RMT symbols into bit stream
/**
* @brief NEC timing spec
*/
#define NEC_LEADING_CODE_DURATION_0 9000
#define NEC_LEADING_CODE_DURATION_1 4500
#define NEC_PAYLOAD_ZERO_DURATION_0 560
#define NEC_PAYLOAD_ZERO_DURATION_1 560
#define NEC_PAYLOAD_ONE_DURATION_0 560
#define NEC_PAYLOAD_ONE_DURATION_1 1690
#define NEC_REPEAT_CODE_DURATION_0 9000
#define NEC_REPEAT_CODE_DURATION_1 2250
static const char *TAG = "example";
/**
* @brief Saving NEC decode results
*/
static uint16_t s_nec_code_address;
static uint16_t s_nec_code_command;
/**
* @brief Check whether a duration is within expected range
*/
static inline bool nec_check_in_range(uint32_t signal_duration, uint32_t spec_duration)
{
return (signal_duration < (spec_duration + EXAMPLE_IR_NEC_DECODE_MARGIN)) &&
(signal_duration > (spec_duration - EXAMPLE_IR_NEC_DECODE_MARGIN));
}
/**
* @brief Check whether a RMT symbol represents NEC logic zero
*/
static bool nec_parse_logic0(rmt_symbol_word_t *rmt_nec_symbols)
{
return nec_check_in_range(rmt_nec_symbols->duration0, NEC_PAYLOAD_ZERO_DURATION_0) &&
nec_check_in_range(rmt_nec_symbols->duration1, NEC_PAYLOAD_ZERO_DURATION_1);
}
/**
* @brief Check whether a RMT symbol represents NEC logic one
*/
static bool nec_parse_logic1(rmt_symbol_word_t *rmt_nec_symbols)
{
return nec_check_in_range(rmt_nec_symbols->duration0, NEC_PAYLOAD_ONE_DURATION_0) &&
nec_check_in_range(rmt_nec_symbols->duration1, NEC_PAYLOAD_ONE_DURATION_1);
}
/**
* @brief Decode RMT symbols into NEC address and command
*/
static bool nec_parse_frame(rmt_symbol_word_t *rmt_nec_symbols)
{
rmt_symbol_word_t *cur = rmt_nec_symbols;
uint16_t address = 0;
uint16_t command = 0;
bool valid_leading_code = nec_check_in_range(cur->duration0, NEC_LEADING_CODE_DURATION_0) &&
nec_check_in_range(cur->duration1, NEC_LEADING_CODE_DURATION_1);
if (!valid_leading_code) {
return false;
}
cur++;
for (int i = 0; i < 16; i++) {
if (nec_parse_logic1(cur)) {
address |= 1 << i;
} else if (nec_parse_logic0(cur)) {
address &= ~(1 << i);
} else {
return false;
}
cur++;
}
for (int i = 0; i < 16; i++) {
if (nec_parse_logic1(cur)) {
command |= 1 << i;
} else if (nec_parse_logic0(cur)) {
command &= ~(1 << i);
} else {
return false;
}
cur++;
}
// save address and command
s_nec_code_address = address;
s_nec_code_command = command;
return true;
}
/**
* @brief Check whether the RMT symbols represent NEC repeat code
*/
static bool nec_parse_frame_repeat(rmt_symbol_word_t *rmt_nec_symbols)
{
return nec_check_in_range(rmt_nec_symbols->duration0, NEC_REPEAT_CODE_DURATION_0) &&
nec_check_in_range(rmt_nec_symbols->duration1, NEC_REPEAT_CODE_DURATION_1);
}
/**
* @brief Decode RMT symbols into NEC scan code and print the result
*/
static void example_parse_nec_frame(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num)
{
printf("NEC frame start---\r\n");
for (size_t i = 0; i < symbol_num; i++) {
printf("{%d:%d},{%d:%d}\r\n", rmt_nec_symbols[i].level0, rmt_nec_symbols[i].duration0,
rmt_nec_symbols[i].level1, rmt_nec_symbols[i].duration1);
}
printf("---NEC frame end: ");
// decode RMT symbols
switch (symbol_num) {
case 34: // NEC normal frame
if (nec_parse_frame(rmt_nec_symbols)) {
printf("Address=%04X, Command=%04X\r\n\r\n", s_nec_code_address, s_nec_code_command);
}
break;
case 2: // NEC repeat frame
if (nec_parse_frame_repeat(rmt_nec_symbols)) {
printf("Address=%04X, Command=%04X, repeat\r\n\r\n", s_nec_code_address, s_nec_code_command);
}
break;
default:
printf("Unknown NEC frame\r\n\r\n");
break;
}
}
static bool example_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;
TaskHandle_t task_to_notify = (TaskHandle_t)user_data;
// send the received RMT symbols to the parser task
xTaskNotifyFromISR(task_to_notify, (uint32_t)edata, eSetValueWithOverwrite, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
void app_main(void)
{
ESP_LOGI(TAG, "create RMT RX channel");
rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = EXAMPLE_IR_RESOLUTION_HZ,
.mem_block_symbols = 64, // amount of RMT symbols that the channel can store at a time
.gpio_num = EXAMPLE_IR_RX_GPIO_NUM,
};
rmt_channel_handle_t rx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel));
ESP_LOGI(TAG, "register RX done callback");
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = example_rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel, &cbs, cur_task));
// the following timing requirement is based on NEC protocol
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560us, 1250ns < 560us, valid signal won't be treated as noise
.signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000us, 12000000ns > 9000us, the receive won't stop early
};
ESP_LOGI(TAG, "create RMT TX channel");
rmt_tx_channel_config_t tx_channel_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = EXAMPLE_IR_RESOLUTION_HZ,
.mem_block_symbols = 64, // amount of RMT symbols that the channel can store at a time
.trans_queue_depth = 4, // number of transactions that allowed to pending in the background, this example won't queue multiple transactions, so queue depth > 1 is sufficient
.gpio_num = EXAMPLE_IR_TX_GPIO_NUM,
};
rmt_channel_handle_t tx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel));
ESP_LOGI(TAG, "modulate carrier to TX channel");
rmt_carrier_config_t carrier_cfg = {
.duty_cycle = 0.33,
.frequency_hz = 38000, // 38KHz
};
ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg));
// this example won't send NEC frames in a loop
rmt_transmit_config_t transmit_config = {
.loop_count = 0, // no loop
};
ESP_LOGI(TAG, "install IR NEC encoder");
ir_nec_encoder_config_t nec_encoder_cfg = {
.resolution = EXAMPLE_IR_RESOLUTION_HZ,
};
rmt_encoder_handle_t nec_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder));
ESP_LOGI(TAG, "enable RMT TX and RX channels");
ESP_ERROR_CHECK(rmt_enable(tx_channel));
ESP_ERROR_CHECK(rmt_enable(rx_channel));
// save the received RMT symbols
rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame
rmt_rx_done_event_data_t *rx_data = NULL;
// ready to receive
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
while (1) {
// wait for RX done signal
if (xTaskNotifyWait(0x00, ULONG_MAX, (uint32_t *)&rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) {
// parse the receive symbols and print the result
example_parse_nec_frame(rx_data->received_symbols, rx_data->num_symbols);
// start receive again
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
} else {
// timeout, transmit predefined IR NEC packets
const ir_nec_scan_code_t scan_code = {
.address = 0x0440,
.command = 0x3003,
};
ESP_ERROR_CHECK(rmt_transmit(tx_channel, nec_encoder, &scan_code, sizeof(scan_code), &transmit_config));
}
}
}

View File

@ -0,0 +1,19 @@
# 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.ir_transceiver
def test_ir_nec_example(dut: Dut) -> None:
dut.expect_exact('example: create RMT RX channel')
dut.expect_exact('example: register RX done callback')
dut.expect_exact('example: create RMT TX channel')
dut.expect_exact('example: modulate carrier to TX channel')
dut.expect_exact('example: install IR NEC encoder')
dut.expect_exact('example: enable RMT TX and RX channels')
dut.expect_exact('NEC frame start---')
dut.expect_exact('---NEC frame end: Address=0440, Command=3003')
dut.expect_exact('---NEC frame end: Address=0440, Command=3003')

Some files were not shown because too many files have changed in this diff Show More