In this project I develop an Arduino library to read the data generated by the DHT11 Humidity and Temperature sensor to send it continuously to the ThingSpeak cloud services with an AZDelivery ESP8266 ESP-12F NodeMCU Lua Amica V2.
There are many libraries ready to use to read DHT11 sensor data. So, why reinvent the wheel? Just for the pleasure of understanding how things work and maybe improving them.
I use a logic analyzer and the DHT11 sensor datasheets to understand the protocol and develop my own library for the sensor.DHT11 Temperature & Humidity Sensor
DHT11 Temperature & Humidity Sensor features a temperature & humidity sensor complex with a calibrated digital signal output. By using the exclusive digital-signal-acquisition technique and temperature & humidity sensing technology, it ensures high reliability and excellent long-term stability. This sensor includes a resistive-type humidity measurement component and an NTC temperature measurement component, and connects to a high performance 8-bit microcontroller, offering excellent quality, fast response, anti-interference ability and cost-effectiveness.
The breakout module used already has a 10 KOhm pull-up resistor.
DHT11 data pin is connected to D1 (GPIO5)
GPIO 0-15 all have a built-in pull up resistor that we could have used if using a DHT11 directly instead a breakout.
More information of how to setup this module in my NodeMCU Amica V2 road test
AZ Delivery NodeMCU V2 road test By Enrique Albertos.
https://www.hackster.io/javagoza/nodemcu-amica-v2-road-test-2e8bff
MCU to DHT11 communicationDHT11 Single-bus data format
Single-bus data format is used for communication and synchronization between MCU and DHT11 sensor. One communication process is about 4ms. Data consists of decimal and integral parts. A complete data transmission is 40 bit, and the sensor sends higher data bit first.
Data format:
8
bit integral RH data + 8
bit decimal RH data + 8
bit integral T data + 8
bit decimal T data + 8
bit checksum.
If the data transmission is right, the checksum should be the last 8bit of:
8
bit integral RH data + 8
bit decimal RH data + 8
bit integral T data + 8
bit decimal T data
PulseView is a graphical frontend for the libsigrok and libsigrok decode libraries, permitting access to a wide range of devices and protocol decoders to let you record, analyze, process and export analog and logic data.
Logic analyzer
We are using this cheap Logic Analyzer from AZ-Delivery to spy the communication between the MCU, and AZ Delivery NodeMCU 8266 Amica module.
The logic analyzer has 8 parallel inputs (0-5 V) and allows at up to 24 million measurement steps per second. For digital signals, you must sample 4 times faster than the bandwidth. That means the bandwidth is one-quarter of the sample rate.
- Maximum digital sample rate: 24 MSPS
- Maximum digital bandwidth: 6 MHz
This is enough for the 200 Khz needed to spy this simple communication.
PulseView
After installation, you will find a program called Zadig in the start menu. By default, certain devices recognized by Windows will have drivers installed for them that PulseView cannot use. The purpose of Zadig is to let you change the driver Windows uses for a particular device - for most devices you’ll need to choose WinUSB to use them with PulseView or the original proprietary Windows driver to use it with whatever other software you access the device with.
New session
- Open a new session
- Select the device you want to work with:
- Click "Run" to acquire signal data (waiting for a trigger first if you set one)
Zoom until you see the signals, communication between MCU and DHT11 takes about 135 ms.
This is the stream of bits when requested the sensor for data.
DHT11 single wire communication protocol decoder
Pulseview comes with a DHT11 protocolo decoder. Let's use it.
Add the decoder and associate it to a serial data line. D1 in our case.
This decoder handles the proprietary single wire communication protocol used by the Aosong AM230x/DHTxx/RHTxx series of digital humidity and temperature sensors.
Sample rate: A sample rate of at least 200 kHz is recommended to properly detect all the elements of the protocol.
Options:
The AM230x and DHTxx/RHTxx digital humidity and temperature sensors use the same single-wire protocol with different encoding of the measured values. Therefore the option 'device' must be used to properly decode the communication of the respective sensor. "dht11 device type" for our purpose.
Zooming to see the decoded data
Relative Humidity %
8 bit integral RH data + 8 bit decimal RH data
Temperature
8bit integral T data + 8 bit decimal T data
Checksum
8 bit integral RH data + 8 bit decimal RH data + 8 bit integral T data + 8 bit decimal T data
We have several approaches to read the data and handle the proprietary single wire communication protocol used by the DHT11:
- Blind cycle: waits a fixed amount of time and assume that the I/O will complete before that fixed delay has elapsed. This method it is not suitable for us for reading the sensor data as 0s and 1s timings are different and you may lose sync, but we will use it to Start Signal to DHT as this is a blind signal that makes DHT11 to change from the low-power-consumption mode to the running-mode.
- Busy wait synchronization or polling. Software loop that checks the I/O status for the done state. This can be our first candidate for reading the data sent by the DHT11 as the system is very simple and a real-time response is not important.
- Interrupts. Uses the hardware to cause a special software execution. The hardware will request and interrupt when input device has new data. The software interrupt service will read from the input device and save in global RAM. To keep things simple, we will not use this method.
The Data Single-bus free status is at high voltage level. When the communication between MCU and DHT11 begins, the MCU program will set Data Single-bus voltage level from high to low and this process must take at least 18 ms to ensure DHT’s detection of MCU's signal, then MCU will pull up voltage and wait 20-40 us for DHT’s response.
/**
MCU Sends Start Signal to DHT as this is a blind cycle that makes DHT11
to change from the low-power-consumption mode to the running-mode
Consists of a pulse of at least 18 ms voltage-length
@params void
@return true if there no problem
*/
boolean DHT11::sendStartSignal(void) {
digitalWrite(pin, LOW); // MCU Send start signal
pinMode(pin, OUTPUT);
delay(timeLengthWakeupSignal_ms); // at least 18 ms
// MCU Pulls up voltage
pinMode(pin, INPUT);
digitalWrite(pin, HIGH); // Switch to receive data
return true;
}
The DHT11 module used in this project has a 10K pull-up resistor so we don't set INPUT_PULLUP.
Once DHT detects the start signal, it will send out a low-voltage-level response signal, which lasts 80 us. Then the programme of DHT sets Data Single-bus voltage level from low to high and keeps it for 80us for DHT’s preparation for sending data.
Then we will use a busy wait synchronization to detect the following pulse edges, form RAISING to FALLIN and from FALLING to RAISING
/**
Waits until digital port changes to final state or timeout occurs
@param pin to check
@param state to wait for
@param maximum time to wait
@return the elapsed time to reach the final state if less than timeout
or the elapsed time that forced the timeout
*/
int DHT11::busyWait(const int pin, const int finalState, const int timeout) {
int elapsedTime = 0;
int startTime = micros();
while ( digitalRead(pin) != finalState && elapsedTime < timeout ) {
elapsedTime = micros() - startTime;
}
return elapsedTime;
}
Busy wait for Start Signal ResponseFirst wait for start signal response
Once DHT detects the start signal, it will send out a low-voltage-level response signal, which lasts 80us. So we will wait for the DHT11 to be ready.
/**
Once DHT detects the start signal, it will send out a low-voltage-level
response signal, which lasts 80us. So we will wait for the DHT11 to be ready.
@param void
@return true if there no problem
*/
boolean DHT11::waitForStartSignalResponse(void) {
return busyWait(pin, LOW, timeoutForStartData_us) < timeoutForStartData_us;
}
Busy wait for Start BitThen wait for the start bit
/**
DHT sends out response signal and keeps it for 80 us
then DHT pulls up voltage and keeps it for 80 us
@param void
@return true if there no problem
*/
boolean DHT11::waitForStartBit(void) {
if ( busyWait(pin, HIGH, timeoutForResponseSignal_us) < timeoutForResponseSignal_us) { // DHT sends oit response signal and keeps it for 80 us
return (busyWait(pin, LOW, timeoutForStartData_us) < timeoutForStartData_us); // then DHT pulls up voltage and keeps it for 80 us
}
return false;
}
Busy wait for reading 40 bits of dataAn Finally read the 40 bits data
/*
Read the 40 bits in a Dht11 data record
@param pointer to the record to be actualized
@return true if there no problem
*/
boolean DHT11::readDht11DataRecord(DHT11::Dht11_data_type * dataRead)
{
uint8_t integralRh = readByte(pin, timeoutForStartToTransmitData_us, timeoutForData_us, zeroLength_us);
uint8_t decimalRh = readByte(pin, timeoutForStartToTransmitData_us, timeoutForData_us, zeroLength_us);
uint8_t integralTemp = readByte(pin, timeoutForStartToTransmitData_us, timeoutForData_us, zeroLength_us);
uint8_t decimalTemp = readByte(pin, timeoutForStartToTransmitData_us, timeoutForData_us, zeroLength_us);
uint8_t checksum = readByte(pin, timeoutForStartToTransmitData_us, timeoutForData_us, zeroLength_us);
// Verify checksum: integral RH + decimal RH + integral Temp data + decimal Temp data
if ( (integralRh + decimalRh + integralTemp + decimalTemp ) != checksum ) {
dataRead->error = ERROR_CHECKSUM;
dataRead->status = statusString[ERROR_CHECKSUM];
return false;
}
dataRead->error = ERROR_NONE;
dataRead->status = statusString[ERROR_NONE];
dataRead->temperature = (float)integralTemp + (float)decimalTemp / 10.0;
dataRead->humidity = (float)integralRh + (float)decimalRh / 10.0;
return true;
}
Reading a byteThe byte is initialized to b0000 0000 if a "1" bit is detected the bit position is toggled with the BIT_MASK
const uint8_t BIT_MASK[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
/*
Read 8 bits in a byte from the line
@param pointer to the record to be actualized
@param int pin,
@param int timeout For Start To Transmit Data in us
@param int timeout For Data in us,
@param int bit Zero Length in us
@return the read byte if any or FF if there is any problem reading
*/
uint8_t DHT11::readByte(const int pin,
const int timeoutForStartToTransmitData_us,
const int timeoutForData_us,
const int bitZeroLength_us) {
uint8_t data = 0;
for (int i = 7; i >= 0; i--) {
if (busyWait(pin, HIGH, timeoutForStartToTransmitData_us) < timeoutForStartToTransmitData_us) { // wait for RAISING
int bitLength = busyWait(pin, LOW, timeoutForData_us) ; // wait for FALLING
if (bitLength > timeoutForData_us) {
return 0xFF;
}
if (bitLength > bitZeroLength_us) { // 1 bit value, toggle bit
data |= BIT_MASK[i];
}
}
}
return data;
}
Checking the solution- Create DHT11 object
- Call setup
- Call readSensor when you need a new lecture. NOTE: Sampling periods at intervals should be no less than 1 second.
#include "DHT11.h"
DHT11 dht11; // DHT11 sensor
void setup()
{
Serial.begin(115200); // for debugging
Serial.println();
Serial.println("Status\tHumidity (%)\tTemperature (C)");
// Initialize DHT11 sensor
dht11.setup(D1); // sensor in D1
}
void loop()
{
DHT11::Dht11_data_type sensorData = dht11.readSensor();
logData(&sensorData);
// NOTE: Sampling periods at intervals should be no less than 1 second
delay(2000); // Wait 2000 milliseconds for the next reading
}
/*
* Logs sensor data to serial log
*
* @params void
* @retun void
*/
void logData(DHT11::Dht11_data_type *sensorData){
Serial.print(sensorData->status);/* status of communication */
Serial.print("\t");
Serial.print(sensorData->humidity, 0);
Serial.print("\t\t");
Serial.println(sensorData->temperature, 2);
}
Logging data by the serial port.
First register at:
Create the channel with two Fields:
- Humidity
- Temperature
Fill your secret data at the file secrets.h
// Use this file to store all of the private credentials
// and connection details
#define SECRET_SSID "MySSID" // replace MySSID with your WiFi network name
#define SECRET_PASS "MyPassword" // replace MyPassword with your WiFi password
#define SECRET_CH_ID 0000000 // replace 0000000 with your channel number
#define SECRET_WRITE_APIKEY "XYZ" // replace XYZ with your channel write API Key
Main code:
void setup()
{
Serial.begin(115200); // for debugging
// Begin ThinkSpeak connection
WiFi.mode(WIFI_STA);
ThingSpeak.begin(client); // Initialize ThingSpeak
Serial.println();
Serial.println("Status\tHumidity (%)\tTemperature (C)");
// Initialize DHT11 sensor
dht11.setup(D1); // sensor in D1
}
void loop()
{
checkWifiConnection();
DHT11::Dht11_data_type sensorData = dht11.readSensor();
logData(&sensorData);
if (DHT11::ERROR_NONE == sensorData.error) {
sendDataToThingSpeak(&sensorData);
}
delay(120000); // Wait 120 seconds to update the channel again
}
Check WIFI connection
/*
* Check if wifi is connected
* if not reconnect
*
* @params void
* @retun void
*/
void checkWifiConnection(void) {
// Connect or reconnect to WiFi
if (WiFi.status() != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(SECRET_SSID);
while (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, pass); // Connect to WPA/WPA2 network.
Serial.print(".");
int counter = 0;
// wait for connection established
while ((WiFi.status() != WL_CONNECTED ) && (counter < 10)) {
delay(1000);
counter++;
}
}
Serial.println("\nConnected.");
}
}
Send data to ThingSpeak:
/*
* Sends sensor data to Thing speak
*
* @params sensor data record reference
* @retun void
*/
void sendDataToThingSpeak(DHT11::Dht11_data_type *sensorData) {
// Write to ThingSpeak. There are up to 8 fields in a channel, allowing you to store up to 8 different
// pieces of information in a channel. Here, we write to field 1.
ThingSpeak.setField(1, sensorData->temperature);
ThingSpeak.setField(2, sensorData->humidity);
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
if (x == 200) {
Serial.println("Temp Channel update successful.");
}
else {
Serial.println("Problem updating temp channel. HTTP error code " + String(x));
}
}
Logging
Comments