ESP32 using RMT driver for neopixel RGB leds

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

ESP32 using RMT driver for neopixel RGB leds

Postby zazas321 » Wed May 05, 2021 12:15 pm

Hey. I am learning programming and how to write my own custom drivers for various modules and IC's. I have been using various neopixel libraries for a while hence I have decided to give it a go and write my own driver for it solely for the purpose to learn various programming techniques. I am using Adafruit neopixel library as a reference which uses RMT. https://github.com/adafruit/Adafruit_NeoPixel


I have got to the point in the code where the RMT module is being initialized :

In my main.c, after creating a constructor and calling begin() functions, I call 2 methods:

Code: Select all

    custom_leds.setPixelColor(i, 100, 0, 0);
    custom_leds.show(); // Send the updated pixel colors to the hardware.
setPixelColor() will set the values in heap allocated memory for the RGB.

Code: Select all

void ws2812b::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b)
{
    if (n < numLEDs)
    {
        uint8_t *p;
        p = &pixels[n * 3]; // p points to pixel which is allocated memory for the NUM_OF_LEDS * 3 bytes
        p[rOffset] = r;     // R,G,B always stored
        p[gOffset] = g;
        p[bOffset] = b;
    }
}

And then the show method will invoke espShow function

Code: Select all

void ws2812b::show(void)
{
    if (!pixels)
        return;

    while (!canShow())
        ;

    espShow(pin, pixels, numBytes);
}


The following function is where everything gets little bit complicated

Code: Select all

void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes)
{
    // Reserve channel
    rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX;
    for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++)
    {
        if (!rmt_reserved_channels[i])
        {
            rmt_reserved_channels[i] = true;
            channel = i;
        }
    }
    if (channel == ADAFRUIT_RMT_CHANNEL_MAX)
    {
        // Ran out of channels!
        return;
    }

    // Match default TX config from ESP-IDF version 3.4
    rmt_config_t config = {
        .rmt_mode = RMT_MODE_TX,
        .channel = channel,
        .gpio_num = pin,
        .clk_div = 2,
        .mem_block_num = 1,
        .tx_config = {
            //.carrier_freq_hz = 38000,
            //.carrier_level = RMT_CARRIER_LEVEL_HIGH,
            .idle_level = RMT_IDLE_LEVEL_LOW,
            //.carrier_duty_percent = 33,
            .carrier_en = false,
            .loop_en = false,
            .idle_output_en = true,
        }};
    rmt_config(&config);
    rmt_driver_install(config.channel, 0, 0);

    // Convert NS timings to ticks
    uint32_t counter_clk_hz = 0;

    // this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
    if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF)
    {
        uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
        uint32_t div = div_cnt == 0 ? 256 : div_cnt;
        counter_clk_hz = REF_CLK_FREQ / (div);
    }
    else
    {
        uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
        uint32_t div = div_cnt == 0 ? 256 : div_cnt;
        counter_clk_hz = APB_CLK_FREQ / (div);
    }

    // NS to tick converter
    float ratio = (float)counter_clk_hz / 1e9;

    t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
    t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
    t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
    t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);

    // Initialize automatic timing translator
    rmt_translator_init(config.channel, ws2812_rmt_adapter);

    // Write and wait to finish
    rmt_write_sample(config.channel, pixels, (size_t)numBytes, true);
    rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));

    // Free channel again
    rmt_driver_uninstall(config.channel);
    rmt_reserved_channels[channel] = false;

    gpio_set_direction(pin, GPIO_MODE_OUTPUT);
}
Has anyone here used the RMT? I am trying to learn more about it but from what I have read in the espressif documentation :
https://docs.espressif.com/projects/esp ... nsmit-data


After the rmt is configured, they suggest using rmt_write_items() method whereas adafruit is using some sort of translator with the callback and then write_sample

Code: Select all

rmt_translator_init(config.channel, ws2812_rmt_adapter);
rmt_write_sample(config.channel, pixels, (size_t)numBytes, true);
The callback translator function is described here:

Code: Select all

bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX];

static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
                                         size_t wanted_num, size_t *translated_size, size_t *item_num)
{
    if (src == NULL || dest == NULL)
    {
        *translated_size = 0;
        *item_num = 0;
        return;
    }
    const rmt_item32_t bit0 = {{{t0h_ticks, 1, t0l_ticks, 0}}}; //Logical 0
    const rmt_item32_t bit1 = {{{t1h_ticks, 1, t1l_ticks, 0}}}; //Logical 1
    size_t size = 0;
    size_t num = 0;
    uint8_t *psrc = (uint8_t *)src;
    rmt_item32_t *pdest = dest;
    while (size < src_size && num < wanted_num)
    {
        for (int i = 0; i < 8; i++)
        {
            // MSB first
            if (*psrc & (1 << (7 - i)))
            {
                pdest->val = bit1.val;
            }
            else
            {
                pdest->val = bit0.val;
            }
            num++;
            pdest++;
        }
        size++;
        psrc++;
    }
    *translated_size = size;
    *item_num = num;
}
Could someone clarify to me what are the differences and how can I simplify this code further?

Who is online

Users browsing this forum: No registered users and 64 guests