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