Wednesday, January 1, 2014

C Program to Read Temperature from Multiple 1-Wire Temperature Sensors Connected to a Raspberry Pi

The following example shows how to read the temperature from multiple DS18B20 1-wire temperature sensors connected to a Raspberry Pi using code written in C.  The program reads and prints the temperature to the console until the user ends the program with a ctrl-c.  I published code for reading a single sensor here.  I've got a Java version of this code here.

For a more developed version of this program that also saves the data to a Sqlite3 database, see this post.

I found this post by Matt Hawkins at Raspberry Pi Spy very helpful.

This code relies on two kernel modules that must be loaded by the following commands before the code is run:

sudo modprobe w1-gpio
sudo modprobe w1-therm

These modules make it possible to access the 1-wire sensors via the Linux file system.

Connections



Looking at the flat side of the DS18B20's plastic head, connect the left pin to ground, the right pin to 3V3, and the center pin to GPIO4.  A 4.7k Ohm pull-up resistor is required on the connection of the first sensor's center pin to GPIO4.  If using multiple sensors, each needs to be connected to the voltage and ground; parasitic power mode does not seem to be supported.  The center (data) pins need to be connected together (with the pull-up on the connection to the Raspberry Pi).

Code



#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main (void) {
 DIR *dir;
 struct dirent *dirent;
 char buf[256];     // Data from device
 char tmpData[5];   // Temp C * 1000 reported by device 
 const char path[] = "/sys/bus/w1/devices"; 
 ssize_t numRead;
 int i = 0;
 int devCnt = 0;

        // 1st pass counts devices
        dir = opendir (path);
        if (dir != NULL)
        {
  while ((dirent = readdir (dir))) {
                 // 1-wire devices are links beginning with 28-
                        if (dirent->d_type == DT_LNK &&
                                        strstr(dirent->d_name, "28-") != NULL) {
                                i++;
                        }
                }
                (void) closedir (dir);
        }
        else
        {
                perror ("Couldn't open the w1 devices directory");
                return 1;
        }
        devCnt = i;
        i = 0;

        // 2nd pass allocates space for data based on device count
        char dev[devCnt][16];
        char devPath[devCnt][128];
 dir = opendir (path);
 if (dir != NULL)
 {
  while ((dirent = readdir (dir))) {
   // 1-wire devices are links beginning with 28-
   if (dirent->d_type == DT_LNK && 
     strstr(dirent->d_name, "28-") != NULL) { 
    strcpy(dev[i], dirent->d_name);
           // Assemble path to OneWire device
    sprintf(devPath[i], "%s/%s/w1_slave", path, dev[i]);
    i++;
   }
                }
  (void) closedir (dir);
        }
 else
 {
  perror ("Couldn't open the w1 devices directory");
  return 1;
 }
 i = 0;

  // Read temp continuously
 // Opening the device's file triggers new reading
 while(1) {
  int fd = open(devPath[i], O_RDONLY);
  if(fd == -1)
  {
   perror ("Couldn't open the w1 device.");
   return 1;
  }
  while((numRead = read(fd, buf, 256)) > 0) 
  {
   strncpy(tmpData, strstr(buf, "t=") + 2, 5);
   float tempC = strtof(tmpData, NULL);
   printf("Device: %s - ", dev[i]);
   printf("Temp: %.3f C  ", tempC / 1000);
   printf("%.3f F\n", (tempC / 1000) * 9 / 5 + 32);
  }
  close(fd);
  i++;
  if(i == devCnt) {
         i = 0;
            printf("%s\n", ""); // Blank line after each cycle
        }
    }
    return 0;
}


To compile and run the code, use the following commands:

gcc -Wall -o w1m w1m.c
./w1m

Use ctrl-c to end the program.