Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 2 FirstFirst 1 2
Results 26 to 48 of 48

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

  1. #26
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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

  2. #27
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    650
    > 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.

  3. #28
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    Quote Originally Posted by jonr View Post
    > 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.

  4. #29
    Senior Member Jp3141's Avatar
    Join Date
    Nov 2012
    Posts
    486
    @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.
    Click image for larger version. 

Name:	10.010 MHz @ 1 us.jpg 
Views:	13 
Size:	59.4 KB 
ID:	21390

    and 1.01 MHz also at 1 us:
    Click image for larger version. 

Name:	1.010 MHz @ 1 us.jpg 
Views:	15 
Size:	33.7 KB 
ID:	21391
    Last edited by Jp3141; 08-16-2020 at 04:50 AM.

  5. #30
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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.

  6. #31
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    650
    @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.

  7. #32
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    @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.

  8. #33
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    650
    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.

  9. #34
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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 rate Bits Mean V Std. Dev V Std. Dev Counts
    0.987M 12 1.3729 0.0030 3.67
    1.339M 12 1.3731 0.0064 7.84
    1.875M 8 1.3649 0.0059 0.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.

  10. #35
    Senior Member Jp3141's Avatar
    Join Date
    Nov 2012
    Posts
    486
    @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.

  11. #36
    Senior Member Jp3141's Avatar
    Join Date
    Nov 2012
    Posts
    486
    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...l=1#post249028. My conclusion is that there are > 5 counts of VREF noise on a Teensy_LC at 16 bits.

  12. #37
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    650
    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).

  13. #38
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    Quote Originally Posted by jonr View Post
    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.

    Click image for larger version. 

Name:	Histo12.jpg 
Views:	17 
Size:	37.4 KB 
ID:	21400

    Click image for larger version. 

Name:	Histo8.jpg 
Views:	15 
Size:	38.9 KB 
ID:	21401


    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.

  14. #39
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    650
    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.

  15. #40
    Senior Member Jp3141's Avatar
    Join Date
    Nov 2012
    Posts
    486
    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.

  16. #41
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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.

  17. #42
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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.

  18. #43
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    650
    Looks like the pedvide DMA example alternates between two buffers and causes an interrupt when either fills up. So quite easy to use and more robust about delays between interrupts. I assume there would be no issue with running both ADCs into 4 buffers and averaging them together as they are transferred later.
    https://github.com/pedvide/ADC/blob/..._timer_dma.ino

  19. #44

    Post

    Quote Originally Posted by mborgerson View Post
    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!

  20. #45
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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());
    }

  21. #46
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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.

  22. #47
    Quote Originally Posted by mborgerson View Post
    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!

  23. #48
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    219
    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.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •