forked from Makuna/NeoPixelBus
Dmx512 and Ws2821 for ESP8266 (#565)
* refactor esp8266 DMA core * refactor NeoUtil * DMX512 * WS2821
This commit is contained in:
@@ -88,6 +88,10 @@ NeoEsp8266DmaInvertedTm1829Method KEYWORD1
|
||||
NeoEsp8266DmaInvertedApa106Method KEYWORD1
|
||||
NeoEsp8266DmaInverted800KbpsMethod KEYWORD1
|
||||
NeoEsp8266DmaInverted400KbpsMethod KEYWORD1
|
||||
NeoEsp8266Dmx512Method KEYWORD1
|
||||
NeoEsp8266Ws2821Method KEYWORD1
|
||||
NeoEsp8266Dmx512InvertedMethod KEYWORD1
|
||||
NeoEsp8266Ws2821InvertedMethod KEYWORD1
|
||||
NeoEsp8266Uart0Ws2813Method KEYWORD1
|
||||
NeoEsp8266Uart0Ws2812xMethod KEYWORD1
|
||||
NeoEsp8266Uart0Ws2812Method KEYWORD1
|
||||
|
@@ -46,6 +46,8 @@ License along with NeoPixel. If not, see
|
||||
// '_state' flags for internal state
|
||||
#define NEO_DIRTY 0x80 // a change was made to pixel data that requires a show
|
||||
|
||||
#include "internal/NeoUtil.h"
|
||||
|
||||
#include "internal/NeoHueBlend.h"
|
||||
|
||||
#include "internal/NeoSettings.h"
|
||||
@@ -101,6 +103,7 @@ License along with NeoPixel. If not, see
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
|
||||
#include "internal/NeoEsp8266DmaMethod.h"
|
||||
#include "internal/NeoEsp8266I2sDmx512Method.h"
|
||||
#include "internal/NeoEsp8266UartMethod.h"
|
||||
#include "internal/NeoEspBitBangMethod.h"
|
||||
|
||||
|
@@ -25,7 +25,7 @@ License along with NeoPixel. If not, see
|
||||
-------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "RgbColor.h"
|
||||
|
||||
#define MAX_HTML_COLOR_NAME_LEN 21
|
||||
@@ -35,10 +35,6 @@ License along with NeoPixel. If not, see
|
||||
#define pgm_read_ptr(addr) (*reinterpret_cast<const void* const *>(addr))
|
||||
#endif
|
||||
|
||||
#ifndef countof
|
||||
#define countof(array) (sizeof(array)/sizeof(array[0]))
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// HtmlColorPair represents an association between a name and a HTML color code
|
||||
// ------------------------------------------------------------------------
|
||||
|
@@ -24,6 +24,9 @@ License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "NeoUtil.h"
|
||||
#include "HtmlColor.h"
|
||||
#include "HtmlColorNameStrings.h"
|
||||
|
||||
|
@@ -24,6 +24,9 @@ License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "NeoUtil.h"
|
||||
#include "HtmlColor.h"
|
||||
#include "HtmlColorNameStrings.h"
|
||||
|
||||
|
@@ -31,51 +31,12 @@ License along with NeoPixel. If not, see
|
||||
#pragma once
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
|
||||
#include "i2s_reg.h"
|
||||
|
||||
#ifdef ARDUINO_ESP8266_MAJOR //this define was added in ESP8266 Arduino Core version v3.0.1
|
||||
#include "core_esp8266_i2s.h" //for Arduino core >= 3.0.1
|
||||
#else
|
||||
#include "i2s.h" //for Arduino core <= 3.0.0
|
||||
#endif
|
||||
|
||||
#include "eagle_soc.h"
|
||||
#include "esp8266_peri.h"
|
||||
#include "slc_register.h"
|
||||
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
#include "user_interface.h"
|
||||
|
||||
#if !defined(__CORE_ESP8266_VERSION_H) || defined(ARDUINO_ESP8266_RELEASE_2_5_0)
|
||||
void rom_i2c_writeReg_Mask(uint32_t block, uint32_t host_id, uint32_t reg_add, uint32_t Msb, uint32_t Lsb, uint32_t indata);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct slc_queue_item
|
||||
{
|
||||
uint32 blocksize : 12;
|
||||
uint32 datalen : 12;
|
||||
uint32 unused : 5;
|
||||
uint32 sub_sof : 1;
|
||||
uint32 eof : 1;
|
||||
uint32 owner : 1;
|
||||
uint8* buf_ptr;
|
||||
struct slc_queue_item* next_link_ptr;
|
||||
};
|
||||
#include "NeoEsp8266I2sMethodCore.h"
|
||||
|
||||
class NeoEsp8266DmaSpeedBase
|
||||
{
|
||||
public:
|
||||
static const uint8_t Level = 0x00;
|
||||
static const uint8_t IdleLevel = 0;
|
||||
static uint16_t Convert(uint8_t value)
|
||||
{
|
||||
const uint16_t bitpatterns[16] =
|
||||
@@ -93,7 +54,7 @@ public:
|
||||
class NeoEsp8266DmaInvertedSpeedBase
|
||||
{
|
||||
public:
|
||||
static const uint8_t Level = 0xFF;
|
||||
static const uint8_t IdleLevel = 1;
|
||||
static uint16_t Convert(uint8_t value)
|
||||
{
|
||||
const uint16_t bitpatterns[16] =
|
||||
@@ -111,8 +72,8 @@ public:
|
||||
class NeoEsp8266DmaSpeed800KbpsBase : public NeoEsp8266DmaSpeedBase
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sClockDivisor = 3;
|
||||
const static uint32_t I2sBaseClockDivisor = 16;
|
||||
const static uint32_t I2sClockDivisor = 3; // 0-63
|
||||
const static uint32_t I2sBaseClockDivisor = 16; // 0-63
|
||||
const static uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800khz speed
|
||||
};
|
||||
|
||||
@@ -149,8 +110,8 @@ public:
|
||||
class NeoEsp8266DmaSpeed400Kbps : public NeoEsp8266DmaSpeedBase
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sClockDivisor = 6;
|
||||
const static uint32_t I2sBaseClockDivisor = 16;
|
||||
const static uint32_t I2sClockDivisor = 6; // 0-63
|
||||
const static uint32_t I2sBaseClockDivisor = 16; // 0-63
|
||||
const static uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed
|
||||
const static uint32_t ResetTimeUs = 50;
|
||||
};
|
||||
@@ -158,8 +119,8 @@ public:
|
||||
class NeoEsp8266DmaSpeedApa106 : public NeoEsp8266DmaSpeedBase
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sClockDivisor = 4;
|
||||
const static uint32_t I2sBaseClockDivisor = 16;
|
||||
const static uint32_t I2sClockDivisor = 4; // 0-63
|
||||
const static uint32_t I2sBaseClockDivisor = 16; // 0-63
|
||||
const static uint32_t ByteSendTimeUs = 17; // us it takes to send a single pixel element
|
||||
const static uint32_t ResetTimeUs = 50;
|
||||
};
|
||||
@@ -169,8 +130,8 @@ public:
|
||||
class NeoEsp8266DmaInvertedSpeed800KbpsBase : public NeoEsp8266DmaInvertedSpeedBase
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sClockDivisor = 3;
|
||||
const static uint32_t I2sBaseClockDivisor = 16;
|
||||
const static uint32_t I2sClockDivisor = 3; // 0-63
|
||||
const static uint32_t I2sBaseClockDivisor = 16; // 0-63
|
||||
const static uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800khz speed
|
||||
};
|
||||
|
||||
@@ -207,8 +168,8 @@ public:
|
||||
class NeoEsp8266DmaInvertedSpeed400Kbps : public NeoEsp8266DmaInvertedSpeedBase
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sClockDivisor = 6;
|
||||
const static uint32_t I2sBaseClockDivisor = 16;
|
||||
const static uint32_t I2sClockDivisor = 6; // 0-63
|
||||
const static uint32_t I2sBaseClockDivisor = 16; // 0-63
|
||||
const static uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400khz speed
|
||||
const static uint32_t ResetTimeUs = 50;
|
||||
};
|
||||
@@ -216,89 +177,15 @@ public:
|
||||
class NeoEsp8266DmaInvertedSpeedApa106 : public NeoEsp8266DmaInvertedSpeedBase
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sClockDivisor = 4;
|
||||
const static uint32_t I2sBaseClockDivisor = 16;
|
||||
const static uint32_t I2sClockDivisor = 4; // 0-63
|
||||
const static uint32_t I2sBaseClockDivisor = 16; // 0-63
|
||||
const static uint32_t ByteSendTimeUs = 17; // us it takes to send a single pixel element
|
||||
const static uint32_t ResetTimeUs = 50;
|
||||
};
|
||||
|
||||
enum NeoDmaState
|
||||
{
|
||||
NeoDmaState_Idle,
|
||||
NeoDmaState_Pending,
|
||||
NeoDmaState_Sending,
|
||||
NeoDmaState_Zeroing,
|
||||
};
|
||||
const uint16_t c_maxDmaBlockSize = 4095;
|
||||
const uint16_t c_dmaBytesPerPixelBytes = 4;
|
||||
const uint8_t c_I2sPin = 3; // due to I2S hardware, the pin used is restricted to this
|
||||
|
||||
class NeoEsp8266DmaMethodCore
|
||||
{
|
||||
protected:
|
||||
static NeoEsp8266DmaMethodCore* s_this; // for the ISR
|
||||
|
||||
volatile NeoDmaState _dmaState;
|
||||
|
||||
slc_queue_item* _i2sBufDesc; // dma block descriptors
|
||||
uint16_t _i2sBufDescCount; // count of block descriptors in _i2sBufDesc
|
||||
|
||||
|
||||
// This routine is called as soon as the DMA routine has something to tell us. All we
|
||||
// handle here is the RX_EOF_INT status, which indicate the DMA has sent a buffer whose
|
||||
// descriptor has the 'EOF' field set to 1.
|
||||
// in the case of this code, the second to last state descriptor
|
||||
static void IRAM_ATTR i2s_slc_isr(void)
|
||||
{
|
||||
ETS_SLC_INTR_DISABLE();
|
||||
|
||||
uint32_t slc_intr_status = SLCIS;
|
||||
|
||||
SLCIC = 0xFFFFFFFF;
|
||||
|
||||
if ((slc_intr_status & SLCIRXEOF) && s_this)
|
||||
{
|
||||
switch (s_this->_dmaState)
|
||||
{
|
||||
case NeoDmaState_Idle:
|
||||
break;
|
||||
|
||||
case NeoDmaState_Pending:
|
||||
{
|
||||
slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA;
|
||||
|
||||
// data block has pending data waiting to send, prepare it
|
||||
// point last state block to top
|
||||
(finished_item + 1)->next_link_ptr = s_this->_i2sBufDesc;
|
||||
|
||||
s_this->_dmaState = NeoDmaState_Sending;
|
||||
}
|
||||
break;
|
||||
|
||||
case NeoDmaState_Sending:
|
||||
{
|
||||
slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA;
|
||||
|
||||
// the data block had actual data sent
|
||||
// point last state block to first state block thus
|
||||
// just looping and not sending the data blocks
|
||||
(finished_item + 1)->next_link_ptr = finished_item;
|
||||
|
||||
s_this->_dmaState = NeoDmaState_Zeroing;
|
||||
}
|
||||
break;
|
||||
|
||||
case NeoDmaState_Zeroing:
|
||||
s_this->_dmaState = NeoDmaState_Idle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ETS_SLC_INTR_ENABLE();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T_SPEED> class NeoEsp8266DmaMethodBase : NeoEsp8266DmaMethodCore
|
||||
template<typename T_SPEED> class NeoEsp8266DmaMethodBase : NeoEsp8266I2sMethodCore
|
||||
{
|
||||
public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
@@ -306,30 +193,25 @@ public:
|
||||
NeoEsp8266DmaMethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize)
|
||||
{
|
||||
uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize;
|
||||
uint16_t dmaSettingsSize = c_dmaBytesPerPixelBytes * settingsSize;
|
||||
size_t dmaPixelSize = DmaBytesPerPixelBytes * elementSize;
|
||||
size_t dmaSettingsSize = DmaBytesPerPixelBytes * settingsSize;
|
||||
|
||||
_i2sBufferSize = pixelCount * dmaPixelSize + dmaSettingsSize;
|
||||
size_t i2sBufferSize = pixelCount * dmaPixelSize + dmaSettingsSize;
|
||||
|
||||
// normally 24 bytes creates the minimum 50us latch per spec, but
|
||||
// with the new logic, this latch is used to space between mulitple states
|
||||
// buffer size = (24 * (reset time / 50)) / 6
|
||||
size_t i2sZeroesSize = (24L * (T_SPEED::ResetTimeUs / 50L)) / 6L;
|
||||
|
||||
size_t is2BufMaxBlockSize = (c_maxDmaBlockSize / dmaPixelSize) * dmaPixelSize;
|
||||
|
||||
_data = static_cast<uint8_t*>(malloc(_sizeData));
|
||||
// data cleared later in Begin()
|
||||
|
||||
_i2sBuffer = static_cast<uint8_t*>(malloc(_i2sBufferSize));
|
||||
// no need to initialize it, it gets overwritten on every send
|
||||
|
||||
// _i2sBuffer[0] = 0b11101000; // debug, 1 bit then 0 bit
|
||||
|
||||
memset(_i2sZeroes, T_SPEED::Level, sizeof(_i2sZeroes));
|
||||
|
||||
_is2BufMaxBlockSize = (c_maxDmaBlockSize / dmaPixelSize) * dmaPixelSize;
|
||||
|
||||
_i2sBufDescCount = (_i2sBufferSize / _is2BufMaxBlockSize) + 1 + 2; // need two more for state/latch blocks
|
||||
_i2sBufDesc = (slc_queue_item*)malloc(_i2sBufDescCount * sizeof(slc_queue_item));
|
||||
|
||||
s_this = this; // store this for the ISR
|
||||
AllocateI2s(i2sBufferSize, i2sZeroesSize, is2BufMaxBlockSize, T_SPEED::IdleLevel);
|
||||
}
|
||||
|
||||
NeoEsp8266DmaMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
NeoEsp8266DmaMethodBase([[maybe_unused]] uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
NeoEsp8266DmaMethodBase(pixelCount, elementSize, settingsSize)
|
||||
{
|
||||
}
|
||||
@@ -351,129 +233,19 @@ public:
|
||||
yield();
|
||||
}
|
||||
|
||||
StopDma();
|
||||
|
||||
s_this = nullptr;
|
||||
pinMode(c_I2sPin, INPUT);
|
||||
FreeI2s();
|
||||
|
||||
free(_data);
|
||||
free(_i2sBuffer);
|
||||
free(_i2sBufDesc);
|
||||
}
|
||||
|
||||
bool IsReadyToUpdate() const
|
||||
{
|
||||
return (_dmaState == NeoDmaState_Idle);
|
||||
return IsIdle();
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
StopDma();
|
||||
|
||||
pinMode(c_I2sPin, FUNCTION_1); // I2S0_DATA
|
||||
|
||||
uint8_t* is2Buffer = _i2sBuffer;
|
||||
uint32_t is2BufferSize = _i2sBufferSize;
|
||||
uint16_t indexDesc;
|
||||
|
||||
// prepare main data block decriptors that point into our one static dma buffer
|
||||
for (indexDesc = 0; indexDesc < (_i2sBufDescCount - 2); indexDesc++)
|
||||
{
|
||||
uint32_t blockSize = (is2BufferSize > _is2BufMaxBlockSize) ? _is2BufMaxBlockSize : is2BufferSize;
|
||||
|
||||
_i2sBufDesc[indexDesc].owner = 1;
|
||||
_i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally
|
||||
_i2sBufDesc[indexDesc].sub_sof = 0;
|
||||
_i2sBufDesc[indexDesc].datalen = blockSize;
|
||||
_i2sBufDesc[indexDesc].blocksize = blockSize;
|
||||
_i2sBufDesc[indexDesc].buf_ptr = is2Buffer;
|
||||
_i2sBufDesc[indexDesc].unused = 0;
|
||||
_i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast<struct slc_queue_item*>(&(_i2sBufDesc[indexDesc + 1]));
|
||||
|
||||
is2Buffer += blockSize;
|
||||
is2BufferSize -= blockSize;
|
||||
}
|
||||
|
||||
// prepare the two state/latch descriptors
|
||||
for (; indexDesc < _i2sBufDescCount; indexDesc++)
|
||||
{
|
||||
_i2sBufDesc[indexDesc].owner = 1;
|
||||
_i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally
|
||||
_i2sBufDesc[indexDesc].sub_sof = 0;
|
||||
_i2sBufDesc[indexDesc].datalen = sizeof(_i2sZeroes);
|
||||
_i2sBufDesc[indexDesc].blocksize = sizeof(_i2sZeroes);
|
||||
_i2sBufDesc[indexDesc].buf_ptr = _i2sZeroes;
|
||||
_i2sBufDesc[indexDesc].unused = 0;
|
||||
_i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast<struct slc_queue_item*>(&(_i2sBufDesc[indexDesc + 1]));
|
||||
}
|
||||
|
||||
// the first state block will trigger the interrupt
|
||||
_i2sBufDesc[indexDesc - 2].eof = 1;
|
||||
// the last state block will loop to the first state block by defualt
|
||||
_i2sBufDesc[indexDesc - 1].next_link_ptr = reinterpret_cast<struct slc_queue_item*>(&(_i2sBufDesc[indexDesc - 2]));
|
||||
|
||||
// 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;
|
||||
|
||||
// Configure DMA
|
||||
SLCC0 &= ~(SLCMM << SLCM); // clear DMA MODE
|
||||
SLCC0 |= (1 << SLCM); // set DMA MODE to 1
|
||||
SLCRXDC |= SLCBINR | SLCBTNR; // enable INFOR_NO_REPLACE and TOKEN_NO_REPLACE
|
||||
SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); // disable RX_FILL, RX_EOF_MODE and RX_FILL_MODE
|
||||
|
||||
// Feed DMA the 1st buffer desc addr
|
||||
// To send data to the I2S subsystem, counter-intuitively we use the RXLINK part, not the TXLINK as you might
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
|
||||
ETS_SLC_INTR_ENABLE();
|
||||
|
||||
//Start transmission
|
||||
SLCTXL |= SLCTXLS;
|
||||
SLCRXL |= SLCRXLS;
|
||||
|
||||
I2S_CLK_ENABLE();
|
||||
I2SIC = 0x3F;
|
||||
I2SIE = 0;
|
||||
|
||||
//Reset I2S
|
||||
I2SC &= ~(I2SRST);
|
||||
I2SC |= I2SRST;
|
||||
I2SC &= ~(I2SRST);
|
||||
|
||||
// Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
|
||||
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM));
|
||||
I2SFC |= I2SDE; //Enable DMA
|
||||
// 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 | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
|
||||
I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (i2s_bck_div << I2SBD) | (i2s_clock_div << I2SCD);
|
||||
|
||||
I2SC |= I2STXS; // Start transmission
|
||||
InitializeI2s(T_SPEED::I2sClockDivisor, T_SPEED::I2sBaseClockDivisor);
|
||||
}
|
||||
|
||||
void IRAM_ATTR Update(bool)
|
||||
@@ -504,20 +276,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// due to encoding required for i2s, we need 4 bytes to encode the pulses
|
||||
static const uint16_t DmaBytesPerPixelBytes = 4;
|
||||
|
||||
const size_t _sizeData; // Size of '_data' buffer
|
||||
uint8_t* _data; // Holds LED color values
|
||||
|
||||
size_t _i2sBufferSize; // total size of _i2sBuffer
|
||||
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 mulitple states
|
||||
// buffer size = (24 * (reset time / 50)) / 6
|
||||
uint8_t _i2sZeroes[(24L * (T_SPEED::ResetTimeUs / 50L)) / 6L];
|
||||
|
||||
uint16_t _is2BufMaxBlockSize; // max size based on size of a pixel of a single block
|
||||
|
||||
|
||||
void FillBuffers()
|
||||
{
|
||||
uint16_t* pDma = (uint16_t*)_i2sBuffer;
|
||||
@@ -529,27 +293,6 @@ 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
|
||||
SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address
|
||||
|
||||
pinMode(c_I2sPin, INPUT);
|
||||
}
|
||||
|
||||
uint32_t getPixelTime() const
|
||||
{
|
||||
return (T_SPEED::ByteSendTimeUs * this->_sizeData);
|
||||
|
335
src/internal/NeoEsp8266I2sDmx512Method.h
Normal file
335
src/internal/NeoEsp8266I2sDmx512Method.h
Normal file
@@ -0,0 +1,335 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoPixel library helper functions for Esp8266.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include "NeoEsp8266I2sMethodCore.h"
|
||||
|
||||
|
||||
class NeoEsp8266I2sDmx512SpeedBase
|
||||
{
|
||||
public:
|
||||
// 4.2 us bit send, 250Kbps
|
||||
static const uint32_t I2sClockDivisor = 21; // 0-63
|
||||
static const uint32_t I2sBaseClockDivisor = 32; // 0-63
|
||||
static const uint32_t ByteSendTimeUs = 47; // us it takes to send a single pixel element
|
||||
static const uint32_t MtbpUs = 100; // min 88
|
||||
// DMX requires the first slot to be zero
|
||||
static const size_t HeaderSize = 1;
|
||||
};
|
||||
|
||||
class NeoEsp8266I2sDmx512Speed : public NeoEsp8266I2sDmx512SpeedBase
|
||||
{
|
||||
public:
|
||||
static const uint8_t MtbpLevel = 0x1; // high
|
||||
static const uint8_t StartBit = 0b00000000;
|
||||
static const uint8_t StopBits = 0b00000011;
|
||||
static const uint32_t BreakMab = 0x00000007; // Break + Mab
|
||||
|
||||
static uint8_t Convert(uint8_t value)
|
||||
{
|
||||
// DMX requires LSB order
|
||||
return NeoUtil::Reverse8Bits( value );
|
||||
}
|
||||
};
|
||||
|
||||
class NeoEsp8266I2sDmx512InvertedSpeed : public NeoEsp8266I2sDmx512SpeedBase
|
||||
{
|
||||
public:
|
||||
static const uint8_t MtbpLevel = 0x00; // low
|
||||
static const uint8_t StartBit = 0b00000001;
|
||||
static const uint8_t StopBits = 0b00000000;
|
||||
static const uint32_t BreakMab = 0xfffffff8; // Break + Mab
|
||||
|
||||
static uint8_t Convert(uint8_t value)
|
||||
{
|
||||
// DMX requires LSB order
|
||||
return NeoUtil::Reverse8Bits( ~value );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class NeoEsp8266I2sWs2821SpeedBase
|
||||
{
|
||||
public:
|
||||
// 1.4 us bit send, 750Kbps
|
||||
static const uint32_t I2sClockDivisor = 7; // 0-63
|
||||
static const uint32_t I2sBaseClockDivisor = 32; // 0-63
|
||||
static const uint32_t ByteSendTimeUs = 16; // us it takes to send a single pixel element
|
||||
static const uint32_t MtbpUs = 33; // min 88
|
||||
// DMX/WS2821 requires the first slot to be zero
|
||||
static const size_t HeaderSize = 1;
|
||||
};
|
||||
|
||||
class NeoEsp8266I2sWs2821Speed : public NeoEsp8266I2sWs2821SpeedBase
|
||||
{
|
||||
public:
|
||||
static const uint8_t MtbpLevel = 0x1; // high
|
||||
static const uint8_t StartBit = 0b00000000;
|
||||
static const uint8_t StopBits = 0b00000011;
|
||||
static const uint32_t BreakMab = 0x00000007; // Break + Mab
|
||||
|
||||
static uint8_t Convert(uint8_t value)
|
||||
{
|
||||
// DMX requires LSB order
|
||||
return NeoUtil::Reverse8Bits(value);
|
||||
}
|
||||
};
|
||||
|
||||
class NeoEsp8266I2sWs2821InvertedSpeed : public NeoEsp8266I2sWs2821SpeedBase
|
||||
{
|
||||
public:
|
||||
static const uint8_t MtbpLevel = 0x00; // low
|
||||
static const uint8_t StartBit = 0b00000001;
|
||||
static const uint8_t StopBits = 0b00000000;
|
||||
static const uint32_t BreakMab = 0xfffffff8; // Break + Mab
|
||||
|
||||
static uint8_t Convert(uint8_t value)
|
||||
{
|
||||
// DMX requires LSB order
|
||||
return NeoUtil::Reverse8Bits(~value);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T_SPEED> class NeoEsp8266I2sDmx512MethodBase : NeoEsp8266I2sMethodCore
|
||||
{
|
||||
public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
NeoEsp8266I2sDmx512MethodBase(uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize + T_SPEED::HeaderSize)
|
||||
{
|
||||
size_t dmaPixelBits = I2sBitsPerPixelBytes * elementSize;
|
||||
size_t dmaSettingsBits = I2sBitsPerPixelBytes * (settingsSize + T_SPEED::HeaderSize);
|
||||
|
||||
// bits + half rounding byte of bits / bits per byte
|
||||
size_t i2sBufferSize = (pixelCount * dmaPixelBits + dmaSettingsBits + 4) / 8;
|
||||
|
||||
i2sBufferSize = i2sBufferSize + sizeof(T_SPEED::BreakMab);
|
||||
|
||||
// size is rounded up to nearest I2sByteBoundarySize
|
||||
i2sBufferSize = NeoUtil::RoundUp(i2sBufferSize, I2sByteBoundarySize);
|
||||
|
||||
// 4.2 us per bit
|
||||
size_t i2sZeroesBitsSize = (T_SPEED::MtbpUs) / 4;
|
||||
size_t i2sZeroesSize = NeoUtil::RoundUp(i2sZeroesBitsSize, 8) / 8;
|
||||
|
||||
// protocol limits use of full block size to I2sByteBoundarySize
|
||||
size_t is2BufMaxBlockSize = (c_maxDmaBlockSize / I2sByteBoundarySize) * I2sByteBoundarySize;
|
||||
|
||||
_data = static_cast<uint8_t*>(malloc(_sizeData));
|
||||
// first "slot" cleared due to protocol requiring it to be zero
|
||||
memset(_data, 0x00, 1);
|
||||
|
||||
AllocateI2s(i2sBufferSize, i2sZeroesSize, is2BufMaxBlockSize, T_SPEED::MtbpLevel);
|
||||
}
|
||||
|
||||
NeoEsp8266I2sDmx512MethodBase([[maybe_unused]] uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
NeoEsp8266I2sDmx512MethodBase(pixelCount, elementSize, settingsSize)
|
||||
{
|
||||
}
|
||||
|
||||
~NeoEsp8266I2sDmx512MethodBase()
|
||||
{
|
||||
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::MtbpUs) * waits))
|
||||
{
|
||||
yield();
|
||||
}
|
||||
|
||||
FreeI2s();
|
||||
|
||||
free(_data);
|
||||
}
|
||||
|
||||
bool IsReadyToUpdate() const
|
||||
{
|
||||
return IsIdle();
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
InitializeI2s(T_SPEED::I2sClockDivisor, T_SPEED::I2sBaseClockDivisor);
|
||||
}
|
||||
|
||||
void IRAM_ATTR Update(bool)
|
||||
{
|
||||
// wait for not actively sending data
|
||||
while (!IsReadyToUpdate())
|
||||
{
|
||||
yield();
|
||||
}
|
||||
FillBuffers();
|
||||
|
||||
// toggle state so the ISR reacts
|
||||
_dmaState = NeoDmaState_Pending;
|
||||
}
|
||||
|
||||
uint8_t* getData() const
|
||||
{
|
||||
return _data + T_SPEED::HeaderSize;
|
||||
};
|
||||
|
||||
size_t getDataSize() const
|
||||
{
|
||||
return _sizeData - T_SPEED::HeaderSize;
|
||||
}
|
||||
|
||||
void applySettings([[maybe_unused]] const SettingsObject& settings)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
// given 11 sending bits per pixel byte,
|
||||
static const uint16_t I2sBitsPerPixelBytes = 11;
|
||||
// i2s sends 4 byte elements,
|
||||
static const uint16_t I2sByteBoundarySize = 4;
|
||||
|
||||
const size_t _sizeData; // Size of '_data' buffer
|
||||
uint8_t* _data; // Holds LED color values
|
||||
|
||||
// encodes the data with start and stop bits
|
||||
// input buffer is bytes
|
||||
// output stream is uint31_t
|
||||
static void Encoder(const uint8_t* pSrc, const uint8_t* pSrcEnd,
|
||||
uint32_t* pOutput, const uint32_t* pOutputEnd)
|
||||
{
|
||||
static const uint32_t Mtbp = 0xffffffff * T_SPEED::MtbpLevel;
|
||||
const uint8_t* pData = pSrc;
|
||||
|
||||
int8_t outputBit = 32;
|
||||
uint32_t output = 0;
|
||||
|
||||
// DATA stream, one start, two stop
|
||||
while (pData < pSrcEnd)
|
||||
{
|
||||
uint8_t data = T_SPEED::Convert( *(pData++) );
|
||||
|
||||
if (outputBit > 10)
|
||||
{
|
||||
// simple
|
||||
outputBit -= 1;
|
||||
output |= T_SPEED::StartBit << outputBit;
|
||||
|
||||
outputBit -= 8;
|
||||
output |= data << outputBit;
|
||||
|
||||
outputBit -= 2;
|
||||
output |= T_SPEED::StopBits << outputBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// split across an output uint32_t
|
||||
// handle start bit
|
||||
if (outputBit < 1)
|
||||
{
|
||||
*(pOutput++) = output;
|
||||
output = 0;
|
||||
outputBit += 32;
|
||||
}
|
||||
outputBit -= 1;
|
||||
output |= (T_SPEED::StartBit << outputBit);
|
||||
|
||||
// handle data bits
|
||||
if (outputBit < 8)
|
||||
{
|
||||
output |= data >> (8 - outputBit);
|
||||
|
||||
*(pOutput++) = output;
|
||||
output = 0;
|
||||
outputBit += 32;
|
||||
}
|
||||
outputBit -= 8;
|
||||
output |= data << outputBit;
|
||||
|
||||
// handle stop bits
|
||||
if (outputBit < 2)
|
||||
{
|
||||
output |= T_SPEED::StopBits >> (2 - outputBit);
|
||||
|
||||
*(pOutput++) = output;
|
||||
output = 0;
|
||||
outputBit += 32;
|
||||
}
|
||||
outputBit -= 2;
|
||||
output |= T_SPEED::StopBits << outputBit;
|
||||
}
|
||||
}
|
||||
if (outputBit > 0)
|
||||
{
|
||||
// padd last output uint32_t with Mtbp
|
||||
output |= Mtbp >> (32 - outputBit);
|
||||
*(pOutput++) = output;
|
||||
}
|
||||
// fill the rest of the output with Mtbp
|
||||
while (pOutput < pOutputEnd)
|
||||
{
|
||||
*(pOutput++) = Mtbp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FillBuffers()
|
||||
{
|
||||
uint32_t* pDma32 = reinterpret_cast<uint32_t*>(_i2sBuffer);
|
||||
const uint32_t* pDma32End = reinterpret_cast<uint32_t*>(_i2sBuffer + _i2sBufferSize);
|
||||
|
||||
// first put Break and MAB at front
|
||||
//
|
||||
// BREAK 121.8us @ 4.2us per bit
|
||||
// MAB 12.6us
|
||||
*(pDma32++) = T_SPEED::BreakMab;
|
||||
|
||||
Encoder(_data, _data + _sizeData, pDma32, pDma32End);
|
||||
}
|
||||
|
||||
uint32_t getPixelTime() const
|
||||
{
|
||||
return (T_SPEED::ByteSendTimeUs * this->_sizeData);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
// normal
|
||||
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sDmx512Speed> NeoEsp8266Dmx512Method;
|
||||
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sWs2821Speed> NeoEsp8266Ws2821Method;
|
||||
|
||||
// inverted
|
||||
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sDmx512InvertedSpeed> NeoEsp8266Dmx512InvertedMethod;
|
||||
typedef NeoEsp8266I2sDmx512MethodBase<NeoEsp8266I2sWs2821InvertedSpeed> NeoEsp8266Ws2821InvertedMethod;
|
||||
|
||||
#endif
|
@@ -25,12 +25,10 @@ License along with NeoPixel. If not, see
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "NeoSettings.h"
|
||||
#include "NeoBusChannel.h"
|
||||
#include "NeoEsp8266DmaMethod.h"
|
||||
#include "NeoEsp8266I2sMethodCore.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
|
||||
NeoEsp8266DmaMethodCore* NeoEsp8266DmaMethodCore::s_this;
|
||||
NeoEsp8266I2sMethodCore* NeoEsp8266I2sMethodCore::s_this;
|
||||
|
||||
#endif
|
325
src/internal/NeoEsp8266I2sMethodCore.h
Normal file
325
src/internal/NeoEsp8266I2sMethodCore.h
Normal file
@@ -0,0 +1,325 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoPixel library helper functions for Esp8266.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
|
||||
#include "i2s_reg.h"
|
||||
|
||||
#ifdef ARDUINO_ESP8266_MAJOR //this define was added in ESP8266 Arduino Core version v3.0.1
|
||||
#include "core_esp8266_i2s.h" //for Arduino core >= 3.0.1
|
||||
#else
|
||||
#include "i2s.h" //for Arduino core <= 3.0.0
|
||||
#endif
|
||||
|
||||
#include "eagle_soc.h"
|
||||
#include "esp8266_peri.h"
|
||||
#include "slc_register.h"
|
||||
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
#include "user_interface.h"
|
||||
|
||||
#if !defined(__CORE_ESP8266_VERSION_H) || defined(ARDUINO_ESP8266_RELEASE_2_5_0)
|
||||
void rom_i2c_writeReg_Mask(uint32_t block, uint32_t host_id, uint32_t reg_add, uint32_t Msb, uint32_t Lsb, uint32_t indata);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct slc_queue_item
|
||||
{
|
||||
uint32 blocksize : 12;
|
||||
uint32 datalen : 12;
|
||||
uint32 unused : 5;
|
||||
uint32 sub_sof : 1;
|
||||
uint32 eof : 1;
|
||||
uint32 owner : 1;
|
||||
uint8* buf_ptr;
|
||||
struct slc_queue_item* next_link_ptr;
|
||||
};
|
||||
|
||||
enum NeoDmaState
|
||||
{
|
||||
NeoDmaState_Idle,
|
||||
NeoDmaState_Pending,
|
||||
NeoDmaState_Sending,
|
||||
NeoDmaState_Zeroing,
|
||||
};
|
||||
|
||||
const uint16_t c_maxDmaBlockSize = 4095;
|
||||
|
||||
const uint8_t c_I2sPin = 3; // due to I2S hardware, the pin used is restricted to this
|
||||
|
||||
class NeoEsp8266I2sMethodCore
|
||||
{
|
||||
protected:
|
||||
static NeoEsp8266I2sMethodCore* s_this; // for the ISR
|
||||
|
||||
volatile NeoDmaState _dmaState;
|
||||
|
||||
slc_queue_item* _i2sBufDesc; // dma block descriptors
|
||||
uint16_t _i2sBufDescCount; // count of block descriptors in _i2sBufDesc
|
||||
|
||||
size_t _i2sBufferSize; // total size of _i2sBuffer
|
||||
uint8_t* _i2sBuffer; // holds the DMA buffer that is referenced by _i2sBufDesc
|
||||
|
||||
size_t _i2sZeroesSize; // total size of _i2sZeroes
|
||||
uint8_t* _i2sZeroes;
|
||||
|
||||
uint16_t _is2BufMaxBlockSize; // max size based on size of a pixel of a single block
|
||||
|
||||
|
||||
// This routine is called as soon as the DMA routine has something to tell us. All we
|
||||
// handle here is the RX_EOF_INT status, which indicate the DMA has sent a buffer whose
|
||||
// descriptor has the 'EOF' field set to 1.
|
||||
// in the case of this code, the second to last state descriptor
|
||||
static void IRAM_ATTR i2s_slc_isr(void)
|
||||
{
|
||||
ETS_SLC_INTR_DISABLE();
|
||||
|
||||
uint32_t slc_intr_status = SLCIS;
|
||||
|
||||
SLCIC = 0xFFFFFFFF;
|
||||
|
||||
if ((slc_intr_status & SLCIRXEOF) && s_this)
|
||||
{
|
||||
switch (s_this->_dmaState)
|
||||
{
|
||||
case NeoDmaState_Idle:
|
||||
break;
|
||||
|
||||
case NeoDmaState_Pending:
|
||||
{
|
||||
slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA;
|
||||
|
||||
// data block has pending data waiting to send, prepare it
|
||||
// point last state block to top
|
||||
(finished_item + 1)->next_link_ptr = s_this->_i2sBufDesc;
|
||||
|
||||
s_this->_dmaState = NeoDmaState_Sending;
|
||||
}
|
||||
break;
|
||||
|
||||
case NeoDmaState_Sending:
|
||||
{
|
||||
slc_queue_item* finished_item = (slc_queue_item*)SLCRXEDA;
|
||||
|
||||
// the data block had actual data sent
|
||||
// point last state block to first state block thus
|
||||
// just looping and not sending the data blocks
|
||||
(finished_item + 1)->next_link_ptr = finished_item;
|
||||
|
||||
s_this->_dmaState = NeoDmaState_Zeroing;
|
||||
}
|
||||
break;
|
||||
|
||||
case NeoDmaState_Zeroing:
|
||||
s_this->_dmaState = NeoDmaState_Idle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ETS_SLC_INTR_ENABLE();
|
||||
}
|
||||
|
||||
NeoEsp8266I2sMethodCore()
|
||||
{ };
|
||||
|
||||
void AllocateI2s(const size_t i2sBufferSize,
|
||||
const size_t i2sZeroesSize,
|
||||
const size_t is2BufMaxBlockSize,
|
||||
const uint8_t idleLevel)
|
||||
{
|
||||
_i2sBufferSize = i2sBufferSize;
|
||||
_i2sZeroesSize = i2sZeroesSize;
|
||||
_is2BufMaxBlockSize = is2BufMaxBlockSize;
|
||||
|
||||
_i2sBuffer = static_cast<uint8_t*>(malloc(_i2sBufferSize));
|
||||
// no need to initialize it, it gets overwritten on every send
|
||||
_i2sZeroes = static_cast<uint8_t*>(malloc(_i2sZeroesSize));
|
||||
memset(_i2sZeroes, idleLevel * 0xff, _i2sZeroesSize);
|
||||
|
||||
_i2sBufDescCount = (_i2sBufferSize / _is2BufMaxBlockSize) + 1 + 2; // need two more for state/latch blocks
|
||||
_i2sBufDesc = (slc_queue_item*)malloc(_i2sBufDescCount * sizeof(slc_queue_item));
|
||||
|
||||
s_this = this; // store this for the ISR
|
||||
}
|
||||
|
||||
void FreeI2s()
|
||||
{
|
||||
StopI2s();
|
||||
|
||||
s_this = nullptr;
|
||||
pinMode(c_I2sPin, INPUT);
|
||||
|
||||
free(_i2sBuffer);
|
||||
free(_i2sBufDesc);
|
||||
free(_i2sZeroes);
|
||||
}
|
||||
|
||||
bool IsIdle() const
|
||||
{
|
||||
return (_dmaState == NeoDmaState_Idle);
|
||||
}
|
||||
|
||||
void InitializeI2s(const uint32_t i2sClockDivisor, const uint32_t i2sBaseClockDivisor)
|
||||
{
|
||||
StopI2s();
|
||||
|
||||
pinMode(c_I2sPin, FUNCTION_1); // I2S0_DATA
|
||||
|
||||
uint8_t* is2Buffer = _i2sBuffer;
|
||||
uint32_t is2BufferSize = _i2sBufferSize;
|
||||
uint16_t indexDesc;
|
||||
|
||||
// prepare main data block decriptors that point into our one static dma buffer
|
||||
for (indexDesc = 0; indexDesc < (_i2sBufDescCount - 2); indexDesc++)
|
||||
{
|
||||
uint32_t blockSize = (is2BufferSize > _is2BufMaxBlockSize) ? _is2BufMaxBlockSize : is2BufferSize;
|
||||
|
||||
_i2sBufDesc[indexDesc].owner = 1;
|
||||
_i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally
|
||||
_i2sBufDesc[indexDesc].sub_sof = 0;
|
||||
_i2sBufDesc[indexDesc].datalen = blockSize;
|
||||
_i2sBufDesc[indexDesc].blocksize = blockSize;
|
||||
_i2sBufDesc[indexDesc].buf_ptr = is2Buffer;
|
||||
_i2sBufDesc[indexDesc].unused = 0;
|
||||
_i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast<struct slc_queue_item*>(&(_i2sBufDesc[indexDesc + 1]));
|
||||
|
||||
is2Buffer += blockSize;
|
||||
is2BufferSize -= blockSize;
|
||||
}
|
||||
|
||||
// prepare the two state/latch descriptors
|
||||
for (; indexDesc < _i2sBufDescCount; indexDesc++)
|
||||
{
|
||||
_i2sBufDesc[indexDesc].owner = 1;
|
||||
_i2sBufDesc[indexDesc].eof = 0; // no need to trigger interrupt generally
|
||||
_i2sBufDesc[indexDesc].sub_sof = 0;
|
||||
_i2sBufDesc[indexDesc].datalen = sizeof(_i2sZeroes);
|
||||
_i2sBufDesc[indexDesc].blocksize = sizeof(_i2sZeroes);
|
||||
_i2sBufDesc[indexDesc].buf_ptr = _i2sZeroes;
|
||||
_i2sBufDesc[indexDesc].unused = 0;
|
||||
_i2sBufDesc[indexDesc].next_link_ptr = reinterpret_cast<struct slc_queue_item*>(&(_i2sBufDesc[indexDesc + 1]));
|
||||
}
|
||||
|
||||
// the first state block will trigger the interrupt
|
||||
_i2sBufDesc[indexDesc - 2].eof = 1;
|
||||
// the last state block will loop to the first state block by defualt
|
||||
_i2sBufDesc[indexDesc - 1].next_link_ptr = reinterpret_cast<struct slc_queue_item*>(&(_i2sBufDesc[indexDesc - 2]));
|
||||
|
||||
// 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;
|
||||
|
||||
// Configure DMA
|
||||
SLCC0 &= ~(SLCMM << SLCM); // clear DMA MODE
|
||||
SLCC0 |= (1 << SLCM); // set DMA MODE to 1
|
||||
SLCRXDC |= SLCBINR | SLCBTNR; // enable INFOR_NO_REPLACE and TOKEN_NO_REPLACE
|
||||
SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); // disable RX_FILL, RX_EOF_MODE and RX_FILL_MODE
|
||||
|
||||
// Feed DMA the 1st buffer desc addr
|
||||
// To send data to the I2S subsystem, counter-intuitively we use the RXLINK part, not the TXLINK as you might
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
|
||||
ETS_SLC_INTR_ENABLE();
|
||||
|
||||
//Start transmission
|
||||
SLCTXL |= SLCTXLS;
|
||||
SLCRXL |= SLCRXLS;
|
||||
|
||||
I2S_CLK_ENABLE();
|
||||
I2SIC = 0x3F;
|
||||
I2SIE = 0;
|
||||
|
||||
//Reset I2S
|
||||
I2SC &= ~(I2SRST);
|
||||
I2SC |= I2SRST;
|
||||
I2SC &= ~(I2SRST);
|
||||
|
||||
// Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
|
||||
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM));
|
||||
I2SFC |= I2SDE; //Enable DMA
|
||||
// Set RX/TX CHAN_MOD=0
|
||||
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM));
|
||||
|
||||
// set the rate
|
||||
uint32_t i2s_clock_div = i2sClockDivisor & I2SCDM;
|
||||
uint8_t i2s_bck_div = i2sBaseClockDivisor & I2SBDM;
|
||||
|
||||
//!trans master, !bits mod, rece slave mod, rece msb shift, right first, msb right
|
||||
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
|
||||
}
|
||||
|
||||
void StopI2s()
|
||||
{
|
||||
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
|
||||
SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address
|
||||
|
||||
pinMode(c_I2sPin, INPUT);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP8266
|
69
src/internal/NeoUtil.h
Normal file
69
src/internal/NeoUtil.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoPixel library helper functions
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef countof
|
||||
#define countof(array) (sizeof(array)/sizeof(array[0]))
|
||||
#endif
|
||||
|
||||
class NeoUtil
|
||||
{
|
||||
private:
|
||||
static constexpr uint8_t Reverse8BitsLookup[16] = {
|
||||
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
|
||||
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf };
|
||||
|
||||
public:
|
||||
inline static uint8_t Reverse8Bits(uint8_t n)
|
||||
{
|
||||
return (Reverse8BitsLookup[n & 0b1111] << 4) | Reverse8BitsLookup[n >> 4];
|
||||
}
|
||||
|
||||
inline static size_t RoundUp(size_t numToRound, size_t multiple)
|
||||
{
|
||||
return ((numToRound + multiple - 1) / multiple) * multiple;
|
||||
}
|
||||
|
||||
// alternatives that proved to be slower but left for more periodic testing
|
||||
/*
|
||||
// marginally slower than the table
|
||||
static uint8_t Reverse8Bits(uint8_t b)
|
||||
{
|
||||
b = (b & 0b11110000) >> 4 | (b & 0b00001111) << 4;
|
||||
b = (b & 0b11001100) >> 2 | (b & 0b00110011) << 2;
|
||||
b = (b & 0b10101010) >> 1 | (b & 0b01010101) << 1;
|
||||
return b;
|
||||
}
|
||||
*/
|
||||
|
||||
/* WAY TO SLOW
|
||||
static uint8_t Reverse8Bits(uint8_t b)
|
||||
{
|
||||
return (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;
|
||||
}
|
||||
*/
|
||||
};
|
Reference in New Issue
Block a user