This post describes a temperature and humidity sensor I made for our local Historical Society.
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.
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:
The schematic is here:
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.
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.
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:
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.
With the "interesting" part facing you (the back is just blank) the pins are numbered:
The wiring for it is:
Adafruit supply a library:
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.
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:
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).
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:
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).
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:
More about the MAX7219 chip here:
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)
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:
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:
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:
The code is written to be fairly modular. It can be downloaded from:
The main loop is this:
void loop ()
// sleep for 8 seconds
// did they push the switch?
// count number of times we slept
// every 48 seconds check the time (6 lots of 8 seconds asleep)
if (counter >= 6)
if (now.minute () != lastMinute)
if ((now.minute () % TAKE_READINGS_EVERY) == 0)
lastMinute = now.minute ();
} // end of change in the minute
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.
To convert from Celsius to Fahrenheit:
- Take the temperature in Celsius and multiply 1.8.
- Add 32 degrees.
- The result is degrees Fahrenheit.
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.
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.