© Warren Gay 2018
Warren GayAdvanced Raspberry Pihttps://doi.org/10.1007/978-1-4842-3948-3_22

22. DHT11 Sensor

Warren Gay1 
(1)
St. Catharine’s, Ontario, Canada
 

The DHT11 humidity and temperature sensor is an economical peripheral manufactured by D-Robotics UK ( www.droboticsonline.com ). It is capable of measuring relative humidity between 20 and 90% RH within the operating temperature range of 0 to 50°C with an accuracy of ±5% RH. Temperature is also measured in the range of 0 to 50°C with an accuracy of ±2°C. Both values are returned with 8-bit resolution.

This assignment is a challenge for a Linux application because of the signal timing constraints being accommodated. After the Pi initiates the sensor, the first event to be measured occurs within about 12 μs, for example. This requires some special handling. Direct GPIO access is used for this project since the sysfs driver is simply unable to cope with the rate of events involved.

Characteristics

The signaling used by the DHT sensor is similar to the 1-Wire protocol but the response times differ. Additionally, there is no device serial number support. These factors make the device incompatible with the 1-Wire drivers within the Linux kernel. Figure 22-1 shows a DHT11 sensor sitting on a breadboard.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig1_HTML.jpg
Figure 22-1

DHT11 sensor front view (left), rear view (right). Pin 1 is the leftmost pin facing the front of the package (left photo).

The DHT11 sensor requires a power supply unlike many 1-Wire peripherals. The datasheet states that the DHT11 can be powered by a range of 3.3 to 5.5 V (this can also be seen on the back of the device in Figure 22-1). Powering it from the Raspberry Pi’s 3.3 V source keeps the signal levels within a safe range for GPIO. The device draws between 0.5 and 2.5 mA. Its standby current is stated as 100 to 150 μA, for those concerned about battery life.

Circuit

Figure 22-2 shows the general circuit connections between the Raspberry Pi and the DHT11 sensor. Pin 4 connects to the common ground, while pin 1 goes to the 3.3 V supply. Pin 2 is the signal pin, which communicates with a chosen GPIO pin. The program listing for dht11.c is configured to use GPIO 22. This can be overridden on the command line.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig2_HTML.jpg
Figure 22-2

DHT11 circuit

When the Pi is listening on the GPIO pin and the DHT11 is not sending data, the line will float. For this reason, R1 is used to pull the line up to a level of 3.3 V. The datasheet recommends a 5 kΩ resistor for the purpose (a more common 4.7 kohm resistor can be substituted safely). This presents less than 1 mA of load on either the GPIO pin or the sensor when active. The datasheet also states that the 5 kohm resistor should be suitable for cable runs of up to 20 meters.

Protocol

The sensor speaks only when prodded by the master (Raspberry Pi). The master must first make a request on the bus and wait for the sensor to respond. The DHT sensor responds with 40 bits of information, 8 of which are a checksum.

Overall Protocol

The overall signal protocol works like this:
  1. 1.

    The line idles high because of the pull-up resistor.

     
  2. 2.

    The master pulls the line low for at least 18 ms to signal a read request and then releases the bus, allowing the line to return to a high state.

     
  3. 3.

    After a pause of about 20 to 40 μs, the sensor responds by bringing the line low for 80 μs and then allows the line to return high for a further 80 μs. This signals its intention to return data.

     
  4. 4.
    Forty bits of information are then written out to the bus by the DHT11: each bit starting with a 50 μs low followed by:
    1. a.

      26 to 28 μs of high to indicate a 0-bit

       
    2. b.

      70 μs of high to indicate a 1-bit

       
     
  5. 5.

    The transmission ends when the sensor drives the line low one more time for 50 μs.

     
  6. 6.

    The sensor releases the bus, allowing the line to return to a high idle state.

     
Figure 22-3 illustrates the overall protocol of the sensor. Master control is shown in thick lines, while sensor control is shown in thin lines. Initially, the bus sits idle until the master brings the line low and releases it (labeled Request). The sensor grabs the bus and signals that it is responding (80 μs low, followed by 80 μs high). The sensor finishes with 40 bits of sensor data, ending with one more transition to low (labeled End) to mark the end of the last bit.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig3_HTML.jpg
Figure 22-3

General DHT11 protocol

Data Bits

Each sensor data bit begins with a transition to low, followed by the transition to high, as shown in Figure 22-4. The end of the bit occurs when the line is brought low again as part of the next bit. The last bit is marked off by one final low-to-high transition.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig4_HTML.jpg
Figure 22-4

DHT11 data bit

Each data bit starts with a transition to low, lasting for 50 μs. The final transition to low after the last bit also lasts for 50 μs. After the bit’s low-to-high transition, the bit becomes a 0-bit if the high lasts only 26 to 28 μs. A 1-bit stays high for 70 μs instead.

Data Format

Figure 22-5 illustrates the 40-bit sensor response, transmitting the most significant bit first. The datasheet states 16 bits of relative humidity, 16 bits of temperature in Celsius , and an 8-bit checksum. However, the DHT11 always sends 0s for the humidity and temperature fractional bytes. Thus the device really only has 8 bits of precision for each measurement. Presumably, other models (or future ones) provide fractional values for greater precision.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig5_HTML.jpg
Figure 22-5

DHT11 data format

The checksum is a simple sum of the first 4 bytes. Any carry overflow is simply discarded. This checksum gives your application greater confidence that it has received correct values in the face of possible reception errors.

Figure 22-6 illustrates an overview scope trace of the DHT11 signal. In this figure, the horizontal axis is dominated by the 30 ms of low signal driven by the Pi, to wake up the device. The DHT11 sends its 40 bits of data after the first initial spike, shown at the right. The datasheet indicates that the sensor should be queried no more than once per second.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig6_HTML.png
Figure 22-6

Overview scope trace of the DHT11 signal

A closeup of the DHT11 response data is shown in Figure 22-7. The first high pulse (at left) is from the Pi releasing the bus and allowing the pull-up resistor to raise the bus voltage. At 12 μs after this, the DHT11 pulls the bus low for 80 μs and then allows the bus to go high for another 80 μs. This marks the beginning of the 40 bits of data that will follow.
../images/326071_2_En_22_Chapter/326071_2_En_22_Fig7_HTML.png
Figure 22-7

Scope trace of the start of the DHT11 response data bits

Software

The user space software written to read the DHT11 sensor on the Raspberry Pi uses the direct register access of the GPIO pin. The challenges presented by this approach include the following:
  • Short timings: 26 to 70 μs

  • Preemptive scheduling delays within the Linux kernel

One approach is to count how many times the program could read the high-level signal before the end of the bit is reached (when the line goes low). Then decide on 0 bits for shorter times and 1s for longer times. After some experimentation, a dividing line could be drawn, where shorter signals mean 0 while the others are 1s.

Source Code

The source code for this project will be found at the following directory:
$ cd ~/RPi/dht11
To rebuild the application from scratch, perform the following:
$ make clobber
$ make
There is help available using the -h option:
$ ./dht11 -h
Usage: ./dht11 [-g gpio] [-h]
where:
      -g gpio   Specify GPIO pin (22 is default)
      -h        This help

Timing

One of the primary challenges of this application is to perform fast and accurate timings. But we can’t have accurate timing measurements with the NTP daemon (network time protocol) updating the system clock. So rather than rely on the wall clock sense of time, we use the Linux monotonic clock instead. This too can be tweaked by the system slightly, but we are guaranteed that this clock only increments forward in time.

Listing 22-1 illustrates the short inline function used to fetch the monotonic time from Raspbian Linux. The struct timespec is declared by Linux as:
struct timespec {
    time_t  tv_sec;     /* seconds */
    long    tv_nsec;    /* and nanoseconds */
}
So our timeofday() function will return seconds and nanoseconds.
0042: static inline void
0043: timeofday(struct timespec *t) {
0044:     clock_gettime(CLOCK_MONOTONIC,t);
0045: }
Listing 22-1

The dht11.c, timeofday() function

The general procedure for computing elapsed time then is to:
  1. 1.

    Capture the initial time (call it t0).

     
  2. 2.

    Capture the current time after the event (call it t1).

     
The a function of the form of ns_diff() in Listing 22-2 is used to compute the elapsed time.
0057: static inline long
0058: ns_diff(struct timespec *t0,struct timespec *t1) {
0059:     int dsec = (int)(t1->tv_sec - t0->tv_sec);
0060:     long dns = t1->tv_nsec - t0->tv_nsec;
0061:
0062:     assert(dsec >= 0);
0063:     dns += dsec * 1000000000L;
0064:     return dns;
0065: }
Listing 22-2

The dht11.c, ns_diff() function to calculate elapsed time in nanoseconds

Main Loop

The main loop is found in the main() function. After processing of command-line options, the top of the loop is illustrated in Listing 22-3.
0192: gpio_open();
0193:
0194: gpio_configure_io(gpio_pin,Output);
0195: gpio_write(gpio_pin,1);
0196:
0197: for (;; ++reading) {
0198:        wait_ready();
0199:
0200:        gpio_write(gpio_pin,1);
0201:        gpio_configure_io(gpio_pin,Output);
0202:        wait_ms(3);
Listing 22-3

The top of the main loop in dht11.c

Line 192 initializes the library for direct GPIO access (see source files libgp.c and libgp.h). Following that, the GPIO pin is configured as Output in line 194 and initially driven high in line 195.

The main loop begins in line 197. The counter variable reading is simply used to provide an incrementing reading counter in the output reports. Line 198 initiates the function wait_ready(), which will be described shortly. Its purpose is to prevent the program from querying the DHT11 device more often than once per second. If it is queried too often, the device simply fails to respond.

After it has been determined that the DHT11 device can be queried, line 200 sets the level of the GPIO output to high. Except for the first time into the loop, the GPIO is configured as an input pin. Setting it high before configuring it as an output in line 201 means that there is no glitch when transitioning from its current input state to high when the GPIO becomes an output again. Line 202 simply waits for 3 ms to allow the line to stabilize.

wait_ms( )

To provide a reasonably accurate millisecond wait function, the poll(2) system call is used (Listing 22-4). This is normally used to monitor open file descriptors. But poll(2) can be used with no descriptors, taking advantage of its timeout argument (argument three in line 92). Notice how argument two indicates zero file descriptor entries.
0087: static void
0088: wait_ms(int ms) {
0089: struct pollfd p[1];
0090: int rc;
0091:
0092: rc = poll(&p[0],0,ms );
0093: assert(!rc);
0094: }
Listing 22-4

The wait_ms() function in dht11.c

wait_ready( )

This function is used to prevent querying the device too often. Listing 22-5 illustrates the code used.
0067: static void
0068: wait_ready(void) {
0069: static struct timespec t0 = {0L,0L};
0070: struct timespec t1;
0071:
0072: if ( !t0.tv_sec ) {
0073:        timeofday(&t0);
0074:        --t0.tv_sec;
0075: }
0076:
0077: for (;;) {
0078:        timeofday(&t1);
0079:        if ( ms_diff(&t0,&t1) >= 1000 ) {
0080:              t0 = t1;
0081:              return;
0082:        }
0083:        usleep(100);
0084:  }
0085: }
Listing 22-5

The wait_ready() function in dht11.c

The static value of variable t0 is established with zeros. When the code is entered for the first time, the date/time is initialized in line 73 and then subtracted by one second (line 74). Subtracting one allows an immediate pass the first time through.

The loop in lines 77 to 84 samples the time every hundred microseconds and returns in line 81 if we have at least one second elapsed, since the last device request.

Reading DHT11

Now comes the fun part—reading the device response. Listing 22-6 illustrates the logic used. Recall that line 206 drives the bus line low, to wake up the device. Then 30 ms is the delay time used before the GPIO is turned into an input pin (lines 207 and 208). By configuring the GPIO as an input pin, we now let the pull-up resistor take over and pull the bus voltage up to +3.3 V.

At this point, the DHT11 will eventually grab the bus and respond. We wait for a line change using the function wait_change() (line 210). It returns the current (final) state of the bus line as well as populating the variable nsec with the nanoseconds elapsed.

The first transition sometimes happens so fast (on Raspberry Pi 3 B+) that it sees its own GPIO line go from low to high, before the pull-up resistor has done its job. Line 219 tests for this, and if this is indeed true, we wait for one more signal transition—the one we care about, which is the the high to low transition (line 220). If the final state is still a 1-bit, or the time in nsec is too high, we reject the response and start over (lines 221 to 223).
0206:        gpio_write(gpio_pin,0);
0207:        wait_ms(30);
0208:        gpio_configure_io(gpio_pin,Input);
0209:
0210:        b = wait_change(&nsec);
0211:
0212:        /*
0213:         * If the returned value is 1, it is likely
0214:         * that we were fast enough to catch the
0215:         * pullup resistor action. When that happens
0216:         * look for the next transition (expecting
0217:         * b == 0).
0218:         */
0219:        if ( b == 1 )
0220:              b = wait_change(&nsec);
0221:        if ( b || nsec > 20000 ) { // Expecting about 12 us
0222:              printf("%04d: Fail, b0=%d, %ld nsec ",reading,b,nsec);
0223:              continue;
0224:        }
0225:
0226:        /*
0227:         * This is the 80 us transition from 0 to 1:
0228:         */
0229:        b = wait_change(&nsec);
0230:        if ( !b || nsec < 40000 || nsec > 90000 ) {
0231:              printf("%04d: Fail, b1=%d, %ld nsec ",reading,b,nsec);
0232:              continue;
0233:        }
0234:
0235:        /*
0236:         * Wait for the 80 us transition from 1 to 0:
0237:         */
0238:        b = wait_change(&nsec);
0239:
0240:        if ( b != 0 || nsec < 40000 || nsec > 90000 ) {
0241:              printf("%04d: Fail, b2=%d, %ld nsec ",reading,b,nsec);
0242:              continue;
0243:        }
0244:
0245:        /*
0246:         * Read the 40-bit value from the DHT11. The
0247:         * returned value is distilled into 16-bits:
0248:         */
0249:        unsigned resp = read_40bits();
Listing 22-6

Reading the DHT11 response in dht11.c

Upon entry to line 229, the line is low. Waiting for the next transition should report that the bus has gone high, with a time near 80 μs. If the final state is not high, or the time is too long, the response is rejected in lines 230 to 232.

If that passes, the next transition from high to low is measured in lines 240 through 243. Again, if the signal measured is not correct, the response is rejected and the program tries again at the top of the loop.

Finally at line 259, the 40-bit response of the DHT11 is ready to be read.

wait_change( )

The wait_change() function is used to monitor the GPIO for a signal change. If the signal was low initially, it waits until the signal goes high and returns on. If the signal was originally high, it waits until the signal goes low and returns zero. In addition to waiting for a state change, the number of nanoseconds elapsed is returned. The function is illustrated in Listing 22-7.
0024: static volatile bool timeout = false;
...
0096: static inline int
0097: wait_change(long *nsec) {
0098:  int b1;
0099:  struct timespec t0, t1;
0100:  int b0 = gpio_read(gpio_pin);
0101:
0102:  timeofday(&t0);
0103:
0104:  while ( (b1 = gpio_read(gpio_pin)) == b0 && !timeout )
0105:       ;
0106:  timeofday(&t1);
0107:
0108:  if ( !timeout ) {
0109:        *nsec = ns_diff(&t0,&t1);
0110:        return b1;
0111:  }
0112:  *nsec = 0;
0113:  return 0;
0114: }
Listing 22-7

The wait_change() function from dht11.c

The program takes a current reading of the GPIO and saves it in variable b0 (line 100). The initial time t0 is captured in line 102. Lines 104 and null statement in line 105 form a tight loop. The current GPIO reading is read and stashed into variable b1. As long as the value of b1 equals the initial value b0, the loop continues. The variable timeout is also tested. As long as the volatile bool timeout remains false, the loop continues. Later on, we’ll see how the timeout value gets set.

The loop normally exits once the GPIO has changed from its initial value. The stop time is captured into t1 in line 106. As long as there is no timeout, line 109 computs and returns the number of nanoseconds elapsed. Line 110 returns the current state of the GPIO.

When a timeout has occurred, we simply return zero for the elapsed time and return zero for the current GPIO value. The purpose at this point is to break out of the while loop. Missed events can occur, especially since the Linux operating system can preempt the execution of the program . If it tries to read 40 bits of data but is missing the reading of one or two signal changes, the loop can be hung forever.

Timout Handling

Given the potential for a program hang, an interval timer is used. At the start of a critical section, the timer is started by calling set_timer(), in Listing 22-8. This starts a timer in the Linux kernel that we don’t have to manage beyond starting.

The interval timer’s configuration is established in the structure timer (line 28 to 31). We don’t want the timer to restart, so the timer.it_interval member is initialized to zero (line 29). Lines 34 and 35 establish the time we want to elapse before the timer expires. Once the timer expires, it will not auto renew.

When the timer expires, the Linux kernel will call our timeout handler named sigalrm_handler() declared in lines 145 to 147. All it does is set the Boolean variable timeout to true. Signal handlers are called as asynchronously. Consequently they must never call non-reentrant routines like printf() or malloc() etc. because the call could arrive at any time. You would not want to call malloc() when the signal interrupted malloc() in the middle of doing its thing.

Also because the signal handler is asynchronous, its handling is like that of another thread. If the variable timeout were not declared volatile, the looping code might never notice that it was changed to true, because the compiler cached the value in a register.
0026: static inline void
0027: set_timer(long usec) {
0028:  static struct itimerval timer = {
0029:        { 0, 0 },    // Interval
0030:        { 0, 0 }     // it_value
0031:  };
0032:  int rc;
0033:
0034:  timer.it_value.tv_sec = 0;
0035:  timer.it_value.tv_usec = usec;
0036:
0037:  rc = setitimer(ITIMER_REAL,&timer,NULL);
0038:  assert(!rc);
0039:  timeout = false;
0040: }
...
0144: static void
0145: sigalrm_handler(int signo) {
0146:  timeout = true;
0147: }
Listing 22-8

The timer and handler in dht11.c

The initial setup of the timer handler for signal SIGALRM, is performed in the main program as shown in Listing 22-9. Once the timeout handler is established, it merely needs a call to set_timer() to start it “ticking.” This is performed at the start of the larger loop that reads the signal on the bus. If the timer gets triggered before the entire response of the DHT11 is read, the loop is exited from line 104, of Listing 22-7.
0187:  new_action.sa_handler = sigalrm_handler;
0188:  sigemptyset(&new_action.sa_mask);
0189:  new_action.sa_flags = 0;
0190:  sigaction(SIGALRM,&new_action,NULL);
Listing 22-9

The timer setup in dht11.c

Demonstration

The demonstration program can be started once you have things wired. This example illustrates specifying the GPIO as 22 but this is the default.
$ ./dht11 -g22
0000: RH 32% Temperature 25 C
0001: RH 32% Temperature 26 C
0002: RH 32% Temperature 26 C
0003: RH 32% Temperature 26 C
0004: RH 32% Temperature 26 C
0005: RH 32% Temperature 26 C
0006: RH 32% Temperature 26 C
0007: RH 32% Temperature 26 C
0008: RH 32% Temperature 26 C

On a fast Pi, like the Pi 3 B+, you should see output like this. If you’re not seeing any successful reads, then check your wiring. Don’t forget the pull-up resistor.

However, due to missed events, it is possible to see some errors:
0040: RH 32% Temperature 26 C
0041: Fail, Checksum error.
0101: RH 32% Temperature 26 C
0102: RH 32% Temperature 26 C
0103: RH 32% Temperature 26 C
0104: RH 32% Temperature 26 C
0105: Fail, Checksum error.
0106: RH 32% Temperature 26 C

Don’t be surprised by this because we are performing real-time measurement of signals on a non-real-time operating system. The Raspberry Pi Zero and Zero W are likely to see more errors due to the lower performance. The Zero will still return good readings often enough to make the project worthwhile. In a finished project, you will simply modify the code to suppress error reports.

Summary

This chapter has tackled the difficulty of reading the DHT11’s real-time signal on a system that does not provide real-time scheduling. Through use of direct GPIO access, we obtained fast enough access to measure signal changes. Applying an interval timer provided recovery safety, if the program got stuck waiting for lost events. These are some of the sneaky things that must be done to solve thorny problems.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.33.157