NTC temperature sensor

I have a 10K NTC temperature sensor and I'm a bit lost how I can use it with my LoPy. How do I connect it? How do I read the temperature from it? I just know that I most probably need to use the adc functionality, but thats all.
Thanks for any hint!

@jmarcelino: Yes, Ive read that. But you would still have to compensate for the nonzero range (at least in your circuit). So at the moment and with limited requirements for precision, using the lower attenuation variants 0 and 6db and simply adding an offset would be sufficient as compensation.
If you need precise values, yes, then an external ADC is anyhow recommended.
B.T.W. Is there still the option to use the ADC in 10 bit mode, like by an option res = xx in the channel setup?

@roberthh
this is an open issue on ESPIDF and also the Arduino corehttps://github.com/espressif/espidf/issues/164
https://github.com/espressif/arduinoesp32/issues/92The official response from Espressif is:
The ADC frontend indeed has a nonlinear response. Once characterization data is ready, we will take it into account inside ADC APIs, and update the datasheet with correct values of INL/DNL and effective resolution.
So any efforts to compensate it at this stage will likely be rendered useless in a future update once the characterisation is complete (at least at ESPIDF/MicroPython level)
That's also why I suggested an external ADC for the time being, at least that won't change.

So I gave it a try, connected a power source to the input and read the average of 100 samples each. The results are not convincing.
a) Even at an average of 100 samples, there is a lot of noise..
b) it looks pretty nonlinear., at least at attn 3
results:attn = 3 value 0 until 125 mV 107 mV > 0 200 mV > 95 300 mV > 215 500 mV > 455 700 mV > 700 1000 mV > 1070 1500 mV > 1700 1600 mV > 1815 2000 mV > 2310 2500 mV > 2930 2900 mV > 3540 3000 mV > 3740 3100 mV > 3955 3170 mV > 4095 attn = 0 Value 0 until 57 mV 100 mV > 127 200 mV > 550 300 mV > 974 500 mV > 1820 700 mV > 2650 900 mV > 3510 1000 mV > 3933 1044 mV > 4095
If you look at that in a graph, at attn 3 the bends up beyond 2.5 V, while the attn=0 graph looks more linear, besides the 0Offset.
That's worse than I expected. The best one can get is to work with attn = 0 and use an external voltage divider.
Edit: I found a link that shows an experience, which pretty much matches mine:
http://www.esp32.com/viewtopic.php?t=1045
The Figures I got are pretty much the same. So 6db attn would be an option too.

As always, the information given by espressif is poor. Nothing compared to the manuals issued for instance by Atmel, STM or Freescale. It is unclear, how much the attenuation level really is, some say 11db, some say 12 db. 11db would be a factor 3.55, 12 db a factor 3,98 (10**(atten/20)) of the base range, which is ?. In any case, better give it a try. If you have a voltage meter, get the value at the input and scale it by:
Vrange = Vinput * (4095/reading)

@roberthh said in NTC temperature sensor:
if you requirement for precision is not extreme (like with 1% error)
There is no exterme precision requirement. 1% would be completely fine.
a) the zero point. look at which input voltage the output is different to zero. According to a comment below, this is somewhat like 0.1 V
b) the scaling: Look at which input voltage the output just reaches the maximum value.
c) nonlinearity. You could make a compensation table, or ignore it, if it is small enough and outside the range of interest.
d) Noise
a) and b) is easily compensated for. d) is reduced with taking an average of many samples, like you did. You do not have to wait a second in between, and a small capacitor like 10 nF with short leads directly at the ADC helps.Thanks for the inputs!
Another observation: You connect the NTC to 3.2 V (are you sure it's not 3.3V?).
It is connected to 3V3, so it is of course 3.3V. I've chosen 3.2V for
Vin
in the second equation because of the parameterattn=3
. I've read that in this threads: https://forum.pycom.io/topic/226/lopyadcs and here https://forum.pycom.io/topic/602/ntctemperaturesensor/6The ADC max range is at 3.6 V, the reading at 3.2 V would be 3631. Instead of 4095, you should therefore use 3631 as the virtual maximum value. Then, the resistor value is 9793 (10412 for 3.3 V), which is 2% off the nominal value, and depending of the precision class of the resistor that could be the real value.
Interesting. I must have missed this information in the documentation, I thought 3.3V is the maximum.

I think you could stil use the builtin ADC, if you requirement for precision is not extreme (like with 1% error). But you have to compensate the errors of the ADC.
a) the zero point. look at which input voltage the output is different to zero. According to a comment below, this is somewhat like 0.1 V
b) the scaling: Look at which input voltage the output just reaches the maximum value.
c) nonlinearity. You could make a compensation table, or ignore it, if it is small enough and outside the range of interest.
d) Noise
a) and b) is easily compensated for. d) is reduced with taking an average of many samples, like you did. You do not have to wait a second between the samples. A few ms is sufficient, if at all, and a small capacitor like 10 nF with short leads directly at the ADC helps.Another observation: You connect the NTC to 3.2 V (are you sure it's not 3.3V?). The ADC max range is at 3.6 V, the reading at 3.2 V would be 3631. Instead of 4095, you should therefore use 3631 as the virtual maximum value. Then, the resistor value is 9793 (10412 for 3.3 V), which is 2% off the nominal value, and depending of the precision class of the resistor that could be the real value.

@roberthh said in NTC temperature sensor:
resistance = SERIESRESISTOR * ((4095 / average)  1)
Yes, that looks better, I use the following test code now:
import machine import time THERMISTORPIN = 'P16' NUMSAMPLES = 5 SERIESRESISTOR = 10000 adc = machine.ADC(0) adcread = adc.channel(attn=3, pin=THERMISTORPIN) samples = [0.0]*NUMSAMPLES for i in range(NUMSAMPLES): samples[i] = adcread() print("sample %u: %u" %(i, samples[i])) time.sleep(1) average = 0 for i in range(NUMSAMPLES): average += samples[i] average /= NUMSAMPLES print("average sample: %u" %average) resistance = SERIESRESISTOR * ((4095 / average)  1) print("resistance: %u" %resistance) Vin = 3.2 buffer = average * Vin Vout = buffer / 4095 buffer = (Vin / Vout)  1 R2 = 10000 * buffer print("resistance2: %u" %R2)
and the output is:
sample 0: 1850 sample 1: 1836 sample 2: 1837 sample 3: 1845 sample 4: 1829 average sample: 1839 resistance: 12262 resistance2: 12262
Nevertheless, the ADC accuracy seems to be very bad so measuring the temperature this way seems useless. I'll try it with an external ADC as soon as it arrived.

I think it should. If you NTC is connected between Vcc and ADC, this line:
resistance = SERIESRESISTOR / ((4095 / average)  1)
should be:
resistance = SERIESRESISTOR * ((4095 / average)  1)
For instance:
at 50°C, the value of the NTC is 3588 Ohm. That should give an ADC reading of:4095 * (10000/(3588 + 10000)) = 3014
Put that into your formula, and you get 27881 Ohm
Put that into the other formula, and you get 3587 Ohm.
Regards, Robert

@roberthh said in NTC temperature sensor:
I'm still thinking how you have connected the thermistor a) between Vcc and ADC (like sketched below) or b) between ADC and GND. From your code, it looḱs like b)
It's currently connected like this:
3V3 +   ++   10k resistor (instead of NTC) ++   ++ P16   ++   10k resistor ++   + GND
Do you think the code doesn't fit this scheme?
@jmarcelino said in NTC temperature sensor:
use a much more accurate dedicated external ADC  connected over I2C for example
I've ordered a Velleman MM112 external ADC with I2C interface, I can then compare the values.

I'm still thinking how you have connected the thermistor a) between Vcc and ADC (like sketched below) or b) between ADC and GND. From your code, it looḱs like b)
And yes, there were quite a few notes about the ADC's poor performance. If you need more precise values, you should compensate at least for the actual range.

@tobru
There is some known nonlinearity in the ESP32 ADC which is not (yet) being compensated on the ESPIDF (and thus neither in micropython)https://twitter.com/ba0sh1/status/815759185816129536/
For example up to 0.1v it registers 0.0. So at the least you need to account for that.
You may be able to compensate these in software. Personally I'd just use a much more accurate dedicated external ADC  connected over I2C for example  for NTC measurements. For example a ADS1115 breakout can be had for less than $2.

@tobru I think the problem could be also caused by nonexactlyaccurate assumption about an ADC range. Even if we assume it's a 3.2V, it's still not the same as the one you are giving to thermister. I think it could cause some nonlinear behavior. (In arduino I think they are using external Vref in this mode.)

Line 32 should not be indented
Ouch. This of course should not have happened! Thanks a lot to @roberthh for spoting this! Looks much better now... But: It seems that the measurement is not really accurate, connecting the 10k resistor instead of the NTC thermistor should give me a value around this (I know that it won't be 100% perfect), but it looks like this:
sample 0: 1827 sample 1: 1869 sample 2: 1823 sample 3: 1843 sample 4: 1845 average sample: 1841 resistance: 8170
the code:
import machine import time THERMISTORPIN = 'P16' NUMSAMPLES = 5 SERIESRESISTOR = 10000 adc = machine.ADC(0) adcread = adc.channel(attn=3, pin=THERMISTORPIN) samples = [0.0]*NUMSAMPLES for i in range(NUMSAMPLES): samples[i] = adcread() print("sample %u: %u" %(i, samples[i])) time.sleep(1) average = 0 for i in range(NUMSAMPLES): average += samples[i] average /= NUMSAMPLES print("average sample: %u" %average) resistance = SERIESRESISTOR / ((4095 / average)  1) print("resistance: %u" %resistance)
Could it be that the ADC isn't very accurate? What can I do to get a more accurate result of the measurement? Or could it be that the equation for calculating the resistance is wrong?

I also think the problem was with average. I did not used your latest line (celsius = ((steinhart  32) / 1.8)  1) in my code and I think I got adequate results, but I didn't check it without thermistor connected. As of line 38  it is actualy "/", not "*"  it make 1/x

Hello @tobru. I see a small glitch in you code. Line 32 should not be indented. It should read:
average /= NUMSAMPLES
without the indents, otherwise the division is done in every loop.
and line 38 has to read
resistance = SERIESRESISTOR * resistance
The SteinhartHart equation calculates Kelvin. I found another example here, which may be the same you were using: https://gist.github.com/100ideas/1119533.

I've tried to convert the arduino code to MicroPython, but I'm not sure if I've done this right (especially the calculation part):
import machine import time import math # which analog pin to connect THERMISTORPIN = 'P16' # resistance at 25 degrees C THERMISTORNOMINAL = 10000 # temp. for nominal resistance (almost always 25 C) TEMPERATURENOMINAL = 25 # how many samples to take and average, more takes longer # but is more 'smooth' NUMSAMPLES = 5 # The beta coefficient of the thermistor (usually 30004000) BCOEFFICIENT = 3950 # the value of the 'other' resistor SERIESRESISTOR = 10000 # take N samples in a row, with a slight delay adc = machine.ADC(0) adcread = adc.channel(attn=3, pin=THERMISTORPIN) samples = [0.0]*NUMSAMPLES for i in range(NUMSAMPLES): samples[i] = adcread() print("sample %u: %u" %(i, samples[i])) time.sleep(1) # average all the samples out average = 0 for i in range(NUMSAMPLES): average += samples[i] average /= NUMSAMPLES print("average sample: %u" %average) # convert the value to resistance resistance = 4095 / average  1 resistance = SERIESRESISTOR / resistance print("resistance: %u" %resistance) steinhart = resistance / THERMISTORNOMINAL # (R/Ro) steinhart = math.log(steinhart) # ln(R/Ro) steinhart /= BCOEFFICIENT # 1/B * ln(R/Ro) steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15) # + (1/To) steinhart = 1.0 / steinhart # Invert steinhart = 273.15 # convert to C celsius = ((steinhart  32) / 1.8)  1 print("celsius: %u" %celsius)
To test it, I've connected a 10k resistor instead of the NTC thermistor. The output is:
sample 0: 1879 sample 1: 1857 sample 2: 1863 sample 3: 1878 sample 4: 1863 average sample: 466 resistance: 1284 celsius: 25
This looks good at first sight. But then I connected a 1k resistor instead of the NTC thermistor, this should output around 85c, but the output is this:
sample 0: 181 sample 1: 211 sample 2: 199 sample 3: 211 sample 4: 212 average sample: 52 resistance: 130 celsius: 75
The value isn't really correct. Any ideas what I'm doing wrong? I'm also wondering about the comment
# convert to C
in the Steinhart equation, the output looks more like Farenheit to me, this is why I added the conversion to celsius (hopefully correct).For the record: I'm using the following NTC thermistor: http://shop.boxtec.ch/temperatursensorntcmitstahlkappep42342.html

You can find some Arduino code here: https://github.com/adafruit/Thermistortest/blob/master/thermistortest.pde. Hope you can convert it to Python. Note that our ADC full range is 4096, so you should use 4095 instead of 1023

At least you need a known mapping between the temperature and the NTC value. If you have a data sheet of that part, you'll find it there.
From the adc value you can calculate back the resistor value of the NTC, and with the mapping between resistor value and the temperature the temperature.
Assuming that the ADC's range is set to 3.2 V, and the Range is 12 bit, and the connection is as shown below,R_NTC = R1 * (ADC_FULL_RANGE/adc_reading  1)
R1 = 10000, and ADC_FULL_RANGE = 4096
Line said below, you might take the average of a few readings, and you might have to interpolate between values not in your mapping table.

@tobru said in NTC temperature sensor:
Where do I find documentation about this?
I answer myself: Here in the forum under LoPy ADCs. Next step: How to calculate the temperature from the measured value. Any hints appreciated.