From d3bc27b9f012b81e41d9c12dcc4484e1ae4399dc Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Fri, 27 Jan 2023 10:12:48 -0800 Subject: [PATCH] Esp32 i2s x (#630) ESP32 x8 and X16 parallel support --------- Co-authored-by: Awawa <69086569+awawa-dev@users.noreply.github.com> --- .../NeoPixel_ESP32_I2sParallel.ino | 54 ++ src/NeoPixelBus.h | 3 +- src/internal/DotStarEsp32DmaSpiMethod.h | 6 + src/internal/DotStarGenericMethod.h | 6 + src/internal/Esp32_i2s.c | 596 +++++++------ src/internal/Esp32_i2s.h | 20 +- src/internal/Lpd6803GenericMethod.h | 6 + src/internal/Lpd8806GenericMethod.h | 6 + src/internal/NeoArmMethod.h | 6 + src/internal/NeoAvrMethod.h | 6 + src/internal/NeoEsp32I2sMethod.h | 20 +- src/internal/NeoEsp32I2sXMethod.h | 796 ++++++++++++++++++ src/internal/NeoEsp32RmtMethod.h | 6 + src/internal/NeoEsp8266DmaMethod.h | 6 + src/internal/NeoEsp8266I2sDmx512Method.h | 6 + src/internal/NeoEsp8266UartMethod.h | 6 + src/internal/NeoEspBitBangMethod.h | 6 + src/internal/NeoNrf52xMethod.h | 6 + src/internal/P9813GenericMethod.h | 6 + src/internal/Tlc5947GenericMethod.h | 6 + src/internal/Ws2801GenericMethod.h | 6 + 21 files changed, 1316 insertions(+), 263 deletions(-) create mode 100644 examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino create mode 100644 src/internal/NeoEsp32I2sXMethod.h diff --git a/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino b/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino new file mode 100644 index 0000000..72d5582 --- /dev/null +++ b/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino @@ -0,0 +1,54 @@ +// +// NeoPixel_ESP32_I2sParallel - +// This sketch demonstrates the use of the I2S Parallel method allowing upto 8 hardware updated channels +// This example only works on the ESP32 +// +// The key part of the method name is Esp32I2s1X8, +// E2p32 (platform specific method), +// I2s Channel 1 (most commonly available), +// X8 (8 parallel channel mode) +// +// In this example, it demonstrates different ColorFeatures, Method specification, and count per strip +// +#include + +// Demonstrating the use of the first four channels, but the method used allows for eight +NeoPixelBus strip1(120, 15); // note: older WS2811 and longer strip +NeoPixelBus strip2(100, 2); // note: modern WS2812 with letter like WS2812b +NeoPixelBus strip3(100, 4); // note: inverted +NeoPixelBus strip4(50, 16); // note: RGBW and Sk6812 and smaller strip + +void setup() { + Serial.begin(115200); + while (!Serial); // wait for serial attach + + Serial.println(); + Serial.println("Initializing..."); + Serial.flush(); + + // must call begin on all the strips + strip1.Begin(); + strip2.Begin(); + strip3.Begin(); + strip4.Begin(); + + Serial.println(); + Serial.println("Running..."); +} + +void loop() { + delay(1000); + + // draw on the strips + strip1.SetPixelColor(0, RgbColor(255, 0, 0)); // red + strip2.SetPixelColor(0, RgbColor(0, 127, 0)); // green + strip3.SetPixelColor(0, RgbColor(0, 0, 53)); // blue + strip4.SetPixelColor(0, RgbwColor(0, 0, 128, 255)); // white channel with a little blue + + // show them, + // only on the last show, no matter the order, will the data be sent + strip1.Show(); + strip2.Show(); + strip3.Show(); + strip4.Show(); +} \ No newline at end of file diff --git a/src/NeoPixelBus.h b/src/NeoPixelBus.h index bea9168..dfd1ca6 100644 --- a/src/NeoPixelBus.h +++ b/src/NeoPixelBus.h @@ -97,6 +97,7 @@ License along with NeoPixel. If not, see #include "internal/NeoEsp32RmtMethod.h" #include "internal/NeoEspBitBangMethod.h" #include "internal/DotStarEsp32DmaSpiMethod.h" +#include "internal/NeoEsp32I2sXMethod.h" #elif defined(ARDUINO_ARCH_NRF52840) // must be before __arm__ @@ -195,7 +196,7 @@ public: void Show(bool maintainBufferConsistency = true) { - if (!IsDirty()) + if (!IsDirty() && !_method.AlwaysUpdate()) { return; } diff --git a/src/internal/DotStarEsp32DmaSpiMethod.h b/src/internal/DotStarEsp32DmaSpiMethod.h index 37b2098..de1964f 100644 --- a/src/internal/DotStarEsp32DmaSpiMethod.h +++ b/src/internal/DotStarEsp32DmaSpiMethod.h @@ -177,6 +177,12 @@ public: ESP_ERROR_CHECK(ret); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data + _sizeStartFrame; diff --git a/src/internal/DotStarGenericMethod.h b/src/internal/DotStarGenericMethod.h index 0d2f7c7..cdb3f2c 100644 --- a/src/internal/DotStarGenericMethod.h +++ b/src/internal/DotStarGenericMethod.h @@ -104,6 +104,12 @@ public: _wire.endTransaction(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/Esp32_i2s.c b/src/internal/Esp32_i2s.c index d5e92f2..7e1fbee 100644 --- a/src/internal/Esp32_i2s.c +++ b/src/internal/Esp32_i2s.c @@ -6,7 +6,6 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software @@ -40,6 +39,7 @@ #include "esp_intr.h" #endif +#include "rom/lldesc.h" #include "soc/gpio_reg.h" #include "soc/gpio_sig_map.h" #include "soc/io_mux_reg.h" @@ -60,55 +60,36 @@ #include "Esp32_i2s.h" #include "esp32-hal.h" +esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t div_a, uint8_t bck, uint8_t bits_per_sample); +esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t sample_rate, bool parallel_mode, size_t bytes_per_sample); + +#define MATRIX_DETACH_OUT_SIG 0x100 + #if ESP_IDF_VERSION_MAJOR<=4 #define I2S_BASE_CLK (160000000L) #endif -#define ESP32_REG(addr) (*((volatile uint32_t*)(0x3FF00000+(addr)))) +#define I2S_DMA_BLOCK_COUNT_DEFAULT 0 +// 20 bytes gives us enough time if we use single stage idle +// But it can't be longer due to non-parrallel mode and 50us reset time +// there just isn't enough silence at the end to fill more than 20 bytes +#define I2S_DMA_SILENCE_SIZE 20 // 4 byte increments +#define I2S_DMA_SILENCE_BLOCK_COUNT_FRONT 2 // two front +#define I2S_DMA_SILENCE_BLOCK_COUNT_BACK 1 // one back, required for non parallel -#define I2S_DMA_BLOCK_COUNT_DEFAULT 16 -// 24 bytes gives us enough time if we use single stage idle -// with the two stage idle we can use the minimum of 4 bytes -#define I2S_DMA_SILENCE_SIZE 4*1 -#define I2S_DMA_SILENCE_BLOCK_COUNT 3 // two front, one back -#define I2S_DMA_QUEUE_COUNT 2 +typedef struct +{ + i2s_dev_t* bus; + int8_t ws; + int8_t bck; + int8_t out; + int8_t in; -typedef struct i2s_dma_item_s { - uint32_t blocksize: 12; // datalen - uint32_t datalen : 12; // len*(bits_per_sample/8)*2 => max 2047*8bit/1023*16bit samples - uint32_t unused : 5; // 0 - uint32_t sub_sof : 1; // 0 - uint32_t eof : 1; // 1 => last? - uint32_t owner : 1; // 1 + intr_handle_t isr_handle; + lldesc_t* dma_items; + size_t dma_count; - void* data; // malloc(datalen) - struct i2s_dma_item_s* next; - - // if this pointer is not null, it will be freed - void* free_ptr; - - // if DMA buffers are preallocated - uint8_t* buf; -} i2s_dma_item_t; - -typedef struct { - i2s_dev_t* bus; - int8_t ws; - int8_t bck; - int8_t out; - int8_t in; - uint32_t rate; - intr_handle_t isr_handle; - xQueueHandle tx_queue; - - uint8_t* silence_buf; - size_t silence_len; - - i2s_dma_item_t* dma_items; - size_t dma_count; - uint32_t dma_buf_len :12; - uint32_t unused :20; - volatile uint32_t is_sending_data; + volatile uint32_t is_sending_data; } i2s_bus_t; // is_sending_data values @@ -116,103 +97,144 @@ typedef struct { #define I2s_Is_Pending 1 #define I2s_Is_Sending 2 -static uint8_t i2s_silence_buf[I2S_DMA_SILENCE_SIZE] = { 0 }; - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // (I2S_NUM_MAX == 2) -static i2s_bus_t I2S[I2S_NUM_MAX] = { - {&I2S0, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_SIZE, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, 0, 0, I2s_Is_Idle}, - {&I2S1, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_SIZE, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, 0, 0, I2s_Is_Idle} +static i2s_bus_t I2S[I2S_NUM_MAX] = +{ + {&I2S0, -1, -1, -1, -1, NULL, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, I2s_Is_Idle}, + {&I2S1, -1, -1, -1, -1, NULL, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, I2s_Is_Idle} }; #else -static i2s_bus_t I2S[I2S_NUM_MAX] = { - {&I2S0, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_SIZE, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, 0, 0, I2s_Is_Idle} +static i2s_bus_t I2S[I2S_NUM_MAX] = +{ + {&I2S0, -1, -1, -1, -1, NULL, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, I2s_Is_Idle} }; #endif void IRAM_ATTR i2sDmaISR(void* arg); +inline void dmaItemInit(lldesc_t* item, uint8_t* posData, size_t sizeData, lldesc_t* itemNext) +{ + item->eof = 0; + item->owner = 1; + item->sosf = 0; + item->offset = 0; + item->buf = posData; + item->size = sizeData; + item->length = sizeData; + item->qe.stqe_next = itemNext; +} -bool i2sInitDmaItems(uint8_t bus_num) { - if (bus_num >= I2S_NUM_MAX) { +bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize) +{ + if (bus_num >= I2S_NUM_MAX) + { return false; } - if (I2S[bus_num].tx_queue) {// already set - return true; - } size_t dmaCount = I2S[bus_num].dma_count; - if (I2S[bus_num].dma_items == NULL) { - I2S[bus_num].dma_items = (i2s_dma_item_t*)heap_caps_malloc(dmaCount * sizeof(i2s_dma_item_t), MALLOC_CAP_DMA); - if (I2S[bus_num].dma_items == NULL) { + if (I2S[bus_num].dma_items == NULL) + { + I2S[bus_num].dma_items = (lldesc_t*)heap_caps_malloc(dmaCount * sizeof(lldesc_t), MALLOC_CAP_DMA); + if (I2S[bus_num].dma_items == NULL) + { log_e("MEM ERROR!"); return false; } } - int i, i2; - i2s_dma_item_t* item = NULL; - i2s_dma_item_t* itemPrev = NULL; + lldesc_t* itemFirst = &I2S[bus_num].dma_items[0]; + lldesc_t* item = itemFirst; +// lldesc_t* itemsEnd = itemFirst + I2S[bus_num].dma_count; + lldesc_t* itemNext = item + 1; + size_t dataLeft = dataSize; + uint8_t* pos = data; + // at the end of the data is the encoded silence reset + uint8_t* posSilence = data + dataSize - I2S_DMA_SILENCE_SIZE; - for(i=0; i< dmaCount; i++) { - itemPrev = item; + // front two are silent items used for looping to micmic single fire + // default to looping + dmaItemInit(item, posSilence, I2S_DMA_SILENCE_SIZE, itemNext); + dmaItemInit(itemNext, posSilence, I2S_DMA_SILENCE_SIZE, item); + item = itemNext; + itemNext++; - i2 = (i+1) % dmaCount; - item = &I2S[bus_num].dma_items[i]; - item->eof = 0; - item->owner = 1; - item->sub_sof = 0; - item->unused = 0; - item->data = I2S[bus_num].silence_buf; - item->blocksize = I2S[bus_num].silence_len; - item->datalen = I2S[bus_num].silence_len; - item->next = &I2S[bus_num].dma_items[i2]; - item->free_ptr = NULL; - item->buf = NULL; + // init blocks with avialable data + // + while (dataLeft) + { + item = itemNext; + itemNext++; + + size_t blockSize = dataLeft; + if (blockSize > I2S_DMA_MAX_DATA_LEN) + { + blockSize = I2S_DMA_MAX_DATA_LEN; + } + dataLeft -= blockSize; + + dmaItemInit(item, pos, blockSize, itemNext); + + pos += blockSize; } - itemPrev->eof = 1; + + // last data item is EOF to manage send state using EOF ISR item->eof = 1; - I2S[bus_num].tx_queue = xQueueCreate(I2S_DMA_QUEUE_COUNT, sizeof(i2s_dma_item_t*)); - if (I2S[bus_num].tx_queue == NULL) {// memory error - log_e("MEM ERROR!"); - heap_caps_free(I2S[bus_num].dma_items); - I2S[bus_num].dma_items = NULL; - return false; - } + // last block, the back silent item, loops to front + item = itemNext; + dmaItemInit(item, posSilence, I2S_DMA_SILENCE_SIZE, itemFirst); + return true; } -bool i2sDeinitDmaItems(uint8_t bus_num) { - if (bus_num >= I2S_NUM_MAX) { +bool i2sDeinitDmaItems(uint8_t bus_num) +{ + if (bus_num >= I2S_NUM_MAX) + { return false; } - if (!I2S[bus_num].tx_queue) { - return false; // nothing to deinit - } - vQueueDelete(I2S[bus_num].tx_queue); - I2S[bus_num].tx_queue = NULL; heap_caps_free(I2S[bus_num].dma_items); I2S[bus_num].dma_items = NULL; return true; } -esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t div_a, uint8_t bck, uint8_t bits) { - if (bus_num >= I2S_NUM_MAX || div_a > 63 || div_b > 63 || bck > 63) { +// normal 4, 10, 63, 12, 16 + +esp_err_t i2sSetClock(uint8_t bus_num, + uint8_t div_num, // 4 13 + uint8_t div_b, // 10 20 + uint8_t div_a, // 63 63 + uint8_t bck, // 12 60 or 7 + uint8_t bits) // 16 8 +{ + if (bus_num >= I2S_NUM_MAX || div_a > 63 || div_b > 63 || bck > 63) + { return ESP_FAIL; } + + //log_i("i2sSetClock bus %u, clkm_div_num %u, clk_div_a %u, clk_div_b %u, bck_div_num %u, bits_mod %u", + // bus_num, + // div_num, + // div_a, + // div_b, + // bck, + // bits); + i2s_dev_t* i2s = I2S[bus_num].bus; typeof(i2s->clkm_conf) clkm_conf; clkm_conf.val = 0; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - clkm_conf.clka_en = 0; + +#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + clkm_conf.clk_sel = 2; // APPL = 1 APB = 2 + clkm_conf.clk_en = 1; // examples of i2s show this being set if sel is set to 2 #else - clkm_conf.clk_sel = 2; + clkm_conf.clka_en = 0; #endif clkm_conf.clkm_div_a = div_a; @@ -227,50 +249,101 @@ esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t d sample_rate_conf.tx_bits_mod = bits; sample_rate_conf.rx_bits_mod = bits; i2s->sample_rate_conf.val = sample_rate_conf.val; + return ESP_OK; } -void i2sSetPins(uint8_t bus_num, int8_t out, bool invert) +void i2sSetPins(uint8_t bus_num, + int8_t out, + int8_t parallel, + int8_t busSampleSize, + bool invert) { if (bus_num >= I2S_NUM_MAX) { return; } - int8_t outOld = I2S[bus_num].out; - - I2S[bus_num].out = out; - - // disable old pin - if (outOld >= 0) - { - gpio_matrix_out(outOld, 0x100, false, false); - pinMode(outOld, INPUT); - } - if (out >= 0) { + uint32_t i2sSignal; + pinMode(out, OUTPUT); - int i2sSignal; -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) -// (I2S_NUM_MAX == 2) - if (bus_num == 1) - { - i2sSignal = I2S1O_DATA_OUT23_IDX; - } - else -#endif +#if defined(CONFIG_IDF_TARGET_ESP32S2) + + // S2 only has one bus + // + // in parallel mode + // 8bit mode : I2S0O_DATA_OUT16_IDX ~I2S0O_DATA_OUT23_IDX + // 16bit mode : I2S0O_DATA_OUT8_IDX ~I2S0O_DATA_OUT23_IDX + // 24bit mode : I2S0O_DATA_OUT0_IDX ~I2S0O_DATA_OUT23_IDX + if (parallel == -1) { i2sSignal = I2S0O_DATA_OUT23_IDX; } + else if (busSampleSize == 1) + { + i2sSignal = I2S0O_DATA_OUT16_IDX + parallel; + } + else if (busSampleSize == 2) + { + i2sSignal = I2S0O_DATA_OUT8_IDX + parallel; + } + else + { + i2sSignal = I2S0O_DATA_OUT0_IDX + parallel; + } +#else + if (bus_num == 0) + { + // in parallel mode + // 0-7 bits : I2S0O_DATA_OUT16_IDX ~I2S0O_DATA_OUT23_IDX + // 8-15 bits : I2S0O_DATA_OUT8_IDX ~I2S0O_DATA_OUT23_IDX + // 16-23 bits : I2S0O_DATA_OUT0_IDX ~I2S0O_DATA_OUT23_IDX + if (parallel == -1) + { + i2sSignal = I2S0O_DATA_OUT23_IDX; + } + else if (parallel < 8) + { + i2sSignal = I2S0O_DATA_OUT16_IDX + parallel; + } + else if (parallel < 16) + { + i2sSignal = I2S0O_DATA_OUT8_IDX + parallel - 8; + } + else + { + i2sSignal = I2S0O_DATA_OUT0_IDX + parallel - 16; + } + } + else + { + if (parallel == -1) + { + i2sSignal = I2S1O_DATA_OUT23_IDX; + } + else + { + i2sSignal = I2S1O_DATA_OUT0_IDX + parallel; + } + } +#endif + //log_i("i2sSetPins bus %u, i2sSignal %u, pin %u, mux %u", + // bus_num, + // i2sSignal, + // out, + // parallel); gpio_matrix_out(out, i2sSignal, invert, false); } } -bool i2sWriteDone(uint8_t bus_num) { - if (bus_num >= I2S_NUM_MAX) { +bool i2sWriteDone(uint8_t bus_num) +{ + if (bus_num >= I2S_NUM_MAX) + { return false; } @@ -278,28 +351,36 @@ bool i2sWriteDone(uint8_t bus_num) { } void i2sInit(uint8_t bus_num, - uint32_t bits_per_sample, + bool parallel_mode, + size_t bytes_per_sample, uint32_t sample_rate, i2s_tx_chan_mod_t chan_mod, i2s_tx_fifo_mod_t fifo_mod, size_t dma_count, - size_t dma_len) { - if (bus_num >= I2S_NUM_MAX) { + uint8_t* data, + size_t dataSize) +{ + if (bus_num >= I2S_NUM_MAX) + { return; } - I2S[bus_num].dma_count = dma_count + I2S_DMA_SILENCE_BLOCK_COUNT; // an extra two for looping silence - I2S[bus_num].dma_buf_len = dma_len & 0xFFF; + I2S[bus_num].dma_count = dma_count + + I2S_DMA_SILENCE_BLOCK_COUNT_FRONT + + I2S_DMA_SILENCE_BLOCK_COUNT_BACK; - if (!i2sInitDmaItems(bus_num)) { + if (!i2sInitDmaItems(bus_num, data, dataSize)) + { return; } #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // (I2S_NUM_MAX == 2) - if (bus_num) { + if (bus_num) + { periph_module_enable(PERIPH_I2S1_MODULE); - } else + } + else #endif { periph_module_enable(PERIPH_I2S0_MODULE); @@ -313,12 +394,6 @@ void i2sInit(uint8_t bus_num, i2s->int_clr.val = 0xFFFFFFFF; i2s->fifo_conf.dscr_en = 0; - // reset fifo - i2s->conf.rx_fifo_reset = 1; - i2s->conf.rx_fifo_reset = 0; - i2s->conf.tx_fifo_reset = 1; - i2s->conf.tx_fifo_reset = 0; - // reset i2s i2s->conf.tx_reset = 1; i2s->conf.tx_reset = 0; @@ -331,11 +406,29 @@ void i2sInit(uint8_t bus_num, i2s->lc_conf.out_rst = 1; i2s->lc_conf.out_rst = 0; + // reset fifo + i2s->conf.rx_fifo_reset = 1; + i2s->conf.rx_fifo_reset = 0; + i2s->conf.tx_fifo_reset = 1; + i2s->conf.tx_fifo_reset = 0; + + + // set parallel (LCD) mode + { + typeof(i2s->conf2) conf2; + conf2.val = 0; + conf2.lcd_en = parallel_mode; + conf2.lcd_tx_wrx2_en = 0; // parallel_mode; // ((parallel_mode) && (bytes_per_sample == 2)); + i2s->conf2.val = conf2.val; + } + // Enable and configure DMA - typeof(i2s->lc_conf) lc_conf; - lc_conf.val = 0; - lc_conf.out_eof_mode = 1; - i2s->lc_conf.val = lc_conf.val; + { + typeof(i2s->lc_conf) lc_conf; + lc_conf.val = 0; + lc_conf.out_eof_mode = 1; + i2s->lc_conf.val = lc_conf.val; + } #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) i2s->pdm_conf.pcm2pdm_conv_en = 0; @@ -343,37 +436,62 @@ void i2sInit(uint8_t bus_num, #endif // SET_PERI_REG_BITS(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, 0x1, RTC_CNTL_SOC_CLK_SEL_S); - typeof(i2s->conf_chan) conf_chan; - conf_chan.val = 0; - conf_chan.tx_chan_mod = chan_mod; // 0-two channel;1-right;2-left;3-righ;4-left - conf_chan.rx_chan_mod = chan_mod; // 0-two channel;1-right;2-left;3-righ;4-left - i2s->conf_chan.val = conf_chan.val; + { + typeof(i2s->fifo_conf) fifo_conf; - typeof(i2s->fifo_conf) fifo_conf; - fifo_conf.val = 0; - fifo_conf.tx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel - fifo_conf.rx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel - i2s->fifo_conf.val = fifo_conf.val; + fifo_conf.val = 0; + fifo_conf.rx_fifo_mod_force_en = 1; + fifo_conf.tx_fifo_mod_force_en = 1; + fifo_conf.tx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel + fifo_conf.rx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel + fifo_conf.rx_data_num = 32; //Thresholds. + fifo_conf.tx_data_num = 32; - typeof(i2s->conf) conf; - conf.val = 0; - conf.tx_msb_shift = (bits_per_sample != 8);// 0:DAC/PCM, 1:I2S - conf.tx_right_first = (bits_per_sample == 8); - i2s->conf.val = conf.val; + i2s->fifo_conf.val = fifo_conf.val; + } - typeof(i2s->conf2) conf2; - conf2.val = 0; - conf2.lcd_en = (bits_per_sample == 8); - i2s->conf2.val = conf2.val; + // $REVIEW old code didn't set this + { + typeof(i2s->conf1) conf1; + conf1.val = 0; + conf1.tx_stop_en = 0; + conf1.tx_pcm_bypass = 1; + i2s->conf1.val = conf1.val; + } - i2s->fifo_conf.tx_fifo_mod_force_en = 1; + { + typeof(i2s->conf_chan) conf_chan; + conf_chan.val = 0; + conf_chan.tx_chan_mod = chan_mod; // 0-two channel;1-right;2-left;3-righ;4-left + conf_chan.rx_chan_mod = chan_mod; // 0-two channel;1-right;2-left;3-righ;4-left + i2s->conf_chan.val = conf_chan.val; + } + + { + typeof(i2s->conf) conf; + conf.val = 0; + conf.tx_msb_shift = !parallel_mode; // 0:DAC/PCM, 1:I2S + conf.tx_right_first = 0; // parallel_mode?; + i2s->conf.val = conf.val; + } + + i2s->timing.val = 0; #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) i2s->pdm_conf.rx_pdm_en = 0; i2s->pdm_conf.tx_pdm_en = 0; #endif + + + i2sSetSampleRate(bus_num, sample_rate, parallel_mode, bytes_per_sample); - i2sSetSampleRate(bus_num, sample_rate, bits_per_sample); + /* */ + //Reset FIFO/DMA -> needed? Doesn't dma_reset/fifo_reset do this? + i2s->lc_conf.in_rst=1; i2s->lc_conf.out_rst=1; i2s->lc_conf.ahbm_rst=1; i2s->lc_conf.ahbm_fifo_rst=1; + i2s->lc_conf.in_rst=0; i2s->lc_conf.out_rst=0; i2s->lc_conf.ahbm_rst=0; i2s->lc_conf.ahbm_fifo_rst=0; + i2s->conf.tx_reset=1; i2s->conf.tx_fifo_reset=1; i2s->conf.rx_fifo_reset=1; + i2s->conf.tx_reset=0; i2s->conf.tx_fifo_reset=0; i2s->conf.rx_fifo_reset=0; + /* */ // enable intr in cpu // int i2sIntSource; @@ -389,12 +507,21 @@ void i2sInit(uint8_t bus_num, i2sIntSource = ETS_I2S0_INTR_SOURCE; } - esp_intr_alloc(i2sIntSource, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1, &i2sDmaISR, &I2S[bus_num], &I2S[bus_num].isr_handle); // enable send intr i2s->int_ena.out_eof = 1; i2s->int_ena.out_dscr_err = 1; +/* ??? */ + // Enable and configure DMA + { + typeof(i2s->lc_conf) lc_conf; + lc_conf.val = 0; + lc_conf.out_data_burst_en = 1; + lc_conf.indscr_burst_en = 1; + i2s->lc_conf.val = lc_conf.val; + } +/* */ i2s->fifo_conf.dscr_en = 1;// enable dma i2s->out_link.start = 0; i2s->out_link.addr = (uint32_t)(&I2S[bus_num].dma_items[0]); // loads dma_struct to dma @@ -404,126 +531,103 @@ void i2sInit(uint8_t bus_num, esp_intr_enable(I2S[bus_num].isr_handle); } -void i2sDeinit(uint8_t bus_num) { +void i2sDeinit(uint8_t bus_num) +{ i2sDeinitDmaItems(bus_num); } -esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, uint8_t bits) { - if (bus_num >= I2S_NUM_MAX) { +esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, bool parallel_mode, size_t bytes_per_sample) +{ + if (bus_num >= I2S_NUM_MAX) + { return ESP_FAIL; } - if (I2S[bus_num].rate == rate) { - return ESP_OK; + uint8_t bck = 12; + + // parallel mode needs a higher sample rate + // + if (parallel_mode) + { +#if defined(CONFIG_IDF_TARGET_ESP32S2) + rate *= bytes_per_sample; + bck *= bytes_per_sample; + + //rate /= bytes_per_sample; + //bck /= bytes_per_sample; +#else + rate *= bytes_per_sample; +#endif } - int clkmInteger, clkmDecimals, bck = 0; - double denom = (double)1 / 63; - int channel = 2; - -// double mclk; - double clkmdiv; - - int factor; - - if (bits == 8) { - factor = 120; - } else { - factor = (256 % bits) ? 384 : 256; - } - - clkmdiv = (double)I2S_BASE_CLK / (rate* factor); - if (clkmdiv > 256) { + // 160,000,000L / (100,000 * 384) + double clkmdiv = (double)I2S_BASE_CLK / ((rate * 384) + 1); + if (clkmdiv > 256.0) + { log_e("rate is too low"); return ESP_FAIL; - } - I2S[bus_num].rate = rate; - - clkmInteger = clkmdiv; - clkmDecimals = ((clkmdiv - clkmInteger) / denom); - - if (bits == 8) { -// mclk = rate* factor; - bck = 60; - bits = 16; - } else { -// mclk = (double)clkmInteger + (denom* clkmDecimals); - bck = factor/(bits* channel); + } + else if (clkmdiv < 2.0) + { + log_e("rate is too fast, clkmdiv = %f (%u, %u, %u)", + clkmdiv, + rate, + parallel_mode, + bytes_per_sample); + return ESP_FAIL; } - i2sSetClock(bus_num, clkmInteger, clkmDecimals, 63, bck, bits); + // calc integer and franctional for more precise timing + // + uint8_t clkmInteger = clkmdiv; + uint8_t clkmFraction = (clkmdiv - clkmInteger) * 63.0; + + i2sSetClock(bus_num, clkmInteger, clkmFraction, 63, bck, bytes_per_sample * 8); return ESP_OK; } - - void IRAM_ATTR i2sDmaISR(void* arg) { - i2s_bus_t* dev = (i2s_bus_t*)(arg); + i2s_bus_t* i2s = (i2s_bus_t*)(arg); - if (dev->bus->int_st.out_eof) + if (i2s->bus->int_st.out_eof) { - // i2s_dma_item_t* item = (i2s_dma_item_t*)(dev->bus->out_eof_des_addr); - if (dev->is_sending_data == I2s_Is_Pending) + // lldesc_t* item = (lldesc_t*)(i2s->bus->out_eof_des_addr); + if (i2s->is_sending_data != I2s_Is_Idle) { - dev->is_sending_data = I2s_Is_Idle; - } - else if (dev->is_sending_data == I2s_Is_Sending) - { - // loop the silent items - i2s_dma_item_t* itemSilence = &dev->dma_items[1]; - itemSilence->next = &dev->dma_items[0]; + // the second item (last of the two front silent items) is + // silent looping item + lldesc_t* itemLoop = &i2s->dma_items[0]; + lldesc_t* itemLoopBreaker = itemLoop + 1; + // set to loop on silent items + itemLoopBreaker->qe.stqe_next = itemLoop; - dev->is_sending_data = I2s_Is_Pending; + i2s->is_sending_data = I2s_Is_Idle; } } - dev->bus->int_clr.val = dev->bus->int_st.val; + i2s->bus->int_clr.val = i2s->bus->int_st.val; } -size_t i2sWrite(uint8_t bus_num, uint8_t* data, size_t len, bool copy, bool free_when_sent) { - if (bus_num >= I2S_NUM_MAX || !I2S[bus_num].tx_queue) { - return 0; - } - size_t blockSize = len; - - i2s_dma_item_t* item = &I2S[bus_num].dma_items[0]; - size_t dataLeft = len; - uint8_t* pos = data; - - // skip front two silent items - item += 2; - - while (dataLeft) { - - blockSize = dataLeft; - if (blockSize > I2S_DMA_MAX_DATA_LEN) { - blockSize = I2S_DMA_MAX_DATA_LEN; - } - dataLeft -= blockSize; - - // data is constant. no need to copy - item->data = pos; - item->blocksize = blockSize; - item->datalen = blockSize; - - item++; - - pos += blockSize; +bool i2sWrite(uint8_t bus_num) +{ + if (bus_num >= I2S_NUM_MAX) + { + return false; } + // the second item (last of the two front silent items) is + // silent looping item + lldesc_t* itemLoopBreaker = &I2S[bus_num].dma_items[1]; + lldesc_t* itemLoopNext = itemLoopBreaker + 1; + + // set to NOT loop on silent items + itemLoopBreaker->qe.stqe_next = itemLoopNext; - // reset silence item to not loop - item = &I2S[bus_num].dma_items[1]; - item->next = &I2S[bus_num].dma_items[2]; I2S[bus_num].is_sending_data = I2s_Is_Sending; - - xQueueReset(I2S[bus_num].tx_queue); - xQueueSend(I2S[bus_num].tx_queue, (void*)&I2S[bus_num].dma_items[0], 10); - - return len; + return true; } #endif // !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) diff --git a/src/internal/Esp32_i2s.h b/src/internal/Esp32_i2s.h index 498813f..0c47047 100644 --- a/src/internal/Esp32_i2s.h +++ b/src/internal/Esp32_i2s.h @@ -20,21 +20,21 @@ typedef enum { } i2s_tx_fifo_mod_t; void i2sInit(uint8_t bus_num, - uint32_t bits_per_sample, + bool parallel_mode, + size_t bytes_per_sample, uint32_t sample_rate, i2s_tx_chan_mod_t chan_mod, i2s_tx_fifo_mod_t fifo_mod, size_t dma_count, - size_t dma_len); - + uint8_t* data, + size_t dataSize); void i2sDeinit(uint8_t bus_num); - -void i2sSetPins(uint8_t bus_num, int8_t out, bool invert); - -esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t div_a, uint8_t bck, uint8_t bits_per_sample); -esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t sample_rate, uint8_t bits_per_sample); - -size_t i2sWrite(uint8_t bus_num, uint8_t* data, size_t len, bool copy, bool free_when_sent); +void i2sSetPins(uint8_t bus_num, + int8_t out, + int8_t parallel, + int8_t busSampleSize, + bool invert); +bool i2sWrite(uint8_t bus_num); bool i2sWriteDone(uint8_t bus_num); #ifdef __cplusplus diff --git a/src/internal/Lpd6803GenericMethod.h b/src/internal/Lpd6803GenericMethod.h index 778a4ef..d3f0408 100644 --- a/src/internal/Lpd6803GenericMethod.h +++ b/src/internal/Lpd6803GenericMethod.h @@ -100,6 +100,12 @@ public: _wire.endTransaction(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/Lpd8806GenericMethod.h b/src/internal/Lpd8806GenericMethod.h index 074e8bc..c4afa61 100644 --- a/src/internal/Lpd8806GenericMethod.h +++ b/src/internal/Lpd8806GenericMethod.h @@ -100,6 +100,12 @@ public: _wire.endTransaction(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/NeoArmMethod.h b/src/internal/NeoArmMethod.h index 35c4953..c68324c 100644 --- a/src/internal/NeoArmMethod.h +++ b/src/internal/NeoArmMethod.h @@ -91,6 +91,12 @@ public: _endTime = micros(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/NeoAvrMethod.h b/src/internal/NeoAvrMethod.h index 8f0c2cb..c58249e 100644 --- a/src/internal/NeoAvrMethod.h +++ b/src/internal/NeoAvrMethod.h @@ -187,6 +187,12 @@ public: _endTime = micros(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/NeoEsp32I2sMethod.h b/src/internal/NeoEsp32I2sMethod.h index c24be66..291b25a 100644 --- a/src/internal/NeoEsp32I2sMethod.h +++ b/src/internal/NeoEsp32I2sMethod.h @@ -170,7 +170,7 @@ public: yield(); } - i2sSetPins(_bus.I2sBusNumber, -1, false); + i2sSetPins(_bus.I2sBusNumber, -1, -1, -1, false); i2sDeinit(_bus.I2sBusNumber); free(_data); heap_caps_free(_i2sBuffer); @@ -186,13 +186,15 @@ public: size_t dmaBlockCount = (_i2sBufferSize + I2S_DMA_MAX_DATA_LEN - 1) / I2S_DMA_MAX_DATA_LEN; i2sInit(_bus.I2sBusNumber, - 16, + false, + 2, // bytes per sample T_SPEED::I2sSampleRate, I2S_CHAN_STEREO, I2S_FIFO_16BIT_DUAL, dmaBlockCount, - 0); - i2sSetPins(_bus.I2sBusNumber, _pin, T_INVERT::Inverted); + _i2sBuffer, + _i2sBufferSize); + i2sSetPins(_bus.I2sBusNumber, _pin, -1, -1, T_INVERT::Inverted); } void Update(bool) @@ -205,7 +207,13 @@ public: FillBuffers(); - i2sWrite(_bus.I2sBusNumber, _i2sBuffer, _i2sBufferSize, false, false); + i2sWrite(_bus.I2sBusNumber); + } + + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; } uint8_t* getData() const @@ -229,7 +237,7 @@ private: uint8_t* _data; // Holds LED color values - uint32_t _i2sBufferSize; // total size of _i2sBuffer + size_t _i2sBufferSize; // total size of _i2sBuffer uint8_t* _i2sBuffer; // holds the DMA buffer that is referenced by _i2sBufDesc void construct(uint16_t pixelCount, size_t elementSize, size_t settingsSize) diff --git a/src/internal/NeoEsp32I2sXMethod.h b/src/internal/NeoEsp32I2sXMethod.h new file mode 100644 index 0000000..32af9a0 --- /dev/null +++ b/src/internal/NeoEsp32I2sXMethod.h @@ -0,0 +1,796 @@ +#pragma once + +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp32. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +// ESP32C3/S3 I2S is not supported yet due to significant changes to interface +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + +extern "C" +{ +#include +#include "Esp32_i2s.h" +} + +#pragma once + +// ESP32 Endian Map +// uint16_t +// 1234 +// 3412 +// uint32_t +// 12345678 +// 78563412 +// uint64_t +// 0123456789abcdef +// efcdab8967452301 + +// +// true size of mux channel, 8 bit +// +class NeoEspI2sMuxBusSize8Bit +{ +public: + NeoEspI2sMuxBusSize8Bit() {}; + + const static size_t MuxBusDataSize = 1; + + static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId) + { +#if defined(CONFIG_IDF_TARGET_ESP32S2) + // 1234 - order + // 3412 = actual due to endianness + // 00000001 + const uint32_t EncodedZeroBit = 0x00000100; + // 00010101 + const uint32_t EncodedOneBit = 0x01000101; +#else + // 8 channel bits layout for DMA 32bit value + // note, right to left + // mux bus bit/id 76543210 76543210 76543210 76543210 + // encode bit # 3 2 1 0 + // value zero 0 0 0 1 + // value one 0 1 1 1 + // + // due to indianness between peripheral and cpu, bytes within the words are swapped in the const + // 1234 - order + // 3412 = actual due to endianness + // 00000001 + const uint32_t EncodedZeroBit = 0x00010000; + // 00010101 + const uint32_t EncodedOneBit = 0x01010001; +#endif + + uint32_t* pDma = reinterpret_cast(dmaBuffer); + const uint8_t* pEnd = data + sizeData; + + for (const uint8_t* pPixel = data; pPixel < pEnd; pPixel++) + { + uint8_t value = *pPixel; + + for (uint8_t bit = 0; bit < 8; bit++) + { + uint32_t dma = *(pDma); + + dma |= (((value & 0x80) ? EncodedOneBit : EncodedZeroBit) << (muxId)); + *(pDma++) = dma; + value <<= 1; + } + } + } +}; + +// +// true size of mux channel, 16 bit +// +class NeoEspI2sMuxBusSize16Bit +{ +public: + NeoEspI2sMuxBusSize16Bit() {}; + + const static size_t MuxBusDataSize = 2; + + static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId) + { +#if defined(CONFIG_IDF_TARGET_ESP32S2) + // 1234 5678 - order + // 3412 7856 = actual due to endianness + // not swap 0000000000000001 + const uint64_t EncodedZeroBit64 = 0x0000000000010000; + // no swap 0000000100010001 + const uint64_t EncodedOneBit64 = 0x0001000000010001; + // can be shifted by 8! + Fillx16(dmaBuffer, + data, + sizeData, + muxId, + EncodedZeroBit64, + EncodedOneBit64); +#else + + // 16 channel bits layout for DMA 64bit value + // note, right to left, destination is 32bit chunks + // due to indianness between peripheral and cpu, + // bytes within the words are swapped and words within dwords + // in the literal constants + // { } { } + // 0123 4567 89ab cdef - order of bytes in literal constant + // efcd ab89 6745 2301 - order of memory on ESP32 due to Endianness + // 6745 2301 efcd ab89 - 32bit dest means only map using 32bits so swap upper and lower + // + // Due to final bit locations, can't shift encoded one bit + // either left more than 7 or right more than 7 so we have to + // split the updates and use different encodings + if (muxId < 8) + { + // endian + dest swap 0000000000000001 + const uint64_t EncodedZeroBit64 = 0x0000000001000000; + // endian + dest swap 0000000100010001 + const uint64_t EncodedOneBit64 = 0x0100000001000100; + // cant be shifted by 8! + Fillx16(dmaBuffer, + data, + sizeData, + muxId, + EncodedZeroBit64, + EncodedOneBit64); + } + else + { + // endian + dest swap 0000000000000001 + // then pre shift by 8 0000000000000100 + const uint64_t EncodedZeroBit64 = 0x0000000000010000; + // endian + dest swap 0000000100010001 + // then pre shift by 8 0000010001000100 + const uint64_t EncodedOneBit64 = 0x0001000000010001; + Fillx16(dmaBuffer, + data, + sizeData, + muxId - 8, // preshifted + EncodedZeroBit64, + EncodedOneBit64); + } +#endif + } + +protected: + static void Fillx16(uint8_t* dmaBuffer, + const uint8_t* data, + size_t sizeData, + uint8_t muxShift, + const uint64_t EncodedZeroBit64, + const uint64_t EncodedOneBit64) + { + uint64_t* pDma64 = reinterpret_cast(dmaBuffer); + const uint8_t* pEnd = data + sizeData; + + for (const uint8_t* pPixel = data; pPixel < pEnd; pPixel++) + { + uint8_t value = *pPixel; + + for (uint8_t bit = 0; bit < 8; bit++) + { + uint64_t dma64 = *(pDma64); + + dma64 |= (((value & 0x80) ? EncodedOneBit64 : EncodedZeroBit64) << (muxShift)); + *(pDma64++) = dma64; + value <<= 1; + } + } + } +}; + +// +// tracks mux channels used and if updated +// +// T_FLAG - type used to store bit flags, UINT8_t for 8 channels, UINT16_t for 16 +// T_MUXSIZE - true size of mux channel = NeoEspI2sMuxBusSize8Bit or NeoEspI2sMuxBusSize16Bit +// +template class NeoEspI2sMuxMap : public T_MUXSIZE +{ +public: + const static uint8_t InvalidMuxId = -1; + const static size_t BusMaxCount = sizeof(T_FLAG) * 8; + + size_t MaxBusDataSize; // max size of stream data from any single mux bus + T_FLAG UpdateMap; // bitmap flags of mux buses to track update state + T_FLAG UpdateMapMask; // mask to used bits in s_UpdateMap + T_FLAG BusCount; // count of mux buses + + // as a static instance, all members get initialized to zero + // and the constructor is called at inconsistent time to other globals + // so its not useful to have or rely on, + // but without it presence they get zeroed far too late + NeoEspI2sMuxMap() + // //: + // //MaxBusDataSize(0), + // //UpdateMap(0), + // //UpdateMapMask(0), + // //BusCount(0) + { + } + + uint8_t RegisterNewMuxBus(const size_t dataSize) + { + // find first available bus id + uint8_t muxId = 0; + while (muxId < BusMaxCount) + { + T_FLAG muxIdField = (1 << muxId); + if ((UpdateMapMask & muxIdField) == 0) + { + // complete registration + BusCount++; + UpdateMapMask |= muxIdField; + if (dataSize > MaxBusDataSize) + { + MaxBusDataSize = dataSize; + } + break; + } + muxId++; + } + if (muxId == BusMaxCount) + { + log_e("exceded channel limit of %u on bus", BusMaxCount); + } + return muxId; + } + + + bool DeregisterMuxBus(uint8_t muxId) + { + T_FLAG muxIdField = (1 << muxId); + if (UpdateMapMask & muxIdField) + { + // complete deregistration + BusCount--; + UpdateMapMask &= ~muxIdField; + if (UpdateMapMask == 0) + { + return true; + } + } + return false; + } + + bool IsAllMuxBusesUpdated() + { + return (UpdateMap == UpdateMapMask); + } + + bool IsNoMuxBusesUpdate() + { + return (UpdateMap == 0); + } + + void MarkMuxBusUpdated(uint8_t muxId) + { + UpdateMap |= (1 << muxId); + } + + void ResetMuxBusesUpdated() + { + UpdateMap = 0; + } + + void Reset() + { + MaxBusDataSize = 0; + UpdateMap = 0; + UpdateMapMask = 0; + BusCount = 0; + } +}; + +// +// Implementation of a Double Buffered version of a I2sContext +// Manages the underlying I2S details including the buffer(s) +// This creates a front buffer that can be filled while actively sending +// the back buffer, thus improving async operation of the i2s DMA. +// Note that the back buffer must be DMA memory, a limited resource, so +// the front buffer uses normal memory and copies rather than swap pointers +// +// T_MUXMAP - NeoEspI2sMuxMap - tracking class for mux state +// +template class NeoEspI2sDblBuffContext +{ +public: + const static size_t DmaBitsPerPixelBit = 4; + + size_t I2sBufferSize; // total size of I2sBuffer + uint8_t* I2sBuffer; // holds the DMA buffer that is referenced by I2sBufDesc + uint8_t* I2sEditBuffer; // hold a editable buffer that is copied to I2sBuffer + T_MUXMAP MuxMap; + + // as a static instance, all members get initialized to zero + // and the constructor is called at inconsistent time to other globals + // so its not useful to have or rely on, + // but without it presence they get zeroed far too late + NeoEspI2sDblBuffContext() + // //: + // //I2sBufferSize(0), + // //I2sBuffer(nullptr), + // //I2sEditBuffer(nullptr), + // //MuxMap() + { + } + + void Construct(const uint8_t busNumber, uint32_t i2sSampleRate) + { + // construct only once on first time called + if (I2sBuffer == nullptr) + { + // MuxMap.MaxBusDataSize = max size in bytes of a single channel + // DmaBitsPerPixelBit = how many dma bits/byte are needed for each source (pixel) bit/byte + // T_MUXMAP::MuxBusDataSize = the true size of data for selected mux mode (not exposed size as i2s0 only supports 16bit mode) + I2sBufferSize = MuxMap.MaxBusDataSize * 8 * DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize; + + // must have a 4 byte aligned buffer for i2s + uint32_t alignment = I2sBufferSize % 4; + if (alignment) + { + I2sBufferSize += 4 - alignment; + } + + size_t dmaBlockCount = (I2sBufferSize + I2S_DMA_MAX_DATA_LEN - 1) / I2S_DMA_MAX_DATA_LEN; + + I2sBuffer = static_cast(heap_caps_malloc(I2sBufferSize, MALLOC_CAP_DMA)); + if (I2sBuffer == nullptr) + { + log_e("send buffer memory allocation failure (size %u)", + I2sBufferSize); + } + memset(I2sBuffer, 0x00, I2sBufferSize); + + I2sEditBuffer = static_cast(malloc(I2sBufferSize)); + if (I2sEditBuffer == nullptr) + { + log_e("edit buffer memory allocation failure (size %u)", + I2sBufferSize); + } + memset(I2sEditBuffer, 0x00, I2sBufferSize); + + i2sInit(busNumber, + true, + T_MUXMAP::MuxBusDataSize, + i2sSampleRate, +#if defined(CONFIG_IDF_TARGET_ESP32S2) +// using these modes on ESP32S2 actually allows it to function +// in both x8 and x16 + I2S_CHAN_STEREO, + I2S_FIFO_16BIT_DUAL, +#else +// but they won't work on ESP32 in parallel mode, but these will + I2S_CHAN_RIGHT_TO_LEFT, + I2S_FIFO_16BIT_SINGLE, +#endif + dmaBlockCount, + I2sBuffer, + I2sBufferSize); + } + } + + void Destruct(const uint8_t busNumber) + { + if (I2sBuffer == nullptr) + { + return; + } + + i2sSetPins(busNumber, -1, -1, -1, false); + i2sDeinit(busNumber); + + free(I2sEditBuffer); + heap_caps_free(I2sBuffer); + + I2sBufferSize = 0; + I2sBuffer = nullptr; + I2sEditBuffer = nullptr; + + MuxMap.Reset(); + } +}; + +// +// Implementation of the low level interface into i2s mux bus +// +// T_BUSCONTEXT - the context to use, currently only NeoEspI2sDblBuffContext but there is +// a plan to provide one that doesn't implement the front buffer but would be less +// async as it would have to wait until the last frame was completely sent before +// updating and new data +// T_BUS - the bus id, NeoEsp32I2sBusZero, NeoEsp32I2sBusOne +// +template class NeoEsp32I2sMuxBus +{ +public: + NeoEsp32I2sMuxBus() : + _muxId(s_context.MuxMap.InvalidMuxId) + { + } + + void RegisterNewMuxBus(size_t dataSize) + { + _muxId = s_context.MuxMap.RegisterNewMuxBus(dataSize); + } + + void Initialize(uint8_t pin, uint32_t i2sSampleRate, bool invert) + { + s_context.Construct(T_BUS::I2sBusNumber, i2sSampleRate); + i2sSetPins(T_BUS::I2sBusNumber, pin, _muxId, s_context.MuxMap.MuxBusDataSize, invert); + } + + void DeregisterMuxBus() + { + if (s_context.MuxMap.DeregisterMuxBus(_muxId)) + { + s_context.Destruct(T_BUS::I2sBusNumber); + } + // disconnect muxed pin? + _muxId = s_context.MuxMap.InvalidMuxId; + } + + void StartWrite() + { + if (s_context.MuxMap.IsAllMuxBusesUpdated()) + { + s_context.MuxMap.ResetMuxBusesUpdated(); + + // wait for not actively sending data + while (!IsWriteDone()) + { + yield(); + } + // copy edit buffer to sending buffer + memcpy(s_context.I2sBuffer, s_context.I2sEditBuffer, s_context.I2sBufferSize); + i2sWrite(T_BUS::I2sBusNumber); + } + } + + bool IsWriteDone() + { + return i2sWriteDone(T_BUS::I2sBusNumber); + } + + void FillBuffers(const uint8_t* data, size_t sizeData) + { + if (s_context.MuxMap.IsNoMuxBusesUpdate()) + { + // clear all the data in preperation for each mux channel to add + memset(s_context.I2sEditBuffer, 0x00, s_context.I2sBufferSize); + } + + s_context.MuxMap.EncodeIntoDma(s_context.I2sEditBuffer, + data, + sizeData, + _muxId ); + + s_context.MuxMap.MarkMuxBusUpdated(_muxId); + } + + void MarkUpdated() + { + s_context.MuxMap.MarkMuxBusUpdated(_muxId); + } + +private: + static T_BUSCONTEXT s_context; + uint8_t _muxId; +}; + +template T_BUSCONTEXT NeoEsp32I2sMuxBus::s_context = T_BUSCONTEXT(); + +// +// wrapping layer of the i2s mux bus as a NeoMethod +// +// T_SPEED - NeoEsp32I2sSpeed* (ex NeoEsp32I2sSpeedWs2812x) used to define output signal form +// T_BUS - NeoEsp32I2sMuxBus, the bus to use +// T_INVERT - NeoEsp32I2sNotInverted or NeoEsp32I2sInverted, will invert output signal +// +template class NeoEsp32I2sXMethodBase +{ +public: + typedef NeoNoSettings SettingsObject; + + NeoEsp32I2sXMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _sizeData(pixelCount * elementSize + settingsSize), + _pin(pin), + _bus() + { + _bus.RegisterNewMuxBus(_sizeData + T_SPEED::ResetTimeUs / T_SPEED::ByteSendTimeUs); + } + + ~NeoEsp32I2sXMethodBase() + { + while (!_bus.IsWriteDone()) + { + yield(); + } + + _bus.DeregisterMuxBus(); + + free(_data); + } + + bool IsReadyToUpdate() const + { + return _bus.IsWriteDone(); + } + + void Initialize() + { + _bus.Initialize(_pin, T_SPEED::I2sSampleRate, T_INVERT::Inverted); + + _data = static_cast(malloc(_sizeData)); + if (_data == nullptr) + { + log_e("front buffer memory allocation failure"); + } + // data cleared later in Begin() + } + + void Update(bool) + { + _bus.FillBuffers(_data, _sizeData); + _bus.StartWrite(); // only triggers actual write after all mux busses have updated + } + + bool AlwaysUpdate() + { + // this method requires update to be called even if no changes to method buffer + // as edit buffer is always cleared and then copied to send buffer and all + // mux bus needs to included + return true; + } + + uint8_t* getData() const + { + return _data; + }; + + size_t getDataSize() const + { + return _sizeData; + } + + void applySettings([[maybe_unused]] const SettingsObject& settings) + { + } + +private: + const size_t _sizeData; // Size of '_data' buffer + const uint8_t _pin; // output pin number + + T_BUS _bus; // holds instance for mux bus support + uint8_t* _data; // Holds LED color values +}; + +#if defined(CONFIG_IDF_TARGET_ESP32S2) + +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; + +#else + +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; + + +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux8Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux16Bus; + +#endif + +// NORMAL +// + +// I2s0x8 +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Ws2812xMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Sk6812Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Tm1814Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Tm1829Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Tm1914Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8800KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8400KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Apa106Method; + +typedef NeoEsp32I2s0X8Ws2812xMethod NeoEsp32I2s0X8Ws2813Method; +typedef NeoEsp32I2s0X8Ws2812xMethod NeoEsp32I2s0X8Ws2812dMethod; +typedef NeoEsp32I2s0X8Ws2812xMethod NeoEsp32I2s0X8Ws2811Method; +typedef NeoEsp32I2s0X8Ws2812xMethod NeoEsp32I2s0X8Ws2816Method; +typedef NeoEsp32I2s0X8800KbpsMethod NeoEsp32I2s0X8Ws2812Method; +typedef NeoEsp32I2s0X8Sk6812Method NeoEsp32I2s0X8Sk6812Method; +typedef NeoEsp32I2s0X8Tm1814Method NeoEsp32I2s0X8Tm1814Method; +typedef NeoEsp32I2s0X8Tm1829Method NeoEsp32I2s0X8Tm1829Method; +typedef NeoEsp32I2s0X8Tm1914Method NeoEsp32I2s0X8Tm1914Method; +typedef NeoEsp32I2s0X8Sk6812Method NeoEsp32I2s0X8Lc8812Method; +typedef NeoEsp32I2s0X8Apa106Method NeoEsp32I2s0X8Apa106Method; + +// I2s0x16 +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Ws2812xMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Sk6812Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Tm1814Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Tm1829Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Tm1914Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16800KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16400KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Apa106Method; + +typedef NeoEsp32I2s0X16Ws2812xMethod NeoEsp32I2s0X16Ws2813Method; +typedef NeoEsp32I2s0X16Ws2812xMethod NeoEsp32I2s0X16Ws2812dMethod; +typedef NeoEsp32I2s0X16Ws2812xMethod NeoEsp32I2s0X16Ws2811Method; +typedef NeoEsp32I2s0X16Ws2812xMethod NeoEsp32I2s0X16Ws2816Method; +typedef NeoEsp32I2s0X16800KbpsMethod NeoEsp32I2s0X16Ws2812Method; +typedef NeoEsp32I2s0X16Sk6812Method NeoEsp32I2s0X16Sk6812Method; +typedef NeoEsp32I2s0X16Tm1814Method NeoEsp32I2s0X16Tm1814Method; +typedef NeoEsp32I2s0X16Tm1829Method NeoEsp32I2s0X16Tm1829Method; +typedef NeoEsp32I2s0X16Tm1914Method NeoEsp32I2s0X16Tm1914Method; +typedef NeoEsp32I2s0X16Sk6812Method NeoEsp32I2s0X16Lc8812Method; +typedef NeoEsp32I2s0X16Apa106Method NeoEsp32I2s0X16Apa106Method; + +#if !defined(CONFIG_IDF_TARGET_ESP32S2) + +// I2s1x8 +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Ws2812xMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Sk6812Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Tm1814Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Tm1829Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Tm1914Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8800KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8400KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Apa106Method; + +typedef NeoEsp32I2s1X8Ws2812xMethod NeoEsp32I2s1X8Ws2813Method; +typedef NeoEsp32I2s1X8Ws2812xMethod NeoEsp32I2s1X8Ws2812dMethod; +typedef NeoEsp32I2s1X8Ws2812xMethod NeoEsp32I2s1X8Ws2811Method; +typedef NeoEsp32I2s1X8Ws2812xMethod NeoEsp32I2s1X8Ws2816Method; +typedef NeoEsp32I2s1X8800KbpsMethod NeoEsp32I2s1X8Ws2812Method; +typedef NeoEsp32I2s1X8Sk6812Method NeoEsp32I2s1X8Sk6812Method; +typedef NeoEsp32I2s1X8Tm1814Method NeoEsp32I2s1X8Tm1814Method; +typedef NeoEsp32I2s1X8Tm1829Method NeoEsp32I2s1X8Tm1829Method; +typedef NeoEsp32I2s1X8Tm1914Method NeoEsp32I2s1X8Tm1914Method; +typedef NeoEsp32I2s1X8Sk6812Method NeoEsp32I2s1X8Lc8812Method; +typedef NeoEsp32I2s1X8Apa106Method NeoEsp32I2s1X8Apa106Method; + +// I2s1x16 +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Ws2812xMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Sk6812Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Tm1814Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Tm1829Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Tm1914Method; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16800KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16400KbpsMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Apa106Method; + +typedef NeoEsp32I2s1X16Ws2812xMethod NeoEsp32I2s1X16Ws2813Method; +typedef NeoEsp32I2s1X16Ws2812xMethod NeoEsp32I2s1X16Ws2812dMethod; +typedef NeoEsp32I2s1X16Ws2812xMethod NeoEsp32I2s1X16Ws2811Method; +typedef NeoEsp32I2s1X16Ws2812xMethod NeoEsp32I2s1X16Ws2816Method; +typedef NeoEsp32I2s1X16800KbpsMethod NeoEsp32I2s1X16Ws2812Method; +typedef NeoEsp32I2s1X16Sk6812Method NeoEsp32I2s1X16Sk6812Method; +typedef NeoEsp32I2s1X16Tm1814Method NeoEsp32I2s1X16Tm1814Method; +typedef NeoEsp32I2s1X16Tm1829Method NeoEsp32I2s1X16Tm1829Method; +typedef NeoEsp32I2s1X16Tm1914Method NeoEsp32I2s1X16Tm1914Method; +typedef NeoEsp32I2s1X16Sk6812Method NeoEsp32I2s1X16Lc8812Method; +typedef NeoEsp32I2s1X16Apa106Method NeoEsp32I2s1X16Apa106Method; + +#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) + +// INVERTED +// +// I2s0x8 INVERTED +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Ws2812xInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Sk6812InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Tm1814InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Tm1829InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Tm1914InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8800KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8400KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8Apa106InvertedMethod; + +typedef NeoEsp32I2s0X8Ws2812xInvertedMethod NeoEsp32I2s0X8Ws2813InvertedMethod; +typedef NeoEsp32I2s0X8Ws2812xInvertedMethod NeoEsp32I2s0X8Ws2812xInvertedMethod; +typedef NeoEsp32I2s0X8Ws2812xInvertedMethod NeoEsp32I2s0X8Ws2811InvertedMethod; +typedef NeoEsp32I2s0X8Ws2812xInvertedMethod NeoEsp32I2s0X8Ws2816InvertedMethod; +typedef NeoEsp32I2s0X8800KbpsInvertedMethod NeoEsp32I2s0X8Ws2812InvertedMethod; +typedef NeoEsp32I2s0X8Sk6812InvertedMethod NeoEsp32I2s0X8Sk6812InvertedMethod; +typedef NeoEsp32I2s0X8Tm1814InvertedMethod NeoEsp32I2s0X8Tm1814InvertedMethod; +typedef NeoEsp32I2s0X8Tm1829InvertedMethod NeoEsp32I2s0X8Tm1829InvertedMethod; +typedef NeoEsp32I2s0X8Tm1914InvertedMethod NeoEsp32I2s0X8Tm1914InvertedMethod; +typedef NeoEsp32I2s0X8Sk6812InvertedMethod NeoEsp32I2s0X8Lc8812InvertedMethod; +typedef NeoEsp32I2s0X8Apa106InvertedMethod NeoEsp32I2s0X8Apa106InvertedMethod; + +// I2s0x16 INVERTED +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Ws2812xInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Sk6812InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Tm1814InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Tm1829InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Tm1914InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16800KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16400KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X16Apa106InvertedMethod; + +typedef NeoEsp32I2s0X16Ws2812xInvertedMethod NeoEsp32I2s0X16Ws2813InvertedMethod; +typedef NeoEsp32I2s0X16Ws2812xInvertedMethod NeoEsp32I2s0X16Ws2812xInvertedMethod; +typedef NeoEsp32I2s0X16Ws2812xInvertedMethod NeoEsp32I2s0X16Ws2811InvertedMethod; +typedef NeoEsp32I2s0X16Ws2812xInvertedMethod NeoEsp32I2s0X16Ws2816InvertedMethod; +typedef NeoEsp32I2s0X16800KbpsInvertedMethod NeoEsp32I2s0X16Ws2812InvertedMethod; +typedef NeoEsp32I2s0X16Sk6812InvertedMethod NeoEsp32I2s0X16Sk6812InvertedMethod; +typedef NeoEsp32I2s0X16Tm1814InvertedMethod NeoEsp32I2s0X16Tm1814InvertedMethod; +typedef NeoEsp32I2s0X16Tm1829InvertedMethod NeoEsp32I2s0X16Tm1829InvertedMethod; +typedef NeoEsp32I2s0X16Tm1914InvertedMethod NeoEsp32I2s0X16Tm1914InvertedMethod; +typedef NeoEsp32I2s0X16Sk6812InvertedMethod NeoEsp32I2s0X16Lc8812InvertedMethod; +typedef NeoEsp32I2s0X16Apa106InvertedMethod NeoEsp32I2s0X16Apa106InvertedMethod; + +#if !defined(CONFIG_IDF_TARGET_ESP32S2) + +// I2s1x8 INVERTED +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Ws2812xInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Sk6812InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Tm1814InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Tm1829InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Tm1914InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8800KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8400KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Apa106InvertedMethod; + +typedef NeoEsp32I2s1X8Ws2812xInvertedMethod NeoEsp32I2s1X8Ws2813InvertedMethod; +typedef NeoEsp32I2s1X8Ws2812xInvertedMethod NeoEsp32I2s1X8Ws2812xInvertedMethod; +typedef NeoEsp32I2s1X8Ws2812xInvertedMethod NeoEsp32I2s1X8Ws2811InvertedMethod; +typedef NeoEsp32I2s1X8Ws2812xInvertedMethod NeoEsp32I2s1X8Ws2816InvertedMethod; +typedef NeoEsp32I2s1X8800KbpsInvertedMethod NeoEsp32I2s1X8Ws2812InvertedMethod; +typedef NeoEsp32I2s1X8Sk6812InvertedMethod NeoEsp32I2s1X8Sk6812InvertedMethod; +typedef NeoEsp32I2s1X8Tm1814InvertedMethod NeoEsp32I2s1X8Tm1814InvertedMethod; +typedef NeoEsp32I2s1X8Tm1829InvertedMethod NeoEsp32I2s1X8Tm1829InvertedMethod; +typedef NeoEsp32I2s1X8Tm1914InvertedMethod NeoEsp32I2s1X8Tm1914InvertedMethod; +typedef NeoEsp32I2s1X8Sk6812InvertedMethod NeoEsp32I2s1X8Lc8812InvertedMethod; +typedef NeoEsp32I2s1X8Apa106InvertedMethod NeoEsp32I2s1X8Apa106InvertedMethod; + +// I2s1x16 INVERTED +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Ws2812xInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Sk6812InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Tm1814InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Tm1829InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Tm1914InvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16800KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16400KbpsInvertedMethod; +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X16Apa106InvertedMethod; + +typedef NeoEsp32I2s1X16Ws2812xInvertedMethod NeoEsp32I2s1X16Ws2813InvertedMethod; +typedef NeoEsp32I2s1X16Ws2812xInvertedMethod NeoEsp32I2s1X16Ws2812xInvertedMethod; +typedef NeoEsp32I2s1X16Ws2812xInvertedMethod NeoEsp32I2s1X16Ws2811InvertedMethod; +typedef NeoEsp32I2s1X16Ws2812xInvertedMethod NeoEsp32I2s1X16Ws2816InvertedMethod; +typedef NeoEsp32I2s1X16800KbpsInvertedMethod NeoEsp32I2s1X16Ws2812InvertedMethod; +typedef NeoEsp32I2s1X16Sk6812InvertedMethod NeoEsp32I2s1X16Sk6812InvertedMethod; +typedef NeoEsp32I2s1X16Tm1814InvertedMethod NeoEsp32I2s1X16Tm1814InvertedMethod; +typedef NeoEsp32I2s1X16Tm1829InvertedMethod NeoEsp32I2s1X16Tm1829InvertedMethod; +typedef NeoEsp32I2s1X16Tm1914InvertedMethod NeoEsp32I2s1X16Tm1914InvertedMethod; +typedef NeoEsp32I2s1X16Sk6812InvertedMethod NeoEsp32I2s1X16Lc8812InvertedMethod; +typedef NeoEsp32I2s1X16Apa106InvertedMethod NeoEsp32I2s1X16Apa106InvertedMethod; + +#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) + +#endif // defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) diff --git a/src/internal/NeoEsp32RmtMethod.h b/src/internal/NeoEsp32RmtMethod.h index 56558d8..e4e3d85 100644 --- a/src/internal/NeoEsp32RmtMethod.h +++ b/src/internal/NeoEsp32RmtMethod.h @@ -587,6 +587,12 @@ public: } } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _dataEditing; diff --git a/src/internal/NeoEsp8266DmaMethod.h b/src/internal/NeoEsp8266DmaMethod.h index d1a8ad1..5ab7feb 100644 --- a/src/internal/NeoEsp8266DmaMethod.h +++ b/src/internal/NeoEsp8266DmaMethod.h @@ -261,6 +261,12 @@ public: _dmaState = NeoDmaState_Pending; } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/NeoEsp8266I2sDmx512Method.h b/src/internal/NeoEsp8266I2sDmx512Method.h index 4f760e8..171cd23 100644 --- a/src/internal/NeoEsp8266I2sDmx512Method.h +++ b/src/internal/NeoEsp8266I2sDmx512Method.h @@ -198,6 +198,12 @@ public: _dmaState = NeoDmaState_Pending; } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data + T_SPEED::HeaderSize; diff --git a/src/internal/NeoEsp8266UartMethod.h b/src/internal/NeoEsp8266UartMethod.h index 3001e5a..2eb5228 100644 --- a/src/internal/NeoEsp8266UartMethod.h +++ b/src/internal/NeoEsp8266UartMethod.h @@ -396,6 +396,12 @@ public: this->UpdateUart(maintainBufferConsistency); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return this->_data; diff --git a/src/internal/NeoEspBitBangMethod.h b/src/internal/NeoEspBitBangMethod.h index faf7886..25ee665 100644 --- a/src/internal/NeoEspBitBangMethod.h +++ b/src/internal/NeoEspBitBangMethod.h @@ -308,6 +308,12 @@ public: _endTime = micros(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/NeoNrf52xMethod.h b/src/internal/NeoNrf52xMethod.h index f15bf58..5a509ba 100644 --- a/src/internal/NeoNrf52xMethod.h +++ b/src/internal/NeoNrf52xMethod.h @@ -400,6 +400,12 @@ public: dmaStart(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/P9813GenericMethod.h b/src/internal/P9813GenericMethod.h index 991d913..bd9369d 100644 --- a/src/internal/P9813GenericMethod.h +++ b/src/internal/P9813GenericMethod.h @@ -96,6 +96,12 @@ public: _wire.endTransaction(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/Tlc5947GenericMethod.h b/src/internal/Tlc5947GenericMethod.h index 7a61e55..001b388 100644 --- a/src/internal/Tlc5947GenericMethod.h +++ b/src/internal/Tlc5947GenericMethod.h @@ -164,6 +164,12 @@ public: digitalWrite(_pinOutputEnable, LOW); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data; diff --git a/src/internal/Ws2801GenericMethod.h b/src/internal/Ws2801GenericMethod.h index fea18a1..9b4ebf7 100644 --- a/src/internal/Ws2801GenericMethod.h +++ b/src/internal/Ws2801GenericMethod.h @@ -102,6 +102,12 @@ public: _endTime = micros(); } + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + uint8_t* getData() const { return _data;