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.

10 comments:

  1. If someone have error 89:5 when compiling paste } after return 0;

    ReplyDelete
    Replies
    1. Hi Vlastislav -

      Thanks for the note. It looks like I missed the } when copying and pasting the code. Silly mistake!

      --Brad

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. I am a beginner of programming.I want to add this program to sleep.How would add a sleep in this program.

    ReplyDelete
    Replies
    1. Hi Simon - You can try using the Linux usleep() method. It takes an argument in microseconds. You can find the documentation for usleep() at http://man7.org/linux/man-pages/man3/usleep.3.html. The code above already includes unistd.h.

      Delete
    2. Polite answer Thank you!

      Delete
    3. I want to display the measurement results every 30 minutes. Is it possible even usleep()?

      Delete
    4. Hi Simon -

      Perhaps the good-old Unix cron/crontab are the way you should go. Cron allows you to schedule jobs at particular times or time intervals at a more "human" scale than microseconds. Have a look at this: http://www.raspberrypi.org/documentation/linux/usage/cron.md.

      Delete
  4. Thank you very much! connected and working. More tinkering that would be written to the file and fine ... Thanks from Russia ... I'm sorry for the English, if that, google translator :) Good Job!

    ReplyDelete
  5. During -13437 external temperature calculated -1.343 cause string...
    I changed
    strncpy(tmpData, strstr(buf, "t=") + 2, 5);
    into
    strncpy(tmpData, strstr(buf, "t=") + 2, 6);
    and OK.

    ReplyDelete