Wednesday, February 18, 2015

Battery Operated Temperature Sensor

This project will take a long time to complete, but I wanted to post about it because it's a nice project for a person starting off in Home Automation.  I do a number of things in this project that can lead to even more projects later; this could be the first step for a person that wants to take control of their house without the huge expense of one of the ready made systems out there.

The story begins after I installed my new barometer out on the fence <link>, I was looking at the temperature sensor that I removed <link> and thought, "It would be cool to have temperature sensors that I could put anywhere around the house."  It would be nice to see the temperature in the hot parts of the house and maybe move some air in there when company is around. If I used a sensor from one of those oven temperature probes, I could watch a roast from anywhere; this could be useful ... so I built one.

I wanted to be able to power it with batteries and be able to read the output on a monitoring XBee, so that would mean a processor of some kind and a lot of learning about low power techniques. Low power pretty much leaves out a normal Arduino, they have power regulators on them and those things constantly consume power; they also get warm and that would mess with the temperature readings. I decided to use one of my Ardweenys.  If you're not familiar with these, here's what they look like:
They're not the tiniest Arduino derivative, but they are really close.  The beauty of these devices is that they have a tiny footprint and no unnecessary components to use power. I don't have to come up with a design for supporting the processor and can just use the device.

Since the new device was going to be battery powered, I would need to somehow monitor the battery level so I could change them when needed.  Did you know that the atmega328p can monitor its own power?  Yep, it can, you just have to be good with google to find out how.  There's also a temperature monitor in there so you can keep track of the heat generated.  I don't actually need that, but it would be fun to do it. Here's the code I used to capture the processor temperature and supply voltage:

/*
These two routines use the atmega328 built in thermometer and VCC measurement techniques
This way I can get the supply voltage to see how the batteries are doing, as well as grab
the temperature of the chip.  It may need calibration, but it's free
*/
float readTemp(){
  long result; // Read temperature sensor against 1.1V reference
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
  delay(20); // Wait for Vref to settle - 2 was inadequate
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (result - 125) * 1075;
  return (result / 10000.0) * 1.8 + 32.0; // I want degrees F
}

float readVcc(){
  long resultVcc; // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(20); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  resultVcc = ADCL;
  resultVcc |= ADCH<<8;
  resultVcc = 1126400L / resultVcc; // calculate AVcc in mV
  return (resultVcc / 1000.0); // but return volts
}


The units (Fahrenheit and Volts) are just my choice, Feel free to use degrees C and millivolts if you want to.  These routines ONLY work on certain of the Atmel processsors, but I'm only going to be using the 328, so that's not a problem.

Next I need a temperature sensor. I've already worked with the tmp36 a lot, and I have one of them on hand, so I went with it. I really can't recommend the tmp36 though; the things are skittish and have to be catered to a bit.  I've found with a lot of experimenting that stabilizing the power supply a lot and reading them several times in a row and taking an average will give a good reading.  Since I can read it 10 times and take an average in a few microseconds, that should work just fine. The power supply noise will only be a problem while I debug it because it will run on batteries.

Edit: I beat the interaction with this sensor. I can get nice readings that seem to be very accurate without too much trouble, the solution is both hardware and software and is described here <link> and here <link>.

/*
Reading a tmp35 chip to get temperature.
These devices are a little flakey. If the power has even a tiny
bit of noise, they give erratic readings. So, filter them as
best you can and then take several readings and average them.
*/
float readTemp2(){
  int total = 0;
  for (int i = 0; i < 10; i++){
    total += analogRead(0);
  }
  int reading = total / 10;
  float voltage =  (reading * 1.1) / 1024;
  float tempC = (voltage - 0.5) * 100;
  float tempF = (tempC * 9.0 / 5.0) + 32.0;
  return(tempF);
}

I've used this type of code several times for various experiments and it seems to work pretty well. There's other techniques, but they're much more complex. I'll try this for a while and see how it holds up in actual operation over time.

Of course, since it's me, there will be an XBee in there to transmit to my House Controller. I've never tried to get the power down low on an XBee, so this should be interesting. Notice how 'interesting' and 'frustrating' often mean the same thing? This time, there really wasn't any problem. I wanted the processor to control the XBee, so all I needed to do was set the XBee up for a pin controlled sleep and the processor could control it through a digital pin.

One of the cool things about sleep and processors is that the GPIO pins retain their state when you put them to sleep.  So, you set a pin high and put the processor to sleep, the pin stays high. That was a bit of a surprise at first, but it makes sense when you think about it a bit. This makes it really simple to put the XBee to sleep, just:

#define SLEEP HIGH

      digitalWrite(xbeeSleepReq, SLEEP); // put the XBee to sleep

I used a #define for SLEEP because I kept forgetting which state I needed to set the pin to.

The idea is that the processor starts, initializes the XBee and sets up anything else needed, then goes to sleep.  Sometime later it wakes up, takes a temperature reading, sends it, then goes back to sleep. Repeat forever, or at least until the batteries die.

Now, all I have to do is learn how to put the 328 to sleep. Yep, there's thousands of blog posts and tutorials out there on how to do this. Tons of examples of setting registers and shifting bits to control internal counters and such. Yuck! I really don't want to spend a few days learning the ins and outs (pun intended) of the 328P registers, I want to use them. JeeLabs to the rescue. Jean-Claude Wippler of JeeLab has created a nice library that includes a function to handle sleep without having to learn a ton of marginally useful register manipulation. However, I need to wake it up also; it's not very useful to put it to sleep and just leave it there. I can't do my usual technique of asynchronous timers in this instance because the timers won't run when the 328 is asleep, so I have to rely on the milli() function. Here's the code I came up with to control the sleep-awake cycle including putting the 328 to sleep.

  if (millis() - savedmillis > AWAKETIME){
    Serial.print("was awake for ");
    Serial.println(millis() - savedmillis);
    delay(100); // delay to allow the characters to get out
    savedmillis = millis();
    unsigned long timeSlept = 0;
    while (timeSlept < SLEEPTIME){
      digitalWrite(xbeeSleepReq, SLEEP); // put the XBee to sleep
      Sleepy::loseSomeTime((unsigned int)1000);
      timeSlept = (millis() - savedmillis);
      digitalWrite(xbeeSleepReq, AWAKE); // wake that boy up now
    }
    Serial.print("was asleep for ");
    Serial.println(millis() - savedmillis);
    savedmillis = millis();
    sendStatusXbee();
  }

AWAKETIME and SLEEPTIME can be changed to limit battery usage while still leaving enough time for the XBee to stabilize and complete the transaction. I'll have to experiment with this over time to find some balance based on real-life experience.  But notice how easy it was to put the board to sleep for a timed period?  One litle call to loseSomeTime() with the length of time I want to sleep was all there was to it.

A problem crept in this technique though.  Since the board is asleep, it isn't counting milliseconds, so the JeeLab library estimates the duration and bumps the millisecond timer to get it close to being correct. The problem comes when an interrupt happens. When something interrupts, the board wakes up to handle the interrupt, and when using SoftwareSerial to get an additional emulated serial port, you get interrupts. That's why I keep my own count and repeat the loop to sleep some more when this happens. In practice, I lose time this way, but it's been less than a second in the testing I've done so far. The timing doesn't have to be perfect, just good enough and this seems to fit the bill nicely.

So, I do some initialization, go to sleep, wake up, send the status and go back to sleep. I based the code on the XBee example I gave in a previous post for the barometer <link> I have outside. I had to take out some stuff and add others, but the code is sorta, mostly the same. I still have the command handling in there in case I need it.

Now, armed with enough hardware to get this project started I cobbled together a setup on a protoboard and started putting it together.


Yes, that messy lump of wires is my temperature sensor.  It takes up less than half a full sized breadboard, and has a few locations to spare.  There were a number of things that crept into the project that after getting it going I didn't expect.  For example, when the board is asleep too long, the XBee coordinator forgets about it. What happens is that the coordinator expects some interaction with every XBee on the network periodically, and if it doesn't get it, it removes the entry from its table of devices. So, on the first few tries, the temperature sensor couldn't connect when it woke up and I got error 22 or 24 depending on what was going on at the time.  A significant amount of time later I had a pretty good solution; just change the timeout on the coordinator for this item to be significantly larger than the sleep time on the temperature sensor.  I decided to make it three minutes and give it a try since I can change it later. The complication to this is that you have to change it for the coordinator and all the routers on the network.  Fine, I just fired up XCTU and used the remote command capability to update the various XBees one at a time.  The values I used in this case were:

SP = 7D0
SN = 9

The SP parameter evaluates to 20 seconds and the SN parameter means do it 9 times. Three minutes on a device that reports once a minute should be fine.  If extended testing indicated that I needed to change it, I'd just have to do the change over again. Not too bad.

Another thing was that I tried sending too soon after waking up the XBee.  The XBee takes a small amout of time to get itself going well enough to respond to send requests or commands, so I had to enable the CTS (clear to send) capability and hook it up to a 328 pin.  After doing that all I had to do was hang in a loop until the CTS signal changed and then I could send to my hearts content.  To set the parameter use the value:

D7 = 1

And my simple solution to monitoring it before sending was:

void sendXbee(const char* command){
  ZBTxRequest zbtx = ZBTxRequest(Destination, (uint8_t *)command, strlen(command));
  while (digitalRead(xbeeCTS) == SLEEP){} // just hang up and wait 
  xbee.send(zbtx);
}

I just hang in a while loop until the pin goes low.  It was nice how the #define I came up with worked in this case.

I decided to play around with it hooked to a battery and rewired the mess until it looked a little better and this is the device as it exists right now:


Maybe it isn't any less messy after all.  I did get to use my rubber band though.  And yes, I use cheap batteries. No, I don't expect you to be able to trace the wires from this.  Here's a Fritzing image of it:


Note that the black thing is NOT a 328 chip, it's an Ardweeny, I couldn't find a Fritzing part for the Ardweeny that worked.  And, for my 'more advanced' readers:


Once again, it isn't a 328, it's an Ardweeny, and of course I'll give you the code, it's in GitHub already with my other Arduino projects and examples <link> it's called RoomTemp; I also added Jeelib so I wouldn't have to keep track of changes to it. But remember, I'm not done with this yet.

I'll have to monitor the battery usage over time and make adjustments or changes as necessary, bugs may show up, or I may get a brilliant idea. The eventual goal is to have them in several of my rooms as well as a couple to play with.  I may even carry out my threat to hook one to the barbecue for monitoring steaks. I'm monitoring this on my Raspberry Pi house controller, but not doing anything with the readings yet.  Here's a sample of the output I'm using:

{u'Temp1': {u'ptemperature': u'79.4', u'temperature': u'73.9', u'voltage': u'3.0'}}
{u'Temp1': {u'ptemperature': u'79.4', u'temperature': u'73.9', u'voltage': u'3.0'}}
{u'Temp1': {u'ptemperature': u'79.4', u'temperature': u'73.7', u'voltage': u'3.0'}}
{u'Temp1': {u'ptemperature': u'79.2', u'temperature': u'73.7', u'voltage': u'3.0'}}
{u'Temp1': {u'ptemperature': u'79.4', u'temperature': u'73.7', u'voltage': u'3.0'}}
{u'Temp1': {u'ptemperature': u'79.4', u'temperature': u'73.9', u'voltage': u'3.0'}}
{u'Temp1': {u'ptemperature': u'79.4', u'temperature': u'73.7', u'voltage': u'3.0'}}

I'm using a JSON string and the 'ptemperature' value is the processor temperature I mentioned above, the voltage is taken from the processor also. Of course you can change the code and use any format you want to in the message.

Have fun.

The next part of this project is here <link>.

No comments:

Post a Comment