Esp32 i2s (#427)

* dynamic i2s bus numbers

* stabile and consistent shows

* faster to start send
This commit is contained in:
Michael Miller
2021-02-11 17:22:10 -08:00
committed by GitHub
parent 7a73ffc7b4
commit 13b7d2e377
4 changed files with 199 additions and 129 deletions

View File

@@ -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
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

View File

@@ -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; i<I2S[bus_num].dma_count; i++) {
i2 = (i+1) % I2S[bus_num].dma_count;
for(i=0; i< dmaCount; i++) {
itemPrev = item;
i2 = (i+1) % dmaCount;
item = &I2S[bus_num].dma_items[i];
item->eof = 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; a<i; a++) {
free(I2S[bus_num].dma_items[i].buf);
}
free(I2S[bus_num].dma_items);
I2S[bus_num].dma_items = NULL;
return false;
}
} else {
item->buf = 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;
}

View File

@@ -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);

View File

@@ -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<uint8_t>(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<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
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<uint8_t*>(malloc(_sizeData));
// data cleared later in Begin()
_i2sBuffer = static_cast<uint8_t*>(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<NeoEsp32I2sSpeed800Kbps, NeoEsp32I2sBusOne, NeoEsp
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed400Kbps, NeoEsp32I2sBusOne, NeoEsp32I2sInverted> NeoEsp32I2s1400KbpsInvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedApa106, NeoEsp32I2sBusOne, NeoEsp32I2sInverted> NeoEsp32I2s1Apa106InvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedWs2812x, NeoEsp32I2sBusN, NeoEsp32I2sNotInverted> NeoEsp32I2sNWs2812xMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedSk6812, NeoEsp32I2sBusN, NeoEsp32I2sNotInverted> NeoEsp32I2sNSk6812Method;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedTm1814, NeoEsp32I2sBusN, NeoEsp32I2sInverted> NeoEsp32I2sNTm1814Method;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed800Kbps, NeoEsp32I2sBusN, NeoEsp32I2sNotInverted> NeoEsp32I2sN800KbpsMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed400Kbps, NeoEsp32I2sBusN, NeoEsp32I2sNotInverted> NeoEsp32I2sN400KbpsMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedApa106, NeoEsp32I2sBusN, NeoEsp32I2sNotInverted> NeoEsp32I2sNApa106Method;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedWs2812x, NeoEsp32I2sBusN, NeoEsp32I2sInverted> NeoEsp32I2sNWs2812xInvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedSk6812, NeoEsp32I2sBusN, NeoEsp32I2sInverted> NeoEsp32I2sNSk6812InvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedTm1814, NeoEsp32I2sBusN, NeoEsp32I2sNotInverted> NeoEsp32I2sNTm1814InvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed800Kbps, NeoEsp32I2sBusN, NeoEsp32I2sInverted> NeoEsp32I2sN800KbpsInvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed400Kbps, NeoEsp32I2sBusN, NeoEsp32I2sInverted> NeoEsp32I2sN400KbpsInvertedMethod;
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedApa106, NeoEsp32I2sBusN, NeoEsp32I2sInverted> NeoEsp32I2sNApa106InvertedMethod;
#endif
/* due to a core issue where requests to send aren't consistent, I2s is no longer the default