Shop OBEX P1 Docs P2 Docs Learn Events
Low Power Am2303 / DHT22 Driver for Propellor — Parallax Forums

Low Power Am2303 / DHT22 Driver for Propellor

tomcrawfordtomcrawford Posts: 1,129
edited 2014-08-23 02:52 in Accessories
Attached is a very low power driver the Aosong Am2303 Temperature Humidity module. It seems to be also called DHT22/23 at Adafruit. The driver takes a PASM cog, but will run reliably at 5MHz (standard crystal with pll1x.

Adafruit has a link to a datasheet; here is a link to a product manual that is actually intelligible.

http://www.aosong.com/asp_bin/Products/en/AM2303.pdf

The Am2303 has a one-wire interface. You have to provide a pullup resistor. You start it up by pulling the pin low for 1 msec or so and then high and then making it an input. The Am2303 then does a little handshake followed by 40 bits of pulse-width modulated data. The attached Saleae screen grab shows the handshake and the first 20 or so bits of data. The bottom two traces are instrumentation indicating when the cog is waiting for a bit to end and also indicating each time it found a "one".
{Demo PASM Driver for AOSONG Am2303}


CON
        _clkmode = xtal1 + pll1x        'clock mode 
        _xinfreq = 5_000_000            '* crystal frequency
                                        'ClkFreq is 5 MHz to reduce power
                                        
        am2303pin = 12                    'am2303 pin

'Define the LED
         Led = 16
                                                      
VAR
  byte  Debug, cog           
  long  MyPar[6]

'these variables are associated with the am2303 and its cog  
  word RH, DegreesC, DegreesF   'relative humidity, Temperatures
  byte Checksum, sumcheck       'returned checksum, calculated checksum
  byte Am2303Data[5]            'the five bytes we read
  byte Am2303Flag               'not equal to zero says read 2303, = 0 says data available


OBJ
  pst      : "parallax serial terminal"

  
PUB main

    pst.Start (9600)                     'start up ser terminal at 9600
    pst.str(String("hello, world   "))   'runs in cog 1
    pst.NewLine
    waitcnt(clkfreq/10+cnt)

    MyPar := @Am2303Data              'where to return the five bytes of data
    MyPar[1] := Am2303Pin             'which pin to look at                
    MyPar[2] := @Am2303Flag           'flag that says start up the am2303
    MyPar[3] := (clkfreq/1_000_000) * 50   'clock tics in 50 useconds               '                              
    cog := cognew(@Am2303Cog, @MyPar)  'start up the 2303 reader cog
    pst.str(String("Am2303 Cog: "))             '
    pst.hex(Cog,1)
    pst.NewLine
       
  repeat                                    'forever
    waitcnt((clkfreq*10) + cnt)              'wait ten seconds   
    Am2303Flag := $55                        'tell the temp cog to begin
    waitcnt(clkfreq/200 + cnt)               'power down about 5 msec 
    repeat while Am2303Flag <> 0             'and wait here until data are returned
    RH.byte[0] := Am2303Data[1]              'sort out returned data and calculate checksum
    sumcheck := RH.byte[0]
    RH.byte[1] := Am2303Data[0]
    sumcheck := sumcheck + RH.byte[1]
    DegreesC.byte[0] := Am2303Data[3]
    sumcheck := sumcheck + DegreesC.byte[0]
    DegreesC.byte[1] := Am2303Data[2]
    sumcheck := sumcheck + DegreesC.byte[1]
    Checksum := Am2303Data[4]                'checksum check
    if checksum <> sumcheck
        Pst.str(string("Checksum Error:   Calculated:  "))
        pst.hex(Sumcheck,2)
        Pst.str(string("  Received:  "))
        pst.hex(Checksum,2)
        pst.newline
    pst.str(string("Raw Temp Hex: "))
    pst.hex(DegreesC,4)
    pst.str(string("  DegreesC: "))
    if (DegreesC & $8000) == 0        '0 degreesC or higher
       pst.dec(DegreesC/10)            'integer part
       pst.char(".")
       pst.dec(DegreesC//10)           'tenths
       DegreesF := ((DegreesC * 9) / 5) + 320    'degrees F times 10
       DegreesF := (DegreesF + 5) / 10    'rounded
       pst.str(String("  DegreesF: "))
       pst.dec(DegreesF)
       
    else                              'below 0 degreesC
       DegreesC := DegreesC & $7FFF   'drop the sign bit, leaving magnitude
       pst.char("-")                  'minus sign
       pst.dec(DegreesC/10)            'integer part
       pst.char(".")
       pst.dec(DegreesC//10)           'tenths      
       DegreesF := 320 - ((DegreesC * 9) / 5)
       DegreesF := (DegreesF + 5) / 10    'rounded
       pst.str(String("  DegreesF: "))
       pst.dec(DegreesF)              
       
    pst.str(string("     Raw Humidity Hex: "))
    pst.hex(RH,4)
    RH := (RH + 5) / 10      'round (up or down) and divide by 10
    Pst.str(string("   Relative Humidity: "))
    pst.dec(RH)
    pst.newline        

 



dat                     'this is the cog that talks to the Am2303
Am2303Cog  org   0
        mov AdPar,Par          'get the address of input parameters
        rdlong AdData, AdPar    'where to store the data
        add AdPar, #4          'to pin number
        rdlong AmPin, AdPar    'get it
        add AdPar, #4          'to flag address
        rdlong AdFlag, AdPar   'get it
        add AdPar, #4          'to 50 usec count
        rdlong Usec50, AdPar    'and get it
        mov AmMask, #1         'form the mask for dira, outa
        shl AmMask, AmPin      'by shifting appropriately
        mov S1Mask, #1         'make the two scope sync masks
        shl S1Mask, #14
        or dira, S1Mask         'and enable the pins
        mov S2Mask, #1
        shl S2Mask, #15
        or dira, S2Mask
        
top     rdbyte MyFlag, AdFlag  'look at the START flag
        test MyFlag, #$FF wz   'see if any bits set
        if_nz jmp #start        'if so, go start the 2303
        mov count2, TwoK       'otherwise set up to wait 100 millisec
top0    mov count1, Usec50
        add count1, cnt
        waitcnt count1, #0     'waiting in low power mode
        djnz count2, #top0      
        jmp #top                'continue to wait    
start   mov count2, #20        '20 times 50 usec is 1 msec
        or outa, AmMask         'dont glitch pin low
        or dira, AmMask        'drive pin high
        andn outa, AmMask      'now low. This starts the am2303 in 1 msec 
loop1   mov count1, Usec50     '50 usec worth of clocks
        add count1, cnt        'plus current time
        waitcnt count1, #0      'wait for it
        djnz count2, #loop1     'use up 1 msec
        
        or outa, AmMask         'drive the pin high
        andn dira, AmMask       'and get off the bus
        waitpeq Zero, AmMask     'wait for Am2303 to take it low      
        waitpeq AmMask, AmMask  'and then back hi
        mov CurAd, AdData        'where to store the first byte
        mov Bytes, #5            'we will store five bytes
        waitpeq Zero, AmMask     'and back low prior to first data bit
                           '     'we are now in the low time prior to first data bit
BytLp   mov Bits, #8             'eight bits per byte
        mov DByte, #0            'nice empty accumulator
BitLp   shl DByte, #1            'shift what we have so far
        or outa, S1Mask         'high while waiting for a bit to complete
        waitpeq AmMask, AmMask   'wait for the beginning of the pulse
        mov BCnt, cnt            'count at beginning of pulse
        waitpeq Zero, AmMask     'wait for pulse to go away
        mov ECnt, cnt            'count at end of pulse
        andn outa, S1Mask       'bit is complete
        sub ECnt, BCnt           'compute length of positive pulse
        cmp ECnt, Usec50 wc
        if_c jmp #BitLp1        'jump if length less than 50 use
        or DByte, #1             'set a one
        or outa, S2Mask           'scope pulse
        nop
        andn outa, S2Mask
BitLp1  djnz Bits, #BitLp         'eight bits
        wrbyte DByte, CurAd       'save this byte
        add CurAd, #1             'to next byte
        djnz Bytes, #BytLp        'five bytes
        wrbyte Zero, AdFlag     'we are done, zero the flag
        jmp #top                  'and go monitor the flag bit again forever
Zero    long 0                  'zero value for waitpeq
TwoK    long 2_000              'two thousand (times 50 usec yields 100 msec)
AdPar   RES                     'the paramemters address
AdData  RES                     'the address of the first byte of data
CurAd   RES                     'the address to store next byte of data
AdFlag  RES                     'Address of the flag byte
AmPin   RES                     'the actual pin number
AmMask  RES                     'single bit corresponding to the Am2303 pin 
Usec50  RES                     'clock tics in 50 usec
count1  RES                     'used for waitcnt's
count2  RES                     'times out one msec   
MyFlag  RES                     'my copy of the flag
S1Mask  RES                      'scope syncs
S2Mask  RES   
BCnt    RES                      'count at beginning of high pulse
ECnt    RES                      'count at end of high pulse
Bits    RES                      'counts bits in each byte
Bytes   RES                      'counts bytes of data
DByte   RES                      'where we accumulate each byte of data                  
         

The program has a spin segment that starts up the PASM. From time to time it sets a flag to allow the PASM cog to run and then displays the results. One interesting thing to note is that it returns temperatures below 0 degrees C as signed magnitude rather than one or twos complement as does the DS1620.

The PASM cog initializes itself by getting the pin number and forming a mask for dira, outa, and ina. It gets the hub addresses of its variables. It also receives a constant corresponding to the number of clock tics in 50 Usec. At label top, it checks once every 100 msec for its start flag to be set. Once it sees the flag set, it starts up the Am2303 and goes through the handshaking. It then accumulates and stores five bytes of eight bits each. It determines whether a bit is a one or a zero by comparing its high time to 50 microseconds. The S1Mask and S2Mask are purely instrumentation.

Note that the clock is programmed for pll1x. The program as shown runs at 2.1 mA. With the pst cog (and attendant calls) disabled, it runs at 1.5 mA. This seems suitable for battery operation.
1024 x 473 - 67K

Comments

  • lfreezelfreeze Posts: 174
    edited 2014-07-16 11:31
    Thank you for posting the code. I just purchased a couple of those sensors. and your code worked perfectly.
    Larry
  • tomcrawfordtomcrawford Posts: 1,129
    edited 2014-07-21 09:15
    I received a PM asking " Hello Tom:
    Thanks for the code "Low Power Am2303 / DHT22 Driver for Propellor"
    Could you do a modification for me.
    I need the pin it is looking at to be modifiable within the running program, so I can monitor 2 RHT03's
    Please help I really need it. "

    Probably the easiest way would be to pass a second Am2303 pin number and a second Am2303Data address in the parameter list (dont forget to make the list bigger). Then duplicate the code from
    Start through the last djnz #BytLp. You will also have to duplicate getting the values from the parameter list and forming the mask for the second Am2303 pin.

    Better yet, If you have an extra cog available, start a second instantiation of the PASM cog, changing the pin number and Data address appropriately.

    PS I tried to PM you but it said you weren't accepting emails.

    Those two suggestions above are not very good.

    Here is a much better idea: Instead of passing just a start command in Am2303Flag, pass the pin number. Am2303Flag := Am2303Pin | $80.
    The hex 80 is in case the Am2303 pin is I/O zero. Then when you have processed the first sensor you can Am2303Flag := OtherAm23Pin | $80.


    In the PASM code, at Start, change it to

    Code:
    start and MyFlag, #$1F 'keep just pin number
    mov AmMask, #1 'form the mask each time you run
    shl AmMask, MyFlag
    mov count2, #20 'continue as before Everything else should be just the same
  • mike.blankenshipmike.blankenship Posts: 4
    edited 2014-08-23 02:52
    Hi Tom,

    Thanks for the code, it works great!

    mike
Sign In or Register to comment.