(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:
here is what it looks like:
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:
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
Pingback: SNMP Environmental Monitoring using ESP8266-based Sensors | Big Dan the Blogging Man
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 ?
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?
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
@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 🙂
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!
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.
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)
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
Hi Joe,
The “this worked fine” section’s code is at https://github.com/nodemcu/nodemcu-firmware/blob/master/lua_examples/onewire-ds18b20.lua. I am just showing the results in my blog. Take a look at the code in that link.
From what I can remember, that program runs fine when loaded straight into LUA.
LUA is an interpreter, like BASIC, so it will run the code w/o compilation.
If you go to my table of contents, https://bigdanzblog.wordpress.com/test/ , and go to the ESP8266 section, I’ve got a couple of entries on getting LUA up and running. And if my explanation is inadequate (I’ve never used LUA before or since those posts), others will explain LUA much better than I.
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
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.
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.
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
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