This is my Arduino-based temperature logger. On the breadboard, from left to right, is a serial real time clock, a 1Mb EEPROM, and lastly the serial temperature sensor. Parts list:
- HD44780-based LCD screen (optional)
- DS1307 real time clock
- 12 pF 32,768Hz crystal for the DS1307
- 3V battery for the DS1307 (optional, not shown)
- DS1631 12-bit serial temperature sensor
- AT24C series EEPROM (I used a AT24C1024B, which is huge, and relatively expensive($4))
- Lots of wire. I was glad I have a nice set of breadboard wire for this project.
Forget that. I was able to put together the functional equivalent with less than $10 worth of parts on a scrap piece of perf board.
Since pretty much everything uses I2C, I'll leave wiring it all up as an exercise for the student (Hint: connect everything to analog 4 & 5).
Note that some of the features of the dumper (setting the clock, reading the clock), don't work. The main goal was to just get the D(ump) and Z(ero) commands working. I'll get the other commands working at some point if I mess up and the DS1307 loses power at some point (I originally set it using some sample code I found online).
I measured the current draw as 42.5mA, so ignoring the loss in the power supply, that's only half a watt. For this much data, I can pay for half a watt!
How It Works
On power-up, the Arduino checks the CH (Clock Halt) flag on the DS1307 clock chip. If this flag is set, it indicates that the clock has lost power, and that the date and data stored on it isn't valid. Without valid data, the Arduino displays an error message on the LCD and enters an infinite loop to do nothing.
If the clock is running, the controller then loads the first two bytes of general purpose RAM off of the clock. The DS1307 provides 56 bytes of RAM which can be backed by its battery in the case of power failure. I use the first two bytes of it to store the current index into the EEPROM, so that if the controller gets reset, as long as the clock has power, I'll never start writing over collected data by starting from the beginning again. It then prints the read count on the LCD for a second before changing to the current time and date. I found this useful for debugging, since I then only needed to hit the reset button to figure out where it was writing to on the EEPROM. It also sends the temperature sensor the signal to start taking temperature readings. The DS1631 is factory set to continuously take readings, as opposed to the other option where it only takes one and goes back into standby.
Once all of the initialization is finished, the Arduino enters a loop where every 100ms it reads the time off the DS1307 and the temperature off the DS1631 to display on the LCD. It then compares the minute value to the previous one, and if they're different, that means it's been a minute since the last temperature recorded in the EEPROM.
To record the temperature to the EEPROM, the current temperature reading is written to the EEPROM, and the address stored in the clock's RAM is incremented, so if the power goes out in the next minute, no data is lost or overwritten.
Conveniently, all three of the ICs use the I2C serial interface (by accident? Who knows...), so that leaves a ton of extra pins if I manage to think up anything else to add on.
Each temperature reading is stored as 2 bytes, one for the whole degrees, and the other to store 4 bits of fractional degrees. It wouldn't be unreasonable to assume my room is never going to swing outside of something like 20-30 degrees Celsius, and try and pack the reading into one byte to save room. I'm using a 1Mb EEPROM, which gives me enough room to take two byte readings every minute for 45 days. This is an incredibly high time resolution, and backing it off to something more reasonable, like once every 15 minutes, stretches this time out to 2 years. Changing the source is trivial; just add minute%15==0 to the if statement in loop().
Since the EEPROM is I2C, having the maximum 4 devices per bus is simply a question of paying the $4 per device. You would have to change how the tempreadcount variable is handled everywhere (store it in three bytes on the DS1307 instead of two, etc) so it doesn't roll over after the first chip is filled, but that would mean being able to take a temperature reading every minute for half a year, and that's just freakin awesome.
Even without the AT24C EEPROM, the Arduino comes with 512 or 1024 bytes of EEPROM on-board. This can get you a couple days of 15 minute readings, which is at least enough to play with, if you just wanted to buy the DS1631 chip.
- For some reason, I could not get the LiquidCrystal library and Serial to both work at the same time. This means that to dump the EEPROM or reset the clock, I have to flash in a different program, then reflash the logging program.
- I was originally using a DS1306 clock chip, but it drifted badly (a few minutes a day). Once I switched to the DS1307, which calls for the 12pF crystal I had, instead of a 6pF crystal, it hasn't noticeably drifted in a few days. Lesson learned: make sure you have the right crystal for your clock.
- Don't copy paste big chunks of code that almost do the same thing without stopping and really thinking about it. I spent a long time trying to figure out why the data dump just read the last measurement for all of them. Was using the read number count instead of the loop variable... I was afraid I had somehow smoked my $4 EEPROM.
- I think finding I2C addresses in data sheets is some kind of right of passage. You'd think that they would be something they're put rather prominently on the first page, or refer to it several times, but instead, every time they do refer to it, they refer to it as, "that address as noted previously," etc. Freakin pain in the ass, digging through not only the text, but all the I2C spec diagrams to find where they happened to show the waveform for the device's address, which you then get to decode into the 7 bit number you needed in the first place. I finally wrote up one page of notes with all the I2C addresses and register numbers so I could actually write some code instead of spend two hours digging through the data sheets every time I try to implement something.
After running for one day, it came time to dump the data into Excel. I did this by writing the second program, which just prints each reading number, and the two bytes of the reading, all tab seperated so they will go into seperate cells nicely. To calculate the temperature was simply a matter of
=(first cell)+(second cell/256). A little bit of good old graphing magic later, I got this:
Click to enlarge!
It is pretty clear that a one minute time resolution on a 12 bit temperature measurement is a little overkill. Most values are repeated for 10 minutes before drifting down another 1/16th of a degree to the next possible value. I've been playing with massaging the data after the fact to get a smoother line, but the logical next step is to just record the average reading for 5-15 minute periods to ink out two more bits, while saving a tremendous amount of EEPROM (45 days per chip to 1.8 years for 15 minute intervals). Note that the spikes in the afternoon were me playing with it by putting my finger on it and watching the temperature go up and fall back down to room temp.
Update 10/24/09: So after running the logger for a whole week, I've dumped the 10,000 some odd data points, and graphed them in Excel, just for your enjoyment (I'm lying, mine too).
Click to enlarge.
The ticks on the x axis are about where I believe midnight to be (I'm planning on patching the firmware to log a 0xFFFF temperature at midnight, since it knows what time it is). It's pretty clear that I left my window open over night two of the seven nights (Monday morning, Friday morning). What I did find surprising was how late it was before it started getting cooler. Most nights you can see it at least holding steady until I finally went to bed around 12:30am (Sunday night being the exception, due to the open window). On Thursday (second to last day) it's most dramatic, but when I leave for class, etc, the temperature markedly stops rising. I expected being home, awake, and active had an effect on my room's temperature, but certainly not to the degree shown on these graphs.
I also implemented a histogram feature on the last of the unused LCD space using the eight custom glyphs feature on the HD44780 controller. Essentially what I did was every hour (every half hour in the picture), stored the current temperature in an array of the last 40 (5 columns * 8 glyphs) such readings. I then calculated the maximum and minimum values for this set, and used that to normalize the values so they ranged between zero and eight, to calculate how many pixels high that column should be. The code ended up being pretty ugly, so I'll leave the specifics as a challenge for the student (don't you hate it when they say that?). The general idea is:
Bar height = (temperature - minimum temperature) * 8 / (maximum - minimum)
Storing the hourly temp readings in an array was just me being lazy. The I2C bus is pretty freakin fast, so the better solution would be to just read the values back off the EEPROM, since this only needs to be done once an hour, and that has the added advantage of not having an empty graph on reset.
Update 10/25/09: Data collected in my refrigerator.