Audio acquisition and analysis with ARM Cortex-M0 and I2S microphone


This - in particular - sparked my interest when having your current problem in mind:

1 Like

Yes … quite ready to start doing so.

However, one good bit of news: with the Adafruit_ZeroI2S library I managed to get a minimal microphone reading sketch working. The code in that library seems to be really bare bones, reading directly from the buffer without any processing. So, perhaps easy for me to understand.

It also brought me one important insight just now: the R/L channel select pin is left floating on the breakout board that I have. I didn’t realise this so far. With the previous microphone it was pulled to one level, so that, left unconnected, it was a left microphone.


Along the lines of

maybe just one more step back to the beginning: May I humbly ask whether the very basic sketch ArduinoCore-samd/InputSerialPlotter.ino at 1.6.20 · arduino/ArduinoCore-samd · GitHub actually works without stalling?


Good to hear! Then, just forget about my last post ;]. Good luck with the next steps with Zero_I2S then.


Yes, that one works like a charm.


Ok, thanks.

When looking at Basic audio recorder for ARM Cortex-M0 and ICS43432 I2S microphone · GitHub again: Why exactly are you calling I2S.end(); there again?


You probably explained it already at I2S.end() hangs · Issue #386 · arduino/ArduinoCore-samd · GitHub :

I want to begin and end I2S within a function, rather than beginning it once and never ending it.

But still: Why actually?


Because I want to do an audio measurement, say, every half our. In between I’d like to switch off
I2S/microphone for two reasons:

  • In earlier tests I noticed issues with LMIC (mostly when expecting to receive a join accept message from the TTN gateway). Though I haven’t gone into a large depth and verified this, it occurred to me that I2S might mess up timing in LMIC.
  • Power saving during the time in which the microphone is not used.

Probably there are other methods in realising same … I guess …


I see your point. Thanks again.


Also, when reconsidering the code at lines 44-47:

if (!I2S.begin(I2S_PHILIPS_MODE, SAMPLE_RATE, 32)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing

This busy loop also might be the reason for the stalls, right? You might miss that point because you are not calling Serial.flush() there, which will pause your program until the transmit buffer has been flushed to the UART. At least, this is what we already learned painfully when attempting to debug in similar conditions.


Not sure how I can edit you gist, so I edited the file on my github: added some Serial.println statements. Please have a look if I understood your comment correctly. Running that sketch outputs the following to serial:

Before end()
After end()
Before end()

and then nothing …


Thanks for sharing insights about the runtime behavior of your code, we’ve updated the Gist Basic audio recorder for ARM Cortex-M0 and ICS43432 I2S microphone · GitHub accordingly.


Did you actually read this post (by Andrew J. Fox again), Wouter?


While I perfectly understand this from looking at the problem from an ad hoc perspective, I would like to add some thoughts here. Usually, when putting the MCU into deep sleep, all the peripherals most probably have been shut down anyway and will have to be initialized again when resuming from hibernation.

So I believe it’s not really appropriate to cycle through I2S.begin() and I2S.end() in this manner so quickly to actually emulate something you will not have in the final version as this most probably leads to some bus member hiccup.

So, I would like to advise to get rid of this I2S.begin() / I2S.end() obstacle until we have produced a stable version if you have no other objections about this.


I’ll give it a try; see if my earlier observation (it standing in the way of LMIC) still stands and if I can work around that…


In tests I did half a year ago, having I2S running would get in the way of successfully joining the TTN network. My assumption was that the I2S library messed with timing, so that LMIC’s receive window for an OTAA join accept from the gateway was slightly out ot sync. This brought me to run I2S only for a brief period of time, between LoRa Tx and Rx events.

Now, with this node (same brand/type of board, different “instance”) it does join TTN when I2S has been initiated already. In the meanwhile what changed as well is that I have a different gateway and it is closer by (so now I can join with SF7 rather than SF11). LoRa is quite sensitive to timing, so, although it works now, I do feel a bit uneasy about not understanding this point. Well it works now … note to self to think about this if at one point it has difficulties joining again.


Hi Wouter,

what you are telling about the LMIC/I2S interaction eventually makes absolute sense to me when looking at the code.

The point is that just almost everything is timing critical here. As LMIC apparently introduces a scheduling/task system, there’s additional care to be taken in comparison/addition to the regular Arduino HAL main() / loop() lifecycle.

I just thought about isolating the I2S sensor domain first and getting this working. After that, we can dedicate ourselves to the interaction of I2S with LMIC. I would have to say quite some words about that, especially as the FFT computation in between obviously is not a cheap operation, if I’m getting this right?

If you think reading from I2S works stable now, another phone call for talking about how to bring I2S together with LMIC/TTN would be appropriate.



Just loud thinking: We do not need I2S and LoRa in parallel. We measure and then we submit data, also “listening” in parallel to the audio acquisition is not necessary. So we can try to get one sleeping / disabled while the other is working in case they come in conflict.


Hi Clemens, that’s right. We know when we schedule for the next LoRA transmission. We can do the audio stuff before the scheduled LoRa transmission and there shouldn’t be an issue.

When a transmission happens, for a few seconds afterwards a window is opened to listen to uplink messages (such as needed for MAC commands or application payloads). This windows needs to be cleanly timed. I am not sure how much jitter is added by the onI2Sreceive event, but it indeed seems undesirable to have that running.

However, I haven’t found a public method (other than end()) to disable it or put it to sleep.


Hi Andreas … thanks; still in the process of verifying that :-)