ESP32 using RMT driver for neopixel RGB leds
Posted: 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:
setPixelColor() will set the values in heap allocated memory for the RGB.
And then the show method will invoke espShow function
The following function is where everything gets little bit complicated
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
The callback translator function is described here:
Could someone clarify to me what are the differences and how can I simplify this code further?
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.
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);
}
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);
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;
}