Advices to improve this simple DHT11 data read, or next steps ?

I've been trying to read DHT11 values from scratch.

I'm just posting to get some advice as to what could be important to know or change in terms of communication with the hardware, and any advice for possible next steps.

Of course, for some it may be all wrong, but some incremental advice wrt my current skills would be appreciated.

I'm also posting it so others can reuse, change and modify etc (MIT license.)

Here is the Arduino code (I don't know C++ so this uses the basic utilities / simplified C from Arduino):


/**
  Hi !!

  Code to read DHT11 values as in the ELEGOO UNO Kit.
  This code reads the binary serial-output values from the sensors.

  Note that the functions use as little extra stuff as possible to make 
  any delays as small as possible. I found problems otherwise.
*/
const uint8_t HT_DATA = 7;        // Pin Number for the Humidity or Temp pin.
const uint8_t over_zero_ms = 30;  // ensures we read just after 0_signal ends.
uint8_t bits[40] = {};            // store the 40 bits as they arrive (MSB order.)
float t = 0;                      // temp
float h = 0;                      // humidity
uint32_t execution_delay_ms = 1000; // loop frequency.

bool connect() {
  /**
    Start the connection between DHT11 and Arduino.
    Datasheet: https://www.circuitbasics.com/wp-content/uploads/2015/11/DHT11-Datasheet.pdf
    If it doesn't connect, returns `false` otherwise `true`.
  */
  pinMode(HT_DATA, OUTPUT);
  digitalWrite(HT_DATA, LOW);
  delay(20);
  pinMode(HT_DATA, INPUT_PULLUP);
  delayMicroseconds(40);
  delayMicroseconds(80);              // low ~80us, next goes high.
  if (digitalRead(HT_DATA) == LOW) {  // shouldn't be low anymore
    return false;
  }
  delayMicroseconds(80);
  if (digitalRead(HT_DATA) == HIGH) {  // shouldn't be high anymore
    return false;
  }
  return true;
  // if we got here, it starts sending the data.
}

void read_bits(uint8_t bits[40]) {
  /**
  Loop 40 times extracting the signal sent by DHT11
  And write it in the input array.
  */
  for (uint8_t i = 0; i < 40; i += 1) {
    // digital read takes a few us.
    while (digitalRead(HT_DATA) == LOW) {
      // ~50 us.
      // This fixes offsets that would otherwise accumulate.
    }
    delayMicroseconds(over_zero_ms);
    if (digitalRead(HT_DATA) == HIGH) {
      bits[i] = 1;
      delayMicroseconds(40);  // 1 uses +40 us in HIGH.
    } else {
      bits[i] = 0;
    }
  }
}

bool bits_to_values(uint8_t my_bits[40]) {
  /**
  Take the 40 bits, group them into 5 bytes, and calculate the values.
  The received order is based on the site:
  https://www.ocfreaks.com/basics-interfacing-dht11-dht22-humidity-temperature-sensor-mcu/
  The order is MSB.

  Returns whether the checksum matches our result.
  */
  uint8_t accs[5] = { 0, 0, 0, 0, 0 };  // H_int, H_dec, T_int, T_dec, Checksum.
  for (uint8_t acc = 0; acc < 5; acc += 1) {
    uint8_t offset = 8 * acc;  // no offset in first run.
    for (uint8_t i = 0; i < 8; i += 1) {
      uint8_t value = my_bits[i + offset];
      accs[acc] += value << (7 - i);  // `(7-i)` is because of MSB. LSB'd just use `i`.
    }
  }
  h = val_as_float(accs[0], accs[1]);
  t = val_as_float(accs[2], accs[3]);
  uint8_t ours = (accs[0] + accs[1] + accs[2] + accs[3]) % 255;
  uint8_t theirs = accs[4];

  return ours == theirs;
}

void print_values(float h, float t, bool match) {
  // simply print the values in a tidy way.
  Serial.print("CHECKSUM MATCH ? ");
  Serial.println(match);

  Serial.print("HUMIDITY: ");
  Serial.println(h);
  
  Serial.print("TEMPERATURE: ");
  Serial.println(t);
}

float val_as_float(uint8_t integer, uint8_t decimal) {
  // take integer and decimal, and make a single float value.
  float float_decimal = float(decimal);
  float float_integer = float(integer);
  while (float_decimal >= 1) {
    float_decimal = float_decimal / 10;
  }
  return float_integer + float_decimal;
}

void run() {
  if (!connect()) {
    Serial.print("Communication failed.");
    return;
  }
  read_bits(bits);
  bool match = bits_to_values(bits);
  print_values(h, t, match);
}

void setup() {
  Serial.begin(9600);
  delay(2000);  // initial stabilise
  run();
}

void loop() {
  delay(execution_delay_ms);
  run();
}

Have you reviewed your code?

Yeah, the code you quote is correct. I removed the first comment line, which was left from copy paste.

Most important question: does it work?
Can you post some output?

Advices to improve this simple DHT11 data read, or next steps ?

Some ideas:

  • support for DHT22
  • add code to range check e.g. if H > 100% or H < 0%
  • idem for T

You might take a look at my lib - GitHub - RobTillaart/DHTNew: Arduino library for DHT11 and DHT22 with automatic sensor recognition
and search for the differences.

1 Like

I'll try to address all tomorrow and reply, nice to get your response !

This is the output, seems very close to (your) DHTNew library which I used days ago. I could test to compare as well.

I do think that the 16 decimal bits for each case aren't useful, the hardware has error in the first integer (+-2, and +-5% for relative humidity.) but still are parsed..

16:03:41.571 -> HUMIDITY: 55.00
16:03:41.604 -> TEMPERATURE: 19.40
16:03:42.595 -> CHECKSUM MATCH ? 1
16:03:42.595 -> HUMIDITY: 55.00
16:03:42.628 -> TEMPERATURE: 19.40
16:03:43.617 -> CHECKSUM MATCH ? 1
16:03:43.649 -> HUMIDITY: 55.00
16:03:43.649 -> TEMPERATURE: 19.40
16:03:44.637 -> CHECKSUM MATCH ? 1
16:03:44.669 -> HUMIDITY: 55.00
16:03:44.669 -> TEMPERATURE: 19.40
16:03:45.655 -> CHECKSUM MATCH ? 1
16:03:45.686 -> HUMIDITY: 55.00
16:03:45.720 -> TEMPERATURE: 19.40
16:03:46.705 -> CHECKSUM MATCH ? 1
16:03:46.705 -> HUMIDITY: 55.00
16:03:46.738 -> TEMPERATURE: 19.40
16:03:47.725 -> CHECKSUM MATCH ? 1
16:03:47.725 -> HUMIDITY: 55.00
16:03:47.758 -> TEMPERATURE: 19.40
16:03:48.742 -> CHECKSUM MATCH ? 1
16:03:48.775 -> HUMIDITY: 55.00
16:03:48.775 -> TEMPERATURE: 19.40

Looks like a very stable output, well done!

1 Like

DHT22 widgets for your reader...

1 Like

This is interesting, and I appreciate your effort.

The whole point here though is going from scratch. Not being a "buy-this and copy-paste-code" monkey.

The aim is understand how to write code that interacts with hardware, from the datasheet.

So I was asking for advice in that sense, plus any additions or best practices. (See suggestions by @robtillaart for example.)

One of the questions for example, would be whether it's important to get this code into plain C++, or it's fine to keep with Arduino subset, or whether it needs better error handling, or whether some extra checks are needed, etc etc.

Another exercise you could do is to expand the code to an array of e.g 5 DHT11 sensors.
Then you could average the values to improve accuracy of the measurement. Also min and max of the day.

The next exercise would be to make the output of the array visible in the serial plotter in the IDE.

Another exercise would be control an output if the temperature is above and or under a threshold, e.g. control an RGB led.
Blue is cold, green is ok yellow is warm, orange is hot and red is dangerous hot.

1 Like

Thanks, that's a nice addition, I'll be testing several soon.

I'm currently looking into Arden Buck equation - Wikipedia

One could then use Ps * RH/100 to get the actual partial pressure (at the specific temperature.)

Then to calculate the speed of sound accurately, assuming one can find or derive the effect of water pressure (which is small, but this is for fun only.)

I found some magic equation elsewhere but don't trust it.

Just writing this up in case you've tried this already, and solved it.

I think one needs something like the ratio to the total pressure (so the total pressure.) to recalculate all partial pressures of gases in air, and then use sqrt(kRT) but seems a bit hard for me.

I understand. The widgets were just for a drop-in, visual interpretation of the data you are extracting. Things that entertain me are adding layers for more simple use, and "un-abstract" the functions, as you are doing here with calling registers. I also like to see raw values changing in real-time in a dashboard display (vt220, text-only, or text-based graphics).

[edit] In the line of "Moba Tools" which is not just a stepper library, but an "all things timing" library. So many use the HC-SR04 to measure distance, but do not have the temperature and pressure (and probably humidity) data - or know how to use it. Adding "all things temp/humi/press" functionality... might be good, or might not. That's why we have the choice of sports cars and caravans.

1 Like

I'm currently looking into Arden_Buck_equation

Interesting

Then to calculate the speed of sound accurately, assuming one can find or derive the effect of water pressure (which is small, but this is for fun only.)

Did some work there - GitHub - RobTillaart/SRF05: Arduino library for SRF05 distance sensor

These might be interesting reads

82084136.pdf (729.5 KB)

Environmental_effects_on_the_speed_of_sound.pdf (812.5 KB)

1 Like

i'll next read the paper, for the moment i did the derivation myself, in case you want to use any of it, it's here fluid dynamics - How to relate speed of sound with relative humidity? - Physics Stack Exchange

i can't be sure whether it's correct, but the current SoS is a reasonable value., and shows how to get both the pressure of vapour alone, saturation pressure etc.

The forum just had a user want to determine the speed of sound using the HC-SR04. Hope they find this link.

Done

Full code (click me) for DHT11:
/**
  Connecting a DHT11 as in the ELEGOO UNO Kit
  This code reads the serial-output values from the sensors.

  Note that the functions use as little extra stuff as possible to make 
  any delays as small as possible.
*/
const uint8_t HT_DATA = 7;        // Pin Number for the Humidity or Temp pin.
const uint8_t over_zero_ms = 30;  // ensures we read just after 0_signal ends.
uint8_t bits[40] = {};            // store the 40 bits as they arrive (MSB order.)
float t = 0;                      // temp C
float rh = 0;                     // % rel humidity
uint32_t execution_delay_ms = 3000;
float atmospheric_pressure = 1.01325;  //bars, P at sea level.
float M_dry_air = 28.9645;             // g/mol
float M_water = 18.015;                // g/mol
float R_u = 8.314462618;               //universal gas constant
bool connect() {
  /**
    Start the connection between DHT11 and Arduino.
    Datasheet: https://www.circuitbasics.com/wp-content/uploads/2015/11/DHT11-Datasheet.pdf
    If it doesn't connect, returns `false` otherwise `true`.
  */
  pinMode(HT_DATA, OUTPUT);
  digitalWrite(HT_DATA, LOW);
  delay(20);
  pinMode(HT_DATA, INPUT_PULLUP);
  delayMicroseconds(40);              // low for 80us
  delayMicroseconds(80);              // low ~80us, next goes high.
  if (digitalRead(HT_DATA) == LOW) {  // shouldn't be low anymore
    return false;
  }
  delayMicroseconds(80);
  if (digitalRead(HT_DATA) == HIGH) {  // shouldn't be high anymore
    return false;
  }
  return true;
  // if we got here, it starts sending the data.
}

void read_bits(uint8_t bits[40]) {
  /**
  Loop 40 times extracting the signal sent by DHT11
  And write it in the input array.
  */
  for (uint8_t i = 0; i < 40; i += 1) {
    // digital read takes a few us.
    while (digitalRead(HT_DATA) == LOW) {
      // ~50 us.
      // This fixes offsets that would otherwise accumulate.
    }
    delayMicroseconds(over_zero_ms);
    if (digitalRead(HT_DATA) == HIGH) {
      bits[i] = 1;
      delayMicroseconds(40);  // 1 uses +40 us in HIGH.
    } else {
      bits[i] = 0;
    }
  }
}

bool bits_to_values(uint8_t my_bits[40]) {
  /**
  Take the 40 bits, group them into 5 bytes, and calculate the values.
  The received order is based on the site:
  https://www.ocfreaks.com/basics-interfacing-dht11-dht22-humidity-temperature-sensor-mcu/
  The order is MSB.

  Returns whether the checksum matches our result.
  */
  uint8_t accs[5] = { 0, 0, 0, 0, 0 };  // H_int, H_dec, T_int, T_dec, Checksum.
  for (uint8_t acc = 0; acc < 5; acc += 1) {
    uint8_t offset = 8 * acc;  // no offset in first run.
    for (uint8_t i = 0; i < 8; i += 1) {
      uint8_t value = my_bits[i + offset];
      accs[acc] += value << (7 - i);  // `(7-i)` is because of MSB. LSB'd just use `i`.
    }
  }
  rh = val_as_float(accs[0], accs[1]);
  t = val_as_float(accs[2], accs[3]);
  uint8_t ours = (accs[0] + accs[1] + accs[2] + accs[3]) % 255;
  uint8_t theirs = accs[4];

  return ours == theirs;
}

void print_values(float rh, float t, bool match) {
  // simply print the values in a tidy way.
  Serial.print("CHECKSUM MATCH ? ");
  Serial.println(match);

  Serial.print("RH (%): ");
  Serial.println(rh);

  Serial.print("T (C): ");
  Serial.println(t);
}

float val_as_float(uint8_t integer, uint8_t decimal) {
  // take integer and decimal, and make a single float value.
  float float_decimal = float(decimal);
  float float_integer = float(integer);
  while (float_decimal >= 1) {
    float_decimal = float_decimal / 10;
  }
  return float_integer + float_decimal;
}

uint8_t find_closest_index(float t) {
  const float ardenbuck_temps[6] = { 0,
                                     10,
                                     20,
                                     30,
                                     40,
                                     50 };
  float last_d = abs(t - ardenbuck_temps[0]);
  uint8_t last_idx = 0;
  for (uint8_t i = 1; i < 6; i += 1) {
    float current = abs(t - ardenbuck_temps[i]);
    if (current < last_d) {
      last_idx = i;
    }
  }
  return last_idx;
}

float get_new_M(float rh, float sp) {
  /**
  Arguments:
    rh: % rel hum
    ps: saturation pressure
  
  Returns molecular weight of the new mix
  */
  const float partial_pressure = sp * rh / (100 * atmospheric_pressure);
  return (1 - partial_pressure) * M_dry_air + partial_pressure * M_water;
}

float get_abs_H(float rh_perc, float sp) {
  /**
  Arguments
    rh: % relative humidity
    sp:saturation pressure

  Return Absolute Humidity.
  */
  const float molar_f = sp * rh_perc / (atmospheric_pressure * 100);  //~0.01
  return molar_f * M_water / (M_dry_air * atmospheric_pressure);
}


float get_saturation_p(float t) {
  /* Saturation pressure as defined in
  https://en.wikipedia.org/wiki/Arden_Buck_equation
  It selects the "enhancement factor" using current temperature.
  
  Returns saturation pressure in bars.
  IMPORTANT: I'm including only to use within the 0-50 C range; it will error otherwise.
  */
  const uint8_t idx = find_closest_index(t);
  const float fs[6] = { 1.00395, 1.00388, 1.004, 1.00426, 1.00467, 1.00519 };  // 0-50
  const float f = fs[idx];

  const float first_term = 18.678 - (t / 234.5);
  const float second_term = t / (257.14 + t);
  // this is Ps(T)
  return f * 6.1121
         * exp(first_term * second_term) / 1000.0;  // in bars
}


float get_R(float M_mix) {
  // J/(grams*T)
  return R_u / M_mix;
}

float get_Cp(float abs_h) {
  // J/(grams*T)
  return 1.005 + 1.820 * abs_h;
}

float get_Cv(float cp, float M) {
  // J/(grams*T)
  return cp - get_R(M);
}

float get_dry_air_speed(float t) {
  float R_dry = get_R(M_dry_air) * 1000;  // kg
  return sqrt(1.4 * R_dry * (t + 273.15));
}

float get_new_speed(float abs_h, float t, float M_moist) {
  float cp = get_Cp(abs_h);
  float cv = get_Cv(cp, M_moist);
  Serial.print("Gamma (Cp/Cv): ");
  Serial.println(cp / cv);

  float R_moist = get_R(M_moist) * 1000;  // kg
  return sqrt((cp / cv) * R_moist * (t + 273.15));
}
void run() {
  if (!connect()) {
    Serial.print("Communication failed.");
    return;
  }
  read_bits(bits);
  bool match = bits_to_values(bits);  // populates h, t
  if (match) {
    print_values(rh, t, match);
    const float sp = get_saturation_p(t);
    const float abs_H = get_abs_H(rh, sp);
    const float M_moist = get_new_M(rh, sp);
    const float new_speed = get_new_speed(abs_H, t, M_moist);
    Serial.print("Saturation Pressure: ");
    Serial.println(sp);
    Serial.print("Absolute Humidity: ");
    Serial.println(get_abs_H(rh, sp));
    Serial.print("MOIST SPEED: ");
    Serial.println(new_speed);
    Serial.print("DRY SPEED: ");
    Serial.println(get_dry_air_speed(t));
  }
}

void setup() {
  Serial.begin(9600);
  delay(2000);  // initial stabilise
  run();
}

void loop() {
  delay(execution_delay_ms);
  run();
}

The output of the code above, which adjusts speed of sound for humidity and temperature, is:

CHECKSUM MATCH ? 1
RH (%): 52.00
T (C): 19.20
Gamma (Cp/Cv): 1.40
Saturation Pressure: 0.02
Absolute Humidity: 0.01
MOIST SPEED: 342.92
DRY SPEED: 342.77