Teensy 4.1 ADC and Timer, Accurate and constant sample periods

homecockpits

New member
Hi everyone,

I am quite new to the Teensy environment and currently developing a little test program to verify my ideas. The program is supposed to do an ADC conversion from one pin every 40µs, and I want to save the reading for test purposes. I currently do not have a MicroSD card on hand so I am printing out the values of micros() and the ADC value so that I can analyse them in MATLAB later on. I have found out that my readings are not consistently spaced in an interval of 40µs, however it fluctuates up to 83ms. The 40µs sample rate is crucial for the reliable function of the project so I would be happy about some guidance on what I could do in order to increase reliablility. I have attached a plot of a sample measurement taken. I have gotten the data via CoolTerm, exported as .txt file and then used Matlab in order to create a plot from the data. I know this is not the most streamlined process but could this be the only reason my program does not deliver constant sample frequencies? Would this be better if saved onto an SD card?
I have already thought that the larger time span could come from a change in the signal, however multiple periods of the signal are displayed find around where the left data tip is set within the attached image. Further towards the end multiple periods are completely cut out or only displayed partly.
Here is my code for reference, also always happy about some comments on it!
C++:
// -----------------------------------------------------------------------------
// Includes
// -----------------------------------------------------------------------------
#include "TeensyTimerInterrupt.h"
#include <ADC.h>
// -----------------------------------------------------------------------------
// Settings
// -----------------------------------------------------------------------------
//Timer
#define ANTENNA_PIN 24  // Analog input pin
#define ANTENNA_SAMPLE_PERIOD 20
TeensyTimer AntennaSample(TEENSY_TIMER_1);
//ADC
#define ACS ADC_CONVERSION_SPEED::VERY_HIGH_SPEED
#define ASS ADC_SAMPLING_SPEED::VERY_HIGH_SPEED
#define ARE 8
ADC *adc = new ADC();

// -----------------------------------------------------------------------------
// setup() function
// -----------------------------------------------------------------------------
void setup()
{
    //Setup Serial
    Serial.begin(6000000);
    //Setup ADC
    adc->adc0->setAveraging(1);
    adc->adc0->setResolution(ARE);
    adc->adc0->setConversionSpeed(ACS);
    adc->adc0->setSamplingSpeed(ASS);
    adc->adc0->startContinuous(ANTENNA_PIN);
    //Setup Timer
    if (AntennaSample.attachInterruptInterval(ANTENNA_SAMPLE_PERIOD, AntennaSampleISR))
    {
        Serial.print(F("Starting AntennaSample OK, millis() = "));
        Serial.println(millis());
    }
    else
    {
        Serial.println(F("Can't start AntennaSample!"));
    }
}

// -----------------------------------------------------------------------------
// loop() function
//------------------------------------------------------------------------------
void loop()
{
    static unsigned long lastTimer0 = 0;
    static bool timer0Stopped = false;
    static unsigned long currTime = 0;
    static unsigned long startTime =0;

    currTime = micros();

    if (currTime - lastTimer0 > ANTENNA_SAMPLE_PERIOD)
    {
        lastTimer0 = currTime;

        if (timer0Stopped)
        {
            AntennaSample.restartTimer();
            startTime = currTime;
        }
        else
        {
            unsigned int value = (uint16_t)adc->adc0->analogReadContinuous();
            Serial.printf("%d,",currTime);
            Serial.println(value);
            AntennaSample.stopTimer();
        }
        timer0Stopped = !timer0Stopped;
    }
}

// -----------------------------------------------------------------------------
// Function declarations
// -----------------------------------------------------------------------------
/**
 * @brief ISR for Charge Antenna sample rate, called every ANTENNA_SAMPLE_PERIOD
 */
void AntennaSampleISR()
{
    static bool toggle0 = false;
    static bool started = false;

    if (!started)
    {
        started = true;
        pinMode(LED_BUILTIN, OUTPUT);
    }

#if (TEENSY_TIMER_INTERRUPT_DEBUG > 0)
    Serial.println("AntennaSample: micros() = " + String(micros()));
#endif
    toggle0 = !toggle0;
}
 

Attachments

  • sampleTime.png
    sampleTime.png
    105.3 KB · Views: 84
I'm not 100% sure how the TeensyTimer works, and I think your program is more complicated than it needs to be. Since you're just getting started, a few recommendations are (1) use IntervalTimer instead of TeensyTimer, (2) don't stop and start the timer, just let it run and do a sample every 40 us, (3) start with a simple analogRead() before you get into using the ADC library, (4) don't call Serial.print() from your ISR.
 
Oh wow, thank you! I have actually just found this program in another thread: It does basically what I intend on doing!
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.
Does anyone know whether it is possible to use the Automatic compare function of the ADC in combination with the timer? I only want to measure all values above an ADC value of 15 with a maximum sample time of 40us.
 
Oh wow, thank you! I have actually just found this program in another thread: It does basically what I intend on doing!

Does anyone know whether it is possible to use the Automatic compare function of the ADC in combination with the timer? I only want to measure all values above an ADC value of 15 with a maximum sample time of 40us.
You absolutely need to use the ADC timer collection mode if you want consistent sampling intervals. In that mode the ADC collection is started by the hardware timer. The time at which the interrupt stores the data can vary if other interrupts of higher priority delay the ADC timer handler. That is not an issue as long as the interrupt handler can finish before the next ADC timer interrupt.
Matching the ADC sampling and conversion speeds to your desired 40 uSec interval may take some experimentation to get the best results.

I don't see any particular advantage to using the comparator when a simple

if(adcval < 15) return;

in the interrupt handler is all that is needed.
 
Back
Top