Arduino Due Multi Channel ADC Read & Print

Hello folks,

I need to read 4 channels of the DUE and transfer the readings at an sampling rate of 50 kHz. I read posts about DUE ADC and I know that Due can reach 1 MSPS, however when printing is involved the speed exponentially goes down.

The below code is printing part. I made my tests with 2 channels and could reach to ~5 kHz only. I see that the speed goes down due to printing. Any help/suggestions appreciated.

t = micros();
for (i = 0; i<10000; i++) {
   while ((adc_get_status(ADC)&ADC_ISR_DRDY) != ADC_ISR_DRDY);
   SerialUSB.println(adc_get_channel_value(ADC, ADC_CHANNEL_7));
   SerialUSB.println(adc_get_channel_value(ADC, ADC_CHANNEL_6));
}
t = micros() - t;

Thanks in advance.

SerialUSB.write() plus ADC PDC DMA is much faster.

An example here:

Referring to the example. If I enable two channels, buffer would hold two channels in order like; ch1(0), ch2(0), ch1(1), ch2(1)..., right? Then, the sampling rate would be half I guess?

Once you set an ADC sampling rate, this sampling rate will be the one for each and every ADC channel enabled. The ADC peripheral will automtically apply this sampling rate to all enabled channels. However the sampling rate times the number of ADC enabled channels can't be higher than 1 Msps.

Thanks for your fast reply.

So I made the below changes to the github code you shared. To enable 4 channels;

In line #31

ADC->ADC_CHER = 0xF0; // 1111 0000

And my loop is as follows;

sampleSize = 10000;
unsigned long st = micros();
for (int i = 0; i<sampleSize; i++) {
  while(obufn==bufn); // wait for buffer to be full
  SerialUSB.write((uint8_t *)buf[obufn],512); // send it - 512 bytes = 256 uint16_t
  obufn=(obufn+1)&3;    
}
unsigned long et = micros();
SerialUSB.println(sampleSize*(256/4)*1000.0 / (et-st)); // divide 256 by 4 since four channels active

This resulted in 166.66 kHz. Do you see any flaws?

And dividing the ADC_FREQ_MAX by two gives a ~100 kHz sampling rate.

adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX/2, ADC_STARTUP_FAST);

mzk1996:
Do you see any flaws?

Yes I do.

Try firstly to understand how works the example sketch as it is with only one ADC enabled channel.

I'm trying to understand as much as I can. But I'm not sure about the changes I'm making this is why I'm consulting in this forum. Can you point my mistakes more specifically. I appreciate your help.

I guess I need to enable interrupts for other channels and compare ADC_ISR with enabled channels.

Here are 2 example sketches for ADC conversions triggered at a precise frequency (44.1 KHz):

Without a PDC DMA, 44.1 KHz sampling, 1 ADC channel:

volatile uint32_t lastConversion;
volatile boolean Flag;
void setup()
{

  adc_setup();
  tc_setup();
}

void loop()
{
if (Flag== true)
{
  Flag = false;
  // A new conversion is available
}
}

/*************  Configure ADC function  *******************/
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_TRGSEL_ADC_TRIG3             // Trigger by TIOA2
                  | ADC_MR_PRESCAL(1);
  ADC->ADC_ACR = ADC_ACR_IBCTL(0b01);                   // For frequencies > 500 KHz

  ADC->ADC_CHER = ADC_CHER_CH7;                        // Enable ADC CH7 = A0
  ADC->ADC_IER = ADC_IER_EOC7;                         // Interrupt on End of conversion
  NVIC_EnableIRQ(ADC_IRQn);                            // Enable ADC interrupt

}
void ADC_Handler()
{
  lastConversion = ADC->ADC_CDR[7];
  
  Flag = true;
}
/*************  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  ************/
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  // 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


  TC0->TC_CHANNEL[2].TC_RC = 238;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 44.117 Hz
  TC0->TC_CHANNEL[2].TC_RA = 40;  //<********************   Any Duty cycle in between 1 and 874

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable

}

Note that for 2 ADC channels (e.g. ch6 and ch7), ADC conversions would be ch6/ch7/ch6/ch7/....Therefore a single interrupt would be sufficient: Interrupt On End of conversion for ch7 only, then read ADC->ADC_CDR[7] and ADC->ADC_CDR[6] in the interrupt handler.

Second example sketch:

With a PDC DMA, 44.1 KHz ADC sampling (and DAC output, useless for you), 2 ADC channels:

/*********************************************************************************************************/
/*  44.1 KHz ADC conversions of A0 , idem for A1, triggered by Timer Counter 0 channel 1 TIOA1           */
/*  44.1 KHz DAC output on DAC0, idem for DAC1, triggered by Timer Counter 0 channel 2 TIOA2             */
/*********************************************************************************************************/

volatile uint8_t bufn, Oldbufn, bufn_dac;
const uint16_t bufsize = 128;            
const uint8_t bufnumber = 4;             
const uint8_t _bufnumber = bufnumber - 1;
volatile uint16_t buf[bufnumber][bufsize];        
void setup()
{

  pinMode(LED_BUILTIN, OUTPUT);  // For ADC debugging
  pinMode(12, OUTPUT);           // For DACC debugging

  adc_setup();
  dac_setup();
  tc_adc_setup();
  tc_dac_setup();
}

void loop()
{

}

/*************  Configure adc_setup function  *******************/
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_TRGSEL_ADC_TRIG2             // Trigger by TIOA1
                  | ADC_MR_PRESCAL(1);

  // ADC->ADC_ACR = ADC_ACR_IBCTL(0b01);                 // For frequencies > 500 KHz

  ADC->ADC_IDR = ~ADC_IDR_ENDRX;
  ADC->ADC_IER = ADC_IER_ENDRX;                         // End Of Conversion interrupt enable for channel 7
  //NVIC_SetPriority(ADC_IRQn, 0xFF);
  NVIC_EnableIRQ(ADC_IRQn);                             // Enable ADC interrupt
  ADC->ADC_CHER = ADC_CHER_CH6 | ADC_CHER_CH7;          // Enable Channels 7 = A0 and 6 = A1; Trigger frequency is multiplied by 2
                                                        // The sampling frequency for 1 channel times the number of channels !!

  /*********  PDC/DMA  buffer filling sequence **********/
  ADC->ADC_RPR = (uint32_t)buf[2];                      // DMA buffer - First one will be buf[1]
  ADC->ADC_RCR = bufsize;
  ADC->ADC_RNPR = (uint32_t)buf[3];                     // next DMA buffer
  ADC->ADC_RNCR = bufsize;
  bufn = 3;
  ADC->ADC_PTCR |= ADC_PTCR_RXTEN;                      // Enable PDC Receiver channel request
  //ADC->ADC_CR = ADC_CR_START;
}

/*********  Call back function for ADC PDC/DMA **************/
void ADC_Handler () {

  //if ( ADC->ADC_ISR & ADC_ISR_ENDRX) {               // Useless because the only one
  Oldbufn = bufn;

  bufn = (bufn + 1) & _bufnumber;
  ADC->ADC_RNPR = (uint32_t)buf[bufn];
  ADC->ADC_RNCR = bufsize;

  /*******************************************/
  boolean chsel;
  for (uint8_t i = 0; i < bufsize; i++) {
    buf[Oldbufn][i] |= chsel << 12;                    // Select alternatively DAC0 and DAC1
    chsel = !chsel;
  }

  // Todo : digital filtering before DAC output
  
 /*************************************************/
  // For debugging only
  static uint32_t Count;
  if (Count++ == 689) { //84000000/8/238/2/128  = ~689
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;  // Toggle LED_BUILTIN every 1 Hz
  }
  /*************************************************/
  // }
}

/*************  Configure adc_setup function  *******************/
void dac_setup ()
{

  PMC->PMC_PCER1 = PMC_PCER1_PID38;                   // DACC power ON

  DACC->DACC_CR = DACC_CR_SWRST ;                     // Reset DACC
  DACC->DACC_MR = DACC_MR_TRGEN_EN                    // Hardware trigger select
                  | DACC_MR_TRGSEL(0b011)             // Trigger by TIOA2
                  | DACC_MR_TAG_EN                    // Output on DAC0 and DAC1
                  | DACC_MR_WORD_HALF                 
                  | DACC_MR_REFRESH (1)
                  | DACC_MR_STARTUP_8
                  | DACC_MR_MAXS;

  DACC->DACC_ACR = DACC_ACR_IBCTLCH0(0b11) //0b10
                   | DACC_ACR_IBCTLCH1(0b11) // 0b10
                   | DACC_ACR_IBCTLDACCORE(0b01);

  DACC->DACC_IDR = ~DACC_IDR_ENDTX;
  DACC->DACC_IER = DACC_IER_ENDTX;                    // TXBUFE works too !!!
  //NVIC_SetPriority(DACC_IRQn, 0xFF);
  NVIC_EnableIRQ(DACC_IRQn);
  DACC->DACC_CHER = DACC_CHER_CH0 | DACC_CHER_CH1;    // enable channels 1 = DAC1 and 0 = DAC0

  /*************   configure PDC/DMA  for DAC *******************/
  DACC->DACC_TPR  = (uint32_t)buf[0];                 // DMA buffer
  DACC->DACC_TCR  = bufsize;
  DACC->DACC_TNPR = (uint32_t)buf[1];                 // next DMA buffer
  DACC->DACC_TNCR =  bufsize;
  bufn_dac = 1;
  DACC->DACC_PTCR = DACC_PTCR_TXTEN;                  // Enable PDC Transmit channel request

}

/*********  Call back function for DAC PDC/DMA **************/
void DACC_Handler() {   // DACC_ISR_HANDLER()         // move Sinus/PDC/DMA pointers to next buffer

  //if ( DACC->DACC_ISR & DACC_ISR_ENDTX) {           // Useless because the only one

  bufn_dac = (bufn_dac + 1) & _bufnumber;
  DACC->DACC_TNPR = (uint32_t)buf[bufn_dac];
  DACC->DACC_TNCR = bufsize;
  
  /*****************************************/
  // For debugging only
  static uint32_t Count;
  if (Count++ == 689) { //84000000/8/119/128 = ~689
    Count = 0;
    PIOD->PIO_ODSR ^= PIO_ODSR_P8;  // Toggle LED_BUILTIN every 1 Hz
  }
  /****************************************/
  //}
}

/*******  Timer Counter 0 Channel 1 to generate PWM pulses thru TIOA1  for ADC ********/
void tc_adc_setup() { 

  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // TC1 power ON : Timer Counter 0 channel 1 IS TC1
  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // 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 TIOA1 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA1 on RC compare match

  TC0->TC_CHANNEL[1].TC_RC = 238;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 44.1 Hz
  TC0->TC_CHANNEL[1].TC_RA = 40;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN;               // TC1 enable

}

/*******  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  for DACC ********/
void tc_dac_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  // 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

  TC0->TC_CHANNEL[2].TC_RC = 119;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 88.2 Hz
  TC0->TC_CHANNEL[2].TC_RA = 20;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[2]. = TC_CCR_CLKEN;               // TC2 enable
  TC0->TC_BCR = TC_BCR_SYNC;                              // Synchro TC1 and TC2
}

Thanks for your valuable comments. I will make some experiments and see how it goes.