Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 3 1 2 3 LastLast
Results 1 to 25 of 58

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

Hybrid View

  1. #1

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

    Hello All,

    I want to record 300 ms of data from the internal ADC at 1 MSPS in 8 bit resolution, this should require 300 kB (1 000 000 (samples) x 8 (bits) x 0.3 (seconds)) of memory and the T4 will still have plenty to spare.

    Is this possible? And if so where do I start? I have spent some time with the Pedvide ADC library but have yet to figure out how to record this data and then read it afterwards. I'm not an embedded systems specialist or a very good programmer so I don't mind spending the time looking for the solution, I just want to know that I am looking in the right place.

  2. #2
    Thought I'd add a picture because everyone likes pictures

    Click image for larger version. 

Name:	Untitled.jpg 
Views:	81 
Size:	57.1 KB 
ID:	19570

  3. #3
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    330
    Thanks for providing this morning's "stay at home and stay busy" project.

    This code worked fine for recording at 10KHz sine wave. It's a bit too simplified to be up to my professional programming standards (global variables, no parameters to functions, etc. etc.), but it is easy to read and it works.
    I ran it on at T4.0 at 600MHz.

    Code:
    /*******************************************************
       1MegaSample T4.0 ADC
       MJB   4/2/20
    
       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  = 1;
    
    // ADMARKHI and ADMARKLO are used to observe timing on oscilloscope
    #define  ADMARKHI digitalWriteFast(admarkpin, HIGH);
    #define  ADMARKLO digitalWriteFast(admarkpin, LOW);
    
    #define ADMAX 300000
    uint8_t adcbuffer[ADMAX];
    volatile uint32_t adcidx;
    volatile bool doneflag = false;
    
    void setup() {
      // put your setup code here, to run once:
      while (!Serial) {}
      Serial.begin(9600);
      Serial.println("\nOne MSample ADC test");
      pinMode(admarkpin, OUTPUT);
      pinMode(A0, INPUT);
      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
    }
    void loop() {
      // put your main code here, to run repeatedly:
      char ch;
      if (Serial.available()) {
        ch = Serial.read();
        if (ch == 'r')  ReadADC();
        if (ch == 'p')  SendPlotADC();
        if (ch == 's')  ShowADC();
      }
    }
    
    
    /*****************************************************
     * This is the intervaltimer interrupt handler
     ******************************************************/
    void ADCChore(void){
      uint8_t adval;
      ADMARKHI
      if(adcidx < ADMAX){  // sample until end of buffer
        adc->adc0->startSingleRead(A0); // start a single conversion
        adcbuffer[adcidx] = adc->adc0->readSingle();
        adcidx++;   
      }
      ADMARKLO
      // 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
      delay(1000);  // plenty of time for 300,000 samples
      ADCTimer.end();  // stop the timer
      Serial.println("ADC Read ");
      Serial.print(adcidx);
      Serial.println(" samples");
    }
    
    /******************************************************
       Send MAXSAMPLES from adcbuffer at 1 millisecond
       intervals.  Output is slowed down to allow plotting
       with the Arduino serial plotter
     *****************************************************/
    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 < ADMAX/5; sendidx++) {
        Serial.println(adcbuffer[sendidx]);
        delay(1);
      }
    }
    
    /******************************************************
    *  Display data from adcbuffer in lines of 20 values
    *  Only the first NUMTOSHOW values are displayed
     *****************************************************/
    #define NUMTOSHOW   1000  // change to alter number output
    void ShowADC(void) {
      uint32_t sendidx;
    
      Serial.println("ADC Data");
      // Sending the full 300,000 samples would  a long time!
      for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) {
        
        Serial.printf("% 4u", adcbuffer[sendidx]);
        if((sendidx % 20) == 19) Serial.println();
      }
    }
    Last edited by mborgerson; 04-02-2020 at 05:35 PM. Reason: removed unused boolean variable

  4. #4
    hehe no problem, thanks for that. I love how you say "this mornings project" this would have taken me at least 10 mornings .

    Cheers

  5. #5
    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:	44 
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:	51 
Size:	33.7 KB 
ID:	21391
    Last edited by Jp3141; 08-16-2020 at 04:50 AM.

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

  7. #7
    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.

  8. #8
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    330
    This afternoon's project was to see if the 1MSample example could be modified to log the 1MSample ADC values to an SD Card. Some time ago, I coerced a micro SD socket into clinging to the bottom of a T4.0 by twisting its pins and applying dabs of molten tin/lead alloy. The result is shown in this photo:

    Click image for larger version. 

Name:	T4_sdio2.jpg 
Views:	80 
Size:	142.3 KB 
ID:	19572

    With that socket in place, I added code to the example to save 10 seconds of data collected at 1MSamples/second.

    Code:
    /*******************************************************
       1MegaSample T4.0 ADC
       MJB   4/2/20
    
       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 ther is
              time for the native SDIO interface to write the data
              to an sd card.
    
              In this case, the SD card is a 128GB SanDisk card formatted
              as an EXFAT volume.  A pre-allocated EXFat file greatly reduces
              write overhead as the system doesn't have to update the FAT when
              moving to a new write cluster.
         
    
              The EXFAT formatter and file routines are part of the SDFAT 2.0
              beta version of SDFAT. You can download the library from
              Bill Greiman's  GITHUB site.
              
              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 "FreeStack.h"
    #include "ExFatlib\ExFatLib.h"
    #include <time.h>
    #include <TimeLib.h>
    #include <ADC.h>
    
    IntervalTimer ADCTimer;
    
    // instantiate a new ADC object
    ADC *adc = new ADC(); // adc object;
    
    
    #define SD_FAT_TYPE 2
    // file system
    SdExFat sd;
    SdioCard sdc;
    ExFile 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;
    
    // 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);
    
    #define  LEDON digitalWriteFast(ledpin, HIGH);
    #define  LEDOFF digitalWriteFast(ledpin, LOW);
    
    
    #define ADBLOCKSIZE  131072
    
    uint8_t DMAMEM adcbuff0[ADBLOCKSIZE];// 128K Buffer
    uint8_t DMAMEM adcbuff1[ADBLOCKSIZE];
    
    volatile uint16_t inbuffnum = 0;
    
    
    uint8_t *inbuffptr, *sdbuffptr;
    volatile uint32_t adcidx, totalbytes;
    
    void setup() {
      // put your setup code here, to run once:
      while (!Serial) {}
      Serial.begin(9600);
      Serial.println(compileTime);
      pinMode(admarkpin, OUTPUT);
      pinMode(wrmarkpin, OUTPUT);
      pinMode(ledpin, OUTPUT);
      pinMode(A0, INPUT);
      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
    
      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 == 's')  ShowADC();
        if (ch == 'd')  sd.ls(LS_SIZE | LS_DATE | LS_R);
      }
    }
    
    // define the number of samples to collect as 10 megabytes
    #define MAXSAMPLES  10*1024l*1024l
    /*****************************************************
       This is the intervaltimer interrupt handler
     ******************************************************/
    void ADCChore(void) {
    ;
      ADMARKHI
      if (totalbytes < MAXSAMPLES) { // sample until enough collected
        adc->adc0->startSingleRead(A0); // start a single conversion
        inbuffptr[adcidx] = adc->adc0->readSingle();
        //inbuffptr[adcidx] = micros() & 0xFF;  // uncomment for timing test
        adcidx++;
        if (adcidx >= ADBLOCKSIZE) {  // switch buffers at end
          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)
        ADMARKLO
        // ADMARHI to ADMARKLO takes about 180nS at 600MHz
        // So some oversampling may be possible
      }  // end of if (totalbytes < MAXSAMPLES)
    }
    
      /******************************************************
         Read MAXSAMPLES from ADC at 1 microsecond intervals
         Store the results in adcbuffer;
       *****************************************************/
    
      void LogADC(void) {
        uint32_t totalbytes = 0;
        Serial.println("Reading ADC Samples");
        inbuffnum = 0;
        inbuffptr = &adcbuff0[0];
        sdbuffptr = NULL;
        adcidx = 0;
        if (!OpenLogFile()) {
          Serial.print("Could not open log file.");
          return;
        }
        ADCTimer.priority(50); // medium-high priority
        ADCTimer.begin(ADCChore, 1.0); // start timer at 1 microsecond intervals
        do {
          if (sdbuffptr != NULL) { // when data in buffer, write to SD card
            WRMARKHI
            logFile.write(sdbuffptr, ADBLOCKSIZE);
            sdbuffptr = NULL;
            totalbytes += ADBLOCKSIZE;
            Serial.print("."); // mark each 128KB block written
            WRMARKLO
          }
        }  while (totalbytes < MAXSAMPLES);
        ADCTimer.end();  // stop the timer
        logFile.truncate();  //truncate to amount actually written
        logFile.close();
        Serial.print("\nADC Read ");
        Serial.print(totalbytes);
        Serial.println(" samples");
      }
    
    
      bool OpenLogFile(void) {
        uint64_t alloclength;
    
        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;
    
      }
    
    
      /******************************************************
         Display data from adcbuffer0 in lines of 20 values
         Only the first NUMTOSHOW values are displayed
       *****************************************************/
    #define NUMTOSHOW   1000  // change to alter numbers output
      void ShowADC(void) {
        uint32_t sendidx;
    
        Serial.println("ADC Data");
        // Sending the full 128K samples would  a long time!
        for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) {
    
          Serial.printf("% 4u", adcbuff0[sendidx]);
          if ((sendidx % 20) == 19) 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());
    }
    The result demonstrates the feasibility of continuously logging ADC data to the SD card at 1M samples/second. Before this example is ready for exposure to the real world, it needs a lot more work:
    * a way to upload the data at the end of sampling.
    * a user interface beyond the single-character selectors
    * verification that the SD writing doesn't unduly disturb the sampling interval
    * a measurement of the degree to which the block write and erase currents of the SD writes affect the ADC values.
    (The T4.0 uses the 3.3V supply as the ADC reference. Noise on that supply affects the ADC results)
    * a test to see if it is possible to collect 12-bit ADC data and write it as uint16_t values.

    Theoretically, a 128GB SD card could log about 1.5 days of data at 1M samples/second. You'd better have crazy Matlab skills if you're going to analyze that much data!

    OTOH, collecting the data seems to take only about 60% of the CPU cycles, so perhaps you can analyze it on the fly and save only the results of the analysis.

  9. #9
    "An Afternoon project", you're killing me

    Looking through the code you've already answered 2 other questions that I haven't yet been able to figure out until now, thanks!

  10. #10
    Hi Mborgerson,

    The first code (no SD Card) you posted above is amazing, since our Covid lockdown I have been doing tests using a trigger wire to capture ADC input this has worked amazingly well.

    I Then bought a T4.1 and planned to use the second code you posted above which included writing to SD card, everything seems to work however the file generated on the SD card (pic below) only contains brackets and no ADC data. Am I doing something wrong or did your comment above "a way to upload the data at the end of sampling" mean that writing to the SD card was not yet implemented, in which case I will try and implement. Thanks again!

    Click image for larger version. 

Name:	Capture.PNG 
Views:	39 
Size:	17.9 KB 
ID:	21324

  11. #11
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    330
    Quote Originally Posted by Maximiljan View Post

    I Then bought a T4.1 and planned to use the second code you posted above which included writing to SD card, everything seems to work however the file generated on the SD card (pic below) only contains brackets and no ADC data. Am I doing something wrong or did your comment above "a way to upload the data at the end of sampling" mean that writing to the SD card was not yet implemented, in which case I will try and implement. Thanks again!
    Note that the sample code writes the ADC data in binary format as a series of bytes. Part of the process of uploading the binary data would be to convert the series of binary bytes to human-readable format. Simply looking at the SD files as if they were ASCII codes could give you the kind of display you showed. You need to implement something like the ShowADC() function, but using the data on the SD card.

    I think the comments by jonr concerning the pitfalls of trying to read the ADC at 1MHz are relevant. I will play around with the code on my T4.1 with a sine-wave input and let you know what I see.

  12. #12
    Quote Originally Posted by mborgerson View Post
    Note that the sample code writes the ADC data in binary format as a series of bytes. Part of the process of uploading the binary data would be to convert the series of binary bytes to human-readable format. Simply looking at the SD files as if they were ASCII codes could give you the kind of display you showed. You need to implement something like the ShowADC() function, but using the data on the SD card.

    I think the comments by jonr concerning the pitfalls of trying to read the ADC at 1MHz are relevant. I will play around with the code on my T4.1 with a sine-wave input and let you know what I see.
    Thank you, I thought I was converting the data from .bin to .csv but clearly my method wasn't working. Will have a look at "ShowADC()". As always thank you.

  13. #13
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    Do some analysis of the data - its likely that you will want to use some different settings and both ADCs to get the accuracy that you would expect from 8 bits. Even "12 bits" is possible.

    > pinMode(A0, INPUT)
    This isn't a good idea - it sets up the pin as a digital input.


    AFAIK, 1Msps is only possible by alternating between two ADCs.

  14. #14
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    > ADMARHI to ADMARKLO takes about 180nS at 600MHz

    But this says nothing about how long a conversion takes to complete. I find that they take about 1.1 usec - so I think your code is over-reading the ADC. A sinewave as input would detect it.

    It might just work using VERY_HIGH_SPEED. But alternating ADCs will allow lower speed for better results.

  15. #15
    Quote Originally Posted by jonr View Post
    > ADMARHI to ADMARKLO takes about 180nS at 600MHz

    But this says nothing about how long a conversion takes to complete. I find that they take about 1.1 usec - so I think your code is over-reading the ADC. A sinewave as input would detect it.

    It might just work using VERY_HIGH_SPEED. But alternating ADCs will allow lower speed for better results.
    Hi Jonr.

    Ok thanks will try to implement the second ADC. I did hook the single ADC up to an oscilloscope and got what I considered to be decent results a 1MSPS but in this case faster is better and your idea to use both ADCs makes sense. Thanks

  16. #16
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    8,843
    Just wondering if you looked at the ADC example sketch:
    adc_timer_dma.ino?

    It uses a Quad Timer to automatically trigger the ADC, and results go out using DMA...

    I have not tried the timer at 1mhz, I think the Quad timer set frequency code in PWM.c will allow it...

    As for can you actually do this fast of a conversion? I don't know. If I remember correctly in the example I was only reading something like 3000 samples per second as I am just trying to get a rough RMS value for 60hz AC and figured that was fast enough.

    The code use two DMA structures which are linked to each other, where you get an interrupt each time each buffer is filled. Assuming that ADC unit can actually go fast enough (Max for PDF - Analog-Digital-Converter (ADC) - Supports up to 1MS/s sampling rate).


    Note: depending on if you are really just wanting to run the ADC at absolutely fastest speed, there is another example like this without the name timer in it, that runs the ADC as fast as it can storing results in DMA...

  17. #17
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    Here is an example of alternating to allow twice the speed. Would be better if it verified (vs assumed) that AD conversion was complete.

    Code:
    void ADCChore(void) {
      static unsigned flipflop = 0;
      unsigned value;
      static unsigned count = 0;   // skip using first two samples
    
      if (flipflop++ & 1) {
        value = adc->adc1->readSingle();     // read 1
        adc->adc0->startSingleRead(A0);     // start 0
      } else {
        value = adc->adc0->readSingle();    // read 0
        adc->adc1->startSingleRead(A0);    // start 1
      }
    
      if (adcidx < ADMAX && count > 2)
        adcbuffer[adcidx++] = value;
      else
        ++count;
    }

  18. #18
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    Interesting. Once I added code to detect incomplete conversions, I got occasional errors, even at rates that should be possible. Ie, interval timer doesn't guarantee that it won't be called with less interval. But it usually worked with a single ADC, which supports your results. Probably timer triggered DMA is the way to go for low jitter and no errors. In any case, your program is quite nice.


    Code:
    if (adc->adc0->isConverting())
        error = 1;
      value = adc->adc0->readSingle();  // read previously started conversion
      adc->adc0->startSingleRead(A0); // start next conversion}

  19. #19
    Quote Originally Posted by jonr View Post
    Interesting. Once I added code to detect incomplete conversions, I got occasional errors, even at rates that should be possible. Ie, interval timer doesn't guarantee that it won't be called with less interval. But it usually worked with a single ADC, which supports your results. Probably timer triggered DMA is the way to go for low jitter and no errors. In any case, your program is quite nice.


    Code:
    if (adc->adc0->isConverting())
        error = 1;
      value = adc->adc0->readSingle();  // read previously started conversion
      adc->adc0->startSingleRead(A0); // start next conversion}
    Thanks but credit where credit is due, that is 100% mborgerson's excellent work.

  20. #20
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    Whoops, my code in #11 isn't right. To interleave the ADCs, it should read from adc0 and immediately start another on adc0 (not adc1). And vice versa.

    Done right, I get 2.55 Msps with VERY_HIGH_SPEED. 2 Msps with HIGH_SPEED and 10 bits resolution.

  21. #21
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    8,843
    Sorry, I know I am probably missing something here and maybe suggesting some more hardcore things than what you actually need or want.

    But I am confused on what is the goal? To do as many fast reads as possible? Or to read at some specific fast speed? How accurate do you want the timings to be?

    Maybe I am missing something but if your goal is to read as fast as the ADC will allow? Why not just turn on continuous sampling and with this you have a few different options:
    a) Interrupts - I believe you can get an interrupt when a conversion completes, which you can query.
    b) Poll again I believe you can sample say is there a conversion ready, yes, read it...
    c) DMA - I pointed to a couple of examples.

    Now if you wish to read N samples at a specific speed like 1MBS and the hardware supports that fast... Again you have a few options, with plus and minus.
    a) You can run your own timing in a loop and say, this much time has expired, so do next read.

    b) IntervalTimer - I have done the approach mentioned in this thread, before where I would read one result and startup the next one. For me I did it this way as I was reading 4 ADC pins to try to detect if a circuit was running by doing an RMS like calculation)... Works reasonably well. BUT: you are at the mercy of Interrupts. That is you start up some SDCard write and either I higher priority interrupt happens or someone disables interrupts and your IntervalTimer interrupt does not happen at consistent timing.

    c) Setup to use a Timer to trigger the ADC at a specific rate as I mentioned there are examples of it in ADC. The Advantage of this is once setup, it is all handled by hardware, that is the in this case QTimer trigger happens at some specific time, and through the XBAR system, it triggers the ADC module. So this is no longer tied to timing issues of other interrupts happening. Especially if again setup to use DMA to move the results out of the ADC into your own buffer. Note, the current code has limitations on how large these buffers are that ADC does the DMA operation to. That is I believe I setup only one DMA data structure for each buffer, so I believe limit is something like 64K bytes.
    But it is setup to handle 2 of them round robin, and has the ability to call you when a buffer is full, so you can then move results out of queue into your own buffer if needed...

    Sorry again if I am in left field.

  22. #22
    Quote Originally Posted by KurtE View Post
    Sorry, I know I am probably missing something here and maybe suggesting some more hardcore things than what you actually need or want.

    But I am confused on what is the goal? To do as many fast reads as possible? Or to read at some specific fast speed? How accurate do you want the timings to be?
    Hello KurtE, apologies did not really expect this rabbit hole to go as deep as it did, so I don't think that I properly explained what I was trying to do. I am measuring the rise time of shock-waves in a hazardous environment as part of my studies, by using the T4.1 I can collect this data and afford to lose a few T4.1's along the way (but trying not to). A sampling rate of 1 msps is a minimum requirement any faster is better but not a prerequisite, the accuracy of the timing however is important in order to be able to generate an accurate impulse graph of the rise time.

    Anyway to Jonr, KurtE and Mborgerson, thanks for all the effort, this is a serious learning curve so really appreciate it, I am going to spend this week fumbling with your codes and suggestions to see if I can get some data.

  23. #23
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    That's a good summary. Some glitches at 1Mhz leads to "so how fast can it go". With interleaving, more than fast enough to always complete even with some interrupt jitter.

  24. #24
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    330
    Quote Originally Posted by jonr View Post
    That's a good summary. Some glitches at 1Mhz leads to "so how fast can it go". With interleaving, more than fast enough to always complete even with some interrupt jitter.
    I modified my sample code to use interleaved ADC collection per jonr's sample code and added some timing data collection using the ARM cycle counter. I also tested with the resolution set to 12 bits. With the new setup, the time spent in the intervaltimer service routine is about 220nsec.

    I connected the ADC to a signal generator and tested various frequencies from 100Hz to 10Khz and got good results:
    Click image for larger version. 

Name:	1MSample_12bit.jpg 
Views:	57 
Size:	58.2 KB 
ID:	21329

    That is a ~10KHz signal---which takes about 100 samples per cycle.

    I found the following points interesting:

    1. There can be up to 0.42 microseconds of jitter in the start of the timer interrupt handler. I presume that this is due to other drivers blocking interrupts while handling Serial output and SD Card writing. I Tested without SD writes and Serial output and the max jitter decreased to 0.13uSec. Better--but still not perfect--perhaps due to things in Serial and the system tick.

    2. It didn't seem to make any difference whether I put my SD card buffers in regular RAM or DMAMEM.

    3. Both EXFat and FAT32 SD cards worked about the same when pre-allocation was used.

    Here's the updated code

    Code:
    /*******************************************************
       One MegaSample T4.0 ADC  12-bit
       MJB   4/2/20
             updated 8/9/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 ther is
              time for the native SDIO interface to write the data
              to an sd card.
    
              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
    /*******************************************************/
    
    IntervalTimer ADCTimer;
    
    // instantiate a new ADC object
    ADC *adc = new ADC(); // adc object;
    
    // use an elapsedmillis object to control collection time
    elapsedMillis collectmillis;
    
    
    // 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 logger Compiled on " __DATE__ " " __TIME__;
    const int admarkpin = 32;   // Changed to end pins on T4.1
    const int wrmarkpin = 33;
    const int ledpin    = 13;
    
    // 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);
    
    #define  LEDON digitalWriteFast(ledpin, HIGH);
    #define  LEDOFF digitalWriteFast(ledpin, LOW);
    
    
    #define ADBLOCKSIZE  (1024 * 100)   //  2 x 200KBytes to leave some room for other users of DMAMEM
    
    uint16_t  adcbuff0[ADBLOCKSIZE];
    uint16_t  adcbuff1[ADBLOCKSIZE];
    
    volatile uint16_t inbuffnum = 0;
    volatile bool logging = false;
    
    uint16_t *inbuffptr, *sdbuffptr;
    volatile uint32_t adcidx;
    uint32_t totalbytes;
    
    void setup() {
      // put your setup code here, to run once:
      Serial.begin(9600);
      delay(500);  // wait for Serial to open
      Serial.println(compileTime);
      pinMode(admarkpin, OUTPUT);
      pinMode(wrmarkpin, OUTPUT);
      pinMode(ledpin, OUTPUT);
      pinMode(A0, INPUT_DISABLE); // disable digital keeper resistors
      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::HIGH_SPEED); // change the sampling speed
      
      adc->adc1->setAveraging(1 ); // set number of averages
      adc->adc1->setResolution(12); // set bits of resolution
      adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change the conversion speed
      adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::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
      #ifdef USEMTP  
      StartMTP();
    
      ARM_DEMCR |= ARM_DEMCR_TRCENA;
      ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
      #endif
    }
    
    
    
    void loop() {
      // put your main code here, to run repeatedly:
      char ch;
      if (Serial.available()) {
        ch = Serial.read();
        if (ch == 'l')  LogADC();
        if (ch == 's')  ShowADC();
        if (ch == 'd')  sdf.ls(LS_SIZE | LS_DATE | LS_R);
      }
      #ifdef USEMTP
      mtpd.loop();
      #endif
    }
    
    
    
    /*****************************************************
       This is the intervaltimer interrupt handler
       8/9/2020   Simplified to have main thread control
                  logging.
                  Changed per jonr to alternate ADCs
                  This code takes about 220nS when logging
     ******************************************************/
    volatile uint32_t dwtlast; 
    volatile uint32_t maxinterval;
    volatile uint16_t overflows;
    void ADCChore(void) {
      uint16_t value;
      uint32_t dwt;
      dwt = ARM_DWT_CYCCNT; 
      ADMARKHI
      if (adcidx & 0x01) { //Read ADC0 and restart it   
        value = adc->adc0->readSingle();
        adc->adc0->startSingleRead(A0);    
     } else  { //Read ADC1 and restart it 
        value = adc->adc1->readSingle();
        adc->adc1->startSingleRead(A0); 
      }
      if (logging) { // Save result to buffer
        inbuffptr[adcidx] = value;
        adcidx++;
        if (adcidx >= ADBLOCKSIZE) {  // switch buffers at end
          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(logging)
        ADMARKLO
        // ADMARHI to ADMARKLO takes about 180nS at 600MHz
        // So some oversampling may be possible
      } 
      //keep track of max clock cycles between interrupts
      if(dwt-dwtlast > maxinterval) maxinterval = (dwt-dwtlast);
      dwtlast = dwt;
    }
    
    /******************************************************
       Read 7 seconds of data from ADCs at 1 microsecond intervals
       Store the results in adcbuffer;
       note that MTP loop is not called during logging
     *****************************************************/
    
    void LogADC(void) {
      uint32_t totalbytes = 0;
    
      Serial.println("Reading ADC Samples");
      inbuffnum = 0;
      inbuffptr = &adcbuff0[0];
      sdbuffptr = NULL;
      adcidx = 0;
      if (!OpenLogFile()) {
        Serial.print("Could not open log file.");
        return;
      }
      ADCTimer.priority(50); // medium-high priority
      ADCTimer.begin(ADCChore, 1.0); // start timer at 1 microsecond intervals
      delay(1);  // wait 1msec before starting logging
      dwtlast = ARM_DWT_CYCCNT;
      maxinterval = 0;
      logging = true;
      collectmillis = 0;  // reset the elapsedmillis timer
      do {
        if (sdbuffptr != NULL) { // when data in buffer, write to SD card
          WRMARKHI
      //    logFile.write(sdbuffptr, ADBLOCKSIZE*2);// save block of 16-bit words
          sdbuffptr = NULL;
          totalbytes += ADBLOCKSIZE;
        // Serial.print("."); // mark each 128KB block written
          WRMARKLO
        }
      }  while(collectmillis  < 7200); // A bit of extra time to make sure last buffer is written
      logging = false;
      ADCTimer.end();  // stop the timer
      logFile.truncate();  //truncate to amount actually written
      logFile.close();
      Serial.printf("\nADC Read %lu samples\n", totalbytes);
      Serial.printf("Maximum Sampling interval was %6.2f microseconds\n", (float)maxinterval/600.0);
    }
    
    
    bool OpenLogFile(void) {
      uint64_t alloclength;
    
      if (!logFile.open("Log1MS12B.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;
    
    }
    
    
    /******************************************************
       Display data from adcbuffer0 in lines of 20 values
       Only the first NUMTOSHOW values are displayed
     *****************************************************/
    #define NUMTOSHOW   1000  // change to alter numbers output
    void ShowADC(void) {
      uint32_t sendidx;
    
      Serial.println("ADC Data");
      // Sending the full 128K samples would  a long time!
      for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) {
    
        Serial.printf("% 5u", adcbuff0[sendidx]);
        if ((sendidx % 20) == 19) 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

  25. #25
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    845
    adc->adc0->setAveraging(1 ); // set number of averages
    adc->adc0->setResolution(12); // set bits of resolution
    adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIG H_SPEED); // change the conversion speed
    adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED ); // change the sampling speed
    Say one wants the best possible accuracy within some amount of time. Is there a process other than "test all combinations" to find the optimal ADC settings?

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
  •