Go Down

Topic: speed of analogRead (Read 16 times) previous topic - next topic

PakARD


I'm working on a similar project at the moment. If analogRead is too slow for you it is possible to put the ADC in 'free-running' mode and read the registers directly - doing this I am getting conversions in 1uS (although I have not tested for accuracy yet). This is the code I was using to test speed:

Code: [Select]

void setup() {
  Serial.begin(9600);
  int t=analogRead(0);

  ADC->ADC_MR |= 0x80; // these lines set free running mode on adc 7 (pin A0)
  ADC->ADC_CR=2;
  ADC->ADC_CHER=0x80;
}

void loop() {
  int t=micros();
  int q=0;
  int a0;
  for(int i=0;i<1000000;i++){
    while((ADC->ADC_ISR & 0x80)==0); // wait for conversion
    a0=ADC->ADC_CDR[7];              // read data
    q+=a0;
  }
  t=micros()-t;
  Serial.print("1 million conversions in ");Serial.print(t);Serial.println(" micros");
}



This piece of code is great!! I'm sampling at 1MHz (one channel) in my Due.
Would the registers setup change for two channels?

Thank you


stimmer

#6
Feb 28, 2013, 02:35 am Last Edit: Feb 28, 2013, 10:36 pm by stimmer Reason: 1
It's similar. Basically in free running mode the SAM3X cycles through all enabled channels. So we just enable 2 channels and wait for 2 conversions each time:
Code: [Select]
void setup() {
 Serial.begin(9600);
 int t=analogRead(0);

 ADC->ADC_MR |= 0x80; // these lines set free running mode on adc 7 and adc 6 (pin A0 and A1 - see Due Pinout Diagram thread)
 ADC->ADC_CR=2;
 ADC->ADC_CHER=0xC0; // this is (1<<7) | (1<<6) for adc 7 and adc 6                                    
}

void loop() {
 int t=micros();
 int q0=0,q1=0;
 int a0,a1;
 for(int i=0;i<500000;i++){
    while((ADC->ADC_ISR & 0xC0)!=0xC0);// wait for two conversions (pin A0[7]  and A1[6])
   a0=ADC->ADC_CDR[7];              // read data on A0 pin
   a1=ADC->ADC_CDR[6];              // read data on A1 pin
   q0+=a0;
   q1+=a1;
 }
 t=micros()-t;
 Serial.print("500000 pairs of conversions in ");Serial.print(t);Serial.println(" micros");
 Serial.print("A0 total:");Serial.println(q0);
 Serial.print("A1 total:");Serial.println(q1);
}


Obviously there's only one ADC which is shared by the 2 channels so the effective sample rate halves. This means that you will lose some accuracy, because internally the chip is having to switch between two different input signals 1 million times per second, and that won't be as good as it tracking the same input all the time like in the 1-channel example.

PakARD


It's similar. Basically in free running mode the SAM3X cycles through all enabled channels. So we just enable 2 channels and wait for 2 conversions each time:
Code: [Select]
void setup() {
  Serial.begin(9600);
  int t=analogRead(0);

  ADC->ADC_MR |= 0x80; // these lines set free running mode on adc 7 and adc 6 (pin A0 and A1 - see Due Pinout Diagram thread)
  ADC->ADC_CR=2;
  ADC->ADC_CHER=0xC0; // this is (1<<7) | (1<<6) for adc 7 and adc 6                                     
}

void loop() {
  int t=micros();
  int q0=0,q1=0;
  int a0,a1;
  for(int i=0;i<500000;i++){
    while((ADC->ADC_ISR & 0x80)==0);
    while((ADC->ADC_ISR & 0x80)==0);// wait for two conversions
    a0=ADC->ADC_CDR[7];              // read data on A0 pin
    a1=ADC->ADC_CDR[6];              // read data on A1 pin
    q0+=a0;
    q1+=a1;
  }
  t=micros()-t;
  Serial.print("500000 pairs of conversions in ");Serial.print(t);Serial.println(" micros");
  Serial.print("A0 total:");Serial.println(q0);
  Serial.print("A1 total:");Serial.println(q1);
}


Obviously there's only one ADC which is shared by the 2 channels so the effective sample rate halves. This means that you will lose some accuracy, because internally the chip is having to switch between two different input signals 1 million times per second, and that won't be as good as it tracking the same input all the time like in the 1-channel example.


This is also working.
But I have some doubts about it:

- Why on the 
Code: [Select]
while((ADC->ADC_ISR & 0x80)==0);
    while((ADC->ADC_ISR & 0x80)==0);// wait for two conversions

line, you use the same pin of the ADC to compare?

Wouldn't it be:
Code: [Select]
while((ADC->ADC_ISR & 0x80)==0);
    while((ADC->ADC_ISR & 0x40)==0);// wait for two conversions

or better:
Code: [Select]

    while((ADC->ADC_ISR & 0xC0)==0);// wait for two conversions (pin A0[7]  and A1[6])

??

On the other hand, to reduce Jitter and free more time for "processing" in the loop function, could it enabling ADC interrupts be a solution?
Do you know how to accomplish this? I have seen ADC_IER register in the datasheet, but I'm pretty new with Arduino Due (just a couple of days) and don't know how to start.  :smiley-red: :smiley-red:

Thank you again


stimmer

#8
Feb 28, 2013, 10:31 pm Last Edit: Feb 28, 2013, 10:35 pm by stimmer Reason: 1
Quote

Code: [Select]
   while((ADC->ADC_ISR & 0xC0)==0);// wait for two conversions (pin A0[7]  and A1[6])


Well spotted - what it should actually be is this:
Code: [Select]
    while((ADC->ADC_ISR & 0xC0)!=0xC0);// wait for two conversions (pin A0[7]  and A1[6])

I'll go back and change my code in case anyone else copy/pastes it.

For reducing missed samples and allowing more processing time in the main loop, the biggest win comes through using peripheral DMA. This example uses DMA and interrupts - although if you are new to the Due, don't expect to understand it all at once :)

Code: [Select]
#undef HID_ENABLED

// Arduino Due ADC->DMA->USB 1MSPS
// by stimmer
// Input: Analog in A0
// Output: Raw stream of uint16_t in range 0-4095 on Native USB Serial/ACM

// on linux, to stop the OS cooking your data:
// stty -F /dev/ttyACM0 raw -iexten -echo -echoe -echok -echoctl -echoke -onlcr

volatile int bufn,obufn;
uint16_t buf[4][256];   // 4 buffers of 256 readings

void ADC_Handler(){     // move DMA pointers to next buffer
 int f=ADC->ADC_ISR;
 if (f&(1<<27)){
  bufn=(bufn+1)&3;
  ADC->ADC_RNPR=(uint32_t)buf[bufn];
  ADC->ADC_RNCR=256;
 }
}

void setup(){
 SerialUSB.begin(0);
 while(!SerialUSB);
 pmc_enable_periph_clk(ID_ADC);
 adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
 ADC->ADC_MR |=0x80; // free running

 ADC->ADC_CHER=0x80;

 NVIC_EnableIRQ(ADC_IRQn);
 ADC->ADC_IDR=~(1<<27);
 ADC->ADC_IER=1<<27;
 ADC->ADC_RPR=(uint32_t)buf[0];   // DMA buffer
 ADC->ADC_RCR=256;
 ADC->ADC_RNPR=(uint32_t)buf[1]; // next DMA buffer
 ADC->ADC_RNCR=256;
 bufn=obufn=1;
 ADC->ADC_PTCR=1;
 ADC->ADC_CR=2;
}

void loop(){
 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;    
}


It reads ADC data at 1 million samples/sec and outputs the data to SerialUSB. (I've only tested it on Linux and most of the time it works, sometimes it is unreliable, I don't know why). Using GNU Radio I was then able to analyse the data stream and receive a long-wave radio signal, with an LC tuned circuit into A0 as an aerial.


PakARD

#9
Mar 01, 2013, 06:22 pm Last Edit: Mar 01, 2013, 06:25 pm by PakARD Reason: 1

Quote

Code: [Select]
   while((ADC->ADC_ISR & 0xC0)==0);// wait for two conversions (pin A0[7]  and A1[6])


Well spotted - what it should actually be is this:
Code: [Select]
   while((ADC->ADC_ISR & 0xC0)!=0xC0);// wait for two conversions (pin A0[7]  and A1[6])

I'll go back and change my code in case anyone else copy/pastes it.

For reducing missed samples and allowing more processing time in the main loop, the biggest win comes through using peripheral DMA. This example uses DMA and interrupts - although if you are new to the Due, don't expect to understand it all at once :)

Code: [Select]
#undef HID_ENABLED

// Arduino Due ADC->DMA->USB 1MSPS
// by stimmer
// Input: Analog in A0
// Output: Raw stream of uint16_t in range 0-4095 on Native USB Serial/ACM

// on linux, to stop the OS cooking your data:
// stty -F /dev/ttyACM0 raw -iexten -echo -echoe -echok -echoctl -echoke -onlcr

volatile int bufn,obufn;
uint16_t buf[4][256];   // 4 buffers of 256 readings

void ADC_Handler(){     // move DMA pointers to next buffer
 int f=ADC->ADC_ISR;
 if (f&(1<<27)){
  bufn=(bufn+1)&3;
  ADC->ADC_RNPR=(uint32_t)buf[bufn];
  ADC->ADC_RNCR=256;
 }
}

void setup(){
 SerialUSB.begin(0);
 while(!SerialUSB);
 pmc_enable_periph_clk(ID_ADC);
 adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
 ADC->ADC_MR |=0x80; // free running

 ADC->ADC_CHER=0x80;

 NVIC_EnableIRQ(ADC_IRQn);
 ADC->ADC_IDR=~(1<<27);
 ADC->ADC_IER=1<<27;
 ADC->ADC_RPR=(uint32_t)buf[0];   // DMA buffer
 ADC->ADC_RCR=256;
 ADC->ADC_RNPR=(uint32_t)buf[1]; // next DMA buffer
 ADC->ADC_RNCR=256;
 bufn=obufn=1;
 ADC->ADC_PTCR=1;
 ADC->ADC_CR=2;
}

void loop(){
 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;    
}


It reads ADC data at 1 million samples/sec and outputs the data to SerialUSB. (I've only tested it on Linux and most of the time it works, sometimes it is unreliable, I don't know why). Using GNU Radio I was then able to analyse the data stream and receive a long-wave radio signal, with an LC tuned circuit into A0 as an aerial.






This code is running smoother and with less jitter.
I am measuring jitter, by setting HIGH and LOW one Digital pin with an oscilloscope and the inline function:
Code: [Select]
inline void digitalWriteDirect(int pin, boolean val){
 if(val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
 else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;
}





BTW, I am controlling the sample rate by using the PRESCAL register in:
Code: [Select]

ADC->ADC_MR |= 0x2680; // these lines set free running mode on adc 7 (pin A0)  [PRESCAL at  50kHz]

Is there any other (better) way to  do it?

Actually, depending on if I am using one channel or two an the ADC, I need to setup a different PRESCAL, since there is only one ADC for all input pins:
Code: [Select]

if (channels_2)
 {
   ADC->ADC_MR |= 0x1280; // these lines set free running mode on adc 7 (pin A0) and adc 6(pin A1) PRESCAL at 50kHz
 }
 else
 {
   ADC->ADC_MR |= 0x2680; // these lines set free running mode on adc 7 (pin A0) PRESCAL at 50kHz
 }




On the other hand, what would the changes for two channels and 4 buffers? Is it possible to get a buffer for each channel?


Finally, I'm trying to measure  performance (the same way as jitter) and for  50kHz sample rate and 512 points, the time in the interrupt is 1.8us and the free time in the loop is 10.23ms (99.98% free!!).
For 1MHz, it is still around 1.8us against 256us (it is still 99.29% free), however more jitter at the beggining of the loop is appreciated.

I think it is because of the
Code: [Select]
while(obufn==bufn); instruction


It is interesting the
Code: [Select]

SerialUSB.begin(0);
while(!SerialUSB);


Does the computer (or other device) set the speed of the port?



Your spectrogram app looks great, keep up the good work!! Congratulations.


Go Up