Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 23 of 23

Thread: analogRead sampling

  1. #1
    Member
    Join Date
    Nov 2020
    Location
    Czech Republic
    Posts
    49

    analogRead sampling

    Hey all,
    I want read from analog pin every 50 micros.

    My code:

    Code:
    void READ_BUFFER()
    {
       analogReadResolution(12);
       period = 50;
       float val;
       float time = 0;
       for(int i= 0;i < BUFFER_SIZE ; i++)
       {
        micros_0 = 0;  
        BUFFER[i] = analogRead(A1);
        val = (((BUFFER[i] * 3.0) / 4095));// - (3.0 / 2)); // convert to volts
        time = micros_0;
        while(micros_0 <=  period) 
        { 
        Serial.print(val,4);
        Serial.print(" - ");
        Serial.println(time); 
        } 
       }
    }
    BUT time is 20 - 21 micros.

    1.5788 - 21
    1.5788 - 21
    1.5766 - 20
    1.5766 - 20

    I need have this time exactly 50 micros = read one sample every 50 micros.
    Any way how to do it?
    Thanks a lot for help.

    Michal

  2. #2
    Do you mean something like this:


    Code:
    long tim = 0;
    
    
    void setup() {
      // put your setup code here, to run once:
      tim = micros();
    }
    
    
    void loop() {
      // put your main code here, to run repeatedly:
      doSomethingEvery50();
    }
    
    
    void doSomethingEvery50() {
      //wait until 50 micros have passed
      while (micros() - tim < 50) {
        //waste time until 50 micros have passed 
      }
      Serial.println(micros() - tim);
      tim = micros();
      //Do the think you want to here so if it uses some time then the while loop will be shorter to accomodate it
    }

  3. #3
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,229
    The correct way to run something every 50us is this:
    Code:
    #define PERIOD 50
    
    unsigned long prevtime = 0L;
    
    void loop ()
    {
      if (micros() - prevtime >= PERIOD)
      {
        prevtime += PERIOD ;  // update the target timestamp precisely by PERIOD.
        ....  do the thing ....
      }
    }
    You definitely do not update the variable by calling micros() again, since the time can have changed by then.

    However all the software methods like this will have jitter, and this is usually bad news for a sampling system -
    using DMA is preferred, even though is more complicated to set up.

  4. #4
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    Another possibility is to use the ADC library and the hardware ADC timer. It is also more complex in that you have to know how to set up and use an interrupt to collect the data-- but it gets you jitter-free sampling.

  5. #5
    Member
    Join Date
    Nov 2020
    Location
    Czech Republic
    Posts
    49
    Quote Originally Posted by mborgerson View Post
    Another possibility is to use the ADC library and the hardware ADC timer. It is also more complex in that you have to know how to set up and use an interrupt to collect the data-- but it gets you jitter-free sampling.
    Hi, could you show me example how read data to buffer via ADC? In one buffer I need value from analogread and in second buffer I need have time in micros (0,50,100,150,200,....) - its because FFT.
    I see many examples but I am not familiar with ADC for now - I never used.
    Thank you for your help.
    Michal

  6. #6
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    Quote Originally Posted by frohr View Post
    Hi, could you show me example how read data to buffer via ADC? In one buffer I need value from analogread and in second buffer I need have time in micros (0,50,100,150,200,....) - its because FFT.
    I see many examples but I am not familiar with ADC for now - I never used.
    Thank you for your help.
    Michal
    I've got lots of example code that might be applicable, but your question is a bit too vague. To pull out the right example I need to know:

    1. Which Teensy are you using?
    2. How many samples do you need to collect at 20KHz (50 uSec sample interval)?
    3. Will a sample set fit in a buffer in your Teensy memory, or does it need to be written to SD Card?
    4. Will you process the data on the Teensy or transfer it to a PC for analysis?

    If you use the ADC timer, there is really no need to save the sample time. The samples will be collected at 50uSec intervals and you can generate the times after collection with something like:
    Code:
    for(i=0; i<numsamples; i++){
       Serial.printf("%6d \t %4d\n", i * 50, samplebuffer[i]);
    }
    For an FFT, you don't necessarily need, sample times, just the sampling rate.

  7. #7
    Member
    Join Date
    Nov 2020
    Location
    Czech Republic
    Posts
    49
    Quote Originally Posted by mborgerson View Post
    I've got lots of example code that might be applicable, but your question is a bit too vague. To pull out the right example I need to know:

    1. Which Teensy are you using?
    2. How many samples do you need to collect at 20KHz (50 uSec sample interval)?
    3. Will a sample set fit in a buffer in your Teensy memory, or does it need to be written to SD Card?
    4. Will you process the data on the Teensy or transfer it to a PC for analysis?

    If you use the ADC timer, there is really no need to save the sample time. The samples will be collected at 50uSec intervals and you can generate the times after collection with something like:
    Code:
    for(i=0; i<numsamples; i++){
       Serial.printf("%6d \t %4d\n", i * 50, samplebuffer[i]);
    }
    For an FFT, you don't necessarily need, sample times, just the sampling rate.
    1. I use Teensy 4.0
    2. I want 16384 samples (maybe less but I have to try it)
    3. I will use just Teensy memory
    4. I will proceed data on Teensy

    With this one buffer I will work in many ways - make calculations and fft. FFT I want have "real time" - update on displej as fast as possible (10-1000Hz and 500-10000Hz) + rms calculations.

    What is important for me now is read data to buffer correctly. I will work in many ways - make calculations and fft. FFT I will have "real time" - update on displej as fast as possible (10-1000Hz and 500-10000)

  8. #8
    Senior Member
    Join Date
    Nov 2012
    Posts
    318
    I have used the simple loop with micros() to time intervals in my data logging examples and assumed lots of jitter. I did a test using the cycle counter in a Teeny 4.1. The result is far better than I hoped for.

    Here is the test code that take 20,000 samples and prints the min and max time in usec between samples.
    Code:
    // Will warn data[] not used.
    void setup() {
      Serial.begin(9600);
      while (!Serial) {}
    }
    
    
    int n = 0;
    void loop() {
      int32_t cycle[20000];
      uint16_t data[20000];
      uint32_t logTime = micros();
      for (int i = 0; i < 20000; i++) {
        logTime += 50; // log every 50 us.
        while ((int)(micros() - logTime) < 0) {}
        cycle[i] = ARM_DWT_CYCCNT;
        data[i] = analogRead(A0);
      }
      float usMin = 999;
      float usMax = 0;
      for (int i = 0; i < 19999; i++) {
        // F_CPU is 600 MHz.
        float us = (float)(cycle[i+1] - cycle[i])/600;
        if (us < usMin) {
          usMin = us;
        }
        if (us > usMax) {
          usMax = us;   
        }
      }
      Serial.print(usMin);
      Serial.print(' ');
      Serial.println(usMax);
      if (n++ > 50) {
        Serial.println("done");
        while(true) {}
      }
    }
    Here is the output. The worst case is less than 0.2 usec. This is for 50 sets of 20,000 samples.

    49.93 50.04
    49.95 50.05
    49.98 50.04
    49.98 50.03
    49.95 50.05
    49.82 50.16
    49.97 50.04
    49.95 50.05
    49.97 50.03
    49.98 50.04
    49.98 50.05
    49.95 50.05
    49.98 50.03
    49.97 50.03
    49.95 50.05
    49.98 50.03
    49.98 50.03
    49.95 50.05
    49.95 50.05
    49.97 50.03
    49.98 50.04
    49.95 50.05
    49.98 50.03
    49.97 50.04
    49.95 50.05
    49.97 50.03
    49.98 50.03
    49.98 50.04
    49.95 50.05
    49.97 50.04
    49.97 50.04
    49.95 50.05
    49.98 50.04
    49.98 50.04
    49.97 50.05
    49.95 50.05
    49.97 50.04
    49.97 50.03
    49.95 50.05
    49.98 50.04
    49.98 50.04
    49.95 50.05
    49.97 50.04
    49.97 50.03
    49.93 50.03
    49.95 50.05
    49.98 50.03
    49.97 50.04
    49.95 50.05
    49.97 50.04
    49.98 50.03
    49.95 50.05
    done

  9. #9
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    Here is a simple demo program to collect 16384 samples using the ADC timer.
    Code:
    // Sample program to Collect ADC data with ADC timer
    // Using the ADC timer to collect minimizes sampling jitter
    // Targeted to T4.0 at 600MHz
    // mborgerson 7/6/2021
    
    const int ledpin = 13;
    
    #define LEDON digitalWriteFast(ledpin, HIGH);
    #define LEDOFF digitalWriteFast(ledpin, LOW);
    #define LEDTOGGLE digitalToggleFast(ledpin);
    
    #include <ADC.h>
    const int readpin = A9; //   Connected to AA Alkaline cell
    
    ADC *adc = new ADC(); // adc object;
    
    #define SAMPRATE 20000
    #define AVERAGING 4
    #define RESOLUTION 12
    
    #define ADCSAMPLES 16384 // number of samples to collect
    uint16_t adcbuffer[ADCSAMPLES]; // data in DTCM so no cache updates required 
    const char compileTime [] = "ADC Timer test Compiled on " __DATE__ " " __TIME__;
    
    void setup() {
      delay(500);
      Serial.begin(115200);
      delay(1000);
      pinMode(ledpin, OUTPUT);
      pinMode(readpin, INPUT_DISABLE);
      delay(1500);
      Serial.print("\n\n");
      Serial.println(compileTime);
      Serial.println("Press <n> to collect and display data");
    
      adc->adc0->setAveraging(AVERAGING); // set number of averages
      adc->adc0->setResolution(RESOLUTION); // set bits of resolution
      adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);  // set the conversion speed
      adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);      // set the sampling speed
    }
    
    void loop() {
      char ch;
      if (Serial.available()) {
        ch = Serial.read();
        if (ch == 'n') GetADCData();
      }
    }
    
    volatile uint32_t adcidx;  // must be volatile as it is changed in interrupt handler
    // This ISR runs in about 60nSec on T4.0 at 600MHz.
    void adc0_isr()  {
      uint16_t adc_val;
      LEDTOGGLE;  // So you can verify sample rate on oscilloscope
      // call readSingle() to clear the interrupt.
      // and store the ADC data
      adc_val = adc->adc0->readSingle();
    
      if (adcidx < ADCSAMPLES) {  // storage stops when enough samples collected
        adcbuffer[adcidx] = adc_val;
        adcidx++;
      }
    }
    
    //Collect the ADC data
    void GetADCData(void) {
      delay(100); // wait for usb serial to finish
      adc->adc0->stopTimer();
      adc->adc0->startSingleRead(readpin); // call this to setup everything before the Timer starts, differential is also possible
      delay(1);
      adc->adc0->readSingle();
      adcidx = 0;
      // now start the ADC collection timer
      adc->adc0->startTimer(SAMPRATE); //frequency in Hz
      adc->adc0->enableInterrupts(adc0_isr);
    
      do {
        // You could process a sample here with multiple buffers or copying to a new buffer
        // for processing while collection is in progress
      } while (adcidx < ADCSAMPLES);
    
      adc->adc0->stopTimer();
      Serial.println("Collection complete.");
      // The data is in the global adcbuffer array.
      ShowADC();
    }
    
    // Display the start of data in counts.  Send 10 values per line
    // data is in the global variable adcbuffer.
    void ShowADC(void) {
      uint32_t i;
      Serial.println("ADC Data in  Counts");
      for (i = 0; i < 100; i++) {
        if ((i % 10) ==  0) {
          Serial.printf("\n% 3d: ", i);
        }
        Serial.printf("% 5d", adcbuffer[i]);
      }
      Serial.println();
    }
    Similar code that collected timing data for a histogram display showed jitter of less than one ARM clock cycle if no other operations were running during the collection. This actually seems hard to believe, since I thought that the systick interrupt would extend the interrupt response time every millisecond.

  10. #10
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,070
    I'd collect 100K+samples to catch another ISR's effect on timing. Periods of disabled interrupts (common in teensy code) will also cause jitter.

  11. #11
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,074
    @Bill Greiman noted timing with ARM_DWT_CYCCNT is more accurate ( takes 3 cycles to read instead of ~36 for micros() )

    ... seems I edited the code to use that and did 100,000 cycles:
    Code:
    T:\tCode\ADC\AnalogTimedRead\AnalogTimedRead.ino Jul  6 2021 13:42:12
    50.00 50.00
    49.99 50.01
    49.99 50.01
    50.00 50.00
    49.99 50.00
    50.00 50.01
    50.00 50.00
    49.99 50.01
    ...
    49.99 50.01
    50.00 50.00
    49.99 50.01
    50.00 50.00
    50.00 50.00
    50.00 50.00
    49.99 50.00
    49.99 50.00
    done
    Code:
    // Will warn data[] not used.
    void setup() {
      Serial.begin(9600);
      while (!Serial) {}
      Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
    }
    
    #define NUM_READS 100'000
    #define NUM_CYCLES 600'000'000/(1'000'000/50)
    int n = 0;
    void loop() {
      int32_t cycle[NUM_READS];
      uint16_t data[NUM_READS]; 
      uint32_t logTime = ARM_DWT_CYCCNT;
      for (int i = 0; i < NUM_READS; i++) {
        logTime += NUM_CYCLES; // log every 50 us.
        while ((int)(ARM_DWT_CYCCNT - logTime) < 0) {}
        cycle[i] = ARM_DWT_CYCCNT;
        data[i] = analogRead(A0);
      }
      float usMin = 999;
      float usMax = 0;
      for (int i = 0; i < NUM_READS-1; i++) {
        // F_CPU is 600 MHz.
        float us = (float)(cycle[i+1] - cycle[i])/600;
        if (us < usMin) {
          usMin = us;
        }
        if (us > usMax) {
          usMax = us;   
        }
      }
      Serial.print(usMin);
      Serial.print(' ');
      Serial.println(usMax);
      if (n++ > 50) {
        Serial.println("done");
        while(true) {}
      }
    }
    Went back and added Min and Max on the actual cycle counts. The mix is diff each time but always between 4 and 6:
    Code:
    T:\tCode\ADC\AnalogTimedRead\AnalogTimedRead.ino Jul  6 2021 14:49:35
    50.00 50.00		29998 30002		4
    49.99 50.01		29997 30003		6
    49.99 50.01		29997 30003		6
    49.99 50.01		29997 30003		6
    50.00 50.00		29998 30002		4
    49.99 50.01		29997 30003		6
    49.99 50.01		29997 30003		6
    Last edited by defragster; 07-06-2021 at 10:26 PM.

  12. #12
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    Quote Originally Posted by jonr View Post
    I'd collect 100K+samples to catch another ISR's effect on timing. Periods of disabled interrupts (common in teensy code) will also cause jitter.
    With the OP's specification of 50uSec sampling, I think there is a low probability that the SysTick interrupt will affect the ADC timing. Both the ADC timer and the Systick timer are generated from the same system clock. Since the 50USec ADC interval divides evenly into the 1000uSec Systick interval, if the SysTick interrupt occurs 5uSec after the ADC interrupt, it will ALWAYS occur 5uSec after the ADC interrupt and the SysTic will NEVER extend the ADC interrupt interval.

    If there is an interrupt handler that takes more than 30 to 40 uSec to execute, it might extend the ADC servicing interval enough to cause problems. If the ADC interval doesn't divide evenly into the SysTick interval, you could also see occasional changes in the interrupt response time. However, it is important to remember that variations in the Interrupt service response DO NOT change the ADC sampling interval--which is controlled by the hardware timer. Only when other interrupts delay the collection of the ADC data past the 50uSec sampling interval will you see jitter in the ADC data. Very few Teensy interrupts block other interrupts for more than a few microseconds, so ADC sampling jitter should be VERY rare. That's the beauty of having the ADC sampling controlled by directly by the hardware timer---without software intervention.

    If I'm going to collect ADC data for spectral analysis, the ADC Timer mode is my first choice. It offers the highest probability of sampling intervals accurate to a 0 to 1 clock cycles. Using other methods, as Bill Greiman demonstrated, allows jitter of up to 4 to 5 ARM clock cycles. The other major advantage of the ADC timer method is that the CPU is available to process the last sample with about 99% of the CPU bandwidth.

  13. #13
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,070
    Agreed, but many other interrupts aren't synchronized like this. Good point about the advantage of hardware timing.

    With this software timed code (derived from defragster code) I get jitter of 2.3 usec or .07 usec with interrupts disabled. Both strike me as terrible - a coding mistake?


    Code:
    #define NUM_READS 100'000
    #define NUM_CYCLES 600'000'000/(1'000'000/60)
    
    
    void loop() {
    
      volatile int data;
      uint32_t tmax = 0;
      uint32_t tmin = 1E9;
      uint32_t prevtime;
      uint32_t logTime = ARM_DWT_CYCCNT;
    
      noInterrupts();  // optional, try with and without
    
      for (int i = 0; i < NUM_READS; i++) {
    
        logTime += NUM_CYCLES; // log every 60 us.
        while ((int)(ARM_DWT_CYCCNT - logTime) < 0) {}
    
        data = analogRead(A0);
        uint32_t curtime = ARM_DWT_CYCCNT;
        uint32_t deltime = curtime - prevtime;
        prevtime = curtime;
    
        if (i > 0) {
          if (deltime > tmax)
            tmax = deltime;
          if (deltime < tmin)
            tmin = deltime;
        }
    
      } // for
    
      interrupts();
    
      Serial.print(tmin);
      Serial.print(" ");
      Serial.print(tmax);
      Serial.print(" jitter = ");
      Serial.println((tmax - tmin) / 600.0);
    
    }
    Last edited by jonr; 07-07-2021 at 04:41 AM.

  14. #14
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,074
    Indeed having ADC use a timer better than this tight loop polling for time to sample. It's DMA background reading it much better.

    Given the test and loop time where reading the ARM_CYCCNT alone takes 3 cycles - skipping by 4,5, or 6 cycles consistently suggest it isn't getting caught much by any _isr()'s as it was running 100K times and none longer seem in some hundreds of those seen.

    An update to SYSTICK for millis() does the inc++ after storing the ARM_CYCCNT to reproduce micros() timing offset from that point.

  15. #15
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,229
    Quote Originally Posted by jonr View Post
    Agreed, but many other interrupts aren't synchronized like this. Good point about the advantage of hardware timing.

    With this software timed code (derived from defragster code) I get jitter of 2.3 usec or .07 usec with interrupts disabled. Both strike me as terrible - a coding mistake?
    Sounds entirely as expected - ISRs taking a couple of microseconds aren't normally a problem, and 70ns jitter on a machine with a
    complex CPU with caching isn't a surprize, cache-misses are expensive. The only processor I know that can deliver precise software
    timing is the Propeller and that was designed explicitly for this (and doesn't use interrupts).

  16. #16
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,279
    The ADC conversion time alone is also quite variant.
    Code:
    66.5.4.5 Sample Time and Total Conversion Time
    The total conversion time depends upon the following:
    • the sample phase time (as determined by ADLSMP and ADSTS bits in ADC_CFG
    register),
    • the compare phase time (determined by MODE bits)
    • the frequency of the conversion clock (fADCK).
    • the MCU bus frequency (for Handshaking and selection of clock)
    From reference manual.
    The chapter has much more information, so I'd suggest to read it
    Last edited by Frank B; 07-07-2021 at 11:31 AM.

  17. #17
    Senior Member
    Join Date
    Nov 2012
    Posts
    318
    Yes timer triggered ADCs are best for jitter. Too bad there are not simple Teensy functions for the ADC to take a 16K point sample using DMA.

    I have been playing with the Raspberry Pi SDK for the RP2040 and it has a simple way to take such a sample with DMA. The ADC is capable of 500 ksps and has a register to space samples in free running mode. "If non-zero, CS_START_MANY will start conversions at regular intervals rather than back-to-back."

    But what are the requirements for jitter for this application?

    Analog Devices has lots of app notes on all the issues of sampling errors for discrete Fourier analysis. An example of 12-bit samples of a 260 kHz signal sampled at 4M samples per second with a sample size of 64K points required less than about 6 ns of jitter. For 20K samples per second with 16K points, jitter of 1 microsecond is probably good enough.

    Jitter in triggering the ADC may be a small issue. The ADC in a micro-controller is generally not great. Especially with raw input to the sample and hold and all the noise generated by the chip and power supply.

    You really need to know what the requirements are for this application then do a test of the entire system by recording high quality pure sine test signals and look at the results of the Fourier analysis to see what the real problems are.

  18. #18
    Member
    Join Date
    Nov 2020
    Location
    Czech Republic
    Posts
    49
    I made a few measurements and comparisons:

    ADC, 16384 samples, sampling 20000Hz, averaging 4, resolution 12 - used source code by mborgerson
    analogRead, 16384 samples, read every 50 micros, averaging 4, resolution 12, code below.

    Compare values - seems very similar, converted to Volts, Value = (BUFFER[x] * 3) / 4095;

    Click image for larger version. 

Name:	analog-adc.jpg 
Views:	37 
Size:	49.1 KB 
ID:	25208


    Execute time:

    ADC: 0,92548 sec
    analogRead: 0,835585
    Time should be 0,8192 second (16384 samples / 20000Hz)

    I measure time this way:
    Code:
    time1 = micros();
    READ_BUFFER();
    //GetADCData();
    time2 = micros();
    Serial.println(time1);
    Serial.println(time2);
    analogRead code:

    Code:
    void READ_BUFFER()
    {
       analogReadResolution(12);
       analogReadAveraging(4);
       period = 50;
       for(int i= 0;i < BUFFER_SIZE ; i++)
       {
        micros_0 = 0;  
        BUFFER[i] = analogRead(A1);
        vImag[i] = period *i;
        while(micros_0 <=  period) 
        { 
        } 
       }
    }
    In vImag[i] I have micros fro every sample.
    1.5663 - 0.00
    1.5648 - 50.00
    1.5619 - 100.00
    ...
    1.5678 - 819050.00
    1.5663 - 819100.00
    1.5692 - 819150.00

    Now it seems that analogRead is faster. Is it correct?
    What are main benefits of using ADC?

    Anyway, thanks all for suggestions.

    Michal

  19. #19
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    If you simply called my GetADC function without change and captured the time before and after the call, the interval is going to be higher by 101 milliseconds due to the delay() calls in the function. If you left in the call to display the data, that also added some time. If you correct for the delay() calls, you would get (0.92548 - 0.101) or 0.82358 seconds. The difference between that and 0.8192 seconds may be the time to display the data.

    Since you did not post complete code, I don't know what "micros_0" is. Please note the "Always post complete source code" at the top of the forum.

    The main benefit of using the ADC timer is the guaranteed low jitter and the fact that 99+ percent of the processor cycles are available to do other work while the data is being collected.
    Your sample code is not going to show any jitter because you are computing the time of sample, not actually measuring the time at which sampling occurs.

  20. #20
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,070
    Apparently some installed-by-default interrupt service routine takes up to 2.5 usec. That's about 2000 ISR instructions, which seems like a lot for a T4 that appears to be idle. Any idea what is causing this?

  21. #21
    Member
    Join Date
    Nov 2020
    Location
    Czech Republic
    Posts
    49
    Here is my cleaned code without any other calculations - this part is very important for futher calculations - to have pin A1 value (in adcbuffer) and sample time duration - expected every 50 micros (in vImag). I am not sure, how to measure time - I know, now I am wrong in my code.
    Or I have to measure between adc->adc0->startTimer(SAMPRATE) and adc->adc0->stopTimer();?

    Code:
    #include <ADC.h>
    #define BUFFER_SIZE 16384
    float vImag[BUFFER_SIZE]; // buffer for sample time
    const int readpin = A1;
    volatile uint32_t adcidx;
    float calc_time1;
    float calc_time2;
    float sample_time;
    float value;
    
    ADC *adc = new ADC(); // adc object;
    #define SAMPRATE 20000
    #define AVERAGING 16
    #define RESOLUTION 12
    #define ADCSAMPLES 16384
    uint16_t adcbuffer[ADCSAMPLES]; 
    
    void setup() {
      Serial.begin(115200);
      adc->adc0->setAveraging(AVERAGING); // set number of averages
      adc->adc0->setResolution(RESOLUTION); // set bits of resolution
      adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);  // set the conversion speed
      adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);      // set the sampling speed
    }
    
    void adc0_isr()  {
      uint16_t adc_val;
      // call readSingle() to clear the interrupt.
      // and store the ADC data
      adc_val = adc->adc0->readSingle();
      if (adcidx < ADCSAMPLES) {  // storage stops when enough samples collected
        calc_time1 = micros(); // ????start time
        adcbuffer[adcidx] = adc_val;
        calc_time2 = micros(); // ???? end time
        vImag[adcidx] = calc_time2 - calc_time1; // ???? sample time to buffer
        adcidx++;
      }
    }
    
    
    void GetADCData(void) {
      delay(100); // wait for usb serial to finish
      adc->adc0->stopTimer();
      adc->adc0->startSingleRead(readpin); // call this to setup everything before the Timer starts, differential is also possible
      delay(1);
      adc->adc0->readSingle();
      adcidx = 0;
      // now start the ADC collection timer
      adc->adc0->startTimer(SAMPRATE); //frequency in Hz
      adc->adc0->enableInterrupts(adc0_isr);
      do {
        // You could process a sample here with multiple buffers or copying to a new buffer
        // for processing while collection is in progress
      } while (adcidx < ADCSAMPLES);
      adc->adc0->stopTimer();
      // The data is in the global adcbuffer array.
    
      
    }
    
    
    void loop() {
     
       GetADCData();
       
       for(int i= 1;i < BUFFER_SIZE; i++)
       { 
         value = adcbuffer[i];
         value = (value * 3) / 4095;
         sample_time = vImag[i];
         Serial.print(value,8); // value from A1 in volts
         Serial.print(" - ");
         Serial.println(sample_time,16); // sample time
       }
    }

  22. #22
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    I ran my histogram analyzer that does 1,000,000 samples at 50uSec intervals for two cases: The first collects the ADC values for histogram analysis without SD card writes, the second collects the data and writes a dummy buffer to SD card. This allows me to evaluate the interrupt delays without and with SD writes. It also allows me to evaluate the change in ADC performance with SD Writes. For the T4.1, I expected and observed significant degradation of the ADC performance because it uses the 3.3V supply as the ADC reference. That supply sees some dips when SD card writes are in progress.
    Code:
    Histogram data without SD Card activity
    Timing Histogram Data in ARM Cycle Counts
    30000 999998
    Maximum at sample 0
    ADC Histogram Data in  Counts
     1824 2
     1825 246
     1826 6618
     1827 6338
     1828 10261
     1829 26632
     1830 743634
     1831 170613
     1832 13476
     1833 20008
     1834 2168
     1836 2
    
    Histogram data with SD Card active
      2.00 MBytes written to SD Card.
    Collection complete.
    Timing Histogram Data in ARM Cycle Counts
    29985 25
    29989 14
    29993 3
    29996 1
    29997 8
    29999 1
    30000 999881
    30001 1
    30003 22
    30007 3
    30008 3
    30011 10
    30012 11
    30015 15
    
    ADC Histogram Data in  Counts
     1822 1
     1823 2
     1824 6
     1825 15
     1826 2586
     1827 7728
     1828 7349
     1829 13118
     1830 242240
     1831 674076
     1832 22896
     1833 19150
     1834 10306
     1835 365
     1836 100
     1837 34
     1838 12
     1839 6
     1840 3
     1841 1
     1842 4

    Note that, without SD card activity, there are no extensions to the interrupt response time. The SYSTick interrupt is higher priority, but is synchronized with the ADC timer and doesn't interfere. The USB Serial port is also active, but also doesn't interfere--either because is is lower priority than the ADC timer, or because the activity occurs between the ADC interrupts.

    When the SD card is active, it does extend the ADC interrupt response time, but never for long enough to cause missed data. (Due to the algorithm used, there should be a shorter interval to match each extended response interval.) The SD card activity also adds significant noise to the ADC data. However, that's an issue that has been discussed in many other forum posts.

    Here is the source code for the histogram analyzer:

    Code:
    // Get timing histograms for ADC Timer with and without SD Card writing. 
    // Targeted to T4.1 at 600MHz  Compiled with ARDUINO 1.8.15 and TD 1.54
    // mborgerson 7/9/2021
    
    #include "SdFat.h"
    #include <TimeLib.h>
    
    SdFs sd;
    FsFile outfile;
    
    const int ledpin = 13;
    const int tmpin = 30;
    
    #define TMLOW digitalWriteFast(tmpin, LOW);
    #define TMHIGH digitalWriteFast(tmpin, HIGH);
    
    #define LEDON digitalWriteFast(ledpin, HIGH);
    #define LEDOFF digitalWriteFast(ledpin, LOW);
    
    #define SD_CONFIG SdioConfig(FIFO_SDIO)
    #define TMHISTOMAX 40960
    #define ADCMAX 4096  // for 12-bit samples
    
    #include <ADC.h>
    #include <ADC_util.h>
    
    const int readpin = A9; // My 2.5V precision reference
    
    ADC *adc = new ADC(); // adc object;
    
    #define SAMPRATE 20000
    #define AVERAGING 4
    #define RESOLUTION 12
    
    uint8_t sdcbuffer[32*1024]; // 32K SD card buffer
    
    uint32_t tm_histobuffer[TMHISTOMAX];  // for timing intervals up to 40.96mSec
    uint32_t adc_histobuffer[ADCMAX];
    
    const char compileTime [] = "ADC Timer histogram Compiled on " __DATE__ " " __TIME__;
    void setup() {
      delay(500);
      Serial.begin(115200);
      delay(1000);
      // activate ARM cycle counter
      ARM_DEMCR |= ARM_DEMCR_TRCENA; // Assure Cycle Counter active
      ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
      pinMode(tmpin, OUTPUT);
      pinMode(ledpin, OUTPUT);
      pinMode(readpin, INPUT_DISABLE);
      delay(2500);
      Serial.print("\n\n");
      Serial.println(compileTime);
      setSyncProvider(getTeensy3Time);
    
      if (timeStatus() != timeSet) {
        Serial.println("Unable to sync with the RTC");
      } else {
        Serial.println("RTC has set the system time");
        Serial.print("Initializing SD card...");
      }
      if (!sd.begin(SD_CONFIG)) {
        Serial.println("\nSD initialization failed.\n");
      } 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);
    
      adc->adc0->setAveraging(AVERAGING); // set number of averages
      adc->adc0->setResolution(RESOLUTION); // 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
    
    }
    
    elapsedMillis blocktimer;
    volatile uint32_t byteswritten;
    volatile uint32_t totalsamples;
    volatile uint32_t lastcycles;
    
    void loop() {
      char ch;
      if (Serial.available()) {
        ch = Serial.read();
        if (ch == 'n') GetHistoData(false);
        if (ch == 'f') GetHistoData(true);
        if (ch == 'a') ShowADCHisto();
        if (ch == 'd') sd.ls(LS_SIZE | LS_DATE | LS_R);
      }
    
    }
    
    /*****************************************************************************
       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());
    }
    volatile uint16_t maxidx;
    volatile uint32_t tmax;
    
    // This ISR runs in about 100nSec on T4.1 at 600MHz.
    // It doesn't actually save the data, but only updates
    // timing and adc histogram values
    void adc0_isr()  {
      uint32_t tmdiff, thiscycles;
      uint16_t adc_val;
      LEDON;
    
      thiscycles =  ARM_DWT_CYCCNT;
      tmdiff = thiscycles - lastcycles;
      if (tmdiff >= TMHISTOMAX) tmdiff = TMHISTOMAX - 1;
    
      lastcycles = thiscycles;
      if(tmdiff > tmax) {
        tmax = tmdiff;
        maxidx = totalsamples;
      }
    
      totalsamples++;
      // signal the main loop to write a 16KB block every 8192 samples
       // call readSingle() to clear the interrupt. 
       // and update the ADC Histogram data
       adc_val = adc->adc0->readSingle();
    
       if (adc_val >= 4095) adc_val = 4095;
       // 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
      LEDOFF;
    }
    
    
    // get timing  and ADC histogram data, optionally writing to SD card while
    // collecting.
    void GetHistoData(bool writesdc) {
      bool sdfileopen = false;
      byteswritten = 0;
      totalsamples = 0;
      uint32_t numtowrite;
      uint16_t seccount = 0;
      elapsedMillis dmillis;
    
      memset(adc_histobuffer, 0, sizeof(adc_histobuffer));  // clear adc histogram counts
      memset(tm_histobuffer, 0, sizeof(tm_histobuffer));  // clear  timing histogram counts
    
      if (writesdc) {
        sdfileopen = outfile.open("dummyfile.dxx",  O_RDWR | O_CREAT | O_TRUNC);
      }
      ShowSetup(writesdc);
      delay(10); // wait for usb serial to finish
    
      adc->adc0->stopTimer();
      adc->adc0->startSingleRead(readpin); // call this to setup everything before the Timer starts, differential is also possible
      maxidx = 0;
      tmax = 0;
      delay(1);
      adc->adc0->readSingle();
    
      dmillis = 0;
    
      // now start the ADC collection timer
      adc->adc0->startTimer(SAMPRATE); //frequency in Hz 
      
      lastcycles =  ARM_DWT_CYCCNT;
      adc->adc0->enableInterrupts(adc0_isr);
      // Write out the elapsed seconds during collection
      // The USB Serial writes occasionally block ADC interrupts
      // for a microsecond or so.
      do {
        if (dmillis > 1000) {
          seccount++;
          dmillis = 0;
          Serial.printf("%4u: %lu\n", seccount, totalsamples);
        }
        // write an appropriate number of bytes from SD Buffer
        if(byteswritten < (totalsamples *2)){
          if (writesdc) {
            numtowrite = (totalsamples * 2) - byteswritten;
            if (sdfileopen)outfile.write(sdcbuffer, numtowrite); // Write bytes when needed
            byteswritten += numtowrite;
          }
        } // end of if(byteswritten < (totalsamples *2)
        
      } while (totalsamples < 1000000);  // stop after 1 million samples
    
      adc->adc0->stopTimer(); 
      if (writesdc) {
        if (sdfileopen)outfile.close();
        Serial.printf("%6.2f MBytes written to SD Card.\n", (float)byteswritten / (1000000));
      }
      Serial.println("Collection complete.");
      ShowTmHisto();
    }
    
    void ShowSetup(bool writesdc) {
      Serial.printf("Collecting Data  ");
      if (writesdc) {
        Serial.printf("with SD Card Writes \n");
      } else {
        Serial.println("with no SD Card writes\n");
      }
    }
    
    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 %lu\n", i, tm_histobuffer[i]);
        }
      }
      Serial.println();
      Serial.printf("Maximum at sample %lu\n",maxidx);
    }
    
    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 %lu\n", i, adc_histobuffer[i]);
        }
      }
      Serial.println();
    }

  23. #23
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    349
    Quote Originally Posted by frohr View Post
    Here is my cleaned code without any other calculations - this part is very important for futher calculations - to have pin A1 value (in adcbuffer) and sample time duration - expected every 50 micros (in vImag). I am not sure, how to measure time - I know, now I am wrong in my code.
    Or I have to measure between adc->adc0->startTimer(SAMPRATE) and adc->adc0->stopTimer();?

    Code:
    
    void adc0_isr()  {
      uint16_t adc_val;
      // call readSingle() to clear the interrupt.
      // and store the ADC data
      adc_val = adc->adc0->readSingle();
      if (adcidx < ADCSAMPLES) {  // storage stops when enough samples collected
        calc_time1 = micros(); // ????start time
        adcbuffer[adcidx] = adc_val;
        calc_time2 = micros(); // ???? end time
        vImag[adcidx] = calc_time2 - calc_time1; // ???? sample time to buffer
        adcidx++;
      }
    }
    Your timing code will probably return 0 for all values in vImag;


    You could try something like this:


    Code:
    void adc0_isr()  {
      uint16_t adc_val;
      // call readSingle() to clear the interrupt.
      // and store the ADC data
      adc_val = adc->adc0->readSingle();
      if (adcidx < ADCSAMPLES) {  // storage stops when enough samples collected
        adcbuffer[adcidx] = adc_val;
        vImag[adcidx] = adcidx *(1000000/SAMPRATE); // ???? sample time to buffer
        adcidx++;
      }
    }
    For a software loop, that calculation of the sample time would not be valid. However, for the timer-controlled sampling it works. The histogram tests have shown that no data will be missed due to other interrupts.

    You could also skip that timing generation in the interrupt handler and calculate the timing array before or after collecting the data---and you would only need to do it once in the setup function.

    To get a verification, you could collect the start and end times in GetADCData:
    Code:
    uint32_t starttime, endtime;
    void GetADCData(void) {
      delay(100); // wait for usb serial to finish
      adc->adc0->stopTimer();
      adc->adc0->startSingleRead(readpin); // call this to setup everything before the Timer starts, differential is also possible
      delay(1);
      adc->adc0->readSingle();
      adcidx = 0;
      starttime = micros();
      // now start the ADC collection timer
      adc->adc0->startTimer(SAMPRATE); //frequency in Hz
      adc->adc0->enableInterrupts(adc0_isr);
      do {
        // You could process a sample here with multiple buffers or copying to a new buffer
        // for processing while collection is in progress
      } while (adcidx < ADCSAMPLES);
      adc->adc0->stopTimer();
      // The data is in the global adcbuffer array.
      endtime = micros();
      Serial.printf("Collection time was %lu microseconds.\n", endtime-starttime);
      
    }

Posting Permissions

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