Measuring temperature with 1-Wire DS18B20 on Raspberry

1-Wire

In todays article we will look at an interesting 1-Wire technology and at how it works together with Raspberry. After the brief introduction, you can look forward to a practical demostration of how to use 1-Wire temperature sensor DS18B20.

1-Wire is a device communications bus system developed by Dallas Smiconductors, simply put, it is a technology providing communication between multiple devices. 1-Wire always has one master device, which runs the communications with the slave devices. This technology is usually used for small and cheap sensors, such as thermometers and other similar devices, that only need low power and transmit only small amounts of data. 1-Wire can work on relatively large distances and needs only 3 wires. Those are power supply, that can be anyting from 3V to 5V, ground and data. Slave devices are all conencted to the same triple of wires.

Raspberry and 1-Wire

If you want to work with 1-Wire devices on your Raspberry, you first need to enable the 1-Wire protocol. You can do this in Raspberry’s configuration and you have multiple options how to do it. You can change the configuration through the raspi-config wizard. Simply open the console and write:

sudo raspi-config

Which will give you this screen:

config_step_1

Select section 5 Interfacing options

config_step_2

Select P7 1-Wire

config_step_3

And select Yes to enable 1-Wire

The other option is more straightforward. You can edit the configuration file directly from any text editor, for example nano.

sudo nano /boot/config.txt

Scroll to the bottom of the file and add a new line with:

dtoverlay=w1-gpio

Save and close the file (press Ctrl+X, Y - as yes to changes and press Enter to confirm).

Now you are ready to go! Reboot you Raspberry:

sudo shutdown -r

And you can start connecting 1-Wire devices.

Connecting 1-Wire devices to Raspberry

Connecting 1-Wire devices to Raspberry is pretty easy, you only need 1 resistor with resistance between 4k7Ω and 10kΩ, that will be put between power supply and data line. That is it. For demonstration, we will use temperature sensors DS18B20. Get at least two, so we can see that 1-Wire is able to work with multiple devices, even in case they are of the same type.

Multiple devices on one line

As was said before, 1-Wire always has one master device, which runs the communication - this will be our Raspberry and can have multiple slave devices - those will be temperature sensors in our case and they are all connected to just one data line. You might be asking how will the Raspberry know, which temperature sensor is sending which data, if they both sit on the same wire and the answer is simple. Each 1-Wire device has its own internal ROM memory, that stores unique 64-bit long address or identifier if you want. Every time a sensor sends some data, it also includes its address, so Raspberry always knows, which sensor sent which data.

DS18B20 temperature sensor

For this tutorial, we are using DS18B20 temperature sensor which is a great sensor with high accuarcy and can handle big range of temperatures. All the details can be found in its datasheet, but lets just point out the important details:

  • power supply from 3V to 5V
  • measures temperatures from -55°C to +125°C (-67°F to +257°F)
  • accuarcy is ±0.5°C for temperatures ranging from -10°C to +85°C

It is also goot to know, which DS18B20 pins are for power supply, ground and data:

DS18B20 datasheet

You can buy this sensor on Ebay, its price starts around 1.5 USD. This sensor comes also in a waterproof version, usually with a 2m cable, which makes it ideal for measuring temperatures in pools or refrigirators.

DS18B20 sensor DS18B20 waterproof sensor

Detecting 1-Wire devices

Lets get into wiring so we can finally see some real outputs. Grab your Raspberry, DS18B20 sensors and one 4k7Ω resistor. By default, the 1-Wire input pin on Rasperry is GPIO 4 in BCM numbering, but you can change that if you want. Go back to Raspberry’s config file:

sudo nano /boot/config.txt

And change the line with

dtoverlay=w1-gpio

to:

dtoverlay=w1-gpio,gpiopin=X

where X is the desired BCM pin number. Nertheless, for this example, we will work with the default settings.

As was already mentioned before, wiring the sensors to the Raspberry is very simple, just connect the power and ground to corresponding sensor pins, connect GPIO4 with data pins and put the 4k7Ω resistor between the power and data line.

wiring

Now we are ready to read the temperature data. All connected 1-Wire devices will create a folder named after its unique identifier in /sys/bus/w1/devices/. To see the folders, use your command line:

ls /sys/bus/w1/devices/

In my case it gives me this output:

28-000009f8259a  28-000009f96000  w1_bus_master1

Lets ignore the w1_bus_master1, the rest of the folders are our sensors. The first two digits tell us, what kind of a sensor we are dealing with, 28 stands for DS18B20 temperature sensor, so everything is working as expected.

Detecting all 1-Wire devices

I have written a neat python script, that prints out data about all connected 1-Wire devices. You can find it on my Gitlab repository for this tutorial. Grab it directly from command line:

git clone git@gitlab.com:bambusekd-dev-blog/raspberry-1-wire-temperature-ds18b20.git

There are two files in this repository, for detecting devices, use the w1-devices.py. So in your command line:

cd raspberry-1-wire-temperature-ds18b20
python w1-devices.py

The output for me is:

 _______________________
|
| Device #1
| Type:        temperature sensor
| ID:          000009f96000
| Sensor type: DS18B20
|_______________________

 _______________________
|
| Device #2
| Type:        temperature sensor
| ID:          000009f8259a
| Sensor type: DS18B20
|_______________________

Reading data from DS18B20

To read the actual measured sensor data, we need to read the contents of a file called w1_slave, that is included in each device file in /sys/bus/w1/devices/. To do that, use your command line and don’t forget to change the folder name to the name of your sensors:

cat /sys/bus/w1/devices/28-000009f8259a/w1_slave

I get this output:

92 01 4b 46 7f ff 0e 10 24 : crc=24 YES
92 01 4b 46 7f ff 0e 10 24 t=25125

WTF is this might be your first reaction, but everything we need to know is written in this output. Lets have a look at the end of the first line. Here we can see if the information was received error-free. If the word says YES, we are fine, if it states NO, there was some problem. At the end of the second line, we have the measured temperature, it just needs to be divided by 1000. So t=25125, gives us 25125/1000 = 25.125°C. Pretty hot day for May in Brno.

Usually, you want to read the measured data automatically running a script, so doing it in a command line is not very practical. So lets write a Python script, that will do it for us.

Reading DS18B20 sensor data in Python

If you have already downloaded my Gitlab repository for this tutorial, you can find the script there under w1-read-temperature-ds18b20.py. It detects all DS18B20 sensors and prints out the mesured temperature. Running the script

python w1-read-temperature-ds18b20.py

will output something like this:

 _________________________
|
| Measurement from: 2018-05-07 18:42:41
|________
|
| Thermometer 28-000009f96000
| Celsius:    25.25
| Fahrenheit: 77.45
|________
|
| Thermometer 28-000009f8259a
| Celsius:    25.062
| Fahrenheit: 77.1116

For completness, I am including the script also directly into this blog post below. Read the in code comments to understand the functionality.

#!/usr/bin/python

import os
import re
from glob import glob
import time

# Folder with 1-Wire devices
w1DeviceFolder = '/sys/bus/w1/devices'

# Function that returns array with IDs of all found thermometers
def find_thermometers():
    # Get all devices
    w1Devices = glob(w1DeviceFolder + '/*/')
    # Create regular expression to filter only those starting with '28', which is thermometer
    w1ThermometerCode = re.compile(r'28-\d+')
    # Initialize the array
    thermometers = []
    # Go through all devices
    for device in w1Devices:
        # Read the device code
        deviceCode = device[len(w1DeviceFolder)+1:-1]
        # If the code matches thermometer code add it to the array
        if w1ThermometerCode.match(deviceCode):
            thermometers.append(deviceCode)
    # Return the array
    return thermometers

# Function that reads and returns the raw content of 'w1_slave' file
def read_temp_raw(deviceCode):
    f = open(w1DeviceFolder + '/' + deviceCode + '/w1_slave' , 'r')
    lines = f.readlines()
    f.close()
    return lines

# Function that reads the temperature from raw file content
def read_temp(deviceCode):
    # Read the raw temperature data
    lines = read_temp_raw(deviceCode)
    # Wait until the data is valid - end of the first line reads 'YES'
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.2)
        lines = read_temp_raw(deviceCode)
    # Read the temperature, that is on the second line
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        # Convert the temperature number to Celsius
        temp_c = float(temp_string) / 1000.0
        # Convert the temperature to Fahrenheit
        temp_f = temp_c * 9.0 / 5.0 + 32.0
        # Return formatted sensor data
        return {
            'thermometerID': deviceCode,
            'celsius': temp_c,
            'fehrenheit': temp_f
        }

# Function that pretty prints the sensor data
def print_temperature(data):
    print '|________'
    print '|'
    print '| Thermometer {}'.format(data['thermometerID'])
    print '| Celsius:    {}'.format(data['celsius'])
    print '| Fahrenheit: {}'.format(data['fehrenheit'])

# Function that prints actual timestamp
def print_timestamp(): 
    print ' _________________________'
    print '|'
    print '| Measurement from: ' + time.strftime('%Y-%m-%d %H:%M:%S')

# Main function
def main():
    # Find all connected thermometers
    thermometers = find_thermometers()
    # Print actual timestamp
    print_timestamp()
    # Go through all connected thermometers
    for thermometer in thermometers:
        # Pretty print sensor data
        print_temperature(read_temp(thermometer))
    
# Run the main function when the script is executed
if __name__ == "__main__":
    main()

If everything worked, then congratz! You have successfuly configured, wired and read 1-Wire sensor data!