Merge branch 'feat/bitscrambler_rmt' into 'master'

feat(rmt): play the BitScrambler as an RMT encoder

Closes IDF-12018

See merge request espressif/esp-idf!37542
This commit is contained in:
morris
2025-04-08 11:51:19 +08:00
20 changed files with 457 additions and 110 deletions

View File

@@ -11,4 +11,5 @@ endif()
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES "esp_mm"
INCLUDE_DIRS "include")
INCLUDE_DIRS "include"
LDFRAGMENTS "linker.lf")

View File

@@ -0,0 +1,16 @@
menu "BitScrambler Configurations"
depends on SOC_BITSCRAMBLER_SUPPORTED
config BITSCRAMBLER_CTRL_FUNC_IN_IRAM
bool "Place BitScrambler control functions in IRAM"
default n
select BITSCRAMBLER_OBJ_CACHE_SAFE
help
Place BitScrambler control functions into IRAM for better performance and fewer cache misses.
config BITSCRAMBLER_OBJ_CACHE_SAFE
bool
default n
help
This will ensure the BitScrambler object will not be allocated from a memory region
where its cache can be disabled.
endmenu # BitScrambler Configurations

View File

@@ -0,0 +1,6 @@
[mapping:bitscrambler_driver]
archive: libesp_driver_bitscrambler.a
entries:
if BITSCRAMBLER_CTRL_FUNC_IN_IRAM = y:
bitscrambler: bitscrambler_reset (noflash)
bitscrambler: bitscrambler_start (noflash)

View File

@@ -6,6 +6,7 @@
#include <string.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "driver/bitscrambler.h"
#include "bitscrambler_private.h"
#include "hal/bitscrambler_ll.h"
@@ -13,6 +14,12 @@
static const char *TAG = "bitscrambler";
#if CONFIG_BITSCRAMBLER_OBJ_CACHE_SAFE
#define BITSCRAMBLER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define BITSCRAMBLER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif
#define BITSCRAMBLER_BINARY_VER 1 //max version we're compatible with
#define BITSCRAMBLER_HW_REV 0
@@ -153,8 +160,8 @@ esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_han
if (!handle) {
return ESP_ERR_INVALID_ARG;
}
// Allocate memory for private data
bitscrambler_t *bs = calloc(1, sizeof(bitscrambler_t));
// Allocate memory for the BitScrambler object from internal memory
bitscrambler_t *bs = heap_caps_calloc(1, sizeof(bitscrambler_t), BITSCRAMBLER_MEM_ALLOC_CAPS);
if (!bs) {
return ESP_ERR_NO_MEM;
}
@@ -173,7 +180,7 @@ esp_err_t bitscrambler_new(const bitscrambler_config_t *config, bitscrambler_han
return r;
}
// Done.
// Return the handle
*handle = bs;
return ESP_OK;
}

View File

@@ -12,10 +12,14 @@ if(CONFIG_SOC_RMT_SUPPORTED)
"src/rmt_tx.c")
endif()
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED AND CONFIG_SOC_RMT_SUPPORT_DMA)
list(APPEND srcs "src/rmt_encoder_bs.c")
endif()
if(${target} STREQUAL "linux")
set(priv_requires "")
else()
set(priv_requires esp_pm esp_driver_gpio esp_mm)
set(priv_requires esp_pm esp_driver_gpio esp_driver_bitscrambler esp_mm)
endif()
idf_component_register(SRCS ${srcs}

View File

@@ -5,6 +5,8 @@ menu "ESP-Driver:RMT Configurations"
bool "Place RMT TX ISR handler in IRAM to reduce latency"
default y
select RMT_OBJ_CACHE_SAFE
select GDMA_CTRL_FUNC_IN_IRAM if SOC_RMT_SUPPORT_DMA
select BITSCRAMBLER_CTRL_FUNC_IN_IRAM if SOC_BITSCRAMBLER_SUPPORTED && SOC_RMT_SUPPORT_DMA
help
Place RMT TX ISR handler in IRAM to reduce latency caused by cache miss.
@@ -19,6 +21,7 @@ menu "ESP-Driver:RMT Configurations"
bool "Place RMT receive function in IRAM"
default n
select RMT_OBJ_CACHE_SAFE
select GDMA_CTRL_FUNC_IN_IRAM if SOC_RMT_SUPPORT_DMA
help
Place RMT receive function into IRAM for better performance and fewer cache misses.

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -26,6 +26,7 @@ typedef enum {
RMT_ENCODING_RESET = 0, /*!< The encoding session is in reset state */
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_ENCODING_WITH_EOF = (1 << 2), /*!< The encoding session has inserted the EOF marker to the symbol stream */
} rmt_encode_state_t;
/**
@@ -126,6 +127,13 @@ typedef struct {
typedef struct {
} rmt_copy_encoder_config_t;
/**
* @brief BitScrambler encoder configuration
*/
typedef struct {
const void *program_bin; /*!< BitScrambler program */
} rmt_bs_encoder_config_t;
/**
* @brief Simple callback encoder configuration
*/
@@ -169,6 +177,8 @@ esp_err_t rmt_bytes_encoder_update_config(rmt_encoder_handle_t bytes_encoder, co
/**
* @brief Create RMT copy encoder, which copies the given RMT symbols into RMT memory
*
* @note When transmitting using a copy encoder, ensure that the input data is already formatted as `rmt_symbol_word_t`.
*
* @param[in] config Copy encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
@@ -179,6 +189,22 @@ esp_err_t rmt_bytes_encoder_update_config(rmt_encoder_handle_t bytes_encoder, co
*/
esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
/**
* @brief Create RMT BitScrambler encoder
*
* @note The BitScrambler encoder is used to encode the user data into RMT symbols by providing the BitScrambler assembly program.
* The BitScrambler program is a binary blob, it should take control of the whole encoding stuffs, including inserting the EOF marker.
*
* @param[in] config BitScrambler encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_OK: Create RMT BitScrambler encoder successfully
* - ESP_ERR_INVALID_ARG: Create RMT BitScrambler encoder failed because of invalid argument
* - ESP_ERR_NO_MEM: Create RMT BitScrambler encoder failed because out of memory
* - ESP_FAIL: Create RMT BitScrambler encoder failed because of other error
*/
esp_err_t rmt_new_bitscrambler_encoder(const rmt_bs_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
/**
* @brief Create RMT simple callback encoder, which uses a callback to convert incoming
* data into RMT symbols.

View File

@@ -8,9 +8,13 @@ entries:
rmt_tx: rmt_tx_do_transaction (noflash)
rmt_tx: rmt_encode_check_result (noflash)
rmt_tx: rmt_tx_mark_eof (noflash)
rmt_encoder: rmt_encoder_reset (noflash)
rmt_encoder_bytes: rmt_encode_bytes (noflash)
rmt_encoder_bytes: rmt_bytes_encoder_reset (noflash)
rmt_encoder_copy: rmt_encode_copy (noflash)
rmt_encoder_copy: rmt_copy_encoder_reset (noflash)
rmt_encoder_simple: rmt_encode_simple (noflash)
rmt_encoder_simple: rmt_simple_encoder_reset (noflash)
if SOC_RMT_SUPPORT_TX_LOOP_COUNT = y:
rmt_tx: rmt_isr_handle_tx_loop_end (noflash)
@@ -18,6 +22,10 @@ entries:
if SOC_RMT_SUPPORT_DMA = y:
rmt_tx: rmt_dma_tx_eof_cb (noflash)
if SOC_BITSCRAMBLER_SUPPORTED = y:
rmt_encoder_bs: rmt_encode_bs (noflash)
rmt_encoder_bs: rmt_bs_encoder_reset (noflash)
if RMT_RX_ISR_HANDLER_IN_IRAM = y:
rmt_rx: rmt_rx_default_isr (noflash)
rmt_rx: rmt_isr_handle_rx_done (noflash)
@@ -30,51 +38,3 @@ entries:
if RMT_RECV_FUNC_IN_IRAM = y:
rmt_rx: rmt_receive (noflash)
[mapping:rmt_driver_gdma]
archive: libesp_hw_support.a
entries:
if RMT_TX_ISR_HANDLER_IN_IRAM = y && SOC_RMT_SUPPORT_DMA = y:
gdma: gdma_reset (noflash)
gdma: gdma_start (noflash)
gdma: gdma_append (noflash)
if RMT_RECV_FUNC_IN_IRAM = y && SOC_RMT_SUPPORT_DMA = y:
gdma: gdma_reset (noflash)
gdma: gdma_start (noflash)
[mapping:rmt_driver_hal]
archive: libhal.a
entries:
if RMT_TX_ISR_HANDLER_IN_IRAM = y:
if SOC_RMT_SUPPORT_DMA = y:
gdma_hal_top: gdma_hal_append (noflash)
gdma_hal_top: gdma_hal_reset (noflash)
gdma_hal_top: gdma_hal_start_with_desc (noflash)
# GDMA implementation layer for AHB-DMA version 1
if SOC_AHB_GDMA_VERSION = 1:
gdma_hal_ahb_v1: gdma_ahb_hal_append (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_reset (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_start_with_desc (noflash)
# GDMA implementation layer for AHB-DMA version 2
if SOC_AHB_GDMA_VERSION = 2:
gdma_hal_ahb_v2: gdma_ahb_hal_append (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_reset (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_start_with_desc (noflash)
if RMT_RECV_FUNC_IN_IRAM = y:
if SOC_RMT_SUPPORT_DMA = y:
gdma_hal_top: gdma_hal_reset (noflash)
gdma_hal_top: gdma_hal_start_with_desc (noflash)
# GDMA implementation layer for AHB-DMA version 1
if SOC_AHB_GDMA_VERSION = 1:
gdma_hal_ahb_v1: gdma_ahb_hal_reset (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_start_with_desc (noflash)
# GDMA implementation layer for AHB-DMA version 2
if SOC_AHB_GDMA_VERSION = 2:
gdma_hal_ahb_v2: gdma_ahb_hal_reset (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_start_with_desc (noflash)

View File

@@ -0,0 +1,143 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "driver/rmt_encoder.h"
#include "driver/bitscrambler.h"
#include "rmt_private.h"
typedef struct rmt_bs_encoder_t {
rmt_encoder_t base; // the base "class", declares the standard encoder interface
size_t last_byte_index; // index of the encoding byte in the primary stream
bitscrambler_handle_t bs; // BitScrambler handle
} rmt_bs_encoder_t;
static esp_err_t rmt_bs_encoder_reset(rmt_encoder_t *encoder)
{
rmt_bs_encoder_t *bs_encoder = __containerof(encoder, rmt_bs_encoder_t, base);
bs_encoder->last_byte_index = 0;
bitscrambler_reset(bs_encoder->bs);
bitscrambler_start(bs_encoder->bs);
return ESP_OK;
}
static esp_err_t rmt_del_bs_encoder(rmt_encoder_t *encoder)
{
rmt_bs_encoder_t *bs_encoder = __containerof(encoder, rmt_bs_encoder_t, base);
bitscrambler_free(bs_encoder->bs);
free(bs_encoder);
return ESP_OK;
}
static size_t rmt_encode_bs(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *input_raw,
size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_bs_encoder_t *bs_encoder = __containerof(encoder, rmt_bs_encoder_t, base);
rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base);
uint8_t *input_bytes = (uint8_t *)input_raw;
rmt_encode_state_t state = RMT_ENCODING_RESET;
rmt_dma_descriptor_t *desc0 = NULL;
rmt_dma_descriptor_t *desc1 = NULL;
// bitscrambler encoder must be used with a TX channel with DMA enabled
assert(tx_chan->base.dma_chan != NULL);
size_t byte_index = bs_encoder->last_byte_index;
// how many bytes will be copied by the encoder
size_t mem_want = (data_size - byte_index);
// how many bytes we can save for this round
size_t mem_have = tx_chan->mem_end * sizeof(rmt_symbol_word_t) - tx_chan->mem_off_bytes;
// target memory buffer to copy to
uint8_t *mem_to_nc = (uint8_t*)tx_chan->dma_mem_base_nc;
// how many bytes will be copied in this round
size_t copy_len = MIN(mem_want, mem_have);
bool encoding_truncated = mem_have < mem_want;
bool encoding_space_free = mem_have > mem_want;
// mark the start descriptor
if (tx_chan->mem_off_bytes < tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) {
desc0 = &tx_chan->dma_nodes_nc[0];
} else {
desc0 = &tx_chan->dma_nodes_nc[1];
}
size_t len = copy_len;
while (len > 0) {
mem_to_nc[tx_chan->mem_off_bytes++] = input_bytes[byte_index++];
len--;
}
// mark the end descriptor
if (tx_chan->mem_off_bytes < tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) {
desc1 = &tx_chan->dma_nodes_nc[0];
} else {
desc1 = &tx_chan->dma_nodes_nc[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
bs_encoder->last_byte_index = byte_index;
} else {
// reset internal index if encoding session has finished
bs_encoder->last_byte_index = 0;
// bitscrambler program will take care of the EOF marker by itself
// so we don't rely on the TX driver to inject an EOF marker
state |= RMT_ENCODING_COMPLETE | RMT_ENCODING_WITH_EOF;
}
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_bytes >= tx_chan->ping_pong_symbols * 2 * sizeof(rmt_symbol_word_t)) {
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_bytes = 0;
}
*ret_state = state;
return copy_len;
}
esp_err_t rmt_new_bitscrambler_encoder(const rmt_bs_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_bs_encoder_t *encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
encoder = rmt_alloc_encoder_mem(sizeof(rmt_bs_encoder_t));
ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for bitscrambler encoder");
bitscrambler_config_t bs_config = {
.dir = BITSCRAMBLER_DIR_TX,
.attach_to = SOC_BITSCRAMBLER_ATTACH_RMT,
};
ESP_GOTO_ON_ERROR(bitscrambler_new(&bs_config, &encoder->bs), err, TAG, "create bitscrambler failed");
// load the bitscrambler program
ESP_GOTO_ON_ERROR(bitscrambler_load_program(encoder->bs, config->program_bin), err, TAG, "load bitscrambler program failed");
encoder->base.encode = rmt_encode_bs;
encoder->base.del = rmt_del_bs_encoder;
encoder->base.reset = rmt_bs_encoder_reset;
// return general encoder handle
*ret_encoder = &encoder->base;
ESP_LOGD(TAG, "new bitscrambler encoder @%p", encoder);
return ESP_OK;
err:
if (encoder) {
if (encoder->bs) {
bitscrambler_free(encoder->bs);
}
}
return ret;
}

View File

@@ -42,7 +42,8 @@ static size_t rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t chan
// 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;
size_t symbol_off = tx_chan->mem_off_bytes / sizeof(rmt_symbol_word_t);
size_t mem_have = tx_chan->mem_end - symbol_off;
// where to put the encoded symbols? DMA buffer or RMT HW memory
rmt_symbol_word_t *mem_to_nc = NULL;
if (channel->dma_chan) {
@@ -57,7 +58,7 @@ static size_t rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t chan
if (channel->dma_chan) {
// mark the start descriptor
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
if (symbol_off < tx_chan->ping_pong_symbols) {
desc0 = &tx_chan->dma_nodes_nc[0];
} else {
desc0 = &tx_chan->dma_nodes_nc[1];
@@ -74,9 +75,9 @@ static size_t rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t chan
}
while ((len > 0) && (bit_index < 8)) {
if (cur_byte & (1 << bit_index)) {
mem_to_nc[tx_chan->mem_off++] = bytes_encoder->bit1;
mem_to_nc[symbol_off++] = bytes_encoder->bit1;
} else {
mem_to_nc[tx_chan->mem_off++] = bytes_encoder->bit0;
mem_to_nc[symbol_off++] = bytes_encoder->bit0;
}
len--;
bit_index++;
@@ -89,7 +90,7 @@ static size_t rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t chan
if (channel->dma_chan) {
// mark the end descriptor
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
if (symbol_off < tx_chan->ping_pong_symbols) {
desc1 = &tx_chan->dma_nodes_nc[0];
} else {
desc1 = &tx_chan->dma_nodes_nc[1];
@@ -119,12 +120,14 @@ static size_t rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t chan
}
// reset offset pointer when exceeds maximum range
if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) {
if (symbol_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;
tx_chan->mem_off_bytes = 0;
} else {
tx_chan->mem_off_bytes = symbol_off * sizeof(rmt_symbol_word_t);
}
*ret_state = state;

View File

@@ -20,11 +20,11 @@ static esp_err_t rmt_copy_encoder_reset(rmt_encoder_t *encoder)
}
static size_t 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)
const void *input_symbols, 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_symbol_word_t *symbols = (rmt_symbol_word_t *)input_symbols;
rmt_encode_state_t state = RMT_ENCODING_RESET;
rmt_dma_descriptor_t *desc0 = NULL;
rmt_dma_descriptor_t *desc1 = NULL;
@@ -33,7 +33,8 @@ static size_t rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t chann
// 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;
size_t symbol_off = tx_chan->mem_off_bytes / sizeof(rmt_symbol_word_t);
size_t mem_have = tx_chan->mem_end - symbol_off;
// where to put the encoded symbols? DMA buffer or RMT HW memory
rmt_symbol_word_t *mem_to_nc = NULL;
if (channel->dma_chan) {
@@ -48,7 +49,7 @@ static size_t rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t chann
if (channel->dma_chan) {
// mark the start descriptor
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
if (symbol_off < tx_chan->ping_pong_symbols) {
desc0 = &tx_chan->dma_nodes_nc[0];
} else {
desc0 = &tx_chan->dma_nodes_nc[1];
@@ -57,13 +58,13 @@ static size_t rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t chann
size_t len = encode_len;
while (len > 0) {
mem_to_nc[tx_chan->mem_off++] = symbols[symbol_index++];
mem_to_nc[symbol_off++] = symbols[symbol_index++];
len--;
}
if (channel->dma_chan) {
// mark the end descriptor
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
if (symbol_off < tx_chan->ping_pong_symbols) {
desc1 = &tx_chan->dma_nodes_nc[0];
} else {
desc1 = &tx_chan->dma_nodes_nc[1];
@@ -91,12 +92,14 @@ static size_t rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t chann
}
// reset offset pointer when exceeds maximum range
if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) {
if (symbol_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;
tx_chan->mem_off_bytes = 0;
} else {
tx_chan->mem_off_bytes = symbol_off * sizeof(rmt_symbol_word_t);
}
*ret_state = state;

View File

@@ -37,6 +37,7 @@ static size_t rmt_encode_simple(rmt_encoder_t *encoder, rmt_channel_handle_t cha
rmt_encode_state_t state = RMT_ENCODING_RESET;
rmt_dma_descriptor_t *desc0 = NULL;
rmt_dma_descriptor_t *desc1 = NULL;
size_t symbol_off = tx_chan->mem_off_bytes / sizeof(rmt_symbol_word_t);
// where to put the encoded symbols? DMA buffer or RMT HW memory
rmt_symbol_word_t *mem_to_nc = NULL;
@@ -48,7 +49,7 @@ static size_t rmt_encode_simple(rmt_encoder_t *encoder, rmt_channel_handle_t cha
if (channel->dma_chan) {
// mark the start descriptor
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
if (symbol_off < tx_chan->ping_pong_symbols) {
desc0 = &tx_chan->dma_nodes_nc[0];
} else {
desc0 = &tx_chan->dma_nodes_nc[1];
@@ -69,11 +70,11 @@ static size_t rmt_encode_simple(rmt_encoder_t *encoder, rmt_channel_handle_t cha
// when then called with a larger buffer.
size_t encode_len = 0; //total amount of symbols written to rmt memory
bool is_done = false;
while (tx_chan->mem_off < tx_chan->mem_end) {
while (symbol_off < tx_chan->mem_end) {
if (simple_encoder->ovf_buf_parsed_pos < simple_encoder->ovf_buf_fill_len) {
// Overflow buffer has data from the previous encoding call. Copy one entry
// from that.
mem_to_nc[tx_chan->mem_off++] = simple_encoder->ovf_buf[simple_encoder->ovf_buf_parsed_pos++];
mem_to_nc[symbol_off++] = simple_encoder->ovf_buf[simple_encoder->ovf_buf_parsed_pos++];
encode_len++;
} else {
// Overflow buffer is empty, so we don't need to empty that first.
@@ -87,11 +88,11 @@ static size_t rmt_encode_simple(rmt_encoder_t *encoder, rmt_channel_handle_t cha
// Try to have the callback write the data directly into RMT memory.
size_t enc_size = simple_encoder->callback(data, data_size,
simple_encoder->last_symbol_index,
tx_chan->mem_end - tx_chan->mem_off,
&mem_to_nc[tx_chan->mem_off],
tx_chan->mem_end - symbol_off,
&mem_to_nc[symbol_off],
&is_done, simple_encoder->arg);
encode_len += enc_size;
tx_chan->mem_off += enc_size;
symbol_off += enc_size;
simple_encoder->last_symbol_index += enc_size;
if (is_done) {
break; // we're done, no more data to write to RMT memory.
@@ -130,7 +131,7 @@ static size_t rmt_encode_simple(rmt_encoder_t *encoder, rmt_channel_handle_t cha
if (channel->dma_chan) {
// mark the end descriptor
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
if (symbol_off < tx_chan->ping_pong_symbols) {
desc1 = &tx_chan->dma_nodes_nc[0];
} else {
desc1 = &tx_chan->dma_nodes_nc[1];
@@ -153,12 +154,14 @@ static size_t rmt_encode_simple(rmt_encoder_t *encoder, rmt_channel_handle_t cha
}
// reset offset pointer when exceeds maximum range
if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) {
if (symbol_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;
tx_chan->mem_off_bytes = 0;
} else {
tx_chan->mem_off_bytes = symbol_off * sizeof(rmt_symbol_word_t);
}
*ret_state = state;

View File

@@ -183,6 +183,7 @@ typedef struct {
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)
uint32_t need_eof_mark: 1; // Indicate whether need to insert an EOF mark (a special RMT symbol)
} flags;
} rmt_tx_trans_desc_t;
@@ -191,7 +192,7 @@ struct rmt_tx_channel_t {
rmt_channel_t base; // channel base class
rmt_symbol_word_t *dma_mem_base; // base address of RMT channel DMA buffer
rmt_symbol_word_t *dma_mem_base_nc; // base address of RMT channel DMA buffer, accessed in non-cached way
size_t mem_off; // runtime argument, indicating the next writing position in the RMT hardware memory
size_t mem_off_bytes; // runtime argument, indicating the next writing position in the RMT hardware memory, the offset unit is in bytes
size_t mem_end; // runtime argument, indicating 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

View File

@@ -589,39 +589,43 @@ esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t channel, int timeout_ms)
return ESP_OK;
}
static void rmt_tx_mark_eof(rmt_tx_channel_t *tx_chan)
static size_t rmt_tx_mark_eof(rmt_tx_channel_t *tx_chan, bool need_eof_marker)
{
rmt_channel_t *channel = &tx_chan->base;
rmt_group_t *group = channel->group;
int channel_id = channel->channel_id;
rmt_symbol_word_t *mem_to_nc = NULL;
rmt_tx_trans_desc_t *cur_trans = tx_chan->cur_trans;
rmt_dma_descriptor_t *desc_nc = NULL;
if (channel->dma_chan) {
mem_to_nc = tx_chan->dma_mem_base_nc;
} else {
mem_to_nc = channel->hw_mem_base;
}
// a RMT word whose duration is zero means a "stop" pattern
mem_to_nc[tx_chan->mem_off++] = (rmt_symbol_word_t) {
.duration0 = 0,
.level0 = cur_trans->flags.eot_level,
.duration1 = 0,
.level1 = cur_trans->flags.eot_level,
};
if (need_eof_marker) {
rmt_symbol_word_t *mem_to_nc = NULL;
if (channel->dma_chan) {
mem_to_nc = tx_chan->dma_mem_base_nc;
} else {
mem_to_nc = channel->hw_mem_base;
}
size_t symbol_off = tx_chan->mem_off_bytes / sizeof(rmt_symbol_word_t);
// a RMT word whose duration is zero means a "stop" pattern
mem_to_nc[symbol_off] = (rmt_symbol_word_t) {
.duration0 = 0,
.level0 = cur_trans->flags.eot_level,
.duration1 = 0,
.level1 = cur_trans->flags.eot_level,
};
tx_chan->mem_off_bytes += sizeof(rmt_symbol_word_t);
}
size_t off = 0;
if (channel->dma_chan) {
if (tx_chan->mem_off <= tx_chan->ping_pong_symbols) {
if (tx_chan->mem_off_bytes <= tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) {
desc_nc = &tx_chan->dma_nodes_nc[0];
off = tx_chan->mem_off;
off = tx_chan->mem_off_bytes;
} else {
desc_nc = &tx_chan->dma_nodes_nc[1];
off = tx_chan->mem_off - tx_chan->ping_pong_symbols;
off = tx_chan->mem_off_bytes - tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t);
}
desc_nc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc_nc->dw0.length = off * sizeof(rmt_symbol_word_t);
desc_nc->dw0.length = off;
// break down the DMA descriptor link
desc_nc->next = NULL;
} else {
@@ -630,6 +634,8 @@ static void rmt_tx_mark_eof(rmt_tx_channel_t *tx_chan)
rmt_ll_enable_interrupt(group->hal.regs, RMT_LL_EVENT_TX_THRES(channel_id), false);
portEXIT_CRITICAL_ISR(&group->spinlock);
}
return need_eof_marker ? 1 : 0;
}
size_t rmt_encode_check_result(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t)
@@ -639,12 +645,13 @@ size_t rmt_encode_check_result(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t
size_t encoded_symbols = encoder->encode(encoder, &tx_chan->base, t->payload, t->payload_bytes, &encode_state);
if (encode_state & RMT_ENCODING_COMPLETE) {
t->flags.encoding_done = true;
bool need_eof_mark = (encode_state & RMT_ENCODING_WITH_EOF) == 0;
// inserting EOF symbol if there's extra space
if (!(encode_state & RMT_ENCODING_MEM_FULL)) {
rmt_tx_mark_eof(tx_chan);
encoded_symbols += 1;
encoded_symbols += rmt_tx_mark_eof(tx_chan, need_eof_mark);
}
t->flags.encoding_done = true;
t->flags.need_eof_mark = need_eof_mark;
}
// for loop transaction, the memory block should accommodate all encoded RMT symbols
@@ -667,6 +674,9 @@ static void rmt_tx_do_transaction(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t
// update current transaction
tx_chan->cur_trans = t;
// reset RMT encoder before starting a new transaction
rmt_encoder_reset(t->encoder);
#if SOC_RMT_SUPPORT_DMA
if (channel->dma_chan) {
gdma_reset(channel->dma_chan);
@@ -717,7 +727,7 @@ static void rmt_tx_do_transaction(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t
// at the beginning of a new transaction, encoding memory offset should start from zero.
// It will increase in the encode function e.g. `rmt_encode_copy()`
tx_chan->mem_off = 0;
tx_chan->mem_off_bytes = 0;
// use the full memory block for the beginning encoding session
tx_chan->mem_end = tx_chan->ping_pong_symbols * 2;
// perform the encoding session, return the number of encoded symbols
@@ -838,8 +848,6 @@ static esp_err_t rmt_tx_disable(rmt_channel_handle_t channel)
// recycle the interrupted transaction
if (tx_chan->cur_trans) {
xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &tx_chan->cur_trans, 0);
// reset corresponding encoder
rmt_encoder_reset(tx_chan->cur_trans->encoder);
}
tx_chan->cur_trans = NULL;
@@ -899,8 +907,7 @@ bool rmt_isr_handle_tx_threshold(rmt_tx_channel_t *tx_chan)
size_t encoded_symbols = t->transmitted_symbol_num;
// encoding finished, only need to send the EOF symbol
if (t->flags.encoding_done) {
rmt_tx_mark_eof(tx_chan);
encoded_symbols += 1;
encoded_symbols += rmt_tx_mark_eof(tx_chan, t->flags.need_eof_mark);
} else {
encoded_symbols += rmt_encode_check_result(tx_chan, t);
}
@@ -1101,8 +1108,7 @@ static bool rmt_dma_tx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t
rmt_tx_trans_desc_t *t = tx_chan->cur_trans;
size_t encoded_symbols = t->transmitted_symbol_num;
if (t->flags.encoding_done) {
rmt_tx_mark_eof(tx_chan);
encoded_symbols += 1;
encoded_symbols += rmt_tx_mark_eof(tx_chan, t->flags.need_eof_mark);
} else {
encoded_symbols += rmt_encode_check_result(tx_chan, t);
}

View File

@@ -12,6 +12,14 @@ if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED AND CONFIG_PM_ENABLE)
list(APPEND srcs "test_rmt_sleep.c")
endif()
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED AND CONFIG_SOC_RMT_SUPPORT_DMA)
list(APPEND srcs "test_rmt_bitscrambler.c")
endif()
idf_component_register(SRCS "${srcs}"
PRIV_REQUIRES unity esp_driver_rmt esp_driver_gpio esp_timer esp_psram
PRIV_REQUIRES unity esp_driver_rmt esp_driver_gpio esp_driver_bitscrambler esp_timer esp_psram
WHOLE_ARCHIVE)
if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED AND CONFIG_SOC_RMT_SUPPORT_DMA)
target_bitscrambler_add_src("test_tx.bsasm")
endif()

View File

@@ -0,0 +1,107 @@
/*
* SPDX-FileCopyrightText: 2024-2025 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 "driver/gpio.h"
#include "driver/bitscrambler.h"
#include "test_board.h"
BITSCRAMBLER_PROGRAM(bitscrambler_program_test_tx, "test_tx");
IRAM_ATTR static bool test_rmt_rx_done_callback(rmt_channel_handle_t channel, const 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;
const rmt_symbol_word_t expected_symbols[] = {
{ .level0 = 1, .duration0 = 273, .level1 = 0, .duration1 = 1 },
{ .level0 = 1, .duration0 = 546, .level1 = 0, .duration1 = 2 },
{ .level0 = 1, .duration0 = 819, .level1 = 0, .duration1 = 3 },
{ .level0 = 1, .duration0 = 1092, .level1 = 0, .duration1 = 4 },
{ .level0 = 1, .duration0 = 1, .level1 = 0, .duration1 = 0 },
};
rmt_symbol_word_t *remote_codes = edata->received_symbols;
esp_rom_printf("%u symbols received:\r\n", edata->num_symbols);
for (int 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);
}
TEST_ASSERT_EQUAL_INT_ARRAY(expected_symbols, remote_codes, sizeof(expected_symbols) / sizeof(rmt_symbol_word_t));
vTaskNotifyGiveFromISR(task_to_notify, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("rmt TX with bitscrambler", "[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 = 48, // no need a large DMA memory to save the primary data
.gpio_num = TEST_RMT_GPIO_NUM_B,
.trans_queue_depth = 4,
.flags.with_dma = true, // bitscrambler has to work 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));
rmt_rx_channel_config_t rx_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 = TEST_RMT_GPIO_NUM_B,
};
printf("install rx channel to the same GPIO\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_ESP_OK(rmt_rx_register_event_callbacks(rx_channel, &cbs, xTaskGetCurrentTaskHandle()));
printf("install bitscrambler encoder\r\n");
rmt_encoder_handle_t bs_encoder = NULL;
rmt_bs_encoder_config_t bs_encoder_config = {
.program_bin = bitscrambler_program_test_tx,
};
TEST_ESP_OK(rmt_new_bitscrambler_encoder(&bs_encoder_config, &bs_encoder));
printf("enable tx+rx channel\r\n");
TEST_ESP_OK(rmt_enable(tx_channel));
TEST_ESP_OK(rmt_enable(rx_channel));
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 500,
.signal_range_max_ns = 2000000,
};
rmt_symbol_word_t symbols[8];
TEST_ESP_OK(rmt_receive(rx_channel, symbols, sizeof(symbols), &receive_config));
printf("transmit!\r\n");
rmt_transmit_config_t transmit_config = {
.loop_count = 0, // no loop
};
TEST_ESP_OK(rmt_transmit(tx_channel, bs_encoder, (uint8_t[]) {
0x12, 0x34, 0x56, 0x78, 0x9a, // dummy test values, will be further processed by bitscrambler program
}, 5, &transmit_config));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
printf("disable tx+rx channel\r\n");
TEST_ESP_OK(rmt_disable(tx_channel));
TEST_ESP_OK(rmt_disable(rx_channel));
printf("remove tx+rx channel and bs encoder\r\n");
TEST_ESP_OK(rmt_del_channel(tx_channel));
TEST_ESP_OK(rmt_del_channel(rx_channel));
TEST_ESP_OK(rmt_del_encoder(bs_encoder));
}

View File

@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
cfg prefetch false # disable data prefetch
cfg eof_on downstream # set EOF on downstream
cfg trailing_bytes 4
cfg lut_width_bits 32
# Define contents that stored in the lookup table
lut 0x00018111 # index 0, 273us high level, 1us low level
lut 0x00028222 # index 1, 546us high level, 2us low level
lut 0x00038333 # index 2, 819us high level, 3us low level
lut 0x00048444 # index 3, 1092us high level, 4us low level
lut 0x00008001 # index 4, saves the RMT end marker (any level with duration equals to 0)
set 16..18 L # init the LUT index: 0 (0b000)
loop:
read 8,
set 31..0 L31..L0,
write 32,
jmp loop

View File

@@ -62,7 +62,7 @@ static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
return ESP_OK;
}
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
IRAM_ATTR 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);
@@ -177,7 +177,7 @@ static esp_err_t rmt_del_nec_protocol_encoder(rmt_encoder_t *encoder)
return ESP_OK;
}
static esp_err_t rmt_nec_protocol_encoder_reset(rmt_encoder_t *encoder)
IRAM_ATTR 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);

View File

@@ -443,6 +443,20 @@ A configuration structure :cpp:type:`rmt_simple_encoder_config_t` should be prov
While the functionality of an encoding process using the simple callback encoder can usually also realized by chaining other encoders, the simple callback can be more easy to understand and maintain than an encoder chain.
.. only:: SOC_BITSCRAMBLER_SUPPORTED and SOC_RMT_SUPPORT_DMA
BitScrambler Encoder
~~~~~~~~~~~~~~~~~~~~
When the RMT transmit channel has DMA enabled, we can control the data on the DMA path by writing :doc:`BitScrambler </api-reference/peripherals/bitscrambler>` assembly code, which implements simple encoding operations. Compared to CPU-based encoding, the BitScrambler offers higher performance without consuming CPU resources. However, due to the limited instruction memory space of the BitScrambler, it cannot implement complex encoding operations. Additionally, the output data format from the BitScrambler program must conform to the :cpp:type:`rmt_symbol_word_t` structure.
A BitScrambler encoder can be created by calling :cpp:func:`rmt_new_bitscrambler_encoder`. This function takes a configuration parameter of type :cpp:type:`rmt_bs_encoder_config_t`, which includes the following configuration items:
- :cpp:member:`rmt_bs_encoder_config_t::program_bin` points to the binary file of the BitScrambler program. This binary file must comply with the BitScrambler assembly language specification and will be loaded into the BitScrambler's instruction memory at runtime. For information on how to write and compile BitScrambler programs, please refer to the :doc:`BitScrambler Programming Guide </api-reference/peripherals/bitscrambler>`.
.. note::
The BitScrambler encoder **must** be used with an RMT channel that has DMA enabled.
Customize RMT Encoder for NEC Protocol
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -443,6 +443,20 @@ RMT 编码器是 RMT TX 事务的一部分,用于在特定时间生成正确
简易回调编码器的功能通常可以通过链式组合其他编码器来实现,但相比编码器链,简易回调编码器更易于理解和维护。
.. only:: SOC_BITSCRAMBLER_SUPPORTED and SOC_RMT_SUPPORT_DMA
比特调节 (BitScrambler) 编码器
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
当 RMT 的发送通道开启了 DMA, 我们可以通过编写 :doc:`比特调节器 </api-reference/peripherals/bitscrambler>` 汇编代码来控制 DMA 通路上的数据,进而实现一些简单的编码工作。相较于使用 CPU 做编码工作,比特调节器的性能更高,且不会占用 CPU 资源,但是受限于 BitScrambler 有限的指令存储器空间,它无法实现复杂的编码工作。此外,比特调节器程序的输出流数据格式必须符合 :cpp:type:`rmt_symbol_word_t` 结构。
调用 :cpp:func:`rmt_new_bitscrambler_encoder` 可以创建一个比特调节器编码器。该函数的配置参数为 :cpp:type:`rmt_bs_encoder_config_t` 结构体,包含以下配置项:
- :cpp:member:`rmt_bs_encoder_config_t::program_bin` 指向比特调节器程序的二进制文件的指针。该二进制文件必须符合比特调节器的汇编语言规范,并且在运行时会被加载到比特调节器的指令存储器中。如何编写并编译比特调节器程序请参考 :doc:`比特调节器编程指南 </api-reference/peripherals/bitscrambler>`
.. note::
比特调节编码器**必须**配合开启了 DMA 的 RMT 通道使用。
自定义 NEC 协议的 RMT 编码器
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~