Having decided I can’t be bothered with digital sensors with oddball serial interfaces like the DHT22 it was time to suck it up when I needed a number of sensors. Cost adds up with lots of sensors – though that Honeywell product more than paid for itself a few times over in much better hatch rates (fertile eggs are about £2 a pop by post, that’s how much of a loss you eat for every failure to hatch!) not every sensor application affects the bottom line like that. Sometimes low cost trumps accuracy, reliability and serviceability. Enter the AM2302, apparently a.k.a. DHT22, produced by the fine Aosong corporation. Their website looks like line noise on my browser, but apparently they are based in Guangzhou, which is China’s third largest city, a conurbation of nearly thirteen million souls.
The sensors are cheap, nasty and have poor accuracy, but the price is right, it’s the cheapest way to get a humidity and temperature sensor. Five for £17.70 or a unit price of £3.54 from a Chinese supplier on ebay, Buyincoins ISTR. They have a non-standard one-wire interface. That requires you to be able to tell a 30μS high duration from a 68μS high duration. No problem, eh, even with a PIC running on the internal 4MHz oscillator so each clock cycle is 1μS?
There was already a JAL library for this, called temperature_humidity_dht11.jal so I am in development heaven.
Except it doesn’t work – it acts up after about 20s in the video. It sort of works some times, tantalising short runs of OK in amongst loads of timeout errors. I fiddle with the power supply a little as the AM2302 is claimed to be finicky on the need for 5V. No luck. Tracing the library code I find it barfs around
-- as soon as we detect it's going high, we need to measure duration: -- * 26-28us means 0 -- * 70us means 1 -- If we wait 40us and check pin level, we should be able to know -- if it was a '1' (pin is still high) or a '0' (pin is low) -- (ok, we're assuming there won't be any timeouts here...) delay_10us(4) if pin_dht11 == high then -- '1' _dht11_buffer[buf_idx] = _dht11_buffer[buf_idx] | (1 << bit_idx) else -- '0' end if
Which looks fair enough, until you take a look at what that actually means in assembler, in particular the routine just before it that loops on the low until the pin goes high
; 104 while pin_dht11 == low loop l__l471 btfsc v_portb, 7 ; pin_b7 goto l__l472 ; 105 counter = counter + 1 incf v___counter_1,f btfsc v_status, v__z incf v___counter_1+1,f ; 106 if counter == DHT11_TIMEOUT_TRY then movlw 232 subwf v___counter_1,w movwf v__pic_temp movlw 3 subwf v___counter_1+1,w iorwf v__pic_temp,w btfss v_status, v__z goto l__l471 ; 107 return DHT11_TIMEOUT retlw 2 ; 109 end loop l__l472 ; 117 delay_10us(4)
There’s a lot of guff in there, you could easily pick up a random delay of up to 15μS if you just miss the test in the loop and have to do all that testing for timeout. Putting extra diagnostics in there shows it tends to catch only about 37 or 38 or the 40 bits, waiting and timing out for the missed bits at the end which never come. What seems to be happening is the uncertainty of when the rising edge is detected is eating into the detection of the corresponding falling edge, as a result it skips a bit or two.
turned this from a seriously ratty and unusable sensor to something much more reliable. The original example for the jal DHT11 library ran at a much higher clock speed than 4MHz, so the problem here is due to me being a cheapskate and using a low clock frequency. However, if you are going to use one of these sensors then you’re a cheapskate by definition…
A look at the waveform on the scope seemed to indicate the periods were about right, however I only have an analogue scope and catching something that only happens every five seconds using the delay timebase is no fun. I had suspected that buyincoins might have supplied some rejects perhaps with an out of spec timing, but that’s probably unfounded 😉
There’s a case to try and tighten this loop up. Using assembler for that loop is one way to reduce the variation. An easier way is to use a byte not a word in the timeout loop counter, since it only needs to dwell on Tlow from the datasheet, a maximum of 55μS, so a byte counter capable to counting up to 255 is more than enough. That would drop out the variation in needing to handle the overflow register, which would shorten this a lot and simplify the code. So I created a 4MHz version of this code, and the corresponding bit of assembler is
; 103 while pin_dht11 == low loop l__l471 btfsc v_portb, 7 ; pin_b7 goto l__l472 ; 104 counter = counter + 1 incf v___counter_1,f ; 105 if counter == DHT11_TIMEOUT_TRY then movlw 200 subwf v___counter_1,w btfss v_status, v__z goto l__l471 ; 106 return DHT11_TIMEOUT retlw 2 ; 108 end loop l__l472 ; 116 delay_10us(3)
which has a delay of 8μS if you’re unlucky enough to just miss the test
btfsc v_portb, 7
and a delay of 3μS if you catch it just right, reducing the sampling jitter to 5μS as opposed to about 12. Inserting the delay of 30μS plus the worst case 3μS gives 33μS, and another 2μS happen after the delay to make a shortest delay of 35μS and longest delay (if you’ve just missed the test in the loop) of 47μS. That’s good enough to detect something other than the worst-case max 30μS high (Th0) of a 0 bit from anything longer which is a 1.
My replacement JAL library temperature_humidity_am2302_4M
this is a text file, rename to jal and stick it in your JAL library and include it in your code
Use like this
const bit USE_DHT22 = true ; const byte DHT11_TIMEOUT_TRY = 202 include temperature_humidity_am2302_4M ;include temperature_humidity_dht11
If you have declared DHT11_TIMEOUT_TRY as a word you need to stop doing that or declare it as a byte
This gave me a win on this device with the 16F628. I’m still not tremendously impressed with the accuracy, swapping the devices makes it a little bit hard for me to believe the accuracy claim of ±1°C, though they aren’t terrible, ±2°C would be better. I still wonder if the low cost means these are factory rejects a little out of spec. But at least it works form a data transfer point of view.