Low Power Am2303 / DHT22 Driver for Propellor
tomcrawford
Posts: 1,129
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".
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.
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.
Comments
Larry
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
Thanks for the code, it works great!
mike