Strange ADC behaviour on teensy 3.2

Jachdich

Member
I've got a couple of old teensy 3.2s around and I'm trying to use them to read some voltages. I've just run the example sketch internal_reference and I'm getting some strange results. I've measured between 3.3v and AGND and modified the value to be 3.33v in the code. Here are the results:

Code:
niter,    a,    b,    midpoint,    diff (mV)
0,    0,    63,    31,        196.81
1,    31,    63,    47,        97.74
2,    47,    63,    55,        59.85
3,    55,    63,    59,        60.31
4,    59,    63,    61,        94.19
5,    61,    63,    62,        75.98
6,    62,    63,    62,        47.30
7,    62,    63,    62,        74.15
8,    62,    63,    62,        49.15
9,    62,    63,    62,        75.26
10,    62,    63,    62,        65.49
11,    62,    63,    62,        70.65
12,    62,    63,    62,        123.23
13,    62,    63,    62,        25.31
14,    62,    63,    62,        103.12
15,    62,    63,    62,        107.12
16,    62,    63,    62,        19.62
17,    62,    63,    62,        115.11
18,    62,    63,    62,        75.85
19,    62,    63,    62,        9.32
20,    62,    63,    62,        40.21
21,    62,    63,    62,        96.67
22,    62,    63,    62,        122.39
23,    62,    63,    62,        121.45
24,    62,    63,    62,        119.25
25,    62,    63,    62,        124.61
26,    62,    63,    62,        12.05
27,    62,    63,    62,        19.35
28,    62,    63,    62,        63.36
29,    62,    63,    62,        109.95
30,    62,    63,    62,        35.74
31,    62,    63,    62,        85.34
32,    62,    63,    62,        57.38
33,    62,    63,    62,        130.82
34,    62,    62,    62,        87.44
Optimal trim value: 62
3.3V pin value: 3.43108 V.
Bandgap value: 0.99792 V. (Should be between 0.97 and 1.03 V.)
3.3V pin value: 3.05212 V.
3.3V pin value: 3.02360 V.
3.3V pin value: 3.47785 V.
3.3V pin value: 3.12794 V.
3.3V pin value: 3.19468 V.
3.3V pin value: 3.00158 V.
3.3V pin value: 3.33352 V.
3.3V pin value: 3.00562 V.
3.3V pin value: 3.61679 V.
3.3V pin value: 3.68001 V.
3.3V pin value: 3.10439 V.
3.3V pin value: 3.77255 V.
3.3V pin value: 3.27442 V.
3.3V pin value: 3.13333 V.
3.3V pin value: 3.96769 V.
3.3V pin value: 3.41462 V.

The diff voltage seems to drift around wildly, it almost always gets to the 100th iteration (not in this one case for brevity) and the diff can be upwards of 800mV. The measured 3.3v value also seems to fluctuate, I've seen it go between 5.2v and 2.1v. What could possibly be causing this? I have two 3.2s and both of them give similar results. They have both been used in other projects but it seems unlikely they've both been damaged in the same way.
 
The diff voltage seems to drift around wildly, it almost always gets to the 100th iteration (not in this one case for brevity) and the diff can be upwards of 800mV. The measured 3.3v value also seems to fluctuate, I've seen it go between 5.2v and 2.1v. What could possibly be causing this? I have two 3.2s and both of them give similar results. They have both been used in other projects but it seems unlikely they've both been damaged in the same way.

Please post a small, simple program that shows the issue (see Forum Rule). It's really not possible to say what might be wrong without seeing your code.
 
Maybe you have pinMode with INPUT or INPUT_PULLUP? Configuring the pin for digital I/O can cause errors with analog, and the amount of error can vary depending on your circuitry's impedance. Best to never use pinMode() or use INPUT_DISABLE.

As already mentioned, we can't see your code so these sorts of just guesswork.
 
The code is the example program, internal_reference, from the ADC library. It does not use any pins but rather an internal reference, therefore I don't think it's a pinMode issue.

Code:
/* Example for the use of the internal voltage reference VREF
 *   VREF can vary between 1.173 V and 1.225 V, so it can be adjusted by
 * software. This example shows how to find the optimum value for this
 * adjustment (trim). You need a good multimeter to measure the real voltage
 * difference between AGND and 3.3V, change voltage_target accordingly and run
 * the program. The optimum trim value is printed at the end. The bisection
 * algorithm prints some diagnostics while running. Additionally, the bandgap
 * voltage is printed too, it should lie between 0.97 V and 1.03 V, with 1.00 V
 * being the typical value. Because of noise, the trim value is not accurate to
 * 1 step, it fluctuates +- 1 or 2 steps.
 */

#include <ADC.h>

#ifdef ADC_USE_INTERNAL_VREF

#include <VREF.h>

ADC *adc = new ADC();

//! change this value to your real input value, measured between AGND and 3.3V
float voltage_target = 3.29;

//! change the TOL (tolerance, minimum difference  in mV between voltage and
//! target that matters) to refine even more, but not to less than 0.5 mV
const float TOL = 2.0;
// Maximum iterations of the algorithm, no need to change it.
const uint8_t MAX_ITER = 100;

float average = 0;
const int NUM_AVGS = 100;
// Get the voltage of VREF using the trim value
float get_voltage(uint8_t trim) {
  average = 0;
  VREF::trim(trim);
  VREF::waitUntilStable();
  delay(50);
  for (int i = 0; i < NUM_AVGS; i++) {
    average += 1.195 / adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT) *
               (adc->adc0->getMaxValue());
  }
  return average / NUM_AVGS;
}

// Simple bisection method to get the optimum trim value
// This method is not the bests for noisy functions...
// Electrical datasheet: "The Voltage Reference (VREF) is intended to supply an
// accurate voltage output that can be trimmed in 0.5 mV steps."
int8_t optimumTrimValue(float voltage_target) {
  // https://en.wikipedia.org/wiki/Bisection_method#Algorithm
  // INPUT: Function f, endpoint values a, b, tolerance TOL, maximum iterations
  // NMAX CONDITIONS: a < b, either f(a) < 0 and f(b) > 0 or f(a) > 0 and f(b) <
  // 0 OUTPUT: value which differs from a root of f(x)=0 by less than TOL
  //
  // N ← 1
  // While N ≤ NMAX # limit iterations to prevent infinite loop
  //   c ← (a + b)/2 # new midpoint
  //   If f(c) = 0 or (b – a)/2 < TOL then # solution found
  //     Output(c)
  //     Stop
  //   EndIf
  //   N ← N + 1 # increment step counter
  //   If sign(f(c)) = sign(f(a)) then a ← c else b ← c # new interval
  // EndWhile
  // Output("Method failed.") # max number of steps exceeded
  const uint8_t MAX_VREF_trim = 63;
  const uint8_t MIN_VREF_trim = 0;

  uint8_t niter = 0;
  uint8_t a = MIN_VREF_trim, b = MAX_VREF_trim, midpoint = (a + b) / 2;
  float cur_diff, diff_a;

  // start VREF
  VREF::start(VREF_SC_MODE_LV_HIGHPOWERBUF, midpoint);

  Serial.println("niter,\ta,\tb,\tmidpoint,\tdiff (mV)");

  while (niter < MAX_ITER) {
    midpoint = (a + b) / 2;
    cur_diff = (get_voltage(midpoint) - voltage_target) * 1000;

    Serial.print(niter, DEC);
    Serial.print(",\t");
    Serial.print(a, DEC);
    Serial.print(",\t");
    Serial.print(b, DEC);
    Serial.print(",\t");
    Serial.print(midpoint, DEC);
    Serial.print(",\t\t");
    Serial.println(cur_diff, 2);

    if (abs(cur_diff) <= TOL || b - a < 1) {
      return midpoint;
    }
    niter++;
    diff_a = get_voltage(a) - voltage_target;
    if ((cur_diff < 0 && diff_a < 0) || (cur_diff > 0 && diff_a > 0)) {
      a = midpoint;
    } else {
      b = midpoint;
    }
  }
  return -1;
}

void setup() {
  Serial.begin(9600);

  // Best measurement conditions
  adc->adc0->setAveraging(32);
  adc->adc0->setResolution(16);
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED);
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_LOW_SPEED);
#ifdef ADC_DUAL_ADCS
  adc->adc1->setAveraging(32);
  adc->adc1->setResolution(16);
  adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED);
  adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_LOW_SPEED);
#endif // ADC_DUAL_ADCS

  delay(2000);

  int8_t VREF_trim = optimumTrimValue(voltage_target);
  Serial.print("Optimal trim value: ");
  Serial.println(VREF_trim);

  VREF::start(VREF_SC_MODE_LV_HIGHPOWERBUF, VREF_trim);
  VREF::waitUntilStable();

  Serial.print("3.3V pin value: ");
  Serial.print(1.195 / adc->adc0->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT) *
                   adc->adc0->getMaxValue(),
               5);
  Serial.println(" V.");

  Serial.print("Bandgap value: ");
  // Serial.print(3.3*adc->adc0->analogRead(ADC_INTERNAL_SOURCE::BANDGAP)/adc->adc0->getMaxValue(),
  // 5);
  adc->adc0->analogRead(ADC_INTERNAL_SOURCE::BANDGAP);
  adc->adc0->differentialMode(); // Use differential mode for better precision.
  Serial.print(
      voltage_target * adc->adc0->readSingle() / adc->adc0->getMaxValue(), 5);
  Serial.println(" V. (Should be between 0.97 and 1.03 V.)");

  // VREF::stop(); // you can stop it to save power.
}

void loop() {

  // If you now run your teensy from a battery you can check the charge by
  // looking at the 3.3V pin voltage it should be 3.3V, but it will fall as the
  // battery discharges.
  Serial.print("3.3V pin value: ");
  Serial.print(1.195 / adc->adc0->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT) *
                   adc->adc0->getMaxValue(),
               5);
  Serial.println(" V.");

  delay(3000);
}

/*

My output with a Teensy 3.6 connected to my laptop's USB.

With a cheap multimeter I measured voltage_target = 3.29 V, the results are:

niter,	a,	b,	midpoint,	diff (mV)
0,  	0,	63,	31,	    	24.26
1,  	31,	63,	47,	    	2.54
2,	    47,	63,	55,	    	-7.90
3,  	47,	55,	51,	    	-2.58
4,  	47,	51,	49,	    	0.03
Optimal trim value: 49
VREF value: 3.29024 V.
Bandgap value: 0.99948 V.




For Teensy 3.5 connected to my laptop's USB.
voltage_target = 3.28 V, the results are:

niter,	a,	b,	midpoint,	diff (mV)
0,  	0,	63,	31,	    	21.97
1,	    31,	63,	47,	    	0.27
Optimal trim value: 47
VREF value: 3.28154 V.
Bandgap value: 1.00387 V.




My output with a (quite old) Teensy 3.0 connected to my laptop's USB.
voltage_target = 3.22 V, the results are:

niter,	a,	b,	midpoint,	diff (mV)
0,  	0,	63,	31,		    37.34
1,  	31,	63,	47,	    	16.56
2,	    47,	63,	55,	    	5.54
3,	    55,	63,	59,	    	-0.00
Optimal trim value: 59
VREF value: 3.21947 V.
Bandgap value: 1.01978 V.


*/

#else  // make sure the example can run for any boards (automated testing)
void setup() {}
void loop() {}
#endif // ADC_USE_INTERNAL_VREF
 
Back
Top