Help w/ programming large equation
AGCB
Posts: 344
in Propeller 1
In my weather recording program (see "Read 8 switches w/ least pins" in General Discussion), to convert humidity to dew point there is a rather large equation listed in the data sheet for the HTU21D Humidity sensor .
I was hoping someone would help me with coding this in Propeller SPIN. I assume this is possible to do.
I can already get the binary output from the sensor but lack the ability to use the dew point formula with Propeller.
I could not figure out a way to attach the page directly to this post so please search " HTU21D " Page 16!
This is code I have so far
Thanks much for your time.
Aaron
I was hoping someone would help me with coding this in Propeller SPIN. I assume this is possible to do.
I can already get the binary output from the sensor but lack the ability to use the dew point formula with Propeller.
I could not figure out a way to attach the page directly to this post so please search " HTU21D " Page 16!
This is code I have so far
CON
{{
Play w/ and learn humidity sensor 2nd try w/ I2C spin driver 1.4
}}
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
'I2C pins
sclpin = 20
sdapin = 21
Bitrate = 100_000 'I2C @ 100k
Hum = $40 ' 7-bit I2C address '%100_0000
OBJ
tv : "tv_text"
i2c : "I2C SPIN driver v1.4od"
VAR
long address, addr0,temp0, humid, RH
long addrs0[2], Humi[2], temp[2]
byte timbuf[7]
PUB main
tv.start(0)
I2C.init(sclpin, sdapin)
tv.str(string("user register bits = "))
I2C.command(Hum,$E7)
tv.bin(I2C.readNext(Hum),8)
repeat
tv.cr
tv.cr
tv.str(string("msb + lsb = "))
tv.bin(I2C.readWordB(Hum,$E5),16)
tv.cr
tv.cr
humid:=(I2C.readWordB(Hum,$E5))
RH:=((humid*125/65536)-6) 'I would rather have Dew Point than relative humidity
tv.str(string("RH = "))
tv.dec(RH)
waitcnt(clkfreq+cnt)
tv.home
Thanks much for your time.
Aaron

Comments
CON A = 8.1332 ' Magic constants B = 1762.39 C = 235.66 OBJ F32 : "F32_1_6" ' From OBEX ' Note: You have to call F32.start somewhere in your initialization code PUB Td( Tamb, RHamb ) | PP_Tamb ' Return dew point temp as integer (C) Tamb := F32.FFloat(Tamb) ' Convert to floating point PP_Tamb := F32.Exp10(F32.FSub(A,F32.FMul(B,F32.FAdd(Tamb,C)))) RHamb := F32.FFloat(RHamb) ' Convert to floating point result := F32.FNeg(F32.FAdd(F32.FDiv(B,F32.FSub(F32.Log10(F32.FMul(RHamb,F32.FDiv(PP_Tamb,100.0))),A)),C)) return F32.FRound(result)I haven't looked at the datasheet myself yet but I will soon. I just wanted to post a link to the datasheet (pdf) to make it easier for others to find. Here's the part at DigiKey.
Here's the equation on page 16.
I generally avoid using floating point math but this might be one of those those times using a floating point library like F32 would be a good idea.
I'll likely post a floating point example a bit later.
(To insert a link use the little chain icon between the YouTube icon and the quote icon.)
PUB GetDewPoint(fRelativeHumidity, fPartialPressure) result := F32.FNeg(F32.FAdd(F32.FDiv(DEW_POINT_B, F32.FSub(F32.Log10(F32.FMul( { } fRelativeHumidity, F32.FDiv(fPartialPressure, 100.0))), DEW_POINT_A)), DEW_POINT_C)) PUB GetPartialPressure(temperature) | fTemperature fTemperature := F32.FFloat(temperature) result := F32.Exp10(F32.FSub(DEW_POINT_A, F32.FDiv(DEW_POINT_B, F32.FAdd(fTemperature, { } DEW_POINT_C))))As one can likely guess, the variables with a "f" prefix are encoded as floating point numbers.
Here's some code I wrote to help display these values.
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 'I2C pins SCL_PIN = 20 SDA_PIN = 21 BITRATE = 100_000 'I2C @ 100k HTU21D_ADDRESS = $40 ' 7-bit I2C address '%100_0000 DEW_POINT_A = 8.1332 DEW_POINT_B = 1762.39 DEW_POINT_C = 235.66 DEFAULT_TEMPERATURE = 22 BAUD = 115_200 OBJ I2c : "I2C PASM driver v1.8od" F32 : "F32" Pst : "Parallax Serial Terminal" Format : "StrFmt" PUB Main | rawHumidity, temperature, relativeHumidity, fRelativeHumidity, fPartialPressure, { } fDewPoint temperature := DEFAULT_TEMPERATURE Pst.Start(BAUD) repeat result := Pst.RxCount Pst.Str(string(11, 13, "Press any key to start.")) waitcnt(clkfreq / 4 + cnt) until result Pst.RxFlush Pst.Clear F32.Start I2C.Start(SCL_PIN, SDA_PIN, BITRATE) Pst.Str(string("user register bits = ")) I2c.Command(HTU21D_ADDRESS, $E7) ReadableBin(I2C.ReadNext(HTU21D_ADDRESS), 8, 4) waitcnt(clkfreq * 2 + cnt) repeat Pst.ClearEnd Pst.NewLine Pst.ClearBelow Pst.Home Pst.Str(string(11, 13, "HTU21D Dew Point Demo")) Pst.Str(string(11, 13, "msb + lsb (rawHumidity) = ")) rawHumidity := I2C.readWordB(HTU21D_ADDRESS, $E5) Pst.Dec(rawHumidity) Pst.Str(string(" = ")) ReadableBin(rawHumidity, 16, 4) relativeHumidity := RawToRhInteger(rawHumidity) fRelativeHumidity := RawToRhFloat(rawHumidity) Pst.Str(string(11, 13, "relativeHumidity Integer Math = ")) Pst.Dec(relativeHumidity) Pst.Str(string(11, 13, "relativeHumidity Floating Point Math = ")) DisplayFloat(fRelativeHumidity, 2) Pst.Str(string(11, 13, "temperature used for dew point calculation = ")) Pst.Dec(temperature) fPartialPressure := GetPartialPressure(temperature) Pst.Str(string(11, 13, "Partial Pressure = ")) DisplayFloat(fPartialPressure, 2) fDewPoint := GetDewPoint(fRelativeHumidity, fPartialPressure) Pst.Str(string(11, 13, "Dew Point = ")) DisplayFloat(fDewPoint, 2) waitcnt(clkfreq + cnt) PUB GetDewPoint(fRelativeHumidity, fPartialPressure) result := F32.FNeg(F32.FAdd(F32.FDiv(DEW_POINT_B, F32.FSub(F32.Log10(F32.FMul( { } fRelativeHumidity, F32.FDiv(fPartialPressure, 100.0))), DEW_POINT_A)), DEW_POINT_C)) PUB GetPartialPressure(temperature) | fTemperature fTemperature := F32.FFloat(temperature) result := F32.Exp10(F32.FSub(DEW_POINT_A, F32.FDiv(DEW_POINT_B, F32.FAdd(fTemperature, { } DEW_POINT_C)))) PUB RawToRhInteger(rawRhValue) result := (rawRhValue * 125 / 65536) - 6 PUB RawToRhFloat(rawRhValue) result := F32.FSub(F32.FDiv(F32.FMul(F32.FFloat(rawRhValue), 125.0), 65536.0), 6.0) PRI DisplayFloat(fValue, decimalPlaces) | fScale fScale := F32.Exp10(F32.FFloat(decimalPlaces)) result := RoundScaled(fValue, fScale) DecPoint(result, decimalPlaces) PRI RoundScaled(fValue, fScale) result := F32.FRound(F32.FMul(fValue, fScale)) PRI DecPoint(value, places) | tempBuffer[4] result := Format.FDec(@tempBuffer, value, 9, places) byte[result] := 0 Pst.Str(@tempBuffer) PUB ReadableBin(localValue, size, groupSize) | bufferPtr, localBuffer[12] '' This method display binary numbers '' in easier to read groups. bufferPtr := Format.Ch(@localBuffer, "%") result := size // groupSize if result size -= result bufferPtr := Format.Bin(bufferPtr, localValue >> size, result) if size bufferPtr := Format.Ch(bufferPtr, "_") size -= groupSize repeat while size => 0 'Debug.Bin(localValue >> size, 4) bufferPtr := Format.Bin(bufferPtr, localValue >> size, groupSize) if size bufferPtr := Format.Ch(bufferPtr, "_") size -= groupSize byte[bufferPtr] := 0 Pst.Str(@localBuffer)I find the method "ReadableBin" very useful. IMO, it's a lot easier to read.
Than to read:
The "DecPoint" method makes it easy to display scaled integers.
I used a different I2C object. I used the one I was using in a different project since I already had it open in the Propeller Tool.
I also used PST to display the data. Hopefully it won't take long to change it back to using a TV output.
I figured some type of float object would have to be used but what threw me the most was the log10. I didn't think that would be in the float object as I'm not too familiar with that. Bad me for failing to look! Also, I took shop math in HS, 45 years ago.
I'll spend some time w/ your code as soon as I can.
I don't know who the author is but the object "I2C devices found" has helped me debug and check hardware more than a few times. Thanks to the author whoever you are! In this case I had 2 devices on the board with the same address specified ($40) which instead should have been %100_0000 and %10_0000
Aaron
Marty
ftemperature:=F32.FSub(F32.FDiv(F32.FMul(F32.FFloat(I2C.ReadWordB(HTU21D_ADDRESS,$E3)){ },175.72),65536.0),46.85) fTemperature:= F32.FAdd(F32.FMul(F32.FDiv(9.0,5.0),fTemperature),32.0) ' degrees FAaron
fTemperature:= F32.FAdd(F32.FMul(F32.FDiv(9.0,5.0),fTemperature),32.0) ' degrees F[/code]
You seem to have a typo
A. House painting - cross the dew point and the house you are painting may turn into a nightmare.
B. Packaging of iron products for shipping -- Passing the dew point after packaging and everything might arrive rusty.
C. Shipment of orchids over long-distance -- Crossing the dew point after placed in a cargo container many ruin the whole shipment in transist. Orchids love high humidity and warmth, but get brown spots from water droplets on their surface.
D. Food packaging -- Too much humidity while packaging, and the food quality is stale when opened.
E. Spray paint your car in a controlled environment and moving it outside may cross the dew point and ruin the paint work.
+++++++++
I've actually found that 26 to 27 degrees centigrade is optimal air conditioning for me as the humidity content of the air remains low enough not to cause slim molds to grow on my walls, while high enough to save a bit of money. I grew up with the idea that 20 degrees centigrade was optimal.
What sort of units does the temperature need to be in in order to use it in the partial pressure calculation?
All calculations in the datasheet are done with Celcius, so it it is likely that the A, B & C constants are adjusted for Celcius. I would to the conversion for dew point to Fahrenheit afterwards and use ftemperature (small t) for the calculations and fTemperature (capital T) for display only
Isn't SPIN case insensitive?
Yes, it is case insensitive. Even if it weren't, it would still be a bad idea to have two variables whose names differ only in case because it would confuse people.
I keep forgotting that
I had missed Mike Green's earlier post. (I'm still trying to figure out how I didn't see it sooner.)
I hope you compare my equation against what Mike wrote.
I've attached both the current code and a screenshot of the PST
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 'I2C pins SCL_PIN = 20 SDA_PIN = 21 BITRATE = 100_000 'I2C @ 100k HTU21D_ADDRESS = $40 ' 7-bit I2C address '%100_0000 DEW_POINT_A = 8.1332 DEW_POINT_B = 1762.39 DEW_POINT_C = 235.66 'DEFAULT_TEMPERATURE = 22 BAUD = 115_200 OBJ I2c : "I2C PASM driver v1.8od" F32 : "Float32" Pst : "Parallax Serial Terminal" Format : "StrFmt" PUB Main | rawHumidity, ftemperature, relativeHumidity, fRelativeHumidity, fPartialPressure, { } fDewPoint, DegFar, temperature, rawTemperature 'temperature := DEFAULT_TEMPERATURE Pst.Start(BAUD) repeat result := Pst.RxCount Pst.Str(string(11, 13, "Press any key to start.")) waitcnt(clkfreq / 4 + cnt) until result Pst.RxFlush Pst.Clear F32.Start I2C.Start(SCL_PIN, SDA_PIN, BITRATE) Pst.Str(string("user register bits = ")) I2c.Command(HTU21D_ADDRESS, $E7) ReadableBin(I2C.ReadNext(HTU21D_ADDRESS), 8, 4) waitcnt(clkfreq / 2 + cnt) repeat Pst.ClearEnd Pst.NewLine Pst.ClearBelow Pst.Home Pst.Str(string(11, 13, "HTU21D Dew Point Demo")) Pst.Str(string(11, 13, "msb + lsb (rawHumidity) = ")) rawHumidity := I2C.readWordB(HTU21D_ADDRESS, $E5) Pst.Dec(rawHumidity) Pst.Str(string(" = ")) ReadableBin(rawHumidity, 16, 4) pst.str(string(13,13)) Pst.Str(string(11, 13, "msb + lsb (rawTemperature) = ")) rawTemperature := I2C.readWordB(HTU21D_ADDRESS,$E3) Pst.dec(rawTemperature) Pst.Str(string(" = ")) ReadableBin(rawTemperature, 16, 4) temperature:=F32.FSub(F32.FDiv(F32.FMul(F32.FFloat(rawTemperature),{ }175.72),65536.0),46.85) pst.str(string(13,13,"Centigrade temperature = ")) DisplayFloat(temperature,2) relativeHumidity := RawToRhInteger(rawHumidity) fRelativeHumidity := RawToRhFloat(rawHumidity) Pst.Str(string(11, 13,13, "relativeHumidity Integer Math = ")) Pst.Dec(relativeHumidity) Pst.Str(string(11, 13,13, "relativeHumidity Floating Point Math = ")) DisplayFloat(fRelativeHumidity, 2) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Get temperature ***** Pst.Str(string(11, 13,13, "temperature °F calculated = ")) DegFar:= F32.FAdd(F32.FMul(F32.FDiv(9.0,5.0),temperature),32.0) fTemperature := F32.FFloat(temperature) displayFloat(DegFar,2) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' fPartialPressure := GetPartialPressure(Temperature) Pst.Str(string(11, 13,13, "Partial Pressure = ")) DisplayFloat(fPartialPressure, 2) fDewPoint := GetDewPoint(fRelativeHumidity, fPartialPressure) Pst.Str(string(11, 13, "Dew Point = ")) DisplayFloat(fDewPoint, 2) waitcnt(clkfreq + cnt) PUB GetDewPoint(fRelativeHumidity, fPartialPressure) result := F32.FNeg(F32.FAdd(F32.FDiv(DEW_POINT_B, F32.FSub(F32.Log10(F32.FMul( { } fRelativeHumidity, F32.FDiv(fPartialPressure, 100.0))), DEW_POINT_A)), DEW_POINT_C)) PUB GetPartialPressure(temperature) | fTemperature fTemperature := F32.FFloat(temperature) result := F32.Exp10(F32.FSub(DEW_POINT_A, F32.FDiv(DEW_POINT_B, F32.FAdd(fTemperature, { } DEW_POINT_C)))) PUB RawToRhInteger(rawRhValue) result := (rawRhValue * 125 / 65536) - 6 PUB RawToRhFloat(rawRhValue) result := F32.FSub(F32.FDiv(F32.FMul(F32.FFloat(rawRhValue), 125.0), 65536.0), 6.0) '''' ''' '' ' ' '' ''' '''' PRI DisplayFloat(fValue, decimalPlaces) | fScale fScale := F32.Exp10(F32.FFloat(decimalPlaces)) result := RoundScaled(fValue, fScale) DecPoint(result, decimalPlaces) PRI RoundScaled(fValue, fScale) result := F32.FRound(F32.FMul(fValue, fScale)) PRI DecPoint(value, places) | tempBuffer[4] result := Format.FDec(@tempBuffer, value, 9, places) byte[result] := 0 Pst.Str(@tempBuffer) PUB ReadableBin(localValue, size, groupSize) | bufferPtr, localBuffer[12] '' This method display binary numbers '' in easier to read groups. bufferPtr := Format.Ch(@localBuffer, "%") result := size // groupSize if result size -= result bufferPtr := Format.Bin(bufferPtr, localValue >> size, result) if size bufferPtr := Format.Ch(bufferPtr, "_") size -= groupSize repeat while size => 0 'Debug.Bin(localValue >> size, 4) bufferPtr := Format.Bin(bufferPtr, localValue >> size, groupSize) if size bufferPtr := Format.Ch(bufferPtr, "_") size -= groupSize byte[bufferPtr] := 0 Pst.Str(@localBuffer)THIS is the PST screen
The partial pressure never changes and I don't know what it should be.
The dew point is what's way off. Everything else is close. I have the unit sitting in a window sill on a 40 degree F rainy day
temperature:=F32.FSub(F32.FDiv(F32.FMul(F32.FFloat(rawTemperature),{ }175.72),65536.0),46.85)The method "GetPartialPressure" expects the temperature to be an integer.
PUB GetPartialPressure(temperature) | fTemperature fTemperature := F32.FFloat(temperature) result := F32.Exp10(F32.FSub(DEW_POINT_A, F32.FDiv(DEW_POINT_B, F32.FAdd(fTemperature, { } DEW_POINT_C))))When the method uses the "FFloat" method on a value which is already encoded as a float the resulting value is not a useful value.
The first thing to try is to change the "GetPartialPressure" method to:
PUB GetPartialPressure(fTemperature) result := F32.Exp10(F32.FSub(DEW_POINT_A, F32.FDiv(DEW_POINT_B, F32.FAdd(fTemperature, { } DEW_POINT_C))))Seems to me I had commented out that line several times but with the unit in the house it was probably enough to give some strange unexpected readings. Now with it sitting on the window sill, the temperature is about 3 degrees above my other outdoor thermometer. I would expect the dew point to be 4 - 7 degrees F below the temperature because there is no fog but it's not too far from it. It's reading at about -2 degrees C.
The local airport (3 miles away) shows 42 degrees F(5.6C), 4.4 degrees C dew point, and 96% RH but no fog.
So it's in the ball park but probably just not accurate enough.
And here's the airport METAR
I kind of wonder if you're getting rounding errors with all those floating point calculations. I don't see any obvious way around the problem.
You ought to try printing out the raw data needed to make the calculation and punch the numbers in on a good calculator. This should let you know if the problem is in rounding errors or not.