In the last Arduino ‘How-To’, we built a display for temperature and humidity. We’ll now show you how to store this data on a SD memory card and record a time for each measurement. Thus, a shield for an SD memory card and a real time clock are added. The display can remain attached to the Grove shield. If you want to use the data logger without the display, you can simply unplug it.
[themen_sprungmarken]
The SD shield
Description and installation
The data logger saves data to a customary SD, SDHC or micro SD card (with adapter). For this, a shield that comes between the Arduino and the base shield is used – the base shield remains on top due to the sockets.
The libraries needed for the SD shield are already included in the Arduino studio and don’t need to be installed separately. The documentation can be found in the Arduino reference:
https://www.arduino.cc/en/Reference/SD
Writing data – hello world
The first data is written onto the card with the following code:
#include <SPI.h> #include <SD.h> int SELECTED_CHIP = 4; int count = 0; void setup() { Serial.begin(9600); if (SD.begin(SELECTED_CHIP)) { Serial.println("SD-Card initialized."); } else { Serial.println("SD-Card failed or is missing"); } } void loop() { String line = String("Hallo Welt #") + String(count); writeToSD(line); count = count + 1; delay(1000); } void writeToSD(String line) { File dataFile = SD.open("test.csv", FILE_WRITE); if (dataFile) { dataFile.println(line); // Write onto SD card dataFile.close(); // Close file Serial.println(line); } else { Serial.println("Error opening datafile"); } }
Firstly, two variables are defined: SELECTED_CHIP shows which hardware is used with the SD shield. The value is taken from the manufacturer of the SD shield. The variable count simply counts the consecutive writing processes to write the number onto the card.
In setup(), an initial serial connection is established to debug, followed by a connection to the SD card. The method SD.begin() feeds back if the connection was successfully established.
In loop() a line is built, which is subsequently written on the SD card by using writeToSD(). For this, we use a string class, which enables flexible working with strings of characters. The operator + connects two strings; thus, the variable count is firstly transformed into a string by using String(count). The value of count is raised by one, and waits for one second.
The writing happens in writeToSD(). Firstly, a file is opened or created (if not yet existing) by using SD.open() (important: the file name can be no longer than 8 characters). FILE_WRITE shows that a file should be opened as writeable. If the opening was successful, file then contains an object of the category file, with which we write the compounded line. Subsequently, be sure to close the file again. This ensures that the file doesn’t get damaged when the card is removed between two writing processes or in case of a power cut. To debug, enter the value from line also onto the serial interface.
If the opening process wasn’t successful (for example because the SC card was missing), then file remains empty and an error message is entered onto the serial interface.
The real time clock
Description and installation
Unlike a computer, the Arduino doesn’t contain an integrated clock as, for many applications, this isn’t required. For the data logger, we use the Grove-RTC_module, which contains a DS1307 impulse generator.
The real time clock requires an additional voltage source in the form of a 3V button cell (CR1225), to ensure that the set time remains even if the Arduino doesn’t have power. This means that the real time clock doesn’t function correctly without a battery, so it cannot be left out, even if the Ardunio is steadily connected to a power source. Communication happens over the I²C bus system, for which an arbitrary I²C connection can be used.
As with the display, a separate library needs to be installed for the real time clock:
Download: https://github.com/Seeed-Studio/RTC_DS1307/archive/master.zip
Documentation: http://wiki.seeed.cc/Grove-RTC/
Download the libraries and subsequently choose the following menus to install: “Sketch” → “integrate library” → “add ZIP libraries …”.
For all libraries, sample codes can be found in “file” → “examples”.
Set and read time
To set the time, a separate sketch instead of the setup() method is used in the final code as otherwise, the time would be reset with every new start of the Arduino.
#include <Wire.h> #include <DS1307.h> DS1307 clock; // create clock object void setup() { Serial.begin(9600); // Establish connection to clock clock.begin(); // Die Zeit einstellen clock.fillByYMD(2017, 9, 24); clock.fillByHMS(18, 53, 15); clock.fillDayOfWeek(SAT); clock.setTime(); } void loop() { printTime(); delay(1000); } void printTime() { clock.getTime(); // Ask time from chip Serial.print(clock.hour, DEC); Serial.print(":"); Serial.print(clock.minute, DEC); Serial.print(":"); Serial.print(clock.second, DEC); Serial.print(" | "); Serial.print(clock.dayOfMonth, DEC); Serial.print("."); Serial.print(clock.month, DEC); Serial.print("."); Serial.print(clock.year, DEC); Serial.println(); }
Firstly, the connection to the serial port is opened in setup(), so the time is sent to the computer for controlling purposes. With clock.begin(), a connection to the clock is then opened, date, time and day are prepared and subsequently sent to the real time clock with clock.setTime() (be sure to enter the actual time!)
In loop() the time is firstly issued and then waits for a second. In the Arduino studio, these values are shown in “Tools” → “Serial monitor”.
Important: Sending the code to the Arduino takes a few seconds, so it is tricky to set the exact time. For this usage, this doesn’t pose a big problem – a slight deviation of a few seconds doesn’t affect the course of the measured values when a value is tracked every few minutes.
The final code
After we’ve tested all parts again, the code from the last programme can be extended as follows:
#include <SPI.h> #include <SD.h> #include <Wire.h> #include <DS1307.h> #include <DHT.h> #include <rgb_lcd.h> DS1307 clock; DHT dht(A0, DHT22); rgb_lcd lcd; byte DEGREE_SYMBOL = 0; byte degree[8] = { 0b00000, 0b00010, 0b00101, 0b00010, 0b00000, 0b00000, 0b00000, 0b00000 }; unsigned long DISPLAY_UPDATE_INTERVAL = 200; // 200 milli seconds unsigned long WRITE_INTERVAL = 60000; // 1 minutes unsigned long lastDisplayUpdate = DISPLAY_UPDATE_INTERVAL; unsigned long lastWrite = WRITE_INTERVAL; void setup() { Serial.begin(9600); // Initialise serial interface // Initialise SD card int selectedChip = 4; if (!SD.begin(selectedChip)) { Serial.println("SD-Card failed or is missing"); } else { Serial.println("SD-Card initialized."); } dht.begin(); // Establish connection to sensor clock.begin(); // Establish connection to real time clock lcd.begin(16, 2); // Establish connection to display and initialise display - 2 lines with 16 characters respectively lcd.createChar(DEGREE_SYMBOL, degree); // Register the new "°" symbol with the display } void loop() { float humidity = dht.readHumidity(); float temperature = dht.readTemperature(); if (isnan(temperature) || isnan(humidity)) { Serial.println("Failed to read from DHT"); return; // No data --> leave loop() at this point } if (millis() - lastDisplayUpdate > DISPLAY_UPDATE_INTERVAL) { const float coldest = 18; const float hottest = 30; int red = limit(255 * (temperature - coldest) / (hottest - coldest)); // Farben von blau über lila nach rot int green = 0; int blue = limit(255 * (hottest - temperature) / (hottest - coldest)); updateDisplay(red, green, blue, temperature, humidity); lastDisplayUpdate = millis(); } if (millis() - lastWrite > WRITE_INTERVAL) { String line = String(getTime()) + ";" + String(temperature) + ";" + String(humidity); writeToSD(line); lastWrite = millis(); } } String getTime() { clock.getTime(); // Ask time from chip String t = String(clock.dayOfMonth, DEC); t += String("."); t += String(clock.month); t += String("."); t += String(clock.year); t += String(" "); t += String(clock.hour); t += ":"; t += String(clock.minute); t += ":"; t += String(clock.second); return t; } void writeToSD(String line) { File dataFile = SD.open("datalog.csv", FILE_WRITE); if (dataFile) { dataFile.println(line); // Write onto the SD card dataFile.close(); Serial.println(line); // Additionally, write onto serial interface to debug } else { Serial.println("Error opening datafile"); } } void updateDisplay(int red, int green, int blue, float temperature, float humidity) { lcd.setRGB(red, green, blue); lcd.setCursor(0, 0); lcd.print("T: "); lcd.print(temperature); lcd.write(DEGREE_SYMBOL); lcd.print("C"); lcd.setCursor(0, 1); lcd.print("H: "); lcd.print(humidity); lcd.print("%"); } float limit(float color) { // colour values need to be in the area between 0..255 if (color < 0) { return 0; } else if (color > 255) { return 255; } else { return color; } }
New with this code is that there are two processes with different clock rates. The display should be updated several times in a second for a current display and flowing colour transitions. For the writing of the measured values onto the card, a minute interval is sufficient. Simple breaks with delay() are thus no longer possible. The solution: with the operation millis(), past milliseconds since the start of the Arduino can be shown. In lastDisplayUpdate and lastWrite, the time of the last display refresh or writing onto the SD card are recorded. With every run through of loop(), it is evaluated whether the difference between these values to millis() is bigger than the one from DISPLAY_UPDATE_INTERVAL or WRITE_INTERVAL. Only if so, the respective actions are performed. The variables lastWrite and lastDisplayUpdate are initialised with the length of the respective interval, so both actions are immediately carried out with the start of the programme.
Finally, the values are taken from the sensor and tested in loop(). If a problem comes up during this process, an error message is displayed and the execution of loop() is stopped at this point and newly started (i.e. values are again taken from the sensor).
Subsequently, it is tested if a refresh of the display is necessary. This is the case if the difference from the current time and lastDisplayUpdate is bigger than DISPLAY_UPDATE_INTERVAL. Then, the colour is determined and the data is sent to the display. For reasons of legibility, we now have the method updateDisplay(). Lastly, lastDisplayUpdate is set to the current time.
A similar process happens for writing data onto the SD card. If the time of the last writing process is further in the past than WRITE_INTERVAL, the current time is taken from the real time clock, the data is written onto the SC card and lastWrite is set to the current time.
The time is determined in the method getTime(), which takes the time from the real time clock and creates a string with date and time. The operator += adds the value of the right string to the left one.
Analysis of data
Data is written in a file with the name datalog.csv and can be imported to Excel, LibreOffice Calc or Google Spreadsheets for analysis.
Simply remove the SD card from the Arduino and read out from the computer.
Have fun!