ZERO's ADC with DMA

Here is a proof of concept sketch that uses DMA to collect ADC samples. With the ADC clock running at 48MHz/16, the DMA reads a sample every 2.0 us. I just did simple testing with DAC, and jumpering to ground or 3.3v. You can experiment with various ADC settings.

EDIT: corrected github sketch

1 Like

very nice,
thanks for your work !

Mantoui you're amazing!

Have you been able to do something similar for the DAC? That would open the possibility for audio playback while CPU will do other things :slight_smile:

AloyseTech:
Have you been able to do something similar for the DAC? That would open the possibility for audio playback while CPU will do other things :slight_smile:

See the thread DAC with DMA

Hmmm... could this be used to build a simple oscilloscope?

drewfish:
Hmmm... could this be used to build a simple oscilloscope?

There is an Atmel example of using DMA to continuously send ADC values to the DAC, see
use case

Hi mantoui,

I can't get the sketch to work. I am using an M0 Pro.
When I compile the sketch it says "void ADCsync()' was declared 'extern' and later 'static' [-fpermissive]"

Any help would be greatly appreciated. I would really like to get this working.

Regards
Bob.

Ok, it is working now. It was a problem that 1.6.6 of arduino had introduced, fixed with a nightly build.

Only problem is when I run it, the console tells me the conversion is taking 2056 uS.

Any ideas?

Huh, I ran this code without modification on an Adafruit Feather M0 (Adafruit's smaller version of Arduino Zero), and I got conversion times of 2057 us too, not 2 us.

Hey guys,

If you check at the beginning of the code you'll find a buffer of 1024 analog value :

#define HWORDS 1024
uint16_t adcbuf[HWORDS];

The printed time is the one needed to complete the bufffer. It means 1024 conversions. So if you have a printed time of 2056us, divide it by HWORDS to get the time needed for one sample... :slight_smile:

void loop() {
 uint32_t t;

 t = micros();
 adc_dma(adcbuf,HWORDS);
 while(!dmadone);  // await DMA done isr
 t = micros() - t;
 Serial.print(float(t/HWORDS));  Serial.print(" us per conversion  ");
 Serial.println(adcbuf[0]);
 delay(2000);
}

I'm a little confused and would love if someone could clarify things for me. The last thing printed is adcbuf, which looks like it contains the analog values read, am I wrong about that? I applied a constant 2V to the pin and got readings that were: 0, 0, 1023, 0, 0, 1023, repeating. Also, is there any limit for how small or large hwords can be besides the memory limit? If I wanted to do only two conversion at a time would that create other issue? Thanks

Hi Mantoui

I have tried your fast adcdma and it is truly impressive.

Is it possible to disable the free run and trigger sampling anew each time the routine is called? I have tried but without success. ( I am new to this and am not sure how to proceed.) I am aiming to use the pin scan facility of the chip.

Any pointer in the right direction will be truly appreciated.

Thanks

Is there any way to change the sample rate? I'm trying to use this for an FFT which requires a very specific sample frequency, and I'm not entirely sure where to insert code... Thanks!

AloyseTech:
Hey guys,

If you check at the beginning of the code you'll find a buffer of 1024 analog value :

#define HWORDS 1024

uint16_t adcbuf[HWORDS];




The printed time is the one needed to complete the bufffer. It means 1024 conversions. So if you have a printed time of 2056us, divide it by HWORDS to get the time needed for one sample... :)



void loop() {
uint32_t t;

t = micros();
adc_dma(adcbuf,HWORDS);
while(!dmadone);  // await DMA done isr
t = micros() - t;
Serial.print(float(t/HWORDS));  Serial.print(" us per conversion  ");
Serial.println(adcbuf[0]);
delay(2000);
}

Hi rylandjackreamy,

The ADC sample time is affected by:

  1. The ADC generic clock frequency (default 48MHz).
  2. The ADC prescaler that divides down the generic clock.
  3. The ADC resolution (default 10 bits).
  4. The ADC input configuration (differential or single ended).
  5. The ADC gain (default divide by 2) and mode (single shot or free running).
  6. The ADC SAMPLEEN bitfield in the SAMPLECTRL register that adds a specfied number of half ADC clock cycles.

In mantoui's DMAC code the ADC is set as follows:

  1. Generic clock = 48MHz
  2. ADC prescaler = 16
  3. ADC resolution = 10 bits
  4. ADC input configuration = single ended
  5. ADC gain = divide by 2 and mode = free running
  6. ADC SAMPLEEN bitfield = 0

Using the formula for propagation delay (sample time) in the SAMD21 datasheet with the ADC set to single ended, free running, gain divide by 2 and 10-bit resolution:

Propagation delay = ((resolution / 2) + delay gain) / ADC CLK frequency

where:
resolution = 10 bits
delay gain = 1 (single ended mode and gain divide by 2), taken from table 33-1 in the SAMD21 datasheet
ADC CLK frequency = 48MHz / 16 = 3MHz

Propagation delay = ((10 / 2) + 1) / 3MHz = 2.0us

That's 6 full ADC clock cycles.

Note however that the 3MHz ADC clock exceeds the maximum ADC clock frequency, specified in the ADC electrical characteristics in the SAMD21 datasheet at 2.1MHz. It might be better to use the divide by 32 prescaler that will generate a 1.5MHz ADC clock giving a 4.0us sample time.

The sample time can then be extended by half ADC clock cycles by increasing the SAMPLEEN bitfield in the ADC's SAMPLECTRL register.

I think I'm missing something with how this code works. Perhaps with regard to memory consumption or access? If I remove the delay from the loop, my program/device locks up in 0-5 seconds of running. Isn't this just overwriting adcbuf every loop? Is something else allocating memory and causing the crash?

I am having the same issue with the serial port stopping printing the times. It appears to be random, or at least I can't see the correlation to anything else. I have even tried to use a dual buffer and this makes no difference. Still looking for a solution as I would like this to sample continuously.

I ran into the same problem, and posted here about it. It seems that ADC->RESULT.reg is not returning true, which causes the while(!dmadone); loop to become endless. It seems to happen more frequently without an active voltage on the pin.