Go Down

Topic: Arduino Due reading 12 ADC channels simultaneously with minimum samplerate 1kHz (Read 270 times) previous topic - next topic

S1058293

Hi Guys,

I'm quite new to Arduino and this forum. Though I've read many topics surrounding my subject, it's still not possible for me to get what I want.

For a project, I want to measure 12 Analoge voltages simultaneoulsy at waves of 75Hz each.
The minimum desired samplerate is 1kHz per channel. So I went for the Arduino Due which has 12 Analoge inputs, and following the datasheet a ADC with sample rate up to 1MSPS.

The values of the readings, I want to transfer to a Raspberry Pi 3B+, to store and to compute later. I tried Serial communication with a baudrate of 115200.

The problem for me lies with getting the samplerate at 1kHz. At this moment, the maximum sample rate is 196 Hz.

Can anyone help me to get a higher frequency?


Regards,
Laxmen

Code: [Select]

unsigned long timeStart;
unsigned long timeEnd;
unsigned long testTimer;
int freq = 0;

int countLoop = 0; 
int value[11];


void setup() {
  // put your setup code here, to run once:
Serial.begin(115200); 
  REG_ADC_MR = (REG_ADC_MR & 0xFFF0FFFF) | 0x00020000; //ADC STARTUP to 2/ 21mHz
  REG_ADC_MR = (REG_ADC_MR & 0xFFFFFFEF); // LOWRES = 0, dus 12-BIT resolutie
  REG_ADC_MR = (REG_ADC_MR & 0x7FFFFFFF); // USEQ = 0, dus normal mode
  REG_ADC_MR = (REG_ADC_MR & 0xFF7FFFFF); // ANARCH = 0, dus DIFF0, GAIN0 en OFFSET0
  REG_ADC_COR = (REG_ADC_COR & 0x0); // DIFFx = 0 on all channels -> Single ended mode. No offset
  ADC->ADC_MR |= 0x80;  //set free running mode on ADC
  ADC->ADC_CHER = 0x3CFF; // Enable A0 tm A11
  delay(2000);
}

void loop() {
  // put your main code here, to run repeatedly:

timeStart = micros();
testTimer = millis();
delayMicroseconds(50);
     while((ADC->ADC_ISR & 0x3CFF)==0); // wait for conversion on EOC7
      value[0]=ADC->ADC_CDR[7]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[1]=ADC->ADC_CDR[6]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[2]=ADC->ADC_CDR[5]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[3]=ADC->ADC_CDR[4]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[4]=ADC->ADC_CDR[3]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[5]=ADC->ADC_CDR[2]; //get values in [0-4095]
//      delayMicroseconds(50);   
      value[6]=ADC->ADC_CDR[1]; //get values in [0-4095]   
//      delayMicroseconds(50);
      value[7]=ADC->ADC_CDR[0]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[8]=ADC->ADC_CDR[10]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[9]=ADC->ADC_CDR[11]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[10]=ADC->ADC_CDR[12]; //get values in [0-4095]
//      delayMicroseconds(50);
      value[11]=ADC->ADC_CDR[13]; //get values in [0-4095]
//      delayMicroseconds(50);


      for (int i = 0; i < 12; i++)
      {
        Serial.print(value[i]);
        Serial.print("\t");
      }
      timeEnd = micros()-timeStart;
      freq = 1000000/timeEnd;
Serial.print("\t");
Serial.print(" | ");
Serial.print("\t");
Serial.print(timeEnd);
Serial.print("\t");
Serial.print(freq);
Serial.print("\t");
Serial.print(countLoop);
Serial.println("\t");
countLoop++;
}

ard_newbie

Whenever you read n ADC channels, keep in mind that (n * samplerate) <= 1 MHz with PRESCAL = 1.

The ADC peripheral automatically multiplies the input conversion frequency for 1 channel with the number of enabled channels.

Every Serial.print (or printf()) takes a few hundreds of us, so use it with moderation only during debugging stage.


Here is an example sketch to read n channels, each at 10 Hz.

Code: [Select]

/*******************************************************************************/
/*                     10 Hz ADC conversions for nchannels                     */
/*******************************************************************************/
#define SampleRate  (10)
uint8_t channels[] = {7,6,5};
#define nchannels (sizeof(channels))
uint16_t Buf[nchannels];

volatile boolean FlagConversion;

void setup()
{
  Serial.begin(250000);
  pinMode(LED_BUILTIN, OUTPUT);
  adc_setup();
  tc_setup();
}

void loop()
{

  if (FlagConversion == true)
  {
    FlagConversion = false;
    for (int i = 0; i < nchannels; i++)
    {
      printf(" ADC[%d] = %d\n", channels[i], Buf[i]);
    }
  }
}

/*************  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_PRESCAL(1) |
                  ADC_MR_TRGSEL_ADC_TRIG3;               // Trigger by TIOA2 Rising edge

  ADC->ADC_IDR = ~(0ul);
  ADC->ADC_CHDR = ~(0ul);
  for (int i = 0; i < nchannels; i++)
  {
    ADC->ADC_CHER |= ADC_CHER_CH0 << channels[i];
  }
  ADC->ADC_IER |= ADC_IER_EOC0 << channels[nchannels - 1];
  //ADC->ADC_ACR = ADC_ACR_IBCTL(0b01);                    // For frequencies > 500 KHz
  ADC->ADC_PTCR |= ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS;    // Disable PDC DMA
  NVIC_EnableIRQ(ADC_IRQn);                              // Enable ADC interrupt
}

/*************  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 = F_CPU / 8 / SampleRate; //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 10 Hz
  TC0->TC_CHANNEL[2].TC_RA = 50;  //<********************   Any Duty cycle in between 1 and (TC_RC - 1)

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

void ADC_Handler() {

  static uint32_t LedBlink;

  for (int i = 0; i < nchannels; i++)
  {
      Buf[i] = (uint16_t) * (ADC->ADC_CDR + channels[i]);
  }
   FlagConversion = true;
 
  // For debugging only : Blink every 1 Hz
  
  if (LedBlink++ == SampleRate)
  {
    LedBlink = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;
  }
  
}


S1058293

Thanks Ard for your fast reply!

Whenever you read n ADC channels, keep in mind that (n * samplerate) <= 1 MHz with PRESCAL = 1.

The ADC peripheral automatically multiplies the input conversion frequency for 1 channel with the number of enabled channels.
What do you mean by this?  I have to use all 12 channels, does this mean that de ADC Peripheral multiplies the input conversion frequency with 12 for each channel?

Though I still don't understand what this means to be honest.

Every Serial.print (or printf()) takes a few hundreds of us, so use it with moderation only during debugging stage.
What would be a better way to send data to another device (Rasp Pi). I was trying to do this with the USB connection, but sounds like that is not the proper way.

Is it even possible to read 12 channels at 1kHz each 'simultaneously' and send the data over with the Arduino Due?

ard_newbie

This is straightforward:  

1/ ADC channels are multiplexed. You choose how many channels you want the uc multiplexes when you enable channels.

2/ You set the conversion frequency. Each channel is sampled at the same frequency than the other enabled channels. The total: number of channels times the conversion frequency for a single channel can't be greater than 1 MHz.


Of course you can use Serial.print , or Serial.Write which is faster.

S1058293

Thanks again! I'm understanding it a bit better now. I'll try your code for my project :) 

Go Up