Welcome to the Pycom forum Forum
    • \[\[global:header.categories\]\]
    • \[\[global:header.recent\]\]
    • \[\[global:header.tags\]\]
    • \[\[global:header.popular\]\]
    • Login

    Explore Pybytes | Official Documentation | Report a Firmware Bug/Issue | GitHub

    NTC temperature sensor

    Scheduled Pinned Locked Moved LoPy
    sensorslopyadctemperaturentc
    26 Posts 6 Posters 15.1k Views 6 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • S Offline
      slavik73
      last edited by

      You can find some Arduino code here: https://github.com/adafruit/Thermistor-test/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

      T 1 Reply Last reply Reply Quote 0
      • T Offline
        tobru @slavik73
        last edited by

        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 3000-4000)
        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/temperatursensor-ntc-mit-stahlkappe-p-42342.html

        1 Reply Last reply Reply Quote 0
        • robert-hhR Offline
          robert-hh
          last edited by robert-hh

          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 Steinhart-Hart equation calculates Kelvin. I found another example here, which may be the same you were using: https://gist.github.com/100ideas/1119533.

          1 Reply Last reply Reply Quote 0
          • S Offline
            slavik73
            last edited by slavik73

            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

            1 Reply Last reply Reply Quote 0
            • T Offline
              tobru
              last edited by

              Line 32 should not be indented

              Ouch. This of course should not have happened! Thanks a lot to @robert-hh 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?

              S J 2 Replies Last reply Reply Quote 0
              • S Offline
                slavik73 @tobru
                last edited by

                @tobru I think the problem could be also caused by non-exactly-accurate 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 non-linear behavior. (In arduino I think they are using external Vref in this mode.)

                1 Reply Last reply Reply Quote 0
                • J Offline
                  jmarcelino @tobru
                  last edited by jmarcelino

                  @tobru
                  There is some known non-linearity in the ESP32 ADC which is not (yet) being compensated on the ESP-IDF (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.

                  1 Reply Last reply Reply Quote 0
                  • robert-hhR Offline
                    robert-hh
                    last edited by robert-hh

                    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.

                    T 1 Reply Last reply Reply Quote 0
                    • T Offline
                      tobru @robert-hh
                      last edited by

                      @robert-hh 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.

                      1 Reply Last reply Reply Quote 0
                      • robert-hhR Offline
                        robert-hh
                        last edited by robert-hh

                        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

                        T 1 Reply Last reply Reply Quote 0
                        • T Offline
                          tobru @robert-hh
                          last edited by

                          @robert-hh 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.

                          1 Reply Last reply Reply Quote 0
                          • robert-hhR Offline
                            robert-hh
                            last edited by robert-hh

                            I think you could stil use the built-in 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.

                            T 1 Reply Last reply Reply Quote 0
                            • T Offline
                              tobru @robert-hh
                              last edited by

                              @robert-hh 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 parameter attn=3. I've read that in this threads: https://forum.pycom.io/topic/226/lopy-adcs and here https://forum.pycom.io/topic/602/ntc-temperature-sensor/6

                              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.

                              Interesting. I must have missed this information in the documentation, I thought 3.3V is the maximum.

                              1 Reply Last reply Reply Quote 0
                              • robert-hhR Offline
                                robert-hh
                                last edited by

                                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)

                                1 Reply Last reply Reply Quote 0
                                • robert-hhR Offline
                                  robert-hh
                                  last edited by robert-hh

                                  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 0-Offset.
                                  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.

                                  J 1 Reply Last reply Reply Quote 0
                                  • J Offline
                                    jmarcelino @robert-hh
                                    last edited by jmarcelino

                                    @robert-hh
                                    this is an open issue on ESP-IDF and also the Arduino core

                                    https://github.com/espressif/esp-idf/issues/164
                                    https://github.com/espressif/arduino-esp32/issues/92

                                    The official response from Espressif is:

                                    The ADC frontend indeed has a non-linear 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 ESP-IDF/MicroPython level)

                                    That's also why I suggested an external ADC for the time being, at least that won't change.

                                    1 Reply Last reply Reply Quote 0
                                    • robert-hhR Offline
                                      robert-hh
                                      last edited by

                                      @jmarcelino: Yes, Ive read that. But you would still have to compensate for the non-zero 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 set-up?

                                      1 Reply Last reply Reply Quote 0

                                      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                                      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                                      With your input, this post could be even better 💗

                                      Register Login
                                      • 1
                                      • 2
                                      • 1 / 2
                                      • First post
                                        Last post

                                      Pycom on Twitter

                                      Pycom