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.
This commit is contained in:
parent
32d03a21da
commit
1b38d8e6eb
|
@ -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 = {
|
||||||
.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_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)
|
||||||
static void ws2812_fill_memory_encoded( rmt_channel_t channel, const uint8_t *data, size_t len, size_t tx_offset )
|
|
||||||
{
|
{
|
||||||
for (size_t idx = 0; idx < len; idx++) {
|
// Note: enabling these commented-out logs will ruin the timing so nothing
|
||||||
|
// 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];
|
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;
|
||||||
|
*item_num = reset_num + data_num * 8;
|
||||||
|
// ESP_DRAM_LOGW("ws2812", "src bytes consumed: %u total rmt items: %u", *translated_size, *item_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void ws2812_isr(void *arg)
|
|
||||||
{
|
|
||||||
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
|
||||||
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 )
|
||||||
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 )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// un-hook our ISR
|
|
||||||
esp_intr_free( ws2812_intr_handle );
|
|
||||||
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue