Go Down

Topic: Recording adc data at up to 227.3 KHz (Read 3080 times) previous topic - next topic

david-

I have been working on an 8bit Arduino (Mega) Oscilloscope.

Part of my code uses interrupt reading to record data at up to 147KHz (Prescalar 8-128)
The prescalar equals 4 section uses register calls to control single ADC conversion, achieving a 227.3 KHz conversion rate.

Others may find this approach useful:
flags:
hasdata - used to signal data exists
pwtoggle - square wave on pw2
triggered - signal data is ready to collect
writeit - inform main loop that data is read to process
trigplus -true for a positive slope trigger
showdetails - true for serial monitor output

byte trigger- contains trigger threshold
buffer bufa- buf-size=1000 bytes
byte prescalar- sets the adc prescalar
byte adport - sets the adc port- I used 1
Code: [Select]

// Defines for clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
// Defines for setting register bits
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define BUF_SIZE 1000
uint8_t bufa[BUF_SIZE];
const byte check = 1<<ADSC;

The following  code shows the key subroutines for data recording:
Code: [Select]

void startad(){
hasdata=false;
 if(pwtoggle) {
 analogWrite(testpin, 127);
 //delayMicroseconds(800);
 }
while (Serial.available() > 0) junk = Serial.read();
bufcount=0;
writeit=false;
// triggering variables
trigcount=0;
 if (trigger==0) {
 triggered=true;
 } else {
 triggered=false;
 oldval=255;
   if(!trigplus) oldval=0;
 }
 if(showdetails){
 Serial.println(F("Logging.."));
 Serial.flush();
 }
// Setup continuous reading of the adc port 'adport' using an interrupt
cli();//disable interrupts
ADCSRA = 0; // clear ADCSRA register
ADCSRB = 0; // free running - only has effect if ADATE in ADCSRA=1
ADMUX |= adport;   //set up continuous sampling of analog pin adport
ADMUX |= (1 << REFS0); // set reference voltage to Vcc
ADMUX |= (1 << ADLAR); // left align the ADC value- so we can read highest 8 bits from ADCH register only
 if (prescalar > 4){
 //   8 prescalar - 147.3 Khz (after tolerable interrupt speed reduction)
 if (prescalar==8) ADCSRA |= (1 << ADPS1) | (1 << ADPS0);
 //  16 prescalar - 76.8 Khz sampling
 if (prescalar==16) ADCSRA |= (1 << ADPS2);
 //  32 prescaler - 38.4 Khz sampling
 if (prescalar==32) ADCSRA |= (1 << ADPS2) | (1 << ADPS0);
 //  64 prescalar - 19.2 Khz sampling
 if(prescalar==64) ADCSRA |= (1 << ADPS2) | (1 << ADPS1);
 // 128 prescalar - 9.6 Khz sampling
 if (prescalar==128) ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
 ADCSRA |= (1 << ADATE); // enable auto trigger
 ADCSRA |= (1 << ADIE); //Activate ADC Conversion Complete Interrupt
 sbi(ADCSRA,ADEN); // enable ADC
 ADCSRA |= (1 << ADSC); //start ADC measurements on interrupt
 starttime=micros();
 sei();//enable interrupts
 }else{
 ADCSRA |= (1 << ADPS1); // prescalar 4
 sbi(ADCSRA,ADEN); // enable ADC
 sei();
 /* Fast read via registers
 cf pages 242-260 of ATmega328P manual
 "A single conversion is started by writing logical 1 to
 the ADC Start conversion bit ADSC. This bit stays high
 as long as the conversion is in progress and will be cleared
 by hardware when the conversion is completed."
 227.3 KHz !!!!!
 */
 sbi(ADCSRA,ADSC); // First conversion- initialises ADC
   while((ADCSRA & check)== check); // wait for ADSC byte to go low
 sbi(ADCSRA,ADSC);// New conversion
   while (!triggered){
     while((ADCSRA & check)== check); // wait for adc conversion
   newval=ADCH;
   sbi(ADCSRA,ADSC); // New conversion
   trip = newval-oldval;
     if(!trigplus) trip = -trip;
     if (trip > trigger) triggered=true; else oldval=newval;
   }
 starttime=micros();
   for(i=0;i<BUF_SIZE;i++){
     while((ADCSRA & check)== check); // wait for conversion
   bufa[i]=ADCH;
   sbi(ADCSRA,ADSC); // New conversion
   }
 endtime=micros();
 cbi(ADCSRA,ADEN); // disable ADC
 writeit=true;
 }
}

// Interrupt routine *******************************************
ISR(ADC_vect) {
 if (triggered){
   bufa[bufcount]=ADCH;;
   bufcount++; // increment buffer counter
     if (bufcount==BUF_SIZE) {
     cbi(ADCSRA,ADEN); // disable ADC
     endtime=micros();
     writeit=true; // flag that a write is needed  
     }
 } else {
 // look for a trigger
 newval=ADCH;
 trigcount++;
 trip = newval-oldval;
   if(!trigplus) trip = -trip;
   if (trip > trigger) triggered=true; else oldval=newval;
 }
}
// End Interupt section *******************************************

The project is published here http://www.instructables.com/id/Arduino-High-speed-Oscilloscope-with-PC-interface/?ALLSTEPS
To date, I have not released the version with the 227.3 KHz section.
I have tested using free running conversion with a prescalar of 4.
The converted values ranged between 7 and 242. This issue disappeared at a prescalar of 8.
My single conversion method produces 0-255  at a prescalar of 4.
I tested at a prescalar of 2. PWM input worked ok, but real Audio signals were not read correctly.

david-

#1
Sep 06, 2014, 10:28 am Last Edit: Sep 06, 2014, 10:31 am by david- Reason: 1
My test code:

Code: [Select]

// adcread
// Defines for clearing register bits

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
// Defines for setting register bits
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

boolean freerun=true; // free running flag
unsigned int startt, endt;
const unsigned int readings=1000;
const byte portno=1;
uint8_t buff[readings];
unsigned int i;
const byte testpin=3; // pwm outpin
byte prescalar=4;

void setup() {
Serial.begin(115200);
// Setup fast pwm
//TCCR3B = TCCR3B & B11111000 | B00000011; // mega default
TCCR3B = TCCR3B & B11111000 | B00000010; // mega 3921.16
pinMode(testpin,OUTPUT);
analogWrite(testpin, 127);
delayMicroseconds(800);

// Disable digital input registers
sbi(DIDR0,ADC0D);
sbi(DIDR0,ADC1D);
sbi(DIDR0,ADC2D);
sbi(DIDR0,ADC3D);
sbi(DIDR0,ADC4D);
sbi(DIDR0,ADC5D);

Serial.println(F("Adc Test"));
Serial.flush();

standardread();

freerun=false;
setread();
freerun=true;
setread();
}

void loop() {
}

void setread(){
cli();//disable interrupts
cbi(ADCSRA,ADEN);
/*
cf atmega manual p242-260
*/
ADCSRA = 0; // clear ADCSRA register
ADCSRB = 0; // free running
// only has effect if ADATE in ADCSRA=1
ADMUX |= portno; // set port

/*  These bits determine the division factor between the system clock
   frequency and the input clock to the ADC.
    ADPS2  ADPS1  ADPS0  Division Factor
    0    0   0      2
    0    0      1      2
    0    1   0      4
    0    1      1      8
    1      0      0      16
    1      0      1      32
    1      1      0      64
    1      1      1 128
*/
 if (prescalar==2) ADCSRA |= (1 << ADPS0); // 2 prescalar
 if (prescalar==4) ADCSRA |= (1 << ADPS1); // 4 prescalar
 if (prescalar==8) ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // 8 prescalar 143Khz
 if (prescalar==16) ADCSRA |= (1 << ADPS2); // 16 prescalar - 72 Khz sampling
 if (prescalar==32) ADCSRA |= (1 << ADPS2) | (1 << ADPS0); // 32 prescaler - 16mHz/32=500kHz - produces 37 Khz sampling
 if(prescalar==64) ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // 64 prescalar produces 19.2 Khz sampling
 if (prescalar==128) ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescalar - 9.4 Khz sampling

 if (freerun) sbi(ADCSRA,ADATE); // Use trigger source in ADCSRB
ADMUX |= (1 << REFS0); //set reference voltage to Vcc
sbi(ADMUX,ADLAR); // left align port values
sbi(ADCSRA,ADEN); // enable ADC
sei();
fastread();
}

/*
Free running mode works up to a point-
Range 8 to 248. To restore 0-255 functionality
Requires setting prescalar to 8
Single coversion works with a check on ADSC bit with prescalar of 4
*/

void fastread(){
/*
Free running mode works up to a point-
Range 8 to 248 without prescalar of 8
Single conversion works perfectly with a check on  ADSC bit
*/

byte check=1<<ADSC;
byte iflag=1<<ADIF;
int c=0;
Serial.print(F("\nFast read..."));
if(freerun)Serial.println(F("Free running")); else Serial.println(F("Single shot"));
Serial.flush();
sbi(ADCSRA,ADSC);// FIRST CONVERSION to initialise
 if(freerun) {
 startt=micros();
   for(i=0;i<readings;i++){
     while((ADCSRA & iflag)==0);// free running check
   sbi(ADCSRA,ADIF); // clear eoc flag ADIF
   buff[i]=ADCH;
   }
 endt=micros();

 } else {
 while((ADCSRA & check)== check); // single convert test
 sbi(ADCSRA,ADSC); // Start new Single conversion
 startt=micros();
   for(i=0;i<readings;i++){
     while((ADCSRA & check)== check); // single convert test
   buff[i]=ADCH;
   sbi(ADCSRA,ADSC); // Start new Single conversion
   }
 endt=micros();
 }
printit(endt-startt);
}

void standardread(){
Serial.println(F("Standard read..."));
Serial.flush();
startt=micros();
 for(i=0;i<readings;i++){
 buff[i]=analogRead(portno);
 }
endt=micros();
Serial.println();
printit(endt-startt);
}

void printit(unsigned int elapsed) {
Serial.print(readings);
Serial.print(F(" conversions in "));
Serial.print(elapsed);
Serial.println(F(" uS"));
Serial.print(F("Frequency "));
float frequency=1000*float(readings)/float(elapsed);
Serial.print(frequency,6);
Serial.println(F(" kHz"));
 for (i=0;i<readings;i++){
 Serial.print(buff[i]);
 Serial.print(F(" "));  
 }
Serial.println();  
Serial.flush();
}


MarkT

At that sort of speed I would imagine a good fast buffer driver/opamp into the
analog pin would help give maximum accuracy - at that sort of rate I think there's
only a few 100ns to charge up the sample/hold capacitor on chip.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

aldolo

200khz isn't that fast, but is too fast for arduino uno and bro. I'm using it but discarding the 4 less significative digit. yes, a 6 bit adc :-) more then enough for a 48 pixel display.

MarkT

Answering 3 years late isn't that fast either!
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

Go Up