i use the Zero and was looking for faster Analog reading times. I found articles for the Due (link), but nothing really for the Zero. Since i'm not verry µC experienced, it would take me a lot time to do the transfer.
But, with google i found a thread in a japanese forum, and by including their code, i come from 436µs to 21µs analog reading time. Maybe this is some use for someone:
void AdcBooster()
{
ADC->CTRLA.bit.ENABLE = 0; // Disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV64 | // Divide Clock by 64.
ADC_CTRLB_RESSEL_12BIT; // Result on 12 bits
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // 1 sample
ADC_AVGCTRL_ADJRES(0x00ul); // Adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // Sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // Enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization
} // AdcBooster
int analogPin = 1;
unsigned long start_times[100];
unsigned long stop_times[100];
unsigned long values[100];
void setup() {
AdcBooster(); //uncomment for reading time without 'boost'
Serial.begin(9600);
pinMode(analogPin, INPUT);
}
void loop() {
analogReadResolution(12); //analog resolution to 12bit
unsigned int i;
for(i=0;i<100;i++) {
start_times[i] = micros();
values[i] = analogRead(analogPin);
stop_times[i] = micros();
}
// print out the results
Serial.println("\n\n--- Results ---");
for(i=0;i<100;i++) {
Serial.print(values[i]);
Serial.print(" elapse = ");
Serial.print(stop_times[i] - start_times[i]);
Serial.print(" us\n");
}
}
The datasheet of the Atmel SAM D21E mentions readingspeed of up to 350ks, so there might still be room for some more optimisations to reach the ~3µs, like making it run in Free Running mode?
I found some more code that could be used as an example here.
You can use the ADC with DMA to fill a buffer with ADC samples. With this method, it takes around 2us to measure one sample. Depending of you needs, this could be useful.
The analogReadFast function is much faster than the original analogRead: 19us instead of 435us. For backward compatibility with AVR based boards, the resolution is 10bits. Although the SAMD21 can handle higher resolutions, this is not implemented.
Several code lines makes no sense so I blocked them out, the speed is the same.
Can anyone tell me if this is okay?
#if defined(__arm__)
int inline analogReadFast(byte ADCpin, byte prescalerBits) // inline library functions must be in header
{ // ADC->CTRLA.bit.ENABLE = 0; // Disable ADC, makes no sense?
// while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization, makes no sense?
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV64 | // Divide Clock by 64.
ADC_CTRLB_RESSEL_10BIT;
// ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // 1 sample, makes no sense?
// ADC_AVGCTRL_ADJRES(0x00ul); // Adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // Sampling Time Length = 0
// ADC->CTRLA.bit.ENABLE = 1; // Enable ADC, makes no sense?
// while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization, makes no sense?
return analogRead(ADCpin);
}
#else
int inline analogReadFast(byte ADCpin, byte prescalerBits) // inline library functions must be in header
{ byte ADCSRAoriginal = ADCSRA;
ADCSRA = (ADCSRA & B11111000) | prescalerBits;
int adc = analogRead(ADCpin);
ADCSRA = ADCSRAoriginal;
return adc;
}
#endif
So wait; how is that faster even though it calls the normal analogRead()? Just by changing the prescaler on the clock?
I was thinking that it should't be necessary to disable/renable the adc for each reading, though that makes it harder to co-exist with the existing code.
The main issue with the ADC's current configuration on the Arduino Zero is not only the ADC's prescaler, but also the SAMPEN bitfield in the ADC's SAMPCTRL register.
In the Arduino Zero core file "wiring.c" the SAMPEN is set to the maximum of 63 (0x3F), this adds an extra 63 half ADC clock cycles to the sample time.
The sample time can be adjusted in this way to account for a higher source resistance, the calculations for the minimum sample time are provided in the Electrical Characteristics at the back of the SAMD21 datasheet.
In my opinion, setting SAMPEN to 63 is extremely conservative. Even with the SAMPEN bitfield set to 0, the source resistance can be up to 165kOhms and is unlikely to trouble the majority of Arduino Zero users, while also offering a four fold decrease in ADC conversion time from the current 430us to about 90us.
It's then a matter of how low to set the ADC's prescaler? At 128 the prescaler reduces the conversion time down to around 28us with a maximum source resistance of 38kOhms.
In my code, I simply add a couple of lines to my sketch's setup() function to set SAMPEN to 0, as well as adjusting the ADC's prescaler and resolution:
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)
Hi Avandelen, I like that analogreadfast() library, many thanks for that. I am interested in the same issue for an Arduino Due. Do you happen to know whether it also works for the Due?