Teensy 3.5 ADC characteristcs

Hello all , I have a couple of doubts about the ADC capabilities of Teensy

1) Whats the maximum resolution of the ADC ? Is it 12bit or 16bit ? The datasheet is a bit confusing , (K64P144M120SF5RM) says Teensy have two 16-bit SAR ADC where as (K64P144M120SF5) says The 16-bit accuracy are achievable on the differential pins ADCx_DP0, ADCx_DM0. All other ADC channels meet the 13-bit differential/12-bit single-ended accuracy
specifications. Can someone please explain what it exactly means !

2) Currently working an analog pressure sensor (Honeywell HSC series , Analog output , 3.3V ,HSCDRRN002NDAA3) and a five hole probe. I'm using Teensy analog pins from A0 to A5 and I am able to get 16 bit data on each channel using the ADC Library. But according to the data sheet Teensy have only two ADC channels that support 16bit data . Does that indicates the ADC value I'm getting is wrong or does teensy can actually support 16bit data on all analog pins ?

3) What is the maximum sampling rate that Teensy ADC can give and how can I use it in my code ? According to the datasheet ADC can give up to some 460K samples/sec . But when I try to log the data directly to the teensy SD card I can barely get up to 4Kz per channel.

loop_time.jpgloop_time.jpg F0000TEK.png



Using the above sketch and oscilloscope we can see that the loop rate is about 160Kz ( toggling the LED 13 pin without a delay ). How can we increase the loop rate ?

I'm using Tessny 3.5 Analog pins to read the analog voltage of the pressure sensor and then dump the values into the SD card .
Code:
#include <ADC.h>
#include<math.h>
#include<SPI.h>
#include <SdFat.h>

#define VREF 3.3
#define resolution (VREF/((pow(2,16))-1));
SdFatSdio sd;
File file;

const int readPin1 = A0; 
const int readPin2 = A1;
const int readPin3 = A2;
const int readPin4 = A3;
const int readPin5 = A4;
const int LED = 13;
ADC *adc = new ADC(); // adc object
void measure_log();
double voltage1,voltage2,voltage3,voltage4,voltage5;
double adc_value1,adc_value2,adc_value3,adc_value4,adc_value5;
  static int ifl = 0;
  static uint32_t count = 0;
  char filename[32];
  static uint32_t MaxCount = 100000;
  bool blah=0;
void setup() 
{
    pinMode(readPin1, INPUT);
    pinMode(readPin2, INPUT);
    pinMode(readPin3, INPUT);
    pinMode(readPin4, INPUT);
    pinMode(readPin5, INPUT);
    pinMode(LED, OUTPUT);
    Serial.begin(115200);
    if (!sd.begin()) 
    {
      digitalWrite(LED, HIGH);
      sd.initErrorHalt();             
    }
    adc->setResolution(16); // set bits of resolution
    adc->setConversionSpeed(ADC_MED_SPEED);
    adc->setSamplingSpeed(ADC_HIGH_SPEED);

}
void loop() 
{
   measure_log();
}
void measure_log()
{
  // following is for logging

  if(count==0)
  {
     sprintf(filename,"Multiple_Log%04d.txt",ifl); ifl++;
     if (!file.open(filename, O_RDWR | O_CREAT))
     {
      digitalWrite(LED, HIGH);
      sd.errorHalt("open failed");
      //count=MaxCount+1;
     }
  } 
  while(count<=MaxCount)
  {
    adc_value1=adc->analogRead(readPin1);
    voltage1 = adc_value1*resolution;
   
    adc_value2=adc->analogRead(readPin2);
    voltage2 = adc_value2*resolution;
    
    adc_value3=adc->analogRead(readPin3);
    voltage3 = adc_value3*resolution;
    
    adc_value4=adc->analogRead(readPin4);
    voltage4 = adc_value4*resolution;
    
    adc_value5=adc->analogRead(readPin5);
    voltage5 = adc_value5*resolution;

    file.println(String(millis())+","+String(adc_value1)+","+String(adc_value2)+","+String(adc_value3)+","+String(adc_value4)+","+String(adc_value5)+","+String(voltage1,4)+","+String(voltage2,4)+","+String(voltage3,4)+","+String(voltage4,4)+","+String(voltage5,4));
    blah=!blah;
    digitalWrite(LED,blah);
     
    count++;
  }
  count=0;
  if(count==0)
  {
     // close file
     file.close();
    
  }
}

If I comment ou file.println() to dump my values to the SD card then the loop ins running only at frequency close to 5Khz which is very very small. And if tried to run the code with file.println() , then the loop rate is close to 1Khz. How can I increase this ? How can I achieve a sampling rate close 200samples/sec per channel and dump that values to the SD card ?

F0001TEK.png F0002TEK.png

Can someone help me with this . Thanks in advance
 
Last edited:
Search the forum, there have been several threads discussing low latency data logging, e.g.,
https://forum.pjrc.com/threads/43708-Teensy-3-6-Datalogging-at-10kHz
In particular, ADC logging example at
https://github.com/greiman/SdFat/tree/master/examples/LowLatencyLogger

the ADC lib has comments about expected ADC acquisition rates
https://github.com/pedvide/ADC


effective ADC accuracy is 13-bits for T3.5 -- discussed in several threads[/QUOTE]

Thanks for the reply. I am bit new to the teensy board so kinda bit confused here .

I am getting only single ended measurements on analog pins A0-A5 . If teensy ADC accuracy is 13 bit then why/how I am able to get 16bit data ? Or why even the data sheet say two 16bit SAR ADC's ?

Using the ADC library I can set adc->setResolution(); as either 8,10,12,12 and 16 . If i tried setting as 13 bit still I'm receiving only 12 bit data.

Please help to get a clear understanding of this !
 
There is a difference between physical resolution and usable resolution. The latter is lower since noise, differential ADC non-linearities, etc., will "eat" a few least significant bits away.
 
The hardware is 16 bit, since silicon is cheap and it means registers line up in sensible ways but enough noise from surrounding parts gets in the disrupt the bottom 2-3 bits. And if you are not careful with your own hardware design it's very easy to end up back at 9-10 effective bits.

Basic idea to use the ADC is to use 12 or 14 bit and then average or otherwise post process to extract what you want from it and not make any particular assumptions to what your last decimal place is giving you.
 
Back when I was playing with my Tiny Scope project, I was able to read 1.1 Msamples/second at 8 bit precision, probably 800Ksps at 10bit IIRC.

See https://forum.pjrc.com/threads/27824-Tiny-Scope?p=99864&viewfull=1#post99864

attachment.php

Thanks for the reply...a couple of questions

Which ADC library are you using ? I'm using the pedvide/ADC library but I'm not able to log the data more than 4000 samples/sec per channel.

I have some confusion regarding about the setConversionSpeed() and setSamplingSpeed() functions.

The adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED) and adc->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); // change the sampling speed calls the function definition in ADC_Module.cpp . Should I use this definition or the one in ADC.cpp.

Supposedly I should be able to 400KHz if I set the number of averages to 1 , and since SPI is used for writing to the SD I should be able to get atleast 250-200Khz .

Code:
#include "ADC.h"
#include<math.h>
#include<SPI.h>
#include <SdFat.h>

#define resolution (3.3/((pow(2,16))-1));
SdFatSdio sd;
File file;

static uint32_t count = 0;
static uint32_t MaxCount = 100000;
const int readPin = A9; // ADC0

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

void setup() 
{ 
   delay(500);
   Serial.println(" setup() ");
   pinMode(LED_BUILTIN, OUTPUT);
   pinMode(readPin, INPUT);
   Serial.begin(115200);
   adc->setAveraging(0); // set number of averages
   adc->setResolution(16); // set bits of resolution
   adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED)  // change the conversion speed    // it can be any of the ADC_MED_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED or VERY_HIGH_SPEED
   adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed
   adc->startContinuous(readPin,ADC_0);
   if (!sd.begin()) 
    {
      sd.initErrorHalt();
    }
   open_file();
}
void loop() 
{
  Serial.println("loop() ");
  while(count<=MaxCount)
  {
    double adc_value=adc->analogRead(readPin);
    double voltage = adc_value*resolution;
    //Serial.print(adc_value);Serial.print(",");Serial.println(voltage,3);
    //file.print(adc_value);
    //file.print(',');
    //file.print(voltage,3);
    //file.println('V');
    file.println(String(millis())+","+String(adc_value)+","+String(voltage));
    count++;
  }
  count=0;
  if(!count)
  {
     // close file
     file.close();
  }
  open_file();
}
void open_file()
{
  Serial.println("open_file()");
  static int ifl = 0;
  static uint32_t count = 0;
  char filename[32];
  static uint32_t MaxCount = 100000;
  if(!count)
  {
     sprintf(filename,"Log_Test%04d.txt",ifl); ifl++;
     if (!file.open(filename, O_RDWR | O_CREAT))
     {
       sd.errorHalt("open failed");
       count=1;
     }
  }
}

Please help to identify the error in my code !!

Thanks in advance
 
Yes it is. I recommend strongly that you look at the code of that library until you understand everything and you see yourself how you can achieve that sampling rate. Then, you will be able to have a maximum of benefit from it. Using libraries without deep understanding what these do and how they do it, is like driving a car without understanding how the motor works. Not acceptable IMNSHFO.
 
Yes it is. I recommend strongly that you look at the code of that library until you understand everything and you see yourself how you can achieve that sampling rate.

Totally agree and I would add reading up on the hardware specs published by Freescale. The more I got into it, the more I realized just how unlikely it is that anyone gets remotely close to 16 bits of real ADC accuracy out of the MKL20 series of chips. I don't doubt it can be done but the warnings are out there, starting with the source impedance and so on. Resolution is not the issue, repeatability and accuracy is. For slow-moving signals, you might be able to use decimation to your advantage.

To get 16 bits, you can only use two channels in differential mode (all others are good to 13, 12 bits, respectively), you likely have to power down most of the chip, have a perfect front end, strong signal, and so on. None of these things are likely in the real world; the way we use these chips. It's a tribute to the care that Paul put into the Teensy PCBs that the chips perform as admirably as they do (i.e. 13, 12 bits for differential vs single-ended DAQ, respectively).

For anyone making better claims than what Paul achieved and tested extensively, I'd like to see the documentation and testing apparatus.
 
Yes it is. I recommend strongly that you look at the code of that library until you understand everything and you see yourself how you can achieve that sampling rate. Then, you will be able to have a maximum of benefit from it. Using libraries without deep understanding what these do and how they do it, is like driving a car without understanding how the motor works. Not acceptable IMNSHFO.

Thanks man..I haven't done much embedded coding so kinda having a bit of confusions and doubts.

Played with ADC Library a bit today (also read up on the ADC hardware specs ) and using the analogContinuousRead() example I'm able to get data around 280kS/s single channel but using this settings :
adc->setAveraging(1); // set number of averages
adc->setResolution(16); // set bits of resolution
adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);

But now I end up with another issue. Let me just explain what I want to do.
I have 5 analog pressure sensor (3.3V) and I need to read the voltage reading sequentially at a very high rate. ( Something close to 100kS/s for 5 channels will do it ) . How will I read 5 channel values at high rate using analogContinuousRead() example ? Since each channel doesn't have its own data register , how will I do that ?
 
Last edited:
For the required speed and number of channels, you'll most probably have to write your own code instead of using the library with its overhead. The algorithm would be using DMA triggered by the conversions done by ADC0 to evacuate the current result into a ring buffer, and reconfiguring the input channel in the inner DMA loop before launching the next conversion. I remember having seen such a code snippet here in the forums, you'll for sure find it using the search function.

You could do things in a still smarter way, using both ADCs to convert always two channels simultaneously.
 
I have some confusion regarding about the setConversionSpeed() and setSamplingSpeed() functions.

The adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED) and adc->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); // change the sampling speed calls the function definition in ADC_Module.cpp . Should I use this definition or the one in ADC.cpp.
The SAR ADC first charges a sampling capacitor and then performs the conversion using that. Sampling speed is the time allowed for charging the sampling cap, if your source impedance is low you can set it to fast. Conversion speed is the time allowed for the actual measurement and affects accuracy. It makes very little sense to have a high conversion speed and perform 16-bit conversions. Better use 12-bit conversions with a lower conversion speed.

Supposedly I should be able to 400KHz if I set the number of averages to 1 , and since SPI is used for writing to the SD I should be able to get atleast 250-200Khz .
Where did you get that idea from? SPI isn't even used. SdFatSdio is very slow for small writes.

Please help to identify the error in my code !!
With regards to performance, everything is wrong. :p

Everything you do is synchronous. The hardware is capable of doing various things in parallel.

Either perform large 32kB writes with SdFatSdio or use SdFatSdioEX. Writing the data as ASCII strings is pretty much out of the question. A float -> string conversion can take 10'000+ clock cycles; an int -> string conversion 3'000+ clock cycles. Don't use the Stream functions (all the 'print()' stuff), use 'write()' with a decent buffer size.

Don't use double. Teensy 3.5 has an FPU, but it only supports float. Double is emulated, a multiplication will cost you around 70 clock cycles.

Don't use 'adc->analogRead(...)', it has has unnecessary overhead to dispatch to the right ADC. Use 'adc->adc0->analogRead()' instead. Even then, it has unnecessary checking that you could eliminate by writing your own function.

Don't use 'digitalWrite()', it has substantial overhead (costs you around 45 clock cycles). Use 'digitalWriteFast()' instead.

( Something close to 100kS/s for 5 channels will do it ) .
Is that 100kS/s or 500kS/s total? 100kS/s is doable with 'analogRead()', 500kS/s is not. At 500kS/s you would have 240 CPU clock cycles per sample, that's not exactly a lot.

How will I read 5 channel values at high rate using analogContinuousRead() example ? Since each channel doesn't have its own data register , how will I do that ?
You can't. The ADC mux needs to be reconfigured, you can't do that with continuous reads. While the manual talks about the great PDB capabilities for sequentially scanning channels, the sad reality is that only 2 are supported.

The easiest way to very quickly scan multiple channels is to use DMA for reconfiguring the IO mux. Code doing that is in this thread:
https://forum.pjrc.com/threads/3017...ransfer-to-allow-multiple-Channel-Acquisition

The code posted there actually uses both ADCs, which you probably want to do. You can use slower, more accurate conversions.

There is an additional gotcha, make sure all the pins use the same 'ADCx_CFG2[MUXSEL]' so that you don't need to change ADCx_CFG2. Look at the manual, '3.7.1.3.1.1 ADC0 Channel Assignment' / 3.7.1.3.2.1 ADC1 Channel Assignment / '35.3.3 ADC Configuration Register 2 (ADCx_CFG2)'.
 
tni, your knowledge is scary sometimes ;)

I have not done much testing with the DMA on the T3.6 yet, looking at some of my notes its possible you may get faster analog speeds at 192MHz vs 216MHz/240MHz. This has to do with how the ADC clock dividers are setup for the various speeds. I think 6(5 plus 1 discard in your case) channels at 100K Cycles/s is going to be tough but you may get there. My guess is 60-70K Cycles/s based on values I got from testing the T3.2.

@abin.ephrem, I placed a list of values for calling the T3.6 DMA ADC's in the thread tni mentioned, it should work on the T3.5 also. I have not confirmed any of them yet.
 
Last edited:
I have not done much testing with the DMA on the T3.6 yet, looking at some of my notes its possible you may get faster analog speeds at 192MHz vs 216MHz/240MHz. This has to do with how the ADC clock dividers are setup for the various speeds. I think 6(5 plus 1 discard in your case) channels at 100K Cycles/s is going to be tough but you may get there. My guess is 60-70K Cycles/s based on values I got from testing the T3.2.
Performance numbers for the DMA scanning, Teensy 3.5 @ 120Mhz:

Using both ADCs, ADC resolution 12, conversion speed MED_SPEED, sampling speed MED_SPEED runs at 154'000 samples per second per channel, total of 6 channels, 3 per ADC (926'000 samples per second total).

Using 1 ADC, scanning 5 channels, conversion speed MED_SPEED, sampling speed HIGH_SPEED runs at 108'000 samples per second per channel (542'000 samples per second total).
 
Performance numbers for the DMA scanning, Teensy 3.5 @ 120Mhz:

Using both ADCs, ADC resolution 12, conversion speed MED_SPEED, sampling speed MED_SPEED runs at 154'000 samples per second per channel, total of 6 channels, 3 per ADC (926'000 samples per second total).

Using 1 ADC, scanning 5 channels, conversion speed MED_SPEED, sampling speed HIGH_SPEED runs at 108'000 samples per second per channel (542'000 samples per second total).

Ah, your right, I was looking at my breakdowns from values I recorded and not doubling them since they are based on 4(ADC0) of 8(ADC0/1) channels I was reading.

CPU speed (FBus) and Resolution will affect the speeds, along with conversion and sample settings.
Highlighted yellow are out of spec for T3.1/3.2, I have an open Github issue asking Paul if it could damage them over time.
ADC DMA Speeds.JPG
 
Thank you so much guys ... @Donziboy2 @ tni @ Theremingenieur

I'm still working out on the code. Will post it once it completes. It will take some time as I'm just a kid when it comes to embedded programming !! :)
 
Yes it is. I recommend strongly that you look at the code of that library until you understand everything and you see yourself how you can achieve that sampling rate. Then, you will be able to have a maximum of benefit from it. Using libraries without deep understanding what these do and how they do it, is like driving a car without understanding how the motor works. Not acceptable IMNSHFO.

LOL all these years later I just realized that probably not one in a thousand people who drive a car have any idea how the motor works, and probably most of those who think they know anything have most of it wrong. The automobile owner's manual used to show you how to change your spark plugs, but today it just warns you not to drink the antifreeze.
 
LOL all these years later I just realized that probably not one in a thousand people who drive a car have any idea how the motor works, and probably most of those who think they know anything have most of it wrong.

Suck, Squeeze, Bang, Blow. Everything else is implementation details :)

And a bit like the contents of a library the average user shouldn't need to worry about the implementation details, they only matter if you want to make changes or push things to the limits. If I want to tune the car or set a new lap record I need to know how it works. It all you want to do is drive to the store and back safely you shouldn't need to worry about the details about how it works. If I want to push the ADC sample rate or accuracy I need to know how it works, if all I want to know is whether something is closer to 2 volts or 3 volts I should be able to ignore the details and trust that the library is working correctly.
 
Six channels at 200 samples per second is not a particularly high rate. However, you want precise timing of the sampling you will need a more sophisticated program.

If you want to sample and store data to the SD card at high rates, you need to learn how to store the analog data values as two-byte binary integers. The conversion to strings and printing to the
SD card slows down your sample rate tremendously. The next thing you will need to learn is the importance of buffering the analog values and writing the data in large blocks of 16 to 32Kbytes. This overcomes the occasional very long write times that occur when the internal processor on the SD card has to erase a data block or move blocks around for wear-leveling. Writing a 16K block may average 8millseconds, but occasionally take 80 to 100 milliseconds.

Why are you using a 64-bit double-precision floating point variables to collect 16-bit integer values? The FPU on the T3.5 only handles single-precision floating point, so you are wasting a lot of cycles with 'double' variables. You gain nothing in resolution or accuracy with double variables.

You should also reconsider your use of the macro definition for 'resolution'. I certainly hope the compiler is smart enough to optimize out the pow(2,16) part, but why not simplify things for the compiler and the reader with "const float resolution = VREF/65536;" I think most programmers working with A/D converters learn very early that 2^12 = 4096 and 2^16 = 65536.
 
Back
Top