Page 1 of 1

RMT led encoder - Modify bytes on the fly

Posted: Wed Jan 22, 2025 7:05 pm
by Xylopyrographer
Hi all. I'm working on some code based on the RMT Transmit Example -- LED Strip located here: https://github.com/espressif/esp-idf/tr ... /led_strip

I need to modify led_strip_encoder.c so that I can modify on the fly the byte pulled from the led_strip_pixels buffer before it is encoded and then sent further down the processing chain.

Need to do it this way so the bytes in the led_strip_pixels buffer remain as-is as the modification prior to encoding is destructive to the original byte value.

I don't have enough C or C++ knowledge to understand where in the led_strip_encoder.c code the byte is pulled from the buffer so if anyone can lend a hand would be very appreciated.

Re: RMT led encoder - Modify bytes on the fly

Posted: Wed Jan 22, 2025 9:29 pm
by MicroController
Check out this example.

Re: RMT led encoder - Modify bytes on the fly

Posted: Fri Jan 24, 2025 6:39 am
by Xylopyrographer
Thanks for the reply.

Yes, I did check out the simple encoder though it seems that with that, options like DMA, setting the GPIO pin as open drain, etc. cannot be used as it doesn't take a user defined `rmt_tx_channel_config_t` structure. Mind, I can't find where a default structure (if any) is defined so...

Re: RMT led encoder - Modify bytes on the fly

Posted: Sat Jan 25, 2025 3:06 am
by ESP_Sprite
It does. You still need to initialize the TX channel first before attaching a simple encoder, and that happens with rmt_new_tx_channel() which takes the usual config struct.

Re: RMT led encoder - Modify bytes on the fly

Posted: Sat Jan 25, 2025 5:02 am
by Xylopyrographer
Well all righty then! I will certainly give that a shot. Very much appreciated.

Re: RMT led encoder - Modify bytes on the fly

Posted: Tue Jan 28, 2025 5:06 am
by Xylopyrographer
Edit:
Got it figured!

Short answer; the complete definition of the callback configuration structure is:

Code: Select all

typedef struct {
    rmt_encode_simple_cb_t callback;  /*!< Callback to call for encoding data into RMT items */
    void *arg;                        /*!< Opaque user-supplied argument for callback */
    size_t min_chunk_size;            /*!< Minimum amount of free space, in RMT symbols, the
                                           encoder needs in order to guarantee it always
                                           returns non-zero. Defaults
                                           to 64 if zero / not given. */
} rmt_simple_encoder_config_t;
So in the example, modify this structure to read:

Code: Select all

    const rmt_simple_encoder_config_t simple_encoder_cfg = {
        encoder_callback,
        &brightness
        //Note we don't set min_chunk_size here as the default of 64 is good enough.
    };
And lo and behold it all works with

`uint8_t brightness = 0;`

and the callback_encoder modified to:

Code: Select all

    .
    .
    .
    uint8_t photons = *( uint8_t* )arg; // Cast arg to uint8_t* and dereference to get the brightness value
    if ( data_pos < data_size ) {
        // Encode a byte
        // uint8_t currentByte = scale8( data_bytes[ data_pos ], brightness );
        // uint8_t currentByte = data_bytes[ data_pos ];
        // currentByte = scale8_video( currentByte, brightness );
        uint8_t currentByte = scale8_video( data_bytes[ data_pos ], photons );
    .
    .
    .
Yippe-ki-a! :D
===================

I've figured out a way to modify the data on the fly.

By declaring a global variable `brightness`, and changing the encoder_callback function, I can do (portion of the encoder callback function here):

Code: Select all

    .
    .
    .
    if ( data_pos < data_size ) {
        // Encode a byte
        uint8_t currentByte = scale8_video( data_bytes[ data_pos ], brightness );
        size_t symbol_pos = 0;
        for ( int bitmask = 0x80; bitmask != 0; bitmask >>= 1 ) {
            symbols[ symbol_pos++ ] = ( currentByte & bitmask ) ? ws2812_one : ws2812_zero;
        }
        // We're done; we should have written 8 symbols.
        return symbol_pos;
    }
    .
    .
    .
which works fine (`scale8_video()` modifies the byte without affecting the original data).

Now the callback function is declared as:

Code: Select all

static size_t encoder_callback( const void* data, size_t data_size,
                                size_t symbols_written, size_t symbols_free,
                                rmt_symbol_word_t* symbols, bool *done, void* arg ) 
and the last argument `void* arg` is an opaque pointer to arguments that can be used inside `encoder_callback()`.

However, I cannot figure out the syntax to make this happen -- that is pass the value of `brightness` into `encoder_callback() as an argument.

With `brightness`declared as a global as

`uint8_t brightness = 0;`

and declaring:

`void* arg = &brightness;`

and then modifying the above code snippet to:

Code: Select all

    .
    .
    .
    uint8_t photons = *( uint8_t* )arg;    // Cast arg to uint8_t* and dereference to get the brightness value
    if ( data_pos < data_size ) {
        // Encode a byte
        uint8_t currentByte = scale8_video( data_bytes[ data_pos ], photons );
        size_t symbol_pos = 0;
        for ( int bitmask = 0x80; bitmask != 0; bitmask >>= 1 ) {
            symbols[ symbol_pos++ ] = ( currentByte & bitmask ) ? ws2812_one : ws2812_zero;
        }
        // We're done; we should have written 8 symbols.
        return symbol_pos;
    }
    .
    .
    .
The machine crashes as soon as the call to send the buffer to the LED's is called.

Any help on figuring this out would be very greatly appreciated.