Simple code to show T4.1 ADC sampling rate is 1MSPS

New_bee

Active member
Guys, I am tasked to prove that the ADC sampling rate of Teensy4.1 is 1MSPS or something close. Part of the project is about when the ADC, say A0 is connected to the signal generator and I supply frequencies between 5kHz and 10kHz to the teensy , I can still get a pure and clean sinusoidal waveform on the Arduino serial plotter.
I tried this 1 MSPS on a T4? Is this possible... still more confusion

I would appreciate any suggestion.
 
This code from the referenced thread should prove that the T4.1 can collect at 1Million samples per second:

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

It may not be simple, but it is complete. If you want something simpler, start with this and remove features like the histogram display (which is only useful when measuring the noise on the fixed reference voltage connected to input A9). Asking to demonstrate sine wave outputs from samples taken at 1MSamples/second is probably not something you should try in real time! ;-). Even if you capture only 100K Samples, the Arduino Serial plotter is probably not the best tool. You would be better off collecting the data on SD and importing that data into a PC app better suited to displaying and plotting lots of data. I use Matlab for that purpose.
 
This code from the referenced thread should prove that the T4.1 can collect at 1Million samples per second:

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

It may not be simple, but it is complete. If you want something simpler, start with this and remove features like the histogram display (which is only useful when measuring the noise on the fixed reference voltage connected to input A9). Asking to demonstrate sine wave outputs from samples taken at 1MSamples/second is probably not something you should try in real time! ;-). Even if you capture only 100K Samples, the Arduino Serial plotter is probably not the best tool. You would be better off collecting the data on SD and importing that data into a PC app better suited to displaying and plotting lots of data. I use Matlab for that purpose.
Thank you very much. I will try your suggestion now.
 
Back
Top