Author Topic: Thermistors, voltage dividers, and analogRead weirdness on the Oak  (Read 6754 times)

postuma

  • Jr. Member
  • **
  • Posts: 64
I've been trying to get temperatures using a simple 10k NTC thermistor and voltage divider circuit on the Oak's A0 pin, i.e.



Where R1 is a 10k, 1% metal film resistor, measures exactly 10.0k, and R2 is my thermistor.

Simple, right?

Only I've noticed the analogRead values were off.

I don't think it's the code, but here it is:

Code: [Select]
void setup() {
  Particle.begin();
}

void loop() { 
  Particle.print(getTemp());
  delay(1000);
}

float getTemp() {
  return analogRead(A0);
  //subsequent processing omitted for simplicity's sake
}


Somewhere I had read that the Oak's A0 pin maxed out at 1V and wondered if that had anything to do with it. (See https://github.com/digistump/OakCore/blob/master/doc/reference.md; I've seen it quoted elsewhere for the ESP8266, but not in any official documentation. I've looked.)

Turns out this is not true - using a 10k resistor for R2 should evenly split the voltage drop from VCC to A0 (measured at exactly 3.33V on my Oak). IF the A0 pin maxed out at 1V, applying 1.67V here should result in a max reading of 1023 (or 1024, depending on the ESP's design - turns out to be 1024 for values above max voltage). Actual analogRead() value - 528. More than the 512 we should get, but close.

The question then: is it predictably close, and can I adjust for it?

Using various known-good resistors I got the following:

R1R2predicted
   voltage drop
at R1
   predicted
voltage
at VA0
predicted %
   voltage drop
at R2
predicted
   analogRead
actual
   analogRead
delta
   notes
100002203.2580.07297.8%220-2.2%
4703.1810.14995.5%4638-0.7%
100001.6651.66550.8%5125281.6%
6060000.0543.27698.4%100610231.6%   *measured voltage 3.18V at A0
47000100002.7460.58417.5%179175-0.4%
470001.6651.66550.0%512494-1.8%
2200000.5862.74482.4%843790-5.2%
10000000.1493.18195.5%977904-7.1%
220000100003.1850.1454.3%4433-1.1%
470002.7440.58617.6%180162-1.8%
2200001.6651.66550.0%512396-11.3%
10000000.6002.73082.0%839560-27.2%
   30000000.2283.10293.2%953610   -33.5%

As you can see, the variance from expected goes from minimal to large, and not in a fashion I can predict.

The Oak puts a 220-ohm resistor between the A0 pin and the ESP8266's ADC input (schematic at https://s3.amazonaws.com/digistump-resources/files/6666015f_oak-prod-v1.pdf, and while I would expect that would alter (i.e. drop) the measured voltage slightly, I don't think it should have such an effect.

Anyone have any ideas? Have I missed something?

Give up on A0?

Interim solutions: run an ADS1015 or ADS1115 ADC board off I2C - you get four analog channels per board, can run up to four boards, and you get higher (12- or 16-bit) precision. Or use another temperature sensor; I'll be using the digital TMP102. With the Oak's limited number of pins, I've become very fond of I2C.

digi_guy

  • Jr. Member
  • **
  • Posts: 87
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #1 on: February 07, 2017, 04:41:16 pm »
Without reading your whole post, I'm going to guess that your problem is
Code: [Select]
Particle.print(getTemp());
Before you rip your hair out, try plugging in a potentiometer, and then use one of these options for sending data from A0:

Code: [Select]
        char publishTime[40];
        unsigned long now = millis();
        // now is in milliseconds
        unsigned nowSec = now/1000UL;
        unsigned sec = nowSec%60;
        unsigned min = (nowSec%3600)/60;
        unsigned hours = (nowSec%86400)/3600;
        sprintf(publishTime,"%u:%u:%u",hours,min,sec);
         Particle.publish("Uptime",publishTime);


Code: [Select]
       

int analogPin = A0;     // potentiometer wiper (middle terminal) connected to analog pin 3
int val = 0;           // variable to store the value read
double Vref = 3.18;
double reading = 3.3;
char buf;
String s;
char publishTemp[40];
        reading = analogRead(A0);    // read the input pin and convert to C
        Vout = reading*Vref/1024;
        temp = (Vout - 1.25)/0.005;
        s = String(temp);
        s.toCharArray(publishTemp, 40);
        Particle.publish("TempC",publishTemp);

Code: [Select]
String msg = String(analogRead(A0));
Particle.publish("Status", msg.c_str());

I wasted a lot of time before I realized that the Particle functions were sending garbage.

postuma

  • Jr. Member
  • **
  • Posts: 64
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #2 on: February 08, 2017, 02:13:29 pm »
fair enough - but I had tested that as well, using the results from a DS3231 timer. It wasn't the problem.

I did test with a potentiometer - the analogRead() of 1023 was obtained using a pot. That's how I figured out that the A0 pin maxed out at a measured 3.18V; when I dialed up the pot's resistance just above this level it went to 1024. I left out some of my intermediate steps, and multiple pot readings, for clarity's sake - but they correspond exactly to what I got with fixed resistors.

What I posted is the stripped-down version of both the code and the results that still illustrates the problem - less fluff for others to wade through.

Thanks for the suggestions, tho .. 
« Last Edit: February 08, 2017, 02:20:13 pm by postuma »

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #3 on: February 08, 2017, 08:48:40 pm »
I haven't run up an Oak to verify this, but I think from a quick probe with a multimeter that the voltage divider shown on the schematic is enabled by default (i.e. the SJ1 trace is not cut/broken), thus scaling the ADC pin to have a maximum of approximately 3.3v (i.e. same as VCC), not 1v like a bare ESP8266 would. You would need to cut that trace to make it so it was *just* the 220k resistor inline, if you wanted it to have a 1v peak.

postuma

  • Jr. Member
  • **
  • Posts: 64
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #4 on: February 09, 2017, 05:09:19 pm »
Correct - as I said in my post, VCC measures 3.33V as is. With 3.18V on A0 I get an analogRead of 1023; anything higher gives me 1024.

If I cut the trace, then I would lose the discrimination the full VCC range offers. Not a big deal, so long as R1 is at least 2.3x larger than the highest resistance I expect for the temp range given.

Will try and see if going with the ESP8266's 1.0V ADC pin (with Oak's 220-ohm on-board resistor) will eliminate those huge variances.

postuma

  • Jr. Member
  • **
  • Posts: 64
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #5 on: February 09, 2017, 07:30:23 pm »
Chop, chop, scratch, scratch:



simple voltage divider with two 10k resistors should put 1.67V to the A0 pin, soooo should produce a max reading of 1024. Confirmed.

R1 of 47k and R2 of 10k should give me 0.584V at A0 (again, my Oak's VCC measures 3.33V), for an analogRead of 0.584 * 1023 = 598. The 220Ω resistor would drop that to 0.581V, for an analogRead of 595. Reported analogRead is 561.

(FWIW, using a voltage of 3.3 produces a predicted value of 590. Lowering this value to 3.14V gets me to 561. But it's not what the voltage drop measures.)

I will test this with a potentiometer over a wider range and see if the relationship between analogRead values and expected values at least is a linear, reproducible one. And play with different source voltages for the math. So far not looking too good. Will report.

For anyone interested - analogRead on A0 works fine for measurements that do not require great precision, i.e. a photocell that triggers some action at an arbitrarily chosen setpoint. Alternately, it can be used for precise cutoffs, so long as you exactly reproduce that setpoint in order to determine its corresponding analogRead value.

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #6 on: February 10, 2017, 12:37:20 am »
Bugger! Unfortunately it is what I would have expected from this type of MCU... especially with the great voltage range it has for sampling. I did a bit of quick searching, as I seemed to remember reading something about the ADC being even less reliable/stable when the WiFi was on/connected (great feature for a WiFi board! :-P)... maybe that might be worth looking into also... is it at least being consistent in giving the *wrong* readings... or is it varying a bit? At least if it give the wrong readings you should be able to work out some sort of scaling/offset... or just give up and go for a higher precision I2C ADC like you were mentioning earlier.

https://github.com/esp8266/Arduino/issues/2070

postuma

  • Jr. Member
  • **
  • Posts: 64
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #7 on: February 10, 2017, 03:19:49 pm »
Thanks Peter! Very useful/informative thread; I wish I'd found it earlier.

I did test my readings using multiple resistances on the revised 1V circuit and values are off by 1.5 to 4.8% for the range examined. Maybe not horrible, but not good enough, and the association is non-linear, so not easy to correct for.

Additionally, there's quite a bit of variation on the pin - readings can fluctuate by up to 6, only milliseconds apart, with fixed-value resistors. This is in line with what is reported in that thread. They've noted that toggling WiFi sleep mode can correct for this fluctuation, but the Oak's Arduino API lacks this functionality. Given the lack of accuracy on A0, it's really not worth porting sleep mode code to the Oak.

I'll try either the ADS1015 ADC board or a TMP102 off I2C, whichever reaches my mailbox first.

postuma

  • Jr. Member
  • **
  • Posts: 64
Re: Thermistors, voltage dividers, and analogRead weirdness on the Oak
« Reply #8 on: March 12, 2017, 03:46:37 pm »
So I have my ADS1015 now and have had a chance to test it - perfect! Analog readings are exactly what they should be, with some fluctuations I believe related to the thermistor itself.

The lesson learned: the ADC on the Oak is crap. For non-critical purposes it will work, but don't count on it to give you precision. If you want/need analog inputs: the ADS1015/ADS1115 ADCs work well and run off the I2C bus so can co-exist with other items on the SDA/SCL pins. Each offers 4 unidirectional or two bidirectional ADCs, and up to four of them can be connected simultaneously. They have greater sensitivity: 12-bit discrimination for the ADS1015, 16-bit for ADS1115. And you can specify a voltage range to measure against, from a gain of 16x (measuring from -0.256 to 0.256V) to 2/3x (-6.144 to 6.144V). Handy.

Since VCC for the Oak is 3.34V (as measured by my oscilloscope), I like to specify a 2x gain, for a range of +/- 2.048V, and make sure that my fixed resistor R1 will drop the voltage to at least that value over the range of values I want to measure, for max discrimination.

Downsides: typical operating current for the ADS1015 is about 150 μA. At 25C, the voltage divider series draws another 33 μA, and if I'd used a 10K resistor instead of a 91K one, it would draw five times as much.

For measuring temps, the TMP102 is more accurate, and has a sleep mode that draws under 1 μA. Even during operation, it sips 10 μA, so this is what I'll actually be using for my temperature measurements.

For other analog readings, the ADS1015/ADS1115 ADCs are my new go-to.

Code: [Select]
/*
Calculate temperature using 10K thermistor attached to an Adafruit ADS1015 Analog-to-Digital Converter on the I2C communication bus

R1 is a 91k metal-film resistor, chosen because a) it's a metal-film resistor I had lying around, and b) combined with a 10k thermistor,
for the temperature range I'm interested in (plus a little room for the unexpected, i.e. down to -28C/-19F), it gives voltages at
the analog pin of up to 2.048V. This exactly matches the two times gain setting of the ADS1015 (measuring from 0 to 2.048V) and so I get
the benefit of 12-bit sensitivity over the full temperature range I'm interested in.
*/

#include <Wire.h>
#include <Adafruit_ADS1015.h>

#define RESISTOR_FIXED 91000                            //R1 in ohms
#define VCC 3.34

Adafruit_ADS1015 ADS1015;


void setup() {
  ADS1015.begin();
  ADS1015.setGain(GAIN_TWO);                            //gain two: voltage = 1 mV per bit; good resolution for the values I'm looking at - up to 2.048V at A0
 
  Particle.begin();
}


void loop() {
  Particle.print(getTemp());
  delay(1000);
}


float getTemp() {                                       //average four analog readings from thermistor and returns temp in Celsius
  float f = 0;
  for (uint8_t i = 0; i < 4; i++) {
    f += ADS1015.readADC_SingleEnded(0);                //read analog value at A0
    delay(1);
  }
  f /= 4000;                                            //average 4 readings and divide by 1000 for voltage at A0
  f = (RESISTOR_FIXED * f) / (VCC - f);                 //calculate resistance at thermistor (R2)
  f = log(f);                                           //Steinhart-Hart equation using coefficients for the 10K thermistor
  f = 1 / (0.00112531 + 0.000234712 * f + 0.0000000856635 * f * f * f);
  return f -= 273.15;                                   //convert Kelvin to Celsius
}