ADC voltage measurements wrong and jumping on Teensy 4.1

Status
Not open for further replies.

nilsvs

New member
ADC voltage measurements on voltage divider wrong and jumping on Teensy 4.1

Hi,
I have a Teensy 4.1 connected to my PC via USB. With its ADC I want to measure the voltage of an external power supply (for this test I am using a SK1730SL3A lab PSU so I can vary the voltage). I want to be able to measure voltages from 0-15 VDC, so I have selected the following resistor values for my voltage divider. R1=470kOhm, R2=100kOhm. If I do the math correct, 0.0V ext. PSU voltage will then be 0.0V (ADC raw value=0) on the Teensy ADC input pin, and 18.74V PSU voltage would be the highest voltage that could be measured with that circuit, which should be around 3.3V (ADC raw value=1023) at the Teensy ADC input. I want to measure 0-15VDC, so that should be fine.

So to make sure my voltage divider works correctly, I probe with a multimeter the voltage divider output as I sweep the external PSU voltage from 0-18.74V and it indeed translates to 0-3.3V as expected. A quick sanity check about linearity shows that 12V input=2.09V output and 3.3V input=0.58V at the voltage divider output, so it works fine.

Now I am ready to read this voltage with the Teensy, so I connect the output of the voltage divider to my Teensy ADC input (A2, which is pin 16).
teensy_4.1_adc_voltage_measurement_setup.png
I run the following code to read the raw ADC value (and from that calculated PSU input voltage) and print that out to the serial port:
Code:
// Analog inputs:
const int analogInPin_PSU_VOLTAGE = A2;  // Analog input pin A2 is pin 16 on PCB
const float adc_scaling = 3.3 / 1024.0;  // TODO: divide by 1023 or 1024 ?  // TODO: use measured ~3.28V here instead?

// Variables
float psu_voltage = 0.0;

void setup() {
  // init serial comms at 9600 bps
  Serial.begin(9600);
}


void loop() {
  if (Serial.available()) {
    float psu_voltage = readADC(analogInPin_PSU_VOLTAGE);
    Serial.print("PSU voltage: ");
    Serial.println(psu_voltage);
    delay(100);  // Sample every 100ms
  }
}


float readADC(int analogPin){
  int adc_value;
  float adc_voltage;
  float measured_voltage;
  adc_value = analogRead(analogPin);
  Serial.print("RAW ADC value: ");
  Serial.println(adc_value);
  adc_voltage = adc_value * adc_scaling;
  measured_voltage = (adc_voltage * (470000.0+100000.0)) / 100000.0;

  return measured_voltage;
}

I watch the serial console output, which prints every 100ms an update of the currently measured ADC raw value as well as the calculated PSU input voltage.
Now the weird thing happens: With the Teensy ADC input now connected to the voltage divider, the output voltage of the voltage divider has changed. 12.0V PSU input voltage is now 2.52V at the voltage divider output (it should be 2.09V). 3.3V PSU input voltage is now 0.36V on the voltage divider output (it should be 0.58V). The teensys displayed values are wrong accordingly.

Another interesting thing is when ramping up the PSU voltage from 0-18.7V that when the ADC raw value reaches 497 it instantly jumps to 879. Probing with my multimeter the voltage on the voltage divider output confirms that jump, its also visible there. When ramping down from 18.7-0.0V PSU voltage there is a jump at ADC raw value 531, which then jumps down instantly to 160 (again multimeter confirms the voltage at the output of the voltage divider really jumps).

What is happening here? How does connecting the Teensy ADC pin to the voltage divider output mess around this much with the voltage divider output voltage and cause these weird jumps? Did I select wrong voltage divider resistor values? I have tried other ADC pins on the Teensy and its the same problem. Probing with my multimeter the teensy ADC input pin when not connected to anything measures ~3.23V, I dont know if that is to be expected.

Any suggestions would be very welcome.
 
Last edited:
I don't know what's happening exactly, but the pins (well I tried A2 and A8 on a T4.0) are sticky to the
rails.

You can verify this by sticking a 'scope probe on a pin and then briefly connecting the pin to 0V or 3.3V with
a jumper. The state sticks.

I measured the bias current when in the high state by switching my probe from x10 to x1, in which case
the analogRead value dropped from 1001 to 905, suggesting about 130k of pullup internally.

My guess is the Schmitt-triggers for digital I/O are configured by default, and this is the leak-back from them.

Try 22k and 4k7 for the divider to minimize this effect perhaps?

[ Ah, its simple, just go pinMode(.., INPUT) in setup() - this is different from the T3's I think ]
 
Thank you for the quick reply. Setting pinMode(PIN_A2, INPUT); in setup() indeed fixes the problem. Thank you very much!

For future reference, for who might stumble across the same, this is the final code that works:
Code:
// Analog inputs:
const int analogInPin_PSU_VOLTAGE = A2;  // Analog input pin A2 is pin 16 on PCB
const float adc_scaling = 3.3 / 1024.0;  // TODO: divide by 1023 or 1024 ?  // TODO: use measured ~3.28V here instead?

// Variables
float psu_voltage = 0.0;

void setup() {
  // init serial comms at 9600 bps
  Serial.begin(9600);
  // Set that the analog input pin A2 is an INPUT (important, otherwise readings will be off)
  pinMode(PIN_A2, INPUT);
}


void loop() {
  if (Serial.available()) {
    float psu_voltage = readADC(analogInPin_PSU_VOLTAGE);
    Serial.print("PSU voltage: ");
    Serial.println(psu_voltage);
    delay(100);  // Sample every 100ms
  }
}


float readADC(int analogPin){
  int adc_value;
  float adc_voltage;
  float measured_voltage;
  adc_value = analogRead(analogPin);
  Serial.print("RAW ADC value: ");
  Serial.println(adc_value);
  adc_voltage = adc_value * adc_scaling;
  measured_voltage = (adc_voltage * (470000.0+100000.0)) / 100000.0;

  return measured_voltage;
}
 
Incidentally according to this thread: https://forum.pjrc.com/threads/34319-Analog-input-impedance-and-pull-up (and my tests on a T3.2) the solution is the opposite on a T3, namely you must not call pinMode() on an analog pin to avoid this clamping/non-linear input effect.

That's the way it was remembered here too.

Looking at T_4.x pinMode it seems the Hysteresis is enabled - except on INPUT - this IIRC seeing noted drops the current used by idle pins.

Here is the setting for INPUT_DISABLE setting it on:
Code:
   87              } else { // INPUT_DISABLE
   88:                 *( p->pad ) = IOMUXC_PAD_DSE( 7 )[B] | IOMUXC_PAD_HYS[/B];
 
Interesting topic. I checked the ADC examples in TeensyDuino, and there is one called adc_test that exercises analog inputs with pull-up, pull-down, range, and other options. The setup() function is pasted below. Probably a good exercise to run it.

Code:
void setup()
{

  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(9600);

  ///// ADC0 ////
  // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REFERENCE::REF_EXT.
  //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

  adc->adc0->setAveraging(16);                                    // set number of averages
  adc->adc0->setResolution(12);                                   // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED);     // change the sampling speed
  adc->adc0->recalibrate();

////// ADC1 /////
#ifdef ADC_DUAL_ADCS
  adc->adc1->setAveraging(16);                                    // set number of averages
  adc->adc1->setResolution(12);                                   // set bits of resolution
  adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); // change the conversion speed
  adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED);     // change the sampling speed
  adc->adc1->recalibrate();
#endif

  delay(2000);

  ////// START TESTS /////////////
  // do the tests

  bool pullup_test = test_pullup_down(true, false);
  resetSettings();
  Serial.print("PULLUP TEST ");
  Serial.println(pullup_test ? "PASS" : "FAIL");
  bool pulldown_test = test_pullup_down(false, false);
  resetSettings();
  Serial.print("PULLDOWN TEST ");
  Serial.println(pulldown_test ? "PASS" : "FAIL");
  bool compare_test = test_compare(true);
  resetSettings();
  Serial.print("COMPARE TEST ");
  Serial.println(compare_test ? "PASS" : "FAIL");
  bool compare_range_test = test_compare_range(true);
  resetSettings();
  Serial.print("COMPARE RANGE TEST ");
  Serial.println(compare_range_test ? "PASS" : "FAIL");
  bool averages_test = test_averages(true);
  resetSettings();
  Serial.print("AVERAGES TEST ");
  Serial.println(averages_test ? "PASS" : "FAIL");
  bool all_combinations_test = test_all_combinations(true);
  resetSettings();
  Serial.print("ALL COMBINATIONS TEST ");
  Serial.println(all_combinations_test ? "PASS" : "FAIL");
  if (pullup_test & pulldown_test & compare_test & compare_range_test & averages_test & all_combinations_test)
  {
    Serial.println("ALL TEST PASSED.");
  }
}
 
Last I knew, good teensy ADC practice was pinMode(xx, INPUT_DISABLE) and a low impedance source.
 
Status
Not open for further replies.
Back
Top