Building a hygrometer with a HS1101

Share

The next sensor I wanted to add to my home was a set of hygrometers. Specifically I wanted an exterior one, and a matching interior one. This would be useful as we have evaporative cooling, and if the humidity level outside is already high, then it doesn’t make a lot of sense to put extra water into the air. Worse than that, it can also damage my books and make the house really clammy. So, adding some sensors was the first step in some form of alerting.

I picked up two HS1101s from ebay quite cheaply (about $4 each IIRC). These devices are capacitors whose capacitance varies proportionally with relative humidity. You also need to provide a temperature at the sensor to correct the value, although the correction is pretty minor so I guess you could skip this if you really wanted to cut costs. Given I have plenty of code for Dallas 1820s now, I just dropped one of those onto the board too.

I just used the circuit from the data sheet for my design, with a few simple tweaks (like the DS1820). Here’s my surprisingly unprofessional circuit diagram:

The DS1820 stuff that’s not on the data sheet is in red. When built, it looks like this (note the crazy amount of jumper wire):

This gives us temperature on a 1-Wire pin, and an oscillator on another pin which relates to the current humidity. You’ll notice that my circuit has some extra wires, that’s because I power down the 555 / HS1101 when I’m not taking a sample. I do this because Peter H. Anderson suggested that noise would be a problem otherwise. This circuit was actually quite hard to build and get working. There are a few reasons for that:

  • The large number of jumpers on the prototype PCB.
  • The lack of documentation from other arduino hackers (with the notable exception of the rather good Peter H. Anderson page).
  • The HS1101 data sheet forgets to mention that connecting pins 1 and 8 on the 555 is assumed knowledge.
  • The values for R1 and R2 vary depending on what model 555 you are using, and are crazily specific. For the LMC555 that I used, R1 is 1238K and R2 is 562K. I got close to these values, but not exact and it did seem to affect accuracy.
  • You must use a CMOS 555. That’s buried in a six word sentence in the middle of a page on the data sheet, and I didn’t notice it for a while. With a NMOS 555, you get effectively random numbers out of the circuit. Worse than that, CMOS 555s are actually a little hard to find, and I had to get mine from Farnell.
  • I attempted to calibrate with the government weather data from the next suburb over. Unfortunately, as best as I can tell, that data is wrong. It claims that its currently as humid here as it is in Cairns in the wet season, which I deny. Calibration is an ongoing issue for me, although I have some ideas on how to progress there. It might also not matter, as I am building an identical sensor for inside the house and as long as they are both equally wrong I can still detect the “turn off the water to the evap” state that I want to.

Here’s a comparison between my data for today and the weather service’s:

The code to run the HS1101 is relatively simple:

// Temperature and humidity sensor node. Based on the FridgeControlWeb project of mine
// as well as http://www.phanderson.com/picaxe/relhum_count.html

#include <enc28j60.h>
#include <etherShield.h>
#include <ip_arp_udp_tcp.h>
#include <ip_config.h>
#include <net.h>
#include <websrv_help_functions.h>
#include <avr/io.h>
#include <math.h>

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONEWIRE 3
#define HS1101DATA 5
#define HS1101POWER 7

int count_transitions(int ms);

// How long between measurement cycles
#define SLEEP_SEC 10

OneWire oneWire(ONEWIRE);
DallasTemperature sensors(&oneWire);

unsigned long last_checked = 0, this_check = 0;

// Web server setup
#define MYWWWPORT 80
#define BUFFER_SIZE 550
#define ERROR_500 "HTTP/1.0 500 Error\r\nContent-Type: text/html\r\n\r\n<h1>500 Error</h1>"

static uint8_t mymac[6] = {0x54, 0x55, 0x58, 0x10, 0x00, 0x25};
static uint8_t myip[4] = {192, 168, 1, 252};
static uint8_t buf[BUFFER_SIZE + 1];
char data[BUFFER_SIZE + 1];

// The ethernet shield
EtherShield es = EtherShield();

uint16_t http200ok(void)
{
  return(es.ES_fill_tcp_data_p(buf, 0, PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n"
                                            "Pragma: no-cache\r\n\r\n")));
}

// prepare the webpage by writing the data to the tcp send buffer
uint16_t print_webpage(uint8_t *buf)
{
  uint16_t plen;
  plen = http200ok();
  plen = es.ES_fill_tcp_data_p(buf, plen, PSTR("<html><head><title>Temperature sensor</title>"
                                               "</head><body><pre>"));
  plen = es.ES_fill_tcp_data(buf, plen, data);
  plen = es.ES_fill_tcp_data_p(buf, plen, PSTR("</pre></body></html>"));

  return(plen);
}

// Float support is hard on arduinos
// (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1164927646) with tweaks
char *ftoa(char *a, double f, int precision)
{
  long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};
  char *ret = a;
  long heiltal = (long)f;

  itoa(heiltal, a, 10);
  while (*a != '\0') a++;
  *a++ = '.';
  long desimal = abs((long)((f - heiltal) * p[precision]));
  itoa(desimal, a, 10);
  return ret;
}

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

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);
}

void setup()   {
  // initialize the digital pin as an output:
  Serial.begin(9600);
  sensors.begin();

  es.ES_enc28j60Init(mymac);
  es.ES_init_ip_arp_udp_tcp(mymac, myip, MYWWWPORT);

  pinMode(HS1101POWER, OUTPUT);
}

void loop()
{
  int i, j, data_inset;
  char float_conv[10];
  float t;
  DeviceAddress addr;
  uint16_t plen, dat_p;
  float relhum_raw, relhum_corrected;
  int relhum_count;

  // Read temperatures, we dump the state to a buffer so we can serve it
  this_check = millis();
  if(this_check > last_checked + SLEEP_SEC * 1000)
  {
    data_inset = 0;
    sensors.requestTemperatures();
    sprintf(data + data_inset, "Sensor count: %d\n",
            sensors.getDeviceCount());
    data_inset = strlen(data);

    for(i = 0; i < sensors.getDeviceCount(); i++)
    {
      t = sensors.getTempCByIndex(i);
      sensors.getAddress(addr, i);

      data[data_inset++] = 't';
      for (j = 0; j < 8; j++)
      {
        sprintf(data + data_inset, "%02x", addr[j]);
        data_inset += 2;
      }

      sprintf(data + data_inset, ": %s\n", ftoa(float_conv, t, 2));
      data_inset = strlen(data);
    }

    // Power up the 555 / HS1101, and take a measurement. Power it down again afterwards.
    digitalWrite(HS1101POWER, HIGH);
    delay(500);
    relhum_count = count_transitions(1000);
    digitalWrite(HS1101POWER, LOW);
    sprintf(data + data_inset, "HS1101 cycles: %d\n", relhum_count);
    data_inset = strlen(data);

    relhum_raw = 557.7 - 0.0759 * relhum_count;
    sprintf(data + data_inset, "Raw humidity: %s\n", ftoa(float_conv, relhum_raw, 2));
    data_inset = strlen(data);

    relhum_corrected = (1.0 + 0.001 * (t - 25.00)) * relhum_raw;
    sprintf(data + data_inset, "Corrected humidity: %s\n",
            ftoa(float_conv, relhum_corrected, 2));

    Serial.println(data);

    last_checked = this_check;
  }

  // Handle network packets
  dat_p = es.ES_packetloop_icmp_tcp(buf, es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf));
  if(dat_p != 0)
  {
    if (strncmp("GET ", (char *)&(buf[dat_p]), 4) != 0){
      // head, post and other methods:
      dat_p = http200ok();
      dat_p = es.ES_fill_tcp_data_p(buf, dat_p, PSTR("<h1>200 OK</h1>"));
    }

    // just one web page in the "root directory" of the web server
    else if (strncmp("/ ", (char *)&(buf[dat_p+4]), 2) == 0){
      dat_p = print_webpage(buf);
      Serial.println("Served temperature web page");
    }

    else{
      dat_p = es.ES_fill_tcp_data_p(buf, 0, PSTR(ERROR_500));
    }

    es.ES_www_server_reply(buf, dat_p);
  }
}

As with previous circuits, I’m going to have to thank Doug for hints and tips along the way, as well as letting me steal his entire collection of 8 pin DIP sockets.

Share

The Man in the Rubber Mask

Share

I’d been looking for this book for ages, as it is quite rare, so it was exciting to find it at Gould’s the other day. This is the memoir of the dude inside the rubber mask that Kryten wears in Red Dwarf. The book is an easy read, and entertaining, although I wouldn’t call it funny. Most of the book focuses on how terribly horrible it is to be encased in rubber day after day while shooting a comedy in terrible locations. Oh, and Robert is slightly insecure which doesn’t help.

Overall I’m glad I found this book, and glad I read it again.

[isbn: 0140235752]

Share

The Renegades of Pern

Share

This book starts off in quite a disjointed manner, with the introduction of a variety of seemingly unrelated characters. The only thing that they all have in common is that they’re holdless. However, as the book progresses these characters are all weaved together into a relatively cohesive story line. I say relatively because there are gaps in the story telling, which can be a little jarring.

Interestingly, this book also clarifies some of the events of the others in the series. Most satisfyingly it includes more detail of the buried settlement at Landing than The White Dragon did, which ties in nicely with the introduction provided in Dragonsdawn. This gives me hope that later books will take the science fiction track I’ve been wanting them to for a while.

[isbn: 0345369335]

Share

Asimov’s Mirage

Share

If I was to name one flaw with the Robot City and Robots and Aliens series, it would have to be that they’re not very good. They’re lackluster, have difficult to believe plots, very simple structure, and are overall poorly thought through. Its a similar sensation to that I feel when I read the tie-in books written after Harrison’s Bill the Galactic Hero series. I feel a little sorry for the writers in later books in these series, because I suspect their hands were tied by the poor decisions of previous authors (similarly to the mess that Bear’s Foundation and Chaos had to dig that series out after Benford’s tragically terrible Foundation’s Fear).

Robot City and Robots and Aliens were disappointments because I read Roger MacBride Allen’s Caliban series before them, and Caliban is ok. Not awesome, but ok.

I say all of this as an introduction to Mirage. I guess what I’m saying is that I’ve been wading through Asimov robot tie-ins from other authors for a while now, and some of them are not very good. That’s why finding Mirage was such a delight. Its well written, has a similar style as Asimov’s own writing, reuses characters and plot elements from previous tie-in books sufficiently to acknowledge their existence without getting bogged down by the poor decisions of those previous series. Its an engaging read, and I’m glad I stuck through these various series long enough to find it.

My only complaint with this book is that the epilogue is confusing and doesn’t align with my understanding of the end of the story.

[isbn: 0743475232]

Share

The Beer Fridge saga continues

Share

Since the last update on the beer fridge, we’ve had to do some murdering of the original PCB to get it to fit in a case. In addition, we’d failed to take into account the startup power draw for the compressor (5 amps), and had to upgrade the relay we’re using from a 3 amp solid state relay. Luckily Doug had the beefier relay just sitting around in his shed. That means we’ve lost our opto-isolation because the new relay is a simple mechanical one, but we have a relay coil doing the same thing now. A new PCB will make the world a lot neater, which will be nice. Oh, we’ve also been mentioned on Hack a Day, which has generated some interesting comments on their site.

I’ve observed that sometimes the relay doesn’t turn on, even though the arduino thinks it has done so. An example can be seen here:

(Note that graph is a Google chart server image, generated by a simple visualization program I wrote in python. If you’re that way inclined, the visualization software is in my public SVN repository).

At first we thought this was a software problem with comparing ints to floats, but it continued to happen even after we fixed that bug. You can see in the graph above that during testing the compressor was “turned on” (i.e. the arduino thinks it has activated the relay), but the temperature continued to rise. A visual inspection showed that in fact the relay hadn’t turned the compressor on, and power cycling the controller (and therefore the relay) didn’t help. However, the simple expedient of hitting the relay with a screw driver handle fixed the problem, which made me think that Doug had given me a bodgy relay. This got me thinking about if it would be possible to unstick a relay in software though — its pretty easy to detect this case (temperature continues to rise despite the compressor being). I’m thinking that perhaps if you cycled the inputs over and over you might be able to get the relay to unstick.

Detecting this case in software might be generally useful anyway though… Ignoring stuck relays, it would also tell you when someone had left the door open for example. It would be nice to detect this case and then give up on the compressor for a few minutes before trying again. I wrote a quick implementation of this “door open detection” and gave it a try, but I’ve now given up on it as being too fiddly. I found problems such as when the outside world is heating up rapidly (summer mornings for example), the compressor can’t make a big enough difference to the internal temperature of the fridge, but you still really want the compressor on because something is better than nothing.

In the end it turned out the problem with the relay was that the arduino wasn’t providing enough current to pull the relay contact in reliably. This was fixed with the addition of a transistor to the board, which is yet another thing to add to the next PCB.

One of the big advantages of a microprocessor based custom thermostat is that I can implement new features that wouldn’t normally be present in a fridge. Some of these features might turn out to be bad ideas, but I view the beer fridge as a bit of an experimentation platform.

Since my extra Dallas 1820s have arrived from ebay, I also added a sensor inside the freezer. This is interesting because the freezer isn’t getting anywhere near the FDA recommended temperature (-17.7 Celsius). I tweaked the code yet again to turn on the compressor if either compartment is over temperature, but this resulted in the fridge running much too cold — it was close to freezing point in fact. I think this is probably a hardware problem with the freezer, and given its a beer fridge I’ve just written “this is not the freezer you are looking for” on the door and gone back to only monitoring the temperature of the fridge compartment. Here’s a graph of my experiments with the freezer yesterday:

Another recent software tweak is a start up delay for the compressor in order to reduce the risk of back pressure damage to the compressor. Thanks Murray! This comes with yet another LED which indicates that the compressor is currently disabled.

I think this project is nearly ready to start showing people the code. The code is also getting big enough that cutting and pasting it into blog posts is starting to get annoying. So, here are a few links:

  • The code for the arduino. This should be self explanatory, except for saying that we’re using a nuelectronics.com ethernet shield, not the standard one.
  • The visualization software. You want temperature.py to scrape the web server on the arduino and push stuff into a MySQL database, and then server.py is a simple python web server that provides the UI for visualization. The UI isn’t very good at the moment, but I shall improve it soon.
Share

Buy Jupiter Short Stories

Share

This is another Asimov short story collection. The following stories appear in the book, although I have already read a couple as part of either the Robot short stories or the Nightfall collection of short stories.

To be honest these stories aren’t Asimov’s strongest. They entertaining, but they’re not as amazing as some of his other stuff. I guess its hard to be a genius all the time.

The following stories appear in this collection:

Buy Jupiter
1975
The Complete Robot
1982
Robot Dreams
1986
Darwinian Pool Room
Day of the Hunters
Shah Guido G.
Button, Button
The Monkey’s Finger
Everest
The Pause
Let’s Not
Each an Explorer
Blank!
Does a Bee Care?
Silly Asses
Buy Jupiter
A Statue for Father
Rain, Rain, Go Away
Founding Father
Exile to Hell
Key Item
The Proper Study
2430 A.D.
The Greatest Asset
Take a Match
Thiotimoline to the Stars
Light Verse

[isbn: 0575041994]
[awards: nominee nebula_short_story 1965 (Founding Father)]

Share