Advanced Users can post their questions and comments here for the not so Newbie crowd.

Moderator: eriksl

User avatar
By davydnorris
#88564 OK I can definitely help you with quite a bit of this.

There are a couple of registers that control everything, and all of the values labelled *_NUM are a bit mask, with the corresponding *_NUM_S being the bit shift. So you'll see lots of code of the form:

Code: Select allsetReg(main_register, specific_control_value & {control_value}_NUM << {control_value}_NUM_S);

unless you know enough to set the values within their range.

BCK_DIV_NUM, CLKM_DIV_NUM and BITS_MOD work together to set the I2S clock frequency and the number of significant bits in the signal. You have to find the optimal value of all three that's closest to your desired frequency. These are my own notes in my header file so I remember:

Code: Select all/* These control the PLL settings for the generation of the I2S SCK or BCK from the 160MHz PLL clock (CPU speed independent)
 The bit clock BCK = (Desired Sampling rate)*(Sampling Depth)*(2 channels)

 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

 This needs examining in detail - not sure how the samples are transmitted or what the BITS field does to the rate or depth.
 The BITS may be 24bit (bit depth), 31bit (max value in register) or 32bit (value size returned from mic) depending on how it works
 For now, these values are for:
 - 48kHz and 32bit size, 4 and 13 (actual rate = 48076.9)
 - 48kHz and 31bit size, 6 and 9  (actual rate = 47789.7)
 - 48kHz and 24bit size, 7 and 10 (actual rate = 47619.0)
 - 48kHz and 17bit size, 7 and 14 (actual rate = 48019.2) <== closest possible rate to desired
#define WS_I2S_BCK_DIV       4   /* Bit clock divisor 2-63 */
#define WS_I2S_CLKM_DIV    13   /* Clock Divisor 5-63 */
#define WS_I2S_BITS         15   /* Sampling depth-16. Currently needs looking into as it's 4 bits max, which gives 31 bits max?? */

/* To receive data from the I2S module, counter-intuitively we use the TXLINK part, not the RXLINK part, and vice versa.
 * Note:At the transmitter side,the size of the DMAs can not be smaller than 128*4 bytes which are the
 * size of the I2S FIFO.
 * Note:At the receiver side,the number of the DMAs can not be smaller than 3 which is limited by the
 * hardware.
#define I2SDMABUFLEN 128   /* this is 32bit words for I2S, but DMA deals in bytes, so in any DMA code it's 128*4 */


//I2S DMA buffer descriptors
static struct sdio_queue i2sBufDescRX[DMABUFFERDEPTH];

This is my I2S init routine, which should also give you an idea about some of the other values - these control whether your I2S Tx and Rx is in master or slave mode in terms of the clock, how many channels, whether the data is 16 or 24 bit, and how the values are stored within the word in the buffer.

I am only using receive in this code, but I've left in the Tx parts so I would remember:

Code: Select all//Initialize the I2S module
//More Registor details in I2S documents.
void ICACHE_FLASH_ATTR i2s_init(void) {

   //Enable a 160MHz clock to i2s subsystem
   i2c_writeReg_Mask_def(i2c_bbpll, i2c_bbpll_en_audio_clock_out, 1);

   //reset I2S

   //Enable FIFO in i2s module

   //set I2S_FIFO
   //set rx,tx data size, both are "24-bit full data discontinue" here

   //set I2S_CHAN
   //set rx,tx channel mode, both are "two channel" here

   //set RX eof num

   //reset sample rate info
         (I2S_BCK_DIV_NUM <<I2S_BCK_DIV_NUM_S));

   //use I2S clock divider to produce a 48KHz Sample Rate
   //set transmit to be I2S slave & receive to be I2S master
   //MSB_shift, right channel data is first, MSB is to the right
         ((WS_I2S_BITS&I2S_BITS_MOD) << I2S_BITS_MOD_S)|
         ((WS_I2S_BCK_DIV&I2S_BCK_DIV_NUM ) << I2S_BCK_DIV_NUM_S));

   //clear int

   //enable int


   //Start transmitter and receiver

void ICACHE_FLASH_ATTR i2s_stop(void) {
User avatar
By eriksl
davydnorris wrote:
eriksl wrote:
davydnorris wrote:Awesome work!! I had seen the FIFO documentation but, as you say, it's completely useless.

I use I2S for audio input so I have to use DMA - I have a pair of I2S mics that I'm driving at 48kHz, 24 bit resolution and I sum the inputs to increase SNR, then run the results through some biquad filters to produce A, C or Z weighted output from the raw mic inputs. I then use that to calculate slow and fast time weighted dB readings as well as raw Leq values binned in 1 sec increments.

I do all of this in fixed point, including the sqrt and log10 at the end when I actually want to use the values.

Even more awesome! And the ESP8266 is simply fast enough to cope, very cool!

How do you get the data out of the chip, over wifi? Is it steady enough to not need significant buffering?

I don't stream the signal - I don't think it would work. I've been asked if we could take 30sec sound bites but I don't like the implications of listening in - it's easier to just say no it can't be done. If I was going to stream the signal, I think I would have to ditch all the signal processing except the initial summing, and I would use WAVPACK to compress the stream on the fly and then do the digital correction at the other end

WAVPACK is a tiny compression library suitable for audio and embedded systems but even then it's a big ask.

Right now, I use MQTT to send the dB(A) or dB(C) slow and fast SPL values whenever the sensor is programmed to, and I send Leq(A) values for windows of time. It seems to be the norm for SPL meters to use what they call short Leq, which is raw noise integrated over 0.125 sec.

I can easily do this, however almost all the noise measurements my clients want are 1min, 5min or 15min and so it makes no sense to calculate and store all those short Leq values when you're only going to combine them in much larger intervals. I calculate them in 1 sec buckets, and store them as an array of 60, 300 or 900 values, which can be moved into flash if needed. I can then do things like report Leq(A)-15min every minute by using a sliding window and a circular buffer.

Ok so you're not actually collecting the audio data itself, only the loudness of it. I already thought it would be a challenge to get all of the data out in time without (variable) network delays and without buffering.
User avatar
By davydnorris
eriksl wrote:Ok so you're not actually collecting the audio data itself, only the loudness of it. I already thought it would be a challenge to get all of the data out in time without (variable) network delays and without buffering.

Technically yes - but to calculate those values you still have to process all the data in real time :-(

I think it would definitely be possible to do - but not at 48kHz 24 bit. One of the things on the to-do list is a lower quality sample stream, but the problem there is you lose the bandwidth so it would not be useable for detailed analysis, just as an audio stream.

Probably the best way to do that would be to sum two samples per mic, so the noise floor would be even lower but the rate would be halved.
User avatar
By eriksl
#88599 At 160 Mhz, it might be possible to run a low complexity audio codec, like SBC or MPEG1-layer2 (aka "mp2"). Or even alaw/ulaw if you're very tight. I guess an 8 bit sample width and one channel would be more appropriate then ;) Interesting application, to have something e.g. an intercom.