ESP8266 and DS18B20: Transmitting Temperature Data

(click here to see index of all ESP8266 posts)

I’m ready to tie together much of what I’ve learned to read a DS18B20 temperature sensor using the oneWire protocol and transmit the temperature via UDP to an echo server. (In a future post I will act upon the received data.)

Wiring the Sensor

Very straight forward:

esp-ds18b20-fig1

here is what it looks like:

esp-ds18b20-fig2

Testing the DS18B20

I entered the following onewire/ds18b20 test code straight into my ESP8266, just changing the pin:

https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_examples/onewire-ds18b20.lua

This worked fine:

40    23    200    64    5    0    0    9
Device is a DS18S20 family device.
P=1
177    1    75    70    127    255    15    16    141
CRC=141
Temperature= 27.625 Centigrade

I decided to make a few changes so I could see the address and data in hex form:

pin = 4
ow.setup(pin)
count = 0
repeat
  count = count + 1
  addr = ow.reset_search(pin)
  addr = ow.search(pin)
  tmr.wdclr()
until((addr ~= nil) or (count > 100))
if (addr == nil) then
  print("No more addresses.")
else
  --print(addr:byte(1,8))
  s=string.format("Addr:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", 
    addr:byte(1),addr:byte(2),addr:byte(3),addr:byte(4), 
    addr:byte(5),addr:byte(6),addr:byte(7),addr:byte(8))
  print(s)
  crc = ow.crc8(string.sub(addr,1,7))
  if (crc == addr:byte(8)) then
    if ((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then
      print("Device is a DS18S20 family device.")
        repeat
          ow.reset(pin)
          ow.select(pin, addr)
          ow.write(pin, 0x44, 1)
          tmr.delay(1000000)
          present = ow.reset(pin)
          ow.select(pin, addr)
          ow.write(pin,0xBE,1)
          print("P="..present)  
          data = nil
          data = string.char(ow.read(pin))
          for i = 1, 8 do
            data = data .. string.char(ow.read(pin))
          end
          --print(data:byte(1,9))
          s=string.format("Data:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", 
            data:byte(1),data:byte(2),data:byte(3), data:byte(4),
            data:byte(5),data:byte(6), data:byte(7),data:byte(8))
          print(s)
          crc = ow.crc8(string.sub(data,1,8))
          --print("CRC="..crc)
          s=string.format("CRC: %02X", crc)
          print(s)
          if (crc == data:byte(9)) then
             t = (data:byte(1) + data:byte(2) * 256) * 625
             t1 = t / 10000
             t2 = t % 10000
             print("Temperature= "..t1.."."..t2.." Centigrade")
          end                   
          tmr.wdclr()
        until false
    else
      print("Device family is not recognized.")
    end
  else
    print("CRC is not valid!")
  end
end

getTemp: Function to Return Current Temperature

Once I knew I could access the DS18B20, I reworked the code into a function that, when called, simply returns the temperature. It returns the temp with an implied decimal point, 4 places from the right (99V9999):

function getTemp()

    local addr      = nil
    local count     = 0
    local data      = nil
    local pin       = 4             -- pin connected to DS18B20
    local s         = ''

    -- setup gpio pin for oneWire access
    ow.setup(pin)

    -- do search until addr is returned
    repeat
        count   = count + 1
        addr    = ow.reset_search(pin)
        addr    = ow.search(pin)
        tmr.wdclr()
        until((addr ~= nil) or (count > 100))

    -- if addr was never returned, abort
    if (addr == nil) then
        print('DS18B20 not found')
        return -999999
        end
  
    s=string.format("Addr:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", 
        addr:byte(1),addr:byte(2),addr:byte(3),addr:byte(4), 
        addr:byte(5),addr:byte(6),addr:byte(7),addr:byte(8))
    --print(s)

    -- validate addr checksum
    crc = ow.crc8(string.sub(addr,1,7))
    if (crc ~= addr:byte(8)) then
        print('DS18B20 Addr CRC failed');
        return -999999
        end

    if not((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then
        print('DS18B20 not found')
        return -999999
        end
        
    ow.reset(pin)               -- reset onewire interface
    ow.select(pin, addr)        -- select DS18B20
    ow.write(pin, 0x44, 1)      -- store temp in scratchpad
    tmr.delay(1000000)          -- wait 1 sec
    
    present = ow.reset(pin)     -- returns 1 if dev present
    if present ~= 1 then
        print('DS18B20 not present')
        return -999999
        end
    
    ow.select(pin, addr)        -- select DS18B20 again
    ow.write(pin,0xBE,1)        -- read scratchpad

    -- rx data from DS18B20
    data = nil
    data = string.char(ow.read(pin))
    for i = 1, 8 do
        data = data .. string.char(ow.read(pin))
        end
    
    s=string.format("Data:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", 
        data:byte(1),data:byte(2),data:byte(3), data:byte(4),
        data:byte(5),data:byte(6), data:byte(7),data:byte(8))
    --print(s)

    -- validate data checksum
    crc = ow.crc8(string.sub(data,1,8))
    if (crc ~= data:byte(9)) then
        print('DS18B20 data CRC failed')
        return -9999
        end

    -- compute and return temp as 99V9999 (V is implied decimal-a little COBOL there)
    return (data:byte(1) + data:byte(2) * 256) * 625
    
    end -- getTemp

This function is tested by simply sending

> =getTemp()
246250
>

The temperature returned is 24.6250 degrees Celsius.

xmitTemp: Transmitting the Temperature

Now I need a function that will get the temperature and transmit it via UDP. Note that the temp is converted to a string before sending it.

function xmitTemp()
    local temp = 0

    temp = getTemp()

    -- if something went wrong, don't transmit
    if temp == -999999 then
        return
        end

    -- transmit the temperature to the host
    cu:send(tostring(temp))

    end -- xmitTemp

Initialization

I need to initialize networking which I do with this function:

function initWIFI()
    print("Setting up WIFI...")

    wifi.setmode(wifi.STATION)

    wifi.sta.config("SSID","PASSWORD")
    wifi.sta.connect()
    tmr.alarm(1, 1000, 1,   
        function() 
            if wifi.sta.getip()== nil then 
                print("IP unavailable, Waiting...") 
            else 
                tmr.stop(1)
                print("Config done, IP is "..wifi.sta.getip())
                end 
            end -- function
        )
    end -- initWIFI

and setup the UDP connection. I am setting up a UDP connection to the echo service on my Raspberry Pi which allows me to easily verify that the ESP8266 is properly transmitting:

function initUDP()

    -- setup UDP port
    cu=net.createConnection(net.UDP)
    cu:connect(7,"192.8.50.106")
    end -- initUDP

Tying it Together

Finally, the initialization functions must be called, and then xmitTemp is called based on a timer. For my initial testing, I am using a 10 second interval.

initWIFI()
initUDP()
tmr.alarm(0, 10000, 1, xmitTemp)

The Entire Program

Using my prior idea for init.lua, here is my init.lua which allow boot termination by setting the abort variable:

function startup()
    if abort == true then
        print('startup aborted')
        return
        end
    print('Starting xmitTemp')
    dofile('xmitTemp.lua')
    end

abort = false
print('Startup in 5 seconds')
tmr.alarm(0,5000,0,startup)

and here is the xmitTemp.lua program in its entirety:

function getTemp()

    local addr      = nil
    local count     = 0
    local data      = nil
    local pin       = 4             -- pin connected to DS18B20
    local s         = ''

    -- setup gpio pin for oneWire access
    ow.setup(pin)

    -- do search until addr is returned
    repeat
        count   = count + 1
        addr    = ow.reset_search(pin)
        addr    = ow.search(pin)
        tmr.wdclr()
        until((addr ~= nil) or (count > 100))

    -- if addr was never returned, abort
    if (addr == nil) then
        print('DS18B20 not found')
        return -999999
        end
  
    s=string.format("Addr:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", 
        addr:byte(1),addr:byte(2),addr:byte(3),addr:byte(4), 
        addr:byte(5),addr:byte(6),addr:byte(7),addr:byte(8))
    --print(s)

    -- validate addr checksum
    crc = ow.crc8(string.sub(addr,1,7))
    if (crc ~= addr:byte(8)) then
        print('DS18B20 Addr CRC failed');
        return -999999
        end

    if not((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then
        print('DS18B20 not found')
        return -999999
        end
        
    ow.reset(pin)               -- reset onewire interface
    ow.select(pin, addr)        -- select DS18B20
    ow.write(pin, 0x44, 1)      -- store temp in scratchpad
    tmr.delay(1000000)          -- wait 1 sec
    
    present = ow.reset(pin)     -- returns 1 if dev present
    if present ~= 1 then
        print('DS18B20 not present')
        return -999999
        end
    
    ow.select(pin, addr)        -- select DS18B20 again
    ow.write(pin,0xBE,1)        -- read scratchpad

    -- rx data from DS18B20
    data = nil
    data = string.char(ow.read(pin))
    for i = 1, 8 do
        data = data .. string.char(ow.read(pin))
        end
    
    s=string.format("Data:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", 
        data:byte(1),data:byte(2),data:byte(3), data:byte(4),
        data:byte(5),data:byte(6), data:byte(7),data:byte(8))
    --print(s)

    -- validate data checksum
    crc = ow.crc8(string.sub(data,1,8))
    if (crc ~= data:byte(9)) then
        print('DS18B20 data CRC failed')
        return -9999
        end

    -- compute and return temp as 99V9999 (V is implied decimal-a little COBOL there)
    return (data:byte(1) + data:byte(2) * 256) * 625
    
    end -- getTemp

function xmitTemp()
    local temp = 0

    temp = getTemp()
    if temp == -999999 then
        return
        end

    cu:send(tostring(temp))

    end -- xmitTemp

function initUDP()

    -- setup UDP port
    cu=net.createConnection(net.UDP)
    cu:connect(7,"192.8.50.106")
    end -- initUDP
    
function initWIFI()
    print("Setting up WIFI...")

    wifi.setmode(wifi.STATION)

    wifi.sta.config("SSID","PASSWORD")
    wifi.sta.connect()
    tmr.alarm(1, 1000, 1,   
        function() 
            if wifi.sta.getip()== nil then 
                print("IP unavailable, Waiting...") 
            else 
                tmr.stop(1)
                print("Config done, IP is "..wifi.sta.getip())
                end 
            end -- function
        )
    end -- initWIFI

initWIFI()
initUDP()
tmr.alarm(0, 10000, 1, xmitTemp)

This program works great. Upon initialization, it gets the network running, sets up the UDP connection, then starts transmitting temperature packets every 10 seconds:

esp-ds18b20-fig3

 

This entry was posted in c-esp8266 and tagged . Bookmark the permalink.

19 Responses to ESP8266 and DS18B20: Transmitting Temperature Data

  1. James says:

    Hi Dan,

    I think the gremlins have been messing with your code, this line looks a bit odd:

    wifi.sta.config(“SSID”,”PASSWORDwifi.stawifi.sta.connect()

    Same thing appears in the section discussing the network initialization. Darn gremlins are always running a muck through my code!

    Good example of reading a sensor and sending UDP packets – thanks.

    Regards,

    James

  2. Pingback: SNMP Environmental Monitoring using ESP8266-based Sensors | Big Dan the Blogging Man

  3. Hi,
    While running the “I decided to make a few changes so I could see the address and data in hex form” I’m getting only (in a loop):

    P=1
    Data:FF-FF-FF-FF-FF-FF-FF-FF
    CRC: C9
    P=1
    Data:FF-FF-FF-FF-FF-FF-FF-FF

    (same happens with the original code you mentioned)
    Yet the code from here: https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_modules/ds18b20/ds18b20.lua) produces good results (but has just a precision of 1 centigrade).

    I’m using ESP-01 with NodeMU 0.9.6, and tried WITH and WITHOUT 4,7k resistor. PIN is correct – in both examples it’s set to 4.

    Any ideas ?

    • Dan TheMan says:

      Hi Lukas,

      I’ve had several comments now from people who are finding ‘mistakes’ in my various examples. Except, they are correct when I look at them. I am doing nothing special for the examples, except they are in html preformatted form, but that feature has been around since HTML 1.0. You might try looking at the examples with a different browser, or maybe bring up the HTML source and look at the code there. I am using firefox and everything is correct in firefox.

      I can only tell you that when I did the code myself, it worked as documented.

      If the code example appears to be butchered and nothing you do shows it correct, let me know and I’ll copy the code to my ftp server to make it available to you.

      • Dan, thanks for quick replay. First – great work on this blog – helps me a lot, and I didn’t thank you first.

        Now I don’t think it’s the code you wrote. It executes OK, but as you suggest – it may be copying from html…But first – it may be the NodeMU firmware – I got the one dated: NodeMCU 0.9.6 build 20150704 powered by Lua 5.1.4 – which version of the FW you’re using?

      • Dan TheMan says:

        Well…. I was hoping I could find a version in one of my examples, but I never mention it. I was working with ESP8266 back in April, so I definitely didn’t have the 20150704 version of the firmware. That’s about the most I can tell you.

        About the only advice I can think to give you is to break the script down piece by piece and see where things go wrong. In glancing at it now, I don’t see any obvious reason for a failure, but that project was also the one and only time I’ve ever used Lua, so I could easily be missing something stupid.

      • Yep, I still want to check it – it’s so much fun to play with this little wonders! Thanks Dan for the code. I’ll post my findings later

  4. harleydk says:

    @Lukasz,

    I had the ‘FF FF FF’-problem too, but was able to resolve it using tiking’s suggesting (http://www.instructables.com/id/Low-cost-WIFI-temperature-data-logger-based-on-ESP/)

    “There is a documented bug for the one wire library in the latest version of NodeMCU 0.9.6. The bug affect particularly the ow.select(pin,addr) function. It can temporarly be replaced by this snippet :

    ow.write(pin,0x55, 1) — 0x55==search aka select command
    for i=1,8 do
    ow.write(pin,addr:byte(i), 1)
    end

    That fixed the problem for me.”

    My nodemcu is NodeMCU 0.9.6 build 20150627

    • Thanks for sharing this Harley; so far I’ve ‘hacked’ a bit the original NodeMCU library to return four digits instead of just two, then converted it to AB.CD format and it works so far (should also work for negative). But it looks I need to get back to Dan’s code with your fix, and since I’m documenting it too – I oughta try both. It’s gonna take some time to complete and translate to English, but since it’s a goal now – I’ll come 🙂

  5. fotastisch says:

    Hi there!

    Thank you very much for your guide!
    I’m trying to get the DS18B20 working, but so far without success. Could you tell me, which firmware you’re using. When I try your code and =getTemp(), I receive:
    DS18B20 data CRC failed
    -9999

    Thank you very much!

    • Dan TheMan says:

      I believe I was running Lua 0.9.5 for that test.

      I would put in code to display exactly what you are getting back from the DS18B20 to cause the checksum error.

      Look at the comments above this and you will see where Lukasz has put that very code because he too was having a problem.

  6. Abdul Kerim says:

    40 255 54 93 161 21 1 5
    Device is a DS18S20 family device.
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    PANIC: unprotected error in call to Lua API (speak.lua:57: attempt to concatenate global ‘t’ (a nil value))
    PANIC: unprotected error in call to Lua API (attempt to concatenate a function value)
    PANIC: unprotected error in call to Lua API (attempt to call a string value)

  7. Joe Eckels says:

    Hello Dan! I have a little experience with pascal code and have some questions.
    First the “This worked fine:” section looks like it may be Machine Code, maybe Octal?
    I am lost with LUA. Can I paste that section in the LUA initialization file and get the temp?
    Do you have to compile it or will it be run the way it is? I appreciate your posting this!
    Thank you!
    Joe

  8. Artur says:

    Hi Dan,
    Thank you for this post.
    Your descriptions seems clear but I cannot proceed because of the first step:
    “This worked fine:” does not work for me….
    I only modified PIO pin from the original code and have following debugs:

    40 255 24 128 147 21 4 109
    Device is a DS18S20 family device.
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1
    255 255 255 255 255 255 255 255 255
    CRC=201
    P=1

    I’m using nodemcu_integer_0.9.6-dev_20150704.bin firmware.
    Could you please give me some clue how to proceed since I’m quite new to LUA.
    Thanks in advance

    • Dan TheMan says:

      Went back and looked at this. I see wordpress has screwed up the formatting of my example again. Ugh.

      Take a look at the comments on this post. Others have had this issue and solved it. Looks like there is a bug with at least on version of Lua.

      I notice you are using DS18S20 whereas the example uses DS18B20. I looked up the difference and I’m pretty sure that doesn’t make a difference, but you might want to check that carefully.

      Finally, you might want to try programming this using Arduino IDE and C++ rather than Lua. I tried using the IDE a few months ago and it is (in my opinion) much easier to work with than Lua – only because I know C well and Lua not at all.

      If you are interested in using the IDE, look in my Contents page under ESP8266 for my post on doing that.

      • Artur says:

        HI Dan,
        Thank you for your prompt response and for the clues.
        Yes, finally I found there is some bug related to ow.select function in the firmware I’m using. The workaround is to do not use this function (use ow.write aka select instead) and then it works well. I can proceed further.

        Smart notice with DS18S20 (whereas the example uses DS18B20), but I do confirm it seems to have no influence.

        So far I’m continuing with LUA, will consider IDE later.
        Thanks again for your help.

  9. samy says:

    Dear all

    I have tried to use your sample code below to test my Ds18b20 temperature sensor using Feather Adafruit Huzzah esp8266 module with pin conncetion of 6 on lua and 12 on the board and run on explorer however I am getting the reading value of 85 c which is not accurate reading.

    Could you suggest me any thing to get exact reading or any suggest ,please?

    regards,
    Sam

    pin=6
    ow.setup(pin)
    count = 0
    repeat
    count = count + 1
    addr = ow.reset_search(pin)
    addr = ow.search(pin)
    tmr.wdclr()
    until((addr ~= nil) or (count > 100))
    if (addr == nil) then
    print(“No more addresses.”)
    else
    –print(addr:byte(1,8))
    s=string.format(“Addr:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X”,
    addr:byte(1),addr:byte(2),addr:byte(3),addr:byte(4),
    addr:byte(5),addr:byte(6),addr:byte(7),addr:byte(8))
    print(s)
    crc = ow.crc8(string.sub(addr,1,7))
    if (crc == addr:byte(8)) then
    if ((addr:byte(1) == 0x10) or (addr:byte(1) == 0x28)) then
    print(“Device is a DS18S20 family device.”)
    repeat
    ow.reset(pin)
    ow.select(pin, addr)
    ow.write(pin, 0x44, 1)
    tmr.delay(1000000)
    present = ow.reset(pin)
    ow.select(pin, addr)
    ow.write(pin,0xBE,1)
    print(“P=”..present)
    data = nil
    data = string.char(ow.read(pin))
    for i = 1, 8 do
    data = data .. string.char(ow.read(pin))
    end
    –print(data:byte(1,9))
    s=string.format(“Data:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X”,
    data:byte(1),data:byte(2),data:byte(3), data:byte(4),
    data:byte(5),data:byte(6), data:byte(7),data:byte(8))
    print(s)
    crc = ow.crc8(string.sub(data,1,8))
    –print(“CRC=”..crc)
    s=string.format(“CRC: %02X”, crc)
    print(s)
    if (crc == data:byte(9)) then
    t = (data:byte(1) + data:byte(2) * 256) * 625
    t1 = t / 10000
    t2 = t % 10000
    print(“Temperature= “..t1..”.”..t2..” Centigrade”)
    end
    tmr.wdclr()
    until false
    else
    print(“Device family is not recognized.”)
    end
    else
    print(“CRC is not valid!”)
    end
    end

  10. Dan TheMan says:

    According the the thread http://forum.arduino.cc/index.php?topic=201863.0

    “There is a footnote on page 4 which says “The power-on reset value of the temperature register is +85°C.”
    so if you read 85 degrees it means it hasn’t done a conversion since the last power on.
    You hadn’t wired it up properly so the sensor was being powered off and on periodically, which resets the register to 85 and that is why you got a valid CRC from it.”

    So it sounds like there is some kind of issue with the sensor because it initializes at 85C until it takes its first reading (perhaps power is cycling as suggested above).

    Take a good look at that thread to see if it helps, if not google ds18b20 and 85c and I bet you’ll find others with the same problem.

    Good luck,

    Big Dan

Leave a reply to Joe Eckels Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.