ADC library, with support for Teensy 4, 3.x, and LC

Teensy's ADC is not slower than Arduino Uno's. It's much, much faster.

You've misundstood the difference between clocks and sample rates.

The ADC clock is NOT the actual rate it converts analog to digital. Not on Uno, and not on Teensy.

That page talks about running the AVR ADC with a 1.5 MHz clock. The Uno ADC is rated for 0.2 MHz clock.

Teensy 3.1 can use up to 18 MHz clock. But the ADC does NOT convert analog to digital in 1 clock cycle. Many clock cycles are needed to perform the conversion.

Teensy 3.1's ADC is dramatically faster than Arduino Uno's ADC. Similar techniques can be used on either. But the details are complex, as explained on that instructibles page, and in this thread. You can't simply read a complex discussion of technical details and find the largest number on the page and wish for that number to be the actual performance of the ADC. Well, you can do that, and it seems you are doing that, but it is wrong-headed wishful thinking. The ADC requires many clock cycles to convert analog to digital, and many complex details matter. You can't simple have the hardware actually work faster than it does by ignoring important technical details and focusing only on its internal clock rate.
 
Last edited:
Regarding my last post, can anyone explain to me why the Teensy's ADC is slower than the UNO?

The Atmega328 ADC needs 13 clock cycles for a conversion. Even at the (unsupported) highest possible ADC clock of 4MHz that only means about 300Ksps. The highest clock speed that is still relatively close to spec is 1MHz so that's about 77Ksps.
 
The Atmega328 ADC needs 13 clock cycles for a conversion. Even at the (unsupported) highest possible ADC clock of 4MHz that only means about 300Ksps. The highest clock speed that is still relatively close to spec is 1MHz so that's about 77Ksps.

Thank you!! :)
 
But the details are complex, as explained on that instructibles page, and in this thread. You can't simply read a complex discussion of technical details and find the largest number on the page and wish for that number to be the actual performance of the ADC.

Oh I can. Actually I just did. Because you already know the answer and I don't have to go trough pages of details. That's what the swarm intelligence of the internet and especially forums like these are for. :) So thank you for your profound answer! I understand now and you helped me a lot!
 
Thanks for this awesome library, Pedvide. It is a huge help.

One thing I noticed is that when a user selects a PGA gain of 1, you have it disable the PGA. Numerically this does seem right - why use a programmable gain amplifier set to gain of 1?

There is one reason why I still would want to use the amp even if set at one : as a signal impediance converter! Per the datasheet, (http://cache.freescale.com/files/32bit/doc/data_sheet/K20P81M72SF1.pdf Page 44) when you use the PGA, you now have the rather great luxury of somewhere between 32K and 128K of input impedance and a nice strong driver for the ADC. Compare this to the paltry 5k or less requirement for the unbuffered ADC (IBID, Page 40).

If I leave the PGA connected for all gains (1-64) I can avoid the use of an input op-amp when I'm looking at weak signals.

I have tweaked my version of the enablePGA( ) function accordingly. Is there any reason to not do this?

Code:
void ADC_Module::enablePGA(uint8_t gain) {
    #if defined(__MK20DX256__)

    if (calibrating) wait_for_cal();

    uint8_t setting;
    if(gain <= 1) {
      setting = 0;
    } else if(gain<=2){
        setting = 1;
    } else if(gain<=4){
        setting = 2;
    } else if(gain<=8){
        setting = 3;
    } else if(gain<=16){
        setting = 4;
    } else if(gain<=32){
        setting = 5;
    } else { // 64
        setting = 6;
    }

    *ADC_PGA = ADC_PGA_PGAEN | ADC_PGA_PGAG(setting);
    pga_value=1<<setting;
    #endif
}
 
Hey Pedvide, thanks for the library. I am having a strange problem, I'm using both ADCs to continuously sample multiple pins using interrupt handlers to switch inputs and store the results in an array. My problem being that when I try to read A11 with ADC_1 it returns ADC_ERROR_VALUE (A10, A12 and A13 work). I was under the impression that A10-A13 should be accessible to ADC_1 (and ADC_0, not that I am using with these pins).

I'm also wondering if its written down somewhere which inputs can be linked to which ADC (not just which ones are accessible to both).
 
The images in the first post show which pins can be accessed by which ADC.
For example, pin 17 (A3) says ADC0_SE9/ADC1_SE9, that means both ADCs can access it (SE means single ended and the number 9 is not useful for the end user).
For the differential pins it gets a bit more complicated because they have several functions.
  • Single-ended: PGAx_DM can't be read in this mode, as PGA is a pure differential function.
    ADC1_DM3 can't be read in this mode either.
    ADC0_DM3 also can't be read in single-ended mode even though the manual says it can, it's a hardware bug that Paul also found when writing his analog code. That means that pin A13 can only be read in single-ended mode by the ADC1 and pin A11 only works in this mode by ADC0.
    Pins A10 and A12 can be read in single-ended mode by either ADC.
  • Differential: The differential pins are A10-A11 and A12-A13, both pairs are accessible by both ADCs.
  • PGA: The same differential pair can be used with PGA enabled.
 
Looking at the sample from msg#101 and wiring my analog mic into "const int readPin = 17;" - pin #17. I see 3.300v on value and an FFT unrelated to my mic and seems to be full of random values (same results seen changing to an open pin).

I just pulled the ADC from GIT and didn't see an FFT sample. Is the 11/5/14 coding on m#101 still valid? Ties in with Audio lib for FFT in that example:"analogAudioIn(15);"

Using an Adafruit example I got good values from my mic, my modifying the Audio/Sinewave example I end up with the lower FFT buckets growing to a fixed value when 'otherwise quiet' after a short time of seeming good data.

Teensy3.1/TeensyD1.21_release/Win7

Pin: 17 = 3.3000000v FFT: - 0.02 - 0.01 0.01 0.02 0.01 0.01 - 0.03 - 0.01 - 0.03 0.02 - - - - 0.01 - 0.03 0.01 - 0.02 0.01 0.02 0.01 0.02 - 0.02 - 0.03 0.02 0.01 0.03 0.01 0.01 0.01 -
Pin: 17 = 3.3000000v FFT: 0.02 - 0.02 0.02 0.02 0.01 0.01 0.02 0.03 0.02 0.01 0.02 0.02 0.02 0.03 - 0.01 0.01 0.03 0.02 0.02 0.02 - - 0.01 0.01 0.02 - - 0.02 0.01 - 0.02 0.02 - 0.02 0.01 - 0.01 -
 
Last edited:
@pedvide - I'm hoping there is a simple change needed to provide an updated version of the m#101 code if you could point me to a similar sample or provide clues to its failing point, it compiles and runs but doesn't present meaningful data so port ID or the interaction with Audio Mic? My only change was the update to my pin #17. Hoping this sample worked before and is mostly right? The ADC lib control over sampling would help me minimize perf hit as try to track the fast action sounds around. I'll be scanning them for details while watching DOF data for matching touchpoints.

Thanks for any tips. As noted I thought I was close updating the similar sinewave sample but that has shown some issue as well when the lower bins get jacked up after working under audio control. This thread had the AdaFruit on Teensy code that show some promising tips, but the manual DSP FFT calls they make are no where near as fast as the Audio lib stuff, though my 2nd mic has been running about 24 hours and still responding properly.
 
Hi, you problem seems to be related to the Audio library, not the ADC. The ADC library has no relation to the Audio library. The only thing that I changed is so you can use the ADC1 when the Audio library uses ADC0, but apart from that I can't help you with audio problems. Ask the same question in the Audio library thread and probably somebody will be able to help you.
 
Thanks. I was hoping that old sample was a good example of proper usage of getting the ADC to pull the samples for the Audio library usage in FFT's.
 
Thanks for your hard work, Pedvide!

I'm using Teensy 3.1 to generate the PWM control signal for a DC/AC inverter. I'm using a 2us IntervalTimer as a timebase and I have to periodically read the output voltage during the this (2us maximum) interval.

Do I have to use continuous or single shot read? It would be best if I could read that voltage in less than 1us (as I have few other instructions in that timer subroutine). Also, an averaging value will be great, too. Seems like the continuous read it's the only option, right?

So, I have to read that voltage every 10ms but I have to read it fast (under 1us). An averaging of 10 would be great, too. Is it possible?
 
The absolute fastest measurement time is 0.79 us, with no averaging, 8 bits and both sampling and conversion speeds at ADC_VERY_HIGH_SPEED. This speed is actually out of specs (fastest measurement time within specs is 1.58 us), so 8 bit precision is not even guaranteed!
Those times were obtained with the continuous mode, because single measurements have a slight longer conversion time. That means that you should use the continuous mode. This means that you convert values at that speed and you read one when you need it. The problem is that last measurement may have been started up to 0.79us ago (no longer than that, otherwise you'll get a fresh value), and because you can't synchronize the conversions (or maybe you can?) that delay can drift from 0 to 0.79us.
 
I ran a small sketch, counting the ADC reads for an elapsed time of 10us. The result was 17-18 reads, that means 0.58us. Pretty impressive! Or am I missing something? That's the sketch I was using:

Code:
#include <ADC.h>

ADC *adc = new ADC();
volatile int elapsed;    
volatile int count;

void setup() 
{
    Serial.begin(38400);
    adc->setAveraging(10);
    adc->setResolution(12);
    adc->setConversionSpeed(ADC_HIGH_SPEED);
    adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED);
    adc->analogReadContinuous(ADC_0);
}

void loop() 
{  
    elapsedMicros elapsed;    
    while (elapsed <= 10)
    {
        adc->analogReadContinuous(ADC_0);
        count++;
    }
    Serial.println(count);
    count=0;
    delay(5000);
}
 
I don't know how the ADC code works; does your example simply start the read, or actually capture a result? Maybe this code simply interrupts a read in progress and restarts it, so it might not tell you how fast a read completes (?)
 
I was following the Pedvide example (analogContinuousRead). I've updated the sketch by adding an input pin, storing the read value inside a variable but the results are the same.

Code:
#include <ADC.h>

ADC *adc = new ADC();

const int readPin = A9;
int elapsed;    
int count;
int value;

void setup() 
{
    Serial.begin(38400);
    pinMode(readPin, INPUT);
    adc->setAveraging(10);
    adc->setResolution(12);
    adc->setConversionSpeed(ADC_HIGH_SPEED);
    adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED);
    adc->startContinuous(readPin, ADC_0);    
    adc->analogReadContinuous(ADC_0);
}

void loop() 
{  
    elapsedMicros elapsed;    
    while (elapsed <= 10)
    {
        value = adc->analogReadContinuous(ADC_0);
        count++;
    }
    Serial.println(count);
    count=0;
    delay(5000);
}
 
Like I've just said, I have counted 17 measurements during 10us thus an average of 0.58us per measurement.

I have further reduced the reading interval down to 2us and I've got 3-4 consistent readings so the total duration per reading could be bellow 0.5us. And it's actually measuring that pin voltage, because I've got various values printed out.

The pin is floating, by the way. Theoretically, I could get better results with a lower impedance signal. Better than 0.5us?!

Could someone run this code, please, and share the results?

Code:
#include <ADC.h>

ADC *adc = new ADC();

const int readPin = A9;
int elapsed;    
int count;
int value;

void setup() 
{
    Serial.begin(38400);
    pinMode(readPin, INPUT);
    adc->setAveraging(10);
    adc->setResolution(12);
    adc->setConversionSpeed(ADC_HIGH_SPEED);
    adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED);
    adc->startContinuous(readPin, ADC_0);    
    adc->analogReadContinuous(ADC_0);
}

void loop() 
{  
    elapsedMicros elapsed;    
    while (elapsed <= 2)
    {
        value = adc->analogReadContinuous(ADC_0);
        count++;
    }
    Serial.print(value); Serial.print(" -> ");
    Serial.println(count);
    count=0;
    delay(5000);
}
 
Sorry im just puzzled as to why your trying to run an AC motor with a 100Khz loop? The fastest control loop I have seen at work was around 20Khz loop to spin a generator(they used as a motor on the Buckeye Bullet 3) to 10K rpm.
 
Sorry im just puzzled as to why your trying to run an AC motor with a 100Khz loop? The fastest control loop I have seen at work was around 20Khz loop to spin a generator(they used as a motor on the Buckeye Bullet 3) to 10K rpm.

Wrong thread??
 
Your aren't measuring at that speed!
adc->analogReadContinuous(ADC_0); Always returns the last measured value, but that doesn't mean that the value has been updated between reads.
To only read new values you can use isComplete()

//! Is an ADC conversion ready?
/**
* \return 1 if yes, 0 if not.
* When a value is read this function returns 0 until a new value exists
* So it only makes sense to call it before analogReadContinuous() or readSingle()
*/
bool isComplete()
 
Thanks for your clarifications!

Actually, I don't mind if that value is 1-2us "older" (as it's an averaging value anyway). My only concern was the reading time (of this average value). It's good to know that I could read it in less than 1us (even 0.5us).

Anyway, you did a great job. Thanks, again!
 
Back
Top