Skip to main content
NBees.1
Associate
October 29, 2020
Question

I2S data gets corrupted

  • October 29, 2020
  • 10 replies
  • 3704 views

Hi there,

I am trying to do some digital signal processing on a STM32G031F6 by interfacing an SPH0645 MEMS microphone via I²S. I'm running into strange issues however, that I haven't been able to solve in the last few days. I am switching to an analogue microphone and ADC for now, but eventually I'd like to use a digital interface to minimize RF interference. I should also mentioned that I'm a beginner to 32 bit Arm MCUs and haven't done a lot of MCU stuff in general.

Anyway. I'm using HAL_I2S_Receive() to poll the values and print them to a serial console via UART. The receive function reads into a unsigned 16 bit integer, so values of around 65000 are expected. I noticed that I only get those if I make the output to the UART console exactly 10 characters long. If it is any shorter, some 32000, 16000s or even zeroes are mixed in. I found this strange, "but maybe that's how it works" I thought to myself.

However, while calculating RMS of the signal, I again noticed strange behaviour, while the calculation itself was implemented correctly. So I repeatedly read 2000 samples into an array and looked at it in the debugger. The readings usually started out stable with values of around 64000 for the first 700 fields. After that, the samples dropped to values of around 650 or lower. Towards the end, sample values rose again to the expected values.

I am suspecting a clock issue, but I have no way to verify that right now. Before I look around for an oscilloscope, I thought I'd try this way.

The MCU is soldered onto a breakout PCB from Adafruit. Both that breakout PCB and the microphone breakout PCB I put into a bread board without crossing data and supply wires. I also tried a few different arrangements, but to no avail.

I don't have an external clock connected. I also wonder if I might need to connect a 12.288 MHz crystal to I2S_CKIN for it to work. Because I don't have one, I tried to use PLLP to at least approximate that frequency with 12.32 MHz. No luck, though.

Thank you for reading this short essay :grinning_face:

This topic has been closed for replies.

10 replies

waclawek.jan
Super User
October 29, 2020

The microphone datasheet says you have to use 32-bit word per side, not 16. Also, why would be the output around 650000?

JW

NBees.1
NBees.1Author
Associate
October 29, 2020

I have the I²S set up according to the data sheet. 24 bits on 32 bit frame. However the function HAL_I2S_Receive() expects a 16 bit unsigned pointer to a buffer for the result. That's why I declared it like this. I switched it to 32 bit unsigned but ran into the same problem, just with different numbers.

I am expecting an output of around 65000 (you had an excess zero in there) because 2^16=65536. When there is no sound (the microphone is "idling") it returns maximum and reduces in value when a sound occurs.

gregstm
Senior II
October 29, 2020

I've noticed with digital microphones that they can sometimes take a long time to settle. It took me by surprise. I logged the initial data from the microphone and graphed it with a spreadsheet - I too thought something was going wrong. One microphone I used took 30 seconds for the DC offset to disappear. You could try passing the data through a dc-blocking filter. I found this application note very useful in helping me implement my dc-blocking filter.

https://www.knowles.com/docs/default-source/default-document-library/dc-blocking-filter.pdf?sfvrsn=fdb77b1_6

Also, it is easy making mistakes converting the I2S data to a usable form - you could try logging the raw 32 bit data and putting it into a spreadsheet and use it to check that your method of converting it is correct.

NBees.1
NBees.1Author
Associate
October 30, 2020

The whole DC offset thing is new to me, but wouldn't a dc offset just mean that the microphone would return a slightly biased value in one direction. So instead of a pure midpoint it would report a little over that. If that is the case, it wouldn't explain why the values fluctuate like that.

I also gave the microphone at least a minute to settle down, but that changed nothing. Usually that happens anyway since I need to step away to cope with the frustration :beaming_face_with_smiling_eyes:

I tried to read out the values to a spreadsheet prior to asking this question and it seemed fine. When I plot the output in the Arduino Serial Plotter I can clearly see the graph responding to my clapping or whatever. The problem is that my analysis doesn't work withing the MCU because the data reported to the UART terminal is apparently different from what is stored in my samples array. Or at least, that's what I can see. It's highly likely, that I'm introducing an error. I just don't know how.

So I guess my main concern right now would be to explain the weird UART behaviour. I'm still welcoming all kinds of suggestions and am eager to try =)

gregstm
Senior II
October 30, 2020

I just tried to refresh my memory regarding my SPH0645 experiences... ( I have moved on to using microphones using the DFSDM so that I could use better performing microphones). I found the old spreadsheet where I graphed the DC offset, after 25 seconds it settled to a value of -6700 not zero - but I also seem to remember that this microphone failed completely later, so it might have been on the way out from the start [ I bought my microphone on a little preassembled daughter board - I read somewhere that these boards may have been mistakenly washed after soldering - resulting in these microphones failing after a while. I solder all microphones myself after that experience ]

How did you convert your data from the SAI to a signed 24 bit number? This is the code I used:

if (val >= 0x800000)

val = val - 0x1000000;

// Only upper upper 18 bits of data is valid, divide off lower 6 bits

val = val / 64;

NBees.1
NBees.1Author
Associate
October 30, 2020

Thanks for digging through your records!

I am indeed using a pre-soldered breakout board, but I also have single microphones here that I can try.

So far I haven't converted anything. I just wanted to look at the raw data to get an understanding of what's happening but got slowed down by that issue.

In your example you are converting a 32-bit unsigned into a 24-bit signed, correct? I tried reading the HAL_I2S_Receive() output into a uint32_t buffer but only sometimes received a 32 bit number. Sometimes it's only 16 bit. Am I missing something?

gregstm
Senior II
October 31, 2020

you said earlier " I can clearly see the graph responding to my clapping" - that's a promising sign. Instead of clapping, use a sine wave generator (or even just a toy musical recorder) to generate the signal. Save the data as unsigned32 and graph it in a spreadsheet - hopefully it is just a data conversion issue, and the sine wave shape will be easier to analyse. I will leave you to it...

waclawek.jan
Super User
October 30, 2020

The microphone outputs values as 2s complement, left aligned. I don't know about 'G0, but on older STM32, SPI is only 16-bit, so receiving MSB data inevitably required shuffling the halfwords. Try to draw a timing diagram.

Quiet at 32-bit 2s complement means values alternating between 0xFFFFFxxx (i.e. small negative) and 0x00000xxx (small positive).

Microphone outputs only 18 valid bits so last digits will be insignificant.

JW

NBees.1
NBees.1Author
Associate
October 31, 2020

Well graphing out the data externally is working perfectly fine. The problem is (as mentioned in my first post) that when I do the same on the chip itself, I only get rubbish as a result which is due to the the values shown via UART are not the same I can see in the variables, which does make no sense to me at all. Also I don't get why the values via UART only are correct whenever the transmitted string is exactly 10 characters long.

waclawek.jan
Super User
November 1, 2020

Is your code transmitting through UART proven good?

I.e. forget about I2S at the moment, fill in manually an array with numbers of your choice (e.g. 0x1, 0x2, 0x4, 0x8, 0x10 etc. all way up to 0x80000000) and transmit.

JW

NBees.1
NBees.1Author
Associate
November 2, 2020

I did that. Worked perfectly fine. I tried it with both a predefined array as well as in a loop. Both worked without any wrong values. I also polled the I2S interface while from within the loop to see if that would throw things off, but it changed nothing. Of course I only did that after I saw the "pure" UART stuff working.

gregstm
Senior II
November 1, 2020

... also, consider writing your own basic routines for displaying data via the UART (eg. uint32, int32) - they are handy routines for compact, robust debugging

NBees.1
NBees.1Author
Associate
November 2, 2020

That's a good tip and something I should've done earlier. Thank you!

NBees.1
NBees.1Author
Associate
November 2, 2020

Since your answers point towards a mistake on my end instead of a clock issue (like I thought initially), maybe a few lines of code can resolve the issue:

uint16_t data_i2s[2]; // Define a double sized (32 bit) pointer for data.
 
// Receive two words of 16 bit length into the pointer.
HAL_I2S_Receive(&hi2s1, &data_i2s, 2, 100); 
 
uint32_t sample = 0;
// Build a full 32 bit integer out of both values.
sample = (uint32_t) data_i2s[1] << 16 | data_i2s[0];

When I take a look into the data_i2s array I noticed that I don't always get the specified two 16 bit words. Sometimes it only fills up one array field with a meaningful number while the other just gets a "0". Of course that really messes with the final data. It seems like something is off there. I mean, I can't really get any closer to the actual data, can I?

waclawek.jan
Super User
November 2, 2020

You wrote above:

> I have the I²S set up according to the data sheet. 24 bits on 32 bit frame.

Then why do you expect that

> // Receive two words of 16 bit length into the pointer.

> HAL_I2S_Receive(&hi2s1, &data_i2s, 2, 100);

would receive 16-bit words?

Cube/HAL is open source. Read it.

JW

NBees.1
NBees.1Author
Associate
November 2, 2020

I would expect that because the documentation states:

[...] when a 24-bit data frame or a 32-bit data frame is selected
the Size parameter means the number of 16-bit data length.

That tells me that if I want to read the 32-bit data frame I need to use two lengths of 16-bit data. Size = 2 is also the parameter I have seen in all of the code examples I could find on I²S.

If my lack of knowledge frustrates you feel free to walk away from this thread and do something nice instead. This was by no means my intention. I have spent the better part of the last 10 days on this problem, have read documentations, tried out various solutions, dozens of parameters and have finally brought up the courage to ask my question on this forum despite being a very stubborn and proud person with very little knowledge in terms of C and STM32 microcontrollers.

Thank you nonetheless for your input so far.