Teensy 4 How to increase speed of my sample readings using both ADCs and 4 Mic inputs

Status
Not open for further replies.

Tootsie

Member
I am working on a project trying to sample 4 analog mics as fast as possible using a Teensy 4.

I was wondering if there was anyway that I could increase the samples/second I am getting. I am using Pedvide's ADC library and modified his analogReadIntervalTimer sketch to incorporate ADC1 as well. So i have 2 timers, the first calls two startSingleRead on pins A10 and A12 (belonging to different ADCs) which triggers an interrupt to record the value in a buffer and increase buffer count.


After a delay, (letting the conversion take place ~.8us) I call the second timer which calls startSingleRead on pins A11 and A13 which, once again, triggers an interrupt to record the value in a buffer and increase buffer count.

I am getting around 340KSPS (total loop of around 3us) but would like to have more if possible. I am running with 1 sample and 8 bit conversion on the fastest settings.

I know that continuous reading is faster than the single reads, can I incorporate that to speed this up? I tried to overclock the Teensy 4, but it slowed it down (might have something to do with the way the library is set up?). Open to any suggestions.

Code:
#include <ADC.h>
// and IntervalTimer
#include <IntervalTimer.h>

//const int ledPin = LED_BUILTIN;

const int delay_time = 800; //ns
const int period = 3; // us
const int readPin0 = A10;
const int readPin1 = A11;
const int readPin2 = A12;
const int readPin3 = A13;
const int readPeriod = 1000000; // us

ADC *adc = new ADC(); // adc object

IntervalTimer timer0, timer1; // timers

#define BUFFER_SIZE 500

uint16_t buffer_0[BUFFER_SIZE];
uint16_t buffer_0_count = 0;
uint32_t delta_time_0 = 0;
uint16_t buffer_1[BUFFER_SIZE];
uint16_t buffer_1_count = 0;
uint32_t delta_time_1 = 0;
uint16_t buffer_2[BUFFER_SIZE];
uint16_t buffer_2_count = 0;
uint32_t delta_time_2 = 0;
uint16_t buffer_3[BUFFER_SIZE];
uint16_t buffer_3_count = 0;
uint32_t delta_time_3 = 0;

elapsedMicros timed_read_elapsed;

int startTimerValue0 = 0, startTimerValue1 = 0;

void setup() {


    pinMode(readPin0, INPUT);
    pinMode(readPin1, INPUT);
    pinMode(readPin2, INPUT);
    pinMode(readPin3, INPUT);

    Serial.begin(9600);

    delay(1000);
    timed_read_elapsed = 0;
    ///// ADC0 ////
    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::VERY_HIGH_SPEED); // change the sampling speed
    ///// ADC1 ////
    adc->adc1->setAveraging(1); // set number of averages
    adc->adc1->setResolution(8); // set bits of resolution
    adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
    adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed


    Serial.println("Starting Timers");

    // start the timers, if it's not possible, startTimerValuex will be false
    startTimerValue0 = timer0.begin(timer0_callback, period);
    //startTimerValue2 = timer2.begin(timer2_callback, period2);
    // wait enough time for the first timer conversion to finish (depends on resolution and averaging),
    // with 16 averages, 12 bits, and ADC_MED_SPEED in both sampling and conversion speeds it takes about 36 us.
    delayNanoseconds(delay_time); // if we wait less than 36us the timer1 will interrupt the conversion
    // initiated by timer0. The adc_isr will restart the timer0's measurement.

    // You can check with an oscilloscope:
    // Pin 14 corresponds to the timer0 initiating a measurement
    // Pin 15 the same for the timer1
    // Pin 16 is the adc_isr when there's a new measurement on readpin0
    // Pin 17 is the adc_isr when there's a new measurement on readpin1

    // Timer0 starts a comversion and 25 us later timer1 starts a new one, "pausing" the first, about 36 us later timer1's conversion
    // is done, and timer0's is restarted, 36 us later timer0's conversion finishes. About 14 us later timer0 starts a new conversion again.
    // (times don't add up to 120 us because the timer_callbacks and adc_isr take time to execute, about 2.5 us and 1 us, respectively)
    // so in the worst case timer0 gets a new value in about twice as long as it would take alone.
    // if you change the periods, make sure you don't go into a loop, with the timers always interrupting each other
    startTimerValue1 = timer1.begin(timer1_callback, period);
    //startTimerValue3 = timer3.begin(timer3_callback, period3);

    adc->adc0->enableInterrupts(adc0_isr);
    adc->adc1->enableInterrupts(adc1_isr);

    Serial.println("Timers started");

    delay(500);
}

int value = 0;
char c=0;

void loop() {
  delayMicroseconds(readPeriod);

    if(startTimerValue0==false) {
            Serial.println("Timer0 setup failed");
    }
    if(startTimerValue1==false) {
            Serial.println("Timer1 setup failed");
    }


    // See if we have a timed read test that finished.
    if (delta_time_0)
    {
      Serial.println("timer0");
      Serial.println(buffer_1_count);
      printTimedADCInfo(ADC_0, buffer_0, delta_time_0);
    }
    if (delta_time_1)
    {
      Serial.println("timer1");
      Serial.println(buffer_0_count);
      printTimedADCInfo(ADC_0, buffer_1, delta_time_1);
    }
    if (delta_time_2)
    {
      Serial.println("timer2");
      Serial.println(buffer_3_count);
      printTimedADCInfo(ADC_1, buffer_2, delta_time_2);
    }
    if (delta_time_3)
    {
      Serial.println("timer3");
      Serial.println(buffer_2_count);
      printTimedADCInfo(ADC_1, buffer_3, delta_time_3);
    }
    
}

void printTimedADCInfo(uint8_t adc_num, uint16_t *buffer, uint32_t &delta_time) {
  uint32_t min_value = 0xffff;
  uint32_t max_value = 0;
  uint32_t sum = 0;
  for (int i = 0; i < BUFFER_SIZE; i++) {
    if (buffer[i] < min_value) min_value = buffer[i];
    if (buffer[i] > max_value) max_value = buffer[i];
    sum += buffer[i];
  }
  float average_value = (float)sum / BUFFER_SIZE; // get an average...
  float sum_delta_sq = 0;
  for (int i = 0; i < BUFFER_SIZE; i++) {
    int delta_from_center = (int)buffer[i] - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;
  }
  int rms = sqrt(sum_delta_sq / BUFFER_SIZE);
  Serial.printf("ADC:%d delta time:%d freq:%d - min:%d max:%d avg:%d rms:%d\n", adc_num,
                delta_time, (1000000 * BUFFER_SIZE) / delta_time,
                min_value, max_value, (int)average_value, rms);

  delta_time = 0;

}

// This function will be called with the desired frequency
// start the measurement
// in my low-res oscilloscope this function seems to take 1.5-2 us.
void timer0_callback(void) {

    adc->adc0->startSingleRead(readPin0); // also: startSingleDifferential, analogSynchronizedRead, analogSynchronizedReadDifferential
    adc->adc1->startSingleRead(readPin2);
    
}

// This function will be called with the desired frequency
// start the measurement
void timer1_callback(void) {
//
    adc->adc0->startSingleRead(readPin1);
    adc->adc1->startSingleRead(readPin3);

}


// when the measurement finishes, this will be called
// first: see which pin finished and then save the measurement into the correct buffer
void adc0_isr() {

    uint8_t pin = ADC::sc1a2channelADC0[ADC1_HC0&0x1f]; // the bits 0-4 of ADC0_SC1A have the channel

    // add value to correct buffer
    if(pin==readPin0) {
        //digitalWriteFast(ledPin+3, HIGH);
        uint16_t adc_val = adc->adc0->readSingle();
        if (buffer_0_count < BUFFER_SIZE) {      
          buffer_0[buffer_0_count++] = adc_val;
          if (buffer_0_count == BUFFER_SIZE) delta_time_0 = timed_read_elapsed;
        }
        //digitalWriteFast(ledPin+3, LOW);
        
    } else if(pin==readPin1) {
        //digitalWriteFast(ledPin+4, HIGH);
        uint16_t adc_val = adc->adc0->readSingle();
        if (buffer_1_count < BUFFER_SIZE) {
          buffer_1[buffer_1_count++] = adc_val;
          if (buffer_1_count == BUFFER_SIZE) delta_time_1 = timed_read_elapsed;
        }
        //digitalWriteFast(ledPin+4, LOW);
    } else { // clear interrupt anyway
        adc->readSingle();
    }

    // restore ADC config if it was in use before being interrupted by the analog timer
    if (adc->adc0->adcWasInUse) {
        // restore ADC config, and restart conversion
        adc->adc0->loadConfig(&adc->adc0->adc_config);
        // avoid a conversion started by this isr to repeat itself
        adc->adc0->adcWasInUse = false;
    }


    //digitalWriteFast(ledPin+2, !digitalReadFast(ledPin+2));

        asm("DSB");


}
void adc1_isr() {
  
    uint8_t pin = ADC::sc1a2channelADC1[(ADC1_HC0+2)&0x1f]; // the bits 0-4 of ADC0_SC1A have the channel

    // add value to correct buffer
    if(pin==readPin2) {
        //digitalWriteFast(ledPin+7, HIGH);
        uint16_t adc_val = adc->adc1->readSingle();
        if (buffer_2_count < BUFFER_SIZE) {
          buffer_2[buffer_2_count++] = adc_val;
          if (buffer_2_count == BUFFER_SIZE) delta_time_2 = timed_read_elapsed;
        }
        //digitalWriteFast(ledPin+7, LOW);
        
    } else if(pin==readPin3) {
        //digitalWriteFast(ledPin+8, HIGH);
        uint16_t adc_val = adc->adc1->readSingle();
        if (buffer_3_count < BUFFER_SIZE) {
          buffer_3[buffer_3_count++] = adc_val;
          if (buffer_3_count == BUFFER_SIZE) delta_time_3 = timed_read_elapsed;
        }
        //digitalWriteFast(ledPin+8, LOW);
    } else { // clear interrupt anyway
        adc->readSingle();
    }

    // restore ADC config if it was in use before being interrupted by the analog timer
    if (adc->adc0->adcWasInUse) {
        // restore ADC config, and restart conversion
        adc->adc0->loadConfig(&adc->adc0->adc_config);
        // avoid a conversion started by this isr to repeat itself
        adc->adc0->adcWasInUse = false;
    }
    if (adc->adc1->adcWasInUse) {
        // restore ADC config, and restart conversion
        adc->adc1->loadConfig(&adc->adc1->adc_config);
        // avoid a conversion started by this isr to repeat itself
        adc->adc1->adcWasInUse = false;
    }


        asm("DSB");


}
 
Update: I used analogReadContinuous() for 2 channels and got rid of the interrupts. I now have about 880KSPS. Is there anything else I can do to the code to help?

Code:
#include <ADC.h>
#include <ADC_util.h>
#include <ADC_Module.h>

const int readPin0 = A10;
const int readPin1 = A11;
const int readPin2 = A12;
const int readPin3 = A13;

ADC *adc = new ADC(); // adc object

#define BUFFER_SIZE 10000

uint32_t buffer_0[BUFFER_SIZE];
uint32_t buffer_0_count = 0;
uint32_t delta_time_0 = 0;
uint32_t buffer_1[BUFFER_SIZE];
uint32_t buffer_1_count = 0;
uint32_t delta_time_1 = 0;
uint32_t buffer_2[BUFFER_SIZE];
uint32_t buffer_2_count = 0;
uint32_t delta_time_2 = 0;
uint32_t buffer_3[BUFFER_SIZE];
uint32_t buffer_3_count = 0;
uint32_t delta_time_3 = 0;

elapsedMicros timed_read_elapsed;

void setup() {

    pinMode(readPin0, INPUT);
    pinMode(readPin1, INPUT);
    pinMode(readPin2, INPUT);
    pinMode(readPin3, INPUT);


    Serial.begin(9600);
    delay(500);

    ///// ADC0 ////
    //adc->adc0->setReference(ADC_REFERENCE::REF_3V3); // change all 3.3 to 1.2 if you change the reference to 1V2

    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::VERY_HIGH_SPEED); // change the sampling speed


    ////// ADC1 /////
    //adc->adc1->setReference(ADC_REFERENCE::REF_3V3);

    adc->adc1->setAveraging(1); // set number of averages
    adc->adc1->setResolution(8); // set bits of resolution
    adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
    adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed

    adc->adc0->startContinuous(readPin0);
    adc->adc1->startContinuous(readPin2);

    delay(500);
    timed_read_elapsed = 0;
}


void loop() {

    if (delta_time_0)
    {
      printTimedADCInfo(ADC_0, buffer_0, delta_time_0);
    }
    if (delta_time_1)
    {
      printTimedADCInfo(ADC_0, buffer_1, delta_time_1);
    }
    if (delta_time_2)
    {
      printTimedADCInfo(ADC_1, buffer_2, delta_time_2);
    }
    if (delta_time_3)
    {
      printTimedADCInfo(ADC_1, buffer_3, delta_time_3);
    }

    
        uint16_t adc_val0 = adc->adc0->analogReadContinuous();
        if (buffer_0_count < BUFFER_SIZE) {      
          buffer_0[buffer_0_count++] = adc_val0;
          if (buffer_0_count == BUFFER_SIZE) delta_time_0 = timed_read_elapsed;
        }
        uint16_t adc_val1 = adc->adc1->analogReadContinuous();
        if (buffer_1_count < BUFFER_SIZE) {      
          buffer_1[buffer_1_count++] = adc_val1;
          if (buffer_1_count == BUFFER_SIZE) delta_time_1 = timed_read_elapsed;
        }
        adc->adc0->startSingleRead(readPin1);
        uint16_t adc_val2 = adc->adc0->readSingle();
        if (buffer_2_count < BUFFER_SIZE) {      
          buffer_2[buffer_2_count++] = adc_val2;
          if (buffer_2_count == BUFFER_SIZE) delta_time_2 = timed_read_elapsed;
        }
        adc->adc1->startSingleRead(readPin3);
        uint16_t adc_val3 = adc->adc1->readSingle();
        if (buffer_3_count < BUFFER_SIZE) {      
          buffer_3[buffer_3_count++] = adc_val3;
          if (buffer_3_count == BUFFER_SIZE) delta_time_3 = timed_read_elapsed;
        }
        adc->adc0->startContinuous(readPin0);
        adc->adc1->startContinuous(readPin2);


}

void printTimedADCInfo(uint8_t adc_num, uint32_t *buffer, uint32_t &delta_time) {
  uint32_t min_value = 0xffff;
  uint32_t max_value = 0;
  uint32_t sum = 0;
  for (int i = 0; i < BUFFER_SIZE; i++) {
    if (buffer[i] < min_value) min_value = buffer[i];
    if (buffer[i] > max_value) max_value = buffer[i];
    sum += buffer[i];
  }
  float average_value = (float)sum / BUFFER_SIZE; // get an average...
  float sum_delta_sq = 0;
  for (int i = 0; i < BUFFER_SIZE; i++) {
    int delta_from_center = (int)buffer[i] - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;
  }
  int rms = sqrt(sum_delta_sq / BUFFER_SIZE);
  Serial.printf("ADC:%d delta time:%dus sample time:%dns freq:%dhz - min:%d max:%d avg:%d rms:%d\n", adc_num,
                delta_time, round(1000*delta_time/BUFFER_SIZE), round((1000000.0 * BUFFER_SIZE) / delta_time),
                min_value, max_value, (int)average_value, rms);

  delta_time = 0;
}
 
You can look into using DMA to copy each conversion into a buffer and when it's full switch to a different pin. I think the library has DMA examples, but none with linking pins.
 
However the minimum conversion time of a T4 is probably around 0.8 us, so you can maybe hope to get closer to 1000ksps, but not much more
 
Status
Not open for further replies.
Back
Top