Faster Analog Input

My project will monitor a low frequency AC wave and I need to collect 1000 or more analog samples in 16 mSec. A for loop with "DataBuffer = analogRead(sensorPin);" is far too slow. In the DUE forum I see code for free-running the ADC and for DMA access. Those code segments do not compile- possibly there are library includes missing.
Can someone point me to some code for ZERO which will collect data in the faster modes?
In the end I will monitor two analog inputs alternately and compare them after the samples are collected.

Hi,

The Zero /M0 PRO has a configurable ADC. This means you can change how the ADC operates by loading different values into registers. (see http://www.atmel.com/images/atmel-42181-sam-d21_datasheet.pdf, pages: 846- 885 for description.)

To speed the ADC read-up you need to manipulate the registers. See my post: ADC instability in M0 Pro - Arduino Zero - Arduino Forum

The standard analogRead(sensorpin); function takes about 220 µs according to my measurement with an oscilloscope. The hex values for the registers and my interpretation were:

INTFLAG: 8 //Synch read interrupt set
INTENSETs: 0
INTENCLR: 0
EVCTRL: 0 // No events set-up
SAMPLEN: 3F // Max sample length
CTRLB: 520 // Prescale factor 128, 10-bit single-conversion, single ended
AVGCTRL: 11 // Average two successive conversions
SWTRIG: 0 // set to 1 to start conversion and no flush action
INPUTCTRL: 1802 // ADC1 input pos and internal negative input.
REFCTL: 1 //2.2297 V internal reference

These are very conservative settings. Probably, they were chosen on purpose to give high stability and reliability.

For your requirement of 1000 samples in 16ms you need a sample time of 16 µs. This is fast, but the zero should be able to do this according to specification where is says 350 ks/s ADC.

By experimenting with the register settings I could get a stable 12 bit conversion time of ~27µs. Shorter than this - it became unstable. This is described in the post. I think it may be possible to to speed things up further by changing the ADC clock, but I had not had time to understand the generic clock generator.

A code example to read and change the ADC registers and a readout routine is given in the post.

In speeding up the ADC it is important that the output impedance of the source is low or the incomplete charging of the sample and hold capacitor will give errors. 50 Ohm should be OK according to my estimate.

I wish you success. (Then please tell me how you did it!!!)

Harry J. Whitlow

Thanks so much, Harry. I am not new to writing code-I have written lengthy projects in assembler for 8080 and derivatives and in other pre-C structured languages. So it is the details of how to write to the registers that has me hung up. I made my living writing code in the 70s.

I wrote a simple loop sketch:

for (int i=1000, i, i--) {
Value= analogRead(A0);
}

using millis() to get me the begin and end times. 12 bit ADC came in at 434 microSeconds per conversion, ugh!
With a null loop (and compiler optimization off) 0.2 uSec per loop, so analogRead is insanely slow.

I see in you other post the code for wiring_analog.h which seems to be a library. Reading up I can install libraries from a .zip or manually if I can find the .h and .cpp files. Can you give me a boost to find them?

I'd like to see your test sketch code if you'd care to share it. From your example and the libraries I'll be off to a running start.

Ah, I found your sample program. It's a new forum for me and I am discovering how to better use it. I copied in your sample sketch2 and it compiles without error. I'll o'scope the line and duplicate your results before going on. You've saved me a whole lot of time, thanks! Rick H

I am getting samples at 7.5 µSec now without going to free-run mode or DMA to store the data. ADC noise came way down when I powered the Zero with a power supply set to 8 volts. USB power just doesn't do the job for clear analog data.

Thanks for this suggestion. I tried it with a 12 V 1A switch-mode supply and it did indeed make an improvement! I did not however manage to get the conversion down to 8 µs for single conversion with proper performance. The reason being that ADC conversion value saturated at about 3600. I did try increasing the sampling time - but this did not help. Increasing the prescale value in CTRLB did improve matters - but slowed things up.

I also looked at how the ADC clock signal is generated but found there is no good reason to change things. The ADC is fed via the generic-clock generator with an 8 MHz clock from the 8 MHz oscillator. Setting CTRLB to 0x0000 gives a prescale of 4, 12 bit resolution and single conversion. The prescale factor of 4 gives an ADC clock of 2 MHz which according to the data sheet is less than the maximum clock frequency of 2.1 MHz. This was too fast to get a correct conversion - the ADC conversion value saturated. The fastest I could do was after setting CTRLB to 0x0400 (prescale = 64) which gave a 13 µs conversion time according to my oscilloscope and full ADC span.

Regarding the reason why I could not achieve full speed conversion time I found note 1 on table 36-21 of the ADC operating conditions: "These values are based on characterization. These values are not covered by test limits in production." It can then be that the maximum speed is limited in my case by manufacturing variations. It may also depend on the ADC resolution and single, or free-running conversion. I have not found any other obvious reason why the ADC did not achieve the specified conversion speed.

I have attached a sample code for the fast ADC as this might be useful for others wanting to speed up the ADC readout. It uses the DAC to generate a DC test voltage. I checked the calculation and the output impedance should be low enough to give sufficiently fast-charging to the sampling capacitor. ~5E-7 sec.

Harry J. Whitlow

Fast_ADC.ino (4.33 KB)

whitlow:
...
The ADC is fed via the generic-clock generator with an 8 MHz clock from the 8 MHz oscillator. Setting CTRLB to 0x0000 gives a prescale of 4, 12 bit resolution and single conversion. The prescale factor of 4 gives an ADC clock of 2 MHz which according to the data sheet is less than the maximum clock frequency of 2.1 MHz. This was too fast to get a correct conversion - the ADC conversion value saturated. The fastest I could do was after setting CTRLB to 0x0400 (prescale = 64) which gave a 13 µs conversion time according to my oscilloscope and full ADC span.

Are you sure about 8mhz oscillator?? I've looked at firmware wiring.c for both M0 Pro and ZERO, and the ADC uses generic clock 0 (48mhz). ZERO prescales by 512, M0 PRO by 128

See

https://github.com/arduino-org/Arduino/blob/1.7.5/hardware/arduino/samd/cores/arduino/wiring.c

generic clock 3 is running at 8mhz (startup.c)

Thanks for the last comment. It triggered some more investigations which lead to progress. First though as it seems the Zero and M0 Pro use different code, let me declare I am using the arduino.org IDE and v1.7.6 with an Arduino M0 PRO. Referring to my previous post, ADC instability in M0 Pro - Arduino Zero - Arduino Forum the prescale factor is determined to be 128. This is exactly what Mantoui said it should be.

(i) Mantoui is correct in that the ADC is clocked by GCLKGEN0 as set-up in wiring.c (I could not read the GCLK GENCTRL register correctly for some reason.) The clock rate is should be 48 MHz but I could not verify this. The value is from startup.c and wiring.c. I took these from the Arduino app package on my Mac.

(ii) The 8 MHz RC oscillator is connected to GCLKGEN3 in startup 3. Again Mantoui is correct.

I used a better oscilloscope this time and noticed the DC logic 1 value on the digital output line (pin 10) was not stable >:( .

I experimented with changing the ADC clock to the 8 MHz RC clock GCLKGEN3. The ADC had full range (0-4095 ) conversion only for conversion times of 13 µs or greater (ADC prescale 8). At prescale=1 it was crimped especially at high conversion values. What was remarkable was the instability on the digital output disappeared :slight_smile: .

Thinking about what rickrlh said a few entries above about the power hinted that not being able to achieve high conversion values for short conversion times might depend on the the voltage levels in the analogue circuits. So I changed the reference voltage to the 1 V internal bandgap reference. I then found the ADC gave conversion values over the whole 0-4095 span with a ADC clock prescale value of 4 and a single conversion time of 6.8 µs. So this close to my goal of 4-5 µs conversion time. So reducing the reference voltage means the comparitors work with lower voltages and hence work faster :slight_smile: .

Fig. 32-6 shows that for my configuration the conversion time should take 7 ADC clock cycles. This is 3.5 µs for a 8 MHz clock and a prescale factor of 4. From the oscilloscope the conversion time is 6.8 µs for my minimal readout function anaRead(). I now wonder where the extra 3.3 µs delay comes from? :confused: Can it be the sync latency? Is DMA needed to convert faster?

I have attached the code which now works with a 7 µs conversion time for 0->1 V input span.

Harry J. Whitlow

_7E-7s_ADC_read.ino (4.67 KB)

whitlow:
Can it be the sync latency?

12.3.8 of SAMD datasheet has an equation for range of sync latency that would suggest 0.25 us or less for the sync time (9 to 12 CPU cycles).

As another measurement, I changed anaRead() to calculate duration of anaRead with SysTick->VAL.

uint32_t anaRead() {
  uint32_t t = SysTick->VAL;
  ADC->INTFLAG.bit.RESRDY = 1;              // Data ready flag cleared
  ADCsync();
  ADC->SWTRIG.bit.START = 1;                // Start ADC conversion
  while ( ADC->INTFLAG.bit.RESRDY == 0 );   // Wait till conversion done
  ADCsync();
  uint32_t valueRead = ADC->RESULT.reg;     // read the result
  ADCsync();
  ADC->SWTRIG.reg = 0x01;                    //  and flush for good measure
  t = t - SysTick->VAL;
  return t;
//  return valueRead;
}

It returns tick counts (CPU cycles) from 302 to 317, and at 48 mhz that's 6.7 us (what you were getting with scope).
I measured just the sync time with SysTick, got 10 ticks. There might be some speed up by running ADC in continuous mode (no .START).

The 8mhz clock is an RC oscillator and it could be off (but probably not by a factor of 2). I have measured the frequency error of the 8mhz oscillator on my ZERO at 4170 ppm.

I don't think you need ADCsync after RESRDY test (ref wiring_analog.c and 31.6.16)

EDIT:
I hacked in ADC_CTRLB_FREERUN mode, doing two test/read/sync's in anaREAD(), the first to flush the register, which should be fast since i'm doing print after each call to anaREAD. I would get tick counts from 45 to 192 ticks. 192 is about 4 us and what we might expect, not sure why we get low cycle count??

I don't think you need to flush the RESULT register, it is overwritten by latest data. so if your app spends more than 190 cycles doing something, then next ADC result will be ready "immediately". This all assumes you are using only one ADC channel/pin. You would need to flush if you switch to another pin/channel.

I timed 3 free-running test/read/sync sequences, and the total was around 500 cycles, or 166 per ADC read, or about 3.5us per read.

Also see ADC with DMA thread, bout 2us per ADC sample.

Thanks once again for your efforts Mantoui its a big help. I am not an expert with C/C++ programming or the internal workings of Arduinos.

I tried the anaRead() function with timing and got 306 ticks. So I guess my RC oscillator is not so bad.

I then used SysTick to time the different lines in anaRead to see where time consumed.

uint32_t anaRead() {
    uint32_t t = SysTick->VAL;
  ADC->INTFLAG.bit.RESRDY = 1;              // Data ready flag cleared
  //  14 tick  (0.29 µs)
  ADCsync();
  //  12 tick  (0.25 µs)
  ADC->SWTRIG.bit.START = 1;                // Start ADC conversion
    //  48 tick  (1.00 µs)
  while ( ADC->INTFLAG.bit.RESRDY == 0 );   // Wait till conversion done
   //  220 tick  (4.58 µs)
  ADCsync();
     //  9 tick  (0.19 µs) 
  uint32_t valueRead = ADC->RESULT.reg;     // read the result
      //  7 tick  (0.15 µs)
  ADCsync();
     //  9 tick  (0.19 µs)
  ADC->SWTRIG.reg = 0x01;                    //  and flush for good measure
    //  3 tick  (0.13 µs)
    // Total time
    //  306 tick  (6.38 µs)
  t = t - SysTick->VAL;
  return t;
   //return valueRead;
}
//##############################################################################

My interpretation is that the conversion takes the longest time. It is different from the value you got with a free running ADC. It seems there is a difference in the conversion time for free running conversions and single conversion. The ADC start takes a whole µs. I absolutely need to run in single conversion mode for my application (described below) so not much to do about this.

I took your advice concerning synchronization after setting the INTFLG register and also the flush at the end. Flush it seems is only needed if a conversion is aborted. (I presume the ZERO/M0 Pro has a pipeline ADC. I never found this documented.) So now I get 5.9 µs conversion time. :slight_smile: I attached the full code.

My application is digitising analogue pulses from X-ray detectors and Si pin diodes. The electronics chain produces Gaussian shaped pulses with amplitude to particle / X-ray energy. I use a fast analog peak sensing circuit as a sample and hold. The pulses come at random times at an average rate of about 3000/s. An analogue discriminator is connected to an interrupt is used to start a single conversion for each pulse. The conversion time must be short as the random, arrival time of the pulses can lead to two closely spaced pulses being interpreted and a single pulse - so called pulse pile-up. This leads to an unwanted background in the pulse height spectra. The gold-standard for this type of work is a Wilkinsson ADC. These have a conversion time of 10-30 µs depending on pulse height. So we expect to outperform the standard with the M0 PRO. A few more weeks is needed to get the thing working.

Harry J. Whitlow

_6E-6s_ADC_read.ino (4.53 KB)

Hello,

I am also very interested in doing fast A to D. The event I am trying to detect is very short duration, so the faster the better.

Did anyone get mantoui's ADC with DMA to work?

When I compile the sketch I get "void ADCsync()' was declared 'extern' and later 'static' [-fpermissive]"

I am very new to arduino, and have spent a day trying to understand what the error is, but no luck.

Regards
Bob.

Ok I got the sketch to compile with a newer version of the IDE.

Only trouble is conversions are taking 2mS, instead of 2uS.

Bob.

Sample Rate (from the Prescaler) can be up to the Maximum, but with loss of Resolution Bits

  • No damage done of course ... somebody posted this ridiculous statement/question somewhere.

__ADC_Freq_ResolutionBitErrors_Test1_Graph.jpg

ARDUINO ADC Speeds (with 16MHz Clock) :

 Speed: PreScaler: Time: Freq: ActFrq: BitsRes:
 
 120KHz 128 116us 9.6ksps 9.6ksps 10b
 240KHz 64 60us 19ksps 19ksps 10b
 500KHz 32 36us 38ksps 38ksps 10b
 1MHz 16 20us 77ksps 50ksps 9b
 2MHz 8 13us 154ksps 77ksps 8b
 4MHz 4 9us 308ksps 111ksps 6b
 8MHz 2 7us 615ksps ? ?


ARDUINO ADC Speeds (with 20MHz Clock) :

 Speed: PreScaler: Time: Freq: ActFrq: BitsRes:
 
 1MHz 16 90ksps 9b
 2MHz 8 170ksps 8b
 4MHz 4 350ksps 6b
 8MHz 2 615ksps ?


ARDUINO ADC Speeds (with 32MHz Clock) :

 Speed: PreScaler: Time: Freq: ActFrq: BitsRes:
 
 1MHz 16 150ksps 9b
 2MHz 8 300ksps 8b
 4MHz 4 600ksps 6b
 8MHz 2 1.2Msps ?

Good explaination here:

5Msps (yes 5Mega) using Ca3306 with Arduino:

References (great High Speed ADC directions for AVR/Arduino) :

http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/
http://frenki.net/2013/10/fast-analogread-with-arduino-due/