(Nov, '07). The discussion has be reworked to also include the Arduino.
Introduction
This discussion focuses on a relatively simple technique for measuring relative humidity using a
It is desireable to measure temperature as people are usually interested in both the relative humidity and temperature. In addition, there is a small correction to the relative humidity and with a powerful processor such as the Arduino or the BX24, one can calculate the dew point using relative humidity and temperature.
Advantages of this technique for measuring relative humidity are that it is inexpensive and as the input to the processor is a pulse train as opposed to an analog voltage, the RH measurement circuitry may be located several hundred feet from the processor. It may be powered using the supply associated with the processor, or using a processor output with a series limiting resistor for protection against an accidental ground or via a remote supply. The value of the remote supply may be in the range of 3 to 12 VDC. However, in the case of a remote supply, I suggest using a optocoupler between the RH measurement circuitry and the processor counter input.
I offer a small assembled package, consisting of a HS-1101 sensor, TS555 (SGThompson), 49.9K and 562K one percent resistors for the 555 circuitry, and a separate 10K NTC thermistor for measuring temperature. Connections are made to this board via a four position binding post; +5 VDC (red), GRD (black), 555 Ouput (blue) and one side of the thermistor (yellow). The other side of the thermistor is connected to ground. Schematic (.pdf).
Processor RH Board Output --- 1K ------------------------- +5 GRD ------------------------------------ GRD Counter Input <---------------------- Pulse (555 Output) through 1K series resistor. +5 VDC | 10.0K | A/D input <-------------------------- NTC Thermistor ---- GRD
In the above I have illustrated the powering of the humidity board using a processor output. Bring the output high (near +5 VDC), wait briefly for the 555 to stabilize, measure the number of counts over a second and then bring the processor low, turning off the 555 circuitry. This avoids having the 555 continually generating a hard square wave which may generate "noise" which will affect A/D measurements. This turning on and off of the external 555 may be overly cautious, but it is simple to implement. A 1K series resistor on the power output is suggested to avoid damaing the procesor output if the lead to the humidity circuit is accidentally grounded. Note that on the RH board, I have a similar resistor in case the Pulse output to the processor is accidentally grounded.
I opted to use an negative temperature coefficient (NTC) thermistor to measure temperature as it is inexpensive and after many years of design, I have come to believe it will stand up to harsh environmental conditions, and in fact, thermistors are usually used in automotive applications which is about as harsh an environment for electronics as I can imagine. The non-linear behaviour of the thermistor poses a challenge for the designer, but once this hurdle is cleared, it is a very simple and foolproof means of measuring temperature.
Arduino.
// HS1101 - Arduino // // Turns on the exteranl 555 and counts the number of pulses on Digital Pin 5 (also ATMEL input T1) // using Timer 1. Then turns the 555 off to avoid the noise associated with this rail to rail // oscillation. The raw relative humidity is then calculated. // // An A/D conversion is performed to measure the voltage across the NTC thermistor, the Rthermistor // and the Tcelius is calculated. // // The relative humidity is corrected for the measured temperature. // // The Dew Point temperature is then calculated. // // copyright, Peter H Anderson, Baltimore, MD, Nov, '07 // --------------------------------------------------------------------- /************************************************************************** * Copyright (c) <2013> - estate of Peter H. Anderson * * * 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. * **************************************************************************/ #include <avr\io.h> #include <math.h> #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #define POWER_PIN 2 unsigned int count_transitions(int ms); void setup() { Serial.begin(9600); } void loop() { float RH_raw, RH_corrected, Tc, Tc_dew_point; while(1) { RH_raw = measure_RH(); Tc = measTemperature(5); RH_corrected = calc_RH_corrected(RH_raw, Tc); Tc_dew_point = calc_dew_point(Tc, RH_corrected); print_float(RH_corrected, 1); Serial.print(" "); print_float(Tc, 2); Serial.print(" "); print_float(Tc_dew_point, 2); Serial.println(); delay(1000); } } float calc_dew_point(float Tc, float RH) { const float a = 17.27, b = 237.7; float x, Tc_dew; x = (a * Tc) / (b + Tc) + log(RH/100.0); Tc_dew = (b * x) / (a - x); return(Tc_dew); } float measure_RH(void) { long RH_count; float RH_raw; pinMode(POWER_PIN, HIGH); digitalWrite(POWER_PIN, HIGH); // power up the 555 cicuit delay(500); // allow some time for the 555 to stabilize RH_count = count_transitions(1000); //Serial.println(RH_count); // for debugging RH_raw = 557.7 - 0.0759 * RH_count; digitalWrite(POWER_PIN, LOW); // turn off the 555 return(RH_raw); } float calc_RH_corrected(float RH_raw, float Tc) { float T_diff, RH_corrected; T_diff = Tc - 25.00; RH_corrected = (1.0 + 0.001 * T_diff) * RH_raw; return(RH_corrected); } unsigned int count_transitions(int ms) { // configure Counter 1 cbi(TCCR1A, WGM11); cbi(TCCR1A, WGM10); cbi(TCCR1B, WGM12); // WGM12::WGM10 000 - Normal mode sbi(TCCR1B, CS12); // CS12::CS10 111 - External clock, count on rising edge. sbi(TCCR1B, CS11); sbi(TCCR1B, CS10); TCNT1 = 0x0000; // note that TCNT1 is 16-bits delay(ms); // not sure if should turn off the counter return(TCNT1); } float measTemperature(int analog_channel) { int ADVal; float RThermistor, Tc; ADVal = analogRead(analog_channel); RThermistor = calcRthermistor(ADVal); Tc = calcTc(RThermistor); return(Tc); } float calcRthermistor(int ADVal) { float Rtherm; if (ADVal <=0) // avoid trouble conditions { ADVal = 10; } Rtherm = 10.0e3 / (1024.0 /((float) ADVal) - 1.0); return(Rtherm); } float calcTc(float RTherm) { const float A_const = 3.354016e-3; const float B_const = 2.569107e-4; const float C_const = 2.626311e-6; const float D_const = 0.675278e-7; float x, TKelvin, Tc; x = log(RTherm / 10.0e3); TKelvin = 1.0 / (A_const + B_const * x + C_const * square(x) + D_const * cube(x)); Tc = TKelvin - 273.15; return(Tc); } float square(float x) { return(x * x); } float cube(float x) { return(square(x) * x); } void print_float(float f, int num_digits) { int f_int; int pows_of_ten[4] = {1, 10, 100, 1000}; int multiplier, whole, fract, d, n; multiplier = pows_of_ten[num_digits]; if (f < 0.0) { f = -f; Serial.print("-"); } whole = (int) f; fract = (int) (multiplier * (f - (float)whole)); Serial.print(whole); Serial.print("."); for (n=num_digits-1; n>=0; n--) // print each digit with no leading zero suppression { d = fract / pows_of_ten[n]; Serial.print(d); fract = fract % pows_of_ten[n]; } }
BasicX BX24
' RH_Count.Bas (BX24) ' ' copyright, Peter H Anderson, Baltimore, MD, Oct, '05 Sub Main() Dim RHUncorrected as Single, TCelcius as Single, RHCorrected as Single, DewPointTCelcius as Single Do RHUncorrected = MeasRH(11, 12) TCelcius = MeasTemp(13) RHCorrected = TempCorrectRH(RHUncorrected, TCelcius) DewPointTCelcius = CalcDewPoint(RHCorrected, TCelcius) Debug.Print CStr(RHUncorrected); " "; CStr(RHCorrected); " "; CStr(TCelcius); " "; CStr(DewPointTCelcius) Call Sleep(20.0) Loop End Sub Function MeasRH(ByVal PowerPin as Byte, ByVal CountPin as Byte) as Single Dim Transitions as Long, Count as Long, RHUncorrected as Single Call PutPin(PowerPin, 1) ' power the RH unit Call Sleep(1.0) ' wait for it to stablize Transitions = CountTransitions(CountPin, 1.0) ' Count = Transitions \ 2 Call PutPin(PowerPin, 0) ' turn off the 555 device RHUncorrected = 557.7 - 0.0759 * CSng(Count) If RHUncorrected > RHUncorrected Then RHUncorrected = 100.0 ElseIf RHUncorrected < 0.0 Then RHUncorrected = 0.0 End If MeasRH = RHUncorrected End Function Function MeasTemp(ByVal MeasPin as Byte) as Single Const A_const as Single = 3.354016e-3 Const B_const as Single = 2.569107e-4 Const C_const as Single = 2.626311e-6 Const D_const as Single = 0.675278e-7 Dim RTherm as Single, TKelvin as Single, Tcelcius as Single Dim Ratio as Single Dim ADVal as Integer ADVal = GetADC(MeasPin) If ADVal = 0 Then ' avoid division by zero ADVal = 1 End If RTherm = 10.0e3 / (1024.0 / CSng(ADVal) - 1.0) Ratio = RTherm / 10.0e3 Tkelvin = 1.0 / (A_const + B_const * log(Ratio) + C_const * log(Ratio)^2 + D_const * log(Ratio)^3) TCelcius = TKelvin - 273.15 MeasTemp = TCelcius End Function Function TempCorrectRH(ByVal RHUncorrected as Single, ByVal TCelcius as Single) as Single Dim TDiff as Single, RHCorrected as Single TDiff = TCelcius - 25.00 RHCorrected = (1.0 + 0.001 * TDiff) * RHUncorrected TempCorrectRH = RHCorrected End Function Function CalcDewPoint(ByVal RHTrue as Single, ByVal TCelcius as Single) as Single Dim EW_RH as Single, DewPointTCelcius as Single EW_RH = exp10(0.6607 + 7.5 * TCelcius / (237.3 + TCelcius)) * RHTrue / 100.0 DewPointTCelcius = ((0.6607 - log10(EW_RH)) * 237.3) / (log10(EW_RH) - 8.16077) CalcDewPoint = DewPointTCelcius End Function
PICAXE-18X.
Note that the 10K NTC thermistor on the RH module is a Vishay / BC Components 2381-640-66103 bead type thermistor with axial leads. The technique used in the following implementation is discssed in a separate discussion.
' HS1101_3.Bas ' ' Illustrates an interface with two modules based on the Humirel HS1101 capacitive sensor ' configured with a 555 so as to generate a pulse train. Temperature is measured using a ' a NTC thermistor. ' ' For each of the two Channel, 0 and 1, the external unit is powered by turning on the appropriate ' PinPower and, after a short delay to allow the 555 to settle, the Count is measured over the course ' one second. Ten times the RH (RH_10) is then calculated. Power is then removed from the external ' unit. ' ' The temperature is then measured using an A/D converter and calculated using table lookup. This powerful ' technique in considered in a separate discussion. ' ' The RH is then adjusted for temperature. I adjusted the RH up 0.1 percent for each degree above ' 25 degrees C and down 0.1 percent for each degree below 25 degrees C. I am not sure this is correct. ' ' The Channel, Temperature and compensated RH are displayed for each channel. ' ' PICAXE-18X RH Module 0 ' ' Out 2 (term 8) -- 1K ---------- Power for Channel 0 ' In0 (term 17) <------------ Pulse train from 555 ' ' +5 ' | ' 10K ' | ' ----- In6 (term 15) ' | ' |---------------------------- Thermistor ------- GRD ' ' ' GRD -------------------------- GRD ' ' ' PICAXE-18X RH Module 1 ' ' Out 3 (term 9) -- 1K ---------- Power for Channel 0 ' In1 (term 18) <------------ Pulse train from 555 ' ' +5 ' | ' 10K ' | ' ----- In7 (term 16) ' | ' |---------------------------- Thermistor ------- GRD ' ' ' GRD -------------------------- GRD ' ' Things that I have not addressed. ' ' How to deal with a clearly incorrect count measurement. How to deal with a relative ' calculation which is over 100. How to deal with an invalid temperature. ' ' I did not test for temperatures below 0 degree C. ' ' Uses about 300 bytes of program memory. Most of the EEPROM is used for the thermistor ' lookup table. ' ' copyright, Peter H Anderson, Oct, '05 Symbol Pulses = W0 ' used only in Sub MeasRH Symbol ADVal = W0 ' used only in Sub MeasTC Symbol RH_10 = W1 ' this is saved in Sub MeasTC for temporary use for ADValHi8 and ADValLow2 Symbol TC_100 = W2 Symbol RHComp_10 = W3 Symbol ADValHi8 = B8 ' used in Sub MeasTC Symbol ADValLow2 = B9 ' used in Sub MeasTC Symbol Diff = B10 ' used in Sub MeasTC Symbol N = B11 ' Used in Subs MeasTC and CompRH Symbol TC = B12 ' used in CompRH Symbol Whole = B8 ' used in Display Subs Symbol Fract = B9 ' used in Display Subs Symbol SignFlag = B10 ' used in DisplayTC Symbol Digit = B11 ' used in DisplayTC Symbol Channel = B13 Symbol PinPower = B8 ' used only in MeasRH, Shared with Whole Symbol PinCount = B9 ' used in MeasRH, Shared with Fract Symbol PinTC = B9 ' used in MeasTC, Shared with Fract Main: Channel = 0 GoSub MeasRH ' result returned in RH_10 ' need to deal with error GoSub MeasTC ' result returned in TC_100 ' need to deal with error GoSub CompRH GoSub DisplayTC GoSub DisplayRH Channel = 1 GoSub MeasRH ' result returned in RH_10 ' need to deal with error GoSub MeasTC ' result returned in TC_100 ' need to deal with error GoSub CompRH GoSub DisplayTC GoSub DisplayRH Pause 5000 Goto Main MeasRH: Lookup Channel, (2, 3), PinPower Lookup Channel, (6, 7), PinCount High PinPower ' turn on power to distant RH measurement circuitry Pause 1000 ' wait for unit to stabilize Count PinCount, 973, Pulses ' 973 is one second on the PICAXE-18X I tested Low PinPower ' remove power from distant RH measurement circuitry ' should test for obvious erroneous conditions ' RH_10 = 5577 - 0.759 * Num_Pulses RH_10 = 7 * Pulses / 10 RH_10 = 5 * Pulses / 100 + RH_10 RH_10 = 9 * Pulses / 1000 + RH_10 RH_10 = 5577 - RH_10 Return MeasTC: Lookup Channel, (0, 1), PinTC ReadADC10 PinTC, ADVal ADValHi8 = ADVal / 4 ' isolate the high 8 bits ADValLow2 = ADVal & $03 ' low two bits TC_100 = 10542 ' adjust this as required If ADValHi8 < 16 Then TooHot If ADValHi8 > 251 Then TooCold For N = 0 to ADValHi8 ' continue to subtract Read N, Diff TC_100 = TC_100 - Diff Next ' Now for the low two bits, a linear interpolation N = N + 1 Read N, Diff Diff = Diff / 4 * ADValLow2 TC_100 = TC_100 - Diff MeasTCDone: Return TooHot: TooCold: TC_100 = $7fff GoTo MeasTCDone Return CompRH: ' Now adjust RH calculation for temperature RHComp_10 = RH_10 - 25 ' start at zero degrees C TC = TC_100 / 100 SignFlag = TC / 128 Branch SignFlag, (CompRHPostive, CompRHMinus) CompRHMinus: TC = TC ^ $ff + 1 RHComp_10 = RHComp_10 - TC Goto CompRHDone CompRHPostive: RHComp_10 = RHComp_10 + TC Goto CompRHDone CompRHDone: Return DisplayTC: SignFlag = Tc_100 / 256 / 128 If SignFlag = 0 Then TCPositive TC_100 = TC_100 ^ $ffff + 1 ' twos comp SerTxD ("-") TCPositive: SerTxD(#Channel, " ") Whole = TC_100 / 100 Fract = TC_100 % 100 SerTxD (#Whole, ".") ' be sure the fractional is two digits Digit = Fract / 10 SerTxD (#Digit) Digit = Fract % 10 SerTxD (#Digit) Return DisplayRH: Whole = RHComp_10 / 10 Fract = RHComp_10 % 10 SerTxD (" ", #Whole, ".", #Fract, 13, 10) Return ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' EEPROM locations 0 - 15 not used EEPROM 16, (254, 236, 220, 206, 194, 183, 173, 164) EEPROM 24, (157, 149, 143, 137, 131, 126, 122, 117) EEPROM 32, (113, 110, 106, 103, 100, 97, 94, 92) EEPROM 40, (89, 87, 85, 83, 81, 79, 77, 76) EEPROM 48, (74, 73, 71, 70, 69, 67, 66, 65) EEPROM 56, (64, 63, 62, 61, 60, 59, 58, 57) EEPROM 64, (57, 56, 55, 54, 54, 53, 52, 52) EEPROM 72, (51, 51, 50, 49, 49, 48, 48, 47) EEPROM 80, (47, 46, 46, 46, 45, 45, 44, 44) EEPROM 88, (44, 43, 43, 43, 42, 42, 42, 41) EEPROM 96, (41, 41, 41, 40, 40, 40, 40, 39) EEPROM 104, (39, 39, 39, 39, 38, 38, 38, 38) EEPROM 112, (38, 38, 37, 37, 37, 37, 37, 37) EEPROM 120, (37, 36, 36, 36, 36, 36, 36, 36) EEPROM 128, (36, 36, 36, 36, 36, 35, 35, 35) EEPROM 136, (35, 35, 35, 35, 35, 35, 35, 35) EEPROM 144, (35, 35, 35, 35, 35, 35, 35, 35) EEPROM 152, (35, 35, 35, 35, 35, 35, 35, 35) EEPROM 160, (36, 36, 36, 36, 36, 36, 36, 36) EEPROM 168, (36, 36, 36, 37, 37, 37, 37, 37) EEPROM 176, (37, 37, 38, 38, 38, 38, 38, 39) EEPROM 184, (39, 39, 39, 39, 40, 40, 40, 41) EEPROM 192, (41, 41, 42, 42, 42, 43, 43, 43) EEPROM 200, (44, 44, 45, 45, 46, 46, 47, 47) EEPROM 208, (48, 48, 49, 50, 50, 51, 52, 53) EEPROM 216, (53, 54, 55, 56, 57, 58, 59, 61) EEPROM 224, (62, 63, 65, 66, 68, 70, 72, 74) EEPROM 232, (76, 78, 81, 84, 87, 90, 94, 98) EEPROM 240, (102, 107, 113, 119, 126, 135, 144, 156) EEPROM 248, (170, 187, 208, 235)