Introduction: DIY Logging Thermometer
I wanted to make this project to learn how to integrate several modules into a working model; and also I have a practical need for monitoring temperatures in various places. The most recent need is to monitor a chicken brooder containing about 20 very young chickens (they started off as day old chicks a couple of weeks ago).
In this Instructable I will describe the hardware components, the assembly, and the Arduino software. It was a big learning experience, especially the software. More on that later.
The main capabilities of this project are:
- measuring temperature with a probe that can be positioned at the end of a about a 1 metre long wire
- a clock funtion that provides the current date and time to the nearest second
- an LCD display for date/time and temperature
- logging the date/time and temperature to an SD card using a simple file format that can be used for processing with a personal computer spreadsheet
The logged data is recorded in a series of day files - that is, one file per day. Each file starts with a brief header and then multiple rows of data. Each row of data consists of two entries, first the date/time field and second the temperature in centi-degrees Celsius (eg 25 C is recorded as 2500 centi-degrees). Each day file has a unique file name and is stored in the root directory of the SD card. The file name structure is in the form "logYYDDD.txt" where YY is the last two digits of the year (eg "16" for 2016) and DDD is a day code calculated by DDD=68+MM*32+DD (MM is the month No. and DD is the day of the month).
Currently I am recording one log file entry per minute, but this can be changed easily in the relevant constant declarations near the beginning of the software, if required. Each line in the file occupies 26 bytes (including the new line codes) so 1 line per minute means each file contains 1440 lines of data. This can easily be graphed with a spreadsheet. A complete day file is about 38 kbytes, so there are about 26 files per megabyte and a 4 Gbyte SD card can hold files for 100,000 days - which seems ample to me.
The SD card can be removed from the logging thermometer for up to 2 hours (at 1 minute logging intervals) which should give enough time to copy the day files onto a PC and replace it. While the SD card is not in the logging thermometer, the logging data is stored in a circular buffer in EEPROM on the RTC module. The circular buffer has room for 127 records. When the SD card is put back, the data from the circular buffer is written to the end of the current day file, and then logging continues. This is described in more detail in Step 5. While data is being written to the SD card, the LCD screen changes to indicate that the SD is being written (and gives the file name); this enables the user to avoid removing the SD card while it is being written, which would corrupt the day file.
This project does not pay particular attention to power consumption. The backlight for the LCD is on all the time, and none of the devices are put to sleep at any time. My intention is to power it from the mains electricity supply through a 12V mains adapter ("wall wart"). However it would be quite feasible to power it from a small solar panel and a 12V battery. With more development, a low power version suitable for use with AA batteries should be posible but that is not part of this project.
Also I have not built this project into a box. This would be possible but I wanted a quick result so I have not made the physical layout with a box in mind.
If you are interested in adding a second temperature sensor to this project, please see my later project "DIY logging thermometer with 2 sensors".
Step 1: Parts
Arduino Nano (first photo)
Temperature sensor DS18B20 (see next step for photo)
Tiny Real Time Clock module (includes 32 kbit EEPROM) (photo 2 and 3)
SD read/write module (photo 4 and 5). Note that although the main IC is a 3V3 component, the module includes a power regulator and data interface drives that allow connection to either 3V3 or 5V environments.
1602 LCD display screen (photo 6)
Adjustable DC-DC buck converter (this one is based on the MP1584 IC) (photo 7)
8 cm * 12 cm double sided prototype circuit board (photo 8)
Power connector, wire, 3 resistors, male and female header strips, various screws and nuts
Hardware Tools - pliers, tweezers, soldering station, wire stripper, cordless drill and drill bits 3mm, 2mm, 1.5mm, 0.7 mm)
Software tools - Aduino IDE (I used version 1.6.12); Internet to research various problems.
Data processing tools - personal computer with micro-SD adapter and a spreadsheet application (I am using a T420 laptop running Manjaro Linux and Libre Office Calc, but any other combination will probably be OK).
Step 2: Connections
The phyical layout can be seen in the photos in the introduction, and also in photo 3 above. I positioned the modules roughly where I wanted them to go, and drew a hand sketch showing their positions. Then I soldered and/or screwed them in position - see details below. After all the pieces were in place, I ran the wires to make the required connections.
Working out the connections to the Arduino Nano was the main task.
The SD module uses the SPI interface, which uses 4 wires of which 3 are fixed as D11, D12 and D13. The fourth wire can go to any pin configured as an output.
The RTC module uses the I2C interface, which uses 2 wires which are fixed as A4 and A5.
The LCD uses 7 wires which can be any of the Arduino pins. Preferably the pin that controls the backlight should be a PWM pin, so that the backlight can be dimmed and not just ON or OFF.
The temperature sensor DS18B20 uses one wire which can be any Arduino pin. The RTC module has a position for a DS18B20 sensor, which can use the same Arduino pin as the external sensor.
I installed the 15-pin headers (supplied) on each side of the Nano, positioned it on the prototype board and soldered the corner pins. The other pins were soldered as I made connections.
D0, D1 - not used (reserved for USB)
D2 - DS18B20 temperature sensor(s)
D3 - SPI Chip Select for the SD card module
D4 to D7 - LCD data;
D8 - LCD RS pin (register select function)
D9 - LCD Enable pin
D10 - LCD backlight control (via 220 ohm resistor)
D11, D12, D13 - SPI to SD module, MOSI. MISO and SCK
A4, A5 - I2C to RTC, SDA and SCL
In addition to the above connections, there were a few more (not very many).
I installed the DC-DC buck converter using 4 pieces of male header, each 2 pins, one on each corner.
I connected the input to the DC-DC buck converter to the power connector which I mounted on a small bracket nearby. Then I connected a 12V plug pack (wall wart) to the power connector, and adjusted the pot on the buck converter until it was putting out 5 volts.
After that I connected Ground and +5V to each module and to a 5-pin male header which I am using as a connector for the temperature sensor. Adjusting the output voltage before connecting to the other modules makes sure these modules are not exposed to excessive voltage.
Usually a 10K pot is specified for the LCD contrast pin V0, However I found that connecting a 3K3 resistor from V0 to ground produced a very acceptable contrast level. The resultant voltage on V0 is about 1.0 volts (4V below VDD).
The LCD backlight consists of 2 white light emitting diodes. On the LCD board, the anode end is marked A and the cathode end is marked K. I wired K to ground, and connected a 220 ohm resistor to A and from that ran a wire to pin D10.
To make the :LCD display work, it is also necessary to connect the RW pin to ground.
SD module connections
This module comes with 2mm holes in two corners. I did not have any short M2 screws, so I used M1.4 * 8 mm screws which I had to hand.
This module is supplied with a 6-pin male right-angle header on the top side of the board. This was not suitable for my layout. I removed the supplied header (by cutting it into 6 pieces with my side cutters and un-soldering each pin individually). Then I installe a 6-pin male straight header on the underside of the module; this provides mechanical support as well as access for wires via the circuit board.
Connections are SPI and power as above, including Chip Select to Arduino D3.
I added two short lengths of male header pins (5 and 7 pins) to support the module, and also used two M3 * 10mm screws to hold it in place.
I2C and power as above.
Temperature sensor connections
I used a 5-pin section of male header on the main board, and a matching 5-pin section of female header on the device. The male header on the board was wired as follows: Ground, +5V, D2 (data), +5V, Ground. I also put in a 4K7 resistor between the middle lead (D2) and Earth. If D2 is wired to the RTC DS connection, this resistor is not necessary as the RTC has a 3K3 resistor from this pin to +5V.
The female header on the device was wired: Black, Red, Yellow, Nil, Nil. Using 5 pins wired like this means the device can be plugged in either way round and still work correctly.
Step 3: Software - Overview
I already had another project going using the thermometer DS18B20 and a serial 2004 LCD, so I used that as a base and added to it. I needed to change the LCD to 1602 and make it directly connected, and add the Real time clock, and the SD card software. I had some struggles setting up so that the SD card can be removed and replaced without disrupting the logging, however that has been solved. I will cover that in another step later on.
I don't regard myself as a software expert. I have tried to provide comments and markers in the source code to make it easier for other people to find particular functions. There are also many Serial.print () statements which could be removed. I have left them there as they may help readers figure out what the software is doing.
The complete software "as is" can be expected to work provided the pin numbers are kept the same. Please let me know of any problems.
When compiled with IDE version 1.6.12 the software uses 22,254 bytes of program memory (72%) and global variables use 1262 bytes of dynamic memory (61%) on the 328P processor of the Nano. Although there is room for more software, care needs to be taken with global memory use. I experienced some "instability" problems before I removed all the text used for debugging print lines using the macro F() - eg Serial.print (F("Debug line")).
The declarations and setup parts of the sketch are structured into sections for each of the modules, which hopefully will make them easier to understand.
The loop routine runs 4 main steps. The first step is the get_temperature routine which is a 4-stage state machine. The steps are to command the sensor to measure or "convert") the temperature; wait for it to do so; request the return of the temperature data and process it; and then aother wait step before starting the next conversion. I have set both wait stages at 1 second (1000 ms using millis()), so the sensor is producing a new temperature value about every 2 seconds. These wait stages are controlled by constants declared in the declarations area.
The second step in LOOP runs once per second and gets the time from the DS1307 real time clock. This is a fixed interval, since the SD logging depends on having an up to date time so this should not be changed.
The third step in LOOP displays date, time and temperature data on the LCD. It runs once per second also, but this interval can be configured through a constant in the declarations section. A separate piece of code in the SD functions displays a warning message when the SD card is being written to.
The fourth (and last) step in LOOP performs the SD card logging functions, including using the EEPROM in the RTC if the SD card is removed. This routine runs once per minute. The interval can be configured to be any whole number of seconds, up to a full day.
Step 4: Software - Temperature Sensor
The 4-state state machine used for getting the temperature uses an enumerated variable for the states.
I wanted to achieve "plug and play" functionality for the temperature sensor. That is, the project should work correctly if the sensor is removed and then replaced, either with the same one or a different one. To achieve this, I found it necessary to include the "Search" step in every temperature measurement cycle.
If the temperature sensor is unplugged, while it is out the LCD display and the logging file both show the text "FAILED" instead of the temperature value.
The DS18B20 sensor delivers the temperature in two bytes, a 16 bit number with a granularity of 1/16 degrees Celsius. For example, a temperature of 10 degrees C is delivered as the value 160. To convert this to Celsius first multiply by 6.25 (160 * 6.25 = 1,000) which is the temperature in centi-degrees (ie hundredths of a degree). This arithmetic can be done easily in the software using integers, so there are no floating point variables used.
Step 5: Software - SD Card and EEPROM
This part of the software was the most challenging. The most tricky part was to find a way of detecting when the SD card has been removed, and later when it has been replaced. Once I had cracked that problem, I found it quite simple to write code for a circular buffer in the EEPROM of the RTC. The battery-backed memory of the RTC itself is used to store the Read and Write pointers used for the circular buffer, so that data stored in the EEPROM can be recovered even after a power outage or reset.
I have described the file structure used on the SD card in the introduction, so I will not go over that again here.
Detecting SD card inserted / not inserted
The SD library provides a function sd.exists(filename) which on the surface would seem to answer this question. The description on the Arduino.cc reference page says "Returns: true if the file or directory exists, false if not". However it is not so easy. Running this function just after startup returns exactly as it says. That is, if the SD card is in, and the file exists, the return is True. If the SD card is in and the file does not exist, or if the SD card is not in, the return is False.
However, if after running this function and getting a True result, then removing the SD card, the function continues to return True, even though the SD card is absent. There are many queries and answers in several different forums on thte internet about this. I believe it happens because there is a cache (somewhere) that is read by this function, and if the cache contains a result it is not updated.
Similarly sd.begin(cspin) and sd.open(filename, mode) are also cached and do not provide information about whether the SD card is inserted or not, after an initial success.
Breakthrough! The various file write functions, such as println(data), return the number of bytes written to the file. AND, if the SD card is inserted, the return is non-zero, but if the SD card is not inserted, the return is zero.
So, to check if the SD card is inserted or not, I open a test file, write a small amount of data to it, and check the return. If it is zero, the SD card is out; if the return is non-zero, the SD card is in and working. Then I close and remove the test file.
Circular buffer in EEPROM
When the SD card is removed, I wanted to continue gathering data. My data records are 24 bytes long (this could be shortened if a less verbose date and time format was used). My calculations are based on allowing for 32 byte records, to allow for future changes.
There are three possible places (maybe four) that could be used as a backup store while the SD card is out. They are:
Arduino EEPROM (328P has 1 k bytes, which will hold 32 records of 32 bytes each). Easily accessed using the library EEPROMex.h.
Arduino program memory (I have never tried using this but it may be possible using the PROGMEM command).
RTC DS1307 has 56 bytes of battery-backed up memory, so it can store 2 records (not really enough to be interesting)
The RTC module, in addition to the DS1307, has an AT24C32 chip which provides 32 kbits (4 k bytes) of storage. Perfect! This will hold 128 records of 32 bytes, enough for more than 2 hours at 1 record per minute. I decided to use this.
I found a libary on the internet, AT24CX.h written by Christian Paul dated 2014-11-24. Thank you Christian! It works well for what I needed. I also found various web sites giving advice about circular buffers.
Following the recipe for a read-pointer, write-pointer circular buffer, and using the AT24CX library, I wrote some software and it worked! Great result.
For simplicity, with capacity for 128 records, I am only storing a maximum of 127 records. This makes the logic for working out buffer empty (read pointer = write pointer) and buffer full (write pointer + 1 = read pointer) simpler. To use the last record is possible, but requires either a "flip flag" or a different way of managing the pointers.
This circular buffer is set up so that once it is full, new data is discarded. The alternative is to overwrite the oldest data and keep the new data. Either way is easy to do.
I stored the read and write pointers in the battery-backed memory of the DS1307 RTC.
Responding to SD card re-inserted
In order to detect when the SD card is re-inserted, every time a record is written to EEPROM the function sd.begin(cspin) is called. This always returns True, whether or not the SD card is inserted. However if this is not done, the next attempt to write the test file will fail. Hence, it is necessary to call this function regularly while the SD card is out.
After the SD card is re-inserted, it is discovered after the next record is written to the EEPROM (ie it could be a minute later). When the following record is ready for writing, the data in EEPROM first needs to be written to the SD card, followed by the new data.
I found that writing up to 127 reccords to the SD card took about 10 seconds, so the first new data record was delayed by a variable amount of time. My solution to this is that when the SD card is re-inserted and it is time to write new record, I write just one record from the EEPROM to the SD card (making room if the EEPROM is full), then write the new record to the EEPROM, then write the remainder of the EEPROM records (including the new record of course) to the SD card. In this way the regularity of the data records is not disturbed even by a long write sequence.
Day file create/modify date and time
Originally my software did not set the file creation or modify date and time. A little internet research discovered how to do it. It requires an additional subroutine called dateTime which I put in the "Global for SD card" section, and in the SD part of SETUP it needs the line
So this turned out to be quite easy. Just how it works I am not sure. Thanks to the many people in various forums who have worked on this topic.
Step 6: Post-processing the Data
Here is a sample of a day file, and a graph produced from it using a few mouse clicks.
This is the temperature inside the heated chicken brooder box.
Note that you can easily work out the date from the day file name. For example, in this case:
LOG - fixed header
16 - this year, 2016
436 - subtract 68, result is 368. Now divide by 32, result is 11.5. Month 11 is November. Now multiply the decimal fraction by 32. That is, 0.5 * 32 = 16 - the day in November. So this file is for 16 November 2016.
Step 7: Software Tools for RTC and SD Card Module
Setting the time on the Real Time Clock
I found several versions via the internet. I ended up using this one, RTC_set that I wrote myself based on several of the other versions. It is very basic. You need to adjust the date and time values for your current time, plus a bit, then upload it to the Arduino a few seconds before the time set in the sketch.
After that, if the time shows OK, you can comment out the Setup section and just use it if needed to give you the time.
Checking for devices on the I2C bus
This address finder sketch, I2C_Scanner.ino, is useful for checking that you have communication to the I2C devices you expect. In this project, the RTC carries the DS1307 clock and also the AT24C32 EEPROM, so there are 2 addresses reported.
Display SD card information and directory of files
The tool SDcard_dir.ino enables you to see the folders and files on the SD card, without having to take it out and mount it on your PC.
Display the contents of an SD card file
The tool SDcard_Read_File.ino lists the contents of an SD card file. I found it useful in the development stage, being a lot quicker than changing it across to the PC.
To use it, you need to hard code the file name into the program.