forked from Makuna/NeoPixelBus
Esp32 i2s jitter (#754)
* ESP32 I2s clock divider calculations improved * ESP32 I2s DMA silence block data improved
This commit is contained in:
26
extras/debug/esp32/I2sDump/I2sDump.ino
Normal file
26
extras/debug/esp32/I2sDump/I2sDump.ino
Normal file
@ -0,0 +1,26 @@
|
||||
#include <NeoPixelBus.h>
|
||||
|
||||
#if !defined(ARDUINO_ARCH_ESP32)
|
||||
@error ESP32 Supported Only
|
||||
#endif
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
while (!Serial); // wait for serial attach
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
Serial.println("I2S0: ");
|
||||
i2sDump(0);
|
||||
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
Serial.println("I2S1: ");
|
||||
i2sDump(1);
|
||||
|
||||
delay(10000);
|
||||
}
|
@ -70,10 +70,13 @@ esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t sample_rate, bool parallel_
|
||||
#endif
|
||||
|
||||
#define I2S_DMA_BLOCK_COUNT_DEFAULT 0
|
||||
// 20 bytes gives us enough time if we use single stage idle
|
||||
// But it can't be longer due to non-parrallel mode and 50us reset time
|
||||
// there just isn't enough silence at the end to fill more than 20 bytes
|
||||
#define I2S_DMA_SILENCE_SIZE 20 // 4 byte increments
|
||||
// 50us reset will calculate out as a 16 byte silence/reset buffer
|
||||
// The latest DMA buffer model this library uses will allow the
|
||||
// silence blocks to as small as they can be (4 bytes)
|
||||
// this also allows the primary data to send less of that silence since
|
||||
// the silence state control blocks (2 front, 1 back) will consume 12 bytes
|
||||
// of that reset time already
|
||||
#define I2S_DMA_SILENCE_SIZE 4 // must be in 4 byte increments
|
||||
#define I2S_DMA_SILENCE_BLOCK_COUNT_FRONT 2 // two front
|
||||
#define I2S_DMA_SILENCE_BLOCK_COUNT_BACK 1 // one back, required for non parallel
|
||||
|
||||
@ -125,13 +128,14 @@ inline void dmaItemInit(lldesc_t* item, uint8_t* posData, size_t sizeData, lldes
|
||||
item->qe.stqe_next = itemNext;
|
||||
}
|
||||
|
||||
bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize)
|
||||
bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize, bool parallel_mode, size_t bytes_per_sample)
|
||||
{
|
||||
if (bus_num >= I2S_NUM_MAX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t silenceSize = parallel_mode ? I2S_DMA_SILENCE_SIZE * 8 * bytes_per_sample : I2S_DMA_SILENCE_SIZE;
|
||||
size_t dmaCount = I2S[bus_num].dma_count;
|
||||
|
||||
if (I2S[bus_num].dma_items == NULL)
|
||||
@ -148,15 +152,21 @@ bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize)
|
||||
lldesc_t* item = itemFirst;
|
||||
// lldesc_t* itemsEnd = itemFirst + I2S[bus_num].dma_count;
|
||||
lldesc_t* itemNext = item + 1;
|
||||
size_t dataLeft = dataSize;
|
||||
// The primary data to map will excludes the time to process through the
|
||||
// 3 silence control blocks.
|
||||
// That data at the end is already silent reset time and no need to send it twice as
|
||||
// part of the data and the control blocks
|
||||
// makes the reset more accurate
|
||||
size_t dataLeft = dataSize - (silenceSize *
|
||||
(I2S_DMA_SILENCE_BLOCK_COUNT_FRONT + I2S_DMA_SILENCE_BLOCK_COUNT_BACK));
|
||||
uint8_t* pos = data;
|
||||
// at the end of the data is the encoded silence reset
|
||||
uint8_t* posSilence = data + dataSize - I2S_DMA_SILENCE_SIZE;
|
||||
// at the end of the data is the encoded silence reset, use it
|
||||
uint8_t* posSilence = data + dataSize - silenceSize;
|
||||
|
||||
// front two are silent items used for looping to micmic single fire
|
||||
// default to looping
|
||||
dmaItemInit(item, posSilence, I2S_DMA_SILENCE_SIZE, itemNext);
|
||||
dmaItemInit(itemNext, posSilence, I2S_DMA_SILENCE_SIZE, item);
|
||||
dmaItemInit(item, posSilence, silenceSize, itemNext);
|
||||
dmaItemInit(itemNext, posSilence, silenceSize, item);
|
||||
item = itemNext;
|
||||
itemNext++;
|
||||
|
||||
@ -184,7 +194,7 @@ bool i2sInitDmaItems(uint8_t bus_num, uint8_t* data, size_t dataSize)
|
||||
|
||||
// last block, the back silent item, loops to front
|
||||
item = itemNext;
|
||||
dmaItemInit(item, posSilence, I2S_DMA_SILENCE_SIZE, itemFirst);
|
||||
dmaItemInit(item, posSilence, silenceSize, itemFirst);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -202,14 +212,12 @@ bool i2sDeinitDmaItems(uint8_t bus_num)
|
||||
return true;
|
||||
}
|
||||
|
||||
// normal 4, 10, 63, 12, 16
|
||||
|
||||
esp_err_t i2sSetClock(uint8_t bus_num,
|
||||
uint8_t div_num, // 4 13
|
||||
uint8_t div_b, // 10 20
|
||||
uint8_t div_a, // 63 63
|
||||
uint8_t bck, // 12 60 or 7
|
||||
uint8_t bits) // 16 8
|
||||
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)
|
||||
{
|
||||
@ -234,6 +242,7 @@ esp_err_t i2sSetClock(uint8_t bus_num,
|
||||
clkm_conf.clk_sel = 2; // APPL = 1 APB = 2
|
||||
clkm_conf.clk_en = 1; // examples of i2s show this being set if sel is set to 2
|
||||
#else
|
||||
clkm_conf.clk_en = 1;
|
||||
clkm_conf.clka_en = 0;
|
||||
#endif
|
||||
|
||||
@ -405,7 +414,7 @@ void i2sInit(uint8_t bus_num,
|
||||
I2S_DMA_SILENCE_BLOCK_COUNT_FRONT +
|
||||
I2S_DMA_SILENCE_BLOCK_COUNT_BACK;
|
||||
|
||||
if (!i2sInitDmaItems(bus_num, data, dataSize))
|
||||
if (!i2sInitDmaItems(bus_num, data, dataSize, parallel_mode, bytes_per_sample))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -476,7 +485,7 @@ void i2sInit(uint8_t bus_num,
|
||||
typeof(i2s->fifo_conf) fifo_conf;
|
||||
|
||||
fifo_conf.val = 0;
|
||||
fifo_conf.rx_fifo_mod_force_en = 1;
|
||||
fifo_conf.rx_fifo_mod_force_en = 0;
|
||||
fifo_conf.tx_fifo_mod_force_en = 1;
|
||||
fifo_conf.tx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel
|
||||
fifo_conf.rx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel
|
||||
@ -486,10 +495,13 @@ void i2sInit(uint8_t bus_num,
|
||||
i2s->fifo_conf.val = fifo_conf.val;
|
||||
}
|
||||
|
||||
// $REVIEW old code didn't set this
|
||||
{
|
||||
typeof(i2s->conf1) conf1;
|
||||
conf1.val = 0;
|
||||
|
||||
conf1.tx_pcm_conf = 1;
|
||||
conf1.rx_pcm_bypass = 1;
|
||||
|
||||
conf1.tx_stop_en = 0;
|
||||
conf1.tx_pcm_bypass = 1;
|
||||
i2s->conf1.val = conf1.val;
|
||||
@ -534,7 +546,8 @@ void i2sInit(uint8_t bus_num,
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// (I2S_NUM_MAX == 2)
|
||||
if (bus_num == 1) {
|
||||
if (bus_num == 1)
|
||||
{
|
||||
i2sIntSource = ETS_I2S1_INTR_SOURCE;
|
||||
}
|
||||
else
|
||||
@ -553,8 +566,8 @@ void i2sInit(uint8_t bus_num,
|
||||
{
|
||||
typeof(i2s->lc_conf) lc_conf;
|
||||
lc_conf.val = 0;
|
||||
lc_conf.out_data_burst_en = 1;
|
||||
lc_conf.indscr_burst_en = 1;
|
||||
lc_conf.out_data_burst_en = 0; // ?
|
||||
lc_conf.indscr_burst_en = 0; // ?
|
||||
i2s->lc_conf.val = lc_conf.val;
|
||||
}
|
||||
/* */
|
||||
@ -572,32 +585,147 @@ void i2sDeinit(uint8_t bus_num)
|
||||
i2sDeinitDmaItems(bus_num);
|
||||
}
|
||||
|
||||
esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, bool parallel_mode, size_t bytes_per_sample)
|
||||
void i2sUnitDecimalToFractionClks(uint8_t* resultN,
|
||||
uint8_t* resultD,
|
||||
double unitDecimal,
|
||||
double accuracy)
|
||||
{
|
||||
if (unitDecimal <= accuracy)
|
||||
{
|
||||
// no fractional
|
||||
*resultN = 0;
|
||||
*resultD = 1;
|
||||
return;
|
||||
}
|
||||
else if (unitDecimal <= (1.0 / 63.0))
|
||||
{
|
||||
// lowest fractional
|
||||
*resultN = 0;
|
||||
*resultD = 2;
|
||||
return;
|
||||
}
|
||||
else if (unitDecimal >= (62.0 / 63.0))
|
||||
{
|
||||
// highest fractional
|
||||
*resultN = 2;
|
||||
*resultD = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("\nSearching for %f\n", unitDecimal);
|
||||
|
||||
// The lower fraction is 0 / 1
|
||||
uint16_t lowerN = 0;
|
||||
uint16_t lowerD = 1;
|
||||
double lowerDelta = unitDecimal;
|
||||
|
||||
// The upper fraction is 1 / 1
|
||||
uint16_t upperN = 1;
|
||||
uint16_t upperD = 1;
|
||||
double upperDelta = 1.0 - unitDecimal;
|
||||
|
||||
uint16_t closestN = 0;
|
||||
uint16_t closestD = 1;
|
||||
double closestDelta = lowerDelta;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// The middle fraction is
|
||||
// (lowerN + upperN) / (lowerD + upperD)
|
||||
uint16_t middleN = lowerN + upperN;
|
||||
uint16_t middleD = lowerD + upperD;
|
||||
double middleUnit = (double)middleN / middleD;
|
||||
|
||||
if (middleD > 63)
|
||||
{
|
||||
// exceeded our clock bits so break out
|
||||
// and use closest we found so far
|
||||
break;
|
||||
}
|
||||
|
||||
if (middleD * (unitDecimal + accuracy) < middleN)
|
||||
{
|
||||
// middle is our new upper
|
||||
upperN = middleN;
|
||||
upperD = middleD;
|
||||
upperDelta = middleUnit - unitDecimal;
|
||||
}
|
||||
else if (middleN < (unitDecimal - accuracy) * middleD)
|
||||
{
|
||||
// middle is our new lower
|
||||
lowerN = middleN;
|
||||
lowerD = middleD;
|
||||
lowerDelta = unitDecimal - middleUnit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// middle is our best fraction
|
||||
*resultN = middleN;
|
||||
*resultD = middleD;
|
||||
|
||||
// printf(" Match %d/%d = %f (%f)\n", middleN, middleD, middleUnit, unitDecimal - middleUnit);
|
||||
return;
|
||||
}
|
||||
|
||||
// track the closest fraction so far (ONLY THE UPPER, so allow only slower Kbps)
|
||||
//
|
||||
//if (upperDelta < lowerDelta)
|
||||
{
|
||||
if (upperDelta < closestDelta)
|
||||
{
|
||||
closestN = upperN;
|
||||
closestD = upperD;
|
||||
closestDelta = upperDelta;
|
||||
|
||||
// printf(" Upper %d/%d = %f (%f)\n", closestN, closestD, middleUnit, closestDelta);
|
||||
}
|
||||
}
|
||||
/*
|
||||
else
|
||||
{
|
||||
if (lowerDelta < closestDelta)
|
||||
{
|
||||
closestN = lowerN;
|
||||
closestD = lowerD;
|
||||
closestDelta = lowerDelta;
|
||||
|
||||
printf(" Lower %d/%d = %f (%f)\n", closestN, closestD, middleUnit, closestDelta);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// printf(" Closest %d/%d = %f (%f)\n\n", closestN, closestD, (double)closestN / closestD, closestDelta);
|
||||
// no perfect match, use the closest we found
|
||||
//
|
||||
*resultN = closestN;
|
||||
*resultD = closestD;
|
||||
}
|
||||
|
||||
esp_err_t i2sSetSampleRate(uint8_t bus_num,
|
||||
uint32_t rate,
|
||||
bool parallel_mode,
|
||||
size_t bytes_per_sample)
|
||||
{
|
||||
if (bus_num >= I2S_NUM_MAX)
|
||||
{
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
uint8_t bck = 12;
|
||||
uint8_t bck = 8;
|
||||
|
||||
// parallel mode needs a higher sample rate
|
||||
//
|
||||
if (parallel_mode)
|
||||
{
|
||||
rate *= bytes_per_sample;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
rate *= bytes_per_sample;
|
||||
bck *= bytes_per_sample;
|
||||
|
||||
//rate /= bytes_per_sample;
|
||||
//bck /= bytes_per_sample;
|
||||
#else
|
||||
rate *= bytes_per_sample;
|
||||
#endif
|
||||
}
|
||||
|
||||
// 160,000,000L / (100,000 * 384)
|
||||
double clkmdiv = (double)I2S_BASE_CLK / ((rate * 384) + 1);
|
||||
double clkmdiv = (double)I2S_BASE_CLK / (rate * 256.0);
|
||||
|
||||
if (clkmdiv > 256.0)
|
||||
{
|
||||
log_e("rate is too low");
|
||||
@ -616,9 +744,18 @@ esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, bool parallel_mode, s
|
||||
// calc integer and franctional for more precise timing
|
||||
//
|
||||
uint8_t clkmInteger = clkmdiv;
|
||||
uint8_t clkmFraction = (clkmdiv - clkmInteger) * 63.0;
|
||||
double clkmFraction = (clkmdiv - clkmInteger);
|
||||
uint8_t divB = 0;
|
||||
uint8_t divA = 0;
|
||||
|
||||
i2sSetClock(bus_num, clkmInteger, clkmFraction, 63, bck, bytes_per_sample * 8);
|
||||
i2sUnitDecimalToFractionClks(&divB, &divA, clkmFraction, 0.000001);
|
||||
|
||||
i2sSetClock(bus_num,
|
||||
clkmInteger,
|
||||
divB,
|
||||
divA,
|
||||
bck,
|
||||
bytes_per_sample * 8);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -629,7 +766,6 @@ void IRAM_ATTR i2sDmaISR(void* arg)
|
||||
|
||||
if (i2s->bus->int_st.out_eof)
|
||||
{
|
||||
// lldesc_t* item = (lldesc_t*)(i2s->bus->out_eof_des_addr);
|
||||
if (i2s->is_sending_data != I2s_Is_Idle)
|
||||
{
|
||||
// the second item (last of the two front silent items) is
|
||||
@ -666,6 +802,235 @@ bool i2sWrite(uint8_t bus_num)
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef NEOPIXELBUS_I2S_DEBUG
|
||||
void DumpI2sPrimary(const char* label, uint32_t val)
|
||||
{
|
||||
printf("%s %08x\n", label, val);
|
||||
}
|
||||
|
||||
void DumpI2sSecondary(const char* label, uint32_t val)
|
||||
{
|
||||
printf(" %s %u\n", label, val);
|
||||
}
|
||||
|
||||
void DumpI2s_sample_rate_conf(const char* label, i2s_dev_t* bus)
|
||||
{
|
||||
typeof(bus->sample_rate_conf) val;
|
||||
|
||||
val.val = bus->sample_rate_conf.val;
|
||||
|
||||
DumpI2sPrimary(label, val.val);
|
||||
|
||||
DumpI2sSecondary("tx_bck_div_num : ", val.tx_bck_div_num);
|
||||
DumpI2sSecondary("rx_bck_div_num : ", val.rx_bck_div_num);
|
||||
DumpI2sSecondary("tx_bits_mod : ", val.tx_bits_mod);
|
||||
DumpI2sSecondary("rx_bits_mod : ", val.rx_bits_mod);
|
||||
}
|
||||
|
||||
void DumpI2s_conf1(const char* label, i2s_dev_t* bus)
|
||||
{
|
||||
typeof(bus->conf1) val;
|
||||
val.val = bus->conf1.val;
|
||||
|
||||
DumpI2sPrimary(label, val.val);
|
||||
|
||||
DumpI2sSecondary("tx_pcm_conf : ", val.tx_pcm_conf);
|
||||
DumpI2sSecondary("tx_pcm_bypass : ", val.tx_pcm_bypass);
|
||||
DumpI2sSecondary("rx_pcm_conf : ", val.rx_pcm_conf);
|
||||
DumpI2sSecondary("rx_pcm_bypass : ", val.rx_pcm_bypass);
|
||||
DumpI2sSecondary("tx_stop_en : ", val.tx_stop_en);
|
||||
DumpI2sSecondary("tx_zeros_rm_en : ", val.tx_zeros_rm_en);
|
||||
}
|
||||
|
||||
void DumpI2s_clkm_conf(const char* label, i2s_dev_t* bus)
|
||||
{
|
||||
typeof(bus->clkm_conf) val;
|
||||
val.val = bus->clkm_conf.val;
|
||||
|
||||
DumpI2sPrimary(label, val.val);
|
||||
|
||||
DumpI2sSecondary("clkm_div_num : ", val.clkm_div_num);
|
||||
DumpI2sSecondary("clkm_div_b : ", val.clkm_div_b);
|
||||
DumpI2sSecondary("clkm_div_a : ", val.clkm_div_a);
|
||||
DumpI2sSecondary("clk_en : ", val.clk_en);
|
||||
DumpI2sSecondary("clka_en : ", val.clka_en);
|
||||
}
|
||||
|
||||
void DumpI2s_lc_conf(const char* label, i2s_dev_t* bus)
|
||||
{
|
||||
typeof(bus->lc_conf) val;
|
||||
val.val = bus->lc_conf.val;
|
||||
|
||||
DumpI2sPrimary(label, val.val);
|
||||
|
||||
DumpI2sSecondary("out_auto_wrback : ", val.out_auto_wrback);
|
||||
DumpI2sSecondary("out_no_restart_clr : ", val.out_no_restart_clr);
|
||||
DumpI2sSecondary("out_eof_mode : ", val.out_eof_mode);
|
||||
DumpI2sSecondary("outdscr_burst_en : ", val.outdscr_burst_en);
|
||||
DumpI2sSecondary("indscr_burst_en : ", val.indscr_burst_en);
|
||||
DumpI2sSecondary("out_data_burst_en : ", val.out_data_burst_en);
|
||||
DumpI2sSecondary("check_owner : ", val.check_owner);
|
||||
DumpI2sSecondary("mem_trans_en : ", val.mem_trans_en);
|
||||
}
|
||||
|
||||
void DumpI2s_conf(const char* label, i2s_dev_t* bus)
|
||||
{
|
||||
typeof(bus->conf) val;
|
||||
val.val = bus->conf.val;
|
||||
|
||||
DumpI2sPrimary(label, val.val);
|
||||
|
||||
DumpI2sSecondary("tx_slave_mod : ", val.tx_slave_mod);
|
||||
DumpI2sSecondary("rx_slave_mod : ", val.rx_slave_mod);
|
||||
DumpI2sSecondary("tx_right_first : ", val.tx_right_first);
|
||||
DumpI2sSecondary("rx_right_first : ", val.rx_right_first);
|
||||
DumpI2sSecondary("tx_msb_shift : ", val.tx_msb_shift);
|
||||
DumpI2sSecondary("rx_msb_shift : ", val.rx_msb_shift);
|
||||
DumpI2sSecondary("tx_short_sync : ", val.tx_short_sync);
|
||||
DumpI2sSecondary("rx_short_sync : ", val.rx_short_sync);
|
||||
DumpI2sSecondary("tx_mono : ", val.tx_mono);
|
||||
DumpI2sSecondary("rx_mono : ", val.rx_mono);
|
||||
DumpI2sSecondary("tx_msb_right : ", val.tx_msb_right);
|
||||
DumpI2sSecondary("rx_msb_right : ", val.rx_msb_right);
|
||||
DumpI2sSecondary("sig_loopback : ", val.sig_loopback);
|
||||
}
|
||||
|
||||
void DumpI2s_fifo_conf(const char* label, i2s_dev_t* bus)
|
||||
{
|
||||
typeof(bus->fifo_conf) val;
|
||||
val.val = bus->fifo_conf.val;
|
||||
|
||||
DumpI2sPrimary(label, val.val);
|
||||
|
||||
DumpI2sSecondary("rx_data_num : ", val.rx_data_num);
|
||||
DumpI2sSecondary("tx_data_num : ", val.tx_data_num);
|
||||
DumpI2sSecondary("dscr_en : ", val.dscr_en);
|
||||
DumpI2sSecondary("tx_fifo_mod : ", val.tx_fifo_mod);
|
||||
DumpI2sSecondary("rx_fifo_mod : ", val.rx_fifo_mod);
|
||||
DumpI2sSecondary("tx_fifo_mod_force_en : ", val.tx_fifo_mod_force_en);
|
||||
DumpI2sSecondary("rx_fifo_mod_force_en : ", val.rx_fifo_mod_force_en);
|
||||
}
|
||||
|
||||
bool i2sDump(uint8_t bus_num)
|
||||
{
|
||||
if (bus_num >= I2S_NUM_MAX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
i2s_dev_t* i2s = I2S[bus_num].bus;
|
||||
|
||||
DumpI2sPrimary("fifo_wr: ", i2s->fifo_wr);
|
||||
DumpI2sPrimary("fifo_rd: ", i2s->fifo_rd);
|
||||
DumpI2s_conf("conf: ", i2s);
|
||||
DumpI2sPrimary("int_raw: ", i2s->int_raw.val);
|
||||
DumpI2sPrimary("int_st: ", i2s->int_st.val);
|
||||
DumpI2sPrimary("int_ena: ", i2s->int_ena.val);
|
||||
DumpI2sPrimary("int_clr: ", i2s->int_clr.val);
|
||||
DumpI2sPrimary("timing: ", i2s->timing.val);
|
||||
DumpI2s_fifo_conf("fifo_conf: ", i2s);
|
||||
DumpI2sPrimary("rx_eof_num: ", i2s->rx_eof_num);
|
||||
DumpI2sPrimary("conf_single_data: ", i2s->conf_single_data);
|
||||
DumpI2sPrimary("conf_chan: ", i2s->conf_chan.val);
|
||||
DumpI2sPrimary("out_link: ", i2s->out_link.val);
|
||||
DumpI2sPrimary("in_link: ", i2s->in_link.val);
|
||||
DumpI2sPrimary("out_eof_des_addr: ", i2s->out_eof_des_addr);
|
||||
DumpI2sPrimary("in_eof_des_addr: ", i2s->in_eof_des_addr);
|
||||
DumpI2sPrimary("out_eof_bfr_des_addr: ", i2s->out_eof_bfr_des_addr);
|
||||
DumpI2sPrimary("ahb_test: ", i2s->ahb_test.val);
|
||||
|
||||
DumpI2sPrimary("in_link_dscr: ", i2s->in_link_dscr);
|
||||
DumpI2sPrimary("in_link_dscr_bf0: ", i2s->in_link_dscr_bf0);
|
||||
DumpI2sPrimary("in_link_dscr_bf1: ", i2s->in_link_dscr_bf1);
|
||||
DumpI2sPrimary("out_link_dscr: ", i2s->out_link_dscr);
|
||||
DumpI2sPrimary("out_link_dscr_bf0: ", i2s->out_link_dscr_bf0);
|
||||
DumpI2sPrimary("out_link_dscr_bf1: ", i2s->out_link_dscr_bf1);
|
||||
|
||||
DumpI2s_lc_conf("lc_conf: ", i2s);
|
||||
|
||||
DumpI2sPrimary("out_fifo_push: ", i2s->out_fifo_push.val);
|
||||
DumpI2sPrimary("in_fifo_pop: ", i2s->in_fifo_pop.val);
|
||||
|
||||
DumpI2sPrimary("lc_state0: ", i2s->lc_state0);
|
||||
DumpI2sPrimary("lc_state1: ", i2s->lc_state1);
|
||||
DumpI2sPrimary("lc_hung_conf: ", i2s->lc_hung_conf.val);
|
||||
|
||||
DumpI2sPrimary("reserved_78: ", i2s->reserved_78);
|
||||
DumpI2sPrimary("reserved_7c: ", i2s->reserved_7c);
|
||||
|
||||
DumpI2sPrimary("cvsd_conf0: ", i2s->cvsd_conf0.val);
|
||||
DumpI2sPrimary("cvsd_conf1: ", i2s->cvsd_conf1.val);
|
||||
DumpI2sPrimary("cvsd_conf2: ", i2s->cvsd_conf2.val);
|
||||
|
||||
DumpI2sPrimary("plc_conf0: ", i2s->plc_conf0.val);
|
||||
DumpI2sPrimary("plc_conf1: ", i2s->plc_conf1.val);
|
||||
DumpI2sPrimary("plc_conf2: ", i2s->plc_conf2.val);
|
||||
|
||||
DumpI2sPrimary("esco_conf0: ", i2s->esco_conf0.val);
|
||||
DumpI2sPrimary("sco_conf0: ", i2s->sco_conf0.val);
|
||||
|
||||
DumpI2s_conf1("conf1: ", i2s);
|
||||
|
||||
DumpI2sPrimary("pd_conf: ", i2s->pd_conf.val);
|
||||
DumpI2sPrimary("conf2: ", i2s->conf2.val);
|
||||
|
||||
|
||||
DumpI2s_clkm_conf("clkm_conf: ", i2s);
|
||||
DumpI2s_sample_rate_conf("sample_rate_conf: ", i2s);
|
||||
|
||||
DumpI2sPrimary("pdm_conf: ", i2s->pdm_conf.val);
|
||||
DumpI2sPrimary("pdm_freq_conf: ", i2s->pdm_freq_conf.val);
|
||||
|
||||
DumpI2sPrimary("state: ", i2s->state.val);
|
||||
|
||||
/*
|
||||
uint32_t reserved_c0;
|
||||
uint32_t reserved_c4;
|
||||
uint32_t reserved_c8;
|
||||
uint32_t reserved_cc;
|
||||
uint32_t reserved_d0;
|
||||
uint32_t reserved_d4;
|
||||
uint32_t reserved_d8;
|
||||
uint32_t reserved_dc;
|
||||
uint32_t reserved_e0;
|
||||
uint32_t reserved_e4;
|
||||
uint32_t reserved_e8;
|
||||
uint32_t reserved_ec;
|
||||
uint32_t reserved_f0;
|
||||
uint32_t reserved_f4;
|
||||
uint32_t reserved_f8;
|
||||
*/
|
||||
DumpI2sPrimary("date: ", i2s->date);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool i2sGetClks(uint8_t bus_num,
|
||||
uint8_t* clkm_div_num,
|
||||
uint8_t* clkm_div_b,
|
||||
uint8_t* clkm_div_a)
|
||||
{
|
||||
if (bus_num >= I2S_NUM_MAX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!clkm_div_num || !clkm_div_b || !clkm_div_a)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
i2s_dev_t* i2s = I2S[bus_num].bus;
|
||||
|
||||
typeof(i2s->clkm_conf) val;
|
||||
val.val = i2s->clkm_conf.val;
|
||||
|
||||
*clkm_div_num = val.clkm_div_num;
|
||||
*clkm_div_b = val.clkm_div_b;
|
||||
*clkm_div_a = val.clkm_div_a;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#endif // defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
|
@ -12,11 +12,18 @@ extern "C" {
|
||||
#define I2S_DMA_MAX_DATA_LEN 4092// maximum bytes in one dma item
|
||||
|
||||
typedef enum {
|
||||
I2S_CHAN_STEREO, I2S_CHAN_RIGHT_TO_LEFT, I2S_CHAN_LEFT_TO_RIGHT, I2S_CHAN_RIGHT_ONLY, I2S_CHAN_LEFT_ONLY
|
||||
I2S_CHAN_STEREO,
|
||||
I2S_CHAN_RIGHT_TO_LEFT,
|
||||
I2S_CHAN_LEFT_TO_RIGHT,
|
||||
I2S_CHAN_RIGHT_ONLY,
|
||||
I2S_CHAN_LEFT_ONLY
|
||||
} i2s_tx_chan_mod_t;
|
||||
|
||||
typedef enum {
|
||||
I2S_FIFO_16BIT_DUAL, I2S_FIFO_16BIT_SINGLE, I2S_FIFO_32BIT_DUAL, I2S_FIFO_32BIT_SINGLE
|
||||
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,
|
||||
@ -41,6 +48,15 @@ void i2sSetClkWsPins(uint8_t bus_num,
|
||||
bool invertWs);
|
||||
bool i2sWrite(uint8_t bus_num);
|
||||
bool i2sWriteDone(uint8_t bus_num);
|
||||
#ifdef NEOPIXELBUS_I2S_DEBUG
|
||||
bool i2sDump(uint8_t bus_num);
|
||||
bool i2sGetClks(uint8_t bus_num, uint8_t* clkm_div_num, uint8_t* clkm_div_b, uint8_t* clkm_div_a );
|
||||
|
||||
void i2sUnitDecimalToFractionClks(uint8_t* resultN,
|
||||
uint8_t* resultD,
|
||||
double unitDecimal,
|
||||
double accuracy);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ public:
|
||||
class NeoEsp32I2sSpeedApa106
|
||||
{
|
||||
public:
|
||||
const static uint32_t I2sSampleRate = 76000;
|
||||
const static uint32_t I2sSampleRate = 72960; // 588,235 hz / 8 = 73,529 sample rate
|
||||
const static uint16_t ByteSendTimeUs = 14;
|
||||
const static uint16_t ResetTimeUs = 50;
|
||||
};
|
||||
@ -147,19 +147,19 @@ template<typename T_SPEED, typename T_BUS, typename T_INVERT> class NeoEsp32I2sM
|
||||
public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
NeoEsp32I2sMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize),
|
||||
NeoEsp32I2sMethodBase(uint8_t pin, uint16_t pixelCount, size_t pixelSize, size_t settingsSize) :
|
||||
_sizeData(pixelCount * pixelSize + settingsSize),
|
||||
_pin(pin)
|
||||
{
|
||||
construct(pixelCount, elementSize, settingsSize);
|
||||
construct(pixelCount, pixelSize, settingsSize);
|
||||
}
|
||||
|
||||
NeoEsp32I2sMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize),
|
||||
NeoEsp32I2sMethodBase(uint8_t pin, uint16_t pixelCount, size_t pixelSize, size_t settingsSize, NeoBusChannel channel) :
|
||||
_sizeData(pixelCount * pixelSize + settingsSize),
|
||||
_pin(pin),
|
||||
_bus(channel)
|
||||
{
|
||||
construct(pixelCount, elementSize, settingsSize);
|
||||
construct(pixelCount, pixelSize, settingsSize);
|
||||
}
|
||||
|
||||
~NeoEsp32I2sMethodBase()
|
||||
@ -242,30 +242,21 @@ private:
|
||||
size_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)
|
||||
void construct(uint16_t pixelCount, size_t pixelSize, size_t settingsSize)
|
||||
{
|
||||
// DMA is too fast to support a single pixel and maintain consistency
|
||||
if (pixelCount < 2)
|
||||
{
|
||||
pixelCount = 2;
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
// must have a 4 byte aligned buffer for i2s
|
||||
// since the reset/silence at the end is used for looping
|
||||
// it also needs to 4 byte aligned
|
||||
size_t dmaSettingsSize = c_dmaBytesPerPixelBytes * settingsSize;
|
||||
size_t dmaPixelSize = c_dmaBytesPerPixelBytes * pixelSize;
|
||||
size_t resetSize = NeoUtil::RoundUp(c_dmaBytesPerPixelBytes * T_SPEED::ResetTimeUs / T_SPEED::ByteSendTimeUs, 4);
|
||||
|
||||
_i2sBufferSize = NeoUtil::RoundUp(pixelCount * dmaPixelSize + dmaSettingsSize, 4) +
|
||||
resetSize;
|
||||
|
||||
_i2sBuffer = static_cast<uint8_t*>(heap_caps_malloc(_i2sBufferSize, MALLOC_CAP_DMA));
|
||||
// no need to initialize all of it, but since it contains
|
||||
// "reset" bits that don't latter get overwritten we just clear it all
|
||||
|
@ -40,7 +40,10 @@ class PixieStreamMethod
|
||||
public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
PixieStreamMethod(uint16_t pixelCount, size_t elementSize, size_t settingsSize, Stream* pixieStream) :
|
||||
PixieStreamMethod(uint16_t pixelCount,
|
||||
size_t elementSize,
|
||||
size_t settingsSize,
|
||||
Stream* pixieStream) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize),
|
||||
_stream(pixieStream),
|
||||
_usEndTime(0)
|
||||
|
Reference in New Issue
Block a user