Commit 1b38d8e6 authored by Tom Sutcliffe's avatar Tom Sutcliffe Committed by Johny Mattsson
Browse files

Fix ws2812 on later esp32 variants

By not relying on the internal details of the RMT registers. For example
on the esp32s2, the threshold event bits start at bit 12 not bit 24, and
on the esp32s3 the memory buffers are only 48 words not 64. Also the
esp-idf recommends against hooking the rmt ISR in the first place.

Instead, use the published rmt APIs (specifically rmt_write_sample and
rmt_translator_init) and the default rmt ISR, which should be much more
resilient against future esp32 SoC changes.
parent 32d03a21
...@@ -33,6 +33,11 @@ ...@@ -33,6 +33,11 @@
#undef WS2812_DEBUG #undef WS2812_DEBUG
// If either of these fails, the reset logic in ws2812_sample_to_rmt will need revisiting.
_Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL % 8 == 0,
"SOC_RMT_MEM_WORDS_PER_CHANNEL is assumed to be a multiple of 8");
_Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL >= 16,
"SOC_RMT_MEM_WORDS_PER_CHANNEL is assumed to be >= 16");
// divider to generate 100ns base period from 80MHz APB clock // divider to generate 100ns base period from 80MHz APB clock
#define WS2812_CLKDIV (100 * 80 /1000) #define WS2812_CLKDIV (100 * 80 /1000)
...@@ -58,84 +63,67 @@ const rmt_item32_t ws2812_rmt_bit1 = { ...@@ -58,84 +63,67 @@ const rmt_item32_t ws2812_rmt_bit1 = {
.duration1 = WS2812_DURATION_T1L .duration1 = WS2812_DURATION_T1L
}; };
#define ws2812_rmt_reset {.level0 = 0, .duration0 = 4, .level1 = 0, .duration1 = 4} // This is one eighth of 512 * 100ns, ie in total a bit above the requisite 50us
// reset signal, spans one complete buffer block const rmt_item32_t ws2812_rmt_reset = { .level0 = 0, .duration0 = 32, .level1 = 0, .duration1 = 32 };
const rmt_item32_t ws2812_rmt_reset_block[64] = { [0 ... 63] = ws2812_rmt_reset };
// descriptor for a ws2812 chain // descriptor for a ws2812 chain
typedef struct { typedef struct {
bool valid; bool valid;
bool needs_reset;
uint8_t gpio; uint8_t gpio;
const uint8_t *data; const uint8_t *data;
size_t len; size_t len;
size_t tx_offset;
} ws2812_chain_t; } ws2812_chain_t;
// chain descriptor array // chain descriptor array
static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX]; static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX];
// interrupt handler for ws2812 ISR #define MIN(a, b) ((a) < (b) ? (a) : (b))
static intr_handle_t ws2812_intr_handle;
static void ws2812_fill_memory_encoded( rmt_channel_t channel, const uint8_t *data, size_t len, size_t tx_offset ) static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num)
{ {
for (size_t idx = 0; idx < len; idx++) { // Note: enabling these commented-out logs will ruin the timing so nothing
uint8_t byte = data[idx]; // will actually work when they're enabled. But I've kept them in as comments
// because they were useful in debugging the buffer management.
// ESP_DRAM_LOGW("ws2812", "ws2812_sample_to_rmt wanted=%u src_size=%u", wanted_num, src_size);
void *ctx;
rmt_translator_get_context(item_num, &ctx);
ws2812_chain_t *chain = (ws2812_chain_t *)ctx;
size_t reset_num = 0;
if (chain->needs_reset) {
// Haven't sent reset yet
// We split the reset into 8 even though it would fit in a single
// rmt_item32_t, simply so that dest stays 8-item aligned which means we
// don't have to worry about having to split a byte of src across multiple
// blocks (assuming the static asserts at the top of this file are true).
for (int i = 0; i < 8; i++) {
dest[i] = ws2812_rmt_reset;
}
dest += 8;
wanted_num -= 8;
reset_num = 8;
chain->needs_reset = false;
}
// Now write the actual data from src
const uint8_t *data = (const uint8_t *)src;
size_t data_num = MIN(wanted_num, src_size * 8) / 8;
for (size_t idx = 0; idx < data_num; idx++) {
uint8_t byte = data[idx];
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
RMTMEM.chan[channel].data32[tx_offset + idx*8 + i].val = byte & 0x80 ? ws2812_rmt_bit1.val : ws2812_rmt_bit0.val; dest[idx * 8 + i] = (byte & 0x80) ? ws2812_rmt_bit1 : ws2812_rmt_bit0;
byte <<= 1; byte <<= 1;
} }
} }
}
*translated_size = data_num;
static void ws2812_isr(void *arg) *item_num = reset_num + data_num * 8;
{ // ESP_DRAM_LOGW("ws2812", "src bytes consumed: %u total rmt items: %u", *translated_size, *item_num);
uint32_t intr_st = RMT.int_st.val;
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
if ((intr_st & (BIT(channel+24))) && (ws2812_chains[channel].valid)) {
RMT.int_clr.val = BIT(channel+24);
ws2812_chain_t *chain = &(ws2812_chains[channel]);
#if defined(CONFIG_IDF_TARGET_ESP32)
uint32_t data_sub_len = RMT.tx_lim_ch[channel].limit/8;
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
uint32_t data_sub_len = RMT.chn_tx_lim[channel].tx_lim_chn/8;
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
uint32_t data_sub_len = RMT.tx_lim_ch[channel].tx_lim/8;
#else
uint32_t data_sub_len = RMT.tx_lim[channel].limit/8;
#endif
if (chain->len >= data_sub_len) {
ws2812_fill_memory_encoded( channel, chain->data, data_sub_len, chain->tx_offset );
chain->data += data_sub_len;
chain->len -= data_sub_len;
} else if (chain->len == 0) {
RMTMEM.chan[channel].data32[chain->tx_offset].val = 0;
} else {
ws2812_fill_memory_encoded( channel, chain->data, chain->len, chain->tx_offset );
RMTMEM.chan[channel].data32[chain->tx_offset + chain->len*8].val = 0;
chain->data += chain->len;
chain->len = 0;
}
if (chain->tx_offset == 0) {
chain->tx_offset = data_sub_len * 8;
} else {
chain->tx_offset = 0;
}
}
}
} }
int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len ) int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len )
{ {
int channel; int channel;
...@@ -147,7 +135,7 @@ int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *dat ...@@ -147,7 +135,7 @@ int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *dat
chain->gpio = gpio_num; chain->gpio = gpio_num;
chain->len = len; chain->len = len;
chain->data = data; chain->data = data;
chain->tx_offset = 0; chain->needs_reset = true;
#ifdef WS2812_DEBUG #ifdef WS2812_DEBUG
ESP_LOGI("ws2812", "Setup done for gpio %d on RMT channel %d", gpio_num, channel); ESP_LOGI("ws2812", "Setup done for gpio %d on RMT channel %d", gpio_num, channel);
...@@ -209,23 +197,23 @@ int platform_ws2812_send( void ) ...@@ -209,23 +197,23 @@ int platform_ws2812_send( void )
res = PLATFORM_ERR; res = PLATFORM_ERR;
break; break;
} }
if (rmt_translator_init(channel, ws2812_sample_to_rmt) != ESP_OK) {
res = PLATFORM_ERR;
break;
}
if (rmt_translator_set_context(channel, &ws2812_chains[channel]) != ESP_OK) {
res = PLATFORM_ERR;
break;
}
} }
} }
// hook-in our shared ISR
esp_intr_alloc( ETS_RMT_INTR_SOURCE, PLATFORM_RMT_INTR_FLAGS, ws2812_isr, NULL, &ws2812_intr_handle );
// start selected channels one by one // start selected channels one by one
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) { for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
if (ws2812_chains[channel].valid) { if (ws2812_chains[channel].valid) {
// we just feed a single block for generating the reset to the rmt driver // ws2812_sample_to_rmt takes care of translating the data to rmt_item32_t
// the actual payload data is handled by our private shared ISR // format, as well as prepending the reset sequence.
if (rmt_write_items( channel, if (rmt_write_sample(channel, ws2812_chains[channel].data, ws2812_chains[channel].len, false) != ESP_OK) {
(rmt_item32_t *)ws2812_rmt_reset_block,
64,
false ) != ESP_OK) {
res = PLATFORM_ERR; res = PLATFORM_ERR;
break; break;
} }
...@@ -239,11 +227,6 @@ int platform_ws2812_send( void ) ...@@ -239,11 +227,6 @@ int platform_ws2812_send( void )
} }
} }
// un-hook our ISR
esp_intr_free( ws2812_intr_handle );
return res; return res;
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment