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

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

  1. #1
    Junior Member
    Join Date
    May 2020
    Posts
    14

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

    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");
    
    
    }

  2. #2
    Junior Member
    Join Date
    May 2020
    Posts
    14
    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;
    }

  3. #3
    Senior Member+
    Join Date
    Jul 2013
    Posts
    292
    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.

  4. #4
    Senior Member+
    Join Date
    Jul 2013
    Posts
    292
    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

Posting Permissions

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