From 13b7d2e3776248785da3c08e581f1af9af86c8f5 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 11 Feb 2021 17:22:10 -0800 Subject: [PATCH] Esp32 i2s (#427) * dynamic i2s bus numbers * stabile and consistent shows * faster to start send --- keywords.txt | 22 +++- src/internal/Esp32_i2s.c | 192 +++++++++++++++---------------- src/internal/Esp32_i2s.h | 11 +- src/internal/NeoEsp32I2sMethod.h | 103 +++++++++++++---- 4 files changed, 199 insertions(+), 129 deletions(-) diff --git a/keywords.txt b/keywords.txt index 60650c6..f7e2f68 100644 --- a/keywords.txt +++ b/keywords.txt @@ -166,6 +166,12 @@ NeoEsp8266BitBangLc8812InvertedMethod KEYWORD1 NeoEsp8266BitBangApa106InvertedMethod KEYWORD1 NeoEsp8266BitBang800KbpsInvertedMethod KEYWORD1 NeoEsp8266BitBang400KbpsInvertedMethod KEYWORD1 +NeoEsp32I2sNWs2812xMethod KEYWORD1 +NeoEsp32I2sNSk6812Method KEYWORD1 +NeoEsp32I2sNTm1814Method KEYWORD1 +NeoEsp32I2sN800KbpsMethod KEYWORD1 +NeoEsp32I2sN400KbpsMethod KEYWORD1 +NeoEsp32I2sNApa106Method KEYWORD1 NeoEsp32I2s0Ws2812xMethod KEYWORD1 NeoEsp32I2s0Sk6812Method KEYWORD1 NeoEsp32I2s0Tm1814Method KEYWORD1 @@ -178,6 +184,12 @@ NeoEsp32I2s1Tm1814Method KEYWORD1 NeoEsp32I2s1800KbpsMethod KEYWORD1 NeoEsp32I2s1400KbpsMethod KEYWORD1 NeoEsp32I2s1Apa106Method KEYWORD1 +NeoEsp32I2sNWs2812xInvertedMethod KEYWORD1 +NeoEsp32I2sNSk6812InvertedMethod KEYWORD1 +NeoEsp32I2sNTm1814InvertedMethod KEYWORD1 +NeoEsp32I2sN800KbpsInvertedMethod KEYWORD1 +NeoEsp32I2sN400KbpsInvertedMethod KEYWORD1 +NeoEsp32I2sNApa106InvertedMethod KEYWORD1 NeoEsp32I2s0Ws2812xInvertedMethod KEYWORD1 NeoEsp32I2s0Sk6812InvertedMethod KEYWORD1 NeoEsp32I2s0Tm1814InvertedMethod KEYWORD1 @@ -584,4 +596,12 @@ NeoTopologyHint_FirstOnPanel LITERAL1 NeoTopologyHint_InPanel LITERAL1 NeoTopologyHint_LastOnPanel LITERAL1 NeoTopologyHint_OutOfBounds LITERAL1 -PixelIndex_OutOfBounds LITERAL1 \ No newline at end of file +PixelIndex_OutOfBounds LITERAL1 +NeoBusChannel_0 LITERAL1 +NeoBusChannel_1 LITERAL1 +NeoBusChannel_2 LITERAL1 +NeoBusChannel_3 LITERAL1 +NeoBusChannel_4 LITERAL1 +NeoBusChannel_5 LITERAL1 +NeoBusChannel_6 LITERAL1 +NeoBusChannel_7 LITERAL1 diff --git a/src/internal/Esp32_i2s.c b/src/internal/Esp32_i2s.c index 72fbf22..032f0fe 100644 --- a/src/internal/Esp32_i2s.c +++ b/src/internal/Esp32_i2s.c @@ -52,9 +52,12 @@ #define ESP32_REG(addr) (*((volatile uint32_t*)(0x3FF00000+(addr)))) -#define I2S_DMA_QUEUE_SIZE 16 - -#define I2S_DMA_SILENCE_LEN 256 // bytes +#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_dma_item_s { uint32_t blocksize: 12; // datalen @@ -91,24 +94,29 @@ typedef struct { size_t dma_count; uint32_t dma_buf_len :12; uint32_t unused :20; + volatile uint32_t is_sending_data; } i2s_bus_t; -static uint8_t i2s_silence_buf[I2S_DMA_SILENCE_LEN]; +// is_sending_data values +#define I2s_Is_Idle 0 +#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) // (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_LEN, NULL, I2S_DMA_QUEUE_SIZE, 0, 0}, - {&I2S1, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_LEN, NULL, I2S_DMA_QUEUE_SIZE, 0, 0} + {&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} }; #else static i2s_bus_t I2S[I2S_NUM_MAX] = { - {&I2S0, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_LEN, NULL, I2S_DMA_QUEUE_SIZE, 0, 0} + {&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} }; #endif void IRAM_ATTR i2sDmaISR(void* arg); -bool i2sInitDmaItems(uint8_t bus_num); bool i2sInitDmaItems(uint8_t bus_num) { if (bus_num >= I2S_NUM_MAX) { @@ -118,8 +126,10 @@ bool i2sInitDmaItems(uint8_t bus_num) { 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*)(malloc(I2S[bus_num].dma_count* sizeof(i2s_dma_item_t))); + I2S[bus_num].dma_items = (i2s_dma_item_t*)(malloc(dmaCount * sizeof(i2s_dma_item_t))); if (I2S[bus_num].dma_items == NULL) { log_e("MEM ERROR!"); return false; @@ -127,12 +137,15 @@ bool i2sInitDmaItems(uint8_t bus_num) { } int i, i2, a; - i2s_dma_item_t* item; + i2s_dma_item_t* item = NULL; + i2s_dma_item_t* itemPrev; - for(i=0; ieof = 1; + item->eof = 0; item->owner = 1; item->sub_sof = 0; item->unused = 0; @@ -141,23 +154,12 @@ bool i2sInitDmaItems(uint8_t bus_num) { item->datalen = I2S[bus_num].silence_len; item->next = &I2S[bus_num].dma_items[i2]; item->free_ptr = NULL; - if (I2S[bus_num].dma_buf_len) { - item->buf = (uint8_t*)(malloc(I2S[bus_num].dma_buf_len)); - if (item->buf == NULL) { - log_e("MEM ERROR!"); - for(a=0; abuf = NULL; - } + item->buf = NULL; } + itemPrev->eof = 1; + item->eof = 1; - I2S[bus_num].tx_queue = xQueueCreate(I2S[bus_num].dma_count, sizeof(i2s_dma_item_t*)); + 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!"); free(I2S[bus_num].dma_items); @@ -167,14 +169,6 @@ bool i2sInitDmaItems(uint8_t bus_num) { return true; } -void i2sSetSilenceBuf(uint8_t bus_num, uint8_t* data, size_t len) { - if (bus_num >= I2S_NUM_MAX || !data || !len) { - return; - } - I2S[bus_num].silence_buf = data; - I2S[bus_num].silence_len = len; -} - 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) { return ESP_FAIL; @@ -205,37 +199,6 @@ esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t d return ESP_OK; } -void i2sSetDac(uint8_t bus_num, bool right, bool left) { - if (bus_num >= I2S_NUM_MAX) { - return; - } - - if (!right && !left) { - dac_output_disable(DAC_CHANNEL_1); - dac_output_disable(DAC_CHANNEL_2); - dac_i2s_disable(); - I2S[bus_num].bus->conf2.lcd_en = 0; - I2S[bus_num].bus->conf.tx_right_first = 0; - I2S[bus_num].bus->conf2.camera_en = 0; - I2S[bus_num].bus->conf.tx_msb_shift = 1;// I2S signaling - return; - } - - i2sSetPins(bus_num, -1, false); - I2S[bus_num].bus->conf2.lcd_en = 1; - I2S[bus_num].bus->conf.tx_right_first = 0; - I2S[bus_num].bus->conf2.camera_en = 0; - I2S[bus_num].bus->conf.tx_msb_shift = 0; - dac_i2s_enable(); - - if (right) {// DAC1, right channel, GPIO25 - dac_output_enable(DAC_CHANNEL_1); - } - if (left) { // DAC2, left channel, GPIO26 - dac_output_enable(DAC_CHANNEL_2); - } -} - void i2sSetPins(uint8_t bus_num, int8_t out, bool invert) { if (bus_num >= I2S_NUM_MAX) { return; @@ -274,15 +237,22 @@ bool i2sWriteDone(uint8_t bus_num) { if (bus_num >= I2S_NUM_MAX) { return false; } - return (I2S[bus_num].dma_items[I2S[bus_num].dma_count - 1].data == I2S[bus_num].silence_buf); + + return (I2S[bus_num].is_sending_data == I2s_Is_Idle); } -void i2sInit(uint8_t bus_num, uint32_t bits_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) { +void i2sInit(uint8_t bus_num, + uint32_t bits_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) { return; } - I2S[bus_num].dma_count = dma_count; + 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; if (!i2sInitDmaItems(bus_num)) { @@ -446,56 +416,76 @@ esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, uint8_t bits) { return ESP_OK; } + + void IRAM_ATTR i2sDmaISR(void* arg) { - i2s_dma_item_t* dummy = NULL; i2s_bus_t* dev = (i2s_bus_t*)(arg); - portBASE_TYPE hpTaskAwoken = 0; - if (dev->bus->int_st.out_eof) { - i2s_dma_item_t* item = (i2s_dma_item_t*)(dev->bus->out_eof_des_addr); - item->data = dev->silence_buf; - item->blocksize = dev->silence_len; - item->datalen = dev->silence_len; - if (xQueueIsQueueFullFromISR(dev->tx_queue) == pdTRUE) { - xQueueReceiveFromISR(dev->tx_queue, &dummy, &hpTaskAwoken); + if (dev->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) + { + 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]; + + dev->is_sending_data = I2s_Is_Pending; } - xQueueSendFromISR(dev->tx_queue, (void*)&item, &hpTaskAwoken); } + dev->bus->int_clr.val = dev->bus->int_st.val; - if (hpTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR(); - } } 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 index = 0; - size_t toSend = len; - size_t limit = I2S_DMA_MAX_DATA_LEN; - i2s_dma_item_t* item = NULL; + size_t blockSize = len; - while (len) { - toSend = len; - if (toSend > limit) { - toSend = limit; - } + i2s_dma_item_t* itemPrev = NULL; + i2s_dma_item_t* item = &I2S[bus_num].dma_items[0]; + size_t dataLeft = len; + uint8_t* pos = data; - if (xQueueReceive(I2S[bus_num].tx_queue, &item, portMAX_DELAY) == pdFALSE) { - log_e("xQueueReceive failed\n"); - break; + // 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 = data + index; - item->blocksize = toSend; - item->datalen = toSend; + item->data = pos; + item->blocksize = blockSize; + item->datalen = blockSize; - len -= toSend; - index += toSend; + itemPrev = item; + item++; + + pos += blockSize; } - return index; + + + // 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; } diff --git a/src/internal/Esp32_i2s.h b/src/internal/Esp32_i2s.h index a95e7c5..d31c51e 100644 --- a/src/internal/Esp32_i2s.h +++ b/src/internal/Esp32_i2s.h @@ -18,16 +18,19 @@ typedef enum { I2S_FIFO_16BIT_DUAL, I2S_FIFO_16BIT_SINGLE, I2S_FIFO_32BIT_DUAL, I2S_FIFO_32BIT_SINGLE } i2s_tx_fifo_mod_t; -void i2sInit(uint8_t bus_num, uint32_t bits_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); +void i2sInit(uint8_t bus_num, + uint32_t bits_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); void i2sSetPins(uint8_t bus_num, int8_t out, bool invert); -void i2sSetDac(uint8_t bus_num, bool right, bool left); 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); -void i2sSetSilenceBuf(uint8_t bus_num, uint8_t* data, size_t len); - size_t i2sWrite(uint8_t bus_num, uint8_t* data, size_t len, bool copy, bool free_when_sent); bool i2sWriteDone(uint8_t bus_num); diff --git a/src/internal/NeoEsp32I2sMethod.h b/src/internal/NeoEsp32I2sMethod.h index 2c474d6..32411c2 100644 --- a/src/internal/NeoEsp32I2sMethod.h +++ b/src/internal/NeoEsp32I2sMethod.h @@ -87,15 +87,32 @@ public: class NeoEsp32I2sBusZero { public: + NeoEsp32I2sBusZero() {}; + const static uint8_t I2sBusNumber = 0; }; class NeoEsp32I2sBusOne { public: + NeoEsp32I2sBusOne() {}; + const static uint8_t I2sBusNumber = 1; }; +// dynamic channel support +class NeoEsp32I2sBusN +{ +public: + NeoEsp32I2sBusN(NeoBusChannel channel) : + I2sBusNumber(static_cast(channel)) + { + } + NeoEsp32I2sBusN() = delete; // no default constructor + + const uint8_t I2sBusNumber; +}; + class NeoEsp32I2sNotInverted { public: @@ -115,24 +132,15 @@ public: _sizeData(pixelCount * elementSize + settingsSize), _pin(pin) { - uint16_t dmaSettingsSize = c_dmaBytesPerPixelBytes * settingsSize; - uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize; - uint16_t resetSize = c_dmaBytesPerPixelBytes * T_SPEED::ResetTimeUs / T_SPEED::ByteSendTimeUs; + construct(pixelCount, elementSize, settingsSize); + } - _i2sBufferSize = pixelCount * dmaPixelSize + dmaSettingsSize + resetSize; - - // must have a 4 byte aligned buffer for i2s - uint32_t alignment = _i2sBufferSize % 4; - if (alignment) - { - _i2sBufferSize += 4 - alignment; - } - - _data = static_cast(malloc(_sizeData)); - // data cleared later in Begin() - - _i2sBuffer = static_cast(malloc(_i2sBufferSize)); - // no need to initialize it, it gets overwritten on every send + NeoEsp32I2sMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) : + _sizeData(pixelCount * elementSize + settingsSize), + _pin(pin), + _bus(channel) + { + construct(pixelCount, elementSize, settingsSize); } ~NeoEsp32I2sMethodBase() @@ -150,14 +158,21 @@ public: bool IsReadyToUpdate() const { - return (i2sWriteDone(T_BUS::I2sBusNumber)); + return (i2sWriteDone(_bus.I2sBusNumber)); } void Initialize() { - size_t dmaCount = (_i2sBufferSize + I2S_DMA_MAX_DATA_LEN - 1) / I2S_DMA_MAX_DATA_LEN; - i2sInit(T_BUS::I2sBusNumber, 16, T_SPEED::I2sSampleRate, I2S_CHAN_STEREO, I2S_FIFO_16BIT_DUAL, dmaCount, 0); - i2sSetPins(T_BUS::I2sBusNumber, _pin, T_INVERT::Inverted); + size_t dmaBlockCount = (_i2sBufferSize + I2S_DMA_MAX_DATA_LEN - 1) / I2S_DMA_MAX_DATA_LEN; + + i2sInit(_bus.I2sBusNumber, + 16, + T_SPEED::I2sSampleRate, + I2S_CHAN_STEREO, + I2S_FIFO_16BIT_DUAL, + dmaBlockCount, + 0); + i2sSetPins(_bus.I2sBusNumber, _pin, T_INVERT::Inverted); } void Update(bool) @@ -170,7 +185,7 @@ public: FillBuffers(); - i2sWrite(T_BUS::I2sBusNumber, _i2sBuffer, _i2sBufferSize, false, false); + i2sWrite(_bus.I2sBusNumber, _i2sBuffer, _i2sBufferSize, false, false); } uint8_t* getData() const @@ -186,12 +201,39 @@ public: private: const size_t _sizeData; // Size of '_data' buffer const uint8_t _pin; // output pin number - + const T_BUS _bus; // holds instance for multi bus support + uint8_t* _data; // Holds LED color values uint32_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) + { + ESP_ERROR_CHECK(pixelCount >= 2 ? ESP_OK : ESP_ERR_INVALID_ARG); + + uint16_t dmaSettingsSize = c_dmaBytesPerPixelBytes * settingsSize; + uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize; + uint16_t resetSize = c_dmaBytesPerPixelBytes * T_SPEED::ResetTimeUs / T_SPEED::ByteSendTimeUs; + + _i2sBufferSize = pixelCount * dmaPixelSize + dmaSettingsSize + resetSize; + + // must have a 4 byte aligned buffer for i2s + uint32_t alignment = _i2sBufferSize % 4; + if (alignment) + { + _i2sBufferSize += 4 - alignment; + } + + _data = static_cast(malloc(_sizeData)); + // data cleared later in Begin() + + _i2sBuffer = static_cast(malloc(_i2sBufferSize)); + // no need to initialize all of it, but since it contains + // "reset" bits that don't latter get overwritten we just clear it all + memset(_i2sBuffer, 0x00, _i2sBufferSize); + } + void FillBuffers() { const uint16_t bitpatterns[16] = @@ -244,6 +286,21 @@ typedef NeoEsp32I2sMethodBase NeoEsp32I2s1400KbpsInvertedMethod; typedef NeoEsp32I2sMethodBase NeoEsp32I2s1Apa106InvertedMethod; + +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNWs2812xMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNSk6812Method; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNTm1814Method; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sN800KbpsMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sN400KbpsMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNApa106Method; + +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNWs2812xInvertedMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNSk6812InvertedMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNTm1814InvertedMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sN800KbpsInvertedMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sN400KbpsInvertedMethod; +typedef NeoEsp32I2sMethodBase NeoEsp32I2sNApa106InvertedMethod; + #endif /* due to a core issue where requests to send aren't consistent, I2s is no longer the default