Prop driver for Sensirion SHT1x SHT7x, integer math.

New obex object:
http://obex.parallax.com/object/517
I usually avoid using floating point if at all possible, and Cam's object is meant partly as an illustration of how to use his floating point library. My version uses Cam's driver code but the math is all-integer. The calls to ReadTemperature and ReadHumidity simply return the values in units of 1/100 degC and 1/10 %RH respectively.
http://obex.parallax.com/object/517
I usually avoid using floating point if at all possible, and Cam's object is meant partly as an illustration of how to use his floating point library. My version uses Cam's driver code but the math is all-integer. The calls to ReadTemperature and ReadHumidity simply return the values in units of 1/100 degC and 1/10 %RH respectively.
Comments
Its annoying, but also kind of fun to work all the floats out of an equation.
I'm a big fan of ** as fractional multiply, ever since working on the SHT code for the BASIC Stamp. BR's integer math is different from mine, and I'm sure there are a lot of other ways to parse it too.
Yesterday evening, I uploaded a revision. The revision is mainly in the demo, because I realized that I hadn't handled display of negative decimals quite right, and I also wanted to add code to print "NA" when the routine returns negx for timeout or range errors.
I am experimenting with humidity sensors for a friend to monitor the enviorment for his house plants.
My first experiment was over on the Ardunio side with a DHT11 and I purchased the Parallax SHT11
sensor for comparison. I did not realize there was a OBEX driver and was bit banging my own, which
has been a good exercise, but I was producing strange answers and yesterday when I saw your post I
had something to run and see if it was me or the only sensor I had was bad. I knew there was a 99.9
percent chance it was me but you never know. It was I forgot to cycle the clock at the ACQ bit location and
it thru off everything.
Tom
I see that Parallax is currently out of stock on the #28018 module on the DIP format carrier. It still is accompanied by my original Stamp code, but for the Prop there is only a link to the generic OBEX listing for sensors. Nothing specific. There should be an easier path to follow.
Tracy's Sensirion Integer Object: http://obex.parallax.com/object/517
Cam Thompson's Sensirion Demo Object: http://obex.parallax.com/object/515
''============================================================================= '' @file sensirion_integer_demo.spin '' MODIFIED BY WBA CONSULTING, ANDREW WILLIAMS '' @target Propeller with Sensirion SHT1x or SHT7x (not SHT2x) '' @author Thomas Tracy Allen, EME Systems '' Copyright (c) 2013 EME Systems LLC '' See end of file for terms of use. '' version 1.3 '' uses integer math to return values directly in degC*100 and %RH*10 '' and print to terminal screen '' no floating point required '' This is the most basic demo. ''============================================================================= '' version log '' 005 AW Added code to convert C to F CON _clkmode = xtal1 + pll8x ' _xinfreq = 5_000_000 ' pins for data and clock. ' Note sht1x and sht7x protocol is like i2c, but not exactly ' Assumes power = 3.3V DPIN = 1 '13 ' needs pullup resistor CPIN = 0 '14 ' best use pulldown resistor for reliable startup, ~100k okay. OBJ pst : "parallax serial terminal" sht : "sensirion_integer" PUB Demo sht.Init(DPIN, CPIN) pst.Start(9600) waitcnt(clkfreq/10+cnt) repeat pst.str(string(13,10,"Degrees F: ")) if (result := sht.ReadTemperature) == negx pst.str(string("NA")) ' read temperature and handle possible error (negx) else result := ((result*18)+32000) ' sad attempt at F=C*1.8+32 in integer math pst.dec(result/1000) ' /1000 to bring it down to proper decimal position pst.char(".") pst.dec(||result//1000) ' grab decimal degrees pst.str(string(" %RH: ")) if (result := sht.ReadHumidity) == negx ' read RH and handle error by printing NA (not available) pst.str(string("NA")) else pst.dec(result/10) ' %RH is in unit of tenths pst.char(".") pst.dec(||result//10) waitcnt(clkfreq*2+cnt) ' wait 2 seconds pst.clear ' Always read temperature shortly before humidity! RH temperature compensation depends on valid temperature reading. ' The routines return NEGX if the sensor times out, or if the readings are grossly out of range. ' Due to sensor tolerances, it is still possible to get readings <0 or >100 %RH. {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
75.324
75.32
75.3
75
Anyhow, here's code:
''============================================================================= '' @file sensirion_integer_demo.spin '' MODIFIED BY WBA CONSULTING, ANDREW WILLIAMS '' @target Propeller with Sensirion SHT1x or SHT7x (not SHT2x) '' @author Thomas Tracy Allen, EME Systems '' Copyright (c) 2013 EME Systems LLC '' See end of file for terms of use. '' version 1.3 '' uses integer math to return values directly in degC*100 and %RH*10 '' and print to terminal screen '' no floating point required '' This is the most basic demo. ''============================================================================= '' version log '' 005 AW Added code to convert C to F '' 006 AW Added code for decimal places CON _clkmode = xtal1 + pll8x ' _xinfreq = 5_000_000 ' pins for data and clock. ' Note sht1x and sht7x protocol is like i2c, but not exactly ' Assumes power = 3.3V DPIN = 1 '13 ' needs pullup resistor CPIN = 0 '14 ' best use pulldown resistor for reliable startup, ~100k okay. OBJ pst : "parallax serial terminal" sht : "sensirion_integer" PUB Demo sht.Init(DPIN, CPIN) pst.Start(9600) waitcnt(clkfreq/10+cnt) repeat pst.str(string(13,10,"Degrees F: ")) if (result := sht.ReadTemperature) == negx pst.str(string("NA")) ' read temperature and handle possible error (negx) else result := ((result*18)+32000) ' sad attempt at F=C*1.8+32 in integer math, results in 75324 for 75.324F pst.newline pst.dec(result/1000) ' /1000 to bring it down to proper decimal position pst.char(".") pst.dec(||(result)//(1000)) ' grab decimal degrees, 3 places, IE: 75.324 pst.newline pst.dec(result/1000) ' /1000 to bring it down to proper decimal position pst.char(".") pst.dec(||(result/10)//100) ' grab decimal degrees, 2 places, IE: 75.32 pst.newline pst.dec(result/1000) ' /1000 to bring it down to proper decimal position pst.char(".") pst.dec(||(result/100)//10) ' grab decimal degrees, 1 place, IE: 75.3 pst.newline pst.dec(result/1000) ' /1000 to bring it down to proper decimal position ' IE: 75 pst.newline pst.str(string("%RH: ")) if (result := sht.ReadHumidity) == negx ' read RH and handle error by printing NA (not available) pst.str(string("NA")) else pst.dec(result/10) ' %RH is in unit of tenths pst.char(".") pst.dec(result//10) waitcnt(clkfreq*2+cnt) ' wait 2 seconds pst.clear ' Always read temperature shortly before humidity! RH temperature compensation depends on valid temperature reading. ' The routines return NEGX if the sensor times out, or if the readings are grossly out of range. ' Due to sensor tolerances, it is still possible to get readings <0 or >100 %RH. {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
There is possibility for roundoff, for example, 75324 --> 753 --> 75.3 and 75364 --> 754 --> 75.4.
degF := (degC + 50) / 100 ' okay for positive only
or better, proper rounding for either positive or negative numbers...
degF := (degC + degC//100) / 100 ' okay for positive or negative. rounds °F*1000 off to °F*10
Keep in mind that both operators, division / and modulus // on the Prop work correctly on both positive and negative 2s complement integers.
Another way to convert °C*100 directly to °F*10 is to use the ** operator.
degF :=degC ** 773094114 + 320 ' convert degC*100 to degF*10
The multiplier is 2^32 * (18/100) = 773094114. That is okay for either positive or negative degC. Caveat: Watch out when constructing a multiplier, keep it between zero and 1/2-, that is, up to ** 2147483647.
~~~~~~~~~
Been playing around with the Sensirion code a bit more and changed it to work with an LCD instead of PST as well as turn on the backlight if the temp changes more than 1 degree and even had a version changing the color of an WS2812B RGB LED based upon temp ranges:
''============================================================================= '' @file sensirion_integer_demo.spin '' MODIFIED BY WBA CONSULTING, ANDREW WILLIAMS '' @target Propeller with Sensirion SHT1x or SHT7x (not SHT2x) '' @author Thomas Tracy Allen, EME Systems '' Copyright (c) 2013 EME Systems LLC '' See end of file for terms of use. '' version 1.3 '' uses integer math to return values directly in degC*100 and %RH*10 '' and print to terminal screen '' no floating point required '' This is the most basic demo. ''============================================================================= '' version log '' 005 AW Added code to convert C to F '' 006 AW Added code for decimal places '' 006L01 AW Changed from PST to LCD and set variable to change decimal places depending on change in temp '' 006L02 AW Added WS2812 RGB LED, Red if >80.0F, Green if 77.0-79.9, Blue if under 76.9 '' 006L03 AW Changed code to just show symbol when temp changes more then 1 degree '' 006L03-RGB AW Removed WS2812 RGB LED code CON _clkmode = xtal1 + pll16x ' _xinfreq = 5_000_000 ' pins for data and clock. ' Note sht1x and sht7x protocol is like i2c, but not exactly ' Assumes power = 3.3V DPIN = 6 '13 ' needs pullup resistor CPIN = 5 '14 ' best use pulldown resistor for reliable startup, ~100k okay. TxPin = 19 VAR long lastresult OBJ sht : "sensirion_integer" LCD : "FullDuplexSerial.spin" PUB Demo sht.Init(DPIN, CPIN) LCD.start(TxPin, TxPin, %1000, 9_600) waitcnt(clkfreq/10+cnt) LCD.tx(12) ' clear LCD repeat LCD.str(string("Deg F: ")) if (result := sht.ReadTemperature) == negx LCD.str(string("NA")) ' read temperature and handle possible error (negx) else result := ((result*18)+32000) ' sad attempt at F=C*1.8+32 in integer math, results in 75324 for 75.324F if result/1000 <> lastresult ' if a reading changed more than 1 degree LCD.tx(17) ' turn on backlight LCD.dec(result/1000) ' /1000 to bring it down to proper decimal position LCD.str(String(".")) LCD.dec(||(result/100)//10) ' grab decimal degrees, 1 place, IE: 75.3 LCD.str(String(" *")) lastresult:=result/1000 ' store latest reading as lastresult else ' if reading didn't change, show 1 decimal place LCD.tx(18) ' turn off backlight LCD.dec(result/1000) ' /1000 to bring it down to proper decimal position LCD.str(String(".")) LCD.dec(||(result/100)//10) ' grab decimal degrees, 1 place, IE: 75.3 LCD.tx(13) ' Line feed LCD.str(string(" %RH: ")) if (result := sht.ReadHumidity) == negx ' read RH and handle error by printing NA (not available) LCD.str(string("NA")) else LCD.dec(result/10) ' %RH is in unit of tenths LCD.str(string(".")) LCD.dec(result//10) waitcnt(clkfreq*2+cnt) ' wait 2 seconds LCD.tx(12) ' Clear LCD ' Always read temperature shortly before humidity! RH temperature compensation depends on valid temperature reading. ' The routines return NEGX if the sensor times out, or if the readings are grossly out of range. ' Due to sensor tolerances, it is still possible to get readings <0 or >100 %RH. {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
''============================================================================= '' @file sensirion_integer_demo.spin '' MODIFIED BY WBA CONSULTING, ANDREW WILLIAMS '' @target Propeller with Sensirion SHT1x or SHT7x (not SHT2x) '' @author Thomas Tracy Allen, EME Systems '' Copyright (c) 2013 EME Systems LLC '' See end of file for terms of use. '' version 1.3 '' uses integer math to return values directly in degC*100 and %RH*10 '' and print to terminal screen '' no floating point required '' This is the most basic demo. ''============================================================================= '' version log '' 005 AW Added code to convert C to F '' 006 AW Added code for decimal places '' 006L01 AW Changed from PST to LCD and set variable to change decimal places depending on change in temp '' 006L02 AW Added WS2812 RGB LED, Red if >80.0F, Green if 77.0-79.9, Blue if under 76.9 '' 006L03 AW Changed code to just show symbol when temp changes more then 1 degree CON _clkmode = xtal1 + pll16x ' _xinfreq = 5_000_000 ' pins for data and clock. ' Note sht1x and sht7x protocol is like i2c, but not exactly ' Assumes power = 3.3V DPIN = 6 '13 ' needs pullup resistor CPIN = 5 '14 ' best use pulldown resistor for reliable startup, ~100k okay. TxPin = 19 ' WS2812 CON CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq ' system freq as a constant MS_001 = CLK_FREQ / 1_000 ' ticks in 1ms US_001 = CLK_FREQ / 1_000_000 ' ticks in 1us STRIP_LEN = 1 ' LEDs in string LEDS = 18 ' LED tx pin VAR long lastresult OBJ sht : "sensirion_integer" LCD : "FullDuplexSerial.spin" strip : "jm_ws2812" ' WS2812 LED driver PUB Demo sht.Init(DPIN, CPIN) LCD.start(TxPin, TxPin, %1000, 9_600) strip.start_b(LEDS, STRIP_LEN) ' start led driver strip.off waitcnt(clkfreq/10+cnt) LCD.tx(12) repeat LCD.str(string("Deg F: ")) if (result := sht.ReadTemperature) == negx LCD.str(string("NA")) ' read temperature and handle possible error (negx) else result := ((result*18)+32000) ' sad attempt at F=C*1.8+32 in integer math, results in 75324 for 75.324F if result/100 > 800 ' if over 80.0 degrees strip.setx(0, $FF_00_00,32) ' RGB LED set to red elseif result/100>770 ' otherwise, if over 77.0 degrees strip.setx(0, $00_FF_00,32) ' RGB LED set to green else strip.setx(0, $00_00_FF,32) ' last case, just set to blue if result/1000 <> lastresult ' if a reading changed more than 1 degree LCD.tx(17) ' turn on backlight LCD.dec(result/1000) ' /1000 to bring it down to proper decimal position LCD.str(String(".")) LCD.dec(||(result/100)//10) ' grab decimal degrees, 1 place, IE: 75.3 LCD.str(String(" *")) lastresult:=result/1000 ' store latest reading as lastresult else ' if reading didn't change, show 1 decimal place LCD.tx(18) ' turn off backlight LCD.dec(result/1000) ' /1000 to bring it down to proper decimal position LCD.str(String(".")) LCD.dec(||(result/100)//10) ' grab decimal degrees, 1 place, IE: 75.3 LCD.tx(13) ' Line feed LCD.str(string(" %RH: ")) if (result := sht.ReadHumidity) == negx ' read RH and handle error by printing NA (not available) LCD.str(string("NA")) else LCD.dec(result/10) ' %RH is in unit of tenths LCD.str(string(".")) LCD.dec(result//10) waitcnt(clkfreq*2+cnt) ' wait 2 seconds LCD.tx(12) ' Clear LCD ' Always read temperature shortly before humidity! RH temperature compensation depends on valid temperature reading. ' The routines return NEGX if the sensor times out, or if the readings are grossly out of range. ' Due to sensor tolerances, it is still possible to get readings <0 or >100 %RH. {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
I think you must mean something more complicated than 102 = 10*10? Fractional powers? Those are digested by the computer using the relation z*10x = z*2x/log10(2). Then that new exponent is separated into integer and fractional parts, using the identity 2y = 2integer part of y*2fractional part of y. The integer part is saved as a shift operation to be used later. The fractional part is determined by some means, such as a lookup and interpolation in the HUB exponential table. Then multiply times z, apply the shift by the saved factor of two, and then apply the inverse of that first identity to convert back to a number with a power of 10. It is not easy in integer math, but the floating point package takes care of all of those details. Of course, floating point is at its core a number and exponent in powers of two.
Nope, just that. In the code, to vary the number of decimal places used, you divide by 10, 100, or 1000. So it would be nice to use a variable (say "decplace") set to 1, 2, or 3 for number of decimal places and then at the beginning of my routine, I can just make decplace = 10 to the decplace power. Now in my formula, when I divide by decplace, it would be 10, 100, or 1000. I guess I could easily use a lookup table or case statement, but was hoping to just use powers of 10, but stay away from floating point objects.
Actually, just realize that there are two values in the code that change depending on decimal places. So maybe a set of lookup tables would be clean. The three unique lines to obtain the decimal places:
pst.dec(||(result/1)//(1000)) ' grab decimal degrees, 3 places, IE: 75.324 pst.dec(||(result/10)//100) ' grab decimal degrees, 2 places, IE: 75.32 pst.dec(||(result/100)//10) ' grab decimal degrees, 1 place, IE: 75.3
Thanks for the compliment, I have been wondering if my code is ok or a mess, LOL. Agree with the SHT71 series. That concept was one of the reasons for the shape of my module. Several of my customers have told me the main value in mine is the quick response by being able to locate the module into the desired path of airflow easily.