Signals you time, not read

The DHT sensor and the infrared remote share one thing, and it is the thing that matters: the information is in how long a pulse lasts.

There is no clock on either of them, no address and no bus.

Most of a kit talks over something with a name: I2C, SPI, a UART. These two do not. They flash a line for a while and the length of the flash is the bit, which means the driver is a stopwatch and the sketch's own timing is part of the circuit. They are the parts that teach what micros() is for.

The DHT11 and the DHT22

One wire, three pins, forty bits. The blue brick is the DHT11 and the white one is the DHT22 (also sold as the AM2302); the wire is identical and only the five bytes differ.

The conversation

  start      the host holds DATA low: at least 18 ms for a DHT11,
             at least 1 ms for a DHT22
  release    the host lets go and the pull-up takes the line high
  response   after 20-40 us the sensor pulls low for 80 us, then high for 80 us
  40 bits    each one is 50 us low, then high for 26-28 us (a nought)
             or 70 us (a one), most significant bit first
  end        a last 50 us low, and the line is let go

A driver splits a nought from a one at about 50 us, which has room either side. On both simulated boards here a pin read sees the level as of the start of a 10 us slice, so a nought measures 20-30 us and a one 70-80. That is three slices of margin, and micros() itself is exact because it is the cycle counter.

The five bytes

  humidity high, humidity low, temperature high, temperature low, checksum

and the checksum is the low eight bits of the sum of the other four. What the four mean is the only place the two sensors differ:

  • DHT22. Humidity is a 16-bit number in tenths of a percent and temperature is a 16-bit number in tenths of a degree, and a negative temperature sets bit 15 and puts the magnitude in the rest. It is sign and magnitude, not two's complement, and getting it wrong is the commonest bug in a hand-written driver. It only shows up in winter.
  • DHT11. Humidity high is whole percent and humidity low is zero; temperature high is whole degrees and temperature low is zero. A sketch that divides a DHT11 reading by ten is wrong on it.

Three things the model does on purpose

The first reading fails. Both datasheets say to leave the sensor alone for a second after power comes up, and the part does exactly that: a start pulse inside that second is not answered at all and the driver times out. That is the real first-reading-fails, and a sketch that hides it behind a delay(2000) in setup() is hiding something true.

It repeats itself. A DHT22 measures every two seconds (a DHT11 every one) and hands back the last reading in between. Drag the slider and the number does not move until the sensor has looked again, which is why a sketch that polls in a tight loop sees a value that seems stuck.

The range is the datasheet's. A DHT11 reads 20 to 90 %RH and 0 to 50 C; a DHT22 reads 0 to 100 %RH and -40 to 80 C. Ask for more and the part clamps, because a sensor cannot report what it cannot see.

DHT.h

The Adafruit tutorial compiles as it stands:

#include "DHT.h"

DHT dht(2, DHT22);

void setup() {
  Serial.begin(9600);
  dht.begin();
}

void loop() {
  delay(2000);
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }
  Serial.print(t);
  Serial.print(" C  ");
  Serial.print(h);
  Serial.println(" %");
}

DHT.h is Mokxi's own, on mokxi_dht.h. Each float it hands back is built from the driver's whole tenths bit by bit: the float is exactly the one a division would give, it prints, and arithmetic of your own on it, such as t * 1.8 + 32, works. readTemperature(true) gives Fahrenheit, computeHeatIndex works, and isnan is there. A reading is taken at most every two seconds, as upstream does, because the sensor cannot answer faster. On an ATtiny85 a sketch that prints a reading fits, and the whole heat-index tutorial does not.

The pull-up

The three-pin modules carry one on DATA, usually 5.1 k or 10 k. The bare four-pin sensor does not, and wants one adding. pullup is that resistor, on by default; turn it off and the line floats between pulses and every reading fails, which is what the bare part does on a real breadboard without a resistor.

What is not modeled

The accuracy: +/-2 %RH and +/-0.5 C on a DHT22, +/-5 and +/-2 on a DHT11. The number the slider says is the number that goes on the wire. Nor the seconds the capacitive element takes to follow a change in the air, nor self-heating.

The remote and the receiver

A 21-button handset and a VS1838B, the pair taped together in every kit.

The beam is a wire

In Mokxi two parts only ever meet on a net, so the light between the handset and the receiver is drawn as a wire from one IR pin to the other. That is the one thing in this circuit that is not what a bench looks like, and it is said here rather than left to be discovered.

What it costs: there is no distance, no angle, no sunlight, no second remote in the room, and therefore no reason for the tuning that is most of what is inside a VS1838B. What it keeps is everything a sketch can see, which is the timing.

The 38 kHz carrier is implied for the same reason. A real remote does not switch its LED on for nine milliseconds, it flashes it at 38 kHz for nine milliseconds, and the receiver is a tuned amplifier that only notices light flickering at about that rate, which is how it ignores daylight and a fluorescent tube. Generating that here would be 340 edges a millisecond and nothing in the model would do anything different with it, so the IR pin carries the envelope: high while the LED is flashing, low while it is dark.

The receiver inverts

This is the whole of what a VS1838B does, and it is the first surprise it hands anybody: OUT idles high and goes low while a burst is arriving. So the 9 ms leader burst shows up as 9 ms of LOW, and every NEC decoder ever written is written from that side. The dome on the part lights while the line is down, so the two can be watched together.

Its output stage is a transistor with an internal 30 k pull-up, which is why it can be read by a bare pin and why it is a poor idea to hang anything heavy off it.

NEC, whole

One unit, T, is 562.5 us: 21 cycles of the carrier, which is why it is not a round number.

  leader     16 T on (9 ms),  8 T off (4.5 ms)
  a nought    1 T on,         1 T off
  a one       1 T on,         3 T off
  stop        1 T on
  repeat     16 T on,         4 T off (2.25 ms), 1 T on, every 110 ms

Thirty-two bits go between the leader and the stop bit, least significant bit first, as four bytes: address, address inverted, command, command inverted. The two inversions are the whole of the error checking, and a decoder that checks them throws away almost every frame a passing car's headlights corrupted.

Holding a button sends one whole frame and then a short repeat every 110 ms. A sketch that treats a repeat as a fresh press is why volume buttons run away.

0xFFA25D, and 0x45

Every table on the web calls the CH- button 0xFFA25D; this part calls it address 0x00, command 0x45. They are the same thirty-two bits. The bits arrive least significant first and the old Arduino decoder assembled them most significant first, so each byte comes out bit-reversed: 0x45 reversed is 0xA2, 0xBA reversed is 0x5D, and the leading 0x00FF is the address and its inverse. Neither reading is wrong. firmware/lib/mokxi_ir.h hands back both, and the ir-lamps example prints both.

The handset's twenty-one commands are the set the VS1838B is sold with:

CH- 0x45 CH 0x46 CH+ 0x47
|<< 0x44 >>| 0x40 >|| 0x43
- 0x07 + 0x15 EQ 0x09
0 0x16 100+ 0x19 200+ 0x0D
1 0x0C 2 0x18 3 0x5E
4 0x08 5 0x1C 6 0x5A
7 0x42 8 0x52 9 0x4A

What is not modeled

The carrier and the air, as above. The automatic gain control, so this receiver never blanks. A real one turns its gain down under a long continuous burst and stops seeing anything, which is why NEC's leader is 9 ms and not 90. The tuning, so a burst at any rate at all is a burst here and two remotes on different carriers would interfere. The few hundred microseconds of edge error a real receiver has, which is what makes a decoder's tolerances necessary; here the edges are exact. And no protocol but NEC: no RC-5, no Sony SIRC, no extended NEC with a 16-bit address.

Circuits to open

  • Weather station: a DHT22 on pin 2 with the reading on an I2C display, and a first reading that fails on purpose.
  • Remote-controlled lamps: a handset, a receiver and three lamps, with every frame printed both ways round.

Watching the wire

Both of these are worth putting a logic analyzer on. The bits are widths, and an instrument that records edges with the time they happened is the instrument for widths. Clip D0 to the DHT's data line or the receiver's OUT, run it, and zoom the window down until one bit fills a division.