[SLOVED] RMT copy encoder bug with ping-pong mode

av_jui
Posts: 8
Joined: Wed Jan 08, 2025 11:09 am

[SLOVED] RMT copy encoder bug with ping-pong mode

Postby av_jui » Sun Sep 14, 2025 5:39 pm

Hi

I’m currently working on an RMT serial component and got stuck on implementing ping-pong in the transmission part.
On the 4th RMT_ENCODING_MEM_FULL call, I always get a duplicate character (nn, tt, dd, rr, ll). After a lot of testing, I suspect this might be a bug in the copy encoder, but before opening an issue on GitHub I’d like to double-check my encoder implementation to make sure I’m not missing something.

Here’s a test file I created:

Code: Select all

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "esp_clk_tree.h"

static const char *TAG = "RMT_UART_STATEMACHINE";

#define TX_GPIO_NUM GPIO_NUM_4
#define RX_GPIO_NUM GPIO_NUM_5
#define UART_PORT UART_NUM_1
#define BAUD_RATE 9600
#define MEMORY_BLOCKS 48 * 4 // Small memory to test chunked transmission

typedef enum
{
    UART_ENCODER_INIT,
    UART_ENCODER_GENERATING_FRAME,
    UART_ENCODER_TRANSMITTING_FRAME,
    UART_ENCODER_DONE
} uart_encoder_state_t;

typedef struct
{
    rmt_encoder_t base;
    rmt_encoder_t *copy_encoder;
    uart_encoder_state_t state;
    size_t byte_index;
    rmt_symbol_word_t frame_symbols[10]; // Current frame being transmitted
    uint16_t bit_duration_ticks;
} uart_encoder_t;

static int encoder_call_count = 0;

static void setup_uart_receiver(void)
{
    uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };

    uart_driver_install(UART_PORT, 2048, 0, 0, NULL, 0);
    uart_param_config(UART_PORT, &uart_config);
    uart_set_pin(UART_PORT, UART_PIN_NO_CHANGE, RX_GPIO_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}

static size_t uart_encode(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
                          const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
    uart_encoder_t *uart_encoder = __containerof(encoder, uart_encoder_t, base);
    const char *data = (const char *)primary_data;
    rmt_encode_state_t session_state = RMT_ENCODING_RESET;
    size_t encoded_symbols = 0;
    static int mem_full_count = 0;

    encoder_call_count++;

    // Process characters until memory is full or all done
    while (uart_encoder->byte_index < data_size)
    {
        switch (uart_encoder->state)
        {
        case UART_ENCODER_INIT:
        {
            uart_encoder->byte_index = 0;
            uart_encoder->state = UART_ENCODER_GENERATING_FRAME;
            // Fall through
        }
        case UART_ENCODER_GENERATING_FRAME:
        {
            uint8_t current_char = data[uart_encoder->byte_index];
            uint16_t frame = (current_char << 1) | (1 << 9); // START + DATA + STOP

            // Generate 10 symbols for this frame
            for (int bit = 0; bit < 10; bit++)
            {
                uint8_t bit_value = (frame >> bit) & 0x01;
                uint8_t rmt_level = bit_value ? 0 : 1; // Invert for hardware
                uart_encoder->frame_symbols[bit] = (rmt_symbol_word_t){
                    .duration0 = (uint16_t)(uart_encoder->bit_duration_ticks / 2),
                    .level0 = rmt_level,
                    .duration1 = (uint16_t)(uart_encoder->bit_duration_ticks / 2),
                    .level1 = rmt_level};
            }
            uart_encoder->state = UART_ENCODER_TRANSMITTING_FRAME;
            // Fall through
        }
        case UART_ENCODER_TRANSMITTING_FRAME:
        {
            size_t frame_encoded = uart_encoder->copy_encoder->encode(
                uart_encoder->copy_encoder, channel,
                uart_encoder->frame_symbols,
                10 * sizeof(rmt_symbol_word_t),
                &session_state);

            encoded_symbols += frame_encoded;

            if (session_state & RMT_ENCODING_MEM_FULL)
            {
                mem_full_count++;
                // Only log critical MEM_FULL events (every 4th one where duplications occur)
                if (mem_full_count % 4 == 0) {
                    esp_rom_printf("MF%d@%d:%c\n", mem_full_count, 
                                   (int)uart_encoder->byte_index, data[uart_encoder->byte_index]);
                }
                *ret_state = RMT_ENCODING_MEM_FULL;
                return encoded_symbols;
            }

            if (session_state & RMT_ENCODING_COMPLETE)
            {
                // Frame complete, move to next character
                uart_encoder->byte_index++;
                uart_encoder->state = UART_ENCODER_GENERATING_FRAME;
                // Continue loop to process next character
                continue;
            }

            // If we get here, something unexpected happened
            *ret_state = session_state;
            return encoded_symbols;
        }
        case UART_ENCODER_DONE:
        {
            *ret_state = RMT_ENCODING_COMPLETE;
            return encoded_symbols;
        }
        }
    }

    // All characters processed
    uart_encoder->state = UART_ENCODER_DONE;
    *ret_state = RMT_ENCODING_COMPLETE;
    return encoded_symbols;
}

static esp_err_t uart_encoder_reset(rmt_encoder_t *encoder)
{
    uart_encoder_t *uart_encoder = __containerof(encoder, uart_encoder_t, base);
    ESP_LOGI(TAG, "Encoder reset called - state: %d, byte_index: %d",
             uart_encoder->state, (int)uart_encoder->byte_index);

    // Reset to initial state
    uart_encoder->state = UART_ENCODER_INIT;
    uart_encoder->byte_index = 0;

    // Reset copy encoder
    if (uart_encoder->copy_encoder)
    {
        rmt_encoder_reset(uart_encoder->copy_encoder);
    }
    return ESP_OK;
}

static esp_err_t uart_encoder_del(rmt_encoder_t *encoder)
{
    uart_encoder_t *uart_encoder = __containerof(encoder, uart_encoder_t, base);

    // No symbols array to free in the frame-by-frame approach
    // frame_symbols is a fixed array in the struct

    if (uart_encoder->copy_encoder)
    {
        rmt_del_encoder(uart_encoder->copy_encoder);
    }
    free(uart_encoder);
    return ESP_OK;
}

static esp_err_t new_uart_encoder(uint16_t bit_duration, rmt_encoder_handle_t *ret_encoder)
{
    uart_encoder_t *uart_encoder = (uart_encoder_t *)calloc(1, sizeof(uart_encoder_t));
    if (!uart_encoder)
    {
        return ESP_ERR_NO_MEM;
    }

    uart_encoder->base.encode = uart_encode;
    uart_encoder->base.del = uart_encoder_del;
    uart_encoder->base.reset = uart_encoder_reset;
    uart_encoder->bit_duration_ticks = bit_duration;
    uart_encoder->state = UART_ENCODER_INIT;

    // Create copy encoder
    rmt_copy_encoder_config_t copy_config = {};
    esp_err_t ret = rmt_new_copy_encoder(&copy_config, &uart_encoder->copy_encoder);
    if (ret != ESP_OK)
    {
        free(uart_encoder);
        return ret;
    }

    *ret_encoder = &uart_encoder->base;
    return ESP_OK;
}

static void uart_test_task(void *param)
{
    ESP_LOGI(TAG, "RMT UART Encoder Test");
    ESP_LOGI(TAG, "Connect GPIO %d (TX) to GPIO %d (RX)", TX_GPIO_NUM, RX_GPIO_NUM);

    setup_uart_receiver();

    // Use proven timing values
    uint32_t resolution_hz = 6666666;  // Working resolution
    uint16_t bit_duration_ticks = 694; // Working bit duration for 9600 baud

    ESP_LOGI(TAG, "Timing: RMT=%d Hz, bit_duration=%d ticks", (int)resolution_hz, bit_duration_ticks);

    // Create RMT TX channel
    rmt_tx_channel_config_t tx_config = {
        .gpio_num = TX_GPIO_NUM,
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = resolution_hz,
        .mem_block_symbols = MEMORY_BLOCKS,
        .trans_queue_depth = 1,
        .intr_priority = 0,
        .flags = {
            .invert_out = true, // Hardware inversion
            .with_dma = false,
            .io_loop_back = false,
            .io_od_mode = false,
            .allow_pd = false}};

    rmt_channel_handle_t tx_channel = NULL;
    ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_config, &tx_channel));
    ESP_ERROR_CHECK(rmt_enable(tx_channel));

    // Create UART encoder
    rmt_encoder_handle_t uart_encoder = NULL;
    ESP_ERROR_CHECK(new_uart_encoder(bit_duration_ticks, &uart_encoder));

    const char *test_msg = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
    size_t msg_len = strlen(test_msg);

    ESP_LOGI(TAG, "Test message: \"%s\" (%d chars = %d symbols)", test_msg, (int)msg_len, (int)(msg_len * 10));
    ESP_LOGI(TAG, "Memory blocks: %d, estimated chunks: %d",
             MEMORY_BLOCKS, (int)((msg_len * 10 + MEMORY_BLOCKS - 1) / MEMORY_BLOCKS));

    // Show expected MEM_FULL positions
    ESP_LOGI(TAG, "=== EXPECTED MEM_FULL POSITIONS (every ~19 chars due to 192 symbol buffer) ===");
    for (int i = 1; i <= 7; i++) {
        int expected_pos = (i * 192) / 10; // 192 symbols = ~19.2 chars
        if (expected_pos < msg_len) {
            ESP_LOGI(TAG, "MEM_FULL #%d expected around position %d: '%c'", 
                     i, expected_pos, test_msg[expected_pos]);
        }
    }

    // Clear UART buffer
    uart_flush(UART_PORT);
    vTaskDelay(pdMS_TO_TICKS(100));

    // Reset encoder call counter
    encoder_call_count = 0;

    // Transmit via RMT
    rmt_transmit_config_t transmit_config = {
        .loop_count = 0,
        .flags = {0}};

    ESP_LOGI(TAG, "Starting transmission...");
    ESP_ERROR_CHECK(rmt_transmit(tx_channel, uart_encoder, test_msg, msg_len, &transmit_config));
    ESP_ERROR_CHECK(rmt_tx_wait_all_done(tx_channel, pdMS_TO_TICKS(10000)));

    ESP_LOGI(TAG, "Transmission complete after %d encoder calls", encoder_call_count);

    // Read received data from UART
    uint8_t rx_buffer[1024];
    memset(rx_buffer, 0, sizeof(rx_buffer));

    // Give time for all data to be received
    vTaskDelay(pdMS_TO_TICKS(1000));

    int received = uart_read_bytes(UART_PORT, rx_buffer, sizeof(rx_buffer) - 1, pdMS_TO_TICKS(3000));

    if (received > 0)
    {
        rx_buffer[received] = '\0';
        ESP_LOGI(TAG, "Expected: \"%s\" (%d bytes)", test_msg, (int)msg_len);
        ESP_LOGI(TAG, "Received: \"%s\" (%d bytes)", rx_buffer, received);

        if (received == msg_len && memcmp(test_msg, rx_buffer, msg_len) == 0)
        {
            ESP_LOGI(TAG, "SUCCESS: Message transmitted correctly!");
        }
        else
        {
            ESP_LOGE(TAG, "FAILURE: Message mismatch detected");
            if (received != msg_len)
            {
                ESP_LOGE(TAG, "Length mismatch: expected %d, got %d", (int)msg_len, received);
                ESP_LOGE(TAG, "Extra bytes: %d (indicates character duplication)", received - (int)msg_len);
            }

            // Analyze the correlation between MEM_FULL events and duplications
            ESP_LOGI(TAG, "=== COPY ENCODER BUG ANALYSIS ===");
            ESP_LOGI(TAG, "MEM_FULL events logged during transmission:");
            ESP_LOGI(TAG, "MF4@47:n  -> Next char at pos 48 should be 'g'");
            ESP_LOGI(TAG, "MF8@85:r  -> Next char at pos 86 should be 't'"); 
            ESP_LOGI(TAG, "MF12@122:a -> Next char at pos 123 should be 'd'");
            ESP_LOGI(TAG, "MF16@160:e -> Next char at pos 161 should be 'd'");
            ESP_LOGI(TAG, "MF20@197:e -> Next char at pos 198 should be 'r'");

            ESP_LOGI(TAG, "Analyzing received data for duplications:");

            // Check for duplications at predicted positions
            int duplication_positions[] = {48, 86, 123, 161, 198};
            char expected_chars[] = {'g', 't', 'd', 'd', 'r'};
            
            for (int i = 0; i < 5; i++) {
                int pos = duplication_positions[i];
                if (pos < received && pos < msg_len) {
                    ESP_LOGI(TAG, "Position %d: Expected '%c', Got '%c' %s", 
                             pos, expected_chars[i], rx_buffer[pos],
                             (rx_buffer[pos] != expected_chars[i]) ? "❌ CORRUPTED" : "✅ OK");
                    
                    // Check if there's a duplication (character appears twice)
                    if (pos + 1 < received) {
                        if (rx_buffer[pos] == rx_buffer[pos + 1]) {
                            ESP_LOGE(TAG, "DUPLICATION DETECTED: '%c' appears twice at positions %d and %d", 
                                     rx_buffer[pos], pos, pos + 1);
                        }
                    }
                }
            }

            // Show first difference for debugging
            int compare_len = (received < msg_len) ? received : msg_len;
            for (int i = 0; i < compare_len; i++)
            {
                if (rx_buffer[i] != test_msg[i])
                {
                    ESP_LOGE(TAG, "First difference at position %d: expected 0x%02X '%c', got 0x%02X '%c'",
                             i, test_msg[i], test_msg[i], rx_buffer[i], rx_buffer[i]);
                    
                    // Check if this matches a predicted corruption position
                    for (int j = 0; j < 5; j++) {
                        if (i == duplication_positions[j]) {
                            ESP_LOGE(TAG, "This matches predicted corruption after MEM_FULL #%d!", (j+1)*4);
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }
    else
    {
        ESP_LOGE(TAG, "No data received from UART - check wiring");
    }

    // Cleanup
    rmt_disable(tx_channel);
    rmt_del_channel(tx_channel);
    rmt_del_encoder(uart_encoder);
    uart_driver_delete(UART_PORT);

    ESP_LOGI(TAG, "Test complete");
    vTaskDelete(NULL);
}
extern "C" void app_main(void)
{
    xTaskCreate(uart_test_task, "uart_test", 8192, NULL, 5, NULL);
}
and here you can see the log i get

Code: Select all

--- Terminal on COM5 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce2820,len:0x159c
load:0x403c8700,len:0xd24 
load:0x403cb700,len:0x2f48
entry 0x403c891c
I (24) boot: ESP-IDF 5.5.0 2nd stage bootloader  
I (25) boot: compile time Sep 13 2025 23:13:33   
I (25) boot: Multicore bootloader
I (25) boot: chip revision: v0.2
I (28) boot: efuse block revision: v1.3
I (31) boot.esp32s3: Boot SPI Speed : 80MHz      
I (35) boot.esp32s3: SPI Mode       : DIO        
I (39) boot.esp32s3: SPI Flash Size : 8MB        
I (43) boot: Enabling RNG early entropy source...
I (47) boot: Partition Table:
I (50) boot: ## Label            Usage          Type ST Offset   Length
I (56) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (62) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (69) boot:  2 factory          factory app      00 00 00010000 00100000
I (76) boot: End of partition table
I (79) esp_image: segment 0: paddr=00010020 vaddr=3c020020 size=0d960h ( 55648) map
I (96) esp_image: segment 1: paddr=0001d988 vaddr=3fc94c00 size=02690h (  9872) load
I (98) esp_image: segment 2: paddr=00020020 vaddr=42000020 size=1e380h (123776) map
I (123) esp_image: segment 3: paddr=0003e3a8 vaddr=3fc97290 size=00768h (  1896) load
I (124) esp_image: segment 4: paddr=0003eb18 vaddr=40374000 size=10be0h ( 68576) load
I (142) esp_image: segment 5: paddr=0004f700 vaddr=600fe000 size=00020h (    32) load
I (148) boot: Loaded app from partition at offset 0x10000
I (149) boot: Disabling RNG early entropy source...
I (159) cpu_start: Multicore app
I (168) cpu_start: Pro cpu start user code
I (168) cpu_start: cpu freq: 160000000 Hz
I (169) app_init: Application information:
I (169) app_init: Project name:     rmt_serial
I (173) app_init: App version:      4d99ecc-dirty
I (177) app_init: Compile time:     Sep 13 2025 23:13:10
I (182) app_init: ELF file SHA256:  5c51484e1...
I (187) app_init: ESP-IDF:          5.5.0
I (190) efuse_init: Min chip rev:     v0.0
I (194) efuse_init: Max chip rev:     v0.99 
I (198) efuse_init: Chip rev:         v0.2
I (202) heap_init: Initializing. RAM available for dynamic allocation:
I (208) heap_init: At 3FC982E8 len 00051428 (325 KiB): RAM
I (213) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM
I (218) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (224) heap_init: At 600FE020 len 00001FC8 (7 KiB): RTCRAM
I (230) spi_flash: detected chip: boya
I (232) spi_flash: flash io: dio
W (235) spi_flash: Detected size(16384k) larger than the size in the binary image header(8192k). Using the size in the binary image header.
I (248) sleep_gpio: Configure to isolate all GPIO pins in sleep state
I (254) sleep_gpio: Enable automatic switching of GPIO sleep configuration
I (261) main_task: Started on CPU0
I (281) main_task: Calling app_main()
I (281) RMT_UART_STATEMACHINE: RMT UART Encoder Test
I (281) RMT_UART_STATEMACHINE: Connect GPIO 4 (TX) to GPIO 5 (RX)
I (291) RMT_UART_STATEMACHINE: Timing: RMT=6666666 Hz, bit_duration=694 ticks
D (291) rmt: new group(0) at 0x3fcecb44, occupy=ffffff00
D (301) rmt: group (0) clock resolution:80000000Hz
D (301) rmt: new tx channel(0,0) at 0x3fcec910, gpio=4, res=6666666Hz, hw_mem_base=0x60016800, dma_mem_base=0x0, dma_nodes=0x0, ping_pong_size=96, queue_depth=1
D (321) rmt: new copy encoder @0x3fcecc10
I (321) RMT_UART_STATEMACHINE: Test message: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." (295 chars = 2950 symbols)
I (351) RMT_UART_STATEMACHINE: Memory blocks: 192, estimated chunks: 260
I (361) RMT_UART_STATEMACHINE: === EXPECTED MEM_FULL POSITIONS (every ~19 chars due to 192 symbol buffer) ===
I (371) RMT_UART_STATEMACHINE: MEM_FULL #1 expected around position 19: 'i'
I (371) RMT_UART_STATEMACHINE: MEM_FULL #2 expected around position 38: ' '
I (381) RMT_UART_STATEMACHINE: MEM_FULL #3 expected around position 57: 's'
I (391) RMT_UART_STATEMACHINE: MEM_FULL #4 expected around position 76: 'm'
I (391) RMT_UART_STATEMACHINE: MEM_FULL #5 expected around position 96: 'u'
I (401) RMT_UART_STATEMACHINE: MEM_FULL #6 expected around position 115: ' '
I (411) RMT_UART_STATEMACHINE: MEM_FULL #7 expected around position 134: 't'
I (411) main_task: Returned from app_main()
I (511) RMT_UART_STATEMACHINE: Starting transmission...
I (511) RMT_UART_STATEMACHINE: Encoder reset called - state: 0, byte_index: 0
MF4@47:n
MF8@85:r
MF12@122:a
MF16@160:e
MF20@197:e
MF24@235:r
MF28@273: 
I (821) RMT_UART_STATEMACHINE: Transmission complete after 31 encoder calls
I (4821) RMT_UART_STATEMACHINE: Expected: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." (295 bytes)
I (4831) RMT_UART_STATEMACHINE: Received: "Lorem ipsum dolor sit amet, consetetur sadipscinng elitr, sed diam nonumy eirmod tempor inviduntt ut labore et dolore magna aliquyam erat, sed ddiam voluptua. At vero eos et accusam et justo dduo dolores et ea rebum. Stet clita kasd gubergrren, no sea takimata sanctus est Lorem ipsum dollor sit amet." (301 bytes)
E (4871) RMT_UART_STATEMACHINE: FAILURE: Message mismatch detected
E (4871) RMT_UART_STATEMACHINE: Length mismatch: expected 295, got 301
E (4881) RMT_UART_STATEMACHINE: Extra bytes: 6 (indicates character duplication)
I (4891) RMT_UART_STATEMACHINE: === COPY ENCODER BUG ANALYSIS ===
I (4891) RMT_UART_STATEMACHINE: MEM_FULL events logged during transmission:
I (4901) RMT_UART_STATEMACHINE: MF4@47:n  -> Next char at pos 48 should be 'g'
I (4901) RMT_UART_STATEMACHINE: MF8@85:r  -> Next char at pos 86 should be 't'
I (4911) RMT_UART_STATEMACHINE: MF12@122:a -> Next char at pos 123 should be 'd'
I (4921) RMT_UART_STATEMACHINE: MF16@160:e -> Next char at pos 161 should be 'd'
I (4931) RMT_UART_STATEMACHINE: MF20@197:e -> Next char at pos 198 should be 'r'
I (4931) RMT_UART_STATEMACHINE: Analyzing received data for duplications:
I (4941) RMT_UART_STATEMACHINE: Position 48: Expected 'g', Got 'n' ❌ CORRUPTED
I (4951) RMT_UART_STATEMACHINE: Position 86: Expected 't', Got 'r' ❌ CORRUPTED
I (4951) RMT_UART_STATEMACHINE: Position 123: Expected 'd', Got ' ' ❌ CORRUPTED
I (4961) RMT_UART_STATEMACHINE: Position 161: Expected 'd', Got ' ' ❌ CORRUPTED
I (4971) RMT_UART_STATEMACHINE: Position 198: Expected 'r', Got 'l' ❌ CORRUPTED
E (4981) RMT_UART_STATEMACHINE: First difference at position 48: expected 0x67 'g', got 0x6E 'n'
E (4981) RMT_UART_STATEMACHINE: This matches predicted corruption after MEM_FULL #4!
D (5011) rmt: del tx channel(0,0)
D (5011) rmt: del group(0)
I (5011) RMT_UART_STATEMACHINE: Test complete
Last edited by av_jui on Mon Sep 15, 2025 10:12 pm, edited 1 time in total.
Visit my github account :arrow: https://github.com/avjui

MicroController
Posts: 2672
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: [BUG] RMT copy encoder bug with ping-pong mode

Postby MicroController » Sun Sep 14, 2025 7:36 pm

Maybe try implementing as a simple encoder.

User avatar
ok-home
Posts: 157
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: [BUG] RMT copy encoder bug with ping-pong mode

Postby ok-home » Mon Sep 15, 2025 1:38 am

Why is it so complicated?
For UART it is enough to send symbol by symbol, and it is enough to reserve only 1 memory block, while leaving the rest of the blocks for reception. as an example:

Code: Select all

    
    rmt_copy_encoder_config_t tx_encoder_config = {};
    ESP_ERROR_CHECK(rmt_new_copy_encoder(&tx_encoder_config, &tx_encoder_handle));

Code: Select all

static void mhi_tx_packet_task(void *p)
{
    mhi_packet_t packet;
    rmt_transmit_config_t rmt_tx_config = {
        .loop_count = 0,
    };
    while (1)
    {
        xQueueReceive(mhi_tx_packet_queue, &packet, portMAX_DELAY); //read char (or packet of chars)
        mhi_item_to_rmt_item_cvt(tx_items, &packet); // convert to rmt
        ESP_ERROR_CHECK(rmt_transmit(tx_chan_handle, tx_encoder_handle, (void*)tx_items, sizeof(tx_items), &rmt_tx_config)); // transmit
        rmt_tx_wait_all_done(tx_chan_handle, portMAX_DELAY); // wait transmit done
        xEventGroupSetBits(mhi_tx_event_group, MHI_TX_DONE_BIT); // char or packet transmitted
    }
}


av_jui
Posts: 8
Joined: Wed Jan 08, 2025 11:09 am

Re: [BUG] RMT copy encoder bug with ping-pong mode

Postby av_jui » Mon Sep 15, 2025 6:18 am

Why is it so complicated?
For UART it is enough to send symbol by symbol, and it is enough to reserve only 1 memory block, while leaving the rest of the blocks for reception. as an example:

Code: Select all

    
    rmt_copy_encoder_config_t tx_encoder_config = {};
    ESP_ERROR_CHECK(rmt_new_copy_encoder(&tx_encoder_config, &tx_encoder_handle));

Code: Select all

static void mhi_tx_packet_task(void *p)
{
    mhi_packet_t packet;
    rmt_transmit_config_t rmt_tx_config = {
        .loop_count = 0,
    };
    while (1)
    {
        xQueueReceive(mhi_tx_packet_queue, &packet, portMAX_DELAY); //read char (or packet of chars)
        mhi_item_to_rmt_item_cvt(tx_items, &packet); // convert to rmt
        ESP_ERROR_CHECK(rmt_transmit(tx_chan_handle, tx_encoder_handle, (void*)tx_items, sizeof(tx_items), &rmt_tx_config)); // transmit
        rmt_tx_wait_all_done(tx_chan_handle, portMAX_DELAY); // wait transmit done
        xEventGroupSetBits(mhi_tx_event_group, MHI_TX_DONE_BIT); // char or packet transmitted
    }
}

Thanks for your replay.

On my tests i have seen that copyencoder never gives me a mem_full when i copy symbol by symbol. That was the reason to switch to frames generation. An other reason was to be shure to send complete frame without corruption.The third reason was to send so fast as possible without breaks between. As you can see i generator frame by frame and copy it. When mem_full i send it. but all 4th time of mem_full i get a double char. die position dependes allways on the mem_block size.

I will test again symbol by symbol logic. maybe i made there a mistake. I also will test generate frames and send frame by frame

Thanks
Visit my github account :arrow: https://github.com/avjui

User avatar
ok-home
Posts: 157
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: [BUG] RMT copy encoder bug with ping-pong mode

Postby ok-home » Mon Sep 15, 2025 7:09 am

When you send symbol by symbol, you guarantee that the symbol itself is transmitted without distortion, and delays can only be in the intersymbol interval, which usually does not lead to loss of information. If you do not have a very large packet, it may be possible to fit it into the size of the rmt memory, and again, delays will only be in the interpacket interval. Plus, you clearly control the end of the symbol/packet transmission.

when you send packets in ping/pong mode, the buffer switching interruption can occur in the middle of a symbol, plus the encoder itself will take a certain amount of time for the rmt buffer size. and the switching itself is not instantaneous, which will quickly lead you to distortion of the symbol itself.

Plus, as far as I remember, the driver always enables ping/pong (for all targets) in tx mode (if the size of the data to be transmitted is larger than the buffer). And it enables this mode in rx mode only for those targets that support this mode in hardware (s3/c3...) with the flag .flags.en_partial_rx = true set

However, both work - but what I suggested is simpler and more stable.

av_jui
Posts: 8
Joined: Wed Jan 08, 2025 11:09 am

Re: [BUG] RMT copy encoder bug with ping-pong mode

Postby av_jui » Mon Sep 15, 2025 5:49 pm

Today I had a bit of time. So I took another closer look at the copyencoder code and I think I found the error. The error occurs when

Code: Select all

mem_have == mem_want
I have now changed the code as follow:

Code: Select all

- bool encoding_truncated = mem_have < mem_want; 
+ bool encoding_truncated = mem_have <= mem_want;
After the change, I can transfer 200 characters at 921600 without any errors.

I will report this and generate a pull reques.

Maybe this is wanted that the copy encoder replayed both states RMT_ENCODING_COMPLETE && RMT_ENCODING_MEM_FULL on this special case. i have add a workaround which handle this case.

Code: Select all

	     /**
             * Handle the special case when the RMT copy encoder returns both COMPLETE and MEM_FULL.
             *
             * In the exact-fit condition (mem_have == mem_want), the ESP-IDF copy encoder sets
             * both RMT_ENCODING_COMPLETE and RMT_ENCODING_MEM_FULL and resets its internal index.
             *
             * If this case is not handled explicitly, the same frame may be re-encoded or data
             * can become misaligned, leading to duplicated or corrupted characters.
             */
            if ((session_state & RMT_ENCODING_COMPLETE) &&
                (session_state & RMT_ENCODING_MEM_FULL))
            {
                uart_encoder->byte_index++;
                uart_encoder->state = UART_ENCODER_GENERATING_FRAME;
                *ret_state = RMT_ENCODING_MEM_FULL;
                return encoded_symbols;
            }
I will report this and see what happened.

Thank you for your input.
Visit my github account :arrow: https://github.com/avjui

Who is online

Users browsing this forum: ChatGPT-User and 5 guests