Recording adc data at up to 227.3 KHz

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

// 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:

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.

My test code:

// 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();
}

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.

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.

Answering 3 years late isn’t that fast either!