From d2cc78e541c21df3db032ed773e8c341d167237d Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 08:50:18 +0300 Subject: [PATCH 1/4] i2s_out: explicit parallel_data_bits to support multiple copies of each serial/parallel data signal --- components/i2s_out/esp32/pin.c | 159 +++++++++++++-------------- components/i2s_out/i2s_out.h | 6 +- components/i2s_out/include/i2s_out.h | 44 ++++---- 3 files changed, 102 insertions(+), 107 deletions(-) diff --git a/components/i2s_out/esp32/pin.c b/components/i2s_out/esp32/pin.c index 0c5febf7..a0ef0625 100644 --- a/components/i2s_out/esp32/pin.c +++ b/components/i2s_out/esp32/pin.c @@ -56,7 +56,7 @@ static void clear_gpio_pin(gpio_num_t gpio) int i2s_out_pin_init(struct i2s_out *i2s_out) { - for (int i = 0; i < I2S_OUT_PARALLEL_SIZE; i++) { + for (int i = 0; i < I2S_OUT_GPIO_PINS_MAX; i++) { i2s_out->bck_gpios[i] = -1; i2s_out->data_gpios[i] = -1; i2s_out->inv_data_gpios[i] = -1; @@ -78,20 +78,12 @@ int i2s_out_pin_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt switch (options->mode) { case I2S_OUT_MODE_16BIT_SERIAL: - LOG_DEBUG("port=%d: mode=I2S_OUT_MODE_16BIT_SERIAL bck_gpio=%d serial_data_gpio=%d inv_data_gpio=%d", i2s_out->port, - options->bck_gpio, - options->data_gpio, - options->inv_data_gpio - ); + LOG_DEBUG("port=%d: mode=I2S_OUT_MODE_16BIT_SERIAL ", i2s_out->port); break; case I2S_OUT_MODE_32BIT_SERIAL: - LOG_DEBUG("port=%d: mode=I2S_OUT_MODE_32BIT_SERIAL bck_gpio=%d serial_data_gpio=%d inv_data_gpio=%d", i2s_out->port, - options->bck_gpio, - options->data_gpio, - options->inv_data_gpio - ); + LOG_DEBUG("port=%d: mode=I2S_OUT_MODE_32BIT_SERIAL", i2s_out->port); break; @@ -101,17 +93,7 @@ int i2s_out_pin_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt return -1; } - LOG_DEBUG("port=%d: mode=I2S_OUT_MODE_8BIT_PARALLEL bck_gpio=%d parallel_data_gpio=[%d, %d, %d, %d, %d, %d, %d, %d]", i2s_out->port, - options->bck_gpio, - options->data_gpios[0], - options->data_gpios[1], - options->data_gpios[2], - options->data_gpios[3], - options->data_gpios[4], - options->data_gpios[5], - options->data_gpios[6], - options->data_gpios[7] - ); + LOG_DEBUG("port=%d: mode=I2S_OUT_MODE_8BIT_PARALLEL", i2s_out->port); break; @@ -120,74 +102,91 @@ int i2s_out_pin_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt return -1; } - taskENTER_CRITICAL(&i2s_out->mux); - - switch (options->mode) { - case I2S_OUT_MODE_16BIT_SERIAL: - case I2S_OUT_MODE_32BIT_SERIAL: - if (options->bck_gpio > 0) { - i2s_out->bck_gpio_inv = options->bck_inv; - i2s_out->bck_gpios[0] = options->bck_gpio; - - setup_gpio_pin(options->bck_gpio); - setup_rtc_pin(options->bck_gpio); - - esp_rom_gpio_connect_out_signal(options->bck_gpio, i2s_bck_out_sig[i2s_out->port], options->bck_inv, false); - } - - if (options->data_gpio > 0) { - i2s_out->data_gpios[0] = options->data_gpio; - - setup_gpio_pin(options->data_gpio); - setup_rtc_pin(options->data_gpio); - - esp_rom_gpio_connect_out_signal(options->data_gpio, i2s_serial_data_out_sig[i2s_out->port], false, false); - } + LOG_DEBUG("port=%d: bck_inv=%d bck_gpios=[%d, %d, %d, %d, %d, %d, %d, %d]", i2s_out->port, + options->bck_inv, + options->bck_gpios[0], + options->bck_gpios[1], + options->bck_gpios[2], + options->bck_gpios[3], + options->bck_gpios[4], + options->bck_gpios[5], + options->bck_gpios[6], + options->bck_gpios[7] + ); + LOG_DEBUG("port=%d: data_gpios=[%d, %d, %d, %d, %d, %d, %d, %d]", i2s_out->port, + options->data_gpios[0], + options->data_gpios[1], + options->data_gpios[2], + options->data_gpios[3], + options->data_gpios[4], + options->data_gpios[5], + options->data_gpios[6], + options->data_gpios[7] + ); + LOG_DEBUG("port=%d: inv_data_gpios=[%d, %d, %d, %d, %d, %d, %d, %d]", i2s_out->port, + options->inv_data_gpios[0], + options->inv_data_gpios[1], + options->inv_data_gpios[2], + options->inv_data_gpios[3], + options->inv_data_gpios[4], + options->inv_data_gpios[5], + options->inv_data_gpios[6], + options->inv_data_gpios[7] + ); - if (options->inv_data_gpio > 0) { - i2s_out->inv_data_gpios[0] = options->inv_data_gpio; - - setup_gpio_pin(options->inv_data_gpio); - setup_rtc_pin(options->inv_data_gpio); + taskENTER_CRITICAL(&i2s_out->mux); - // invert gpio output pin - esp_rom_gpio_connect_out_signal(options->inv_data_gpio, i2s_serial_data_out_sig[i2s_out->port], true, false); - } + for (int i = 0; i < I2S_OUT_GPIO_PINS_MAX; i++) { + unsigned data_out_sig; + + switch (options->mode) { + case I2S_OUT_MODE_16BIT_SERIAL: + case I2S_OUT_MODE_32BIT_SERIAL: + // each gpio is a copy of the same serial data signal + data_out_sig = i2s_serial_data_out_sig[i2s_out->port]; + + break; + + case I2S_OUT_MODE_8BIT_PARALLEL: + // loop over the used parallel data signals, repeating as necessary + // data[0] is mapped to the most significant bit, which is OUT7 + // data[7] -> OUT0 + data_out_sig = i2s_parallel8_data_out_sig[i2s_out->port] + 8 - (i % options->parallel_data_bits) - 1; + + break; + + default: + LOG_FATAL("mode=%d", options->mode); + } - break; + if (options->bck_gpios[i] > 0) { + i2s_out->bck_gpio_inv = options->bck_inv; + i2s_out->bck_gpios[i] = options->bck_gpios[i]; - case I2S_OUT_MODE_8BIT_PARALLEL: - for (int i = 0; i < I2S_OUT_PARALLEL_SIZE; i++) { - if (options->bck_gpios[i] > 0) { - i2s_out->bck_gpio_inv = options->bck_inv; - i2s_out->bck_gpios[i] = options->bck_gpios[i]; + setup_gpio_pin(options->bck_gpios[i]); + setup_rtc_pin(options->bck_gpios[i]); - setup_gpio_pin(options->bck_gpios[i]); - setup_rtc_pin(options->bck_gpios[i]); - - esp_rom_gpio_connect_out_signal(options->bck_gpios[i], i2s_bck_out_sig[i2s_out->port], options->bck_inv, false); - } + esp_rom_gpio_connect_out_signal(options->bck_gpios[i], i2s_bck_out_sig[i2s_out->port], options->bck_inv, false); + } - if (options->data_gpios[i] > 0) { - i2s_out->data_gpios[i] = options->data_gpios[i]; + if (options->data_gpios[i] > 0) { + i2s_out->data_gpios[i] = options->data_gpios[i]; - setup_gpio_pin(options->data_gpios[i]); - setup_rtc_pin(options->data_gpios[i]); + setup_gpio_pin(options->data_gpios[i]); + setup_rtc_pin(options->data_gpios[i]); - // data[0] is mapped to the most significant bit, which is OUT7 - esp_rom_gpio_connect_out_signal(options->data_gpios[i], i2s_parallel8_data_out_sig[i2s_out->port] + 8 - i - 1, false, false); - } + esp_rom_gpio_connect_out_signal(options->data_gpios[i], data_out_sig, false, false); + } - if (options->inv_data_gpios[i] > 0) { - i2s_out->inv_data_gpios[i] = options->inv_data_gpios[i]; + if (options->inv_data_gpios[i] > 0) { + i2s_out->inv_data_gpios[i] = options->inv_data_gpios[i]; - setup_gpio_pin(options->inv_data_gpios[i]); - setup_rtc_pin(options->inv_data_gpios[i]); + setup_gpio_pin(options->inv_data_gpios[i]); + setup_rtc_pin(options->inv_data_gpios[i]); - // data[0] is mapped to the most significant bit, which is OUT7 - esp_rom_gpio_connect_out_signal(options->inv_data_gpios[i], i2s_parallel8_data_out_sig[i2s_out->port] + 8 - i - 1, true, false); - } - } + // invert gpio output pin + esp_rom_gpio_connect_out_signal(options->inv_data_gpios[i], data_out_sig, true, false); + } } taskEXIT_CRITICAL(&i2s_out->mux); @@ -203,7 +202,7 @@ void i2s_out_pin_teardown(struct i2s_out *i2s_out) // place output into a safe state - for (int i = 0; i < I2S_OUT_PARALLEL_SIZE; i++) { + for (int i = 0; i < I2S_OUT_GPIO_PINS_MAX; i++) { if (i2s_out->bck_gpios[i] >= 0) { clear_gpio_pin(i2s_out->bck_gpios[i]); diff --git a/components/i2s_out/i2s_out.h b/components/i2s_out/i2s_out.h index 20621df0..1b7ff5e4 100644 --- a/components/i2s_out/i2s_out.h +++ b/components/i2s_out/i2s_out.h @@ -34,9 +34,9 @@ struct i2s_out { SemaphoreHandle_t pin_mutex; #if CONFIG_IDF_TARGET_ESP32 bool bck_gpio_inv; - gpio_num_t bck_gpios[I2S_OUT_PARALLEL_SIZE]; - gpio_num_t data_gpios[I2S_OUT_PARALLEL_SIZE]; - gpio_num_t inv_data_gpios[I2S_OUT_PARALLEL_SIZE]; + gpio_num_t bck_gpios[I2S_OUT_GPIO_PINS_MAX]; + gpio_num_t data_gpios[I2S_OUT_GPIO_PINS_MAX]; + gpio_num_t inv_data_gpios[I2S_OUT_GPIO_PINS_MAX]; #endif /* dma */ diff --git a/components/i2s_out/include/i2s_out.h b/components/i2s_out/include/i2s_out.h index 7cc8560e..011983e3 100644 --- a/components/i2s_out/include/i2s_out.h +++ b/components/i2s_out/include/i2s_out.h @@ -37,8 +37,9 @@ typedef int i2s_port_t; #define I2S_PORT_MAX 2 #define I2S_OUT_GPIO_PINS_SUPPORTED 1 + #define I2S_OUT_GPIO_PINS_MAX 8 #define I2S_OUT_PARALLEL_SUPPORTED 1 - #define I2S_OUT_PARALLEL_SIZE 8 + #define I2S_OUT_PARALLEL_DATA_BITS_MAX 8 #define I2S_OUT_BASE_CLOCK (2 * APB_CLK_FREQ) @@ -105,30 +106,25 @@ struct i2s_out_options { TickType_t pin_timeout; #if I2S_OUT_GPIO_PINS_SUPPORTED - // Use GPIO pin for bit clock out signal, -1 to disable - union { - gpio_num_t bck_gpio; // -1 to disable -# if I2S_OUT_PARALLEL_SUPPORTED - gpio_num_t bck_gpios[I2S_OUT_PARALLEL_SIZE]; // parallel -# endif - }; - bool bck_inv; // invert bck signal - - // Use GPIO pin for data out signal, -1 to disable - union { - gpio_num_t data_gpio; // serial -# if I2S_OUT_PARALLEL_SUPPORTED - gpio_num_t data_gpios[I2S_OUT_PARALLEL_SIZE]; // parallel -# endif - }; + // Use GPIO pin > 0 for bit clock out signal + // Each gpio is a copy of the same clock out signal + gpio_num_t bck_gpios[I2S_OUT_GPIO_PINS_MAX]; - // Use GPIO pin for inverted data out signal, -1 to disable - union { - gpio_num_t inv_data_gpio; // serial -# if I2S_OUT_PARALLEL_SUPPORTED - gpio_num_t inv_data_gpios[I2S_OUT_PARALLEL_SIZE]; // parallel -# endif - }; + // Invert bck signal + bool bck_inv; + + // Use GPIO pin > 0 for data out signal + // In serial mode, each gpio > 0 is a copy of the data bit + // In parallel mode, each gpio > 0 is a separate data bit up to parallel_data_bits, remaining ones repeat + gpio_num_t data_gpios[I2S_OUT_GPIO_PINS_MAX]; + + // Use GPIO pin > 0 for inverted data out signal + gpio_num_t inv_data_gpios[I2S_OUT_GPIO_PINS_MAX]; +#endif + +#if I2S_OUT_PARALLEL_SUPPORTED + // number of parallel data bits in use + unsigned parallel_data_bits; #endif }; From 1e7b50f3c1e12a7ffe493a53106fff76786d2c0c Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 09:05:57 +0300 Subject: [PATCH 2/4] leds: separate i2s parallel, gpio pin count to allow outputing multiple copies of the same signals --- components/leds/i2s.c | 8 ++--- components/leds/include/leds.h | 55 +++++++++++------------------ components/leds/interfaces/i2s/tx.c | 41 ++++++++------------- 3 files changed, 39 insertions(+), 65 deletions(-) diff --git a/components/leds/i2s.c b/components/leds/i2s.c index f5b0e4e0..a8cc04b6 100644 --- a/components/leds/i2s.c +++ b/components/leds/i2s.c @@ -30,24 +30,24 @@ #endif #if CONFIG_LEDS_I2S_ENABLED && I2S_OUT_PARALLEL_SUPPORTED - size_t leds_i2s_parallel_buffer_size(enum leds_protocol protocol, unsigned led_count, unsigned pin_count) + size_t leds_i2s_parallel_buffer_size(enum leds_protocol protocol, unsigned led_count, unsigned parallel) { const struct leds_protocol_type *protocol_type = leds_protocol_type(protocol); if (protocol_type->i2s_interface_mode) { - return leds_interface_i2s_buffer_size(protocol_type->i2s_interface_mode, led_count, pin_count); + return leds_interface_i2s_buffer_size(protocol_type->i2s_interface_mode, led_count, parallel); } else { // not defined return 0; } } - size_t leds_i2s_parallel_buffer_align(enum leds_protocol protocol, unsigned pin_count) + size_t leds_i2s_parallel_buffer_align(enum leds_protocol protocol, unsigned parallel) { const struct leds_protocol_type *protocol_type = leds_protocol_type(protocol); if (protocol_type->i2s_interface_mode) { - return leds_interface_i2s_buffer_align(protocol_type->i2s_interface_mode, pin_count); + return leds_interface_i2s_buffer_align(protocol_type->i2s_interface_mode, parallel); } else { // not defined return 0; diff --git a/components/leds/include/leds.h b/components/leds/include/leds.h index 759864f2..3de7bec5 100644 --- a/components/leds/include/leds.h +++ b/components/leds/include/leds.h @@ -9,8 +9,9 @@ #if CONFIG_LEDS_I2S_ENABLED # include # define LEDS_I2S_GPIO_PINS_ENABLED I2S_OUT_GPIO_PINS_SUPPORTED -# define LEDS_I2S_DATA_PINS_ENABLED I2S_OUT_PARALLEL_SUPPORTED -# define LEDS_I2S_DATA_PINS_SIZE I2S_OUT_PARALLEL_SIZE +# 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 #endif #if CONFIG_LEDS_SPI_ENABLED && CONFIG_IDF_TARGET_ESP8266 @@ -219,39 +220,23 @@ enum leds_interface leds_interface_for_protocol(enum leds_protocol protocol); TickType_t pin_timeout; - #if LEDS_I2S_GPIO_PINS_ENABLED - # if LEDS_I2S_DATA_PINS_ENABLED - unsigned data_pins_count; // default 0 -> serial output with a single data_pin - # endif - - // use GPIO_NUM_NC < 0 to disable - union { - gpio_num_t data_pin; // for pin_count == 0 -> serial output - #if LEDS_I2S_DATA_PINS_ENABLED - gpio_num_t data_pins[LEDS_I2S_DATA_PINS_SIZE]; // for pin_count >= 1 -> parallel output - #endif - }; - - // use GPIO_NUM_NC < 0 to disable - union { - gpio_num_t inv_data_pin; // for pin_count == 0 -> serial output - #if LEDS_I2S_DATA_PINS_ENABLED - gpio_num_t inv_data_pins[LEDS_I2S_DATA_PINS_SIZE]; // for pin_count >= 1 -> parallel output - #endif - }; - #endif - // only used for protocols with separate clock/data lines int clock_rate; #if LEDS_I2S_GPIO_PINS_ENABLED - // use GPIO_NUM_NC < 0 to disable - union { - gpio_num_t clock_pin; - #if LEDS_I2S_DATA_PINS_ENABLED - gpio_num_t clock_pins[LEDS_I2S_DATA_PINS_SIZE]; // for pin_count >= 1 -> parallel output - #endif - }; + unsigned gpio_pins_count; // up to LEDS_I2S_GPIO_PINS_SIZE + + // in serial mode, each pin outputs a copy of the same data signal + // in parallel mode, the first `parallel` pins each output their own data signal, and any remaining pins loop over to repeat the earlier data signals + gpio_num_t clock_pins[LEDS_I2S_GPIO_PINS_SIZE]; // use GPIO_NUM_NC <= 0 to disable + gpio_num_t data_pins[LEDS_I2S_GPIO_PINS_SIZE]; // use GPIO_NUM_NC <= 0 to disable + gpio_num_t inv_data_pins[LEDS_I2S_GPIO_PINS_SIZE]; // use GPIO_NUM_NC <= 0 to disable + #endif + + #if LEDS_I2S_PARALLEL_ENABLED + // enable parallel mode with up to LEDS_I2S_PARALLEL_MAX separate outputs + // default 0 -> serial output with a single data signal + unsigned parallel; #endif }; @@ -263,14 +248,14 @@ enum leds_interface leds_interface_for_protocol(enum leds_protocol protocol); size_t leds_i2s_serial_buffer_size(enum leds_protocol protocol, unsigned led_count); size_t leds_i2s_serial_buffer_align(enum leds_protocol protocol); - #if LEDS_I2S_DATA_PINS_ENABLED + #if LEDS_I2S_PARALLEL_ENABLED /* - * Returns total TX buffer size/align required for protocol with `led_count` LEDs across `pin_count` parallel pins. + * Returns total TX buffer size/align required for protocol with `led_count` LEDs across `parallel` pins. * * @return 0 if not supported for protocol */ - size_t leds_i2s_parallel_buffer_size(enum leds_protocol protocol, unsigned led_count, unsigned pin_count); - size_t leds_i2s_parallel_buffer_align(enum leds_protocol protocol, unsigned pin_count); + size_t leds_i2s_parallel_buffer_size(enum leds_protocol protocol, unsigned led_count, unsigned parallel); + size_t leds_i2s_parallel_buffer_align(enum leds_protocol protocol, unsigned parallel); #endif #endif diff --git a/components/leds/interfaces/i2s/tx.c b/components/leds/interfaces/i2s/tx.c index f3fdcf59..2f4c2301 100644 --- a/components/leds/interfaces/i2s/tx.c +++ b/components/leds/interfaces/i2s/tx.c @@ -182,8 +182,8 @@ int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct l interface->mode = mode; interface->func = func; -#if LEDS_I2S_DATA_PINS_ENABLED - interface->parallel = options->data_pins_count; +#if LEDS_I2S_PARALLEL_ENABLED + interface->parallel = options->parallel; #else interface->parallel = 0; #endif @@ -198,17 +198,12 @@ int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct l // shared IO pins .pin_mutex = options->pin_mutex, .pin_timeout = options->pin_timeout, -#if LEDS_I2S_GPIO_PINS_ENABLED - .bck_gpio = GPIO_NUM_NC, - .data_gpio = options->data_pin, - .inv_data_gpio = options->inv_data_pin, -#endif }; switch(mode) { case LEDS_INTERFACE_I2S_MODE_32BIT_BCK: - #if LEDS_I2S_DATA_PINS_ENABLED - if (interface->parallel) { + #if LEDS_I2S_PARALLEL_ENABLED + if (options->parallel) { interface->i2s_out_options.mode = I2S_OUT_MODE_8BIT_PARALLEL; } else { interface->i2s_out_options.mode = I2S_OUT_MODE_32BIT_SERIAL; @@ -222,8 +217,8 @@ int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct l case LEDS_INTERFACE_I2S_MODE_24BIT_1U200_4X4_80UL: case LEDS_INTERFACE_I2S_MODE_24BIT_1U250_4X4_80UL: case LEDS_INTERFACE_I2S_MODE_32BIT_1U250_4X4_80UL: - #if LEDS_I2S_DATA_PINS_ENABLED - if (interface->parallel) { + #if LEDS_I2S_PARALLEL_ENABLED + if (options->parallel) { interface->i2s_out_options.mode = I2S_OUT_MODE_8BIT_PARALLEL; } else { // using 4x4bit -> 16-bit samples @@ -264,29 +259,23 @@ int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct l LOG_FATAL("unknown mode=%d", mode); } -#if LEDS_I2S_DATA_PINS_ENABLED - if (interface->parallel) { - for (int i = 0; i < I2S_OUT_PARALLEL_SIZE; i++) { - interface->i2s_out_options.bck_gpios[i] = (i < options->data_pins_count && i < LEDS_I2S_DATA_PINS_SIZE) ? options->clock_pins[i] : GPIO_NUM_NC; - interface->i2s_out_options.data_gpios[i] = (i < options->data_pins_count && i < LEDS_I2S_DATA_PINS_SIZE) ? options->data_pins[i] : GPIO_NUM_NC; - interface->i2s_out_options.inv_data_gpios[i] = (i < options->data_pins_count && i < LEDS_I2S_DATA_PINS_SIZE) ? options->inv_data_pins[i] : GPIO_NUM_NC; - } +#if LEDS_I2S_GPIO_PINS_ENABLED + for (int i = 0; i < LEDS_I2S_GPIO_PINS_SIZE; i++) { + interface->i2s_out_options.bck_gpios[i] = (i < options->gpio_pins_count) ? options->clock_pins[i] : GPIO_NUM_NC; + interface->i2s_out_options.data_gpios[i] = (i < options->gpio_pins_count) ? options->data_pins[i] : GPIO_NUM_NC; + interface->i2s_out_options.inv_data_gpios[i] = (i < options->gpio_pins_count) ? options->inv_data_pins[i] : GPIO_NUM_NC; + } +#endif +#if LEDS_I2S_PARALLEL_ENABLED + if (options->parallel) { // I2S LCD mode requires the BCK/WS signal to be inverted when routed through the GPIO matrix // invert BCK to idle high, transition on falling edge, and sample data on rising edge interface->i2s_out_options.bck_inv = true; } else { - interface->i2s_out_options.bck_gpio = options->clock_pin; - interface->i2s_out_options.data_gpio = options->data_pin; - interface->i2s_out_options.inv_data_gpio = options->inv_data_pin; - // BCK is idle low, transition on falling edge, and sample data on rising edge interface->i2s_out_options.bck_inv = false; } -#elif LEDS_I2S_GPIO_PINS_ENABLED - interface->i2s_out_options.bck_gpio = options->clock_pin; - interface->i2s_out_options.data_gpio = options->data_pin; - interface->i2s_out_options.inv_data_gpio = options->inv_data_pin; #endif interface->gpio = options->gpio; From db36148aa43a4632cc1c68079f39efaae6810baa Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 09:31:06 +0300 Subject: [PATCH 3/4] main: allow configuring leds i2s data width separately from gpio pin count --- main/leds_config.h | 9 ++-- main/leds_configtab.i | 27 ++++++++-- main/leds_i2s.c | 121 +++++++++++++++++++++++++++--------------- 3 files changed, 106 insertions(+), 51 deletions(-) diff --git a/main/leds_config.h b/main/leds_config.h index e553d667..7a1e1069 100644 --- a/main/leds_config.h +++ b/main/leds_config.h @@ -134,11 +134,14 @@ struct leds_config { #if CONFIG_LEDS_I2S_ENABLED int i2s_clock; +# if LEDS_I2S_PARALLEL_ENABLED + uint16_t i2s_data_width; +# endif # 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_DATA_PINS_SIZE]; - uint16_t i2s_data_pins[LEDS_I2S_DATA_PINS_SIZE]; - uint16_t i2s_data_inv_pins[LEDS_I2S_DATA_PINS_SIZE]; + uint16_t i2s_clock_pins[LEDS_I2S_GPIO_PINS_SIZE]; + uint16_t i2s_data_pins[LEDS_I2S_GPIO_PINS_SIZE]; + uint16_t i2s_data_inv_pins[LEDS_I2S_GPIO_PINS_SIZE]; # endif #endif diff --git a/main/leds_configtab.i b/main/leds_configtab.i index 599558a4..d722e2d8 100644 --- a/main/leds_configtab.i +++ b/main/leds_configtab.i @@ -66,21 +66,38 @@ 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 }, }, +# if LEDS_I2S_PARALLEL_ENABLED + { CONFIG_TYPE_UINT16, "i2s_data_width", + .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_width, .max = LEDS_I2S_PARALLEL_MAX }, + }, +# endif # if LEDS_I2S_GPIO_PINS_ENABLED { CONFIG_TYPE_UINT16, "i2s_clock_pin", .description = "Output I2S bit clock to GPIO pin. Only used for protocols with a separate clock/data.", - .count = &LEDS_CONFIG.i2s_clock_pin_count, .size = LEDS_I2S_DATA_PINS_SIZE, + .count = &LEDS_CONFIG.i2s_clock_pin_count, .size = LEDS_I2S_GPIO_PINS_SIZE, .uint16_type = { .value = LEDS_CONFIG.i2s_clock_pins, .max = (GPIO_NUM_MAX - 1) }, }, { CONFIG_TYPE_UINT16, "i2s_data_pin", .alias = "i2s_gpio_pin", - .description = "Output I2S data to GPIO pin.", - .count = &LEDS_CONFIG.i2s_data_pin_count, .size = LEDS_I2S_DATA_PINS_SIZE, + .description = ( + "Output serial/parallel I2S data to GPIO pin. 0 -> skip this pin.\n" + "\tIf there are more gpio pins than i2s_data_width, the remaining gpio pins loop over to output copies of the first N signals.\n" + ), + .count = &LEDS_CONFIG.i2s_data_pin_count, .size = LEDS_I2S_GPIO_PINS_SIZE, .uint16_type = { .value = LEDS_CONFIG.i2s_data_pins, .max = (GPIO_NUM_MAX - 1) }, }, { CONFIG_TYPE_UINT16, "i2s_data_inv_pin", - .description = "Output inverted I2S data to GPIO pin.", - .count = &LEDS_CONFIG.i2s_data_inv_pin_count, .size = LEDS_I2S_DATA_PINS_SIZE, + .description = ( + "Output inverted copy of I2S data to GPIO pin. 0 -> skip this pin.\n" + "\tIf there are more gpio pins than i2s_data_width, the remaining gpio pins loop over to output copies of the first N signals.\n" + ), + .count = &LEDS_CONFIG.i2s_data_inv_pin_count, .size = LEDS_I2S_GPIO_PINS_SIZE, .uint16_type = { .value = LEDS_CONFIG.i2s_data_inv_pins, .max = (GPIO_NUM_MAX - 1) }, }, # endif diff --git a/main/leds_i2s.c b/main/leds_i2s.c index 58fd8e66..c70f687a 100644 --- a/main/leds_i2s.c +++ b/main/leds_i2s.c @@ -43,6 +43,26 @@ {}, }; + #if LEDS_I2S_PARALLEL_ENABLED + unsigned config_leds_i2s_data_width(const struct leds_config *config) + { + if (config->i2s_data_width > 0) { + return config->i2s_data_width; + } + + if (config->i2s_data_pin_count > 1 || config->i2s_data_inv_pin_count > 1) { + // parallel output + return config->i2s_data_pin_count > config->i2s_data_inv_pin_count ? config->i2s_data_pin_count : config->i2s_data_inv_pin_count; + } else if (config->i2s_data_pin_count == 1 || config->i2s_data_inv_pin_count == 1) { + // serial output + return 1; + } else { + // no output + return 0; + } + } + #endif + // state struct i2s_out *leds_i2s_out; @@ -75,21 +95,24 @@ // update maximum transfer size size_t size, align; - #if LEDS_I2S_GPIO_PINS_ENABLED - if (config->i2s_data_pin_count > 1 || config->i2s_data_inv_pin_count > 1) { - // parallel output - unsigned pin_count = config->i2s_data_pin_count > config->i2s_data_inv_pin_count ? config->i2s_data_pin_count : config->i2s_data_inv_pin_count; + #if LEDS_I2S_PARALLEL_ENABLED + unsigned data_width = config_leds_i2s_data_width(config); - // parallel output - size = leds_i2s_parallel_buffer_size(config->protocol, config->count, pin_count); - align = leds_i2s_parallel_buffer_align(config->protocol, pin_count); + if (data_width > 1) { + size = leds_i2s_parallel_buffer_size(config->protocol, config->count, data_width); + align = leds_i2s_parallel_buffer_align(config->protocol, data_width); - LOG_INFO("leds%d: i2s%d configured for %u parallel leds on %u pins, data buffer size=%u align=%u", i + 1, i2s_config->port, config->count, pin_count, size, align); - } else { + LOG_INFO("leds%d: i2s%d configured for %u parallel leds on %u pins, data buffer size=%u align=%u", i + 1, i2s_config->port, config->count, data_width, size, align); + } else if (data_width) { size = leds_i2s_serial_buffer_size(config->protocol, config->count); align = leds_i2s_serial_buffer_align(config->protocol); LOG_INFO("leds%d: i2s%d configured for %u serial leds, data buffer size=%u align=%u", i + 1, i2s_config->port, config->count, size, align); + } else { + size = 0; + align = 0; + + LOG_WARN("leds%d: i2s%d configured for %u leds without data outputs", i + 1, i2s_config->port, config->count); } #else size = leds_i2s_serial_buffer_size(config->protocol, config->count); @@ -134,51 +157,63 @@ options->i2s_out = leds_i2s_out; #if CONFIG_IDF_TARGET_ESP8266 + // TODO: use i2s_pin_mutex for arbitrary gpio pins with LEDS_I2S_GPIO_PINS_ENABLED? options->pin_mutex = pin_mutex[PIN_MUTEX_I2S0_DATA]; // shared with console uart0 options->pin_timeout = LEDS_I2S_PIN_TIMEOUT; - #elif LEDS_I2S_GPIO_PINS_ENABLED - if (config->i2s_data_pin_count > 1 || config->i2s_data_inv_pin_count > 1) { - // parallel output - options->data_pins_count = config->i2s_data_pin_count >= config->i2s_data_inv_pin_count ? config->i2s_data_pin_count : config->i2s_data_inv_pin_count; - - for (int i = 0; i < config->i2s_data_pin_count || i < config->i2s_data_inv_pin_count; i++) { - options->data_pins[i] = i < config->i2s_data_pin_count ? config->i2s_data_pins[i] : GPIO_NUM_NC; - options->inv_data_pins[i] = i < config->i2s_data_inv_pin_count ? config->i2s_data_inv_pins[i] : GPIO_NUM_NC; - options->clock_pins[i] = i < config->i2s_clock_pin_count ? config->i2s_clock_pins[i] : GPIO_NUM_NC; - - LOG_INFO("leds%d: parallel data_pins[%d]=%d inv_data_pins[%d]=%d clock_pins[%d]=%d", state->index + 1, - i, options->data_pins[i], - i, options->inv_data_pins[i], - i, options->clock_pins[i] - ); - } + #endif + options->clock_rate = config->i2s_clock; + #if LEDS_I2S_GPIO_PINS_ENABLED + options->gpio_pins_count = 0; + + // max + if (config->i2s_clock_pin_count > options->gpio_pins_count) { + options->gpio_pins_count = config->i2s_clock_pin_count; + } + if (config->i2s_data_pin_count > options->gpio_pins_count) { + options->gpio_pins_count = config->i2s_data_pin_count; + } + if (config->i2s_data_inv_pin_count > options->gpio_pins_count) { + options->gpio_pins_count = config->i2s_data_inv_pin_count; + } + + for (int i = 0; i < options->gpio_pins_count; i++) { + options->data_pins[i] = i < config->i2s_data_pin_count ? config->i2s_data_pins[i] : GPIO_NUM_NC; + options->inv_data_pins[i] = i < config->i2s_data_inv_pin_count ? config->i2s_data_inv_pins[i] : GPIO_NUM_NC; + options->clock_pins[i] = i < config->i2s_clock_pin_count ? config->i2s_clock_pins[i] : GPIO_NUM_NC; + + LOG_INFO("leds%d: i2s data_pins[%d]=%d inv_data_pins[%d]=%d clock_pins[%d]=%d", state->index + 1, + i, options->data_pins[i], + i, options->inv_data_pins[i], + i, options->clock_pins[i] + ); + } + #endif + #if LEDS_I2S_PARALLEL_ENABLED + unsigned data_width = config_leds_i2s_data_width(config); + + if (data_width > 1) { + options->parallel = data_width; + } else if (data_width == 1) { + // just use serial mode + options->parallel = 0; } else { - // serial output - options->data_pins_count = 0; - - options->data_pin = config->i2s_data_pin_count > 0 ? config->i2s_data_pins[0] : GPIO_NUM_NC; - options->inv_data_pin = config->i2s_data_inv_pin_count > 0 ? config->i2s_data_inv_pins[0] : GPIO_NUM_NC; - options->clock_pin = config->i2s_clock_pin_count > 0 ? config->i2s_clock_pins[0] : GPIO_NUM_NC; - - LOG_INFO("leds%d: serial data_pin=%d inv_data_pin=%d clock_pin=%d", state->index + 1, - options->data_pin, - options->inv_data_pin, - options->clock_pin - ); + options->parallel = 0; } - // TODO: use i2s_pin_mutex for arbitrary gpio pins? #endif - options->clock_rate = config->i2s_clock; - - LOG_INFO("leds%d: i2s port=%d: pin_mutex=%p data_pin_count=%u clock_rate=%d", state->index + 1, + LOG_INFO("leds%d: i2s port=%d: pin_mutex=%p clock_rate=%d gpio_pins_count=%u parallel=%u", state->index + 1, i2s_config->port, options->pin_mutex, + options->clock_rate, #if LEDS_I2S_GPIO_PINS_ENABLED - options->data_pins_count, + options->gpio_pins_count, #else 0, #endif - options->clock_rate + #if LEDS_I2S_PARALLEL_ENABLED + options->parallel + #else + 0 + #endif ); return 0; From 454ed3fb0b18f5719f9f6f7775e18defba9663e7 Mon Sep 17 00:00:00 2001 From: Tero Marttila Date: Sat, 25 Oct 2025 09:35:57 +0300 Subject: [PATCH 4/4] leds: fix use of i2s_out_options->parallel_data_bits --- components/i2s_out/esp32/pin.c | 6 +++++- components/i2s_out/include/i2s_out.h | 2 +- components/leds/interfaces/i2s/tx.c | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/components/i2s_out/esp32/pin.c b/components/i2s_out/esp32/pin.c index a0ef0625..c744773c 100644 --- a/components/i2s_out/esp32/pin.c +++ b/components/i2s_out/esp32/pin.c @@ -151,7 +151,11 @@ int i2s_out_pin_setup(struct i2s_out *i2s_out, const struct i2s_out_options *opt // loop over the used parallel data signals, repeating as necessary // data[0] is mapped to the most significant bit, which is OUT7 // data[7] -> OUT0 - data_out_sig = i2s_parallel8_data_out_sig[i2s_out->port] + 8 - (i % options->parallel_data_bits) - 1; + if (options->parallel_data_bits) { + data_out_sig = i2s_parallel8_data_out_sig[i2s_out->port] + 8 - (i % options->parallel_data_bits) - 1; + } else { + data_out_sig = i2s_parallel8_data_out_sig[i2s_out->port] + 8 - i - 1; + } break; diff --git a/components/i2s_out/include/i2s_out.h b/components/i2s_out/include/i2s_out.h index 011983e3..2bf93ae8 100644 --- a/components/i2s_out/include/i2s_out.h +++ b/components/i2s_out/include/i2s_out.h @@ -123,7 +123,7 @@ struct i2s_out_options { #endif #if I2S_OUT_PARALLEL_SUPPORTED - // number of parallel data bits in use + // number of parallel data bits in use, 0 -> all unsigned parallel_data_bits; #endif }; diff --git a/components/leds/interfaces/i2s/tx.c b/components/leds/interfaces/i2s/tx.c index 2f4c2301..88a66c29 100644 --- a/components/leds/interfaces/i2s/tx.c +++ b/components/leds/interfaces/i2s/tx.c @@ -268,6 +268,9 @@ int leds_interface_i2s_init(struct leds_interface_i2s *interface, const struct l #endif #if LEDS_I2S_PARALLEL_ENABLED + // allow multiple copies of parallel data bits on different GPIOs + interface->i2s_out_options.parallel_data_bits = options->parallel; + if (options->parallel) { // I2S LCD mode requires the BCK/WS signal to be inverted when routed through the GPIO matrix // invert BCK to idle high, transition on falling edge, and sample data on rising edge