Unable to decipher I2S ADC data on ESP32 as read from WM8906 Audio Codec

Hi

In using the setup in the title, I’m trying to manipulate the ADC values coming to the ESP from the ADC in the Audio Codec, but I haven’t been able to make sense of the numbers in the array returned (e.g. sBuffer[3]) or find the i2s_read and i2s_write functions in the .cpp file to try to figure it out better. Any help would be greatly appreciated!

Thanks!

Details:

I’m using the ESP32 development board with the WM8906 and things are working great. I’ve run many of the examples, and have settled on Example 8: I2S Passthrough as the baseline for what I want to do: Digital Signal Processing on the audio signal read by the ADC on the CODEC, sent to the ESP32 over I2S, some manipulation, and then sending it back over I2S to the CODEC for output by the DAC.

I have been struggling to figure out how to get the numerical values from the buffer without luck. I thought I understood arrays, but I’m not getting it. I’ve been using the Sparkfun logic analyzer to decode the I2S data, and there I see the equivalent of signed 16 bit integers changing as expected when driven by a sine wave from a function generator (+/- 0.3 volts at 300 Hz, corresponding to about +/-6860).

However, when I try to look at the values in sBufferr from the function call below, I don’t get numbers I can use.

#define bufferLen 64
int16_t sBuffer[bufferLen];

esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);

or set the buffer values to known values before the call to:

esp_err_t result_w = i2s_write(I2S_PORT, &sBuffer, bytesIn, &bytesOut, portMAX_DELAY);

I’m not having much luck. For example, if I read the buffer once (say in setup) and print the results using

int32_t value;

value=sBuffer[ii];
Serial.println(value,DEC);

Also tried masking out the 16 right most bits and casting to uint16 and int16

uint16_t value_uint16;
int16_t value_int16;

value_uint16=sBuffer[ii]& 0xFFFF;
value_int16=(int16_t)value_uint16;

While driving the left channel only, I get values like 29989, 117, -2878,118,-2967 … in the first 32 slots of the 64 element long buffer, and zeros for the last 32 which seems promising. But these aren’t the expected values for a relatively low frequency sine wave (300 Hz) or the values I’m capturing from the I2S bus with the logic analyzer.
When I tried filling the buffer before the write command with the integers 0 through 63 (sBuffer[i]= value, every other value shows up on the bus analyzer, which seems encouraging, but I haven’t been able to sort it out.

I’ve looked for the source code for i2s_read and i2s_write, without any luck (only the .h file declarations). If anyone knows where that is, I’d appreciate it.

I’ve attempted some different ways to access this array and done a fair bit of googling. I’m guessing that somehow is more levels of indirect addressing than I am understanding.

My goal is to work through the labs from the Prof Tretter’s University of Maryland old digital signal processing for communications lab course and lab text book which is really great material (https://user.eng.umd.edu/~tretter/). I’ve made some progress on another processor, but I really want to do it with an audio CODEC and this seems like exactly the right thing.

Hi -
Update: So I did some more digging, and the Sparkfun example code refers to another project and provides this info and a link to the website:

This code was created using some modified code from DroneBot Workshop.
Specifically, the I2S configuration setup was super helpful to get I2S working.
This example has a similar I2S config to what we are using here: Microphone to
serial plotter example. Although, here we are doing a full duplex I2S port, in
order to do reads and writes. To see the original Drone Workshop code and
learn more about I2S in general, please visit:
Sound with ESP32 - I2S Protocol | DroneBot Workshop

The code there looks very similar to the Sparkfun example I’m using. And an additional section (see below) that would seem to be exactly what I’m looking: it computes the mean/average value of the values read in, buffer read by buffer read . It seems to access the array the same way I am, but implies that the values of interest are in every 8th element of the input buffer, which I don’t understand.

Here are the results when I ran it again using a 300 Hz +/- 300 mV sine wave into one of the ADC channel. The computed mean/average values don’t make much sense to me. Some examples consequitive values from the serial monitor:
-12287.50
-12287.62
-4096.00
-12288.25
-1.25
4094.13
-8191.50
8191.13

The logic analyzer decode of the I2S signal shows the WM8096 is sending out the 300 Hz sine wave with an amplitude of about +/- 6860, so the average value being sometimes so much larger than that doesn’t seem right.

In the mean/average code:

  1. bufferLen is 64 and dimensions a 16 bit (2 byte) signed integer:
    #define bufferLen 64
    int16_t sBuffer[bufferLen];
  2. bytesin appears to be returning 64 (added a print to see)

So why are they averaging every 8th element of element of sBuffer which is 2*64 bytes long.

What am I missing? Any ideas what else I can try?

p.s. If you try to run the Sparkfun example, it doesn’t compile as is (for me anyway). This change was required in i2s_install (it seems the default isn’t assigned).

.mclk_multiple = i2s_mclk_multiple_t(I2S_MCLK_MULTIPLE_512),   // ***!!! mod made to compile

// .mclk_multiple = i2s_mclk_multiple_t(I2S_MCLK_MULTIPLE_DEFAULT),

I did try different values, and saw no change in the behavior. I am confident the example works correctly, as I get the sine wave input into the ADC back as a nice tone on the DAC played into a speaker.


Here’s the code fragment from the web site that produced the above results.

// Get I2S data and place in data buffer
size_t bytesIn = 0;
esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);

if (result == ESP_OK)
{
// Read I2S data buffer
int16_t samples_read = bytesIn / 8;
if (samples_read > 0) {
float mean = 0;
for (int16_t i = 0; i < samples_read; ++i) {
mean += (sBuffer[i]);
}
// Average the data reading
mean /= samples_read;
// Print to serial plotter
Serial.println(mean);
}

I2S configuration should match the WM8906 settings for data width, word length, and clock settings.Please check all these parameters.

thanks for responding!

So the thing is, the code is the Sparkfun example, and it is working: A signal put in to the ADC on the WM8906 goes to the ESP32 on I2S, gets sent back to the WM8906 and can be heard on the speaker. The numerical values for the I2S are also seen on the logic analyzer in both directions.

What I can’t seem to do is on the ESP32 look at the contents of the I2S buffer being read in and see the numerical values I’d expect or put numbers into the buffer and see them coming out correctly. I can easily believe there’s a trick I’m missing (like needing to shift the results), but looking at the bit patterns I sure haven’t sorted it out.

There’s a comment in the code about how much time should be available to do something between reads and writes (300 microseconds if the buffer size is left at 64), so I was hoping to do somethings like demonstrate amplitude modulation and demodulation algorithms.

Has anyone else tried to use the data from the buffer?

Thanks

Are you applying negative voltages to the ADC input?

There is far too little information in your posts to guess what you are doing wrong. For example, you need to post ALL your code (using code tags), so people can see how the I2S buffer is declared, being accessed, etc.