Using DHT11 with ChibiOS/RT

A cheap sensor for humidity and temperature

The DHT11 is a basic, low-cost digital temperature and humidity sensor. It uses a capacitive humidity sensor and a thermistor to measure the surrounding air, and spits out a digital signal on the data pin (no analog input pins needed).

The communication on data pin occurs according to a non-standard protocol known as One-Wire protocol.

Getting started with DHT11

In this article we want to provide a demonstration for DHT11 compatible with ChibiOS/HAL 3.0, but also explain concepts inside that demo. Following that purpose we need for the DHT11 datasheet.

DHT11 Datasheet

Features and connections

DHT11 mechanical draft
DHT11 mechanical draft.

Typical connections

Typical circuit requires a pull-up resistor on data pin. In our case Data out is connected directly to Port A, pad 8 of a ST Nucleo F4 using a simple short wire, and it works perfectly. Anyway in a definitive circuit is suggested to connect a pull-up resistor of 4,7 kΩ as shown at page 4 of datasheet.

Pin Description

  1. the VDD power supply 3.5~5.5V DC
  2. DATA serial data, a single bus
  3. NC, empty pin
  4. GND ground, the negative power

Data format

When the DHT11 is queried , it replies using a 40bit length data packet. Data is organized as:

  • 8bit humidity integer data + 8bit humidity decimal data
  • 8bit temperature integer data + 8bit temperature decimal data
  • 8bit parity bit

Humidity rate data is provided as percentage with resolution 1%. Temperature is provided as Celsius with resolution of 1°C. That means on DHT11 data packet, decimal data is always zero. This is quite different for DHT22 that provides same data packet but with higher resolution and in this case decimal data is non-zero.

Data timing

DHT11 timing diagram
A timing diagram for the DHT11 one wire protocol communication.

According to data sheet DHT11 must be queried from host(our MCU) pulling down data pin for at least 18ms, than sensor lowers data pin for 80us as the response signal, followed by an high logical state output of 80 micro-seconds as notification of starting to send the data packet: so sensor replies with data. DHT11 actually sends out square waves and bit could be recognized measuring the time during which signal stays high: if that time is 26-28us it is a 0-bit, if it is 70 us is a 1-bit.

DHT11 bits
Differences between ‘0’ and ‘1’ bit format.

Summering getting data from DHT11 requires:

  1. Set a pin as output and pull down that output to start communication.
  2. Set the same pin as input to capture the square wave measuring high and low logical state period.
  3. Eventually control if communication succeeds using check sum.

Using DHT11 with ChibiOS/HAL 3.0

Rereading above, the question is: what we need for to use DHT11 with ChibiOS? Basically one-wire protocol requires a pin that could be used as output push-pull as well as capture input. HAL has a driver that can capture an input (typically a square wave) and measure time period as well as time during which signal is in logical high state or logical low state. This driver is Input Capture Unit (ICU) ad is associated to a TIM like PWM.

Basically every pin could be used as digital output, but only some pin are connected to a TIM. We have to choose a pin internally connected to a timer channel. Reading Alternate Function table on data-sheet we could see that PA8 could be used as TIM1 Channel 1 thought AF 1 (If you are not familiar with PAL driver checkout the tutorial Hello ChibiOS). Reading ST Nucleo user manual we could learn that PA8 is connected to D7 pin of Arduino Connector.
Let outlining our program flow:

  1. Set PA8 mode as output push-pull and request data to DHT11 lowering Data out for at least 18ms.
  2. Set PA8 as AF1 and start ICU with a clock of at least 1MHz (This way we have a resolution of 1us).
  3. Start capturing data with ICU. Using ICU width callback acquire 41 widths.
  4. For each acquisition we need to write some variable according to widths.
  5. Eventually stop the ICU and restore PA8 mode to default configuration (input pull-up)

Note that first acquisition that should be 80 ticks (80us since our resolution is 1us) and others should be 26-28 ticks for a 0-bit or 70 ticks for 1-bit.

Creating this application from scratch

First step as always is duplicate a working project (If you don’t know how to do that checkout the tutorial Quickview of Chibistudio paying particular attention to video. Once we have created a new project, we have to enable ICU in halconf.h.

/**
 * @brief   Enables the ICU subsystem.
 */
#if !defined(HAL_USE_ICU) || defined(__DOXYGEN__)
#define HAL_USE_ICU                 TRUE
#endif

Since we want to use PA8 and so TIM1 CH1 we need to enable ICUD1 in mcuconf.h

/*
 * ICU driver system settings.
 */
#define STM32_ICU_USE_TIM1                  TRUE

If we want to use chprintf() to print data on a serial we have to edit makefile adding memstreams.c and chprintf.c to CSRC and including related folder modifying INCDIR

# C sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
CSRC = $(STARTUPSRC) \
       $(KERNSRC) \
       $(PORTSRC) \
       $(OSALSRC) \
       $(HALSRC) \
       $(PLATFORMSRC) \
       $(BOARDSRC) \
       $(TESTSRC) \
       $(CHIBIOS)/os/hal/lib/streams/memstreams.c \
       $(CHIBIOS)/os/hal/lib/streams/chprintf.c \
       main.c
...
INCDIR = $(STARTUPINC) $(KERNINC) $(PORTINC) $(OSALINC) \
         $(HALINC) $(PLATFORMINC) $(BOARDINC) $(TESTINC) \
         $(CHIBIOS)/os/hal/lib/streams \
         $(CHIBIOS)/os/various

Now we are ready to write main.c. First of all we have to include chprintf.h declare a base sequential stream to use with chprintf(). Furthermore we could define some constants.

/*
 * Enable if your terminal supports ANSI ESCAPE CODE
 */
#define ANSI_ESCAPE_CODE_ALLOWED                  TRUE
static BaseSequentialStream * chp = (BaseSequentialStream*) &SD2;
/*===========================================================================*/
/* DHT11 related defines                                                     */
/*===========================================================================*/
/*
 * Width are in useconds
 */
#define    MCU_REQUEST_WIDTH                     18000
#define    DHT_ERROR_WIDTH                         200
#define    DHT_START_BIT_WIDTH                      80
#define    DHT_LOW_BIT_WIDTH                        28
#define    DHT_HIGH_BIT_WIDTH                       70
/*===========================================================================*/
/* ICU related code                                                          */
/*===========================================================================*/
#define    ICU_TIM_FREQ                        1000000

Now we can write an ICU configuration according to ChibiOS documentation. Since we need to measure time during high logical states we need to set ICU input as Active High. That means ICU width will measure time between signal rising edge and signal falling edge. Measure could have an uncertainly of ± 1 tick (In this case 1us). Other configuration are width, period and overflow callbacks, channels selected and value of DIE Register of TIM. We choose only width callback and enable channel 1.

static ICUConfig icucfg = {
  ICU_INPUT_ACTIVE_HIGH,
  ICU_TIM_FREQ,                                /* 1MHz ICU clock frequency.   */
  icuwidthcb,
  NULL,
  NULL,
  ICU_CHANNEL_1,
  0
};

ICU width callback must be declared before ICU configuration. According to our configuration (ICU_INPUT_ACTIVE_HIGH), width callback is called on falling edge. Here we can get a width measure from driver ICU, and make some elaboration. Lets take a look to the code.

static uint8_t TEMP, HR, CHECK_SUM, tmp, bit_counter = 0;;
static icucnt_t widths [40];
static void icuwidthcb(ICUDriver *icup) {
  icucnt_t width = icuGetWidthX(icup);
  if(width >= DHT_START_BIT_WIDTH){
    /* starting bit resetting the bit counter */
    bit_counter = 0;
  }
  else{
    /* Recording current width. Just for fun  */
    widths[bit_counter] = width;
    if(width > DHT_LOW_BIT_WIDTH){
      tmp |= (1 << (7 - (bit_counter % 8)));
    }
    else{
      tmp &= ~(1 << (7 - (bit_counter % 8)));
    }
    /* When bit_counter is 7, tmp contains the bit from 0 to 7 corresponding to
       The Humidity Rate integer part (Decimal part is 0 on DHT 11) */
    if(bit_counter == 7)
      HR = tmp;
    /* When bit_counter is 23, tmp contains the bit from 16 to 23 corresponding to
       The Temperature integer part (Decimal part is 0 on DHT 11) */
    if(bit_counter == 23)
      TEMP = tmp;
    /* When bit_counter is 39, tmp contains the bit from 32 to 39 corresponding to
       The Check sum value */
    if(bit_counter == 39)
      CHECK_SUM = tmp;
    bit_counter++;
  }
}

This code gets the latest width, checking if it is related to a start bit (80us) or to one of the forty bits sent from DHT11. If current bit is the start one the counter bit_counter is reset. During the forty callback occurencies related to data bits, width is stored into a buffer (just for debug purposes).

bit_counter counts data related callback occurencies and data is “built” bit by bit and stored into the static variables (HR, TEMP, CHECK_SUM). To do this bit masks are used (Take a look at Registers and bit-masks). The tmp variable is used as temporary variable to build 8-bit lenght words during the sequence of callbacks. So when bit_counter reaches the value 7, tmp contains the integer part of the humidity rate, on 15 the decimal part of the humidity rate, on 23 the integer part of the temperature and so on…

Main initialize serial driver (We will use it with chprintf), set pin properly to make request, reset pin for ICU launching this driver, waits time needed by communication and prints out data.

How to port this application on other MCUs

Most likely we have just to change ICU configuration and pin used by data. Note that every driver configuration in ChibiOS has a part common to every MCU. For ICU the timer frequency, capture mode, and callback are independent from platform. We should expect to modify last part of configuration like DIER. A good idea to understand how is structured configuration, is copying it from a platform demo in chibios3/testhal (same MCU same configuration, so, as example F401RE and F407 have same configurations).

/*
    PLAY Embedded demos - Copyright (C) 2014-2016 Rocco Marco Guglielmi

    This file is part of PLAY Embedded demos.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/*
    Tested under ChibiOS 16.1.4, Project version 1.1

    * 1.1 Change-log
    * - Fixed bug on Checksum as suggested by Dave Edwards.
 */
#include "ch.h"
#include "hal.h"

#include "chprintf.h"

/*
 * Enable if your terminal supports ANSI ESCAPE CODE
 */
#define ANSI_ESCAPE_CODE_ALLOWED                  TRUE

static BaseSequentialStream * chp = (BaseSequentialStream*) &SD2;
/*===========================================================================*/
/* DHT11 related defines                                                     */
/*===========================================================================*/
/*
 * Width are in useconds
 */
#define    MCU_REQUEST_WIDTH                     18000
#define    DHT_ERROR_WIDTH                         200
#define    DHT_START_BIT_WIDTH                      80
#define    DHT_LOW_BIT_WIDTH                        28
#define    DHT_HIGH_BIT_WIDTH                       70
/*===========================================================================*/
/* ICU related code                                                          */
/*===========================================================================*/

#define    ICU_TIM_FREQ                        1000000

static uint8_t TEMP, HR, CHECK_SUM, tmp, bit_counter = 0;;
static icucnt_t widths [40];

static void icuwidthcb(ICUDriver *icup) {

  icucnt_t width = icuGetWidthX(icup);

  if(width >= DHT_START_BIT_WIDTH){
    /* starting bit resetting the bit counter */
    bit_counter = 0;
  }
  else{
    /* Recording current width. Just for fun  */
    widths[bit_counter] = width;

    if(width > DHT_LOW_BIT_WIDTH){
      tmp |= (1 << (7 - (bit_counter % 8)));
    }
    else{
      tmp &= ~(1 << (7 - (bit_counter % 8)));
    }

    /* When bit_counter is 7, tmp contains the bit from 0 to 7 corresponding to
       The Humidity Rate integer part (Decimal part is 0 on DHT 11) */
    if(bit_counter == 7)
      HR = tmp;
    /* When bit_counter is 23, tmp contains the bit from 16 to 23 corresponding to
       The Temperature integer part (Decimal part is 0 on DHT 11) */
    if(bit_counter == 23)
      TEMP = tmp;
    /* When bit_counter is 39, tmp contains the bit from 32 to 39 corresponding to
       The Check sum value */
    if(bit_counter == 39)
      CHECK_SUM = tmp;
    bit_counter++;
  }
}


static ICUConfig icucfg = {
  ICU_INPUT_ACTIVE_HIGH,
  ICU_TIM_FREQ,                                /* 1MHz ICU clock frequency.   */
  icuwidthcb,
  NULL,
  NULL,
  ICU_CHANNEL_1,
  0
};

/*===========================================================================*/
/* Generic function                                                          */
/*===========================================================================*/

/*
 * Red LED blinker thread, times are in milliseconds.
 */
static THD_WORKING_AREA(waThread1, 128);
static THD_FUNCTION(Thread1, arg) {

  (void)arg;
  chRegSetThreadName("blinker");
  while (true) {
    palClearPad(GPIOA, GPIOA_LED_GREEN);
    chThdSleepMilliseconds(500);
    palSetPad(GPIOA, GPIOA_LED_GREEN);
    chThdSleepMilliseconds(500);
  }
}

/*
 * Application entry point.
 */
int main(void) {

  /*
   * System initializations.
   * - HAL initialization, this also initializes the configured device drivers
   *   and performs the board-specific initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  halInit();
  chSysInit();

  /*
   * Activates the serial driver 2 using the driver default configuration.
   */
  sdStart(&SD2, NULL);

  /*
   * Creates the blinker thread.
   */
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO, Thread1, NULL);

  chThdSleepMilliseconds(1000);

  /*
   * Normal main() thread activity, in this demo it does nothing except
   * sleeping in a loop and check the button state.
   */
  while (true) {
    /*
     * Making a request
     */
    palSetPadMode(GPIOA, GPIOA_PIN8, PAL_MODE_OUTPUT_PUSHPULL);
    palWritePad(GPIOA, GPIOA_PIN8, PAL_LOW);
    chThdSleepMicroseconds(MCU_REQUEST_WIDTH);
    palWritePad(GPIOA, GPIOA_PIN8, PAL_HIGH);

    /*
     * Initializes the ICU driver 1.
     * GPIOA8 is the ICU input.
     */
    palSetPadMode(GPIOA, GPIOA_PIN8, PAL_MODE_ALTERNATE(1));
    icuStart(&ICUD1, &icucfg);
    icuStartCapture(&ICUD1);
    icuEnableNotifications(&ICUD1);
    chThdSleepMilliseconds(700);
#if ANSI_ESCAPE_CODE_ALLOWED
    chprintf(chp, "\033[2J\033[1;1H");
#endif
    icuStopCapture(&ICUD1);
    icuStop(&ICUD1);

    chprintf(chp, "Temperature: %d C, Humidity Rate: %d %% \n\r", TEMP, HR);
    if(CHECK_SUM == (TEMP + HR)){
      chprintf(chp, "Checksum OK!\n\r");
    }
    else{
      chprintf(chp, "Checksum FAILED!\n\r");
    }
  }
}

Project download

The attached demo has been tested under ChibiOS 17.6.x. Note that you can find more recent version of this project int the Download Page. Note also that starting from the version 20 all the demos from PLAY Embedded will be distributed together with ChibiStudio.

RT-STM32F401RE-NUCLEO-DHT11-176

Replies to Using DHT11 with ChibiOS/RT

  • Hi, starting from your example, I’m trying to use the ICU driver on a F401RE to acquire the signal from the PWM generated on PWMD1 (using TIM1 on PB4) on PB6 setting it as ALTERNATE(1) and using TIM4. However, once I’ve started the ICU driver and i’ve electrically connected PB6 to PB4, it seems that the ICU callbacks are never triggered. Do I miss to configure something? ANy hint?

    • Hi,
      looking at STM323F401RE datasheet it seems that on PB4 there is TIM3CH1 mapped on AF2 and on PB6 there is TIM4CH1 mapped on AF2. BTW I don’t have figured out which demo are you talking about. FYI next time you need help we have also a forum (). Thanks for reading us and for your comment.

  • Thanks! I followed your instructions for my AM2303 sensor, thinking it was the same as a DHT-11. After getting the system running on my STM32F401RE Nucleo board connected to the sensor, I realized I was getting what looked like bad data from the sensor, along with checksums that were failing.

    Looking at the data on an oscilloscope however, the checksums were correct.

    It turns out that the AM2303 is actually compatible with the DHT-22 sensor, not the DHT-11. The pinout and one wire protocol is exactly the same, but the data is sent in a slightly different format. The low bytes for temperature and humidity are not zeros, so must be captured and included in the checksum.

    The data display format should also be handled slightly differently, since it has higher resolution than the DHT-11 data. (0.1 degree C and 0.1 % humidity instead of 1 degree and 1%).

    Once I realized the difference it was easy to update your code to work with the DHT-22 sensors. I’d be happy to send you the changes I made if you’re interested.

    • Thanks for this reply and for these kind words.
      First time I wrote the code it was for a DHT22 (AM2303). I edited it before to publish it on this website since DHT11 is cheaper than DHT22 and hence more popular. Feel free to share with us your code. Since the biggest problem is the maintenance of these articles and of the code, maybe the best way for me will be to add a preprocessor switch in the demo which allows to choose between DHT11 and DHT22. Some textual notes in the article will be also added.

      Thanks,
      RM

  • Hi everyone. I am working with the DHT22 and dont understand all your code for the changes. Please, if u like explain a little more the problem or send the code for the DHT22. Thanks

    • Hello Daniel.
      Right now is quite complicated to edit this code to make it compliant with DHT22 but I have a solution in mind. Maybe tomorrow I will update it. Keep in touch.

  • Hi Rocco.
    I changed the code and now i’m reading the DHT22 in a STM32F100. With your code I understood several things about Chibi and I was able to do what I needed.. Thanks 🙂

    • Dear,
      your comment is completely out of topic. Please take care next time. Anyway, I never used ds1307 RTC as most of STM32 MCUs already have embedded RTC. I would suggest you to migrate to STM32F3. In this case ChibiOS already has RTC driver.

Leave a Reply