From a2d93a27bbce125283a02f21f3236d00996672cd Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 20 Mar 2018 11:43:19 -0700 Subject: [PATCH 01/13] Migrate to i2s_struct_t to make I2S RX simpler Much of the I2S TX infrastructure can be used to handle I2S input, so move the per-channel data structures to a struct for easier changes. --- cores/esp8266/core_esp8266_i2s.c | 184 +++++++++++++++++++------------ 1 file changed, 115 insertions(+), 69 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 4251a7036a..b89ce0d9b3 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -39,43 +39,78 @@ extern void ets_wdt_disable(void); //always a buffer that is being used by the DMA subsystem *right now* and we don't want to be able to write to that //simultaneously. -struct slc_queue_item { - uint32 blocksize:12; - uint32 datalen:12; - uint32 unused:5; - uint32 sub_sof:1; - uint32 eof:1; - uint32 owner:1; - uint32 buf_ptr; - uint32 next_link_ptr; -}; - -static uint32_t i2s_slc_queue[SLC_BUF_CNT-1]; -static uint8_t i2s_slc_queue_len; -static uint32_t *i2s_slc_buf_pntr[SLC_BUF_CNT]; //Pointer to the I2S DMA buffer data -static struct slc_queue_item i2s_slc_items[SLC_BUF_CNT]; //I2S DMA buffer descriptors -static uint32_t *i2s_curr_slc_buf=NULL;//current buffer for writing -static int i2s_curr_slc_buf_pos=0; //position in the current buffer -static void (*i2s_callback) (void)=0; //Callback function should be defined as 'void ICACHE_FLASH_ATTR function_name()', placing the function in IRAM for faster execution. Avoid long computational tasks in this function, use it to set flags and process later. - -bool ICACHE_FLASH_ATTR i2s_is_full(){ - return (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) && (i2s_slc_queue_len == 0); +typedef struct slc_queue_item { + uint32_t blocksize:12; + uint32_t datalen:12; + uint32_t unused:5; + uint32_t sub_sof:1; + uint32_t eof:1; + uint32_t owner:1; + uint32_t buf_ptr; + uint32_t next_link_ptr; +} slc_queue_item_t; + +typedef struct i2s_state { + uint32_t i2s_slc_queue[SLC_BUF_CNT-1]; + uint8_t i2s_slc_queue_len; + uint32_t * i2s_slc_buf_pntr[SLC_BUF_CNT]; //Pointer to the I2S DMA buffer data + slc_queue_item_t i2s_slc_items[SLC_BUF_CNT]; //I2S DMA buffer descriptors + uint32_t * i2s_curr_slc_buf; // Current buffer for writing + uint32_t i2s_curr_slc_buf_pos; // Position in the current buffer + void (*i2s_callback) (void); + // Callback function should be defined as 'void ICACHE_FLASH_ATTR function_name()', + // and be placed in IRAM for faster execution. Avoid long computational tasks in this + // function, use it to set flags and process later. +} i2s_state_t; + +// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC) +static i2s_state_t *rx = NULL; +static i2s_state_t *tx = NULL; + +static bool ICACHE_FLASH_ATTR _i2s_is_full(const i2s_state_t *ch) { + return (ch->i2s_curr_slc_buf_pos==SLC_BUF_LEN || ch->i2s_curr_slc_buf==NULL) && (ch->i2s_slc_queue_len == 0); } -bool ICACHE_FLASH_ATTR i2s_is_empty(){ - return (i2s_slc_queue_len >= SLC_BUF_CNT-1); +bool ICACHE_FLASH_ATTR i2s_is_full() { + return _i2s_is_full( tx ); +} + +bool ICACHE_FLASH_ATTR i2s_rx_is_full() { + return _i2s_is_full( rx ); +} + +static bool ICACHE_FLASH_ATTR _i2s_is_empty(const i2s_state_t *ch) { + return (ch->i2s_slc_queue_len >= SLC_BUF_CNT-1); +} + +bool ICACHE_FLASH_ATTR i2s_is_empty() { + return _i2s_is_empty( tx ); +} + +bool ICACHE_FLASH_ATTR i2s_rx_is_empty() { + return _i2s_is_empty( rx ); +} + +static int16_t ICACHE_FLASH_ATTR _i2s_available(const i2s_state_t *ch) { + return (SLC_BUF_CNT - ch->i2s_slc_queue_len) * SLC_BUF_LEN; } int16_t ICACHE_FLASH_ATTR i2s_available(){ - return (SLC_BUF_CNT - i2s_slc_queue_len) * SLC_BUF_LEN; + return _i2s_available( tx ); } -uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue +int16_t ICACHE_FLASH_ATTR i2s_rx_available(){ + return _i2s_available( rx ); +} + +// Pop the top off of the queue and return it +uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { uint8_t i; - uint32_t item = i2s_slc_queue[0]; - i2s_slc_queue_len--; - for(i=0;ii2s_slc_queue[0]; + ch->i2s_slc_queue_len--; + for ( i = 0; i < ch->i2s_slc_queue_len; i++) { + ch->i2s_slc_queue[i] = ch->i2s_slc_queue[i+1]; + } return item; } @@ -87,37 +122,41 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { SLCIC = 0xFFFFFFFF; if (slc_intr_status & SLCIRXEOF) { ETS_SLC_INTR_DISABLE(); - struct slc_queue_item *finished_item = (struct slc_queue_item*)SLCRXEDA; + slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA; memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);//zero the buffer so it is mute in case of underflow - if (i2s_slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow - i2s_slc_queue_next_item(); //free space for finished_item + if (tx->i2s_slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow + i2s_slc_queue_next_item(tx); //free space for finished_item } - i2s_slc_queue[i2s_slc_queue_len++] = finished_item->buf_ptr; - if (i2s_callback) i2s_callback(); + tx->i2s_slc_queue[tx->i2s_slc_queue_len++] = finished_item->buf_ptr; + if (tx->i2s_callback) tx->i2s_callback(); ETS_SLC_INTR_ENABLE(); } } void i2s_set_callback(void (*callback) (void)){ - i2s_callback = callback; + tx->i2s_callback = callback; +} + +void i2s_rx_set_callback(void (*callback) (void)){ + rx->i2s_callback = callback; } -void ICACHE_FLASH_ATTR i2s_slc_begin(){ - i2s_slc_queue_len = 0; +void ICACHE_FLASH_ATTR i2s_slc_begin() { + tx->i2s_slc_queue_len = 0; int x, y; for (x=0; xi2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); + for (y=0; yi2s_slc_buf_pntr[x][y] = 0; + + tx->i2s_slc_items[x].unused = 0; + tx->i2s_slc_items[x].owner = 1; + tx->i2s_slc_items[x].eof = 1; + tx->i2s_slc_items[x].sub_sof = 0; + tx->i2s_slc_items[x].datalen = SLC_BUF_LEN*4; + tx->i2s_slc_items[x].blocksize = SLC_BUF_LEN*4; + tx->i2s_slc_items[x].buf_ptr = (uint32_t)&tx->i2s_slc_buf_pntr[x][0]; + tx->i2s_slc_items[x].next_link_ptr = (int)((x<(SLC_BUF_CNT-1))?(&tx->i2s_slc_items[x+1]):(&tx->i2s_slc_items[0])); } ETS_SLC_INTR_DISABLE(); @@ -136,9 +175,9 @@ void ICACHE_FLASH_ATTR i2s_slc_begin(){ //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)&i2s_slc_items[1] << SLCTXLA; //set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid + SLCTXL |= (uint32)&tx->i2s_slc_items[1] << SLCTXLA; //set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address - SLCRXL |= (uint32)&i2s_slc_items[0] << SLCRXLA; //set RX descriptor address + SLCRXL |= (uint32)&tx->i2s_slc_items[0] << SLCRXLA; //set RX descriptor address ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); SLCIE = SLCIRXEOF; //Enable only for RX EOF interrupt @@ -158,7 +197,7 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){ SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address for (int x = 0; xi2s_slc_buf_pntr[x]); } } @@ -166,11 +205,12 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){ //at least the current sample rate. You can also call it quicker: it will suspend the calling //thread if the buffer is full and resume when there's room again. -bool ICACHE_FLASH_ATTR i2s_write_sample(uint32_t sample) { - if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) { - if(i2s_slc_queue_len == 0){ +bool ICACHE_FLASH_ATTR _i2s_write_sample(uint32_t sample, bool nb) { + if (tx->i2s_curr_slc_buf_pos==SLC_BUF_LEN || tx->i2s_curr_slc_buf==NULL) { + if (tx->i2s_slc_queue_len == 0) { + if (nb) return false; while(1){ - if(i2s_slc_queue_len > 0){ + if(tx->i2s_slc_queue_len > 0){ break; } else { ets_wdt_disable(); @@ -179,26 +219,20 @@ bool ICACHE_FLASH_ATTR i2s_write_sample(uint32_t sample) { } } ETS_SLC_INTR_DISABLE(); - i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(); + tx->i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(tx); ETS_SLC_INTR_ENABLE(); - i2s_curr_slc_buf_pos=0; + tx->i2s_curr_slc_buf_pos=0; } - i2s_curr_slc_buf[i2s_curr_slc_buf_pos++]=sample; + tx->i2s_curr_slc_buf[tx->i2s_curr_slc_buf_pos++]=sample; return true; } +bool ICACHE_FLASH_ATTR i2s_write_sample(uint32_t sample) { + return _i2s_write_sample(sample, false); +} + bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) { - if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) { - if(i2s_slc_queue_len == 0){ - return false; - } - ETS_SLC_INTR_DISABLE(); - i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(); - ETS_SLC_INTR_ENABLE(); - i2s_curr_slc_buf_pos=0; - } - i2s_curr_slc_buf[i2s_curr_slc_buf_pos++]=sample; - return true; + return _i2s_write_sample(sample, true); } bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){ @@ -255,6 +289,13 @@ float ICACHE_FLASH_ATTR i2s_get_real_rate(){ } void ICACHE_FLASH_ATTR i2s_begin(){ + if (tx || rx) { + i2s_end(); // Stop and free any ongoing stuff + } + + tx = (i2s_state_t*)calloc(1, sizeof(*tx)); + rx = (i2s_state_t*)calloc(1, sizeof(*rx)); + _i2s_sample_rate = 0; i2s_slc_begin(); @@ -291,4 +332,9 @@ void ICACHE_FLASH_ATTR i2s_end(){ pinMode(15, INPUT); i2s_slc_end(); + + free(tx); + tx = NULL; + free(rx); + rx = NULL; } From 740e9bdaa00a50b050e2e05c94a1015be7dd5ccf Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 20 Mar 2018 20:29:17 -0700 Subject: [PATCH 02/13] Preliminary I2S reception shows life Initial testing of I2S reception API Add API calls: i2s_rxtx_begin(bool rx, rool tx); i2s_read_sample(uint32_t *l, uint32_t *r); --- cores/esp8266/core_esp8266_i2s.c | 315 +++++++++++++++++++++++-------- cores/esp8266/i2s.h | 8 +- 2 files changed, 240 insertions(+), 83 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index b89ce0d9b3..55d3782ecf 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -30,31 +30,38 @@ extern void ets_wdt_enable(void); extern void ets_wdt_disable(void); -#define SLC_BUF_CNT (8) //Number of buffers in the I2S circular buffer -#define SLC_BUF_LEN (64) //Length of one buffer, in 32-bit words. +#define SLC_BUF_CNT (8) // Number of buffers in the I2S circular buffer +#define SLC_BUF_LEN (64) // Length of one buffer, in 32-bit words. -//We use a queue to keep track of the DMA buffers that are empty. The ISR will push buffers to the back of the queue, -//the mp3 decode will pull them from the front and fill them. For ease, the queue will contain *pointers* to the DMA -//buffers, not the data itself. The queue depth is one smaller than the amount of buffers we have, because there's -//always a buffer that is being used by the DMA subsystem *right now* and we don't want to be able to write to that -//simultaneously. +// We use a queue to keep track of the DMA buffers that are empty. The ISR +// will push buffers to the back of the queue, the I2S transmitter will pull +// them from the front and fill them. For ease, the queue will contain +// *pointers* to the DMA buffers, not the data itself. The queue depth is +// one smaller than the amount of buffers we have, because there's always a +// buffer that is being used by the DMA subsystem *right now* and we don't +// want to be able to write to that simultaneously. + +// For RX, it's a little different. The buffers in i2s_slc_queue are +// placed onto the list when they're filled by DMA typedef struct slc_queue_item { - uint32_t blocksize:12; - uint32_t datalen:12; - uint32_t unused:5; - uint32_t sub_sof:1; - uint32_t eof:1; - uint32_t owner:1; - uint32_t buf_ptr; - uint32_t next_link_ptr; + uint32_t blocksize : 12; + uint32_t datalen : 12; + uint32_t unused : 5; + uint32_t sub_sof : 1; + volatile uint32_t eof : 1; + volatile uint32_t owner : 1; + uint32_t *buf_ptr; + uint32_t *next_link_ptr; + // This is my own way of tracking item ID + volatile uint32_t myid; } slc_queue_item_t; typedef struct i2s_state { - uint32_t i2s_slc_queue[SLC_BUF_CNT-1]; - uint8_t i2s_slc_queue_len; - uint32_t * i2s_slc_buf_pntr[SLC_BUF_CNT]; //Pointer to the I2S DMA buffer data - slc_queue_item_t i2s_slc_items[SLC_BUF_CNT]; //I2S DMA buffer descriptors + uint32_t * i2s_slc_queue[SLC_BUF_CNT]; + volatile uint8_t i2s_slc_queue_len; + uint32_t * i2s_slc_buf_pntr[SLC_BUF_CNT]; // Pointer to the I2S DMA buffer data + slc_queue_item_t i2s_slc_items[SLC_BUF_CNT]; // I2S DMA buffer descriptors uint32_t * i2s_curr_slc_buf; // Current buffer for writing uint32_t i2s_curr_slc_buf_pos; // Position in the current buffer void (*i2s_callback) (void); @@ -67,8 +74,21 @@ typedef struct i2s_state { static i2s_state_t *rx = NULL; static i2s_state_t *tx = NULL; +volatile int rx_irqs = 0; +volatile int tx_irqs = 0; + +// Some constants that aren't defined in i2s_regs.h +#define I2SI_DATA 12 +#define I2SI_BCK 13 +#define I2SI_WS 14 + + static bool ICACHE_FLASH_ATTR _i2s_is_full(const i2s_state_t *ch) { - return (ch->i2s_curr_slc_buf_pos==SLC_BUF_LEN || ch->i2s_curr_slc_buf==NULL) && (ch->i2s_slc_queue_len == 0); + if (!ch) { + return false; + } else { + return (ch->i2s_curr_slc_buf_pos==SLC_BUF_LEN || ch->i2s_curr_slc_buf==NULL) && (ch->i2s_slc_queue_len == 0); + } } bool ICACHE_FLASH_ATTR i2s_is_full() { @@ -80,7 +100,11 @@ bool ICACHE_FLASH_ATTR i2s_rx_is_full() { } static bool ICACHE_FLASH_ATTR _i2s_is_empty(const i2s_state_t *ch) { - return (ch->i2s_slc_queue_len >= SLC_BUF_CNT-1); + if (!ch) { + return false; + } else { + return (ch->i2s_slc_queue_len >= SLC_BUF_CNT-1); + } } bool ICACHE_FLASH_ATTR i2s_is_empty() { @@ -92,7 +116,11 @@ bool ICACHE_FLASH_ATTR i2s_rx_is_empty() { } static int16_t ICACHE_FLASH_ATTR _i2s_available(const i2s_state_t *ch) { - return (SLC_BUF_CNT - ch->i2s_slc_queue_len) * SLC_BUF_LEN; + if (!ch) { + return 0; + } else { + return (SLC_BUF_CNT - ch->i2s_slc_queue_len) * SLC_BUF_LEN; + } } int16_t ICACHE_FLASH_ATTR i2s_available(){ @@ -104,9 +132,9 @@ int16_t ICACHE_FLASH_ATTR i2s_rx_available(){ } // Pop the top off of the queue and return it -uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { +uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { uint8_t i; - uint32_t item = ch->i2s_slc_queue[0]; + uint32_t *item = ch->i2s_slc_queue[0]; ch->i2s_slc_queue_len--; for ( i = 0; i < ch->i2s_slc_queue_len; i++) { ch->i2s_slc_queue[i] = ch->i2s_slc_queue[i+1]; @@ -114,14 +142,30 @@ uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { return item; } -//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. +// Append an item to the end of the queue from receive +void ICACHE_FLASH_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) { + // Shift everything up, except for the one corresponding to this item + for (int i=0, dest=0; i < ch->i2s_slc_queue_len; i++) { + if (ch->i2s_slc_queue[i] != item) { + ch->i2s_slc_queue[dest++] = ch->i2s_slc_queue[i]; + } + } + if (ch->i2s_slc_queue_len < SLC_BUF_CNT - 1) { + ch->i2s_slc_queue[ch->i2s_slc_queue_len++] = item; + } else { + ch->i2s_slc_queue[ch->i2s_slc_queue_len] = item; + } +} + +// 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. void ICACHE_FLASH_ATTR i2s_slc_isr(void) { uint32_t slc_intr_status = SLCIS; SLCIC = 0xFFFFFFFF; if (slc_intr_status & SLCIRXEOF) { ETS_SLC_INTR_DISABLE(); + tx_irqs++; slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA; memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);//zero the buffer so it is mute in case of underflow if (tx->i2s_slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow @@ -131,6 +175,16 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { if (tx->i2s_callback) tx->i2s_callback(); ETS_SLC_INTR_ENABLE(); } + if (slc_intr_status & SLCITXEOF) { + ETS_SLC_INTR_DISABLE(); + rx_irqs++; + slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCTXEDA; + finished_item->owner = 1; // Or else RX just stops + finished_item->myid++; + i2s_slc_queue_append_item(rx, finished_item->buf_ptr); + if (rx->i2s_callback) rx->i2s_callback(); + ETS_SLC_INTR_ENABLE(); + } } void i2s_set_callback(void (*callback) (void)){ @@ -141,22 +195,42 @@ void i2s_rx_set_callback(void (*callback) (void)){ rx->i2s_callback = callback; } -void ICACHE_FLASH_ATTR i2s_slc_begin() { - tx->i2s_slc_queue_len = 0; +static void ICACHE_FLASH_ATTR _alloc_channel(i2s_state_t *ch) { int x, y; - + + ch->i2s_slc_queue_len = 0; for (x=0; xi2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); - for (y=0; yi2s_slc_buf_pntr[x][y] = 0; - - tx->i2s_slc_items[x].unused = 0; - tx->i2s_slc_items[x].owner = 1; - tx->i2s_slc_items[x].eof = 1; - tx->i2s_slc_items[x].sub_sof = 0; - tx->i2s_slc_items[x].datalen = SLC_BUF_LEN*4; - tx->i2s_slc_items[x].blocksize = SLC_BUF_LEN*4; - tx->i2s_slc_items[x].buf_ptr = (uint32_t)&tx->i2s_slc_buf_pntr[x][0]; - tx->i2s_slc_items[x].next_link_ptr = (int)((x<(SLC_BUF_CNT-1))?(&tx->i2s_slc_items[x+1]):(&tx->i2s_slc_items[0])); + ch->i2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); + for (y=0; yi2s_slc_buf_pntr[x][y] = 0; + + ch->i2s_slc_items[x].unused = 0; + ch->i2s_slc_items[x].owner = 1; + ch->i2s_slc_items[x].eof = 1; + ch->i2s_slc_items[x].sub_sof = 0; + ch->i2s_slc_items[x].datalen = SLC_BUF_LEN * 4; + ch->i2s_slc_items[x].blocksize = SLC_BUF_LEN * 4; + ch->i2s_slc_items[x].buf_ptr = (uint32_t*)&ch->i2s_slc_buf_pntr[x][0]; + ch->i2s_slc_items[x].next_link_ptr = (uint32_t*)((x<(SLC_BUF_CNT-1))?(&ch->i2s_slc_items[x+1]):(&ch->i2s_slc_items[0])); + ch->i2s_slc_items[x].myid = 0; + } +} +#if 0 +void dumprx() +{ +for (int i=0; ii2s_slc_items[i].myid, rx->i2s_slc_items[i].owner, rx->i2s_slc_items[i].eof, rx->i2s_slc_items[i].sub_sof, rx->i2s_slc_items[i].datalen, rx->i2s_slc_items[i].blocksize, +rx->i2s_slc_items[i].buf_ptr, rx->i2s_slc_items[i].next_link_ptr); +} +} +#endif + + +static void ICACHE_FLASH_ATTR i2s_slc_begin() { + if (tx) { + _alloc_channel(tx); + } + if (rx) { + _alloc_channel(rx); } ETS_SLC_INTR_DISABLE(); @@ -164,32 +238,42 @@ void ICACHE_FLASH_ATTR i2s_slc_begin() { 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 + // 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 - SLCTXL |= (uint32)&tx->i2s_slc_items[1] << SLCTXLA; //set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address - SLCRXL |= (uint32)&tx->i2s_slc_items[0] << SLCRXLA; //set RX descriptor address + if (!rx) { + SLCTXL |= (uint32)&tx->i2s_slc_items[1] << SLCTXLA; // Set fake (unused) RX descriptor address + } else { + SLCTXL |= (uint32)&rx->i2s_slc_items[0] << SLCTXLA; // Set real RX address + } + if (!tx) { +// SLCRXL |= (uint32)&rx->i2s_slc_items[1] << SLCRXLA; // Set fake (ununsed) TX descriptor address + } else { + SLCRXL |= (uint32)&tx->i2s_slc_items[0] << SLCRXLA; // Set real TX address + } ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); - SLCIE = SLCIRXEOF; //Enable only for RX EOF interrupt + SLCIE = (tx?SLCIRXEOF:0) | (rx?SLCITXEOF:0); // Enable appropriate EOF IRQ ETS_SLC_INTR_ENABLE(); - //Start transmission + // Start transmission ("TX" DMA always needed to be enabled) SLCTXL |= SLCTXLS; - SLCRXL |= SLCRXLS; + if (tx) { + SLCRXL |= SLCRXLS; + } } -void ICACHE_FLASH_ATTR i2s_slc_end(){ +static void ICACHE_FLASH_ATTR i2s_slc_end(){ ETS_SLC_INTR_DISABLE(); SLCIC = 0xFFFFFFFF; SLCIE = 0; @@ -197,7 +281,14 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){ SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address for (int x = 0; xi2s_slc_buf_pntr[x]); + if (tx) { + free(tx->i2s_slc_buf_pntr[x]); + tx->i2s_slc_buf_pntr[x] = NULL; + } + if (rx) { + free(rx->i2s_slc_buf_pntr[x]); + rx->i2s_slc_buf_pntr[x] = NULL; + } } } @@ -205,12 +296,12 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){ //at least the current sample rate. You can also call it quicker: it will suspend the calling //thread if the buffer is full and resume when there's room again. -bool ICACHE_FLASH_ATTR _i2s_write_sample(uint32_t sample, bool nb) { +static bool ICACHE_FLASH_ATTR _i2s_write_sample(uint32_t sample, bool nb) { if (tx->i2s_curr_slc_buf_pos==SLC_BUF_LEN || tx->i2s_curr_slc_buf==NULL) { if (tx->i2s_slc_queue_len == 0) { if (nb) return false; - while(1){ - if(tx->i2s_slc_queue_len > 0){ + while (1) { + if (tx->i2s_slc_queue_len > 0){ break; } else { ets_wdt_disable(); @@ -242,6 +333,33 @@ bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){ return i2s_write_sample(sample); } +bool ICACHE_FLASH_ATTR i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { + if (rx->i2s_curr_slc_buf_pos==SLC_BUF_LEN || rx->i2s_curr_slc_buf==NULL) { + if (rx->i2s_slc_queue_len == 0) { + if (!blocking) return false; + while (1) { + if (rx->i2s_slc_queue_len > 0){ + break; + } else { + ets_wdt_disable(); + ets_wdt_enable(); + } + } + } + ETS_SLC_INTR_DISABLE(); + rx->i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(rx); + ETS_SLC_INTR_ENABLE(); + rx->i2s_curr_slc_buf_pos=0; + } + +// *left = rx->i2s_slc_items[0].buf_ptr[rx->i2s_curr_slc_buf_pos++]; +// *right = rx->i2s_slc_items[0].buf_ptr[rx->i2s_curr_slc_buf_pos++]; + *left = rx->i2s_curr_slc_buf[rx->i2s_curr_slc_buf_pos++]; + *right = rx->i2s_curr_slc_buf[rx->i2s_curr_slc_buf_pos++]; + + return true; +} + // END DMA // ========= // START I2S @@ -269,18 +387,16 @@ void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ } } - //os_printf("Rate %u Div %u Bck %u Frq %u\n", _i2s_sample_rate, i2s_clock_div, i2s_bck_div, I2SBASEFREQ/(i2s_clock_div*i2s_bck_div*2)); - //!trans master, !bits mod, rece slave mod, rece msb shift, right first, msb right - I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); - I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | ((sbd_div_best) << I2SBD) | ((scd_div_best) << I2SCD); + I2SC &= ~(I2STSM | I2SRSM | /*(I2SBMM << I2SBM) |*/ (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); + I2SC |= I2SRF | I2SMR | I2SRMS | ((sbd_div_best) << I2SBD) | ((scd_div_best) << I2SCD); } void ICACHE_FLASH_ATTR i2s_set_dividers(uint8_t div1, uint8_t div2){ div1 &= I2SBDM; div2 &= I2SCDM; - I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); + I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (div1 << I2SBD) | (div2 << I2SCD); } @@ -288,38 +404,67 @@ float ICACHE_FLASH_ATTR i2s_get_real_rate(){ return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM); } -void ICACHE_FLASH_ATTR i2s_begin(){ +void ICACHE_FLASH_ATTR i2s_rxtx_begin(bool enableRx, bool enableTx) { if (tx || rx) { i2s_end(); // Stop and free any ongoing stuff } - tx = (i2s_state_t*)calloc(1, sizeof(*tx)); - rx = (i2s_state_t*)calloc(1, sizeof(*rx)); + if (enableTx) { + tx = (i2s_state_t*)calloc(1, sizeof(*tx)); + if (!tx) { + return; // OOM Error! + } + pinMode(2, FUNCTION_1); // I2SO_WS (LRCK) + pinMode(3, FUNCTION_1); // I2SO_DATA (SDIN) + pinMode(15, FUNCTION_1); // I2SO_BCK (SCLK) + } + if (enableRx) { + rx = (i2s_state_t*)calloc(1, sizeof(*rx)); + if (!rx) { + free(tx); + tx = NULL; + return; // OOM error! + } + pinMode(I2SI_WS, OUTPUT); + pinMode(I2SI_BCK, OUTPUT); + pinMode(I2SI_DATA, INPUT); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_I2SI_DATA); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS); + } _i2s_sample_rate = 0; i2s_slc_begin(); - pinMode(2, FUNCTION_1); //I2SO_WS (LRCK) - pinMode(3, FUNCTION_1); //I2SO_DATA (SDIN) - pinMode(15, FUNCTION_1); //I2SO_BCK (SCLK) - I2S_CLK_ENABLE(); I2SIC = 0x3F; I2SIE = 0; - //Reset I2S + // Reset I2S I2SC &= ~(I2SRST); I2SC |= I2SRST; I2SC &= ~(I2SRST); - I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); //Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) - I2SFC |= I2SDE; //Enable DMA - I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); //Set RX/TX CHAN_MOD=0 + I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) + I2SFC |= I2SDE | (rx ? 2/*24bpc, 2ch*/< Date: Wed, 21 Mar 2018 19:43:37 -0700 Subject: [PATCH 03/13] Refactor i2s_state_t member names, remove i2s_ prefix --- cores/esp8266/core_esp8266_i2s.c | 128 +++++++++++++++---------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 55d3782ecf..fd363330f6 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -58,13 +58,13 @@ typedef struct slc_queue_item { } slc_queue_item_t; typedef struct i2s_state { - uint32_t * i2s_slc_queue[SLC_BUF_CNT]; - volatile uint8_t i2s_slc_queue_len; - uint32_t * i2s_slc_buf_pntr[SLC_BUF_CNT]; // Pointer to the I2S DMA buffer data - slc_queue_item_t i2s_slc_items[SLC_BUF_CNT]; // I2S DMA buffer descriptors - uint32_t * i2s_curr_slc_buf; // Current buffer for writing - uint32_t i2s_curr_slc_buf_pos; // Position in the current buffer - void (*i2s_callback) (void); + uint32_t * slc_queue[SLC_BUF_CNT]; + volatile uint8_t slc_queue_len; + uint32_t * slc_buf_pntr[SLC_BUF_CNT]; // Pointer to the I2S DMA buffer data + slc_queue_item_t slc_items[SLC_BUF_CNT]; // I2S DMA buffer descriptors + uint32_t * curr_slc_buf; // Current buffer for writing + uint32_t curr_slc_buf_pos; // Position in the current buffer + void (*callback) (void); // Callback function should be defined as 'void ICACHE_FLASH_ATTR function_name()', // and be placed in IRAM for faster execution. Avoid long computational tasks in this // function, use it to set flags and process later. @@ -87,7 +87,7 @@ static bool ICACHE_FLASH_ATTR _i2s_is_full(const i2s_state_t *ch) { if (!ch) { return false; } else { - return (ch->i2s_curr_slc_buf_pos==SLC_BUF_LEN || ch->i2s_curr_slc_buf==NULL) && (ch->i2s_slc_queue_len == 0); + return (ch->curr_slc_buf_pos==SLC_BUF_LEN || ch->curr_slc_buf==NULL) && (ch->slc_queue_len == 0); } } @@ -103,7 +103,7 @@ static bool ICACHE_FLASH_ATTR _i2s_is_empty(const i2s_state_t *ch) { if (!ch) { return false; } else { - return (ch->i2s_slc_queue_len >= SLC_BUF_CNT-1); + return (ch->slc_queue_len >= SLC_BUF_CNT-1); } } @@ -119,7 +119,7 @@ static int16_t ICACHE_FLASH_ATTR _i2s_available(const i2s_state_t *ch) { if (!ch) { return 0; } else { - return (SLC_BUF_CNT - ch->i2s_slc_queue_len) * SLC_BUF_LEN; + return (SLC_BUF_CNT - ch->slc_queue_len) * SLC_BUF_LEN; } } @@ -134,10 +134,10 @@ int16_t ICACHE_FLASH_ATTR i2s_rx_available(){ // Pop the top off of the queue and return it uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { uint8_t i; - uint32_t *item = ch->i2s_slc_queue[0]; - ch->i2s_slc_queue_len--; - for ( i = 0; i < ch->i2s_slc_queue_len; i++) { - ch->i2s_slc_queue[i] = ch->i2s_slc_queue[i+1]; + uint32_t *item = ch->slc_queue[0]; + ch->slc_queue_len--; + for ( i = 0; i < ch->slc_queue_len; i++) { + ch->slc_queue[i] = ch->slc_queue[i+1]; } return item; } @@ -145,15 +145,15 @@ uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { // Append an item to the end of the queue from receive void ICACHE_FLASH_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) { // Shift everything up, except for the one corresponding to this item - for (int i=0, dest=0; i < ch->i2s_slc_queue_len; i++) { - if (ch->i2s_slc_queue[i] != item) { - ch->i2s_slc_queue[dest++] = ch->i2s_slc_queue[i]; + for (int i=0, dest=0; i < ch->slc_queue_len; i++) { + if (ch->slc_queue[i] != item) { + ch->slc_queue[dest++] = ch->slc_queue[i]; } } - if (ch->i2s_slc_queue_len < SLC_BUF_CNT - 1) { - ch->i2s_slc_queue[ch->i2s_slc_queue_len++] = item; + if (ch->slc_queue_len < SLC_BUF_CNT - 1) { + ch->slc_queue[ch->slc_queue_len++] = item; } else { - ch->i2s_slc_queue[ch->i2s_slc_queue_len] = item; + ch->slc_queue[ch->slc_queue_len] = item; } } @@ -168,11 +168,11 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { tx_irqs++; slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA; memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);//zero the buffer so it is mute in case of underflow - if (tx->i2s_slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow + if (tx->slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow i2s_slc_queue_next_item(tx); //free space for finished_item } - tx->i2s_slc_queue[tx->i2s_slc_queue_len++] = finished_item->buf_ptr; - if (tx->i2s_callback) tx->i2s_callback(); + tx->slc_queue[tx->slc_queue_len++] = finished_item->buf_ptr; + if (tx->callback) tx->callback(); ETS_SLC_INTR_ENABLE(); } if (slc_intr_status & SLCITXEOF) { @@ -182,44 +182,44 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { finished_item->owner = 1; // Or else RX just stops finished_item->myid++; i2s_slc_queue_append_item(rx, finished_item->buf_ptr); - if (rx->i2s_callback) rx->i2s_callback(); + if (rx->callback) rx->callback(); ETS_SLC_INTR_ENABLE(); } } void i2s_set_callback(void (*callback) (void)){ - tx->i2s_callback = callback; + tx->callback = callback; } void i2s_rx_set_callback(void (*callback) (void)){ - rx->i2s_callback = callback; + rx->callback = callback; } static void ICACHE_FLASH_ATTR _alloc_channel(i2s_state_t *ch) { int x, y; - ch->i2s_slc_queue_len = 0; + ch->slc_queue_len = 0; for (x=0; xi2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); - for (y=0; yi2s_slc_buf_pntr[x][y] = 0; - - ch->i2s_slc_items[x].unused = 0; - ch->i2s_slc_items[x].owner = 1; - ch->i2s_slc_items[x].eof = 1; - ch->i2s_slc_items[x].sub_sof = 0; - ch->i2s_slc_items[x].datalen = SLC_BUF_LEN * 4; - ch->i2s_slc_items[x].blocksize = SLC_BUF_LEN * 4; - ch->i2s_slc_items[x].buf_ptr = (uint32_t*)&ch->i2s_slc_buf_pntr[x][0]; - ch->i2s_slc_items[x].next_link_ptr = (uint32_t*)((x<(SLC_BUF_CNT-1))?(&ch->i2s_slc_items[x+1]):(&ch->i2s_slc_items[0])); - ch->i2s_slc_items[x].myid = 0; + ch->slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); + for (y=0; yslc_buf_pntr[x][y] = 0; + + ch->slc_items[x].unused = 0; + ch->slc_items[x].owner = 1; + ch->slc_items[x].eof = 1; + ch->slc_items[x].sub_sof = 0; + ch->slc_items[x].datalen = SLC_BUF_LEN * 4; + ch->slc_items[x].blocksize = SLC_BUF_LEN * 4; + ch->slc_items[x].buf_ptr = (uint32_t*)&ch->slc_buf_pntr[x][0]; + ch->slc_items[x].next_link_ptr = (uint32_t*)((x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0])); + ch->slc_items[x].myid = 0; } } #if 0 void dumprx() { for (int i=0; ii2s_slc_items[i].myid, rx->i2s_slc_items[i].owner, rx->i2s_slc_items[i].eof, rx->i2s_slc_items[i].sub_sof, rx->i2s_slc_items[i].datalen, rx->i2s_slc_items[i].blocksize, -rx->i2s_slc_items[i].buf_ptr, rx->i2s_slc_items[i].next_link_ptr); +printf("%d: %d %d %d %d %d %d %p %p\n", i, rx->slc_items[i].myid, rx->slc_items[i].owner, rx->slc_items[i].eof, rx->slc_items[i].sub_sof, rx->slc_items[i].datalen, rx->slc_items[i].blocksize, +rx->slc_items[i].buf_ptr, rx->slc_items[i].next_link_ptr); } } #endif @@ -251,14 +251,14 @@ static void ICACHE_FLASH_ATTR i2s_slc_begin() { SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address if (!rx) { - SLCTXL |= (uint32)&tx->i2s_slc_items[1] << SLCTXLA; // Set fake (unused) RX descriptor address + SLCTXL |= (uint32)&tx->slc_items[1] << SLCTXLA; // Set fake (unused) RX descriptor address } else { - SLCTXL |= (uint32)&rx->i2s_slc_items[0] << SLCTXLA; // Set real RX address + SLCTXL |= (uint32)&rx->slc_items[0] << SLCTXLA; // Set real RX address } if (!tx) { -// SLCRXL |= (uint32)&rx->i2s_slc_items[1] << SLCRXLA; // Set fake (ununsed) TX descriptor address +// SLCRXL |= (uint32)&rx->slc_items[1] << SLCRXLA; // Set fake (ununsed) TX descriptor address } else { - SLCRXL |= (uint32)&tx->i2s_slc_items[0] << SLCRXLA; // Set real TX address + SLCRXL |= (uint32)&tx->slc_items[0] << SLCRXLA; // Set real TX address } ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); @@ -282,12 +282,12 @@ static void ICACHE_FLASH_ATTR i2s_slc_end(){ for (int x = 0; xi2s_slc_buf_pntr[x]); - tx->i2s_slc_buf_pntr[x] = NULL; + free(tx->slc_buf_pntr[x]); + tx->slc_buf_pntr[x] = NULL; } if (rx) { - free(rx->i2s_slc_buf_pntr[x]); - rx->i2s_slc_buf_pntr[x] = NULL; + free(rx->slc_buf_pntr[x]); + rx->slc_buf_pntr[x] = NULL; } } } @@ -297,11 +297,11 @@ static void ICACHE_FLASH_ATTR i2s_slc_end(){ //thread if the buffer is full and resume when there's room again. static bool ICACHE_FLASH_ATTR _i2s_write_sample(uint32_t sample, bool nb) { - if (tx->i2s_curr_slc_buf_pos==SLC_BUF_LEN || tx->i2s_curr_slc_buf==NULL) { - if (tx->i2s_slc_queue_len == 0) { + if (tx->curr_slc_buf_pos==SLC_BUF_LEN || tx->curr_slc_buf==NULL) { + if (tx->slc_queue_len == 0) { if (nb) return false; while (1) { - if (tx->i2s_slc_queue_len > 0){ + if (tx->slc_queue_len > 0){ break; } else { ets_wdt_disable(); @@ -310,11 +310,11 @@ static bool ICACHE_FLASH_ATTR _i2s_write_sample(uint32_t sample, bool nb) { } } ETS_SLC_INTR_DISABLE(); - tx->i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(tx); + tx->curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(tx); ETS_SLC_INTR_ENABLE(); - tx->i2s_curr_slc_buf_pos=0; + tx->curr_slc_buf_pos=0; } - tx->i2s_curr_slc_buf[tx->i2s_curr_slc_buf_pos++]=sample; + tx->curr_slc_buf[tx->curr_slc_buf_pos++]=sample; return true; } @@ -334,11 +334,11 @@ bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){ } bool ICACHE_FLASH_ATTR i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { - if (rx->i2s_curr_slc_buf_pos==SLC_BUF_LEN || rx->i2s_curr_slc_buf==NULL) { - if (rx->i2s_slc_queue_len == 0) { + if (rx->curr_slc_buf_pos==SLC_BUF_LEN || rx->curr_slc_buf==NULL) { + if (rx->slc_queue_len == 0) { if (!blocking) return false; while (1) { - if (rx->i2s_slc_queue_len > 0){ + if (rx->slc_queue_len > 0){ break; } else { ets_wdt_disable(); @@ -347,15 +347,15 @@ bool ICACHE_FLASH_ATTR i2s_read_sample(uint32_t *left, uint32_t *right, bool blo } } ETS_SLC_INTR_DISABLE(); - rx->i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(rx); + rx->curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(rx); ETS_SLC_INTR_ENABLE(); - rx->i2s_curr_slc_buf_pos=0; + rx->curr_slc_buf_pos=0; } -// *left = rx->i2s_slc_items[0].buf_ptr[rx->i2s_curr_slc_buf_pos++]; -// *right = rx->i2s_slc_items[0].buf_ptr[rx->i2s_curr_slc_buf_pos++]; - *left = rx->i2s_curr_slc_buf[rx->i2s_curr_slc_buf_pos++]; - *right = rx->i2s_curr_slc_buf[rx->i2s_curr_slc_buf_pos++]; +// *left = rx->slc_items[0].buf_ptr[rx->curr_slc_buf_pos++]; +// *right = rx->slc_items[0].buf_ptr[rx->curr_slc_buf_pos++]; + *left = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; + *right = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; return true; } From 0478d47600cb391f01117edf0ead016e83756813 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 21 Mar 2018 19:55:06 -0700 Subject: [PATCH 04/13] Add simple I2S input example code --- cores/esp8266/core_esp8266_i2s.c | 20 ++++---- .../esp8266/examples/I2SInput/I2SInput.ino | 48 +++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 libraries/esp8266/examples/I2SInput/I2SInput.ino diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index fd363330f6..7e0ada29ce 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -45,16 +45,16 @@ extern void ets_wdt_disable(void); // placed onto the list when they're filled by DMA typedef struct slc_queue_item { - uint32_t blocksize : 12; - uint32_t datalen : 12; - uint32_t unused : 5; - uint32_t sub_sof : 1; - volatile uint32_t eof : 1; - volatile uint32_t owner : 1; - uint32_t *buf_ptr; - uint32_t *next_link_ptr; + uint32_t blocksize : 12; + uint32_t datalen : 12; + uint32_t unused : 5; + uint32_t sub_sof : 1; + uint32_t eof : 1; + volatile uint32_t owner : 1; // DMA can change this value + uint32_t * buf_ptr; + struct slc_queue_item * next_link_ptr; // This is my own way of tracking item ID - volatile uint32_t myid; + volatile uint32_t myid; } slc_queue_item_t; typedef struct i2s_state { @@ -210,7 +210,7 @@ static void ICACHE_FLASH_ATTR _alloc_channel(i2s_state_t *ch) { ch->slc_items[x].datalen = SLC_BUF_LEN * 4; ch->slc_items[x].blocksize = SLC_BUF_LEN * 4; ch->slc_items[x].buf_ptr = (uint32_t*)&ch->slc_buf_pntr[x][0]; - ch->slc_items[x].next_link_ptr = (uint32_t*)((x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0])); + ch->slc_items[x].next_link_ptr = (x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0]); ch->slc_items[x].myid = 0; } } diff --git a/libraries/esp8266/examples/I2SInput/I2SInput.ino b/libraries/esp8266/examples/I2SInput/I2SInput.ino new file mode 100644 index 0000000000..a9f37867d6 --- /dev/null +++ b/libraries/esp8266/examples/I2SInput/I2SInput.ino @@ -0,0 +1,48 @@ +/* + I2S stereo microphone (input) example + Run using the Arduion Serial Plotter to see waveform. + Released to the Public Domain by Earle F. Philhower, III +*/ + +#include +#include + +void dump() +{ + Serial.printf("I2SC: %08x\n", I2SC); + Serial.printf("I2SFC: %08x\n", I2SFC); + Serial.printf("I2SCC: %08x\n", I2SCC); + Serial.printf("I2SRXEN: %08x\n", I2SRXEN); + Serial.printf("SLCC0: %08x\n", SLCC0); + Serial.printf("SLCRXDC: %08x\n", SLCRXDC); + Serial.printf("SLCTXL: %08x\n", SLCTXL); + Serial.printf("SLCIE: %08x\n", SLCIE); +} + +void setup() { + Serial.begin(115200); + WiFi.forceSleepBegin(); + delay(500); + + i2s_rxtx_begin(true, false); // Enable I2S RX + i2s_set_rate(11025); + dump(); + + delay(1000); + + while (1) { + uint32_t l, r; + i2s_read_sample(&l, &r, true); + int16_t lh = l>>16; + int16_t rh = r>>16; + char withScale[256]; + sprintf(withScale, "%d %d", lh, rh); + Serial.println(withScale); + yield(); + } +} + +void loop() { +} + + From bdef11153ba4d286ea5ff963c7ccaef353a6b8ff Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 22 Mar 2018 07:27:32 -0700 Subject: [PATCH 05/13] Address @devyte's style and code issues --- cores/esp8266/core_esp8266_i2s.c | 105 +++++++++--------- .../esp8266/examples/I2SInput/I2SInput.ino | 14 +-- 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 7e0ada29ce..ab46a6aa7e 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -65,7 +65,7 @@ typedef struct i2s_state { uint32_t * curr_slc_buf; // Current buffer for writing uint32_t curr_slc_buf_pos; // Position in the current buffer void (*callback) (void); - // Callback function should be defined as 'void ICACHE_FLASH_ATTR function_name()', + // Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()', // and be placed in IRAM for faster execution. Avoid long computational tasks in this // function, use it to set flags and process later. } i2s_state_t; @@ -74,65 +74,65 @@ typedef struct i2s_state { static i2s_state_t *rx = NULL; static i2s_state_t *tx = NULL; -volatile int rx_irqs = 0; -volatile int tx_irqs = 0; +volatile uint32_t rx_irqs = 0; +volatile uint32_t tx_irqs = 0; // Some constants that aren't defined in i2s_regs.h +#define I2SO_DATA 3 +#define I2SO_BCK 15 +#define I2SO_WS 2 #define I2SI_DATA 12 #define I2SI_BCK 13 #define I2SI_WS 14 -static bool ICACHE_FLASH_ATTR _i2s_is_full(const i2s_state_t *ch) { +static bool _i2s_is_full(const i2s_state_t *ch) { if (!ch) { return false; - } else { - return (ch->curr_slc_buf_pos==SLC_BUF_LEN || ch->curr_slc_buf==NULL) && (ch->slc_queue_len == 0); } + return (ch->curr_slc_buf_pos==SLC_BUF_LEN || ch->curr_slc_buf==NULL) && (ch->slc_queue_len == 0); } -bool ICACHE_FLASH_ATTR i2s_is_full() { +bool i2s_is_full() { return _i2s_is_full( tx ); } -bool ICACHE_FLASH_ATTR i2s_rx_is_full() { +bool i2s_rx_is_full() { return _i2s_is_full( rx ); } -static bool ICACHE_FLASH_ATTR _i2s_is_empty(const i2s_state_t *ch) { +static bool _i2s_is_empty(const i2s_state_t *ch) { if (!ch) { return false; - } else { - return (ch->slc_queue_len >= SLC_BUF_CNT-1); } + return (ch->slc_queue_len >= SLC_BUF_CNT-1); } -bool ICACHE_FLASH_ATTR i2s_is_empty() { +bool i2s_is_empty() { return _i2s_is_empty( tx ); } -bool ICACHE_FLASH_ATTR i2s_rx_is_empty() { +bool i2s_rx_is_empty() { return _i2s_is_empty( rx ); } -static int16_t ICACHE_FLASH_ATTR _i2s_available(const i2s_state_t *ch) { +static int16_t _i2s_available(const i2s_state_t *ch) { if (!ch) { return 0; - } else { - return (SLC_BUF_CNT - ch->slc_queue_len) * SLC_BUF_LEN; } + return (SLC_BUF_CNT - ch->slc_queue_len) * SLC_BUF_LEN; } -int16_t ICACHE_FLASH_ATTR i2s_available(){ +int16_t i2s_available(){ return _i2s_available( tx ); } -int16_t ICACHE_FLASH_ATTR i2s_rx_available(){ +int16_t i2s_rx_available(){ return _i2s_available( rx ); } // Pop the top off of the queue and return it -uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { +static uint32_t * ICACHE_RAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { uint8_t i; uint32_t *item = ch->slc_queue[0]; ch->slc_queue_len--; @@ -143,7 +143,7 @@ uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) { } // Append an item to the end of the queue from receive -void ICACHE_FLASH_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) { +static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) { // Shift everything up, except for the one corresponding to this item for (int i=0, dest=0; i < ch->slc_queue_len; i++) { if (ch->slc_queue[i] != item) { @@ -160,7 +160,7 @@ void ICACHE_FLASH_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item // 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. -void ICACHE_FLASH_ATTR i2s_slc_isr(void) { +void ICACHE_RAM_ATTR i2s_slc_isr(void) { uint32_t slc_intr_status = SLCIS; SLCIC = 0xFFFFFFFF; if (slc_intr_status & SLCIRXEOF) { @@ -172,7 +172,9 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { i2s_slc_queue_next_item(tx); //free space for finished_item } tx->slc_queue[tx->slc_queue_len++] = finished_item->buf_ptr; - if (tx->callback) tx->callback(); + if (tx->callback) { + tx->callback(); + } ETS_SLC_INTR_ENABLE(); } if (slc_intr_status & SLCITXEOF) { @@ -182,7 +184,9 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { finished_item->owner = 1; // Or else RX just stops finished_item->myid++; i2s_slc_queue_append_item(rx, finished_item->buf_ptr); - if (rx->callback) rx->callback(); + if (rx->callback) { + rx->callback(); + } ETS_SLC_INTR_ENABLE(); } } @@ -195,13 +199,11 @@ void i2s_rx_set_callback(void (*callback) (void)){ rx->callback = callback; } -static void ICACHE_FLASH_ATTR _alloc_channel(i2s_state_t *ch) { - int x, y; - +static void _alloc_channel(i2s_state_t *ch) { ch->slc_queue_len = 0; - for (x=0; xslc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); - for (y=0; yslc_buf_pntr[x][y] = 0; + memset(ch->slc_buf_pntr[x], 0, SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[x][0])); ch->slc_items[x].unused = 0; ch->slc_items[x].owner = 1; @@ -225,7 +227,7 @@ rx->slc_items[i].buf_ptr, rx->slc_items[i].next_link_ptr); #endif -static void ICACHE_FLASH_ATTR i2s_slc_begin() { +static void i2s_slc_begin() { if (tx) { _alloc_channel(tx); } @@ -242,7 +244,7 @@ static void ICACHE_FLASH_ATTR i2s_slc_begin() { 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 + 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 @@ -256,7 +258,7 @@ static void ICACHE_FLASH_ATTR i2s_slc_begin() { SLCTXL |= (uint32)&rx->slc_items[0] << SLCTXLA; // Set real RX address } if (!tx) { -// SLCRXL |= (uint32)&rx->slc_items[1] << SLCRXLA; // Set fake (ununsed) TX descriptor address + SLCRXL |= (uint32)&rx->slc_items[1] << SLCRXLA; // Set fake (ununsed) TX descriptor address } else { SLCRXL |= (uint32)&tx->slc_items[0] << SLCRXLA; // Set real TX address } @@ -273,7 +275,7 @@ static void ICACHE_FLASH_ATTR i2s_slc_begin() { } } -static void ICACHE_FLASH_ATTR i2s_slc_end(){ +static void i2s_slc_end(){ ETS_SLC_INTR_DISABLE(); SLCIC = 0xFFFFFFFF; SLCIE = 0; @@ -296,7 +298,7 @@ static void ICACHE_FLASH_ATTR i2s_slc_end(){ //at least the current sample rate. You can also call it quicker: it will suspend the calling //thread if the buffer is full and resume when there's room again. -static bool ICACHE_FLASH_ATTR _i2s_write_sample(uint32_t sample, bool nb) { +static bool _i2s_write_sample(uint32_t sample, bool nb) { if (tx->curr_slc_buf_pos==SLC_BUF_LEN || tx->curr_slc_buf==NULL) { if (tx->slc_queue_len == 0) { if (nb) return false; @@ -326,14 +328,14 @@ bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) { return _i2s_write_sample(sample, true); } -bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){ +bool i2s_write_lr(int16_t left, int16_t right){ int sample = right & 0xFFFF; sample = sample << 16; sample |= left & 0xFFFF; return i2s_write_sample(sample); } -bool ICACHE_FLASH_ATTR i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { +bool i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { if (rx->curr_slc_buf_pos==SLC_BUF_LEN || rx->curr_slc_buf==NULL) { if (rx->slc_queue_len == 0) { if (!blocking) return false; @@ -352,22 +354,15 @@ bool ICACHE_FLASH_ATTR i2s_read_sample(uint32_t *left, uint32_t *right, bool blo rx->curr_slc_buf_pos=0; } -// *left = rx->slc_items[0].buf_ptr[rx->curr_slc_buf_pos++]; -// *right = rx->slc_items[0].buf_ptr[rx->curr_slc_buf_pos++]; *left = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; *right = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; return true; } -// END DMA -// ========= -// START I2S - - static uint32_t _i2s_sample_rate; -void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ +void i2s_set_rate(uint32_t rate){ //Rate in HZ if(rate == _i2s_sample_rate) return; _i2s_sample_rate = rate; @@ -392,7 +387,7 @@ void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ I2SC |= I2SRF | I2SMR | I2SRMS | ((sbd_div_best) << I2SBD) | ((scd_div_best) << I2SCD); } -void ICACHE_FLASH_ATTR i2s_set_dividers(uint8_t div1, uint8_t div2){ +void i2s_set_dividers(uint8_t div1, uint8_t div2){ div1 &= I2SBDM; div2 &= I2SCDM; @@ -400,11 +395,11 @@ void ICACHE_FLASH_ATTR i2s_set_dividers(uint8_t div1, uint8_t div2){ I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (div1 << I2SBD) | (div2 << I2SCD); } -float ICACHE_FLASH_ATTR i2s_get_real_rate(){ +float i2s_get_real_rate(){ return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM); } -void ICACHE_FLASH_ATTR i2s_rxtx_begin(bool enableRx, bool enableTx) { +void i2s_rxtx_begin(bool enableRx, bool enableTx) { if (tx || rx) { i2s_end(); // Stop and free any ongoing stuff } @@ -414,9 +409,9 @@ void ICACHE_FLASH_ATTR i2s_rxtx_begin(bool enableRx, bool enableTx) { if (!tx) { return; // OOM Error! } - pinMode(2, FUNCTION_1); // I2SO_WS (LRCK) - pinMode(3, FUNCTION_1); // I2SO_DATA (SDIN) - pinMode(15, FUNCTION_1); // I2SO_BCK (SCLK) + pinMode(I2SO_WS, FUNCTION_1); + pinMode(I2SO_DATA, FUNCTION_1); + pinMode(I2SO_BCK, FUNCTION_1); } if (enableRx) { rx = (i2s_state_t*)calloc(1, sizeof(*rx)); @@ -460,14 +455,14 @@ void ICACHE_FLASH_ATTR i2s_rxtx_begin(bool enableRx, bool enableTx) { I2SC |= (rx?I2SRXS:0) | (tx?I2STXS:0); // Start transmission/reception } -void ICACHE_FLASH_ATTR i2s_begin() { +void i2s_begin() { i2s_rxtx_begin(false, true); } -void ICACHE_FLASH_ATTR i2s_end() { +void i2s_end() { I2SC &= ~I2STXS; - //Reset I2S + // Reset I2S I2SC &= ~(I2SRST); I2SC |= I2SRST; I2SC &= ~(I2SRST); @@ -475,9 +470,9 @@ void ICACHE_FLASH_ATTR i2s_end() { i2s_slc_end(); if (tx) { - pinMode(2, INPUT); - pinMode(3, INPUT); - pinMode(15, INPUT); + pinMode(I2SO_DATA, INPUT); + pinMode(I2SO_BCK, INPUT); + pinMode(I2SO_WS, INPUT); free(tx); tx = NULL; } diff --git a/libraries/esp8266/examples/I2SInput/I2SInput.ino b/libraries/esp8266/examples/I2SInput/I2SInput.ino index a9f37867d6..2f91e9bf1b 100644 --- a/libraries/esp8266/examples/I2SInput/I2SInput.ino +++ b/libraries/esp8266/examples/I2SInput/I2SInput.ino @@ -1,5 +1,5 @@ /* - I2S stereo microphone (input) example + I2S stereo microphone (input) example Run using the Arduion Serial Plotter to see waveform. Released to the Public Domain by Earle F. Philhower, III */ @@ -7,8 +7,7 @@ #include #include -void dump() -{ +void dump() { Serial.printf("I2SC: %08x\n", I2SC); Serial.printf("I2SFC: %08x\n", I2SFC); Serial.printf("I2SCC: %08x\n", I2SCC); @@ -29,12 +28,12 @@ void setup() { dump(); delay(1000); - + while (1) { uint32_t l, r; i2s_read_sample(&l, &r, true); - int16_t lh = l>>16; - int16_t rh = r>>16; + int16_t lh = l >> 16; + int16_t rh = r >> 16; char withScale[256]; sprintf(withScale, "%d %d", lh, rh); Serial.println(withScale); @@ -43,6 +42,5 @@ void setup() { } void loop() { + /* Nothing here */ } - - From d2af6fa239c94179199fef32d71ab9b1bc876fa0 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 22 Mar 2018 09:34:59 -0700 Subject: [PATCH 06/13] Note IRQ potential race condition --- cores/esp8266/core_esp8266_i2s.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index ab46a6aa7e..8ca281dbbd 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -53,8 +53,6 @@ typedef struct slc_queue_item { volatile uint32_t owner : 1; // DMA can change this value uint32_t * buf_ptr; struct slc_queue_item * next_link_ptr; - // This is my own way of tracking item ID - volatile uint32_t myid; } slc_queue_item_t; typedef struct i2s_state { @@ -158,13 +156,16 @@ static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t } // 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 +// handle here is the *_EOF_INT status, which indicate the DMA has finished a buffer whose // descriptor has the 'EOF' field set to 1. void ICACHE_RAM_ATTR i2s_slc_isr(void) { + ETS_SLC_INTR_DISABLE(); + // TODO - Seems like there's a chance of missed IRQ notification because the clear happens at least 1 cycle + // after the status read. Not sure if there's a HW way to prevernt this, even atomic SWAP + // won't help since these are 2 separate addresses. Ugh! uint32_t slc_intr_status = SLCIS; SLCIC = 0xFFFFFFFF; if (slc_intr_status & SLCIRXEOF) { - ETS_SLC_INTR_DISABLE(); tx_irqs++; slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA; memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);//zero the buffer so it is mute in case of underflow @@ -175,20 +176,17 @@ void ICACHE_RAM_ATTR i2s_slc_isr(void) { if (tx->callback) { tx->callback(); } - ETS_SLC_INTR_ENABLE(); } if (slc_intr_status & SLCITXEOF) { - ETS_SLC_INTR_DISABLE(); rx_irqs++; slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCTXEDA; finished_item->owner = 1; // Or else RX just stops - finished_item->myid++; i2s_slc_queue_append_item(rx, finished_item->buf_ptr); if (rx->callback) { rx->callback(); } - ETS_SLC_INTR_ENABLE(); } + ETS_SLC_INTR_ENABLE(); } void i2s_set_callback(void (*callback) (void)){ @@ -213,14 +211,13 @@ static void _alloc_channel(i2s_state_t *ch) { ch->slc_items[x].blocksize = SLC_BUF_LEN * 4; ch->slc_items[x].buf_ptr = (uint32_t*)&ch->slc_buf_pntr[x][0]; ch->slc_items[x].next_link_ptr = (x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0]); - ch->slc_items[x].myid = 0; } } #if 0 void dumprx() { for (int i=0; islc_items[i].myid, rx->slc_items[i].owner, rx->slc_items[i].eof, rx->slc_items[i].sub_sof, rx->slc_items[i].datalen, rx->slc_items[i].blocksize, +printf("%d: %d %d %d %d %d %d %p %p\n", i, 0, rx->slc_items[i].owner, rx->slc_items[i].eof, rx->slc_items[i].sub_sof, rx->slc_items[i].datalen, rx->slc_items[i].blocksize, rx->slc_items[i].buf_ptr, rx->slc_items[i].next_link_ptr); } } From 4472df0f2feab0258327c96f00792fc0750de622 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 22 Mar 2018 10:27:00 -0700 Subject: [PATCH 07/13] Fix WDT resets in sample push/pop est_wdt_(dis/en)able were being called instead of yield in the i2s_(read/write)_sample branches, but those calls don't actually reset the WDT. Replace with optimistic_yield(10ms). --- cores/esp8266/core_esp8266_i2s.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 8ca281dbbd..f1309e1cd2 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -24,12 +24,10 @@ #include "osapi.h" #include "ets_sys.h" + #include "i2s_reg.h" #include "i2s.h" -extern void ets_wdt_enable(void); -extern void ets_wdt_disable(void); - #define SLC_BUF_CNT (8) // Number of buffers in the I2S circular buffer #define SLC_BUF_LEN (64) // Length of one buffer, in 32-bit words. @@ -303,8 +301,7 @@ static bool _i2s_write_sample(uint32_t sample, bool nb) { if (tx->slc_queue_len > 0){ break; } else { - ets_wdt_disable(); - ets_wdt_enable(); + optimistic_yield(10000); } } } @@ -340,8 +337,7 @@ bool i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { if (rx->slc_queue_len > 0){ break; } else { - ets_wdt_disable(); - ets_wdt_enable(); + optimistic_yield(10000); } } } From 1285d9d1765fafe72dbe60aa2775cc82cf634c87 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 27 Mar 2018 20:10:22 -0700 Subject: [PATCH 08/13] Correct I2S receive mode to be master Fix mistake in merge. I2S recevive was in slave mode (external clock driven). --- cores/esp8266/core_esp8266_i2s.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 178699046a..161c6e855e 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -381,15 +381,14 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) { div1 &= I2SBDM; div2 &= I2SCDM; - // !trans master(?), !bits mod(==16 bits/chanel), clear clock dividers - I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); + // !trans master(?), !recv master(?), !bits mod(==16 bits/chanel), clear clock dividers + I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); // I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left) // I2SMR = MSB recv/xmit first - // I2SRSM = Receive slave mode (?) // I2SRMS, I2STMS = 1-bit delay from WS to MSB (I2S format) // div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this - I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD); + I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD); } float i2s_get_real_rate(){ From bc743d0360c50baa1531005f039869eeedebc7d3 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 27 Mar 2018 20:29:20 -0700 Subject: [PATCH 09/13] Add intro usage text to example Mostly for my edification, list the IO pins and where to find them on the D1 mini and other boards, and how to use the AIY Voice Hat. --- .../esp8266/examples/I2SInput/I2SInput.ino | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libraries/esp8266/examples/I2SInput/I2SInput.ino b/libraries/esp8266/examples/I2SInput/I2SInput.ino index 2f91e9bf1b..cb2b4f64b6 100644 --- a/libraries/esp8266/examples/I2SInput/I2SInput.ino +++ b/libraries/esp8266/examples/I2SInput/I2SInput.ino @@ -2,6 +2,29 @@ I2S stereo microphone (input) example Run using the Arduion Serial Plotter to see waveform. Released to the Public Domain by Earle F. Philhower, III + + For the Google AIY Voice Hat Microphone daughterboard, part + of the Raspberry Pi AIY cardboard box, the I2S stereo pinout + looking at the board top with the RPI logo on the left hand + side: + +-- ------------------------------------ --+ + left RPI | (1) GND (2) DIN (3) BCLK (4) LRCLK (5) 3.3V | AIY right + +---------------------------------------------+ + + The I2S pins are on different pins depending on your board. + The *internal GPIO number* which is NOT NECESSARIALY the + same as the pin numbers, are as follows: + I2SI_DATA = GPIO12 + IS2I_BCK = GPIO13 + I2SI_WS/LRCLK = GPIO14 + + On the D1 mini the I2SI pins map to the following D pins: + I2SI_DATA = GPIO12 = D6 + IS2I_BCK = GPIO13 = D7 + I2SI_WS/LRCLK = GPIO14 = D5 + + Expect different D pins on different ESP8266 boards, and of + course be sure to wire up VCC(3.3V) and GND. */ #include From 0039dbc5447c8f815a24356be9a567b993c4785d Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 27 Mar 2018 20:40:49 -0700 Subject: [PATCH 10/13] Move to 16bps per channel like xmit, clean up Some I2S configuration and DMA tweaks makes 16 bits per sample per channel work, giving a basically identical interface as xmit. The received data values now span the full range of +/-32767 as well. --- cores/esp8266/core_esp8266_i2s.c | 28 +++++-------------- cores/esp8266/i2s.h | 2 +- .../esp8266/examples/I2SInput/I2SInput.ino | 18 ++---------- 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 161c6e855e..0264c89f3a 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -70,9 +70,6 @@ typedef struct i2s_state { static i2s_state_t *rx = NULL; static i2s_state_t *tx = NULL; -volatile uint32_t rx_irqs = 0; -volatile uint32_t tx_irqs = 0; - // IOs used for I2S. Not defined in i2s.h, unfortunately. // Note these are internal IOs numbers and not pins on an // Arduino board. Users need to verify their particular wiring. @@ -159,7 +156,6 @@ void ICACHE_RAM_ATTR i2s_slc_isr(void) { uint32_t slc_intr_status = SLCIS; SLCIC = 0xFFFFFFFF; if (slc_intr_status & SLCIRXEOF) { - tx_irqs++; slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA; // Zero the buffer so it is mute in case of underflow ets_memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4); @@ -173,7 +169,6 @@ void ICACHE_RAM_ATTR i2s_slc_isr(void) { } } if (slc_intr_status & SLCITXEOF) { - rx_irqs++; slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCTXEDA; // Set owner back to 1 (SW) or else RX stops. TX has no such restriction. finished_item->owner = 1; @@ -210,16 +205,6 @@ static void _alloc_channel(i2s_state_t *ch) { } } -#if 0 -void dumprx() -{ -for (int i=0; islc_items[i].owner, rx->slc_items[i].eof, rx->slc_items[i].sub_sof, rx->slc_items[i].datalen, rx->slc_items[i].blocksize, -rx->slc_items[i].buf_ptr, rx->slc_items[i].next_link_ptr); -} -} -#endif - static void i2s_slc_begin() { if (tx) { _alloc_channel(tx); @@ -327,7 +312,7 @@ bool i2s_write_lr(int16_t left, int16_t right){ return i2s_write_sample(sample); } -bool i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { +bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) { if (rx->curr_slc_buf_pos==SLC_BUF_LEN || rx->curr_slc_buf==NULL) { if (rx->slc_queue_len == 0) { if (!blocking) return false; @@ -345,8 +330,9 @@ bool i2s_read_sample(uint32_t *left, uint32_t *right, bool blocking) { rx->curr_slc_buf_pos=0; } - *left = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; - *right = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; + uint32_t sample = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; + *left = sample & 0xffff; + *right = sample >> 16; return true; } @@ -381,7 +367,7 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) { div1 &= I2SBDM; div2 &= I2SCDM; - // !trans master(?), !recv master(?), !bits mod(==16 bits/chanel), clear clock dividers + // trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); // I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left) @@ -438,7 +424,7 @@ void i2s_rxtx_begin(bool enableRx, bool enableTx) { // I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) - I2SFC |= I2SDE | (rx ? 2/*24bpc, 2ch*/< Dual channel mode I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set RX/TX CHAN_MOD=0 @@ -446,9 +432,9 @@ void i2s_rxtx_begin(bool enableRx, bool enableTx) { i2s_set_rate(44100); if (rx) { + // Need to prime the # of samples to receive in the engine I2SRXEN = SLC_BUF_LEN; } -// I2SC |= (15< #include -void dump() { - Serial.printf("I2SC: %08x\n", I2SC); - Serial.printf("I2SFC: %08x\n", I2SFC); - Serial.printf("I2SCC: %08x\n", I2SCC); - Serial.printf("I2SRXEN: %08x\n", I2SRXEN); - Serial.printf("SLCC0: %08x\n", SLCC0); - Serial.printf("SLCRXDC: %08x\n", SLCRXDC); - Serial.printf("SLCTXL: %08x\n", SLCTXL); - Serial.printf("SLCIE: %08x\n", SLCIE); -} - void setup() { Serial.begin(115200); WiFi.forceSleepBegin(); @@ -48,17 +37,14 @@ void setup() { i2s_rxtx_begin(true, false); // Enable I2S RX i2s_set_rate(11025); - dump(); delay(1000); while (1) { - uint32_t l, r; + int16_t l, r; i2s_read_sample(&l, &r, true); - int16_t lh = l >> 16; - int16_t rh = r >> 16; char withScale[256]; - sprintf(withScale, "%d %d", lh, rh); + sprintf(withScale, "%d %d", l, r); Serial.println(withScale); yield(); } From 81f7f217a582360816c2c4c3a38b5731c3c489e4 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 27 Mar 2018 21:33:34 -0700 Subject: [PATCH 11/13] Add I2S input and transmit over WiFi example Very simple example sends I2S microphone data using UDP over WiFi. Runs very well and proves the I2S receive DMA buffer management is working pretty well. --- .../examples/I2STransmit/I2STransmit.ino | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 libraries/esp8266/examples/I2STransmit/I2STransmit.ino diff --git a/libraries/esp8266/examples/I2STransmit/I2STransmit.ino b/libraries/esp8266/examples/I2STransmit/I2STransmit.ino new file mode 100644 index 0000000000..17c5cbd985 --- /dev/null +++ b/libraries/esp8266/examples/I2STransmit/I2STransmit.ino @@ -0,0 +1,75 @@ +/* + I2S stereo microphone (input) UDP transmitter + Needs a UDP listener (netcat/etc.) on port 8266 on the PC + + Under Linux: + nc -u -p 8266 -l | play -t raw -r 11025 -b 16 -c 2 -e signed-integer - + + Released to the Public Domain by Earle F. Philhower, III +*/ + +#include +#include +#include + +// Set your network here +const char *SSID = "...."; +const char *PASS = "...."; + +WiFiUDP udp; +// Set your listener PC's IP here: +const IPAddress listener = { 192, 168, 1, 2 }; +const int port = 8266; + +int16_t buffer[100][2]; // Temp staging for samples + +void setup() { + Serial.begin(115200); + + // Connect to WiFi network + Serial.println(); + Serial.println(); + Serial.print("Connecting to "); + Serial.println(SSID); + + WiFi.begin(SSID, PASS); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("My IP: "); + Serial.println(WiFi.localIP()); + + i2s_rxtx_begin(true, false); // Enable I2S RX + i2s_set_rate(11025); + + Serial.print("\nStart the listener on "); + Serial.print(listener); + Serial.print(":"); + Serial.println(port); + Serial.println("ex: nc -u -p 8266 -l | play -t raw -r 11025 -b 16 -c 2 -e signed-integer -"); + + udp.beginPacket(listener, port); + udp.write("I2S Receiver\r\n"); + udp.endPacket(); + +} + +void loop() { + static int cnt = 0; + // Each loop will send 100 raw samples (400 bytes) + // UDP needs to be < TCP_MSS which can be 500 bytes in LWIP2 + for (int i=0; i<100; i++) { + i2s_read_sample(&buffer[i][0], &buffer[i][1], true); + } + udp.beginPacket(listener, port); + udp.write((uint8_t*)buffer, sizeof(buffer)); + udp.endPacket(); + cnt++; + if ((cnt % 100) == 0) { + Serial.printf("%d\n", cnt); + } +} From ea0699cad36c86c7d1a12f67e1fcce21b4cb276d Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 28 Mar 2018 06:56:47 -0700 Subject: [PATCH 12/13] Fix example formatting, ugh. --- cores/esp8266/core_esp8266_i2s.c | 2 +- libraries/esp8266/examples/I2STransmit/I2STransmit.ino | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 0264c89f3a..53ad05f941 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -222,7 +222,7 @@ static void i2s_slc_begin() { 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 + 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 diff --git a/libraries/esp8266/examples/I2STransmit/I2STransmit.ino b/libraries/esp8266/examples/I2STransmit/I2STransmit.ino index 17c5cbd985..cdf2cfd0f7 100644 --- a/libraries/esp8266/examples/I2STransmit/I2STransmit.ino +++ b/libraries/esp8266/examples/I2STransmit/I2STransmit.ino @@ -62,7 +62,7 @@ void loop() { static int cnt = 0; // Each loop will send 100 raw samples (400 bytes) // UDP needs to be < TCP_MSS which can be 500 bytes in LWIP2 - for (int i=0; i<100; i++) { + for (int i = 0; i < 100; i++) { i2s_read_sample(&buffer[i][0], &buffer[i][1], true); } udp.beginPacket(listener, port); From c86413bc018cbdf537347d73e814f028644adcf6 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 28 Mar 2018 13:01:24 -0700 Subject: [PATCH 13/13] Add better OOM error checking, formatting Clean up a few dangling 1-line-ifs, make the new I2S code check for an OOM error and return it up to the app instead of crashing when it tries to memset a NULL pointer. Old comments updated to new structure. --- cores/esp8266/core_esp8266_i2s.c | 99 +++++++++++++++++++++----------- cores/esp8266/i2s.h | 4 +- 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 53ad05f941..046c6dd064 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -70,8 +70,11 @@ typedef struct i2s_state { static i2s_state_t *rx = NULL; static i2s_state_t *tx = NULL; +// Last I2S sample rate requested +static uint32_t _i2s_sample_rate; + // IOs used for I2S. Not defined in i2s.h, unfortunately. -// Note these are internal IOs numbers and not pins on an +// Note these are internal GPIO numbers and not pins on an // Arduino board. Users need to verify their particular wiring. #define I2SO_DATA 3 #define I2SO_BCK 15 @@ -151,7 +154,7 @@ static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t } } -void ICACHE_RAM_ATTR i2s_slc_isr(void) { +static void ICACHE_RAM_ATTR i2s_slc_isr(void) { ETS_SLC_INTR_DISABLE(); uint32_t slc_intr_status = SLCIS; SLCIC = 0xFFFFFFFF; @@ -180,18 +183,22 @@ void ICACHE_RAM_ATTR i2s_slc_isr(void) { ETS_SLC_INTR_ENABLE(); } -void i2s_set_callback(void (*callback) (void)){ +void i2s_set_callback(void (*callback) (void)) { tx->callback = callback; } -void i2s_rx_set_callback(void (*callback) (void)){ +void i2s_rx_set_callback(void (*callback) (void)) { rx->callback = callback; } -static void _alloc_channel(i2s_state_t *ch) { +static bool _alloc_channel(i2s_state_t *ch) { ch->slc_queue_len = 0; for (int x=0; xslc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); + ch->slc_buf_pntr[x] = (uint32_t *)malloc(SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[0][0])); + if (!ch->slc_buf_pntr[x]) { + // OOM, the upper layer will free up any partially allocated channels. + return false; + } memset(ch->slc_buf_pntr[x], 0, SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[x][0])); ch->slc_items[x].unused = 0; @@ -203,14 +210,19 @@ static void _alloc_channel(i2s_state_t *ch) { ch->slc_items[x].buf_ptr = (uint32_t*)&ch->slc_buf_pntr[x][0]; ch->slc_items[x].next_link_ptr = (x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0]); } + return true; } -static void i2s_slc_begin() { +static bool i2s_slc_begin() { if (tx) { - _alloc_channel(tx); + if (!_alloc_channel(tx)) { + return false; + } } if (rx) { - _alloc_channel(rx); + if (!_alloc_channel(rx)) { + return false; + } } ETS_SLC_INTR_DISABLE(); @@ -236,7 +248,7 @@ static void i2s_slc_begin() { SLCTXL |= (uint32)&rx->slc_items[0] << SLCTXLA; // Set real RX address } if (!tx) { - SLCRXL |= (uint32)&rx->slc_items[1] << SLCRXLA; // Set fake (ununsed) TX descriptor address + SLCRXL |= (uint32)&rx->slc_items[1] << SLCRXLA; // Set fake (unused) TX descriptor address } else { SLCRXL |= (uint32)&tx->slc_items[0] << SLCRXLA; // Set real TX address } @@ -251,6 +263,8 @@ static void i2s_slc_begin() { if (tx) { SLCRXL |= SLCRXLS; } + + return true; } static void i2s_slc_end(){ @@ -272,16 +286,21 @@ static void i2s_slc_end(){ } } -//This routine pushes a single, 32-bit sample to the I2S buffers. Call this at (on average) -//at least the current sample rate. You can also call it quicker: it will suspend the calling -//thread if the buffer is full and resume when there's room again. - +// These routines push a single, 32-bit sample to the I2S buffers. Call at (on average) +// at least the current sample rate. static bool _i2s_write_sample(uint32_t sample, bool nb) { + if (!tx) { + return false; + } + if (tx->curr_slc_buf_pos==SLC_BUF_LEN || tx->curr_slc_buf==NULL) { if (tx->slc_queue_len == 0) { - if (nb) return false; + if (nb) { + // Don't wait if nonblocking, just notify upper levels + return false; + } while (1) { - if (tx->slc_queue_len > 0){ + if (tx->slc_queue_len > 0) { break; } else { optimistic_yield(10000); @@ -297,11 +316,11 @@ static bool _i2s_write_sample(uint32_t sample, bool nb) { return true; } -bool ICACHE_FLASH_ATTR i2s_write_sample(uint32_t sample) { +bool i2s_write_sample(uint32_t sample) { return _i2s_write_sample(sample, false); } -bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) { +bool i2s_write_sample_nb(uint32_t sample) { return _i2s_write_sample(sample, true); } @@ -313,9 +332,14 @@ bool i2s_write_lr(int16_t left, int16_t right){ } bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) { + if (!rx) { + return false; + } if (rx->curr_slc_buf_pos==SLC_BUF_LEN || rx->curr_slc_buf==NULL) { if (rx->slc_queue_len == 0) { - if (!blocking) return false; + if (!blocking) { + return false; + } while (1) { if (rx->slc_queue_len > 0){ break; @@ -331,16 +355,21 @@ bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) { } uint32_t sample = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; - *left = sample & 0xffff; - *right = sample >> 16; + if (left) { + *left = sample & 0xffff; + } + if (right) { + *right = sample >> 16; + } return true; } -static uint32_t _i2s_sample_rate; -void i2s_set_rate(uint32_t rate){ //Rate in HZ - if(rate == _i2s_sample_rate) return; +void i2s_set_rate(uint32_t rate) { //Rate in HZ + if (rate == _i2s_sample_rate) { + return; + } _i2s_sample_rate = rate; uint32_t scaled_base_freq = I2SBASEFREQ/32; @@ -348,8 +377,8 @@ void i2s_set_rate(uint32_t rate){ //Rate in HZ uint8_t sbd_div_best=1; uint8_t scd_div_best=1; - for (uint8_t i=1; i<64; i++){ - for (uint8_t j=i; j<64; j++){ + for (uint8_t i=1; i<64; i++) { + for (uint8_t j=i; j<64; j++) { float new_delta = fabs(((float)scaled_base_freq/i/j) - rate); if (new_delta < delta_best){ delta_best = new_delta; @@ -381,7 +410,7 @@ float i2s_get_real_rate(){ return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM); } -void i2s_rxtx_begin(bool enableRx, bool enableTx) { +bool i2s_rxtx_begin(bool enableRx, bool enableTx) { if (tx || rx) { i2s_end(); // Stop and free any ongoing stuff } @@ -389,7 +418,8 @@ void i2s_rxtx_begin(bool enableRx, bool enableTx) { if (enableTx) { tx = (i2s_state_t*)calloc(1, sizeof(*tx)); if (!tx) { - return; // OOM Error! + // Nothing to clean up yet + return false; // OOM Error! } pinMode(I2SO_WS, FUNCTION_1); pinMode(I2SO_DATA, FUNCTION_1); @@ -398,9 +428,8 @@ void i2s_rxtx_begin(bool enableRx, bool enableTx) { if (enableRx) { rx = (i2s_state_t*)calloc(1, sizeof(*rx)); if (!rx) { - free(tx); - tx = NULL; - return; // OOM error! + i2s_end(); // Clean up any TX or pin changes + return false; // OOM error! } pinMode(I2SI_WS, OUTPUT); pinMode(I2SI_BCK, OUTPUT); @@ -411,7 +440,11 @@ void i2s_rxtx_begin(bool enableRx, bool enableTx) { } _i2s_sample_rate = 0; - i2s_slc_begin(); + if (!i2s_slc_begin()) { + // OOM in SLC memory allocations, tear it all down and abort! + i2s_end(); + return false; + } I2S_CLK_ENABLE(); I2SIC = 0x3F; @@ -437,6 +470,8 @@ void i2s_rxtx_begin(bool enableRx, bool enableTx) { } I2SC |= (rx?I2SRXS:0) | (tx?I2STXS:0); // Start transmission/reception + + return true; } void i2s_begin() { diff --git a/cores/esp8266/i2s.h b/cores/esp8266/i2s.h index 60dbe3ece2..6f1761e6fa 100644 --- a/cores/esp8266/i2s.h +++ b/cores/esp8266/i2s.h @@ -41,7 +41,7 @@ extern "C" { #endif void i2s_begin(); // Enable TX only, for compatibility -void i2s_rxtx_begin(bool enableRx, bool enableTx); // allow TX and/or RX +bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error void i2s_end(); void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000) void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate @@ -49,7 +49,7 @@ float i2s_get_real_rate();//The actual Sample Rate on output bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full) bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result -bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data, 2x32 bits, left-aligned +bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs. bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow) bool i2s_is_empty();//returns true if DMA is empty (underflow) bool i2s_rx_is_full();