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

Hello Pedvide,

I discovered a possible quirk with the ADC library's setAveraging() function.

adc->setAveraging(1); -- works as expected
adc->setAveraging(2); -- behaves like adc->setAveraging(4)
adc->setAveraging(4); -- works as expected (1/4th rate)
adc->setAveraging(8); -- works as expected (1/8th rate)
adc->setAveraging(16); -- works as expected (1/16th rate)

Hi G Douglas
I have experimented with "setAveraging(x) on my T3.6 and found that it makes an x-fold average of each x number of lines of data then puts that average value on all of the lines to which it applies - not useful - it produces a staircase of identical values and an x-fold reduction in lines. Instead, and taking advantage of the T3.6's memory I collected over 8-times more lines of data with setAveraging(1) than required then did an 8-fold average using this code: av.JPG. This works to increase the effective resolution of the signal - more like real 16 bit data, although it also produces an x = 8 fold reduction in lines per second.
 
If im reading the library right there is no setting for "adc->setAveraging(2);" so most likely it reverts to 4. The allowed values are 1, 4, 8, 16, 32.

@randomrichard, what it does is read the ADC channel multiple times in a row and then average those values using the internal ADC hardware.
Try setting your averages to 8 and your ADC resolution(s) to 16, then remove your averaging math. The result should be roughly the same.

Another thought randomrichard is to bitshift instead of divide, division is very slow even on the Teensy.
 
Last edited:
what to include in adc_module.h on teensy 3.6

I notice RingBuffer.h implies modifying this line as follows:
Code:
// include new and delete
//#include <Arduino.h>
In RingBuffer.h, should I use kinetis.h instead?
And ADC_module.h shows
Code:
#include <Arduino.h>

If I'm using a teensy 3.6 which is a Kinetis microcontroller from Freescale, do I still use the Arduino header? Is this header for the IDE or the hardware?
The Arduino.h says its for the SDK, but it also looks like it is specific for an AVR core.

Thanks if you can explain.
 
Hi Pedro,

firts, thank you for this great library!

There is a little problem with the documentation. When trying to open Documentation.html, it tries to open "file:///C:/Arduino/hardware/teensy/avr/libraries/ADC/doxygen/html/a_d_c__r_e_a_d_m_e.html" - which does not work.
 
It seems that the Teensy 3.6 CPU only provides 1 differential channel pair. This sucks.

The Teensy 3.5, however, seems to provide several differential channel pairs, especially it offers at least one for both ADC0 and ADC1, separately.
According to https://www.nxp.com/docs/en/reference-manual/K64P144M120SF5RM.pdf, page 245, the following pins can be used for differential measurements:
J1, J2
K1, K2
L1, L2
M1, M2 <-- this is also available on Teensy 3.6

Looking at the code, it seems that Teensy 3.5 and 3.6 are treated equally, meaning the library currently does not support analogSyncReadDifferential() on Teensy 3.5. :(
Is the ADC library still being updated? The last commit is more than 6 months old and there are several open issues..
 
Thanks for taking the time to do this. Sorry for my very basic question, but what will the results of using this library do? Faster analog read, more accurate analog read, less power consumption, other?
 
Before I break things: I'd like to measure the supply voltage, which won't work without some fixed reference.

Can I do a conversion from ADC_INTERNAL_SOURCE::VREF_OUT (10-bit result for 3.3 V supply would be approximately 372) and calculate the supply voltage from there? Like so:
Code:
value = adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT);
float v_bat = value*1.2/adc->getMaxValue(ADC_0);
 
I think what you need is.

Code:
value = adc->analogRead(VREF_OUT);
float v_bat = value / adc->getMaxValue(ADC_0);
v_bat = 1.2 / v_bat;

Keep in mind the internal reference is not super accurate.
 
First: You cannot put a voltage higher than 3.3V into an analog in, so there is that. (I e, no hooking batteries straight to the analog in pin!)
Second: The VREF uses specific hardware to make sure the voltage is always at the fixed value, no matter what the input voltage is. It's like a voltage regulator. Trying to make assumptions about other voltages (such as 3.3V) based on the reference is not going to work well.
Third: The 3.3V voltage on the Teensy is also generated by a regulator, with low drop-out. As long as the input is about 3.6V or higher, it will generate 3.3V. Thus, you won't be able to tell much about the main battery in (USB or VBat) by looking at the 3.3V rail. Again, it's a stabilized regulator, so it's not expected to fluctuate much at all.

If you want to measure the supply voltage, the most straight-forward way is to create a resistive divider, and buffer it with a 100 nF capacitor. You can make the resistive divider quite high impedance to avoid draining the source, as long as you don't read it too often. The parallel capacitor will make sure the ADC sees a representative voltage.
This example uses a 604K upper resistor and 100K lower resistor, for a drain at 12V in of 17 microamps. The time constant for 604k*0.1uF is about 60 milliseconds, so if I read this no more often than every 500 milliseconds (half a second) it should be quite stable. If you're OK reading even more slowly, you can probably go to megaohms of resistance, although you start becoming susceptible to pick-up EMI noise at some point.
(FWIW, the circuit below generates 3.3V in to the analog input when the battery voltage is 3.3V*(704/100) or about 23.2V, so it's safe up to about 5S LiPo input. If you want to push it all the way to "absolute max" (which I think is 3.6V in the data sheet) you'll see 25.3 Volts so 6S probably won't kill it. If your actual battery is lower voltage, you can increase the lower resistor to gain resolution and lower leakage.)

Finally, if you want to measure the voltage, I suggest calling analogReadRes(12) to get a better resolution measurement.

teensy-vref.png
 
Last edited:
Not quite. I want to run the teensy off a battery with not more than 3.6 V, probably without the regulator in between. So the controller's supply voltage is not known as it would usually be. That's why I want to compare it with a known reference, hence my question.
 

The time constant is much faster then you think. The formula for the series resistors connected to a capacitor is Rtot = (R1 * R2) / (R1 + R2), not just R1.

So for a 604K/100K divider the total resistance is around 86.5K. Making your time constant about 8.65mS with a 0.1uF capacitor.
 
So the controller's supply voltage is not known

I see. At that point, you can still use a resistive divider, but use the 1.2V reference instead of the 3.3V reference.
Thus, use analogReference(INTERNAL1V1) and divide the input voltage at least 3:1.

Rtot = (R1 * R2) / (R1 + R2), not just R1.
...
So for a 604K/100K divider the total resistance is around 86.5K.

For trying to get the capacitor down to ground, I can see how the time constant could go to "parallel resistors" resistance. Thanks!

Anyway, that just means you get a more settled reading at 500 ms. The main point is to have a big enough capacitor to feed the ADC input when it actually samples, while having high through resistance to reduce current consumption.
 
I see. At that point, you can still use a resistive divider, but use the 1.2V reference instead of the 3.3V reference.
Thus, use analogReference(INTERNAL1V1) and divide the input voltage at least 3:1.

I think what he is doing should be fine as long as he avoids going over Vmax for the IC, he is using the internal 1.2V reference to determine what the actual voltage is on the Teensy since the ADC defaults to the 3.3V rail or whatever voltage it actually is if he is directly feeding the 3.3V pin. His idea does not require any external parts and allows him to adjust for the variation in voltage on the 3.3V rail.


Anyway, that just means you get a more settled reading at 500 ms. The main point is to have a big enough capacitor to feed the ADC input when it actually samples, while having high through resistance to reduce current consumption.

The difference between a 10n and 100n capacitor is about 800uV (value of 1 at 12bit resolution) when sampled by the Teensy regardless of how much time you allow to charge back up. And the difference between the ideal value for the 604k/100K divider and what the Teensy will see is about 20mV all the way down to about 15-20mS per sample.
 
Last edited:
@jwatte: I don't want to use the internal reference as reference for the ADC. You're right in that I'd have to scale down the supply voltage then. My plan was different: VDD (battery voltage) is used as ADC reference (as it is by default). Then I measure the internal reference voltage when it is an input to the ADC, so I can extrapolate VDD from there.
 
So I picked a board and ran the following:
Code:
#include <ADC.h>

ADC* adc = new ADC();

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  int value = adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT);
  float voltage = 1.2/value*adc->getMaxValue(ADC_0);
  Serial.print("value = ");Serial.print(value);
  Serial.print(" ^= ");Serial.println(voltage);
  delay(1000);
}

And I got this output:

Code:
value = 349 ^= 3.52
value = 344 ^= 3.57
value = 340 ^= 3.61
value = 335 ^= 3.66
value = 331 ^= 3.71
value = 327 ^= 3.75
value = 322 ^= 3.81
value = 318 ^= 3.86
value = 314 ^= 3.91
value = 309 ^= 3.97
value = 305 ^= 4.02
The ADC value is monotonically falling, so the calculated VDD is rising. That's definitely something I didn't expect...

ADC library downloaded 20170918 (just now)
Teensy 3.6
teensyduino 1.39 release
arduino 1.8.4
arduino build system reports that it is using the correct library
 
@christoph

try breaking
Code:
float voltage = 1.2/value*adc->getMaxValue(ADC_0);
into 2 operations. Or try the example I posted earlier.

Also is
Code:
int value = adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT);
even valid? I don't recognize that as a command for reading adc's, only setting the reference.....
 
Same effect when breaking it into two operations.

The usage of analogRead is mentioned in the first post of this thread, in section "Other conversion sources". This usage is also in the datasheet, ADC chapter, register ADCx_SC1n[4..0] (ADCH), bandgap input.

Edit: now as I had a closer look at the datasheet it seems that bandgap input is *not* the same as the 1.2 V reference - so I'm unsure if what I want is feasible. The bandgap has 3% tolerance (0.97 V ... 1.03 V).
 
Last edited:
It seems that for the teensy 3.6, VREF_OUT can only be measured with the second adc on channel 18 (chapter "ADC1 connections/Channel Assignment"). I'll try that later today when I'm back home.

Edit: for all chips:

  • Teensy 3.2 (MK20DX51VLH7)
    • ADC0: VREF_OUT on channel AD22
    • ADC1: VREF_OUT on channel AD18
  • Teensy 3.5 (MK64FX512VMD12)
    • ADC0: VREF_OUT not available for ADC0 in 144-pin package
    • ADC1: VREF_OUT on channel AD18
  • Teensy 3.6 (MK66FX1M0VMD18)
    • ADC0: VREF_OUT not available for ADC0
    • ADC1: VREF_OUT on channel AD18
So what all chips have in common is that VREF_OUT can be measured on ADC1::AD18

The solution would then be (if I'm right):
Code:
#include <ADC.h>

ADC* adc = new ADC();

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  int value = adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT, ADC_1); // explicitly select ADC1, same in next row
  float voltage = 1.2/value*adc->getMaxValue(ADC_1);
  Serial.print("value = ");Serial.print(value);
  Serial.print(" ^= ");Serial.println(voltage);
  delay(1000);
}

Anyone fancy trying? I don't have hardware at hand. No external parts or wires needed, just a blank Teensy 3.2/3.5/3.6.
 
Last edited:
Problem persists, this is the code I've tried:
Code:
#include <ADC.h>

ADC* adc;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  adc = new ADC();
}

void loop() {
  // put your main code here, to run repeatedly:
  int value = adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT, ADC_1);
  if (value == ADC_ERROR_VALUE)
  {
    Serial.println("error");
  }
  float voltage = 1.2/value*adc->getMaxValue(ADC_1);
  Serial.print("value = ");Serial.print(value);
  Serial.print(" ^= ");Serial.println(voltage);
  delay(1000);
}
Output:
Code:
value = 366 ^= 3.35
value = 362 ^= 3.39
value = 358 ^= 3.43
value = 353 ^= 3.48
...
So what I had in mind is either not possible, or I'm doing it wrong. Another reason might be some error in the library since reading from the internal reference might have been tested with somewhat low priority. I don't know.
 
Success (apparently)!

I think I fixed it:
  • enable VREF_OUT and low-power buffer
  • wait for VREF_OUT to stabilize

Code:
#include <ADC.h>

ADC* adc;

void setup() {
  Serial.begin(9600);
  adc = new ADC();
  // enable VREF_OUT and wait for it to stabilize
  VREF_TRM |= VREF_TRM_CHOPEN;
  VREF_SC = VREF_SC_VREFEN | VREF_SC_REGEN | VREF_SC_ICOMPEN | VREF_SC_MODE_LV(2);
  while(!(VREF_SC & VREF_SC_VREFST))
  {    
  }
}

void loop() {
  int value = adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT, ADC_1);
  if (value == ADC_ERROR_VALUE)
  {
    Serial.println("error");
  }
  float voltage = 1.2/value*(adc->getMaxValue(ADC_1));
  Serial.print("value = ");Serial.print(value);
  Serial.print(" ^= ");Serial.println(voltage);
  delay(1000);
}

Output:
Code:
value = 370 ^= 3.32
value = 370 ^= 3.32
value = 370 ^= 3.32
value = 370 ^= 3.32
value = 370 ^= 3.32
value = 370 ^= 3.32

So when I change from USB power to battery power, I should be able to measure VDD with this.

I'm still playing with different VREF_SC_MODE_LV settings. VREF_SC_MODE_LV(1) or VREF_SC_MODE_LV(2) should both work, while VREF_SC_MODE_LV(0) puts the reference into standby.
 
Last edited:
Awesome, I might barrow some of that, its nice to have a second reference to insure your voltage is not way out of whack.
 
@Pedvide, hope your doing ok.

Been poking around the library, mainly looking at speeds and such and noticed some inconsistency's vs the ADC Code Paul provides for the Teensy. Here is the Breakdown table for FBus to ADCK. For both the stock analog.c and ADC_Module.h
108Mhz has some math issues and 36Mhz seems to be missing 4.5Mhz.

FBus to ADCK.JPG


On another note I was playing around and created a small lookup table for selecting resistor values for a divider. The values indicated are safe to use with or without an external capacitor on the divider. Although you may have noise issues without the capacitor.


Just set the Time Constant to the value you want and using the table provided(values are calculated sample time for the ADC), insert the sample time your after and it will highlight all values that are safe to use. The pictures show the usable values for a Time constant of 7 and a 250nS sample time(24Mhz @ Very High Speed). You can change RADIN and CADIN if you want, both are set to worst case single ended read.
ADC Lookup pg1.jpg
ADC Lookup pg2.JPG
ADC Time Constant Losses.JPG

Link to the Excel file on my Google Drive.
 
@christoph I have a simple question on bit naming convention. This topic is detailed in the K66 Freescale reference manual section 42.3 but if I search (the manual) for any of the bit terms you use (say VREF_SC_REGEN) they are not found. But I can find VREF_SC[REGEN] and similar for others like VREF_SC[CHOPEN] ...

What is the convention for bit naming and is it explained anywhere ? And is this just a Freescale convention, or industry wide?

Code:
VREF_TRM |= VREF_TRM_CHOPEN;
VREF_SC = VREF_SC_VREFEN | VREF_SC_REGEN | VREF_SC_ICOMPEN | VREF_SC_MODE_LV(2);

TIA
 
Last edited:
Back
Top