Article Index

In this chapter we make use of all of the ideas introduced in earlier chapters to create a raw interface with the low cost DHT11/22 temperature and humidity sensor. It is an exercise in implementing a custom protocol directly in C. 

 

 

 

Now On Sale!

You can now buy a print or ebook edition of Raspberry Pi IoT in C from Amazon.

 

For Errata and Listings Visit: IO Press

 

 

This our ebook on using the Raspberry Pi to implement IoT devices using the C programming language. The full contents can be seen below. Notice this is a first draft and a work in progress. 

Chapter List

  1. Introducing Pi (paper book only)

  2. Getting Started With NetBeans In this chapter we look at why C is a good language to work in when you are creating programs for the IoT and how to get started using NetBeans. Of course this is where Hello C World makes an appearance.

  3. First Steps With The GPIO
    The bcm2835C library is the easiest way to get in touch with the Pi's GPIO lines. In this chapter we take a look at the basic operations involved in using the GPIO lines with an emphasis on output. How fast can you change a GPIO line, how do you generate pulses of a given duration and how can you change multiple lines in sync with each other? 

  4. GPIO The SYSFS Way
    There is a Linux-based approach to working with GPIO lines and serial buses that is worth knowing about because it provides an alternative to using the bcm2835 library. Sometimes you need this because you are working in a language for which direct access to memory isn't available. It is also the only way to make interrupts available in a C program.

  5. Input and Interrupts
    There is no doubt that input is more difficult than output. When you need to drive a line high or low you are in command of when it happens but input is in the hands of the outside world. If your program isn't ready to read the input or if it reads it at the wrong time then things just don't work. What is worse is that you have no idea what your program was doing relative to the event you are trying to capture - welcome to the world of input.

  6. Memory Mapped I/O
    The bcm2835 library uses direct memory access to the GPIO and other peripherals. In this chapter we look at how this works. You don't need to know this but if you need to modify the library or access features that the library doesn't expose this is the way to go. 

  7. Near Realtime Linux
    You can write real time programs using standard Linux as long as you know how to control scheduling. In fact it turns out to be relatively easy and it enables the Raspberry Pi to do things you might not think it capable of. There are also some surprising differences between the one and quad core Pis that make you think again about real time Linux programming.

  8. PWM
    One way around the problem of getting a fast response from a microcontroller is to move the problem away from the processor. In the case of the Pi's processor there are some builtin devices that can use GPIO lines to implement protocols without the CPU being involved. In this chapter we take a close look at pulse width modulation PWM including, sound, driving LEDs and servos.

  9. I2C Temperature Measurement
    The I2C bus is one of the most useful ways of connecting moderately sophisticated sensors and peripherals to the any processor. The only problem is that it can seem like a nightmare confusion of hardware, low level interaction and high level software. There are few general introductions to the subject because at first sight every I2C device is different, but here we present one.

  10. A Custom Protocol - The DHT11/22
    In this chapter we make use of all of the ideas introduced in earlier chapters to create a raw interface with the low cost DHT11/22 temperature and humidity sensor. It is an exercise in implementing a custom protocol directly in C. 

  11. One Wire Bus Basics
    The Raspberry Pi is fast enough to be used to directly interface to 1-Wire bus without the need for drivers. The advantages of programming our own 1-wire bus protocol is that it doesn't depend on the uncertainties of a Linux driver.

  12. iButtons
    If you haven't discovered iButtons then you are going to find of lots of uses for them. At its simples an iButton is an electronic key providing a unique coce stored in its ROM which can be used to unlock or simply record the presence of a particular button. What is good news is that they are easy to interface to a Pi. 

  13. The DS18B20
    Using the software developed in previous chapters we show how to connect and use the very popular DS18B20 temperature sensor without the need for external drivers. 

  14. The Multidrop 1-wire bus
    Some times it it just easier from the point of view of hardware to connect a set of 1-wire devices to the same GPIO line but this makes the software more complex. Find out how to discover what devices are present on a multi-drop bus and how to select the one you want to work with.

  15. SPI Bus
    The SPI bus can be something of a problem because it doesn't have a well defined standard that every device conforms to. Even so if you only want to work with one specific device it is usually easy to find a configuration that works - as long as you understand what the possibilities are. 

  16. SPI MCP3008/4 AtoD  (paper book only)

  17. Serial (paper book only)

  18. Getting On The Web - After All It Is The IoT (paper book only)

  19. WiFi (paper book only)


The Device

The DHT22 is a more accurate version of the DHT11 and it is used in this project but the hardware and software will work with both versions and with the AM2302 which is similar to the DHT22. 

Model AM2302/DHT22

Power supply 3.3-5.5V DC
Output signal digital signal via 1-wire bus 
Sensing element Polymer humidity capacitor
Operating range
  humidity 0-100%RH;
  temperature -40~80Celsius
Accuracy
 humidity +-2%RH(Max +-5%RH);
 temperature +-0.5Celsius
Resolution or sensitivity
 humidity 0.1%RH;
 temperature 0.1Celsius
Repeatability
 humidity +-1%RH;
 temperature +-0.2Celsius
 

 

So the device will work at 3.3V and it makes use of a 1-wire open collector style bus which makes it very easy to make the physical connection to the Pi.

The one wire bus used isn't standard and is only used by this family of devices so we have little choice but to implement the protocol in C. 

 

 The pin outs are:

  1. VDD
  2. SDA serial data
  3. not used
  4. GND

and the standard way of connecting the device is:

Although the recommended pull up resistor is 1K a higher value works better with the Pi - typically 4.7K but larger will work.

The serial protocol is also fairly simple:

  • The host pulls the line low for between 0.8 and 29 ms,
    usually 1ms
     
  • It then releases the bus which is pulled high 
     
  • After between 20 and 200 microseconds, usually 30 microseconds, the device starts to send data by pulling the line down for around 80 microseconds and then lets float high for another 80 microseconds. 
     
  • Next 40 bits of data are sent using a 70 microsecond high for a 1 and a 26 microsecond high for a zero the high pluses are separated by around 50 microsecond low period. 
     

 

 

So what we have to do is pull the line low for 1ms or so to start the device sending data and this is very easy. Then we have to wait for the device to pull the line down and let it pull up again - about 160 microsecond and then read the time that the line is high 80 times.

A one corresponds to 70 microseconds and a zero corresponds to 26 microseconds.

This is within the range of pulse measurement that can be achieved using standard library function. There is also a 50 microsecond period between each data bit an this can be used to do some limited processing. Notice that we are only interested in the time that the line is held high. 

When trying to work out how to decode a new protocol it often helps to try to answer the question - how can I tell the difference between a zero and a one. If you have a logic analyzer it can help to look at the wave form and see how you work it out manually. In this case for example, despite the complex looking timing diagram the difference comes down to a short verses a long pulse!


The Electronics

Exactly how you build the circuit is a matter of preference. The basic layout can be seen below.

.

It is very easy to create this circuit using a prototyping board and some jumper wires. You can also put the resistor close to the DHT22 to make a sensor package connected to the Pi using three cables.

The Software

With the hardware shown above connected to the Pi the first thing that we need to do is establish that the system is working - just a little.

The simplest way to do this is to pull the line down for 1ms and see if the device responds with a stream of pulses. These can be seen on a logic analyzer or an oscilloscope - both are indispensable tools. 

If you don't have access to either tool then you will just have to skip to the next stage and see if you can read in some data. 

The simplest program that will do the job is: 

void GetDHT22data(uint8_t pin) {
    bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
    bcm2835_gpio_write(pin, LOW);
    bcm2835_delayMicroseconds(1000);
    bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_INPT);
    return;
}

Setting the line initially high, to ensure that it is configured as an output, we then set it low, wait for around 1000 microseconds and then change its direction to input by reading the data.

There is no need to set the lines pull up mode because it is the only device driving the line until it releases the line by changing direction to input. When a line is in input mode it is high impedance and this is why we need an external pull up resistor in the circuit. 

As long as the circuit has been correctly assembled and you have a working device you should see something like:

 

 

Reading The Data

With preliminary flight checks complete it is time to read the 40-bit data stream.

The first thing to do is wait for the low that the device sends before the start bit:

 int i;
 for (i = 1; i < 2000; i++) {
   if (bcm2835_gpio_lev(pin)==0)break;            
 };

Next we can start to read in the bit stream.

When doing this there are two things to keep in mind.

The first is that it is only the time the line is high that matters and you need to measure just this accurately - you don't care so much about how long the line is low for.

The second is that it is usually better to collect the bits and only later process them and extract the data.

To this end it is usually a good idea to save the data in a buffer. 

uint64_tbuf[41];
int j;
 for(j=0;j<41;j++){
  for(i=1;i<2000;i++){
   if(bcm2835_gpio_lev(pin)==1)break;
  };
  t=bcm2835_st_read();
  for(i=1;i<2000;i++){
   if(bcm2835_gpio_lev(pin)==0) break;
  }
  buf[j]=(bcm2835_st_read()-t);  
}

 

You should be able to see how this works.

The outer for loop, indexed on j,  repeats to read in all 41 bits, 40 data bits and the initial start bit.

The first inner loop waits for the line to go high. The final loop count gives us a measure of how long the line has been low. This is of no interest because the device keeps the line low for the same length of time for a zero or a one.

Next the second for loop waits for the line to go low. The final count i is now proportional to the time the line was high. For machines that don't have a microsecond clock this would be enough to decode the data. In this case we can use the low order 32 bits of the Pi's clock as returned by cm2835_st_read(). We just need the difference between the time the clock when high and returned to low to work out if the data is a one or a zero.

If you add 

for(j=0;j<=40;j++){
 printf("%d %d \n",j,buf[j]);
}

to the end of the program you will be able to see the counts and you should quickly be able to work out the value half way between the long one pulses and the short zero pulses.

Examining the data reveals that  short pulses returned 26 to 27 microseconds  and long returned 73 to 74. Thus the threshold half way between the two is approximately 50.

With a threshold of 50  we can classify the pulses into long and short and store one and zero in the buffer. 

int buf[41];
int j;
for(j=0;j<41;j++){
 for(i=1;i<2000;i++){
  if(bcm2835_gpio_lev(pin)==1)break;
 };
 t=bcm2835_st_read();
 for(i=1;i<2000;i++){
  if(bcm2835_gpio_lev(pin)==0) break;
 }
 buf[j]=(bcm2835_st_read()-t)>50;
}

You can afford to include this extra processing in the data collection loop because it happens while the line is low and we aren't interested in measuring this time accurately.  If we were then it would be a good idea to put all processing off until the loop had finished. Notice that now we can use an integer buffer because we aren't storing the 64 bit time in it just the result of the conditional expression which is zero or one. 

There is a small bug in this program as it stands but probably one that we can live with. The time difference will be wrong if the clock rolls over to zero. You can fix this by doing the arithmetic mod the size of the clock or you can simply put up with the fact that you will get a checksum error - very rarely. 

 


Extracting The Data

Now we have the data in the buffer as zeros and ones. All that remains is to decode it into temperature and humidity readings. But first we will convert the bits into five bytes of data. The simplest way of doing this is to write a function that will pack eight bits into an uint:

uint8_t getByte(int b,int buf[]){
 int i;
 uint8_t result=0;
 b=(b-1)*8+1;
 for(i=b;i<=b+7;i++){
  result= result<<1;
  result=result | buf[i];
 }
 return result;
}

The b parameter can be set to the byte that you want to extract from the array. For example, if b=2 then the for loop runs from i=9 to i=16 i.e. the second byte stored in the array.  

Notice that we skip the first bit because this just signals the start of the data. 

The bit manipulation in the for loop is a fairly standard shift left and or the least significant bit into the result. 

Using this function getting the five bytes is trivial:

    int byte1 = getByte(1, buf);
    int byte2 = getByte(2, buf);
    int byte3 = getByte(3, buf);
    int byte4 = getByte(4, buf);
    int byte5 = getByte(5, buf);

The first two bytes are the humidity measurement, the second two the temperature and the final byte is the checksum.  

The reason for the cast to int rather than keeping the data as bytes is that it makes computing the checksum easier.

The checksum is just the sum of the first four bytes reduced to eight bits and we can test it using:

printf("Checksum %hho %d \n",byte5,(byte1+byte2+byte3+byte4) & 0xFF);

If the two values are different there has been a transmission error.  The addition of the bytes is done as a full integer and then it is reduced back to a single byte by the and operation. 

If there is a checksum error. the simplest thing to do is get another reading from the device. However notice that you shouldn't read the device more than once every 2 seconds. 

The humidity and temperature data are also easy to reconstruct as they are transmitted high byte first and times 10 the actual value.

The humidity data is easy:

float humidity= (float) (byte1<<8 |byte2)/10.0;
printf("Humidity= %f \n",humidity);

The temperature data is slightly more difficult in that the top most bit is used to indicate a negative temperature. This means we have to test for the most significant bit and flip the sign of the temperature if it is set:

float temperature;
int neg=byte3 & 0x80;
byte3=byte3 & 0x7F;
temperature= (float) (byte3<<8 |byte4)/10.0;
if(neg>0)temperature=-temperature;
printf("Temperature= %f \n",temperature);

This complete the data processing.

A main program to call the measuring function is just:

int main(int argc, char** argv) {
 const struct sched_param priority = {5};
 sched_setscheduler(0, SCHED_FIFO, &priority);
 mlockall(MCL_CURRENT | MCL_FUTURE);
 if (!bcm2835_init())
     return 1;
 bcm2835_gpio_fsel(RPI_GPIO_P1_07, BCM2835_GPIO_FSEL_INPT);
 bcm2835_delayMicroseconds(1000);

 GetDHT22data(RPI_GPIO_P1_07);
 return 0;
}

You can, of course modify the GetDHT22data to not print any results and to return the temperature and humidity. You need to add a test on the checksum to take the measurement again if there is an error but you need to keep in mind the 2 second maximum reading rate. Using a static variable you could even slow down the rate that the GetDHT22 function was used to more than 2 seconds per call.

The point is that we have a simple and reliable program that reads the DHT22 without the need for drivers or a third party library. Decoding protocols of this sort is easy and makes your program self contained. 

 


The Complete Listing

That's all we need to do and the final program, complete with some minor tidying up can be seen below:

#include <bcm2835.h>
#include <stdio.h>
#include <sched.h>
#include <sys/mman.h>

uint8_t getByte(int b, int buf[]);
void GetDHT22data(uint8_t pin);

int main(int argc, char** argv) {
    const struct sched_param priority = {1};
    sched_setscheduler(0, SCHED_FIFO, &priority);
    mlockall(MCL_CURRENT | MCL_FUTURE);
    if (!bcm2835_init())
        return 1;
    bcm2835_gpio_fsel(RPI_GPIO_P1_07, BCM2835_GPIO_FSEL_INPT);
    bcm2835_delayMicroseconds(1000);

   GetDHT22data(RPI_GPIO_P1_07);
   return 0;
}

void GetDHT22data(uint8_t pin) {
    bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
    bcm2835_gpio_write(pin, LOW);
    bcm2835_delayMicroseconds(1000);
    bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_INPT);
    int i;
    for (i = 1; i < 2000; i++) {
        if (bcm2835_gpio_lev(pin) == 0)break;
    };
    uint64_t t;
    int buf[41];
    int j;
    bcm2835_delayMicroseconds(1);
    for (j = 0; j < 41; j++) {
        for (i = 1; i < 2000; i++) {
            if (bcm2835_gpio_lev(pin) == 1)break;
        };
        t = bcm2835_st_read();
        for (i = 1; i < 2000; i++) {
            if (bcm2835_gpio_lev(pin) == 0) break;
        }
        buf[j] = (bcm2835_st_read() - t) > 50;
    }

    int byte1 = getByte(1, buf);
    int byte2 = getByte(2, buf);
    int byte3 = getByte(3, buf);
    int byte4 = getByte(4, buf);
    int byte5 = getByte(5, buf);

    printf("Checksum %d %d \n\r", byte5,
            (byte1 + byte2 + byte3 + byte4) & 0xFF);
    float humidity = (float) (byte1 << 8 | byte2) / 10.0;
    printf("Humidity= %f \n\r", humidity);
    float temperature;
    int neg = byte3 & 0x80;
    byte3 = byte3 & 0x7F;
    temperature = (float) (byte3 << 8 | byte4) / 10.0;
    if (neg > 0)temperature = -temperature;
    printf("Temperature= %f \n\r", temperature);
    return;
}

uint8_t getByte(int b, int buf[]) {
    int i;
    uint8_t result = 0;
    b = (b - 1)*8 + 1;
    for (i = b; i <= b + 7; i++) {
        result = result << 1;
        result = result | buf[i];
    }
    return result;
}

Where Next

The DHT11/22 is a useful device but when it comes to low cost temperature measurement the DS18D20 is a very popular choice and it is more accurate than the DHT11/22. It is also an example of a standard 1-wire device which gives us access to a range of sensors that work in the same way. This is the subject of later chapters.

What is of importance next is getting the Pi on the IoT - after all it is the "Internet" of Things. So how do we connect our c programs with the outside world? The good news is that in most cases you don't need a web server, it is much easier than this. 

 

 

 

Now On Sale!

You can now buy a print or ebook edition of Raspberry Pi IoT in C from Amazon.

 

For Errata and Listings Visit: IO Press

 

 

This our ebook on using the Raspberry Pi to implement IoT devices using the C programming language. The full contents can be seen below. Notice this is a first draft and a work in progress. 

Chapter List

  1. Introducing Pi (paper book only)

  2. Getting Started With NetBeans In this chapter we look at why C is a good language to work in when you are creating programs for the IoT and how to get started using NetBeans. Of course this is where Hello C World makes an appearance.

  3. First Steps With The GPIO
    The bcm2835C library is the easiest way to get in touch with the Pi's GPIO lines. In this chapter we take a look at the basic operations involved in using the GPIO lines with an emphasis on output. How fast can you change a GPIO line, how do you generate pulses of a given duration and how can you change multiple lines in sync with each other? 

  4. GPIO The SYSFS Way
    There is a Linux-based approach to working with GPIO lines and serial buses that is worth knowing about because it provides an alternative to using the bcm2835 library. Sometimes you need this because you are working in a language for which direct access to memory isn't available. It is also the only way to make interrupts available in a C program.

  5. Input and Interrupts
    There is no doubt that input is more difficult than output. When you need to drive a line high or low you are in command of when it happens but input is in the hands of the outside world. If your program isn't ready to read the input or if it reads it at the wrong time then things just don't work. What is worse is that you have no idea what your program was doing relative to the event you are trying to capture - welcome to the world of input.

  6. Memory Mapped I/O
    The bcm2835 library uses direct memory access to the GPIO and other peripherals. In this chapter we look at how this works. You don't need to know this but if you need to modify the library or access features that the library doesn't expose this is the way to go. 

  7. Near Realtime Linux
    You can write real time programs using standard Linux as long as you know how to control scheduling. In fact it turns out to be relatively easy and it enables the Raspberry Pi to do things you might not think it capable of. There are also some surprising differences between the one and quad core Pis that make you think again about real time Linux programming.

  8. PWM
    One way around the problem of getting a fast response from a microcontroller is to move the problem away from the processor. In the case of the Pi's processor there are some builtin devices that can use GPIO lines to implement protocols without the CPU being involved. In this chapter we take a close look at pulse width modulation PWM including, sound, driving LEDs and servos.

  9. I2C Temperature Measurement
    The I2C bus is one of the most useful ways of connecting moderately sophisticated sensors and peripherals to the any processor. The only problem is that it can seem like a nightmare confusion of hardware, low level interaction and high level software. There are few general introductions to the subject because at first sight every I2C device is different, but here we present one.

  10. A Custom Protocol - The DHT11/22
    In this chapter we make use of all of the ideas introduced in earlier chapters to create a raw interface with the low cost DHT11/22 temperature and humidity sensor. It is an exercise in implementing a custom protocol directly in C. 

  11. One Wire Bus Basics
    The Raspberry Pi is fast enough to be used to directly interface to 1-Wire bus without the need for drivers. The advantages of programming our own 1-wire bus protocol is that it doesn't depend on the uncertainties of a Linux driver.

  12. iButtons
    If you haven't discovered iButtons then you are going to find of lots of uses for them. At its simples an iButton is an electronic key providing a unique coce stored in its ROM which can be used to unlock or simply record the presence of a particular button. What is good news is that they are easy to interface to a Pi. 

  13. The DS18B20
    Using the software developed in previous chapters we show how to connect and use the very popular DS18B20 temperature sensor without the need for external drivers. 

  14. The Multidrop 1-wire bus
    Some times it it just easier from the point of view of hardware to connect a set of 1-wire devices to the same GPIO line but this makes the software more complex. Find out how to discover what devices are present on a multi-drop bus and how to select the one you want to work with.

  15. SPI Bus
    The SPI bus can be something of a problem because it doesn't have a well defined standard that every device conforms to. Even so if you only want to work with one specific device it is usually easy to find a configuration that works - as long as you understand what the possibilities are. 

  16. SPI MCP3008/4 AtoD  (paper book only)

  17. Serial (paper book only)

  18. Getting On The Web - After All It Is The IoT (paper book only)

  19. WiFi (paper book only)

 

 

comments powered by Disqus