Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to "verify" your details, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Electronics ➜ Microprocessors ➜ Temperature and humidity sensor - battery powered

Temperature and humidity sensor - battery powered

Postings by administrators only.

Refresh page


Posted by Nick Gammon   Australia  (23,070 posts)  Bio   Forum Administrator
Date Mon 05 Aug 2013 11:16 AM (UTC)

Amended on Sun 10 Dec 2017 04:20 AM (UTC) by Nick Gammon

Message
Introduction


This post describes a temperature and humidity sensor I made for our local Historical Society.


Namely:

Heidelberg Historical Society

http://www.heidelberghistoricalsociety.com.au/


Design objectives


Before starting construction or coding I set myself a few objectives. By having specific objectives you know if you have met them or not. :)


  • Measure temperature and humidity (for preservation of old documents etc.).
  • Measure light levels (added basically because it was easy to do).
  • Record many measurements to a micro SD card, so they could be uploaded to a spreadsheet for graphing purposes.
  • The measurements need to be date/time stamped, so there needed to be a real-time clock (RTC).
  • Run from battery power, so it wouldn't be necessary to find a handy power-point, and also so it could survive power outages.
  • Because of the above, use very low power unless actually doing something useful. My target was around 5 µA, which was somewhat lower than the normal self-discharge rate of AA batteries.
  • Have provision to be interrogated by pressing a switch, so it would report the current date/time, temperature, humidity, etc. to verify it was working correctly.
  • Have a "warning LED" which would flash just before writing to the SD card, so you don't accidentally remove it while it is being written to.
  • Survive the batteries being inserted the wrong way around.
  • Have a FTDI programming interface so it could be easily programmed using the bootloader.
  • Also have a ICSP (In Circuit Serial Programming) header so that the bootloader could be uploaded, and fuses changed if necessary.


Finished board


The finished board looks like this (parts labelled):



In operation, displaying the temperature (20.10 degrees C):



I assembled it on this prototyping board:



I got that board from Futurlec for $US 1.50:

http://www.futurlec.com/Protoboards.shtml#PROTO777

The schematic is here:

http://gammon.com.au/images/Arduino/HHS_Temperature_Logger1.png


Tip

To make the schematic less cluttered I have not shown every connection. Pins with the same "net name" (eg. 5V_SW, /RESET, PC0(A0) ) are considered connected.


Although it looks complex, I'll describe each piece, as taken in parts it is quite simple.

Power management


To achieve the objective of very low power usage I didn't want to rely on each device (like the clock, SD card, LED display) having low "idle" power. So instead the board consists of the normal +5V line to power the processor, and then a "switched 5V" line (called 5V_SW on the schematic) which can be turned on and off. The circuit for that is:





A P-channel MOSFET is used to provide a "high-side" switch. To turn on a P-channel MOSFET the gate needs to be more negative than the source. Since the source is connected to +5V, by setting the gate to 0V then the MOSFET turns on and conducts, which means that +5V is now available at the drain.

The 330 ohm resistor (R3) limits current to 15 mA to protect the output pin. The 10K resistor (R4) provides a weak pull-up to ensure that the gate is high even when the processor is powered down.


I = V / R
I = 5 / 330 = 0.015
I = 15 mA


Thus to "power on" the peripherals we drive that pin low:


void powerOnPeripherals ()
  {
  digitalWrite (POWER, LOW);  // turn power ON
  delay (1); // give time to power up    
  }  // end of powerOnPeripherals


The 100 uF capacitor (C5) is designed to even out the sudden surge of power consumed by the peripherals as power is applied to them, so that the +5V line does not drop too low and maybe cause the processor to reset.

I also chose to use the internal oscillator rather than a crystal or resonator. Speed was not of the essence, and the processor uses less power when running from the internal oscillator. However it runs at 8 MHz so I chose the "Lilypad Arduino w / Atmega328" board type when programming it in the Arduino IDE.

Power consumption


Tests show that the above technique, combined with putting the processor into "power down" sleep mode, results in power consumption of around 6 µA during the sleep phase. This is consistent with the results documented here:

http://gammon.com.au/power

The watchdog timer itself (needed to wake the processor up and take readings) consumes around 6 µA of current. Without it we would expect around 100 nA of consumption, so a result of 6 to 6.5 µA is within expectations.

An alkaline battery (AA) might advertise to have 2900 mAH capacity, that is, it can provide a mA for 2900 hours. Since there are 1000 µA to a mA that would be 2900000 µA. And since we are consuming 6 of those µA it should last for:


2900000 / 6 = 483333 hours
2900000 / 6 / 24 / 30 = 671 months


Clearly the battery is going to pass its expiry date before those 671 months are up (around 55 years later).

Of course, more power is consumed when writing to the SD card, so figures won't be that good. However by only taking a reading every 15 minutes or so, the vast bulk of the time will be in low power-consumption mode.

Tests show that the actual time taken to write to the SD card is in the order of 128 mS, and we assume that the card will consume something like 250 mA. So if we take a reading every 15 minutes (every 900 seconds) and if that reading takes 128 mS, then the time taken writing to the SD card is on average:


1 / (15 * 60 / .128) = 0.000142


Multiply that by the estimated 250 mA and we get an average of:


1 / (15 * 60 / .128) * 250 = 0.0355 mA


Thus, averaging out the writing, it will add 36 µA to the consumption. Still well under the self-discharge rate of the battery (estimated at an equivalent of 80 µA).

Reading temperature and humidity


I obtained a DHT22 temperature and humidity sensor from Adafruit for $US12.50.

http://www.adafruit.com/products/385

With the "interesting" part facing you (the back is just blank) the pins are numbered:



The wiring for it is:



Adafruit supply a library:

http://learn.adafruit.com/dht/using-a-dhtxx-sensor

A slight trap is that, when running at 8 MHz you have to use a different form of the constructor for the DHT type, so that the code (which is heavily dependent on timing loops) works correctly at that speed:


// DHT-22 temperature and humidity sensor
DHT dht (DHTPIN, DHTTYPE, 3);    // 6 for 16 MHz, 3 for 8 Mhz (defines pulse width for zero/one)


This sensor requires a 10K pull-up resistor (R6) between the data pin and Vdd because it uses an open-drain configuration to switch between the processor sending a pulse and receiving a response.

The sensor is connected to the 5V_SW (switched 5V) line, and it requires around 2 seconds to take a reading after being powered on, so there is a 2 second delay built into the code.

Thermistor


I became a bit suspicious of the temperatures reported by the DHT22 (suspicions which turned out to be largely unfounded) so I added a second temperature sensor, this being a NJ28MA0502G NTC 5K thermistor.



The relevant figures for this particular thermistor were taken from the datasheet:



They are plugged into the sketch here:


// temp. for nominal resistance (almost always 25 C) 
const int TEMPERATURENOMINAL = 25;

// resistance at TEMPERATURENOMINAL (above)
const int THERMISTORNOMINAL = 5000;

// The beta coefficient of the thermistor (usually 3000-4000) 
const int BCOEFFICIENT = 3960;

// the value of the 'other' resistor (measure to make sure)
const int SERIESRESISTOR = 4644;


The 4.7K resistor I measured as accurately as I could (getting 4644 ohms) so I used that exact value in the sketch.

The thermistor is "powered" by the AREF pin (which is set to +5V when doing an analogRead). It later turned out that this consumed around 500 µA even when sleeping due to the current out of the AREF pin through the thermistor so I had to disable that in the "sleep" part of the sketch:


  ADMUX = 0;  // turn off internal Vref


Code for the thermistor courtesy of the Adafruit tutorial:

http://learn.adafruit.com/thermistor/using-a-thermistor


Real-time clock


In order to log the time, we have to know what it is, and have the time survive resets, power outages (eg. changing the battery) etc.

To do this easily we can incorporate the DS1307 RTC (Real Time Clock) chip. This is easy to use, and interfaces using I2C. I got 30 of the DS1307 chips from eBay for $10 (for the lot).

http://www.gammon.com.au/i2c



I used a CR2032 3V lithium battery as the "backup battery". This keeps the clock running even if the chip is powered down. The chip is connected to the 5V_SW line so it is powered down when not required. There is a small 32.768 kHz crystal visible in the photo above.

Note that the two 4.7K resistors do not appear in the photo as I accidentally wired them to 5V rather than 5V_SW, and then removed them due to concerns with parasitic powering of the chip. It still works without them, but for extra reliability they should be there.

Reverse polarity protection


In order to save a lot of expense from someone plugging the battery in backwards we incorporate a reverse-polarity protection system.

Afrotechmods did a great tutorial on this:

P-FET Reverse Voltage Polarity Protection Tutorial

The idea here is that, rather than just using a diode (which has a substantial voltage drop of around 0.7V) you use a P-channel MOSFET. The basic circuit is this:





In the case of the FQP27P06 transistor, the figure for RDS(on) is typically 0.055 ohms, so at a drain of 200 mA, the voltage drop would only be 0.2 * 0.055 = 0.011V. This is much better than the 0.7V drop from a protection diode.

Again, the gate has to be lower than the source for the MOSFET to conduct, this condition occurs when the battery is connected the correct way around. Note that this condition initially occurs because of the current flow through the MOSFET body diode.

Texas Instruments have a paper on this technique:

http://www.ti.com/lit/an/slva139/slva139.pdf‎

Measuring light level


I added this largely because it was easy to do, and the current light level could be useful to know if someone was in the room at night, for example (or even during the day if there are no windows, and you have to turn the light on to see).



Display output


I thought it would be cool to be able to press a switch and see the current temperature, humidity, date, etc.

The easy way to do this is with a 8 x 7-segment LED display. I got these from eBay for $10 each which is really cheap considering the cost of the individual parts. Try searching for: "MAX7219 8 Digit 7 segment LED Module kit".

The kit comes with a board, 2 x 4-digit LEDs, a MAX7219 chip, and the other required parts (two capacitors and a resistor).



Soldering it together is fairly quick.



This is one I put aside because the "header" pins were too short. You need extra long ones to get past the width of the MAX7219 chip.



This is how it is connected on the board:



I used bit-banged SPI for talking to the LED board, because the hardware SPI was in use for the SD card module.

Bit-banged SPI described here:

http://www.gammon.com.au/spi

More about the MAX7219 chip here:

http://www.gammon.com.au/forum/?id=11516

You can use the MAX7219 in "decode" or "no-decode" mode. In "decode" mode it knows the patterns for the digits 0 to 9, plus "E", "H", "L", "P", "-" and space.

However in "no-decode" mode you can make up extra letters, which I have chosen to do.

You can make various letters (eg. A, b, C, d, E, F, L, o, P, r, S, t) by combining the 7 segments in various ways. This lets you put up error messages like "Sd Error".

To do this you set a "1" bit for the appropriate segments:



Part of the code that sets the appropriate bit patterns:


    case 'A':          converted = 0b1110111;  break;  
    case 'b':          converted = 0b0011111;  break;  
    case 'c':          converted = 0b0001101;  break;  
    case 'C':          converted = 0b1001111;  break;  
    case 'd':          converted = 0b0111101;  break;  
    case 'E':          converted = 0b1001111;  break;  


SD card interface


To save readings we need to write to an SD card.

I got this adapter from Adafruit ($US 14.95)

http://www.adafruit.com/products/254

It basically saves you the worry of getting a holder for the card, and it has a level shifter to convert the signals from 5V to 3.3V.





I used the SdFat library which you can get from here:

https://github.com/greiman/SdFat


Tip:
The SdFat library seems to be undergoing changes/improvements as at April 2015. The code may not compile with the new version of the SdFat library.

An archived version of the SdFat library (which works with this sketch) is available from:

http://gammon.com.au/Arduino/SdFat-master.zip (2.2 Mb)

Unzip that file, and from within the folders inside it, copy the SdFat folder into your "libraries" folder (which is under your Arduino sketchbook folder - not inside the Arduino application folder). Then restart the Arduino IDE.


The interface with the processor is by SPI:

http://www.gammon.com.au/spi

Code


The code is written to be fairly modular. It can be downloaded from:

http://gammon.com.au/Arduino/Temperature_Monitor.ino


The main loop is this:


void loop () 
  {
  // sleep for 8 seconds
  myWatchdogEnable (WDT_8_SEC);

  // did they push the switch?
  if (buttonPressed)
    showStuff ();
    
  // count number of times we slept
  counter++;

  // every 48 seconds check the time (6 lots of 8 seconds asleep)
  if (counter >= 6)
    {  
    powerOnPeripherals ();
    getTime ();
    if (now.minute () != lastMinute)
      {
      if ((now.minute () % TAKE_READINGS_EVERY) == 0)
        recordReading ();
      lastMinute = now.minute ();
      }  // end of change in the minute
    powerOffPeripherals ();
      
    counter = 0;
    }  // end of sleeping for 6 times
    
}  // end of loop


The sketch sleeps for 8 seconds until the watchdog timer wakes it up, or the switch is pressed.

It then tests if the switch was pressed (which would generate an interrupt, waking it from sleep). If so, it calls showStuff to display the following:


  • datE dd.mm (date)
  • Hour hh.mm (time)
  • t1 26.78 (temperature from DHT22)
  • t2 27.10 (temperature from thermistor)
  • H 52.45 (relative humidity)
  • d 9.25 (dew point)
  • L 524 (light level from 0 to 1023)
  • Err 0 (Error count)
  • Err-LotS (Error count is > 99999)
  • Out 42 (Number of lines written to SD card)
  • Out-LotS (Number of lines written is > 99999)


Each line is shown for 3 seconds. The letters at the start identify the type of information. The error count is intended to let the user see if there have been errors reported during the night. An error can be one of:


  • rtc Err1 (RTC error: cannot find DS1307)
  • rtc Err2 (RTC error: cannot get data from DS1307)
  • rtc Err3 (RTC error: clock not running)
  • rtc Err4 (RTC error: date/time invalid)
  • dht H (DHT22 returned invalid humidity)
  • dht t (DHT22 returned invalid temperature)
  • Sd Error (Could not initialize SD card - maybe card not installed?)
  • FILE (Could not open readings.txt file for writing)


The switch can be pressed at any time (except when the SD card is being written to) to check the date/time, temperature, etc.

Then every 6 times around the loop (every 48 seconds) it powers up the MOSFET to allow it to check the time using the real-time clock. If the minutes part of the time indicates it is time to make a reading (eg. every 15 or 30 minutes) then it calls recordReading to take those readings.

The code for taking a reading reads the temperature (two ways), the humidity, and the light level. It calculates the dew point, and then connects to the SD card and writes (appending) to the readings.txt file.

The code does not include provision for setting the clock. The standard DS1307 test sketch (which sets the clock to the time the sketch was compiled) can be used for an initial clock setup.

Output file format


The output file is "READINGS.TXT" (you can change the name in the sketch if you like) in the root directory.

Example output line, annotated underneath:


2013-08-06 09:00:40	47.20	18.30	18.85	6.87	518

YYYY-MM-DD HH:MM:SS    Humidity Temp1   Temp2    Dew    Light  
-- Date -- - Time -        %      C       C       C     0 - 1023  


Humidity is relative humidity. Temperature is in degrees Celsius. Temp1 is the reading from the DHT22, Temp2 is the reading from the thermistor. Dew point is in degrees Celsius. Light level is from 0 to 1023 where a higher number is more light.

Each field is separated by the tab character ('\t' in C, or hex 0x09). You can load the file into a spreadsheet as a "tab-delimited" format.


Tip

To convert from Celsius to Fahrenheit:


  • Take the temperature in Celsius and multiply 1.8.
  • Add 32 degrees.
  • The result is degrees Fahrenheit.



Summary


The board functions as intended. It has the following main features:


  • Records temperature, humidity and light levels
  • Data is saved to a micro-SD card at 15-minute intervals
  • A warning LED flashes just before writing to avoid corruption of the disk file
  • Line are written to the file tab-delimited, suitable for importing into a spreadsheet
  • Each line is date/time stamped.
  • The dew point is calculated and stored.
  • You can press the "info" button and have the current date, time, temperature etc. shown to you.
  • Current consumption is around 6 to 7 µA during idle mode (which is almost all of the time)
  • It runs off 3 x AA batteries.


Example of data loaded into a spreadsheet:



Example of data graphed:



In the graph above note the fluctuations during the day as the heating turns on and off. Then at night the temperature slowly drops.

Note: The graphed and spreadsheet data was taken when I was testing with a one-minute interval. The logging interval was later changed to 15 minutes to save power and disk space.

Updates


8 August 2013 (Version 1.01)

Added a couple of extra lines of code to pass the current date/time to the SdFat library, so that the disk file is now time-stamped with the file creation/modification date.

19 October 2013 (Version 1.02)

Added a test for a DST (daylight saving time) switch. Connect this between D9 (pin 15 on the chip) and Gnd. When open the value will be HIGH (because of the internal pullup) and it assumes DST is off (so you can omit the switch if you like). When closed the value will be LOW and it adds an hour to the time from the clock. The idea is you push the switch to the closed position during summer (if needed).

Also added a file-write count which is displayed when you press the "info" button.

20 October 2013 (Version 1.03)

Further improvements to the DST calculations. Version 1.02 simply added one to the hour, without regard for if this would overflow into a new day/month/year. This is now allowed for.

10 December 2017 (Version 1.04)

Fixed compiler errors introduced in newer versions of the IDE.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,070 posts)  Bio   Forum Administrator
Date Reply #1 on Tue 06 Aug 2013 12:24 AM (UTC)

Amended on Mon 11 Dec 2017 07:10 AM (UTC) by Nick Gammon

Message
How to set the clock


Initially the DS1307 chip will not be running because the time has not been set. The sketch below will set it (taking into account the need to power it up first).


// Set date/time on Temperature / Humidity / Light level logger
//
// Author: Nick Gammon
// Date:   6 August 2013

// Use Lilypad Arduino w / Atmega328 board type

#include <Wire.h>
#include <RTClib.h>

const byte POWER = 3;
// const byte DS1307_ADDRESS = 0x68;  // for checking clock there (I2C address)

RTC_DS1307 RTC;

void setup () 
  {
  Serial.begin (115200);
  Serial.println ();
  Serial.println ("Setting the date/time to: " __DATE__ " " __TIME__);
  
  // power MOSFET - initially off
  pinMode      (POWER, OUTPUT);
  digitalWrite (POWER, LOW);  // turn power ON
  delay (1); // give time to power up    

  Wire.begin ();
  RTC.begin ();
  delay (2);
  TWBR = 72;  // 50 kHz at 8 MHz clock
  
  // check RTC is on I2C bus
  Wire.beginTransmission (DS1307_ADDRESS);
  Wire.write ((byte) 0);	
  if (Wire.endTransmission () != 0)
    {
    Serial.println  ("Cannot find Real Time Clock");
    return;
    }

  // check RTC clock responds
  if (Wire.requestFrom (DS1307_ADDRESS, 1) != 1)
    {
    Serial.println  ("Real Time Clock not responding");
    return;
    }

  // read and discard that one byte
  Wire.read ();
  
  RTC.adjust (DateTime(__DATE__, __TIME__));
  Serial.println ("Time set.");
  }  // end of setup

void loop () {}


The time is set to the time the sketch was compiled, so compile it and immediately upload it.

Then, immediately replace the sketch with the "main" one in the earlier post, otherwise the clock will be reset to the compile time every time you power the board back on.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,070 posts)  Bio   Forum Administrator
Date Reply #2 on Sun 10 Dec 2017 05:50 AM (UTC)

Amended on Sun 10 Dec 2017 08:44 PM (UTC) by Nick Gammon

Message
Plotting the results


If you run the sensor for a while you will end up with a rather large data file (around 35,040 entries if you record every 15 minutes for a year).

You may find that plotting this with spreadsheet programs will be slow and tedious. You can use gnuplot - a free plotting program to do the plotting for you.

See the gnuplot home page.

The format for using gnuplot may be a little obscure, so here is an example plot file which works with the sensor data:


# Data file uses tab as a separator
set datafile separator '\t'

# Title of the plot
set title "Temperature and humidity plot"

# We want a grid
set grid

# X-axis label
set xlabel "Date/Time"

# Use automatic scaling
set autoscale

# Title for Y-axis
set ylabel "Temperature (C)"

# Title for Y2-axis
set y2label "Humidity (%)"

# place ticks on second Y2-axis
set y2tics border

# Define that data on X-axis should be interpreted as time
set xdata time

# Time in log-file is given in format YYYY-MM-DD HH:MM:SS
set timefmt "%Y-%m-%d %H:%M:%S"

# Display notation for time
set format x "%d %b\n%Y"

# generate a legend which is placed underneath the plot
set key outside bottom center box title "Temperature Sensor"

# output into png file
set terminal png size 800,500
set output "plot.png"

# Data columns are:
# 1 is date/time
# 2 is humidity
# 3 is temperature
# 4 is temperature (second sensor)
# 5 is dew point
# 6 is light

# Below, "using 1:2" means use column 1 of the data for X, and column 2 of the data for Y

# read data from file and generate plot
plot "READINGS.TXT" using 1:3 title "Temperature (C)" with lines axes x1y1 \
  , "" using 1:2 title "Relative Humidity (%)" with lines axes x1y2


Example of a month's output plotted with that file:



To run the above, install gnuplot, and using the command-line enter:


gnuplot (filename)


Where (filename) is the name you saved that plot script under. In my case that executed almost instantly, even when run with a year's worth of data.

This particular file expects an input file "READINGS.TXT" in the current directory, and creates an output file "plot.png" which is 800 x 500 pixels in size. You can alter the size by changing those figures in the script.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


63,552 views.

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.