From e642f746e6dfbce8d7269f252373e853f2816d94 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 11:36:17 +0300 Subject: [PATCH 01/10] i2s_out: initialize DMA chain with next = NULL for last desc --- components/i2s_out/esp32/dma.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index f3e262be..2856f0e1 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -69,7 +69,11 @@ struct dma_desc *commit_dma_desc(struct dma_desc *desc) { desc->owner = 1; - return desc->next; + if (desc->next) { + return desc->next; + } else { + return desc; + } } void init_dma_eof_desc(struct dma_desc *eof_desc, uint32_t value, unsigned count) @@ -156,8 +160,8 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) } // initialize linked list of DMA descriptors - init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, i2s_out->dma_eof_desc); - init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), i2s_out->dma_eof_desc); + init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), NULL); i2s_out->dma_rx_count = desc_count; @@ -177,7 +181,7 @@ int i2s_out_dma_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt init_dma_eof_desc(i2s_out->dma_eof_desc, options->eof_value, options->eof_count); // init RX desc - reinit_dma_desc(i2s_out->dma_rx_desc, i2s_out->dma_rx_count, i2s_out->dma_eof_desc); + reinit_dma_desc(i2s_out->dma_rx_desc, i2s_out->dma_rx_count, NULL); taskENTER_CRITICAL(&i2s_out->mux); @@ -255,7 +259,7 @@ size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, s if (desc->len + size > desc->size) { LOG_DEBUG("commit desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) < size=%u -> next=%p", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, size, desc->next); - // commit, try with the next buffer + // commit, try with the next desc, if available i2s_out->dma_write_desc = commit_dma_desc(desc); continue; From a3f9a5a44b2749db3a8b6900e2df1a5433f49593 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 11:36:58 +0300 Subject: [PATCH 02/10] i2s_out: DEBUG each write_desc --- components/i2s_out/esp32/dma.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index 2856f0e1..e2fe61cd 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -331,15 +331,17 @@ void i2s_out_dma_start(struct i2s_out *i2s_out) i2s_out->dma_eof_desc->owner = 1; i2s_out->dma_eof_desc->next = i2s_out->dma_eof_desc; - LOG_DEBUG("dma_write_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", - i2s_out->dma_write_desc, - i2s_out->dma_write_desc->owner, - i2s_out->dma_write_desc->eof, - i2s_out->dma_write_desc->len, - i2s_out->dma_write_desc->size, - i2s_out->dma_write_desc->buf, - i2s_out->dma_write_desc->next - ); + for (unsigned i = 0; i < i2s_out->dma_rx_count; i++) { + LOG_DEBUG("dma_write_desc[%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, + &i2s_out->dma_write_desc[i], + i2s_out->dma_write_desc[i].owner, + i2s_out->dma_write_desc[i].eof, + i2s_out->dma_write_desc[i].len, + i2s_out->dma_write_desc[i].size, + i2s_out->dma_write_desc[i].buf, + i2s_out->dma_write_desc[i].next + ); + } LOG_DEBUG("dma_eof_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", i2s_out->dma_eof_desc, From 81257b98b42273efa702eedeec657eb53380b2ab Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 12:19:13 +0300 Subject: [PATCH 03/10] i2s_out: add dma buffer repeat option --- components/i2s_out/esp32/dma.c | 66 ++++++++++++++++++++++++++-- components/i2s_out/i2s_out.c | 22 +++++++++- components/i2s_out/i2s_out.h | 6 ++- components/i2s_out/include/i2s_out.h | 9 +++- 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index e2fe61cd..ec358852 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -111,7 +111,7 @@ void reinit_dma_desc(struct dma_desc *head, unsigned count, struct dma_desc *nex } } -int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) +int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigned repeat) { size_t buf_size = 0; unsigned desc_count = 0; @@ -133,7 +133,7 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) } } - LOG_DEBUG("size=%u align=%u -> desc_count=%u buf_size=%u", size, align, desc_count, buf_size); + LOG_DEBUG("size=%u align=%u repeat=%u -> desc_count=%u buf_size=%u", size, align, repeat, desc_count, buf_size); // allocate single word-aligned buffer if (!(i2s_out->dma_rx_buf = dma_malloc(buf_size))) { @@ -154,6 +154,10 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) LOG_ERROR("dma_calloc(dma_rx_desc)"); return -1; } + if (repeat && !(i2s_out->dma_repeat_desc = dma_calloc(desc_count * repeat, sizeof(*i2s_out->dma_rx_desc)))) { + LOG_ERROR("dma_calloc(dma_repeat_desc)"); + return -1; + } if (!(i2s_out->dma_eof_desc = dma_calloc(1, sizeof(*i2s_out->dma_eof_desc)))) { LOG_ERROR("dma_calloc(dma_eof_desc)"); return -1; @@ -161,9 +165,13 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) // initialize linked list of DMA descriptors init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + for (unsigned i = 0; i < repeat; i++) { + init_dma_desc(i2s_out->dma_repeat_desc + i * repeat, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + } init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), NULL); i2s_out->dma_rx_count = desc_count; + i2s_out->dma_rx_repeat = repeat; return 0; } @@ -308,6 +316,39 @@ int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size) return len; } +void i2s_out_dma_repeat(struct i2s_out *i2s_out, unsigned count) +{ + struct dma_desc **nextp = &i2s_out->dma_write_desc->next; + + // commit + i2s_out->dma_write_desc->owner = 1; + + for (unsigned i = 0; i < count; i++) { + for (unsigned j = 0; j < i2s_out->dma_rx_count; i++) { + struct dma_desc *s = &i2s_out->dma_write_desc[j]; + struct dma_desc *d = &i2s_out->dma_repeat_desc[i * count + j]; + + if (!s->owner) { + break; + } + + if (nextp) { + *nextp = d; + } + + d->len = s->len; + d->owner = s->owner; + + nextp = &d->next; + } + } + + if (nextp) { + // eof + *nextp = i2s_out->dma_eof_desc; + } +} + int i2s_out_dma_pending(struct i2s_out *i2s_out) { if (i2s_out->dma_start) { @@ -325,8 +366,11 @@ int i2s_out_dma_pending(struct i2s_out *i2s_out) void i2s_out_dma_start(struct i2s_out *i2s_out) { - i2s_out->dma_write_desc->owner = 1; - i2s_out->dma_write_desc->next = i2s_out->dma_eof_desc; + // commit if not repeat() + if (!i2s_out->dma_write_desc->owner) { + i2s_out->dma_write_desc->owner = 1; + i2s_out->dma_write_desc->next = i2s_out->dma_eof_desc; + } i2s_out->dma_eof_desc->owner = 1; i2s_out->dma_eof_desc->next = i2s_out->dma_eof_desc; @@ -342,6 +386,20 @@ void i2s_out_dma_start(struct i2s_out *i2s_out) i2s_out->dma_write_desc[i].next ); } + + for (unsigned i = 0; i < i2s_out->dma_rx_repeat; i++) { + for (unsigned j = 0; i < i2s_out->dma_rx_count; i++) { + LOG_DEBUG("dma_repeat_desc[%u][%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, j, + &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j], + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].owner, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].eof, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].len, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].size, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].buf, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].next + ); + } + } LOG_DEBUG("dma_eof_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", i2s_out->dma_eof_desc, diff --git a/components/i2s_out/i2s_out.c b/components/i2s_out/i2s_out.c index e5e364f8..4db3ee1e 100644 --- a/components/i2s_out/i2s_out.c +++ b/components/i2s_out/i2s_out.c @@ -33,7 +33,7 @@ int i2s_out_init(struct i2s_out *i2s_out, i2s_port_t port) return 0; } -int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, size_t buffer_align) +int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, size_t buffer_align, unsigned repeat_data_count) { struct i2s_out *i2s_out = NULL; int err; @@ -53,7 +53,7 @@ int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, goto error; } - if ((err = i2s_out_dma_init(i2s_out, buffer_size, buffer_align))) { + if ((err = i2s_out_dma_init(i2s_out, buffer_size, buffer_align, repeat_data_count))) { LOG_ERROR("i2s_out_dma_init"); goto error; } @@ -329,6 +329,24 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t } #endif +int i2s_out_repeat(struct i2s_out *i2s_out, unsigned count) +{ + int err = 0; + + if (!xSemaphoreTakeRecursive(i2s_out->mutex, portMAX_DELAY)) { + LOG_ERROR("xSemaphoreTakeRecursive"); + return -1; + } + + i2s_out_dma_repeat(i2s_out, count); + + if (!xSemaphoreGiveRecursive(i2s_out->mutex)) { + LOG_WARN("xSemaphoreGiveRecursive"); + } + + return err; +} + int i2s_out_flush(struct i2s_out *i2s_out) { int err = 0; diff --git a/components/i2s_out/i2s_out.h b/components/i2s_out/i2s_out.h index 1b7ff5e4..f7901750 100644 --- a/components/i2s_out/i2s_out.h +++ b/components/i2s_out/i2s_out.h @@ -42,9 +42,10 @@ struct i2s_out { /* dma */ uint8_t *dma_rx_buf, *dma_eof_buf; struct dma_desc *dma_rx_desc; + struct dma_desc *dma_repeat_desc; struct dma_desc *dma_eof_desc; - unsigned dma_rx_count; + unsigned dma_rx_count, dma_rx_repeat; // pointer to software-owned dma_rx_desc used for write() struct dma_desc *dma_write_desc; @@ -53,11 +54,12 @@ struct i2s_out { }; /* dma.c */ -int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align); +int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigned repeat); int i2s_out_dma_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options); size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, size_t size); void i2s_out_dma_commit(struct i2s_out *i2s_out, unsigned count, size_t size); int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size); +void i2s_out_dma_repeat(struct i2s_out *i2s_out, unsigned count); int i2s_out_dma_pending(struct i2s_out *i2s_out); void i2s_out_dma_start(struct i2s_out *i2s_out); int i2s_out_dma_flush(struct i2s_out *i2s_out); diff --git a/components/i2s_out/include/i2s_out.h b/components/i2s_out/include/i2s_out.h index 2bf93ae8..b0cc0518 100644 --- a/components/i2s_out/include/i2s_out.h +++ b/components/i2s_out/include/i2s_out.h @@ -136,7 +136,7 @@ struct i2s_out_options { * The `buffer_align` MUST be a power of two. * The 32-bit hardware FIFO dictates a minimum 4-byte alignment, and `buffer_align` will be adjusted if necessary. */ -int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, size_t buffer_align); +int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, size_t buffer_align, unsigned repeat_data_count); /** * Setup the I2S output. @@ -219,6 +219,13 @@ int i2s_out_write_serial32(struct i2s_out *i2s_out, const uint32_t *data, size_t int i2s_out_write_parallel8x32(struct i2s_out *i2s_out, uint32_t *data, unsigned width); #endif +/** + * Setup DMA to repeat all written buffers given count of times. Completes any writes. + * + * Returns <0 on error, 0 on success. + */ +int i2s_out_repeat(struct i2s_out *i2s_out, unsigned count); + /** * Start I2S output, and wait for the complete TX buffer and EOF frame to be written. * From d73c164c03feb5f888a34b9153fbda657bc28f4a Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 12:19:33 +0300 Subject: [PATCH 04/10] leds: i2s_data_repeat option --- components/leds/include/leds.h | 4 ++++ components/leds/interfaces/i2s.h | 1 + components/leds/interfaces/i2s/tx.c | 8 ++++++++ main/leds_config.h | 1 + main/leds_configtab.i | 9 +++++++++ main/leds_i2s.c | 24 ++++++++++++++++++------ 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 3de7bec5..316cde24 100644 --- a/components/leds/include/leds.h +++ b/components/leds/include/leds.h @@ -12,6 +12,7 @@ # define LEDS_I2S_GPIO_PINS_SIZE I2S_OUT_GPIO_PINS_MAX # define LEDS_I2S_PARALLEL_ENABLED I2S_OUT_PARALLEL_SUPPORTED # define LEDS_I2S_PARALLEL_MAX I2S_OUT_PARALLEL_DATA_BITS_MAX +# define LEDS_I2S_REPEAT_MAX 64 #endif #if CONFIG_LEDS_SPI_ENABLED && CONFIG_IDF_TARGET_ESP8266 @@ -238,6 +239,9 @@ enum leds_interface leds_interface_for_protocol(enum leds_protocol protocol); // default 0 -> serial output with a single data signal unsigned parallel; #endif + + // repeat data on each output + unsigned repeat; // LEDS_I2S_REPEAT_MAX }; /* diff --git a/components/leds/interfaces/i2s.h b/components/leds/interfaces/i2s.h index 9f64d9ec..ff9678d1 100644 --- a/components/leds/interfaces/i2s.h +++ b/components/leds/interfaces/i2s.h @@ -56,6 +56,7 @@ struct leds_interface_i2s { union leds_interface_i2s_buf *buf; unsigned parallel; + unsigned repeat; struct i2s_out *i2s_out; struct i2s_out_options i2s_out_options; diff --git a/components/leds/interfaces/i2s/tx.c b/components/leds/interfaces/i2s/tx.c index 88a66c29..eaf0850a 100644 --- a/components/leds/interfaces/i2s/tx.c +++ b/components/leds/interfaces/i2s/tx.c @@ -187,6 +187,7 @@ int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct l #else interface->parallel = 0; #endif + interface->repeat = options->repeat; if (!(interface->buf = calloc(1, leds_interface_i2s_buf_size(interface->mode, interface->parallel)))) { LOG_ERROR("calloc"); @@ -333,6 +334,13 @@ int leds_interface_i2s_tx(struct leds_interface_i2s *interface, const struct led } } + if (interface->repeat) { + if ((err = i2s_out_repeat(interface->i2s_out, interface->repeat))) { + LOG_ERROR("i2s_out_repeat"); + goto error; + } + } + WITH_STATS_TIMER(&stats->flush) { if ((err = i2s_out_flush(interface->i2s_out))) { LOG_ERROR("i2s_out_flush"); diff --git a/main/leds_config.h b/main/leds_config.h index 7a1e1069..5d6de8d0 100644 --- a/main/leds_config.h +++ b/main/leds_config.h @@ -137,6 +137,7 @@ struct leds_config { # if LEDS_I2S_PARALLEL_ENABLED uint16_t i2s_data_width; # endif + uint16_t i2s_data_repeat; # if LEDS_I2S_GPIO_PINS_ENABLED unsigned i2s_data_pin_count, i2s_data_inv_pin_count, i2s_clock_pin_count; uint16_t i2s_clock_pins[LEDS_I2S_GPIO_PINS_SIZE]; diff --git a/main/leds_configtab.i b/main/leds_configtab.i index d722e2d8..b52c79fd 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -66,6 +66,15 @@ const struct configtab LEDS_CONFIGTAB[] = { .description = "Output I2S bit rate. Only used for protocols with a separate clock/data.", .enum_type = { .value = &LEDS_CONFIG.i2s_clock, .values = leds_i2s_clock_enum, .default_value = I2S_CLOCK_DEFAULT }, }, + { CONFIG_TYPE_UINT16, "i2s_data_repeat", + .description = ( + "Split LEDs across parallel I2S data signals to different GPIOs.\n" + "\t0 (default, compat) -> automatically determine based on number of configured data pins.\n" + "\t1 -> serial mode with a single data signal, optionally multiple copies of the same leds on each gpio pin.\n" + "\tN -> parallel mode with multiple data signals, separate leds on each gpio pin.\n" + ), + .uint16_type = { .value = &LEDS_CONFIG.i2s_data_repeat, .max = LEDS_I2S_REPEAT_MAX }, + }, # if LEDS_I2S_PARALLEL_ENABLED { CONFIG_TYPE_UINT16, "i2s_data_width", .description = ( diff --git a/main/leds_i2s.c b/main/leds_i2s.c index c70f687a..d475a45a 100644 --- a/main/leds_i2s.c +++ b/main/leds_i2s.c @@ -70,6 +70,7 @@ { const struct leds_i2s_config *i2s_config = &leds_i2s_config; size_t buffer_size = 0, buffer_align = 0; + unsigned data_repeat = 0; bool enabled = false; int err; @@ -127,6 +128,9 @@ if (align > buffer_align) { buffer_align = align; } + if (config->i2s_data_repeat > data_repeat) { + data_repeat = config->i2s_data_repeat; + } } if (!enabled) { @@ -134,11 +138,11 @@ return 0; } - LOG_INFO("leds: i2s port=%d -> buffer_size=%u buffer_align=%u", i2s_config->port, - buffer_size, buffer_align + LOG_INFO("leds: i2s port=%d -> buffer_size=%u buffer_align=%u repeat_data_count=%u", i2s_config->port, + buffer_size, buffer_align, data_repeat ); - if ((err = i2s_out_new(&leds_i2s_out, i2s_config->port, buffer_size, buffer_align))) { + if ((err = i2s_out_new(&leds_i2s_out, i2s_config->port, buffer_size, buffer_align, data_repeat))) { LOG_ERROR("i2s_out_new(port=%d)", i2s_config->port); return err; } @@ -200,7 +204,14 @@ options->parallel = 0; } #endif - LOG_INFO("leds%d: i2s port=%d: pin_mutex=%p clock_rate=%d gpio_pins_count=%u parallel=%u", state->index + 1, + + if (config->i2s_data_repeat) { + LOG_INFO("leds%d: repeat count=%u", state->index + 1, config->i2s_data_repeat); + + options->repeat = config->i2s_data_repeat; + } + + LOG_INFO("leds%d: i2s port=%d: pin_mutex=%p clock_rate=%d gpio_pins_count=%u parallel=%u repeat=%u", state->index + 1, i2s_config->port, options->pin_mutex, options->clock_rate, @@ -210,10 +221,11 @@ 0, #endif #if LEDS_I2S_PARALLEL_ENABLED - options->parallel + options->parallel, #else - 0 + 0, #endif + options->repeat ); return 0; From 1a8e95baea6505103e826b70776fe7ba53cea64a Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 12:19:52 +0300 Subject: [PATCH 05/10] i2s_out: bugfix dma_repeat_desc --- components/i2s_out/esp32/dma.c | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index ec358852..21f64afd 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -166,7 +166,7 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigne // initialize linked list of DMA descriptors init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); for (unsigned i = 0; i < repeat; i++) { - init_dma_desc(i2s_out->dma_repeat_desc + i * repeat, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + init_dma_desc(i2s_out->dma_repeat_desc + i * desc_count, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); } init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), NULL); @@ -324,9 +324,9 @@ void i2s_out_dma_repeat(struct i2s_out *i2s_out, unsigned count) i2s_out->dma_write_desc->owner = 1; for (unsigned i = 0; i < count; i++) { - for (unsigned j = 0; j < i2s_out->dma_rx_count; i++) { - struct dma_desc *s = &i2s_out->dma_write_desc[j]; - struct dma_desc *d = &i2s_out->dma_repeat_desc[i * count + j]; + for (unsigned j = 0; j < i2s_out->dma_rx_count; j++) { + struct dma_desc *s = &i2s_out->dma_rx_desc[j]; + struct dma_desc *d = &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j]; if (!s->owner) { break; @@ -376,27 +376,27 @@ void i2s_out_dma_start(struct i2s_out *i2s_out) i2s_out->dma_eof_desc->next = i2s_out->dma_eof_desc; for (unsigned i = 0; i < i2s_out->dma_rx_count; i++) { - LOG_DEBUG("dma_write_desc[%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, - &i2s_out->dma_write_desc[i], - i2s_out->dma_write_desc[i].owner, - i2s_out->dma_write_desc[i].eof, - i2s_out->dma_write_desc[i].len, - i2s_out->dma_write_desc[i].size, - i2s_out->dma_write_desc[i].buf, - i2s_out->dma_write_desc[i].next + LOG_DEBUG("dma_rx_desc[%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, + &i2s_out->dma_rx_desc[i], + i2s_out->dma_rx_desc[i].owner, + i2s_out->dma_rx_desc[i].eof, + i2s_out->dma_rx_desc[i].len, + i2s_out->dma_rx_desc[i].size, + i2s_out->dma_rx_desc[i].buf, + i2s_out->dma_rx_desc[i].next ); } for (unsigned i = 0; i < i2s_out->dma_rx_repeat; i++) { - for (unsigned j = 0; i < i2s_out->dma_rx_count; i++) { + for (unsigned j = 0; j < i2s_out->dma_rx_count; j++) { LOG_DEBUG("dma_repeat_desc[%u][%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, j, - &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j], - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].owner, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].eof, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].len, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].size, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].buf, - i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_repeat + j].next + &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j], + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].owner, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].eof, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].len, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].size, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].buf, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].next ); } } From ac30ed950e8b8871c95551b83d758212c1c5cc69 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 12:20:01 +0300 Subject: [PATCH 06/10] logging: fix LOG_DEBUG_BUFFER --- components/logging/include/logging.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/logging/include/logging.h b/components/logging/include/logging.h index 5bb248c3..9dc941ad 100644 --- a/components/logging/include/logging.h +++ b/components/logging/include/logging.h @@ -46,7 +46,7 @@ // XXX: CONFIG_LOG_DEFAULT_LEVEL defaults to skip ESP_LOG_DEBUG, and raising will include ALL debug output by default - not possible to override this per-call #if DEBUG - #define LOG_DEBUG_BUFFER(buf, len) IF_DEBUG({ ESP_LOG_BUFFER_HEX_LEVEL(__func__, buf, len, ESP_LOG_INFO); }) + #define LOG_DEBUG_BUFFER(buf, len) do { if (DEBUG) ESP_LOG_BUFFER_HEX_LEVEL(__func__, buf, len, ESP_LOG_INFO); } while(0) #else #define LOG_DEBUG_BUFFER(buf, len) #endif From 41660b8d402013d9d11b10e9707cb88be1996bb9 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 12:32:14 +0300 Subject: [PATCH 07/10] leds: fix i2s_data_repeat description, interpret as N+1 --- main/leds_configtab.i | 6 ++---- main/leds_i2s.c | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/main/leds_configtab.i b/main/leds_configtab.i index b52c79fd..fd1b019d 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -68,10 +68,8 @@ const struct configtab LEDS_CONFIGTAB[] = { }, { CONFIG_TYPE_UINT16, "i2s_data_repeat", .description = ( - "Split LEDs across parallel I2S data signals to different GPIOs.\n" - "\t0 (default, compat) -> automatically determine based on number of configured data pins.\n" - "\t1 -> serial mode with a single data signal, optionally multiple copies of the same leds on each gpio pin.\n" - "\tN -> parallel mode with multiple data signals, separate leds on each gpio pin.\n" + "Repeat the data multiple times on each I2S data output.\n" + "\tFor example, use count = 600, i2s_data_width = 4, i2s_data_repeat = 4 to control 4 sets of 150 LEDs on each output, with different signals on each output, but each set of 150 LEDs on one output using the same signal.\n" ), .uint16_type = { .value = &LEDS_CONFIG.i2s_data_repeat, .max = LEDS_I2S_REPEAT_MAX }, }, diff --git a/main/leds_i2s.c b/main/leds_i2s.c index d475a45a..24110310 100644 --- a/main/leds_i2s.c +++ b/main/leds_i2s.c @@ -205,10 +205,10 @@ } #endif - if (config->i2s_data_repeat) { + if (config->i2s_data_repeat > 1) { LOG_INFO("leds%d: repeat count=%u", state->index + 1, config->i2s_data_repeat); - options->repeat = config->i2s_data_repeat; + options->repeat = config->i2s_data_repeat - 1; } LOG_INFO("leds%d: i2s port=%d: pin_mutex=%p clock_rate=%d gpio_pins_count=%u parallel=%u repeat=%u", state->index + 1, From a58553231b5474123d2dfea973d8a10f328d68b7 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 15:53:11 +0300 Subject: [PATCH 08/10] leds: rename i2s_data_copies to match repeat + 1 --- main/leds_config.h | 2 +- main/leds_configtab.i | 10 ++++++---- main/leds_i2s.c | 10 +++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/main/leds_config.h b/main/leds_config.h index 5d6de8d0..bedda6f6 100644 --- a/main/leds_config.h +++ b/main/leds_config.h @@ -137,7 +137,7 @@ struct leds_config { # if LEDS_I2S_PARALLEL_ENABLED uint16_t i2s_data_width; # endif - uint16_t i2s_data_repeat; + uint16_t i2s_data_copies; # if LEDS_I2S_GPIO_PINS_ENABLED unsigned i2s_data_pin_count, i2s_data_inv_pin_count, i2s_clock_pin_count; uint16_t i2s_clock_pins[LEDS_I2S_GPIO_PINS_SIZE]; diff --git a/main/leds_configtab.i b/main/leds_configtab.i index fd1b019d..f24ef35e 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -66,12 +66,14 @@ const struct configtab LEDS_CONFIGTAB[] = { .description = "Output I2S bit rate. Only used for protocols with a separate clock/data.", .enum_type = { .value = &LEDS_CONFIG.i2s_clock, .values = leds_i2s_clock_enum, .default_value = I2S_CLOCK_DEFAULT }, }, - { CONFIG_TYPE_UINT16, "i2s_data_repeat", + { CONFIG_TYPE_UINT16, "i2s_data_copies", .description = ( - "Repeat the data multiple times on each I2S data output.\n" - "\tFor example, use count = 600, i2s_data_width = 4, i2s_data_repeat = 4 to control 4 sets of 150 LEDs on each output, with different signals on each output, but each set of 150 LEDs on one output using the same signal.\n" + "Output multiple copies of the data on each I2S output, to control a series of identical LEDs.\n" + "\tFor example, use count = 600, i2s_data_width = 4, i2s_data_copies = 4 to control 4 sets of 150 LEDs on each of four outputs." + "Each output will be independently controllable, but each set of 150 LEDs per output will behave identically.\n" + "\t0 -> disabled, 1 -> no effect, N -> repeat data for a total of N copies per output." ), - .uint16_type = { .value = &LEDS_CONFIG.i2s_data_repeat, .max = LEDS_I2S_REPEAT_MAX }, + .uint16_type = { .value = &LEDS_CONFIG.i2s_data_copies, .max = LEDS_I2S_REPEAT_MAX }, }, # if LEDS_I2S_PARALLEL_ENABLED { CONFIG_TYPE_UINT16, "i2s_data_width", diff --git a/main/leds_i2s.c b/main/leds_i2s.c index 24110310..23dc6448 100644 --- a/main/leds_i2s.c +++ b/main/leds_i2s.c @@ -128,8 +128,8 @@ if (align > buffer_align) { buffer_align = align; } - if (config->i2s_data_repeat > data_repeat) { - data_repeat = config->i2s_data_repeat; + if (config->i2s_data_copies > data_repeat + 1) { + data_repeat = config->i2s_data_copies - 1; } } @@ -205,10 +205,10 @@ } #endif - if (config->i2s_data_repeat > 1) { - LOG_INFO("leds%d: repeat count=%u", state->index + 1, config->i2s_data_repeat); + if (config->i2s_data_copies > 1) { + LOG_INFO("leds%d: repeat copies=%u", state->index + 1, config->i2s_data_copies); - options->repeat = config->i2s_data_repeat - 1; + options->repeat = config->i2s_data_copies - 1; } LOG_INFO("leds%d: i2s port=%d: pin_mutex=%p clock_rate=%d gpio_pins_count=%u parallel=%u repeat=%u", state->index + 1, From dde014885e7a3743116e552d402094e67b701af9 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 15:58:35 +0300 Subject: [PATCH 09/10] i2s_out: defensive dma_write_desc->next --- components/i2s_out/esp32/dma.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index 21f64afd..23d9ca80 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -369,6 +369,9 @@ void i2s_out_dma_start(struct i2s_out *i2s_out) // commit if not repeat() if (!i2s_out->dma_write_desc->owner) { i2s_out->dma_write_desc->owner = 1; + } + + if (!i2s_out->dma_write_desc->next) { i2s_out->dma_write_desc->next = i2s_out->dma_eof_desc; } From 81bc96c6977f5b19f50972854d369b526f635d58 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 16:05:23 +0300 Subject: [PATCH 10/10] i2s_out: fix esp8266 build --- components/i2s_out/esp8266/dma.c | 103 +++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/components/i2s_out/esp8266/dma.c b/components/i2s_out/esp8266/dma.c index 5563a9b6..89587ca0 100644 --- a/components/i2s_out/esp8266/dma.c +++ b/components/i2s_out/esp8266/dma.c @@ -70,7 +70,11 @@ struct dma_desc *commit_dma_desc(struct dma_desc *desc) { desc->owner = 1; - return desc->next; + if (desc->next) { + return desc->next; + } else { + return desc; + } } void init_dma_eof_desc(struct dma_desc *eof_desc, uint32_t value, unsigned count) @@ -108,7 +112,7 @@ void reinit_dma_desc(struct dma_desc *head, unsigned count, struct dma_desc *nex } } -int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) +int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align, unsigned repeat) { size_t buf_size = 0; unsigned desc_count = 0; @@ -130,7 +134,7 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) } } - LOG_DEBUG("size=%u align=%u -> desc_count=%u buf_size=%u", size, align, desc_count, buf_size); + LOG_DEBUG("size=%u align=%u repeat=%u -> desc_count=%u buf_size=%u", size, align, repeat, desc_count, buf_size); // allocate single word-aligned buffer if (!(i2s_out->dma_rx_buf = dma_malloc(buf_size))) { @@ -151,16 +155,24 @@ int i2s_out_dma_init(struct i2s_out *i2s_out, size_t size, size_t align) LOG_ERROR("dma_calloc(dma_rx_desc)"); return -1; } + if (repeat && !(i2s_out->dma_repeat_desc = dma_calloc(desc_count * repeat, sizeof(*i2s_out->dma_rx_desc)))) { + LOG_ERROR("dma_calloc(dma_repeat_desc)"); + return -1; + } if (!(i2s_out->dma_eof_desc = dma_calloc(1, sizeof(*i2s_out->dma_eof_desc)))) { LOG_ERROR("dma_calloc(dma_eof_desc)"); return -1; } // initialize linked list of DMA descriptors - init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, i2s_out->dma_eof_desc); - init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), i2s_out->dma_eof_desc); + init_dma_desc(i2s_out->dma_rx_desc, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + for (unsigned i = 0; i < repeat; i++) { + init_dma_desc(i2s_out->dma_repeat_desc + i * desc_count, desc_count, i2s_out->dma_rx_buf, buf_size, align, NULL); + } + init_dma_desc(i2s_out->dma_eof_desc, 1, i2s_out->dma_eof_buf, DMA_EOF_BUF_SIZE, sizeof(uint32_t), NULL); i2s_out->dma_rx_count = desc_count; + i2s_out->dma_rx_repeat = repeat; // setup isr slc_isr_mask(); @@ -218,7 +230,7 @@ int i2s_out_dma_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt init_dma_eof_desc(i2s_out->dma_eof_desc, options->eof_value, options->eof_count); // init RX desc - reinit_dma_desc(i2s_out->dma_rx_desc, i2s_out->dma_rx_count, i2s_out->dma_eof_desc); + reinit_dma_desc(i2s_out->dma_rx_desc, i2s_out->dma_rx_count, NULL); taskENTER_CRITICAL(); @@ -295,7 +307,7 @@ size_t i2s_out_dma_buffer(struct i2s_out *i2s_out, void **ptr, unsigned count, s if (desc->len + size > desc->size) { LOG_DEBUG("commit desc=%p (owner=%u eof=%u buf=%p len=%u size=%u) < size=%u -> next=%p", desc, desc->owner, desc->eof, desc->buf, desc->len, desc->size, size, desc->next); - // commit, try with the next buffer + // commit, try with the next desc, if available i2s_out->dma_write_desc = commit_dma_desc(desc); continue; @@ -343,6 +355,39 @@ int i2s_out_dma_write(struct i2s_out *i2s_out, const void *data, size_t size) return len; } +void i2s_out_dma_repeat(struct i2s_out *i2s_out, unsigned count) +{ + struct dma_desc **nextp = &i2s_out->dma_write_desc->next; + + // commit + i2s_out->dma_write_desc->owner = 1; + + for (unsigned i = 0; i < count; i++) { + for (unsigned j = 0; j < i2s_out->dma_rx_count; j++) { + struct dma_desc *s = &i2s_out->dma_rx_desc[j]; + struct dma_desc *d = &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j]; + + if (!s->owner) { + break; + } + + if (nextp) { + *nextp = d; + } + + d->len = s->len; + d->owner = s->owner; + + nextp = &d->next; + } + } + + if (nextp) { + // eof + *nextp = i2s_out->dma_eof_desc; + } +} + int i2s_out_dma_pending(struct i2s_out *i2s_out) { if (i2s_out->dma_start) { @@ -360,21 +405,43 @@ int i2s_out_dma_pending(struct i2s_out *i2s_out) void i2s_out_dma_start(struct i2s_out *i2s_out) { - i2s_out->dma_write_desc->owner = 1; - i2s_out->dma_write_desc->next = i2s_out->dma_eof_desc; + // commit if not repeat() + if (!i2s_out->dma_write_desc->owner) { + i2s_out->dma_write_desc->owner = 1; + } + + if (!i2s_out->dma_write_desc->next) { + i2s_out->dma_write_desc->next = i2s_out->dma_eof_desc; + } i2s_out->dma_eof_desc->owner = 1; i2s_out->dma_eof_desc->next = i2s_out->dma_eof_desc; - LOG_DEBUG("dma_write_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", - i2s_out->dma_write_desc, - i2s_out->dma_write_desc->owner, - i2s_out->dma_write_desc->eof, - i2s_out->dma_write_desc->len, - i2s_out->dma_write_desc->size, - i2s_out->dma_write_desc->buf, - i2s_out->dma_write_desc->next - ); + for (unsigned i = 0; i < i2s_out->dma_rx_count; i++) { + LOG_DEBUG("dma_rx_desc[%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, + &i2s_out->dma_rx_desc[i], + i2s_out->dma_rx_desc[i].owner, + i2s_out->dma_rx_desc[i].eof, + i2s_out->dma_rx_desc[i].len, + i2s_out->dma_rx_desc[i].size, + i2s_out->dma_rx_desc[i].buf, + i2s_out->dma_rx_desc[i].next + ); + } + + for (unsigned i = 0; i < i2s_out->dma_rx_repeat; i++) { + for (unsigned j = 0; j < i2s_out->dma_rx_count; j++) { + LOG_DEBUG("dma_repeat_desc[%u][%u]=%p: owner=%d eof=%d len=%u size=%u buf=%p next=%p", i, j, + &i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j], + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].owner, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].eof, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].len, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].size, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].buf, + i2s_out->dma_repeat_desc[i * i2s_out->dma_rx_count + j].next + ); + } + } LOG_DEBUG("dma_eof_desc=%p: owner=%d eof=%d len=%u size=%u -> buf=%p next=%p", i2s_out->dma_eof_desc,