|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include "driver/rmt_encoder.h" |
| 8 | +#include "driver/bitscrambler.h" |
| 9 | +#include "rmt_private.h" |
| 10 | + |
| 11 | +typedef struct rmt_bs_encoder_t { |
| 12 | + rmt_encoder_t base; // the base "class", declares the standard encoder interface |
| 13 | + size_t last_byte_index; // index of the encoding byte in the primary stream |
| 14 | + bitscrambler_handle_t bs; // BitScrambler handle |
| 15 | +} rmt_bs_encoder_t; |
| 16 | + |
| 17 | +static esp_err_t rmt_bs_encoder_reset(rmt_encoder_t *encoder) |
| 18 | +{ |
| 19 | + rmt_bs_encoder_t *bs_encoder = __containerof(encoder, rmt_bs_encoder_t, base); |
| 20 | + bs_encoder->last_byte_index = 0; |
| 21 | + bitscrambler_reset(bs_encoder->bs); |
| 22 | + bitscrambler_start(bs_encoder->bs); |
| 23 | + return ESP_OK; |
| 24 | +} |
| 25 | + |
| 26 | +static esp_err_t rmt_del_bs_encoder(rmt_encoder_t *encoder) |
| 27 | +{ |
| 28 | + rmt_bs_encoder_t *bs_encoder = __containerof(encoder, rmt_bs_encoder_t, base); |
| 29 | + bitscrambler_free(bs_encoder->bs); |
| 30 | + free(bs_encoder); |
| 31 | + return ESP_OK; |
| 32 | +} |
| 33 | + |
| 34 | +static size_t rmt_encode_bs(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *input_raw, |
| 35 | + size_t data_size, rmt_encode_state_t *ret_state) |
| 36 | +{ |
| 37 | + rmt_bs_encoder_t *bs_encoder = __containerof(encoder, rmt_bs_encoder_t, base); |
| 38 | + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); |
| 39 | + uint8_t *input_bytes = (uint8_t *)input_raw; |
| 40 | + rmt_encode_state_t state = RMT_ENCODING_RESET; |
| 41 | + rmt_dma_descriptor_t *desc0 = NULL; |
| 42 | + rmt_dma_descriptor_t *desc1 = NULL; |
| 43 | + |
| 44 | + // bitscrambler encoder must be used with a TX channel with DMA enabled |
| 45 | + assert(tx_chan->base.dma_chan != NULL); |
| 46 | + |
| 47 | + size_t byte_index = bs_encoder->last_byte_index; |
| 48 | + // how many bytes will be copied by the encoder |
| 49 | + size_t mem_want = (data_size - byte_index); |
| 50 | + // how many bytes we can save for this round |
| 51 | + size_t mem_have = tx_chan->mem_end * sizeof(rmt_symbol_word_t) - tx_chan->mem_off_bytes; |
| 52 | + // target memory buffer to copy to |
| 53 | + uint8_t *mem_to_nc = (uint8_t*)tx_chan->dma_mem_base_nc; |
| 54 | + // how many bytes will be copied in this round |
| 55 | + size_t copy_len = MIN(mem_want, mem_have); |
| 56 | + bool encoding_truncated = mem_have < mem_want; |
| 57 | + bool encoding_space_free = mem_have > mem_want; |
| 58 | + |
| 59 | + // mark the start descriptor |
| 60 | + if (tx_chan->mem_off_bytes < tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) { |
| 61 | + desc0 = &tx_chan->dma_nodes_nc[0]; |
| 62 | + } else { |
| 63 | + desc0 = &tx_chan->dma_nodes_nc[1]; |
| 64 | + } |
| 65 | + |
| 66 | + size_t len = copy_len; |
| 67 | + while (len > 0) { |
| 68 | + mem_to_nc[tx_chan->mem_off_bytes++] = input_bytes[byte_index++]; |
| 69 | + len--; |
| 70 | + } |
| 71 | + |
| 72 | + // mark the end descriptor |
| 73 | + if (tx_chan->mem_off_bytes < tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) { |
| 74 | + desc1 = &tx_chan->dma_nodes_nc[0]; |
| 75 | + } else { |
| 76 | + desc1 = &tx_chan->dma_nodes_nc[1]; |
| 77 | + } |
| 78 | + |
| 79 | + // cross line, means desc0 has prepared with sufficient data buffer |
| 80 | + if (desc0 != desc1) { |
| 81 | + desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); |
| 82 | + desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; |
| 83 | + } |
| 84 | + |
| 85 | + if (encoding_truncated) { |
| 86 | + // this encoding has not finished yet, save the truncated position |
| 87 | + bs_encoder->last_byte_index = byte_index; |
| 88 | + } else { |
| 89 | + // reset internal index if encoding session has finished |
| 90 | + bs_encoder->last_byte_index = 0; |
| 91 | + // bitscrambler program will take care of the EOF marker by itself |
| 92 | + // so we don't rely on the TX driver to inject an EOF marker |
| 93 | + state |= RMT_ENCODING_COMPLETE | RMT_ENCODING_WITH_EOF; |
| 94 | + } |
| 95 | + |
| 96 | + if (!encoding_space_free) { |
| 97 | + // no more free memory, the caller should yield |
| 98 | + state |= RMT_ENCODING_MEM_FULL; |
| 99 | + } |
| 100 | + |
| 101 | + // reset offset pointer when exceeds maximum range |
| 102 | + if (tx_chan->mem_off_bytes >= tx_chan->ping_pong_symbols * 2 * sizeof(rmt_symbol_word_t)) { |
| 103 | + desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); |
| 104 | + desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; |
| 105 | + tx_chan->mem_off_bytes = 0; |
| 106 | + } |
| 107 | + |
| 108 | + *ret_state = state; |
| 109 | + return copy_len; |
| 110 | +} |
| 111 | + |
| 112 | +esp_err_t rmt_new_bitscrambler_encoder(const rmt_bs_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) |
| 113 | +{ |
| 114 | + esp_err_t ret = ESP_OK; |
| 115 | + rmt_bs_encoder_t *encoder = NULL; |
| 116 | + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); |
| 117 | + encoder = rmt_alloc_encoder_mem(sizeof(rmt_bs_encoder_t)); |
| 118 | + ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for bitscrambler encoder"); |
| 119 | + |
| 120 | + bitscrambler_config_t bs_config = { |
| 121 | + .dir = BITSCRAMBLER_DIR_TX, |
| 122 | + .attach_to = SOC_BITSCRAMBLER_ATTACH_RMT, |
| 123 | + }; |
| 124 | + ESP_GOTO_ON_ERROR(bitscrambler_new(&bs_config, &encoder->bs), err, TAG, "create bitscrambler failed"); |
| 125 | + |
| 126 | + // load the bitscrambler program |
| 127 | + ESP_GOTO_ON_ERROR(bitscrambler_load_program(encoder->bs, config->program_bin), err, TAG, "load bitscrambler program failed"); |
| 128 | + |
| 129 | + encoder->base.encode = rmt_encode_bs; |
| 130 | + encoder->base.del = rmt_del_bs_encoder; |
| 131 | + encoder->base.reset = rmt_bs_encoder_reset; |
| 132 | + // return general encoder handle |
| 133 | + *ret_encoder = &encoder->base; |
| 134 | + ESP_LOGD(TAG, "new bitscrambler encoder @%p", encoder); |
| 135 | + return ESP_OK; |
| 136 | +err: |
| 137 | + if (encoder) { |
| 138 | + if (encoder->bs) { |
| 139 | + bitscrambler_free(encoder->bs); |
| 140 | + } |
| 141 | + } |
| 142 | + return ret; |
| 143 | +} |
0 commit comments