arduino / ArduinoCore-samd

Arduino Core for SAMD21 CPU
GNU Lesser General Public License v2.1
470 stars 718 forks source link

DMA I2S callback not triggering at high frequency #294

Open oscgonfer opened 6 years ago

oscgonfer commented 6 years ago

Hello,

We are using the I2S library in order to read data from an ICS43432 with a SAMD21 in order to develop applications for ambient noise frequency analysis, within the SmartCitizen project.

We are facing an issue within the library that halts the I2S receive process, due to a DMA.onTransferComplete(_dmaChannel, I2SClass::onDmaTransferComplete); callback not being raised after a DMA transfer. This provokes the doubleBuffer system to never be updated since neither the I2S.read(buffer, size) nor the I2S.onTransferComplete() functions go through the process of rising another DMA.transfer and therefore not returning anything. Here is the code in question:

int I2SClass::read(void* buffer, size_t size)
{
 if (_state != I2S_STATE_RECEIVER) {
   enableReceiver();
 }

 uint8_t enableInterrupts = ((__get_PRIMASK() & 0x1) == 0);

 // disable interrupts,
 __disable_irq();

 int read = _doubleBuffer.read(buffer, size);

 if (_dmaTransferInProgress == false && _doubleBuffer.available() == 0) {
   // no DMA transfer in progress, start a receive process
   _dmaTransferInProgress = true;

   DMA.transfer(_dmaChannel, i2sd.data(_deviceIndex), _doubleBuffer.data(), _doubleBuffer.availableForWrite());

   // switch to the next buffer for user output (will be empty)
   _doubleBuffer.swap();
 }

 if (enableInterrupts) {
   // re-enable the interrupts
   __enable_irq();
 }

 return read;

 }

In case of _dmaTransferInProgress == true, due to a faulty DMA callback, the I2S.read() always returns zeroes since no buffer is available after emptying the allocated _doubleBuffer. This issue takes place normally at high sampling rates (our use case is 44.1kHz) for the microphone and, from our point of view, randomly. We assume though that since the DMA.onTransferComplete is triggered by a DMA software trigger, the i2sd.data() itself shouldn't be the rootcause, but this is just an assumption out of our lack of expertise about the I2S and DMA interactions in depth.

--> Therefore, we wonder if this issue can be due to a DMA callback failure itself? Is there a way we can check if this is the case and/or try to find if the data is being still received from the I2S? We will try to provide some debugging data from gdb if possible, since the issue is a bit tricky to catch.

NB: This was also raised after testing out the ArduinoSound library in this issue, although further analysis of the event lead us to this point.

Thank you in advance!

oscgonfer commented 6 years ago

Hi!

Just a quick question: are you able to reproduce this issue with your setup and maybe the ArduinoSound Library? Have you had any chance to have a look into it?

Thank you again,

andrewjfox commented 6 years ago

Hi, I'm having trouble with lost data samples @ 8kHz and wondering if the issue is related to the DMA callbacks mentioned above. Has there been any progress on this issue?

sandeepmistry commented 6 years ago

Please provide us with a sketch to reproduce this issue. It's not clear what you are doing in the callback.

oscgonfer commented 6 years ago

Hello,

The main task to perform is audio signal analysis through a similar method as the one in ArduinoSound, but adding further steps involving signal pre and post processing via A-Weighting, signal windowing and so on. The full version till now is in the master branch of this repository. I use PlatformIO's folder structure, so the main code can be found under /src/sensorRead/mainSensorRead.cpp and the library under /lib/AudioAnalyser, kind of similarly to those of ArduinoSound, but adapted to our project.

Basicly, we are doing in terms of processing:

Apart from the workflow being different in general, it is important to take into account that we need to push higher some parameters compared to those of the examples of the library ArduinoSound:

const int fftSize = 512;
const int bitsPerSample = 32;
const int channels = 2;
const int bufferSize = 512; //Actual I2S buffer
const int sampleRate = 44100;

Now, as mentioned in the openning comment of this issue, the problem is that running this code (and similarly the SpectrumSerialPlotter.ino in ArduinoSound with the same set of parameters), it seems that the DMA callback is not being triggered in the I2S Library, not being able to fill the buffer or restarting it back. Finally, it seems the issue is easier to reproduce with higher sampling rates and bufferSize-fftSize.

Hopefully this clarifies a bit. Do not hesitate to let me know if more detail is needed.

Thank you,

Óscar

andrewjfox commented 6 years ago

This may not be the issue, but I have found that if the buffer is not emptied (using i2s.read()) before the next cycle then the library can stop sampling, and the only way to get it going again is to restart the i2s (i2s.end -> is2.begin). The I2S library uses a double buffer so while one buffer is available to read, the other is being filled by the DMA. If the buffer is not available then the process stops. If you're doing lots of processing on the buffered data, before going back to read next buffer, the delay between emptying the buffer might be too long.