I am trying to follow, adapt, understand (and clean up a bit) a variation around the code available there, for the Arduino Due: https://forum.arduino.cc/index.php?topic=589213.0 . I do not like the forum format, as things end up buried deep, so asking here instead. Unfortunately this means that there is quite a lot of explanations before the question. If you think this is wrong to post it here, let me know, and I can move.
Basically, the idea is to log several ADC channels in a buffer, using timer-based triggering. There is a bit of setup:
// sample rate in Hz
constexpr int sample_rate = 1000;
constexpr uint8_t channels[] = {7, 6, 5, 4, 3};
constexpr int nbr_channels = sizeof(channels);
Then time counter 0 channel 2 is set at the right frequency for triggering the ADC conversion:
// use time counter 0 channel 2 to generate the ADC start of conversion signal
// i.e. this sets a rising edge with the right frequency for triggering ADC conversions corresponding to sample_rate
// for more information about the timers: https://github.com/ivanseidel/DueTimer/blob/master/TimerCounter.md
// NOTE: TIOA2 should not be available on any due pin https://github.com/ivanseidel/DueTimer/issues/11
void tc_setup() {
PMC->PMC_PCER0 |= PMC_PCER0_PID29; // TC2 power ON : Timer Counter 0 channel 2 IS TC2
TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2 // clock 2 has frequency MCK/8, clk on rising edge
| TC_CMR_WAVE // Waveform mode
| TC_CMR_WAVSEL_UP_RC // UP mode with automatic trigger on RC Compare
| TC_CMR_ACPA_CLEAR // Clear TIOA2 on RA compare match
| TC_CMR_ACPC_SET; // Set TIOA2 on RC compare match
constexpr int ticks_per_sample = F_CPU / 8 / sample_rate; // F_CPU / 8 is the timer clock frequency, see MCK/8 setup
constexpr int ticks_duty_cycle = ticks_per_sample / 2; // duty rate up vs down ticks over timer cycle; use 50%
TC0->TC_CHANNEL[2].TC_RC = ticks_per_sample;
TC0->TC_CHANNEL[2].TC_RA = ticks_duty_cycle;
TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
}
Finally this can be used to trigger the ADC:
// start ADC conversion on rising edge on time counter 0 channel 2
// perform ADC conversion on several channels in a row one after the other
// report finished conversion using ADC interrupt
void adc_setup() {
PMC->PMC_PCER1 |= PMC_PCER1_PID37; // ADC power on
ADC->ADC_CR = ADC_CR_SWRST; // Reset ADC
ADC->ADC_MR |= ADC_MR_TRGEN_EN | // Hardware trigger select
ADC_MR_PRESCAL(1) | // the pre-scaler: as high as possible for better accuracy, while still fast enough to measure everything
// see: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due
ADC_MR_TRGSEL_ADC_TRIG3; // Trigger by TIOA2 Rising edge
ADC->ADC_IDR = ~(0ul);
ADC->ADC_CHDR = ~(0ul);
for (int i = 0; i < nbr_channels; i++)
{
ADC->ADC_CHER |= ADC_CHER_CH0 << channels[i];
}
ADC->ADC_IER |= ADC_IER_EOC0 << channels[nbr_channels - 1];
ADC->ADC_PTCR |= ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS; // Disable PDC DMA
NVIC_EnableIRQ(ADC_IRQn); // Enable ADC interrupt
}
and the ADC output can be captured in the corresponding ISR:
void ADC_Handler() {
for (size_t i = 0; i < nbr_channels; i++)
{
SOME_BUFFER[i] = static_cast<volatile uint16_t>( * (ADC->ADC_CDR + channels[i]) & 0x0FFFF ); // get the output
}
}
I think this is quite understandable, but I have one question: the setting of the pre-scaler.
if I understand well discussions online, the pre-scaler should be set so that
frq_ADC >= sample_rate * nbr_channels
, basically because the chip is just multiplexing the ADC through several channelsif I understand well, we want to set such pre-scaler value as high as possible given the previous constraint, so that the ADC frequency is as low as possible, because this improves ADC conversion quality
Is that right?
The problem is that I am confused about how to set the pre-scaler, and what value corresponds to what, because what I find in the datasheet disagree with some other online responses I read.
From the datasheet https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf : "The ADC clock range is between MCK/2, if PRESCAL is 0, and MCK/512, if PRESCAL is set to 255 (0xFF).". This is consistent with what I find on page 1334: "ADCClock = MCK / ( (PRESCAL+1) * 2 )". But page 1318, it is written that the conversion rate is 1MHz. Then how is that compatible with having a MCK frequency of 84MHz on the Due? 84/2 = 48MHz, 84/512 = 0.164MHz, the high freq value is too high.
Then to add to the confusion I have found this issue: https://arduino.stackexchange.com/questions/12723/how-to-slow-adc-clock-speed-to-1mhz-on-arduino-due/21054#21054 that also seem to conflict with the 1MHz upper bound.
Any idea where I misunderstand something? (and any more comments around the general working of the program?).
Ok, so I did some tests with the code, checking when I was missing some conversions depending on the timer frequency and the prescaler value. The code is a bit long, so I post it at the end of the answer. Basically:
I think this indicates that:
the pre-scaler value is well an 8 bits int, between 0 and 255, as it wraps up at 256
I have difficultie matching the results to the formula in the datasheet. I guess this is because there is some overhead switching channels etc (?). For example:
the results are consistent with ```ADC_freq = 1MHz / ( ps ) at the highest frequencies, but I suppose this is because there is a bit of overhead switching channels
the results are consistent with ```ADC_freq = 2MHz / ( ps ) at 10 kHz, and at 1kHz, even using the highest prescaler is fine.
The code I was using is the following, and the criterion for deciding that things fail is that the code reports a drop in the effective sample frequency over the 5 channels: