1 MSPS on a T4? Is this possible...

I converted my earlier sample to be pseudo transient logger with the following characteristics:

* Storage is started by a low-going pulse on Pin 1.
* ADC conversions are done in continuous mode with an interrupt at the end of conversion.
* The interrupt handler records the number CPU cycles since the last interrupt and the ADC value.
* The data is stored in the T4.1 PSRAM chip. I record 4 bytes per sample, for a total of about 4MB per file.
* After the data is collected, it is written to the SD card.
* During collection, the ADC is assigned a high interrupt priority (32) so that it is not disturbed by things like
USB or serial port interrupts---but systick interrupts still happen.
* The recorded 12-bit data has about +/- 20 LSBs of noise. I suspect that most of it is in the 3.3V reference,
since my test input is a 3.3K resistor from a digital pin to the ADC pin with a 10uF cap from the ADC pin to
ground.

I found that I got pretty impressive sampling speeds with the T4.1 clock at 600MHz:


12-bit conversion, High Speed ADC clock, Medium Speed Sampling: 987,354 samples/second
12-bit conversion, Very High Speed ADC clock, High Speed Sampling: 1,172,903 samples/second
12-bit conversion, Very High Speed ADC clock, Very High Speed Sampling: 1,339,177 samples/second
8-bit conversion, Very High Speed ADC clock, Very High Speed Sampling: 1,875,806 samples/second

As you can see, all except the slowest meet the MegaSample criterion.

Another thing I finally realized is although there is about 30 to 40 cycles timing jitter in the collection of the data
by the interrupt handler, THIS DOES NOT MEAN THERE IS JITTER IN THE TIMING OF THE ADC COLLECTION!
The ADC sampling and conversion timing is under the control of the ADC clock system. So long as the interrupt
handler reads the data before the end of the next conversion, there are no ADC timing errors. Since the maximum
disturbance to the interrupt timing is about 40 CPU cycles, I don't think ADC timing errors are likely.

The ADC IRQ handler executes in about 80nSec, so there are plenty of CPU cycles for other things, even while
collection is in progress.

Some things I plan to work on over the next few days:

1. Put the data in a circular buffer. After arming, the data goes into the queue. At trigger time, the
queue position is marked. After a full circle in the queue, the logger starts at some position before
the trigger an saves a full queue's worth of data. This allows the file to have a 'pre-trigger' front
porch.
2. Add a second ADC channel on ADC1. Save that data instead of all the timing intervals. The IRQ
handler will just record the maximum and minimum intervals between interrupts.
3. See whether continuous megasample logging is possible using the PSRAM for two large ping-pong buffers.

Here's the code:
Code:
/*******************************************************
   One MegaSample T4.0 ADC  12-bit
   MJB   4/2/20
         updated 8/9/2020
   NOTE:
          this test runs the ADC as fast as possible and saves the
          data in an IRQ handler

          The program uses the ADC library from Pedvide, which
          is one of the libraries installed by  Teensyduino.
          If you plan to do high-speed ADC acquisition, learning
          how this library works should be high on your TO-DO
          list.


***************************************************************/
#include "SdFat.h"
#include "sdios.h"
#include <TimeLib.h>
#include <ADC.h>

/*******************************************************/
// when USEMTP is defined, you can upload file with MTP,
// but you have to have the MTP library and modified USB files
#define USEMTP

#ifdef USEMTP
#include "MTP.h"
#include <Storage.h>
#include <usb1_mtp.h>

MTPStorage_SD storage;
MTPD       mtpd(&storage);
#endif
/*******************************************************/

// instantiate a new ADC object
ADC *adc = new ADC(); // adc object;

// This version uses SdFs, which can be either FAT32 or EXFat
SdFs sdf;
SdioCard sdc;
FsFile logFile;
#define SD_CONFIG SdioConfig(FIFO_SDIO)
const char compileTime [] = "T4.1 MegaSample 12-bit transient logger Compiled on " __DATE__ " " __TIME__;
const int tranpin   = 31;
const int admarkpin = 32;   // Changed to end pins on T4.1
const int wrmarkpin = 33;
const int ledpin    = 13;
const int trigpin   = 0;
// ADMARKHI and ADMARKLO are used to observe ADC timing on oscilloscope
#define  ADMARKHI digitalWriteFast(admarkpin, HIGH);
#define  ADMARKLO digitalWriteFast(admarkpin, LOW);

// WRMARKHI and WRMARKLO are used to observe SDC Write timing on oscilloscope
#define  WRMARKHI digitalWriteFast(wrmarkpin, HIGH);
#define  WRMARKLO digitalWriteFast(wrmarkpin, LOW);


// tranpin is the signal we record as it brings A) high through RC network
#define  TRANHI digitalWriteFast(tranpin, HIGH);
#define  TRANLO digitalWriteFast(tranpin, LOW);

#define  LEDON digitalWriteFast(ledpin, HIGH);
#define  LEDOFF digitalWriteFast(ledpin, LOW);
struct datrec {
  uint16_t deltacount;
  uint16_t adval;
};

volatile struct datrec *baseptr = (datrec *) 0x70000000;
volatile struct datrec *bufptr = baseptr;

volatile bool logging = false;
volatile uint32_t bufcount;
uint16_t adspeed = 1;

#define ADBLOCKSIZE 1048576  // 4MBytes of samples at ~1MHz

/*****************************************************
  This is the ADC Completion interrupt handler
  It executes in about 80nSec when saving data
  and about 40nSec when not saving
******************************************************/
volatile uint32_t dwtlast;

void ADCChore(void) {
  uint16_t value;
  uint32_t dwt;
  dwt = ARM_DWT_CYCCNT;
  ADMARKHI

  value = adc->adc0->readSingle();
  if (logging) { // Save result to buffer
    bufptr->deltacount = dwt - dwtlast;
    bufptr->adval = value;
    bufptr++;
    bufcount++;
    // stop logging when enough samples collected
    if (bufcount >= ADBLOCKSIZE) logging = false;

  }  // end of if(logging)

  // ADMARHI to ADMARKLO takes about 180nS at 600MHz
  // So some oversampling may be possible
  dwtlast = dwt;
  ADMARKLO
}


void SetupPins(void) {
  pinMode(trigpin, INPUT_PULLUP);  // low going signals triggers recording
  pinMode(tranpin, OUTPUT);  // Generates hi-going transient to record
  TRANLO
  pinMode(admarkpin, OUTPUT); // for measuring interrupt time with O-Scope
  pinMode(wrmarkpin, OUTPUT); // Signals SD Write Interval for O-Scope
  pinMode(ledpin, OUTPUT);    // LED is on during transient recording
  pinMode(A0, INPUT_DISABLE); // disable digital keeper resistors
}

// Set ADC Speed:  1: slower   2: medium   3:fastest possible 12-bit   4 fastest 8-bit
// Speeds are relative---after all this is a high-speed transient recorder!
void SetADCSpeed(uint16_t spd) {

  adc->adc0->disableInterrupts();
  adc->adc0->stopContinuous();

  switch (spd) {
    case 1:
      Serial.println("ADC Speed set to 12-bit low range.");
       adc->adc0->setResolution(12); // set bits of resolution
      adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
      adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED);
      adspeed = 1;
      break;
    case 2:
      Serial.println("ADC Speed set to 12-bit medium range.");
      adc->adc0->setResolution(12); // set bits of resolution
      adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
      adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);
      adspeed = 2;
      break;
    case 3:
      Serial.println("ADC Speed set to 12-bit high range.");
      adc->adc0->setResolution(12); // set bits of resolution
      adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
      adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
      adspeed = 3;
      break;
    case 4:
      Serial.println("ADC Speed set to 8-bit high range.");
      adc->adc0->setResolution(8); // set bits of resolution
      adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
      adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
      adspeed = 4;
      break;
  }
  adc->adc0->enableInterrupts(ADCChore);
  adc->adc0->startContinuous(A0);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  delay(500);  // wait for Serial to open
  Serial.println();
  Serial.println(compileTime);
  Serial.printf("CPU Frequency: %lu\n", F_CPU_ACTUAL);
  Serial.println("Requires installed PSRAM chip.");
  SetupPins();
  // enable the CPU cycle counter for timing interrupt routine
  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  logging = false;
  adc->adc0->setAveraging(1 ); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution

  if (!StartSDCard()) {
    // do fast blink forever
    do { // hang with blinking LED
      LEDON
      delay(100);
      LEDOFF
      delay(100);
    } while (1);

  }// end of  if (!StartSDCard())
  SetADCSpeed(1);  // start at slowest speed
  setSyncProvider(getTeensy3Time); // helps put time into file directory data
#ifdef USEMTP
  StartMTP();
#endif
}



void loop() {
  // put your main code here, to run repeatedly:
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == '1')  SetADCSpeed(1);
    if (ch == '2')  SetADCSpeed(2);
    if (ch == '3')  SetADCSpeed(3);
    if (ch == '4')  SetADCSpeed(4);
    
    if (ch == 'l')  LogTran();
    if (ch == 's')  ShowADC();
    if (ch == 't')  ShowTiming();
    if (ch == 'd')  sdf.ls(LS_SIZE | LS_DATE | LS_R);
  }
#ifdef USEMTP
  mtpd.loop();
#endif
}



/******************************************************
  Read one milllion samples and store in PSRAM
  During collection, ADC Interrupt priority is raised
  to a high level so that it is higher than everything
  except the system tick
*****************************************************/

void LogTran(void) {
  uint32_t startmillis, dmillis, drate;
  Serial.print("Waiting for low-going trigger. . . .");
  Serial.send_now();// wait for USB serial to finish up.
  delay(500);
  // if trigger pin still low, wait until it goes high
  while(!digitalReadFast(trigpin)) delay(5);
  // trigger pin is high now
  while(digitalReadFast(trigpin))delay(5);  
  // Trigger pin has gone low, so trigger tranpin and  start logging
  LEDON
  bufcount = 0;
  bufptr = (datrec *) baseptr;
  NVIC_SET_PRIORITY(IRQ_ADC1, 32); // Very high priority
  
 // NVIC_DISABLE_IRQ(IRQ_USB1);  // didn't really help only about 6 clocks less 
  startmillis = millis();
  logging = true;
  delay(2);  // put a 2msec leader before transient
  TRANHI
  delay(200);
  TRANLO
  // Now wait until logging is done
  while (logging); // wait until sampling finished
  //NVIC_ENABLE_IRQ(IRQ_USB1);
  dmillis = millis()-startmillis;
  NVIC_SET_PRIORITY(IRQ_ADC1, 128); // Normal priority
  TRANLO
  LEDOFF
  drate = (1000 * bufcount)/dmillis;
  Serial.printf("\nCollected %lu samples in %lu milliseconds or %u samples/second\n", bufcount, dmillis, drate);
  Serial.printf("Last Sample stored at %p\n", bufptr - 1);

  // save log file by writing as one large chunk
  if ( OpenLogFile()) {
    logFile.write((void *)baseptr, ADBLOCKSIZE * sizeof(datrec));
    logFile.close();
    Serial.printf("Wrote %lu bytes to log file.\n",ADBLOCKSIZE * sizeof(datrec));
  } else Serial.println("Could not open log file!");
}



/******************************************************
   Transient logger SD Card File Name Creator
   
   output has appended  datetime  
   in _MMDDHHmm format
   for example:  Tran_08152353.TRn where n is conversion speed
 *****************************************************/
const char* FNString(void) {
  static char fname[64];
  time_t nn;
  nn = now();
  int mo = month(nn);
  int dd = day(nn);
  int hh = hour(nn);
  int mn = minute(nn);
  sprintf(fname, "Tran_%02d%02d%02d%02d.TR%d",mo,dd,hh,mn, adspeed);
  return fname;
}


bool OpenLogFile(void) {
  if (!logFile.open(FNString(),  O_RDWR | O_CREAT | O_TRUNC)) {
    return false;
  }
  return true;
}

void ShowTiming(void) {
  uint32_t tidx,maxidx, minidx, spikecnt;
  uint16_t val, mind, maxd;
  float dsum, meand;
  uint16_t psize;
  volatile struct datrec *bptr = baseptr;
  
  maxd = 0; maxidx = 0;
  mind = 65535; minidx = 0;
  dsum = 0.0;
  for (tidx = 0; tidx < ADBLOCKSIZE; tidx++) {
    val = bptr->deltacount;
    dsum += val;
    if (val > maxd) {
      maxd = val;
      maxidx = tidx;
    }
    if (val < mind) {
      mind = val;
      minidx = tidx;
    }
    bptr++;
  }
  Serial.printf("Timing Sum: %8.1f\n", dsum);
  meand = dsum/ADBLOCKSIZE;
  Serial.println("\nSample Timing intervals in CPU Clock Cycles");
  Serial.printf("Minimum: %u at %u    Maximum: %u at %u  ", mind, minidx, maxd, maxidx);
  Serial.printf(" Mean: %8.3f\n", meand);

  psize = (maxd - mind) / 8;
  // Determine hom many times there was a significant increase in write interval
  spikecnt = 0;
  bptr = baseptr;
  for (tidx = 0; tidx < ADBLOCKSIZE - 1; tidx++) {
    val = bptr->deltacount;
    if (val > meand + psize) {
      spikecnt++;
    }
    bptr++;
  }
  Serial.printf("Total disruptions: %lu\n", spikecnt);

}

/******************************************************
  Display data from adcbuffer0 in lines of 20 values
  Only the first NUMTOSHOW values are displayed
*****************************************************/
#define NUMTOSHOW   100  // change to alter numbers output
void ShowADC(void) {
  uint32_t sendidx;
  uint16_t val;
  volatile struct datrec *bptr = baseptr;
  Serial.println("Delta Counts");
  // Sending the full 128K samples would  a long time!
  for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) {

    if ((sendidx % 16) == 0) {
      Serial.printf("\n%p: ", bptr);
    }
    val = bptr->deltacount;
    bptr++;
    Serial.printf("% 5u", val);
  }
  bptr = baseptr;
  Serial.println("\nADC Values");
  for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) {
    if ((sendidx % 16) == 0) {
      Serial.printf("\n%p: ", bptr);
    }
    val = bptr->adval;
    bptr++;
    Serial.printf("% 5u", val);
  }
  Serial.println();
}


bool StartSDCard() {
  if (!sdf.cardBegin(SD_CONFIG)) {
    Serial.println("cardBegin failed");
  }
  if (!sdf.volumeBegin()) {
    Serial.println("volumeBegin failed");
  }
  if (!sdf.begin(SdioConfig(FIFO_SDIO))) {
    Serial.println("\nSD File initialization failed.\n");
    return false;
  } else  Serial.println("initialization done.");

  if (sdf.fatType() == FAT_TYPE_EXFAT) {
    Serial.println("Type is exFAT");
  } else {
    Serial.printf("Type is FAT%d\n", int16_t(sdf.fatType()));
  }
  // set date time callback function
  SdFile::dateTimeCallback(dateTime);
  return true;
}
/*****************************************************************************
  Read the Teensy RTC and return a time_t (Unix Seconds) value

******************************************************************************/
time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

//------------------------------------------------------------------------------
/*
  User provided date time callback function.
  See SdFile::dateTimeCallback() for usage.
*/
void dateTime(uint16_t* date, uint16_t* time) {
  // use the year(), month() day() etc. functions from timelib

  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(year(), month(), day());

  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(hour(), minute(), second());
}

#ifdef USEMTP
void StartMTP(void) {
  Serial.println("Starting MTP Responder");
  usb_mtp_configure();
  if (!Storage_init(&sdf)) {
    Serial.println("Could not initialize MTP Storage!");
  }
}
#endif
 
> The recorded 12-bit data has about +/- 20 LSBs of noise.

I assume you mean 20 counts. Would be helpful to lower this. Lower source impedance and less capacitance might be better. Averaging simultaneous conversions from ADC0 and ADC1 will help a little.

If you want really good (but slow) ADC results, take lots of samples and take the median.
 
> The recorded 12-bit data has about +/- 20 LSBs of noise.

I assume you mean 20 counts. Would be helpful to lower this. Lower source impedance and less capacitance might be better. Averaging simultaneous conversions from ADC0 and ADC1 will help a little.

If you want really good (but slow) ADC results, take lots of samples and take the median.

I was definitely looking at ADC counts. But isn't 1 count equal to 1 LSB?

If the noise is coming from the fact that the ADC reference is the 3.3V rail, reducing the input impedance from the port pin driving the ADC input should definitely reduce the noise. With the large RC constant I've imposed on the input, noise on the 3.3V rail at the input is delayed from the 3.3V ADC reference. OTOH the large phase delay better emulates the output from an external source whose output is not ratiometric to the T4.1 3.3V rail. Since the OP was thinking of 8-bit conversions at over 1MHz, I think 'fast and close' is more important than 'slow and clean'. I worked in the 'slow and clean' environment for many years where oversampling and filtering got us to +/- 1 LSB at 16 bits for a 4.096V input range at 40 Hz. The 'fast and close' regime is interesting because its something new to keep me busy when travel and exploration are more difficult.

BTW, a 1.3MSample file looks much better after you import it into Matlab and process it with a 5KHz 8-pole low pass filter ;-) However, there's not much point in collecting at 1.3MHz if your only need a 5KHz bandwidth in the end.
 
@mborgerson, I can't get your 1st post to work on a T4.1 (with a PSRAM also) at anything other than 8 bits. Am I missing something simple ?

I have tried changing these lines:
Code:
  adc->adc0->setResolution(8); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed

and also changed the buffer to be an 'int' (16 bits), not uint8_t

Code:
/*******************************************************
   1MegaSample T4.0 ADC
   MJB   4/2/20
   jP314 Aug 15, 2020

   NOTE:  This is a proof-of-concept program written for
          a novice programmer.  In the interest of simplicity
          it uses global variables and simple functions without
          parameters.

          The sampling runs fast enough at 600MHz that some 
          oversampling may be possible to reduce noise

          There may be occasional timing glitches if other
          processes such as the tick interrupt, run at higher
          priority than the ADC interval timer.

          The program uses the ADC library from Pedvide, which
          is one of the libraries installed by  Teensyduino.
          If you plan to do high-speed ADC acquisition, learning
          how this library works should be high on your TO-DO
          list.
***************************************************************/
#include <ADC.h>

IntervalTimer ADCTimer;

// instantiate a new ADC object
ADC *adc = new ADC(); // adc object;

const int admarkpin  = 0;

#define ADMAX 300000
EXTMEM  int adcbuffer[ADMAX];
volatile uint32_t adcidx;

void setup() {
  while (!Serial);
  Serial.begin(9600);
  Serial.println("\n1 MSample ADC test. 'g' to generate, 's' to show, ' ' to generate & show.");
  pinMode(admarkpin, OUTPUT);
  //pinMode(A9, INPUT);  // 4th down on RHS
  adc->adc0->setAveraging(1); // set number of averages
  adc->adc0->setResolution(8); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed
  ReadADC();
}

void loop() {
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'g')  ReadADC();
    if (ch == 's')  ShowADC();
    if (ch == 'p')  SendPlotADC();
    if (ch == ' ')  {ReadADC(); ShowADC();}
  }
}


/*****************************************************
 * This is the intervaltimer interrupt handler
 ******************************************************/
void ADCChore(void) {
 // uint8_t adval;
  digitalWriteFast(admarkpin, HIGH);
  if(adcidx < ADMAX){  // sample until end of buffer
    adc->adc0->startSingleRead(A9); // start a single conversion
    adcbuffer[adcidx] = adc->adc0->readSingle();
    adcidx++;   
  }
  digitalWriteFast(admarkpin, LOW);
  // ADMARHI to ADMARKLO takes about 180ns at 600MHz
  // So some oversampling may be possible
}

/******************************************************
   Read MAXSAMPLES from ADC at 1 microsecond intervals
   Store the results in adcbuffer;
 *****************************************************/
void ReadADC(void) {
  Serial.println("Reading ADC Samples");
  adcidx = 0;
  ADCTimer.begin(ADCChore, 1.0);  // start timer at 1 microsecond intervals
  while (adcidx < ADMAX);  // wait for ADC to complete ADMAX samples  
  ADCTimer.end();  // stop the timer
  Serial.printf("ADC generated %i samples\n", adcidx);
}

void SendPlotADC(void) {
  uint32_t sendidx;

  Serial.println("Switch to plotter now.");
  delay(5000);  //Wait for user to switch
  // Sending the full 300,000 samples would take 5 minutes
  // at 1mSec/ sample. So I send only the first fifth
  for (sendidx = 0; sendidx < 1500; sendidx++) {
    Serial.println(adcbuffer[sendidx]);
    //delay(1);
  }
}

void ShowADC(void) {
  uint32_t sendidx;
  long int index = 0;
  const int linesize = 20;

  Serial.print("ADC Data");
  // Sending the full 300,000 samples would  a long time!
  for (sendidx = 0; sendidx < 10000; sendidx++) {
    if(!(sendidx % linesize)) {
      Serial.printf("\n%5u: ", index);
      index += linesize;
    }
    
    Serial.printf(", %5i", adcbuffer[sendidx]);
  }
  Serial.printf("\nDone %i samples.\n", sendidx);
}

In the meantime, the sampling and input BW seem quite good. Here's 20.010 MHz sinusoid sampled at 1 us; it aliases to 10 kHz as expected. I'm trying to figure out if the (small amount of) jitter observed can account for the 50 counts p-p of aliasing noise; I don't think it does; seems very regular and may be an amplifier slew rate limitation or something.
10.010 MHz @ 1 us.jpg

and 1.01 MHz also at 1 us:
1.010 MHz @ 1 us.jpg
 
Last edited:
What do you mean by "
@mborgerson, I can't get your 1st post to work on a T4.1 (with a PSRAM also) at anything other than 8 bits. Am I missing something simple ?

Does it compile OK?
Do you collect any data?
Do you get unexpected data back?

I recommend that you avoid data types like 'int'. An int can be 16 bits on some systems and 32 bits on others. "uint16_t" is 16 bits everywhere.


"(sendidx % linesize)" also bothers me--although it may work just fine. linesize is a signed int and sendidx is a uint32_t and your using them in a test. Then there's 'index' which is a signed long integer. My mind is not as good as GNU C and I get a bit confused when three different data types are dancing together.

I'm also not sure of the point behind sampling a 1.01MHz signal at 1.00Mhz. If there's even a little thermal drift in either the Teensy or your signal source, your results are going to change a lot. There might be a use for that kind of thing in a software-defined-radio, but it's probably not the best test for a demo program.

If you are going to sample very high frequency signals, you need to be sure that the signal will be stable at the end of the sampling interval. For a signal at 1.01 or 20Mhz, you need a very low impedance driver to the ADC input. As you suggested, it might be a slew rate limitation on the amplifier driving the ADC input.
 
@mborgerson:

Lower noise is usually desirable. +/- 20 counts peak-to-peak (6.68 bits noise free) may or may not satisfy someone looking for "8 bits".

> THIS DOES NOT MEAN THERE IS JITTER IN THE TIMING OF THE ADC COLLECTION!

If I understand your code correctly, it is running the ADC as fast as possible but only recording the value every 1usec. These asynchronous rates WILL cause substantial jitter.

For many uses, the sampling rate can be accounted for - so sampling as fast as possible at some fixed/known value close to 1 Msps might be fine.
 
@jonr:The most recently posted code does exactly what you suggest in your last sentence. The interrupt used is the ADC end-of-conversion interrupt, not a timer interrupt. The interrupt that collects the data occurs at the rate determined by the ADC cycle time, not at some fixed interval, like 1uSec. You can see that in the posted collection rates---which change based on the setup of the ADC and how many clock cycles it takes for that hardware to do the sampling and conversion.

The ADC on the T4.x is always going to be noisier than an ADC that uses a good reference voltage instead of the power supply rail. The 20 counts of noise that I mentioned was for a sample collected at 12-bit resolution. I'll try collecting samples from a stable voltage source at both 8 and 12-bit resolution and see what kind of noise I get.
 
Thanks, I should have looked at your code more closely.

I get the same ~40 counts peak-to-peak on my T4 with voltage divider resistors. But averaging two samples drops this to 20 counts. Seems well worth it to make use of the second ADC running in parallel.
 
I ran some tests with a stable voltage source (NIMH battery with 1K current limiting resistor and 10UF from ADC port to ground) and got the following results:

ADC rateBitsMean VStd. Dev VStd. Dev Counts
0.987M121.37290.00303.67
1.339M121.37310.00647.84
1.875M81.36490.00590.46

(NOTE: That table entry would have gone much faster if I could just figure out how to use a monospaced font and the forum software would stop compressing down multiple space characters!

I suspect that the 10mV drop in the mean for the fastest sampling scheme may be caused by the 1K impedance of my source. After all, the ADC is sucking a bit of charge from the 10uF capacitor about twice as often with the faster sampling and conversion when compared to the slowest conversion. In any case, it looks like about 3 to 6mV of noise is what you get with the T4.X ADC. For 8-bit conversions, that's in the sub-1LSB range since 1 LSB is about 13mV.
 
@borgerson -- yes, it compiles; I don't think the variable sizes are the problem. What seems to be happening is that at > 10 bits resolution, I can't run 1 ADC at 1 MHz - I need 500 kHz for 12 bits, and about 200 kHz for 16 bits. I'm trying now to understand why that is.

Undersampling a signal (e.g. 1.01 MHz at 1.00 MHz) will give a sinusoid output at 0.0-1 MHz, but noise (distortion) on this will indicate weaknesses (jitter, aperture time) on the sampling and conversion. I'm driving that signal with a 50 Ω terminated line.
 
At 8 bits, quantization errors possibly make STDEV analysis inaccurate -- you may have a DC input that falls well within an ADC conversion step, thus giving 0 STDEV (even with some noise on the signal (LSB = 12.9 mV). On the other hand, if you are just at the edge, you can get a STDEV of (12.9 / 2) mV (== +/- ½ count). It's best to measure over multiple inputs and average the STDEVs -- see this: https://forum.pjrc.com/threads/62257-Teensy-16bit-ADC?p=249028&viewfull=1#post249028. My conclusion is that there are > 5 counts of VREF noise on a Teensy_LC at 16 bits.
 
Agreed, std dev becomes meaningless at low values. For example, some ADCs have std dev = 0. So math wise, infinite effective bits.

When looking at teensy ADC performance, I'd always include peak-to-peak and offset measurements. That's where they tend to not compare well to good external ADCs (where peak-to-peak may be 1 count).
 
Agreed, std dev becomes meaningless at low values. For example, some ADCs have std dev = 0. So math wise, infinite effective bits.

I agree that quantization effects can render Std. Deviation less useful when quantization effects are likely. One good way to look at the data is to look at a histogram. I did that for slow 12-bit and fast 8-bit samples.

Histo12.jpg

Histo8.jpg


These histograms give you a good idea of the distribution of the signal values--much better than either mean/std dev or mean/P-P. In the case of the 12-bit data, the Pk-Pk value greatly overestimates the probability that a signal will be more than 3 bits away from the mean. For the 8-bit data, a std. deviation of 0.5 bits isn't a good estimate either. The histogram shows that a better estimate would be -0.2 to +1.0 bits. As noted, the std. deviation isn't very good with highly quantized data like this. A lot of the statistical measures fail when the curve isn't close to a normal distribution.
 
Interesting, I find that taking two full AD samples and averaging them is much more effective at reducing noise than setting up the ADC to average two samples internally.

The T4 noise definitely isn't Gaussian. When oversampling, median works much better than mean.
 
I looked at interrupt-driven ADC conversions some more. Using this ISR:
Code:
FASTRUN void ADC_loop(void) {
  digitalWriteFast(admarkpin, HIGH);
  adc->adc0->startSingleRead(A9); // start a single conversion
  digitalWriteFast(admarkpin, LOW);
  adcbuffer[adcidx] = adc->adc0->readSingle();
  if (adcidx < ADMAX-1) adcidx++;   
}

at 1 us sampling I occasionally (estimate 0.1 % of samples) get a timing error of 40 ns. Thus while most samples are at 1.000 us spacing, occasionally a sample will be delayed by 40 ns. If you are measuring a full scale 500 kHz sinusoid (Nyquist limit; not really practical) at 1 MS/s, you could have an error of about 1/8 full scale. To maintain 8-bit accuracy, the maximum full scale input frequency can only be 1/32 this, or about 15 kHz. This can probably be improved by shuffling interrupt priorities, but I suspect the DMA approach (as jonr suggested earlier) is the only option.
 
I suspect that your occasional error of 40nSec occurs when your interrupt routine gets delayed by the SysTick interrupt. That interrupt occurs at priority 0, so you can't avoid it with elevation of the priority of your timer interrupt. I think the only way to avoid this problem is to do all your collection inside noInterrupt()/ interrupt() calls. That raises a lot of problems with millis() and micros(), so I've avoided that.

Even when I've raised the ADC interrupt to priority 32, I get occasional delays in interrupt processing of 40 clock cycles. If the ADC is in continuous mode, that doesn't affect the ADC collection timing. However if I try to write to SD card while collecting, the interrupt delays go up to a microsecond or two. Something in the SDFat write routines is blocking interrupts for more than a microsecond, and that messes up the ADC collection. The ADC itself keeps on truckin' but the interrupt handling gets postponed which means that you miss a sample. This doesn't happen if you don't write to SD while collecting.
 
The possibility of collecting the ADC data with DMA is intriguing. I will have to look at that. The DMA approach also raises some questions:

1. How can you save other than ADC data? (such as the timing data I save in my demo programs)
2. IIRC, the standard DMA library limits you to 32768 samples. It takes some special mods to collect the 1M samples I collect in my demo program. (there's a thread somewhere about DMA > 32K and I'll look it up).
3. Can the existing demo code handle more than one ADC or one channel?
4. Some of the existing DMA demo code chains together multiple blocks of DMA transfers. Does that DMA chaining happen in less than the ~1uSec between ADC conversions.

On the plus side, DMA activated by ADC conversions should be immune to timing glitches caused by things like the SysTick interrupt.
 
The idea of capturing shock wave data intrigues me. It takes me back to my first years out of college in the early 1970s. I was in the Navy and on a ship that spent about 3 months each year in '72 and '73 sailing in circles near Mururoa Atoll. Every few weeks, there was a large shock wave generated over the atoll. We were 20 miles away, so the shock wave had degraded to a loud "BOOM" by the time it reached us. (You can decode this diversion by Googling "Mururoa").

Been away for work for a bit, so took a break from the studies, hence the late response. Mururoa Atoll, enough said ;) . I am super jealous, can only imagine what that was like. 20 Miles away is not a long way off for this sort of testing!
 
Trapped indoors by wildfire smoke in the Willamette valley, I decided to revisit this issue. I came to several conclusions:

1. You can collect a single channel of ADC data at 1.000 MHz without sampling jitter if you use the ADCTimer functions. This uses a spare quadtimer on the T4.x to trigger the ADC from the quadtimer hardware. At the end of the conversion, an interrupt handler is called to read and store the data. Since the ADC is triggered by hardware, variations in the interrupt response time do not affect the sampling interval unless interrupts are blocked for more than 1 microsecond--in which case a sample is lost.
2. With proper buffering, you can write data to the SD card at 1MSamples/second without sampling jitter with SDFat 2.0b, as it doesn't block interrupts for anything near 1uSec. However, there is a lot of sub-microsecond jitter in the interrupt response time as there is a short interrupt blockage with every block write.
3. Writing to the Serial USB port during collection will also cause jitter in the interrupt response times. However, short bursts of output do not cause missed samples.
4. Writing to the SD Card while collecting analog data increases the noise level. This is to be expected because of current spikes during write which affect the 3.3V supply---which is also the ADC reference.

My test program collects 1 channel of data and optionally writes it to SD. It also collects histogram data for timing and analog values. Here is the timing data when collecting at 1.00 MSamples/second and writing it to SD:

Code:
MegaSample logger Compiled on Sep 19 2020 13:46:39
initialization done.
Type is FAT32
Reading ADC Samples
Collection rate is 1000000 samples/second.
Serial output during collection is OFF
SDC Writes are ON
Pre-Allocation succeeded.

ADC Read 10485760 samples with 0 overflows
Timing Histogram Data in ARM Cycle Counts
  577  2543
  580   161
  581 56156
  584   734
  585 45747
  588   787
  589 22108
  590     1
  592 4999402
  593 20708
  595 13369
  596   272
  597 28560
  599 48978
  600  2832
  601 46630
  603 31757
  604  1347
  605 12140
  607 20413
  608 5009714
  610     1
  611 17334
  612  1098
  615 43881
  616   226
  618     1
  619 56502
  623  2356

When not writing to SD, the interrupt jitter disappears:
Code:
Reading ADC Samples
Collection rate is 1000000 samples/second.
Serial output during collection is OFF
SDC Writes are OFF
Did not open log file.
ADC Read 10485760 samples with 0 overflows
Timing Histogram Data in ARM Cycle Counts
  592 5242879
  608 5242879

With a bit of Serial output during collection, timing jitter is back, but different than when writing to SDC.
Code:
Reading ADC Samples
Collection rate is 1000000 samples/second.
Serial output during collection is ON
SDC Writes are OFF
Did not open log file
......................
.....................
.....................
.....................
.....................
.....................
.....................
.............
ADC Read 10485760 samples with 0 overflows
Timing Histogram Data in ARM Cycle Counts
  352     1
  355    10
  363    45
  367    22
  385    21
  389     7
  393     8
  397    45
  590     1
  591    10
  592 5242693
  593    16
  607    16
  608 5242693
  609    10
  610     1
  803    45
  807     8
  811     7
  815    21
  833    22
  837    45
  845    10
  848     1

Remember, that since the timing jitter was less than 1uS, no data was missed and THERE WAS NO JITTER IN ADC SAMPLING!

Here's the demo program code:
Code:
/*******************************************************
    1.0 MegaSample T4.1 ADC using ADC timer to trigger the
    ADC collection.

    This version saves histogram data for timing interval
    and ADC values
   
   MJB   0/19/20

          Modified to log 12-bit samples and save as uint16_t
***************************************************************/
#include "SdFat.h"
#include <TimeLib.h>
#include <ADC.h>

// instantiate a new ADC object
ADC *adc = new ADC(); // adc object;

// SdFS file system accepts both FAT and ExFAT cards
SdFs sd;
FsFile logFile;


#define SD_CONFIG SdioConfig(FIFO_SDIO)
const char compileTime [] = "MegaSample logger Compiled on " __DATE__ " " __TIME__;
//const int admarkpin = 1;
const int wrmarkpin = 0;
const int ledpin    = 13;

// WRMARKHI and WRMARKLO are used to observe SDC Write timing on oscilloscope
#define  WRMARKHI digitalWriteFast(wrmarkpin, HIGH);
#define  WRMARKLO digitalWriteFast(wrmarkpin, LOW);

#define  LEDON digitalWriteFast(ledpin, HIGH); // Also marks IRQ handler timing
#define  LEDOFF digitalWriteFast(ledpin, LOW);

// buffers for histogram data
#define TMHISTOMAX 1000  // max interval  10000 cycles  at 600MHz  = 6 microseconds
#define ADCMAX 4096  // for 12-bit samples
uint32_t tm_histobuffer[TMHISTOMAX];  // for timing intervals up to 40.96mSec
uint32_t adc_histobuffer[ADCMAX];

// Saving 12-bit data as uint16_t
#define ADBLOCKSIZE  65536
#define SAMPRATE 1000000

// if you need larger buffers for slower SD cards, you can increase these
// sizes, however, you may need to put one of them into DMAMEM
uint16_t adcbuff0[ADBLOCKSIZE];// 64K * 2 bytes = 128K Buffers
uint16_t adcbuff1[ADBLOCKSIZE];

bool verboseflag = false;  // true to show some output during sampling
bool writeflag = true;   // true to write, false for no writes to SDC
volatile uint16_t inbuffnum = 0;
volatile uint32_t totalsamples = 0;

uint16_t *inbuffptr, *sdbuffptr;
volatile uint32_t adcidx, totalbsamples;

const uint adcpin = A9;  // my 2.5V precision reference

void setup() {
  // put your setup code here, to run once:
 
  Serial.begin(9600);
  delay(500);
  Serial.println(compileTime);
  // activate ARM cycle counter
  ARM_DEMCR |= ARM_DEMCR_TRCENA; // Assure Cycle Counter active
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  pinMode(wrmarkpin, OUTPUT);
  pinMode(ledpin, OUTPUT);
  pinMode(adcpin, INPUT_DISABLE);
  adc->adc0->setAveraging(1 ); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed

  if (!StartSDCard()) {
    // do fast blink forever

    do { // hang with blinking LED
      LEDON
      delay(100);
      LEDOFF
      delay(100);
    } while (1);

  }// end of  if (!StartSDCard())

  setSyncProvider(getTeensy3Time); // helps put time into file directory data
}



void loop() {
  // put your main code here, to run repeatedly:
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'l')  LogADC();
    if (ch == 'a')  ShowADCHisto();
    if (ch == 't')  ShowTmHisto();
    if (ch == 'v')  verboseflag = !verboseflag;
    if (ch == 'w')  writeflag = !writeflag;
    if (ch == 'd')  sd.ls(LS_SIZE | LS_DATE | LS_R);
  }
}

void ShowSetup(void){
  Serial.printf("Collection rate is %lu samples/second.\n",SAMPRATE);
  Serial.print("Serial output during collection is ");
  if(verboseflag) Serial.println("ON"); else Serial.println("OFF");
  Serial.print("SDC Writes are ");
  if(writeflag) Serial.println("ON"); else Serial.println("OFF");
}

// define the number of samples to collect as 10 M Samples
#define MAXSAMPLES  10*1024l*1024l
/*****************************************************
   This is the ADC timer interrupt handler
 ******************************************************/

volatile uint32_t lastcycles;
volatile uint16_t overflows;

// This ISR runs in about 120nSec on T4.1 at 600MHz.
// It buffers the data, but SDC writes are user-selected
// timing and adc histogram values are saved in RAM
void adc0_isr()  {
  uint32_t tmdiff, thiscycles;
  uint16_t adc_val;
  LEDON;
  if (totalsamples < MAXSAMPLES) { // sample until enough collected
    thiscycles =  ARM_DWT_CYCCNT;
    tmdiff = thiscycles - lastcycles;


    lastcycles = thiscycles;
    totalsamples++;

    // Collect ADC value and update the ADC Histogram data
    adc_val = adc->adc0->readSingle();
    // Save ADC data in buffer
    inbuffptr[adcidx] = adc_val;  // save the data
    adcidx++;
    if (adcidx >= ADBLOCKSIZE) {  // switch buffers at end
      // if main loop hasn't finished with last SD buffer
      // we have a potential overflow
      if(sdbuffptr != NULL) overflows++;
      sdbuffptr = inbuffptr; // set up block for output
      if (inbuffnum == 0) { // swap input to other buffer
        inbuffptr = &adcbuff1[0];
        inbuffnum = 1;
      } else {
        inbuffptr = &adcbuff0[0];
        inbuffnum = 0;
      }
      adcidx = 0;
    }// end of  if (adcidx < ADMAX)


    // make sure we don't write outside histogram buffers
    if (adc_val >= ADCMAX) adc_val = ADCMAX;
    if (tmdiff >= TMHISTOMAX) tmdiff = TMHISTOMAX - 1;
    // Skip the first two samples, as they often have
    // weird timing values. Update histogram data
    if (totalsamples > 2) {
      tm_histobuffer[tmdiff]++;
      adc_histobuffer[adc_val]++;
    }

#if defined(__IMXRT1062__)  // Teensy 4.0
    asm("DSB");
#endif
  }  // end of if(totalsamples < maxsamples
  LEDOFF;
}

/******************************************************
   Read MAXSAMPLES from ADC at 1 microsecond intervals
   Store the results in adcbuffer;
 *****************************************************/

void LogADC(void) {
  uint16_t lcount;
  Serial.println("Reading ADC Samples");
  totalsamples = 0;
  inbuffnum = 0;
  inbuffptr = &adcbuff0[0];
  sdbuffptr = NULL;
  overflows = 0;
  adcidx = 0;

  memset(adc_histobuffer, 0, sizeof(adc_histobuffer));  // clear adc histogram counts
  memset(tm_histobuffer, 0, sizeof(tm_histobuffer));  // clear  timing histogram counts
  ShowSetup();
  if (!OpenLogFile()) {
    Serial.print("Did not open log file.");
  }

  adc->adc0->stopTimer();
  adc->adc0->startSingleRead(adcpin); // call this to setup everything before the Timer starts, differential is also possible
  delay(1);
  adc->adc0->readSingle();

  // now start the ADC collection timer
  adc->adc0->startTimer(SAMPRATE); //frequency in Hz
  lastcycles =  ARM_DWT_CYCCNT;
  adc->adc0->enableInterrupts(adc0_isr);

  lcount = 0;
  do {
    if (sdbuffptr != NULL) { // when data in buffer, write to SD card
      WRMARKHI
      if (logFile) logFile.write(sdbuffptr, ADBLOCKSIZE * sizeof(uint16_t)); //sample is now 2 bytes
      sdbuffptr = NULL;  // indicates that we are finished writing
      //totalsamples += ADBLOCKSIZE;  // incremented in ISR
      if(verboseflag)Serial.print("."); // mark each 128KB block written
      if (lcount++ > 19) {
      if(verboseflag)Serial.println();
        lcount = 0;
      }
      WRMARKLO
    }
  }  while (totalsamples < MAXSAMPLES);
  adc->adc0->stopTimer(); 
  if (logFile) {
    logFile.truncate();  //truncate to amount actually written
    logFile.close();
  }
  Serial.printf("\nADC Read %lu samples with %u overflows\n",totalsamples,overflows);
}


bool OpenLogFile(void) {
  uint64_t alloclength;
  if(!writeflag) return false;  // don't open file if not writing
  if (!logFile.open("Log1MS.dat",  O_RDWR | O_CREAT | O_TRUNC)) {
    return false;
  }
  alloclength = (uint64_t)200 * (uint64_t)(1024L * 1024l); //200MB

  if (!logFile.preAllocate(alloclength)) {
    Serial.println("Pre-Allocation failed.");
    return false;
  } else {
    Serial.println("Pre-Allocation succeeded.");
  }
  return true;

}


void ShowTmHisto(void) {
  uint32_t i;
  Serial.println("Timing Histogram Data in ARM Cycle Counts");
  for (i = 0; i < TMHISTOMAX; i++) {
    if (tm_histobuffer[i] > 0) {
      Serial.printf("%5lu %5lu\n", i, tm_histobuffer[i]);
    }
  }
  Serial.println();

}

void ShowADCHisto(void) {
  uint32_t i;
  Serial.println("ADC Histogram Data in  Counts");
  for (i = 0; i < ADCMAX; i++) {
    if (adc_histobuffer[i] > 0) {
      Serial.printf("%5lu %5lu\n", i, adc_histobuffer[i]);
    }
  }
  Serial.println();
}


bool StartSDCard() {
  if (!sd.cardBegin(SD_CONFIG)) {
    Serial.println("cardBegin failed");
  }
  if (!sd.volumeBegin()) {
    Serial.println("volumeBegin failed");
  }
  if (!sd.begin(SdioConfig(FIFO_SDIO))) {
    Serial.println("\nSD File initialization failed.\n");
    return false;
  } else  Serial.println("initialization done.");

  if (sd.fatType() == FAT_TYPE_EXFAT) {
    Serial.println("Type is exFAT");
  } else {
    Serial.printf("Type is FAT%d\n", int16_t(sd.fatType()));
  }
  // set date time callback function
  SdFile::dateTimeCallback(dateTime);
  return true;
}
/*****************************************************************************
   Read the Teensy RTC and return a time_t (Unix Seconds) value

 ******************************************************************************/
time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

//------------------------------------------------------------------------------
/*
   User provided date time callback function.
   See SdFile::dateTimeCallback() for usage.
*/
void dateTime(uint16_t* date, uint16_t* time) {
  // use the year(), month() day() etc. functions from timelib

  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(year(), month(), day());

  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(hour(), minute(), second());
}
 
A Quick Note: I was able to run the demo code in post #45 on a Teensy 3.6 by reducing the buffer sizes a bit and setting the sampling rate to 240KSamples/second. The T3.6 uses the PDB delay block to time the samples. IRQ timing jitter is worse, but the ADC seems to do OK at that rate. A disturbing anomaly is that the program runs the first time after a reboot, but locks up when the same settings are run a second time. This happens even if SD writes are disabled and at slower sample rates, so I suspect a problem in the shutdown of the ADC timer.
 
Trapped indoors by wildfire smoke in the Willamette valley, I decided to revisit this issue.

[/CODE]

Sorry to hear that, some good wine from your valley :( hopefully all fires are under control by now.

I only just got the RAM modules last week, I have soldered one on to test, and so far everything looks good with the code that you posted in post #26.

I will look into your latest code (post #45) this evening but just to confirm you are saying that the code in post #45 is the better option due to the lack of sampling jitter?

As always thank you!
 
The code in Post #26 writes the ADC data to the PSRAM at a speed determined by the ADC conversion speed settings---which is not exactly 1MSamples/second. The code in Post #45 writes to the SD Card at a speed determined by the ADC timer---which can be exactly 1MSamples/second (within the limits of the accuracy of the T4.1 clock). Neither version has sampling jitter as the start of the conversion is controlled by hardware and does not depend on the interrupt handler---as is the case if you use an IntervalTimer to control the sampling.

The ADC data in Post #45 will have higher noise limits due to the current spikes when writing to the SD card during collection. For a transient logger where you need a limited number of samples, you could modify the code in Post #45 to save ADC data to the PSRAM and do the SD card writes after the sampling is complete.
 
The code in Post #26 writes the ADC data to the PSRAM at a speed determined by the ADC conversion speed settings---which is not exactly 1MSamples/second. The code in Post #45 writes to the SD Card at a speed determined by the ADC timer---which can be exactly 1MSamples/second (within the limits of the accuracy of the T4.1 clock). Neither version has sampling jitter as the start of the conversion is controlled by hardware and does not depend on the interrupt handler---as is the case if you use an IntervalTimer to control the sampling.

The ADC data in Post #45 will have higher noise limits due to the current spikes when writing to the SD card during collection. For a transient logger where you need a limited number of samples, you could modify the code in Post #45 to save ADC data to the PSRAM and do the SD card writes after the sampling is complete.

Hello Mborgerson, I've done my best to avoid pestering you and the others on the forum with this topic and I think to my credit I did OK...for a while :p

Your help along with the others who assisted on the topic of measuring pressure waves at 1MSPS was invaluable in generating test data for my studies.

I have since Late December 2020 been trying to modify the code in post #26 to accommodate measurement of a full wheatstone bridge (Picture attached), doing this will reduce much of the environmentally induced variability in my readings from a pressure sensor but to put it bluntly it's been an astounding disaster.

So ultimately my question is: is it even possible to modify the code in post #26 or #45 to accommodate separate readings from ADC0 and ADC1? Obviously the sample speed will take a knock but at least variance due to error of the actual sensor will be mildly better.

I have spent a fair bit of time brushing up on my coding but never quite seems to be enough for this mechanical engineer.

Capture.PNG

With regards to the code in post #45, I must be honest I could never get the pressure wave translated in Matlab so I did not try and modify this code and that is the only reason why I haven't used this code despite it being possibly more suitable.
 
A differential ADC on the Teensy 3.6 (Pins A10 and A11) should work well for this application.
 
Back
Top