(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 sensor. This sensor consists of a capacitor which varies with relative humidity and is used in a 555 circuit to generate a pulse train of frequency related to relative humidity. The number of pulses over a one second period are counted and the RH is then calculated.
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)