AM2302 (DHT22 ) Temp Humidity sensor and JAL on a 16F628 at 4MHz clock

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.

AM2302, humidity and temperature sensor, a.k.a. DHT22 apparently
AM2302, humidity and temperature sensor, a.k.a. DHT22 apparently

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.

Changing

delay_10us(4)for

delay_10us(2)
delay_5us()

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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *