From a01ccd9f26b103018da0f213cfccfef8b38737e9 Mon Sep 17 00:00:00 2001 From: Victor Tseng Date: Sun, 11 Sep 2016 07:57:08 +0800 Subject: [PATCH 1/7] fix whitespace in i2s stuff --- cores/esp8266/core_esp8266_i2s.c | 20 ++++++++++---------- cores/esp8266/i2s.h | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 3a7ca505ed..31c2c715c7 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -1,11 +1,11 @@ -/* +/* i2s.c - Software I2S library for esp8266 - + Code taken and reworked from espessif's I2S example - + Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -95,7 +95,7 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { void ICACHE_FLASH_ATTR i2s_slc_begin(){ i2s_slc_queue_len = 0; int x, y; - + for (x=0; x Date: Sun, 11 Sep 2016 08:20:10 +0800 Subject: [PATCH 2/7] i2s: add non-blocking version of i2s_write_lr() --- cores/esp8266/core_esp8266_i2s.c | 7 +++++++ cores/esp8266/i2s.h | 1 + 2 files changed, 8 insertions(+) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 31c2c715c7..33599ad247 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -194,6 +194,13 @@ bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){ return i2s_write_sample(sample); } +bool ICACHE_FLASH_ATTR i2s_write_lr_nb(int16_t left, int16_t right){ + int sample = right & 0xFFFF; + sample = sample << 16; + sample |= left & 0xFFFF; + return i2s_write_sample_nb(sample); +} + // END DMA // ========= // START I2S diff --git a/cores/esp8266/i2s.h b/cores/esp8266/i2s.h index 58e93dc926..7659c40d24 100644 --- a/cores/esp8266/i2s.h +++ b/cores/esp8266/i2s.h @@ -46,6 +46,7 @@ void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000) 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_write_lr_nb(int16_t left, int16_t right);//same as above but does not block when DMA is full and returns false instead 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) From 0437af2042ed79222ddc61206ac1bc4d1018e970 Mon Sep 17 00:00:00 2001 From: Victor Tseng Date: Sun, 11 Sep 2016 08:26:17 +0800 Subject: [PATCH 3/7] i2s: pull malloc() codes out of i2s_slc_begin() we `malloc()` memories in `i2s_slc_begin()`, but never `free()` them. so the cpu runs out of memory after several `i2s_begin(); i2s_end();` so we add a `i2s_init()` to do the `malloc()` and `i2s_deinit()` to `free()` those allocated buffers. --- cores/esp8266/core_esp8266_i2s.c | 20 ++++++++++++++------ cores/esp8266/i2s.h | 2 ++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 33599ad247..fb2fafd47b 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -92,13 +92,10 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) { } } -void ICACHE_FLASH_ATTR i2s_slc_begin(){ - i2s_slc_queue_len = 0; - int x, y; - - for (x=0; x Date: Sun, 11 Sep 2016 08:44:00 +0800 Subject: [PATCH 4/7] i2s: properly configure sample rate - do a search to find the closest divider for optimal performance. - use the current cpu clock to get the correct sample rate. - only set the sample rate if it's changed --- cores/esp8266/core_esp8266_i2s.c | 59 +++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index fb2fafd47b..8226f0840d 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -214,22 +214,60 @@ bool ICACHE_FLASH_ATTR i2s_write_lr_nb(int16_t left, int16_t right){ // START I2S -static uint32_t _i2s_sample_rate; +static uint32_t _i2s_sample_rate = 0; void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ if(rate == _i2s_sample_rate) return; _i2s_sample_rate = rate; - uint32_t i2s_clock_div = (I2SBASEFREQ/(_i2s_sample_rate*32)) & I2SCDM; - uint8_t i2s_bck_div = (I2SBASEFREQ/(_i2s_sample_rate*i2s_clock_div*2)) & I2SBDM; - //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 | ((i2s_bck_div-1) << I2SBD) | ((i2s_clock_div-1) << I2SCD); + // Find closest divider + uint32_t cpu_freq = ets_get_cpu_frequency() * 1000000L; + int bestfreq = 0; + uint32_t i2s_clkm_div, i2s_bck_div; + + // CLK_I2S = CPU_FREQ / I2S_CLKM_DIV_NUM + // BCLK = CLK_I2S / I2S_BCK_DIV_NUM + // WS = BCLK/ 2 / (16 + I2S_BITS_MOD) + // Note that I2S_CLKM_DIV_NUM must be >5 for I2S data + // I2S_CLKM_DIV_NUM - 5 - 63 + // I2S_BCK_DIV_NUM - 2 - 63 + for (int bckdiv = 2; bckdiv < 64; bckdiv++) { + for (int clkmdiv = 5; clkmdiv < 64; clkmdiv++) { + uint32_t testfreq = cpu_freq / (bckdiv * clkmdiv * 32); + if (abs(_i2s_sample_rate - testfreq) < abs(_i2s_sample_rate - bestfreq)) { + bestfreq = testfreq; + i2s_clkm_div = clkmdiv; + i2s_bck_div = bckdiv; + } + } + } + + // Apply the sample rate + // ~I2S_TRANS_SLAVE_MOD (TX master mode) + // ~I2S_BITS_MOD + // I2S_RIGHT_FIRST + // I2S_MSB_RIGHT + // I2S_RECE_SLAVE_MOD (TX slave mode) + // I2S_RECE_MSB_SHIFT (??) + // I2S_TRANS_MSB_SHIFT (??) + + // !trans master, !bits mod, + // rece slave mod, rece msb shift, right first, msb right + I2SC &= ~( I2STSM | // TX master mode + I2STMS | // TX LSB first + (I2SBMM << I2SBM) | // clear bits mode + (I2SBDM << I2SBD) | // clear bck_div + (I2SCDM << I2SCD)); // clear clkm_div + I2SC |= I2SRF | // right first + I2SMR | // MSB first + I2SRSM | // RX slave mode + I2SRMS | // receive MSB shift + // bits_mode == 0 (16bit) + ((i2s_bck_div & I2SBDM) << I2SBD) | // set bck_div + ((i2s_clkm_div & I2SCDM) << I2SCD); // set clkm_div } void ICACHE_FLASH_ATTR i2s_begin(){ - _i2s_sample_rate = 0; i2s_slc_begin(); pinMode(2, FUNCTION_1); //I2SO_WS (LRCK) @@ -248,7 +286,10 @@ void ICACHE_FLASH_ATTR i2s_begin(){ 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 - i2s_set_rate(44100); + + // defaults to 44100 if unset + i2s_set_rate(_i2s_sample_rate == 0 ? 44100 : _i2s_sample_rate); + I2SC |= I2STXS; //Start transmission } From 3ae6e8b06da9dc27e036fbc6acaf1193b4a3703a Mon Sep 17 00:00:00 2001 From: Victor Tseng Date: Tue, 13 Sep 2016 08:17:28 +0800 Subject: [PATCH 5/7] i2s: DMA buffer management return the underlying buffer used by I2S SLC DMA subsystem, so client code can write data directly into it, instead of calling i2s_write_sample() / i2s_write_sample_nb(). greatly boost the performance, and reduce memory footprint when using an audio codec. because the codec can now decode the stream directly into the buffer, instead of cache them and doing i2s_write_sample{,_nb}(). --- cores/esp8266/core_esp8266_i2s.c | 17 +++++++++++++++++ cores/esp8266/i2s.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 8226f0840d..af8cdd9d49 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -156,6 +156,23 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){ SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address } +uint32_t * i2s_get_buffer() { + if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) { + if(i2s_slc_queue_len == 0){ + return NULL; + } + 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; + } + return i2s_curr_slc_buf; +} + +void i2s_put_buffer() { + i2s_curr_slc_buf_pos=SLC_BUF_LEN; +} + //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. diff --git a/cores/esp8266/i2s.h b/cores/esp8266/i2s.h index e883b9e2bf..48ee20a59d 100644 --- a/cores/esp8266/i2s.h +++ b/cores/esp8266/i2s.h @@ -49,6 +49,8 @@ bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper 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_write_lr_nb(int16_t left, int16_t right);//same as above but does not block when DMA is full and returns false instead +uint32_t * i2s_get_buffer(); // buffer available for writing, nullptr if not applicatable. +void i2s_put_buffer(); // yup you guessed it. 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) From 6b3dca965a2eba01fed63a24b2f40173461ee802 Mon Sep 17 00:00:00 2001 From: Victor Tseng Date: Tue, 20 Sep 2016 23:48:37 +0800 Subject: [PATCH 6/7] allow user to adjust the size of i2s buffer the i2s buffer was fixed to 8*64 uint32_t, this may be inefficient for some codecs. by allowing the user to adjust the buffer size, the codec can now decode the whole window directly into the acquired buffer, thus avoid a lot of memory copy. --- cores/esp8266/core_esp8266_i2s.c | 85 ++++++++++++++++++-------------- cores/esp8266/i2s.h | 4 +- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index af8cdd9d49..645105c601 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -30,9 +30,6 @@ 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. - //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 @@ -40,21 +37,24 @@ extern void ets_wdt_disable(void); //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; + 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; + struct slc_queue_item * next_link_ptr; }; -static uint32_t i2s_slc_queue[SLC_BUF_CNT-1]; +static size_t SLC_BUF_CNT = 0; //Number of buffers in the I2S circular buffer +static size_t SLC_BUF_LEN = 0; //Length of one buffer, in 32-bit words. + +static uint32_t ** i2s_slc_queue; 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 uint32_t * i2s_slc_buf_pntr=NULL; // Pointer to the I2S DMA buffer data +static struct slc_queue_item * i2s_slc_items; //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 bool ICACHE_FLASH_ATTR i2s_is_full(){ @@ -65,9 +65,9 @@ bool ICACHE_FLASH_ATTR i2s_is_empty(){ return (i2s_slc_queue_len >= SLC_BUF_CNT-1); } -uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue +uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue uint8_t i; - uint32_t item = i2s_slc_queue[0]; + uint32_t * item = i2s_slc_queue[0]; i2s_slc_queue_len--; for(i=0;i Date: Tue, 20 Sep 2016 23:56:04 +0800 Subject: [PATCH 7/7] make sample variable in i2s_write_lr*() uint32_t --- cores/esp8266/core_esp8266_i2s.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_i2s.c b/cores/esp8266/core_esp8266_i2s.c index 645105c601..6e03e325ee 100644 --- a/cores/esp8266/core_esp8266_i2s.c +++ b/cores/esp8266/core_esp8266_i2s.c @@ -224,14 +224,14 @@ bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) { } bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){ - int sample = right & 0xFFFF; + uint32_t sample = right & 0xFFFF; sample = sample << 16; sample |= left & 0xFFFF; return i2s_write_sample(sample); } bool ICACHE_FLASH_ATTR i2s_write_lr_nb(int16_t left, int16_t right){ - int sample = right & 0xFFFF; + uint32_t sample = right & 0xFFFF; sample = sample << 16; sample |= left & 0xFFFF; return i2s_write_sample_nb(sample);