So I’m working on a side project – which may or may not go anywhere – to make a working version of my home control 2016 ESP8266 software in the Arduino environment.
One popular device is the Dallas DS18b20 temperature sensor, a simple 3 wire device that looks like a transistor, has a single communications line and returns accurate temperature digitally.
Well, I’ve had this running for AGES on my code and can handle both the normal and P-suffix variety – so I was a bit dismayed after wasting an hour and throwing a chip away to find that the Arduino version does not. Also it seems people are still writing libraries out there which wait for the chip – wasting valuable time – so I thought I’d do a short write-up on my own code.
Another annoyance I discovered was the need to predefine which GPIO pin you’re using for the temperature sensor – making dynamic change impossible. There really is no need for this.
So – how do I get around the delay – simple – I swap things around – instead of priming the unit, waiting and taking a reading, I take a reading and prime the unit – making the first ever reading after power-up a waste of time – and I use a flag to return zero the first time. This means no waiting and hence VERY fast reading. I also check the result and if the DS18b20 fails I try for a DS18b20P.
In my own code I use EASYGPIO which isn’t available on the Arduino version – which means (as I understand it) putting the GPIO pin from output to input means two operations, one to reverse the state of the pin and another to set the output value – but hey, it works.
No libraries needed of any kind though if you were cleverer than I you could dump my routines into a library. Of course nothing is new and some of my code is simply refactored from elsewhere as you’ll see.
Firstly – I’m not going for accuracy here, I’m only interested in temperature to the nearest degree – if you want more you’ll have to make minor changes. I’m also not interested in multiple devices per pin. I am however interested in changing pins without static declarations. Funny enough this all started for the Arduino, got adapted for ESP8266 and ended up back in an ESP8266/Arduino setup.
/*
Adaptation of Paul Stoffregen’s One wire library to the ESP8266 and
Necromant’s Frankenstein firmware by Erland Lewin <erland@lewin.nu>Paul’s original library site:
http://www.pjrc.com/teensy/td_libs_OneWire.htmlSee also http://playground.arduino.cc/Learning/OneWire
Stripped down to bare minimum by Peter Scargill for single DS18B20 or DS18B20P integer read
*/// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn’t then it is broken or shorted
// and we return;void ds_reset(void)
{
uint8_t retries = 125;pinMode(temperaturePort, INPUT_PULLUP);
// wait until the wire is high… just in case
do {
if (–retries == 0) return;
delayMicroseconds(2);
} while (!digitalRead(temperaturePort));
digitalWrite(temperaturePort,LOW);
pinMode(temperaturePort, OUTPUT);
delayMicroseconds(480);
pinMode(temperaturePort, INPUT_PULLUP);
delayMicroseconds(480);
}// Write a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
static inline void write_bit(int v)
{
digitalWrite(temperaturePort,LOW);
pinMode(temperaturePort, OUTPUT);
if (v) {
delayMicroseconds(10);
digitalWrite(temperaturePort,HIGH);
delayMicroseconds(55);
}
else {
delayMicroseconds(65);
digitalWrite(temperaturePort,HIGH);
delayMicroseconds(5);
}
}//
// Read a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
static inline int read_bit(void)
{
int r;
digitalWrite(temperaturePort,LOW);
pinMode(temperaturePort, OUTPUT);
delayMicroseconds(3);
pinMode(temperaturePort, INPUT_PULLUP);
delayMicroseconds(10);
r = digitalRead(temperaturePort);
delayMicroseconds(53);
return r;
}//
// Write a byte. The writing code uses the active drivers to raise the
// pin high, if you need power after the write (e.g. DS18S20 in
// parasite power mode) then set ‘power’ to 1, otherwise the pin will
// go tri-state at the end of the write to avoid heating in a short or
// other mishap.
//
void ds_write(uint8_t v, int power)
{
uint8_t bitMask;for (bitMask = 0x01; bitMask; bitMask <<= 1) {
write_bit((bitMask & v) ? 1 : 0);
}
if (!power) {
pinMode(temperaturePort, INPUT_PULLUP);
digitalWrite(temperaturePort,LOW);
pinMode(temperaturePort, OUTPUT);
}
}//
// Read a byte
//
uint8_t ds_read()
{
uint8_t bitMask;
uint8_t r = 0;for (bitMask = 0x01; bitMask; bitMask <<= 1) {
if (read_bit()) r |= bitMask;
}
return r;
}
So what you see above are the basic routines for talking to the two chip variations. Here’s the actual code I call once a minute (you could use any interval) to store the temperature. I don’t stop interrupts as I might be running an RGB fader and I don’t want any flashing.
Doesn’t seem to present issues, never has, but if you wanted you could average temperature over time.
I run my heating without any of that and I don’t get glitches on my Home Control software – this is the same code but with changes for the Arduino-style port handling. I wonder if I’m missing a single instruction to set an input to an output and make it zero at the same time?
ds_reset();
ds_write(0xcc, 1);
ds_write(0xbe, 1);
temperature = (int) ds_read();
temperature = temperature + (int) ds_read() * 256;
temperature /= 16;
if (temperature > 100) temperature -= 4096;
ds_reset();
ds_write(0xcc, 1);
ds_write(0x44, 1);
if (gotDsReading == 0) { gotDsReading=1; temperature = 0; } // ignore first reading
else
{
Serial.printf(“Here’s your temperature %dc”,temperature);
}
It would be nice to see this in a stand-alone library somehow. temperaturePort is the number of the GPIO pin. I generally use 2 or 14 on the ESP and I don’t want them dedicated in case I want to use them for something else. Define gotDSReading and make it zero on power-up or change of pin. If the dummy value of zero is no good – change it.
Oh and in the process of putting this together I realised a slight silly in my main Home Control software – and that is now fixed….
Works for me!!
not sure if it’s the right place but…
I’ve a general issue with DS18B20 sensors and ESP8266.
After a certain amount of readings the ESP starts to reboot itself at each subsequential reading… Stress test performed: I added a DS18B20 into 3 of my working and stable ESP8266, and I placed in my code a temperature reading every 1 minute = all esp crashes after few hours (5/6 more or less); changed the code to read every 15 minutes = all esp crashes after one day (no direct correlation with previous interval). Of course those tests has been run many times (to confirm repeatibility) in several days and with 3 differents DS18B20 sensors each purchased in different timing (to exclude a faulty one) which I randomly swapped between each test run.
Anyone has any clue what can be the issue and where I have to look for solution?
Trust me – there is no “general problem” with ESP and DS18b20 sensors – including the P suffix variations – I have temperature logs at 15 mins intervals for something like 4 years that say all is ok. It is down to software or pullup issues or power.
Seems to have lost something in the paste there. Like all the formatting – duh. And the includes – bizarre to say the least.
Here it is again. Not sure if you can edit these 2 posts into 1 to save anyone any issues if they copy the code.
/* supports 5 dallas DS18B20 temperature sensors
*/
// normally DEBUG is commented out
#define DEBUG
// I/O pin useage
#define W1PIN 13 // 1-Wire pin for temperature
#include
#include
#include
// DS18B20 sensor data
#define MAXSENSORS 5 // maximum number of sensors on the one wire bus
byte DS18B20Address [MAXSENSORS][8]; // 8 bytes per address
float temperatures [MAXSENSORS]; // latest temperature readings
int numTempSensors; // number of sensors detected
int nextTempSensor; // used to work out which sensor to read next – read one every main loop
int errorcode; // used to hold last error code
OneWire oneWire(W1PIN); // One wire object
DallasTemperature tempSensors(&oneWire);
// Ticker Timer; // Timer to attach the read next temp routine to
Ticker TempsTimer; // Timer for the main loop to get the temperatures 10s
Ticker NextTempTimer; // Timer to attach the read next temp routine to
// Routine to get the temperature readings, this is called once every 10 seconds by being attached
// to a timer by the setup code. The routine calls the requestTemperatures and attaches the routine
// to do the actual readings to a 750Ms timer so that each sensor is read in turn. The routine to read the
// actual temperatures detaches itself from the 750s timer once all sensors have been read.
void readtemps()
{
tempSensors.requestTemperatures(); // Send the command to start conversion
NextTempTimer.attach(0.75, readnexttemp); // attach the sensor read routine to the 750ms timer
#ifdef DEBUG
Serial.println(“Current temperature readings”);
#endif
}
// Routine to get next temperature reading. Called every 750Ms by attaching to a timer.
void readnexttemp()
{
// get temperature from next sensor and reset counter to zero when all read
// routine activated every 750Ms
// a requestTemperatures has been sent with waitForConversion OFF
// so get temperature reading for next sensor which should be ready
float temp=(tempSensors.getTempC(DS18B20Address[nextTempSensor]));
// if reading is within range for the sensor convert float to int and save it
if ((temp-40.0)) temperatures[nextTempSensor]=(temp*10);
else errorcode = 60 + nextTempSensor +1;
Serial.print(“Number of sensors: “); Serial.println(numTempSensors);
Serial.print(“Sensor number: “); Serial.println(nextTempSensor);
Serial.print(“temperature: “); Serial.println(temperatures[nextTempSensor]*0.1);
// get ready to read the next sensor at the next interval
if (nextTempSensor == (numTempSensors -1)) {
nextTempSensor = 0;
NextTempTimer.detach ();
}
else {
nextTempSensor++;
// request temperatures again
tempSensors.requestTemperatures(); // Send the command to start conversion
}
}
void setup()
{
Serial.begin(9600);
// set up the Dallas Temp sensors find out how many are connected and get their Ids
tempSensors.begin();
numTempSensors=(tempSensors.getDeviceCount());
#ifdef DEBUG
Serial.print(“Number of sensors: “); Serial.println(numTempSensors);
#endif
if (numTempSensors > 0) {
// search for one wire devices and put device addresses in array.
int j=0;
while ((j < numTempSensors) && (oneWire.search(DS18B20Address[j]))) j++;
// now turn off WaitforConversion so that any RequestTemperatures returns asap
// the read next temperatures routine will be attached to a 750Ms timer
// so the temperature readings will always be ready
tempSensors.setWaitForConversion(false); // turn off the wait for conversion
tempSensors.setResolution (12); // set global resolution to 12 bit
nextTempSensor=0; // initialise the counter used to select first sensor to read
readtemps(); // and run the main routine to read the temperature sensors
// now attach the main routine to get the sensor readings to a 10s timer
// so that it runs once every 10 seconds
TempsTimer.attach (10, readtemps);
}
}
void loop()
{
}
OK Pete. Some code. I’m pretty sure I’ve copied all the relevant bits and it compiles although I haven’t tested it.
Obviously you can make changes to the I/O pin for the dallas devices and also amend the timer on the main temp routine. I have it set to 10 seconds but could of course be 1 minute which is what you have I think.
Here goes:
/* supports 5 dallas DS18B20 temperature sensors
*/
// normally DEBUG is commented out
#define DEBUG
// I/O pin useage
#define W1PIN 13 // 1-Wire pin for temperature
#include
#include
#include
// DS18B20 sensor data
#define MAXSENSORS 5 // maximum number of sensors on the one wire bus
byte DS18B20Address [MAXSENSORS][8]; // 8 bytes per address
float temperatures [MAXSENSORS]; // latest temperature readings
int numTempSensors; // number of sensors detected
int nextTempSensor; // used to work out which sensor to read next – read one every main loop
int errorcode; // used to hold last error code
OneWire oneWire(W1PIN); // One wire object
DallasTemperature tempSensors(&oneWire);
// Ticker Timer; // Timer to attach the read next temp routine to
Ticker TempsTimer; // Timer for the main loop to get the temperatures 10s
Ticker NextTempTimer; // Timer to attach the read next temp routine to
// Routine to get the temperature readings, this is called once every 10 seconds by being attached
// to a timer by the setup code. The routine calls the requestTemperatures and attaches the routine
// to do the actual readings to a 750Ms timer so that each sensor is read in turn. The routine to read the
// actual temperatures detaches itself from the 750s timer once all sensors have been read.
void readtemps()
{
tempSensors.requestTemperatures(); // Send the command to start conversion
NextTempTimer.attach(0.75, readnexttemp); // attach the sensor read routine to the 750ms timer
#ifdef DEBUG
Serial.println(“Current temperature readings”);
#endif
}
// Routine to get next temperature reading. Called every 750Ms by attaching to a timer.
void readnexttemp()
{
// get temperature from next sensor and reset counter to zero when all read
// routine activated every 750Ms
// a requestTemperatures has been sent with waitForConversion OFF
// so get temperature reading for next sensor which should be ready
float temp=(tempSensors.getTempC(DS18B20Address[nextTempSensor]));
// if reading is within range for the sensor convert float to int and save it
if ((temp-40.0)) temperatures[nextTempSensor]=(temp*10);
else errorcode = 60 + nextTempSensor +1;
Serial.print(“Number of sensors: “); Serial.println(numTempSensors);
Serial.print(“Sensor number: “); Serial.println(nextTempSensor);
Serial.print(“temperature: “); Serial.println(temperatures[nextTempSensor]*0.1);
// get ready to read the next sensor at the next interval
if (nextTempSensor == (numTempSensors -1)) {
nextTempSensor = 0;
NextTempTimer.detach ();
}
else {
nextTempSensor++;
// request temperatures again
tempSensors.requestTemperatures(); // Send the command to start conversion
}
}
void setup()
{
Serial.begin(9600);
// set up the Dallas Temp sensors find out how many are connected and get their Ids
tempSensors.begin();
numTempSensors=(tempSensors.getDeviceCount());
#ifdef DEBUG
Serial.print(“Number of sensors: “); Serial.println(numTempSensors);
#endif
if (numTempSensors > 0) {
// search for one wire devices and put device addresses in array.
int j=0;
while ((j < numTempSensors) && (oneWire.search(DS18B20Address[j]))) j++;
// now turn off WaitforConversion so that any RequestTemperatures returns asap
// the read next temperatures routine will be attached to a 750Ms timer
// so the temperature readings will always be ready
tempSensors.setWaitForConversion(false); // turn off the wait for conversion
tempSensors.setResolution (12); // set global resolution to 12 bit
nextTempSensor=0; // initialise the counter used to select first sensor to read
readtemps(); // and run the main routine to read the temperature sensors
// now attach the main routine to get the sensor readings to a 10s timer
// so that it runs once every 10 seconds
TempsTimer.attach (10, readtemps);
}
}
void loop()
{
}
Pete, back to the original topic of the blog…
I learnt the trick of doing things back to front so to speak from Martin Roberts solar diverter code on the openenergymonitor site.
I’ve been using the OneWire and DallasTemperature libraries for Arduino without a hitch since then and since I found the esp8266also without a hitch .
I think you can improve your logic and get rid of the null reading first time round by taking a different approach.
I have multiple sensors connected measuring temperatures on a heat bank at various levels. so my code has an array to manage the data. I’ll bet it won’t be long before you have a need for more than one sensor, so it might be an idea to build this into your code to start with.
Anyway, in the setup code I first find out how many sensors are connected with getDeviceCount()
Then I get the unique ids into an array for the sensors.
Then I set wait for conversion off with setWaitForConversion(false)
Then set the resolution with setResolution (12)
Then I attach a routine to a timer – 10 seconds in my case.
This 10 second timer routine simply calls requestTemperatures() to kick off the conversion. All of the sensors will begin the conversion simultaneously. And it attaches another routine to a 750msec timer.
The 750 millisecond timer does the actual read using getTempC(DS18B20Address[nextTempSensor])) followed by another requestTemperatures() until all sensors have been read. As you can see (I hope) this reads each sensor in turn as discovered in setup as above. Once all the sensors have been read the routine detaches itself from the timer.
So with 2 timers and an array to hold the info needed you can easily read the temperatures without any wait for conversion time.
You had another couple of points about the pin for the DS18B20s and also the type of device. Surely in your config system you would hold the pin info and have that sent across or retrieved at boot time. It won’t change at run time. So you could use the above logic but make sure it runs after wifi is up and running and the config has been set.
As for 18B20P, I would have to get one to test my code, but I don’t think from memory I care what the type of one wire device is connected, it’s always a temp sensor in my case, so maybe my code would work.
Finally on multiple sensors connected to the system, I’ve found that once a system is wired, set up and running then the order in which the sensors respond doesn’t change
so I can quite happily assign the sensors logically in openenergymonitor feed names which don’t have to change due to reboots etc.
PS I could strip out the code from my code for the heat bank monitor if it would help so you can see the detail.
Always happy to look at another way and I must admit I’d not thought of using a timer like that. I just have a regular 1 minute timer to do the polling no matter what kind of sensor – hence the duff first reading…. if it’s not too much bother I’d love to look at your code.
Hi Pete,
Do these Dallas devices offer any advantage over a DHT22 (for temp sensors at least). Obviously the DHT devices detect humidity as well but my only use for that is in my wife’s greenhouse – for indoor temp sensors (central heating etc) I wouldn’t bother with the humidity.
Cheers,
Brian
Hmm, well one advantage is they are cheaper and smaller – really that’s about it, the DHT22 temperature sensor seems pretty reliable. Oh another good reason – you can buy ready made leads with the Dallas chip buried in a stainless steel tube. For indoors use – well, you can ignore humidity – but a tip – when it is DAMP it tends to FEEL cooler – so if you wanted to you could experiment- say with heating, with adjusting the heat depending on the humidity. In my case I adjust plant watering time for outside plants depending on humidity – not a lot of point in watering plants when it is raining outside 🙂
Agreed on price, I did a bit of trawling on AliExpress and they’re about 1/4 of the price of DHT22s – I also saw the probe versions although I’m not sure of a use for those. Perhaps I’ll order some and have a play. Cheers.
Peter, do you use an automatic docs generator? For example this, it’s integrated in bitbucket and can do all the work for you automatically…
Pete,
Have you looked at the Platformio IoT development environment? I’ve only just discovered it and would love to hear your, or anyone else’s, opinion on it. It supports numerous target chips and platforms, including a range of ESP8266-based boards. If the platform lives up to the hype it would be great to see your codebase working on it as its much easier to setup than the official ‘unofficial’ ESP build environment.
http://platformio.org/
Hi – I’ve looked at it – not that impressed – but maybe it would work for some – the Unofficial dev environment if you follow the instructions precisely is EXTREMELY easy to set up – absolutely nothing to it – and the examples therein usually compile at the touch of a button. I have a setup that works- and works extremely reliably due to much trial and error – and I have plenty of room for expansion. The ONLY reason I’m even looking at the Arduino environment is that some of the larger, more complex libraries are difficult to port but that environment is bloated. I’m playing it by ear at the moment but I have perfectly working OTA thank to Richard Burton, working web updates thanks to Aidan Ruff – and the number of devices the boards can handle just keeps on getting bigger – all reliably. I look at some of the memory issues people have or unexplained this or that and think.. why…
Now- if you’re not comfortable programming in C – I could see that being an issue – to me it’s second nature. I’m not really interested in the interpreted languages – not that I have it in for interpreted languages – I use Node-Red and NODE.JS on the Pi etc but those machines are a different kettle of fish – LOTS of RAM etc… the ESP has it’s limits and for me, C coding with the SDK is a way of getting LOTS of functionality out of the limits imposed on us – I have room for a LOT more expansion.
But then, things change – I might not say the same next month or next year. I did warm up to the Arduino environment a little more this afternoon when I discovered TABS… as I really can’t be bothered to make a load of libraries and I like my code broken up functionally – so that’s good.. but of course Eclipse is generally a better editor. I did have a go at setting up Visual Studio but the plug in that makes it possible to compile ESP/SDK code – well, just didn’t setup for me – said there were missing files etc. So right now my main thrust is in the Unofficial environment with tinkering around the edges of the Arduino IDE as a possible alternative.
Thanks Pete, appreciate the insight. I may have a play with PlatformIO, but I already have the Eclipse-based IDE running too and, as you mention, it just works.
have you seen this? From video it seems very well done arduino/other boards integration in eclipse… author is a little bit… “extravagant”, as you’ll see, but plugin is excellent… http://eclipse.baeyens.it
Dear Peter,
> I’ve looked at it – not that impressed – but maybe it would work for some
Could you share your feedback? Which problems did you have with PlatformIO?
Thanks in advance!
Please don’t advertise. It just does not suit my purpose right now.
Peter,
I see you got a mention today in the Microsoft News app.
https://1drv.ms/i/s!AnmQHe6hqnlciuwrba2_zzHRK4ONyw
So I did! Somewhat.. mind you out of date now ?
So I did! Somewhat.. mind you out of date now ?
Peter, i think you published an old version of the hackitt and bodgitt doc… it was 3mb, now 120k, and reports an april date inside…
uh! went on and back into revisions, and now it’s updated… strange, maybe a cached version, but i downloaded (and so, refreshed that page) just yesterday… sorry, you can delete this comments…
oh, ok found the error… in the page https://tech.scargill.net/home-control-2016/
you link directly to a specific version of the doc, not the master one… first link…
Well spotted – suitably updated!!!!