Relative Humidity Measurement using the Humirel HS1101 Sensor

Arduino, BasicX BX24 and PICAXE-18X

copyright, Peter H. Anderson, Baltimore, MD, Nov, '07

(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 Humirel HS1101 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)