From 57bf7e83cb0ec8f35c2cb7ea805547316cfc4aef Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Mon, 14 Jan 2019 17:00:00 -0800 Subject: [PATCH] Update NeoEsp8266DmaMethod.h (#245) Fix Esp8266 DMA to not truncate the send when the bus is destructed Fix Esp8266 DMA to send first show, due to state change to idle to quickly --- src/internal/NeoEsp8266DmaMethod.h | 102 ++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/src/internal/NeoEsp8266DmaMethod.h b/src/internal/NeoEsp8266DmaMethod.h index f466bfd..ec1a842 100644 --- a/src/internal/NeoEsp8266DmaMethod.h +++ b/src/internal/NeoEsp8266DmaMethod.h @@ -68,6 +68,7 @@ class NeoEsp8266DmaSpeed800KbpsBase public: const static uint32_t I2sClockDivisor = 3; const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800khz speed }; class NeoEsp8266DmaSpeedWs2812x : public NeoEsp8266DmaSpeed800KbpsBase @@ -91,8 +92,9 @@ public: class NeoEsp8266DmaSpeed400Kbps { public: - const static uint32_t I2sClockDivisor = 6; + const static uint32_t I2sClockDivisor = 6; const static uint32_t I2sBaseClockDivisor = 16; + const static uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed const static uint32_t ResetTimeUs = 50; }; @@ -101,6 +103,7 @@ enum NeoDmaState NeoDmaState_Idle, NeoDmaState_Pending, NeoDmaState_Sending, + NeoDmaState_Zeroing, }; const uint16_t c_maxDmaBlockSize = 4095; const uint16_t c_dmaBytesPerPixelBytes = 4; @@ -109,7 +112,7 @@ const uint8_t c_I2sPin = 3; // due to I2S hardware, the pin used is restricted t template class NeoEsp8266DmaMethodBase { public: - NeoEsp8266DmaMethodBase(uint16_t pixelCount, size_t elementSize) + NeoEsp8266DmaMethodBase(uint16_t pixelCount, size_t elementSize) { uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize; @@ -122,6 +125,8 @@ public: _i2sBuffer = (uint8_t*)malloc(_i2sBufferSize); memset(_i2sBuffer, 0x00, _i2sBufferSize); + // _i2sBuffer[0] = 0b11101000; // debug, 1 bit then 0 bit + memset(_i2sZeroes, 0x00, sizeof(_i2sZeroes)); _is2BufMaxBlockSize = (c_maxDmaBlockSize / dmaPixelSize) * dmaPixelSize; @@ -138,14 +143,36 @@ public: ~NeoEsp8266DmaMethodBase() { + digitalWrite(4, HIGH); + + uint8_t waits = 1; + while (!IsReadyToUpdate()) + { + waits = 2; + yield(); + } + + // wait for any pending sends to complete + // due to internal i2s caching/send delays, this can more that once the data size + uint32_t time = micros(); + while ((micros() - time) < ((getPixelTime() + T_SPEED::ResetTimeUs) * waits)) + { + yield(); + } + + digitalWrite(4, LOW); + StopDma(); + s_this = nullptr; + pinMode(c_I2sPin, INPUT); + free(_pixels); free(_i2sBuffer); free(_i2sBufDesc); } - bool IsReadyToUpdate() const + bool IsReadyToUpdate() { return (_dmaState == NeoDmaState_Idle); } @@ -153,7 +180,8 @@ public: void Initialize() { StopDma(); - _dmaState = NeoDmaState_Sending; // start off sending empty buffer + + pinMode(c_I2sPin, FUNCTION_1); // I2S0_DATA uint8_t* is2Buffer = _i2sBuffer; uint32_t is2BufferSize = _i2sBufferSize; @@ -198,6 +226,11 @@ public: // setup the rest of i2s DMA // ETS_SLC_INTR_DISABLE(); + + // start off in sending state as that is what it will be all setup to be + // for the interrupt + _dmaState = NeoDmaState_Sending; + SLCC0 |= SLCRXLR | SLCTXLR; SLCC0 &= ~(SLCRXLR | SLCTXLR); SLCIC = 0xFFFFFFFF; @@ -213,9 +246,11 @@ public: // expect. The TXLINK part still needs a valid DMA descriptor, even if it's unused: the DMA engine will throw // an error at us otherwise. Just feed it any random descriptor. SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address - SLCTXL |= (uint32)&(_i2sBufDesc[_i2sBufDescCount-1]) << SLCTXLA; // set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid + // set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid + SLCTXL |= (uint32)&(_i2sBufDesc[_i2sBufDescCount-1]) << SLCTXLA; SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address - SLCRXL |= (uint32)_i2sBufDesc << SLCRXLA; // set RX descriptor address + // set RX descriptor address. use first of the data addresses + SLCRXL |= (uint32)&(_i2sBufDesc[0]) << SLCRXLA; ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); SLCIE = SLCIRXEOF; // Enable only for RX EOF interrupt @@ -226,8 +261,6 @@ public: SLCTXL |= SLCTXLS; SLCRXL |= SLCRXLS; - pinMode(c_I2sPin, FUNCTION_1); // I2S0_DATA - I2S_CLK_ENABLE(); I2SIC = 0x3F; I2SIE = 0; @@ -237,16 +270,18 @@ public: I2SC |= I2SRST; I2SC &= ~(I2SRST); - I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) + // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) + I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); I2SFC |= I2SDE; //Enable DMA - I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set RX/TX CHAN_MOD=0 + // Set RX/TX CHAN_MOD=0 + I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // set the rate uint32_t i2s_clock_div = T_SPEED::I2sClockDivisor & I2SCDM; uint8_t i2s_bck_div = T_SPEED::I2sBaseClockDivisor & I2SBDM; //!trans master, !bits mod, rece slave mod, rece msb shift, right first, msb right - I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); + I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (i2s_bck_div << I2SBD) | (i2s_clock_div << I2SCD); I2SC |= I2STXS; // Start transmission @@ -255,12 +290,12 @@ public: void ICACHE_RAM_ATTR Update() { // wait for not actively sending data - while (_dmaState != NeoDmaState_Idle) + while (!IsReadyToUpdate()) { yield(); } FillBuffers(); - + // toggle state so the ISR reacts _dmaState = NeoDmaState_Pending; } @@ -285,9 +320,9 @@ private: uint8_t* _i2sBuffer; // holds the DMA buffer that is referenced by _i2sBufDesc // normally 24 bytes creates the minimum 50us latch per spec, but - // with the new logic, this latch is used to space between three states - // buffer size = (24 * (speed / 50)) / 3 - uint8_t _i2sZeroes[(24L * (T_SPEED::ResetTimeUs / 50L)) / 3L]; + // with the new logic, this latch is used to space between mulitple states + // buffer size = (24 * (reset time / 50)) / 6 + uint8_t _i2sZeroes[(24L * (T_SPEED::ResetTimeUs / 50L)) / 6L]; slc_queue_item* _i2sBufDesc; // dma block descriptors uint16_t _i2sBufDescCount; // count of block descriptors in _i2sBufDesc @@ -301,15 +336,15 @@ private: // in the case of this code, the second to last state descriptor volatile static void ICACHE_RAM_ATTR i2s_slc_isr(void) { + ETS_SLC_INTR_DISABLE(); + uint32_t slc_intr_status = SLCIS; SLCIC = 0xFFFFFFFF; - if (slc_intr_status & SLCIRXEOF) + if ((slc_intr_status & SLCIRXEOF) && s_this) { - ETS_SLC_INTR_DISABLE(); - - switch (s_this->_dmaState) + switch (s_this->_dmaState) { case NeoDmaState_Idle: break; @@ -335,14 +370,17 @@ private: // just looping and not sending the data blocks (finished_item + 1)->next_link_ptr = (uint32_t)(finished_item); - s_this->_dmaState = NeoDmaState_Idle; + s_this->_dmaState = NeoDmaState_Zeroing; } break; + + case NeoDmaState_Zeroing: + s_this->_dmaState = NeoDmaState_Idle; + break; } - - - ETS_SLC_INTR_ENABLE(); } + + ETS_SLC_INTR_ENABLE(); } void FillBuffers() @@ -367,6 +405,16 @@ private: void StopDma() { ETS_SLC_INTR_DISABLE(); + + // Disable any I2S send or receive + I2SC &= ~(I2STXS | I2SRXS); + + // Reset I2S + I2SC &= ~(I2SRST); + I2SC |= I2SRST; + I2SC &= ~(I2SRST); + + SLCIC = 0xFFFFFFFF; SLCIE = 0; SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address @@ -374,6 +422,12 @@ private: pinMode(c_I2sPin, INPUT); } + + uint32_t getPixelTime() const + { + return (T_SPEED::ByteSendTimeUs * this->_pixelsSize); + }; + }; template