diff --git a/components/esp_driver_rmt/CMakeLists.txt b/components/esp_driver_rmt/CMakeLists.txt index 44f229e6717..b37d08438f0 100644 --- a/components/esp_driver_rmt/CMakeLists.txt +++ b/components/esp_driver_rmt/CMakeLists.txt @@ -6,6 +6,7 @@ if(CONFIG_SOC_RMT_SUPPORTED) list(APPEND srcs "src/rmt_common.c" "src/rmt_encoder.c" "src/rmt_encoder_bytes.c" + "src/rmt_encoder_bits.c" "src/rmt_encoder_copy.c" "src/rmt_encoder_simple.c" "src/rmt_rx.c" diff --git a/components/esp_driver_rmt/include/driver/rmt_encoder.h b/components/esp_driver_rmt/include/driver/rmt_encoder.h index 69039eafb3e..762c5e1ce8b 100644 --- a/components/esp_driver_rmt/include/driver/rmt_encoder.h +++ b/components/esp_driver_rmt/include/driver/rmt_encoder.h @@ -44,7 +44,7 @@ struct rmt_encoder_t { * @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[in] data_size Size of primary_data, in bytes (especially for bits encoder, it is in bits) * @param[out] ret_state Returned current encoder's state * @return Number of RMT symbols that the primary data has been encoded into */ @@ -129,6 +129,17 @@ typedef struct { } flags; /*!< Encoder config flag */ } rmt_bytes_encoder_config_t; +/** + * @brief Bits 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; /*!< Encoder config flag */ +} rmt_bits_encoder_config_t; + /** * @brief Copy encoder configuration */ @@ -182,6 +193,38 @@ esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, rmt_en */ esp_err_t rmt_bytes_encoder_update_config(rmt_encoder_handle_t bytes_encoder, const rmt_bytes_encoder_config_t *config); +/** + * @brief Create RMT bits encoder, which can encode bit stream into RMT symbols + * + * @note The bits encoder is similar to bytes encoder, but it can handle arbitrary bit lengths, + * not just multiples of 8 bits. The data_size parameter in (*encode)() represents + * the number of bits to encode, not bytes. + * + * @param[in] config Bits encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_OK: Create RMT bits encoder successfully + * - ESP_ERR_INVALID_ARG: Create RMT bits encoder failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT bits encoder failed because out of memory + * - ESP_FAIL: Create RMT bits encoder failed because of other error + */ +esp_err_t rmt_new_bits_encoder(const rmt_bits_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +/** + * @brief Update the configuration of the bits encoder + * + * @note The configurations of the bits encoder is also set up by `rmt_new_bits_encoder()`. + * This function is used to update the configuration of the bits encoder at runtime. + * + * @param[in] bits_encoder Bits encoder handle, created by e.g `rmt_new_bits_encoder()` + * @param[in] config Bits encoder configuration + * @return + * - ESP_OK: Update RMT bits encoder successfully + * - ESP_ERR_INVALID_ARG: Update RMT bits encoder failed because of invalid argument + * - ESP_FAIL: Update RMT bits encoder failed because of other error + */ +esp_err_t rmt_bits_encoder_update_config(rmt_encoder_handle_t bits_encoder, const rmt_bits_encoder_config_t *config); + /** * @brief Create RMT copy encoder, which copies the given RMT symbols into RMT memory * diff --git a/components/esp_driver_rmt/include/driver/rmt_tx.h b/components/esp_driver_rmt/include/driver/rmt_tx.h index b51a9f76e78..08024064835 100644 --- a/components/esp_driver_rmt/include/driver/rmt_tx.h +++ b/components/esp_driver_rmt/include/driver/rmt_tx.h @@ -96,7 +96,7 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_ * @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] payload_size Size of the `payload` in bytes (specially for bits encoder, it is in bits) * @param[in] config Transmission specific configuration * @return * - ESP_OK: Transmit data successfully @@ -105,7 +105,7 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_ * - 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); +esp_err_t rmt_transmit(rmt_channel_handle_t tx_channel, rmt_encoder_handle_t encoder, const void *payload, size_t payload_size, const rmt_transmit_config_t *config); /** * @brief Wait for all pending TX transactions done diff --git a/components/esp_driver_rmt/src/rmt_encoder_bits.c b/components/esp_driver_rmt/src/rmt_encoder_bits.c new file mode 100644 index 00000000000..6534d6bf58a --- /dev/null +++ b/components/esp_driver_rmt/src/rmt_encoder_bits.c @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "driver/rmt_encoder.h" +#include "rmt_private.h" + +typedef struct rmt_bits_encoder_t { + rmt_encoder_t base; // encoder base class + size_t last_bit_index; // index of the current bit position within the current byte (0-7) + size_t last_byte_index; // index of the current byte in the data 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_bits_encoder_t; + +RMT_ENCODER_FUNC_ATTR +static esp_err_t rmt_bits_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_bits_encoder_t *bits_encoder = __containerof(encoder, rmt_bits_encoder_t, base); + // reset index to zero + bits_encoder->last_bit_index = 0; + bits_encoder->last_byte_index = 0; + return ESP_OK; +} + +RMT_ENCODER_FUNC_ATTR +static size_t rmt_encode_bits(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_bits_encoder_t *bits_encoder = __containerof(encoder, rmt_bits_encoder_t, base); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + const uint8_t *raw_data = (const uint8_t *)primary_data; + rmt_encode_state_t state = RMT_ENCODING_RESET; + + size_t byte_index = bits_encoder->last_byte_index; + size_t bit_index = bits_encoder->last_bit_index; + // calculate total bit position for remaining bits calculation + size_t total_bit_pos = byte_index * 8 + bit_index; + // how many symbols will be generated by the encoder (data_size is in bits) + size_t mem_want = data_size - total_bit_pos; + // how many symbols we can save for this round + 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) { + mem_to_nc = tx_chan->dma_mem_base_nc; + } else { + mem_to_nc = 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 SOC_RMT_SUPPORT_DMA + uint8_t dma_lli0_index = 0; + uint8_t dma_lli1_index = 0; + if (channel->dma_chan) { + // mark the start descriptor + if (symbol_off < tx_chan->ping_pong_symbols) { + dma_lli0_index = 0; + } else { + dma_lli0_index = 1; + } + } +#endif // SOC_RMT_SUPPORT_DMA + + size_t len = encode_len; + while (len > 0) { + // start from last time truncated encoding + uint8_t cur_byte = raw_data[byte_index]; + // bit-wise reverse + if (bits_encoder->flags.msb_first) { + cur_byte = hal_utils_bitwise_reverse8(cur_byte); + } + while ((len > 0) && (bit_index < 8)) { + if (cur_byte & (1 << bit_index)) { + mem_to_nc[symbol_off++] = bits_encoder->bit1; + } else { + mem_to_nc[symbol_off++] = bits_encoder->bit0; + } + len--; + bit_index++; + } + if (bit_index >= 8) { + byte_index++; + bit_index = 0; + } + } + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + // mark the end descriptor + if (symbol_off < tx_chan->ping_pong_symbols) { + dma_lli1_index = 0; + } else { + dma_lli1_index = 1; + } + + // cross line, means desc0 has prepared with sufficient data buffer + if (dma_lli0_index != dma_lli1_index) { + gdma_link_set_owner(tx_chan->dma_link, dma_lli0_index, GDMA_LLI_OWNER_DMA); + gdma_link_set_length(tx_chan->dma_link, dma_lli0_index, tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)); + } + } +#endif // SOC_RMT_SUPPORT_DMA + + if (encoding_truncated) { + // this encoding has not finished yet, save the truncated position + bits_encoder->last_bit_index = bit_index; + bits_encoder->last_byte_index = byte_index; + } else { + // reset internal index if encoding session has finished + bits_encoder->last_bit_index = 0; + bits_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 (symbol_off >= tx_chan->ping_pong_symbols * 2) { +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_link_set_owner(tx_chan->dma_link, dma_lli1_index, GDMA_LLI_OWNER_DMA); + gdma_link_set_length(tx_chan->dma_link, dma_lli1_index, tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)); + } +#endif // SOC_RMT_SUPPORT_DMA + tx_chan->mem_off_bytes = 0; + } else { + tx_chan->mem_off_bytes = symbol_off * sizeof(rmt_symbol_word_t); + } + + *ret_state = state; + return encode_len; +} + +static esp_err_t rmt_del_bits_encoder(rmt_encoder_t *encoder) +{ + rmt_bits_encoder_t *bits_encoder = __containerof(encoder, rmt_bits_encoder_t, base); + free(bits_encoder); + return ESP_OK; +} + +esp_err_t rmt_new_bits_encoder(const rmt_bits_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_bits_encoder_t *encoder = rmt_alloc_encoder_mem(sizeof(rmt_bits_encoder_t)); + ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for bits encoder"); + encoder->base.encode = rmt_encode_bits; + encoder->base.del = rmt_del_bits_encoder; + encoder->base.reset = rmt_bits_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 bits encoder @%p", encoder); +err: + return ret; +} + +esp_err_t rmt_bits_encoder_update_config(rmt_encoder_handle_t bits_encoder, const rmt_bits_encoder_config_t *config) +{ + ESP_RETURN_ON_FALSE(bits_encoder && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_bits_encoder_t *encoder = __containerof(bits_encoder, rmt_bits_encoder_t, base); + encoder->bit0 = config->bit0; + encoder->bit1 = config->bit1; + encoder->flags.msb_first = config->flags.msb_first; + return ESP_OK; +} diff --git a/components/esp_driver_rmt/src/rmt_private.h b/components/esp_driver_rmt/src/rmt_private.h index 1332d5aebcf..e5462defc0c 100644 --- a/components/esp_driver_rmt/src/rmt_private.h +++ b/components/esp_driver_rmt/src/rmt_private.h @@ -181,7 +181,7 @@ struct rmt_channel_t { typedef struct { rmt_encoder_handle_t encoder; // encode user payload into RMT symbols const void *payload; // encoder payload - size_t payload_bytes; // payload size + size_t payload_size; // payload size, in bytes (specially for bits encoder, it is in bits) 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 diff --git a/components/esp_driver_rmt/src/rmt_tx.c b/components/esp_driver_rmt/src/rmt_tx.c index 37bccd0ce6c..9736d3ecaa9 100644 --- a/components/esp_driver_rmt/src/rmt_tx.c +++ b/components/esp_driver_rmt/src/rmt_tx.c @@ -517,9 +517,9 @@ esp_err_t rmt_tx_register_event_callbacks(rmt_channel_handle_t channel, const rm return ESP_OK; } -esp_err_t rmt_transmit(rmt_channel_handle_t channel, rmt_encoder_t *encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config) +esp_err_t rmt_transmit(rmt_channel_handle_t channel, rmt_encoder_t *encoder, const void *payload, size_t payload_size, const rmt_transmit_config_t *config) { - ESP_RETURN_ON_FALSE(channel && encoder && payload && payload_bytes && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel && encoder && payload && payload_size && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); #if !SOC_RMT_SUPPORT_TX_LOOP_COUNT ESP_RETURN_ON_FALSE(config->loop_count <= 1, ESP_ERR_NOT_SUPPORTED, TAG, "loop count is not supported"); @@ -546,7 +546,7 @@ esp_err_t rmt_transmit(rmt_channel_handle_t channel, rmt_encoder_t *encoder, con memset(t, 0, sizeof(rmt_tx_trans_desc_t)); t->encoder = encoder; t->payload = payload; - t->payload_bytes = payload_bytes; + t->payload_size = payload_size; // treat loop_count == 1 as no loop t->loop_count = config->loop_count == 1 ? 0 : config->loop_count; t->remain_loop_count = t->loop_count; @@ -649,7 +649,7 @@ size_t rmt_encode_check_result(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t { rmt_encode_state_t encode_state = RMT_ENCODING_RESET; rmt_encoder_handle_t encoder = t->encoder; - size_t encoded_symbols = encoder->encode(encoder, &tx_chan->base, t->payload, t->payload_bytes, &encode_state); + size_t encoded_symbols = encoder->encode(encoder, &tx_chan->base, t->payload, t->payload_size, &encode_state); bool is_mem_full = encode_state & RMT_ENCODING_MEM_FULL; bool need_eof_mark = (encode_state & RMT_ENCODING_WITH_EOF) == 0; diff --git a/components/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c b/components/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c index df2b94e2b5f..2a8de2ee737 100644 --- a/components/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c +++ b/components/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c @@ -114,6 +114,81 @@ TEST_CASE("rmt bytes encoder", "[rmt]") TEST_ESP_OK(rmt_del_encoder(bytes_encoder)); } +TEST_CASE("rmt bits encoder", "[rmt]") +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, // 1MHz resolution + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .trans_queue_depth = 4, + .gpio_num = TEST_RMT_GPIO_NUM_B, + }; + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + TEST_ESP_OK(rmt_enable(tx_channel)); + + printf("install bits encoder\r\n"); + rmt_encoder_handle_t bits_encoder = NULL; + rmt_bits_encoder_config_t bits_enc_config = { + .bit0 = { + .level0 = 1, + .duration0 = 3, // T0H=3us + .level1 = 0, + .duration1 = 9, // T0L=9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 9, // T1H=9us + .level1 = 0, + .duration1 = 3, // T1L=3us + }, + .flags.msb_first = 1, + }; + TEST_ESP_OK(rmt_new_bits_encoder(&bits_enc_config, &bits_encoder)); + + uint8_t test_data[] = {0xAB, 0xDA}; // 10101011, 11011010 + size_t num_bits1 = 8; + size_t num_bits2 = 5; + size_t num_bits3 = 13; + + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + + printf("transmit bits pattern with MSB first(%zu bits)\r\n", num_bits1); + TEST_ESP_OK(rmt_transmit(tx_channel, bits_encoder, test_data, num_bits1, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("transmit bits pattern with MSB first(%zu bits)\r\n", num_bits2); + TEST_ESP_OK(rmt_transmit(tx_channel, bits_encoder, test_data, num_bits2, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("transmit bits pattern with MSB first(%zu bits)\r\n", num_bits3); + TEST_ESP_OK(rmt_transmit(tx_channel, bits_encoder, test_data, num_bits3, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + // change to LSB first + printf("update bits encoder configuration to LSB first\r\n"); + bits_enc_config.flags.msb_first = 0; + TEST_ESP_OK(rmt_bits_encoder_update_config(bits_encoder, &bits_enc_config)); + + printf("transmit bits pattern with LSB first(%zu bits)\r\n", num_bits1); + TEST_ESP_OK(rmt_transmit(tx_channel, bits_encoder, test_data, num_bits1, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("transmit bits pattern with LSB first(%zu bits)\r\n", num_bits2); + TEST_ESP_OK(rmt_transmit(tx_channel, bits_encoder, test_data, num_bits2, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("transmit bits pattern with LSB first(%zu bits)\r\n", num_bits3); + TEST_ESP_OK(rmt_transmit(tx_channel, bits_encoder, test_data, num_bits3, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + TEST_ESP_OK(rmt_del_encoder(bits_encoder)); + TEST_ESP_OK(rmt_disable(tx_channel)); + TEST_ESP_OK(rmt_del_channel(tx_channel)); +} + static void test_rmt_channel_single_trans(size_t mem_block_symbols, bool with_dma) { rmt_tx_channel_config_t tx_channel_cfg = {