From a56929531e869bf156d69c049154e293f8463557 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 4 Apr 2024 09:30:29 -0700 Subject: [PATCH] Esp32 i2s parallel uses a single back buffer (#793) --- src/internal/methods/Esp32_i2s.c | 42 ++-- src/internal/methods/NeoEsp32I2sXMethod.h | 245 ++++++++++++++++++---- 2 files changed, 229 insertions(+), 58 deletions(-) diff --git a/src/internal/methods/Esp32_i2s.c b/src/internal/methods/Esp32_i2s.c index 4c41ffe..46b3986 100644 --- a/src/internal/methods/Esp32_i2s.c +++ b/src/internal/methods/Esp32_i2s.c @@ -85,6 +85,16 @@ esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t sample_rate, bool parallel_ #define I2S_DMA_SILENCE_BLOCK_COUNT_FRONT 2 // two front #define I2S_DMA_SILENCE_BLOCK_COUNT_BACK 1 // one back, required for non parallel +// compatibility shim between versions of the IDF +// note that I2S_NUM_MAX is an enum element, so we check for +// existence of the new SOC_I2S_NUM +// +#if defined(SOC_I2S_NUM) +#define NEO_I2S_COUNT (SOC_I2S_NUM) +#else +#define NEO_I2S_COUNT (I2S_NUM_MAX) +#endif + typedef struct { i2s_dev_t* bus; @@ -106,14 +116,14 @@ typedef struct #define I2s_Is_Sending 2 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) -// (SOC_I2S_NUM == 2) -static i2s_bus_t I2S[SOC_I2S_NUM] = +// (NEO_I2S_COUNT == 2) +static i2s_bus_t I2S[NEO_I2S_COUNT] = { {&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[SOC_I2S_NUM] = +static i2s_bus_t I2S[NEO_I2S_COUNT] = { {&I2S0, -1, -1, -1, -1, NULL, NULL, I2S_DMA_BLOCK_COUNT_DEFAULT, I2s_Is_Idle} }; @@ -135,7 +145,7 @@ inline void dmaItemInit(lldesc_t* item, uint8_t* posData, size_t sizeData, lldes bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize, bool parallel_mode, size_t bytes_per_sample) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return false; } @@ -206,7 +216,7 @@ bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize, bool paral bool i2sDeinitDmaItems(uint8_t bus_num) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return false; } @@ -224,7 +234,7 @@ esp_err_t i2sSetClock(uint8_t bus_num, uint8_t bck, uint8_t bits) { - if (bus_num >= SOC_I2S_NUM || div_a > 63 || div_b > 63 || bck > 63) + if (bus_num >= NEO_I2S_COUNT || div_a > 63 || div_b > 63 || bck > 63) { return ESP_FAIL; } @@ -273,7 +283,7 @@ void i2sSetPins(uint8_t bus_num, int8_t busSampleSize, bool invert) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return; } @@ -361,7 +371,7 @@ void i2sSetClkWsPins(uint8_t bus_num, bool invertWs) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return; } @@ -392,7 +402,7 @@ void i2sSetClkWsPins(uint8_t bus_num, bool i2sWriteDone(uint8_t bus_num) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return false; } @@ -410,7 +420,7 @@ void i2sInit(uint8_t bus_num, uint8_t* data, size_t dataSize) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return; } @@ -425,7 +435,7 @@ void i2sInit(uint8_t bus_num, } #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) -// (SOC_I2S_NUM == 2) +// (NEO_I2S_COUNT == 2) if (bus_num) { periph_module_enable(PERIPH_I2S1_MODULE); @@ -550,7 +560,7 @@ void i2sInit(uint8_t bus_num, int i2sIntSource; #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) -// (SOC_I2S_NUM == 2) +// (NEO_I2S_COUNT == 2) if (bus_num == 1) { i2sIntSource = ETS_I2S1_INTR_SOURCE; @@ -712,7 +722,7 @@ esp_err_t i2sSetSampleRate(uint8_t bus_num, bool parallel_mode, size_t bytes_per_sample) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return ESP_FAIL; } @@ -789,7 +799,7 @@ void IRAM_ATTR i2sDmaISR(void* arg) bool i2sWrite(uint8_t bus_num) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return false; } @@ -918,7 +928,7 @@ void DumpI2s_fifo_conf(const char* label, i2s_dev_t* bus) bool i2sDump(uint8_t bus_num) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return false; } @@ -1014,7 +1024,7 @@ bool i2sGetClks(uint8_t bus_num, uint8_t* clkm_div_b, uint8_t* clkm_div_a) { - if (bus_num >= SOC_I2S_NUM) + if (bus_num >= NEO_I2S_COUNT) { return false; } diff --git a/src/internal/methods/NeoEsp32I2sXMethod.h b/src/internal/methods/NeoEsp32I2sXMethod.h index 8473abb..9fc2303 100644 --- a/src/internal/methods/NeoEsp32I2sXMethod.h +++ b/src/internal/methods/NeoEsp32I2sXMethod.h @@ -307,6 +307,140 @@ public: } }; +// +// Implementation of a Single Buffered version of a I2sContext +// Manages the underlying I2S details including the buffer +// This creates only a actively sending back buffer, +// Note that the back buffer must be DMA memory, a limited resource +// +// T_MUXMAP - NeoEspI2sMuxMap - tracking class for mux state +// +template +class NeoEspI2sMonoBuffContext +{ +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 + 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 + NeoEspI2sMonoBuffContext() + //: + //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); + + 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); + + heap_caps_free(I2sBuffer); + + I2sBufferSize = 0; + I2sBuffer = nullptr; + + MuxMap.Reset(); + } + + void StartWrite(uint8_t i2sBusNumber) + { + if (MuxMap.IsAllMuxBusesUpdated()) + { + MuxMap.ResetMuxBusesUpdated(); + i2sWrite(i2sBusNumber); + } + } + + void FillBuffers(const uint8_t* data, + size_t sizeData, + uint8_t muxId, + uint8_t i2sBusNumber) + { + // wait for not actively sending data + while (!i2sWriteDone(i2sBusNumber)) + { + yield(); + } + + // to keep the inner loops for EncodeIntoDma smaller + // they will just OR in their values + // so the buffer must be cleared first + if (MuxMap.IsNoMuxBusesUpdate()) + { + // clear all the data in preperation for each mux channel to add + memset(I2sBuffer, 0x00, I2sBufferSize); + } + + MuxMap.EncodeIntoDma(I2sBuffer, + data, + sizeData, + muxId); + + MuxMap.MarkMuxBusUpdated(muxId); + } +}; + // // Implementation of a Double Buffered version of a I2sContext // Manages the underlying I2S details including the buffer(s) @@ -317,8 +451,8 @@ public: // // T_MUXMAP - NeoEspI2sMuxMap - tracking class for mux state // -template -class NeoEspI2sDblBuffContext +template +class NeoEspI2sDblBuffContext { public: const static size_t DmaBitsPerPixelBit = 4; @@ -332,12 +466,12 @@ public: // 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() + NeoEspI2sDblBuffContext() + //: + //I2sBufferSize(0), + //I2sBuffer(nullptr), + //I2sEditBuffer(nullptr), + //MuxMap() { } @@ -381,12 +515,12 @@ public: 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 + // 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 + // but they won't work on ESP32 in parallel mode, but these will I2S_CHAN_RIGHT_TO_LEFT, I2S_FIFO_16BIT_SINGLE, #endif @@ -415,8 +549,50 @@ public: MuxMap.Reset(); } + + void StartWrite(uint8_t i2sBusNumber) + { + if (MuxMap.IsAllMuxBusesUpdated()) + { + MuxMap.ResetMuxBusesUpdated(); + + // wait for not actively sending data + while (!i2sWriteDone(i2sBusNumber)) + { + yield(); + } + + // copy edit buffer to sending buffer + memcpy(I2sBuffer, I2sEditBuffer, I2sBufferSize); + + i2sWrite(i2sBusNumber); + } + } + + void FillBuffers(const uint8_t* data, + size_t sizeData, + uint8_t muxId, + uint8_t i2sBusNumber) + { + // to keep the inner loops for EncodeIntoDma smaller + // they will just OR in their values + // so the buffer must be cleared first + if (MuxMap.IsNoMuxBusesUpdate()) + { + // clear all the data in preperation for each mux channel to add + memset(I2sEditBuffer, 0x00, I2sBufferSize); + } + + MuxMap.EncodeIntoDma(I2sEditBuffer, + data, + sizeData, + muxId); + + MuxMap.MarkMuxBusUpdated(muxId); + } }; + // // Implementation of the low level interface into i2s mux bus // @@ -462,19 +638,7 @@ public: 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); - } + s_context.StartWrite(T_BUS::I2sBusNumber); } bool IsWriteDone() const @@ -484,18 +648,7 @@ public: 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); + s_context.FillBuffers(data, sizeData, _muxId, T_BUS::I2sBusNumber); } void MarkUpdated() @@ -598,17 +751,23 @@ private: #if defined(CONFIG_IDF_TARGET_ESP32S2) -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; + +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0DblMux8Bus; + +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8DblWs2812xMethod; #else -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux8Bus; -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux16Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux8Bus; +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux16Bus; + +typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1DblMux8Bus; #endif @@ -654,6 +813,8 @@ typedef NeoEsp32I2s0X16Sk6812Method NeoEsp32I2s0X16Lc8812Method; #if !defined(CONFIG_IDF_TARGET_ESP32S2) // I2s1x8 +typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8DblWs2812xMethod; + typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Ws2812xMethod; typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Ws2805Method; typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Sk6812Method;