diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index f85113c8b..046c6dd06 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -24,104 +24,205 @@ #include "osapi.h" #include "ets_sys.h" + #include "i2s_reg.h" #include "i2s.h" +#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 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; + volatile uint32_t owner : 1; // DMA can change this value + uint32_t * buf_ptr; + struct slc_queue_item * next_link_ptr; +} slc_queue_item_t; + +typedef struct i2s_state { + 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_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; + +// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC) +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_WS 2 #define I2SO_DATA 3 -#define I2SO_BCK 15 +#define I2SO_BCK 15 +#define I2SO_WS 2 +#define I2SI_DATA 12 +#define I2SI_BCK 13 +#define I2SI_WS 14 -#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. - -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_RAM_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 i2s_is_full(){ - return (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) && (i2s_slc_queue_len == 0); +static bool _i2s_is_full(const i2s_state_t *ch) { + if (!ch) { + return false; + } + return (ch->curr_slc_buf_pos==SLC_BUF_LEN || ch->curr_slc_buf==NULL) && (ch->slc_queue_len == 0); } -bool i2s_is_empty(){ - return (i2s_slc_queue_len >= SLC_BUF_CNT-1); +bool i2s_is_full() { + return _i2s_is_full( tx ); +} + +bool i2s_rx_is_full() { + return _i2s_is_full( rx ); +} + +static bool _i2s_is_empty(const i2s_state_t *ch) { + if (!ch) { + return false; + } + return (ch->slc_queue_len >= SLC_BUF_CNT-1); +} + +bool i2s_is_empty() { + return _i2s_is_empty( tx ); +} + +bool i2s_rx_is_empty() { + return _i2s_is_empty( rx ); +} + +static int16_t _i2s_available(const i2s_state_t *ch) { + if (!ch) { + return 0; + } + return (SLC_BUF_CNT - ch->slc_queue_len) * SLC_BUF_LEN; } int16_t i2s_available(){ - return (SLC_BUF_CNT - i2s_slc_queue_len) * SLC_BUF_LEN; + return _i2s_available( tx ); } -uint32_t ICACHE_RAM_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue +int16_t i2s_rx_available(){ + return _i2s_available( rx ); +} + +// Pop the top off of the queue and return it +static uint32_t * ICACHE_RAM_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;islc_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; } -//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_RAM_ATTR i2s_slc_isr(void) { - uint32_t slc_intr_status = SLCIS; - SLCIC = 0xFFFFFFFF; - if (slc_intr_status & SLCIRXEOF) { - ETS_SLC_INTR_DISABLE(); - struct slc_queue_item *finished_item = (struct slc_queue_item*)SLCRXEDA; - ets_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 +// Append an item to the end of the queue from receive +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) { + ch->slc_queue[dest++] = ch->slc_queue[i]; } - i2s_slc_queue[i2s_slc_queue_len++] = finished_item->buf_ptr; - if (i2s_callback) i2s_callback(); - ETS_SLC_INTR_ENABLE(); + } + if (ch->slc_queue_len < SLC_BUF_CNT - 1) { + ch->slc_queue[ch->slc_queue_len++] = item; + } else { + ch->slc_queue[ch->slc_queue_len] = item; } } -void i2s_set_callback(void (*callback) (void)){ - i2s_callback = callback; +static void ICACHE_RAM_ATTR i2s_slc_isr(void) { + ETS_SLC_INTR_DISABLE(); + uint32_t slc_intr_status = SLCIS; + SLCIC = 0xFFFFFFFF; + if (slc_intr_status & SLCIRXEOF) { + 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); + 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->slc_queue[tx->slc_queue_len++] = finished_item->buf_ptr; + if (tx->callback) { + tx->callback(); + } + } + if (slc_intr_status & SLCITXEOF) { + 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; + i2s_slc_queue_append_item(rx, finished_item->buf_ptr); + if (rx->callback) { + rx->callback(); + } + } + ETS_SLC_INTR_ENABLE(); } -void i2s_slc_begin(){ - i2s_slc_queue_len = 0; - int x, y; - - for (x=0; xcallback = callback; +} - i2s_slc_items[x].unused = 0; - i2s_slc_items[x].owner = 1; - i2s_slc_items[x].eof = 1; - i2s_slc_items[x].sub_sof = 0; - i2s_slc_items[x].datalen = SLC_BUF_LEN*4; - i2s_slc_items[x].blocksize = SLC_BUF_LEN*4; - i2s_slc_items[x].buf_ptr = (uint32_t)&i2s_slc_buf_pntr[x][0]; - i2s_slc_items[x].next_link_ptr = (int)((x<(SLC_BUF_CNT-1))?(&i2s_slc_items[x+1]):(&i2s_slc_items[0])); +void i2s_rx_set_callback(void (*callback) (void)) { + rx->callback = callback; +} + +static bool _alloc_channel(i2s_state_t *ch) { + ch->slc_queue_len = 0; + for (int x=0; xslc_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; + 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 = (x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0]); + } + return true; +} + +static bool i2s_slc_begin() { + if (tx) { + if (!_alloc_channel(tx)) { + return false; + } + } + if (rx) { + if (!_alloc_channel(rx)) { + return false; + } } ETS_SLC_INTR_DISABLE(); @@ -129,32 +230,44 @@ void 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)&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 + if (!rx) { + SLCTXL |= (uint32)&tx->slc_items[1] << SLCTXLA; // Set fake (unused) RX descriptor address + } else { + SLCTXL |= (uint32)&rx->slc_items[0] << SLCTXLA; // Set real RX address + } + if (!tx) { + 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 + } 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; + } + + return true; } -void i2s_slc_end(){ +static void i2s_slc_end(){ ETS_SLC_INTR_DISABLE(); SLCIC = 0xFFFFFFFF; SLCIE = 0; @@ -162,19 +275,32 @@ void i2s_slc_end(){ SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address for (int x = 0; xslc_buf_pntr[x]); + tx->slc_buf_pntr[x] = NULL; + } + if (rx) { + free(rx->slc_buf_pntr[x]); + rx->slc_buf_pntr[x] = NULL; + } } } -//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; + } -bool 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){ - while(1){ - if(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) { + // Don't wait if nonblocking, just notify upper levels + return false; + } + while (1) { + if (tx->slc_queue_len > 0) { break; } else { optimistic_yield(10000); @@ -182,26 +308,20 @@ bool i2s_write_sample(uint32_t sample) { } } ETS_SLC_INTR_DISABLE(); - i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(); + tx->curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(tx); ETS_SLC_INTR_ENABLE(); - i2s_curr_slc_buf_pos=0; + tx->curr_slc_buf_pos=0; } - i2s_curr_slc_buf[i2s_curr_slc_buf_pos++]=sample; + tx->curr_slc_buf[tx->curr_slc_buf_pos++]=sample; return true; } +bool i2s_write_sample(uint32_t sample) { + return _i2s_write_sample(sample, false); +} + bool 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 i2s_write_lr(int16_t left, int16_t right){ @@ -211,15 +331,45 @@ bool i2s_write_lr(int16_t left, int16_t right){ return i2s_write_sample(sample); } -// END DMA -// ========= -// START I2S +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; + } + while (1) { + if (rx->slc_queue_len > 0){ + break; + } else { + optimistic_yield(10000); + } + } + } + ETS_SLC_INTR_DISABLE(); + rx->curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(rx); + ETS_SLC_INTR_ENABLE(); + rx->curr_slc_buf_pos=0; + } + + uint32_t sample = rx->curr_slc_buf[rx->curr_slc_buf_pos++]; + 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; @@ -227,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; @@ -246,29 +396,55 @@ 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(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) // 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(){ return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM); } -void i2s_begin() { - _i2s_sample_rate = 0; - i2s_slc_begin(); +bool i2s_rxtx_begin(bool enableRx, bool enableTx) { + if (tx || rx) { + i2s_end(); // Stop and free any ongoing stuff + } - // Redirect control of IOs to the I2S block - pinMode(I2SO_WS, FUNCTION_1); - pinMode(I2SO_DATA, FUNCTION_1); - pinMode(I2SO_BCK, FUNCTION_1); + if (enableTx) { + tx = (i2s_state_t*)calloc(1, sizeof(*tx)); + if (!tx) { + // Nothing to clean up yet + return false; // OOM Error! + } + 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)); + if (!rx) { + i2s_end(); // Clean up any TX or pin changes + return false; // 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; + if (!i2s_slc_begin()) { + // OOM in SLC memory allocations, tear it all down and abort! + i2s_end(); + return false; + } I2S_CLK_ENABLE(); I2SIC = 0x3F; @@ -278,28 +454,54 @@ void i2s_begin() { I2SC &= ~(I2SRST); I2SC |= I2SRST; I2SC &= ~(I2SRST); - + // I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out - I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); //Set RX/TX FIFO_MOD=0 (16-bit) and disable DMA (FIFO only) - I2SFC |= I2SDE; //Enable DMA + I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only) + I2SFC |= I2SDE; // Enable DMA + // I2STXCMM, I2SRXCMM=0 => Dual channel mode - I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); //Set RX/TX CHAN_MOD=0 + I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set RX/TX CHAN_MOD=0 + i2s_set_rate(44100); - I2SC |= I2STXS; //Start transmission + + if (rx) { + // Need to prime the # of samples to receive in the engine + I2SRXEN = SLC_BUF_LEN; + } + + I2SC |= (rx?I2SRXS:0) | (tx?I2STXS:0); // Start transmission/reception + + return true; } -void i2s_end(){ - I2SC &= ~I2STXS; +void i2s_begin() { + i2s_rxtx_begin(false, true); +} - //Reset I2S +void i2s_end() { + // Disable any I2S send or receive + // ? Maybe not needed since we're resetting on the next line... + I2SC &= ~(I2STXS | I2SRXS); + + // Reset I2S I2SC &= ~(I2SRST); I2SC |= I2SRST; I2SC &= ~(I2SRST); - // Redirect IOs to user control/GPIO - pinMode(I2SO_WS, INPUT); - pinMode(I2SO_DATA, INPUT); - pinMode(I2SO_BCK, INPUT); - i2s_slc_end(); + + if (tx) { + pinMode(I2SO_DATA, INPUT); + pinMode(I2SO_BCK, INPUT); + pinMode(I2SO_WS, INPUT); + free(tx); + tx = NULL; + } + if (rx) { + pinMode(I2SI_DATA, INPUT); + pinMode(I2SI_BCK, INPUT); + pinMode(I2SI_WS, INPUT); + free(rx); + rx = NULL; + } } diff --git a/cores/esp8266/i2s.h b/cores/esp8266/i2s.h index 925dfed50..6f1761e6f 100644 --- a/cores/esp8266/i2s.h +++ b/cores/esp8266/i2s.h @@ -40,7 +40,8 @@ speed. extern "C" { #endif -void i2s_begin(); +void i2s_begin(); // Enable TX only, for compatibility +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 @@ -48,10 +49,15 @@ 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 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(); +bool i2s_rx_is_empty(); int16_t i2s_available();// returns the number of samples than can be written before blocking +int16_t i2s_rx_available();// returns the number of samples than can be written before blocking void i2s_set_callback(void (*callback) (void)); +void i2s_rx_set_callback(void (*callback) (void)); #ifdef __cplusplus } diff --git a/libraries/esp8266/examples/I2SInput/I2SInput.ino b/libraries/esp8266/examples/I2SInput/I2SInput.ino new file mode 100644 index 000000000..0f1ba7e43 --- /dev/null +++ b/libraries/esp8266/examples/I2SInput/I2SInput.ino @@ -0,0 +1,55 @@ +/* + 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 +#include + +void setup() { + Serial.begin(115200); + WiFi.forceSleepBegin(); + delay(500); + + i2s_rxtx_begin(true, false); // Enable I2S RX + i2s_set_rate(11025); + + delay(1000); + + while (1) { + int16_t l, r; + i2s_read_sample(&l, &r, true); + char withScale[256]; + sprintf(withScale, "%d %d", l, r); + Serial.println(withScale); + yield(); + } +} + +void loop() { + /* Nothing here */ +} diff --git a/libraries/esp8266/examples/I2STransmit/I2STransmit.ino b/libraries/esp8266/examples/I2STransmit/I2STransmit.ino new file mode 100644 index 000000000..cdf2cfd0f --- /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); + } +}