arduino-libraries / Arduino_AdvancedAnalog

Advanced Analog Library
GNU Lesser General Public License v2.1
7 stars 5 forks source link

Giga R1 Wifi Sample Rate Slower than expected #31

Closed jmdodd95682 closed 1 year ago

jmdodd95682 commented 1 year ago

I'm new to Giga R1, so I've been trying out a few simple things. I'm trying to sample live audio via ADC. I'm using the Arduino_AdvancedAnalog library. I'm seeing that the sample rate is about 4 times slower than I would expect. I have an external strobe connected to a digital pin so I can measure the rate on a logic analyzer (roughly).

I programmed a single ADC:

adc1.begin(AN_RESOLUTION_16, 32000, 1, 32)

Which I THINK means 16-bit resolution, 32KHz sample rate, 1 sample per channel, 32-deep FIFO.

Then I loop on reading this as fast as I can. Obviously I'm outstripping the FIFO and exposing the underlying sample rate. The adc1.read() is blocking, waiting for the DMA interrupt most of the time. What I see is that the adc sample is actually only being sampled twice every 250us. Two quick samples, and then 250us waiting, and two more samples...etc. So, kind of like 8KHz sampling. Not sure I understand what's going on here.

I tried different resolutions, different FIFO depths and the number of samples per channel. Nothing seems to budge this off this behavior (except moving the samples per channel to 16...which increases the period to 500us).

If I increase the sample rate to 48000, this seems to increase the sample rate as expected by 33%, but it is still slow by a factor or 4x. Is this expected? I've pasted my code below. If I'm doing something stupid (always a possibility) I would appreciate being told what it is. Thanks in advance.

#include <Arduino_AdvancedAnalog.h>

#define OUTPUT_PIN 3

AdvancedADC adc1(A0);
uint64_t last_millis = 0;

uint16_t data_Buffer[1024];
uint16_t data_ptr = 0;

void setup() {
  Serial.begin(9600);

  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!adc1.begin(AN_RESOLUTION_16, 32000, 1, 32)) {
    Serial.println("Failed to start analog acquisition!");
    while (1)
      ;
  }

  pinMode(OUTPUT_PIN, OUTPUT);
  delay(2000);
}

void loop() {
  data_ptr = 0;
  while (data_ptr < 1024) {
    digitalWrite(OUTPUT_PIN, 1);
    SampleBuffer buf = adc1.read();
    digitalWrite(OUTPUT_PIN, 0);
    data_Buffer[data_ptr] = buf[0];
    buf.release();
    data_ptr++;
  }
  for (uint16_t i = 0; i < 1024; i++) {
    Serial.println(data_Buffer[i]);
  }
  while (true) {
    digitalWrite(OUTPUT_PIN, 1);
  }
}
jmdodd95682 commented 1 year ago

I'm starting to understand what the issue really is. First there is some misunderstanding on my part on how this library works. adc.read() is returning a buffer. The intent, I guess, is to copy the contents of this buffer ASAP and release the buffer (adc.release()). I was able to hack a sketch which did this. I was able to acheive >96KHz sampling rate.

I now see that there are really two issues which make it tricky to use this to sample audio input real-time.

1) The adc.read() is a blocking operation. This means if the next buffer is not ready, you stall waiting for the interrupt. So you must first check adc.available() or you can incur a penalty.

2) The adc.read() seems to not be able to do less than 8 samples per buffer. A value for num_samples of less than 8 seems to take just as long as num_samples=8. This may be a limitation of the hardware.

The documentation does not really explain this. Perhaps better documentation is all that is required.

Anyway, I now understand the library much better.

Thanks again for creating this library.

aentinger commented 1 year ago

Hi @iabdalkader :coffee: :wave:

Can you perhaps take a look at this? :bow:

iabdalkader commented 1 year ago

The adc.read() seems to not be able to do less than 8 samples per buffer. A value for num_samples of less than 8 seems to take just as long as num_samples=8. This

I'm not sure what you're trying to do but I don't think the time you're measuring is relevant, regardless of the buffer size there will always be some overhead from the dequeue calls etc.. so read() will take some fixes time + total time to convert N samples (if there are no buffers in the queue). You can try to stop the ADC and measure how long it takes to read a buffer, but I think what really matters is that samples are 1/Fs apart regardless of the number of samples or number of buffers. As for audio sampling, the ADC is currently configured to run at 64 MHz, the total conversion time is (Sample Time + Resolution/2 + 0.5) which is 17 cycles (or 0.265625 uS) so it should be possible in theory to sample up to ~3.6 Msps.

jmdodd95682 commented 1 year ago

I'll try to capture a waveform from the logic analyzer. Here's what I see. When I put the num_samples to 1 (and the queue_depth is 8) and the sample rate to 32KHz, and I try to run adc.read() as without checking adc.available(), the waveform shows one adc.read() can complete every 250us...that about 4KH sample rate. The adc.read() blocks waiting for the DMA interrupt, so I'm guessing what is happening is that while num_samples is 1, the DMA engine always fetches at least 8 (which is 16*8=128-bits of data).

Like I said in the subsequent reply, I can get it to sample at even 96KHz, but I have to set the num_samples to 8 and check adc.available(). I guess what I learned is never use adc.read() blindly, always use adv.available() to check.

Thanks again for looking into this.

iabdalkader commented 1 year ago

so I'm guessing what is happening is that while num_samples is 1, the DMA engine always fetches at least 8 (which is 16*8=128-bits of data).

Ah I see what you mean now, yes you're right that's due to the DMA FIFO threshold, the DMA does not transfer samples immediately but it stores them in a FIFO. The FIFO level can be configured, from none (direct mode) to FULL, so it's probably something we can improve, but I'm wondering if we should just return an error on very small buffers and document this, as it's not very efficient to trigger a memory transfer on every sample.

jmdodd95682 commented 1 year ago

I agree. I don't think this is a bug, so much as documentation and possibly an error message to the user. I was used to the standard Arduino analogRead function which returns a single sample each time you run it. So, I was expecting that doing adc.read() for a single sample would be a normal operating mode. Now that I see how it really works I'm fine with 8 being the minimum for full sample rate. Doing a single data sample is very inefficient, and I'm starting to like the DMA model.

jmdodd95682 commented 1 year ago

BTW. Just to close. I was able to get the ADC working using this library for my oscilloscope sketch. Works great.

aentinger commented 1 year ago

Terrific! Glad to hear you got your sketch working. Will be closing this issue.