Broken ADC?

Hello everybody,

For a project i’m trying to capture the vibrations sensed by an analogue accelerometer. To capture the vibrations correctly, I know I need a rather high sampling speed.

Herein lies the problem. I’m using a MKR1000 board to capture the data, but when I try to verify the sampling speed I average about 1200 samples per second which is slow for an SAMD21 board, if I’m correct? Since the Arduino Uno has a standard (theoretical) sampling speed of ±9600 Hz. This sampling speed of 1200 S/s is really constant as well. I did not change any prescalers, clock speeds or have serial prints in the code. If I change to a MKR NB1500 board, I get the same results. Since both boards output the correct ADC values, just at ‘low’ sampling speeds, I am rather puzzled about what the problem could be?

In attachment you can find the code used to output the sampling speeds. If I forgot about some valuable information, just let me know.

Example.txt (411 Bytes)

Hi count_jones,

The MKR board's ADC is capable of sampling up to 350k samples/s, however Arduino have set the sample rate for analogRead() to a very conservative 1200 samples/s.

The ADC is clocked at 48MHz from generic clock 0 (GCLK0). This clock is divided down by the ADC prescaler set to 512:

ADC Clock Rate = 48MHz / 512 = 93750Hz

Arduino sets the ADC to single ended, single read mode, gain divide by 2 with 10-bit resolution. According to the SAMD21 datasheet, a conversion takes 7.5 ADC clock cycles to complete:

Conversion ADC clock cycles = 1 + (10-bits / 2) + 1.5 delay gain = 7.5 clock cycles

However, in addition Arduino set the ADC sample control register to 0x3f (63 decimal), this adds a further 64 half clock cycles to the conversion time:

ADC Conversion Time = (7.5 / 93750) + ((63 + 1) * (1 / (2 * 93750))) = 421.333us = 2373 samples/s

The Arduino analogRead() function performs two sequential reads each time it's called, therefore this gives a final sample rate of:

2373 / 2 = 1187 samples/s

Which is what you're observing on the MKR 1000 and 1500 boards.

It's possible to change the ADC sample rate, but this requires register manipulation.

Thanks for the reply and the good explanation!

What is the reason that this is done? And why isn't it done on the regular 8 bit boards? Or do they?

Hi count_jones,

I don't know why Arduino decided to set the sample control register to the maximum value of 0x3f. Increasing the sample time gives the ADC's sample and hold capacitor more time to charge, allowing for a larger source resistance at the analog input.

Here's an example that reads the ADC on A0, using single shot mode, 12-bit resolution and up to 11000 samples/s with register manipulation:

// Set-up the ADC to read analog input on A0 with 12-bit resolution
void setup() {
  SerialUSB.begin(115200);                         // Set-up the native serial port at 115200 bit/s
  while(!SerialUSB);                               // Wait for the console to open
  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 |    // Divide Clock ADC GCLK by 512 (48MHz/512 = 93.7kHz)
                   ADC_CTRLB_RESSEL_12BIT;         // Set ADC resolution to 12 bits
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->SAMPCTRL.reg = 0x00;                        // Set max Sampling Time Length to half divided ADC clock pulse (5.33us)
  ADC->INPUTCTRL.bit.MUXPOS = 0x0;                 // Set the analog input to A0
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->CTRLA.bit.ENABLE = 1;                       // Enable the ADC
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization

void loop() {
  ADC->SWTRIG.bit.START = 1;                       // Initiate a software trigger to start an ADC conversion
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for write synchronization
  while(!ADC->INTFLAG.bit.RESRDY);                 // Wait for the conversion to complete
  ADC->INTFLAG.bit.RESRDY = 1;                     // Clear the result ready (RESRDY) interrupt flag
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for read synchronization
  int result = ADC->RESULT.reg;                    // Read the ADC result
  SerialUSB.println(result);                       // Output the result
  delay(500);                                      // Wait for 500ms

Thank you very much for the example code, since this is a bit over my head!

Thank you very much for the example code, since this is a bit over my head!

No worries. It's possible to increase the sample rate up to 6250 samples/s and use analogRead(), just by setting the sample control register to zero at the beginning of the setup() portion of your sketch:

ADC->SAMPCTRL.reg = 0x00;
Thereafter just call analogRead() as usual.