Hello. Over the last few months I have been developing a datalogger. The datalogger consists of four main components:
- An Arduino Mega 2560 Rev 3
- A SD Card Shield (Adafruit Assembled Data Logging shield for Arduino : ID 1141 : $13.95 : Adafruit Industries, Unique & fun DIY electronics and kits)
- A DS3231 RTC (Adafruit DS3231 Precision RTC Breakout : ID 3013 : $17.50 : Adafruit Industries, Unique & fun DIY electronics and kits)
- An ADS1115 ADC (ADS1115 16-Bit ADC - 4 Channel with Programmable Gain Amplifier [STEMMA QT / Qwiic] : ID 1085 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits)
Here is the code I wrote for the datalogger:
#include <Adafruit_ADS1X15.h>
#include "RTClib.h"
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
// The following define your basic variables: ---------------------------------------------------------------------------------------
#define LOG_INTERVAL 10000 // mills between entries (set to 10,000 for 10 seconds)
#define SYNC_INTERVAL 10000 // mills between calls to "flush()" (write data to the card)
uint32_t syncTime = 0; // time of last "sync()"
#define LED1 2 // Make LED1 Controlled by Pin 2 on the Arduino
#define LED2 3 // Make LED2 Controlled by Pin 3 on the Arduino
const int chipSelect = 10; // The Data Logging Shield uses Pin 10 to communicate with the SD Card
const int ADS1115_ADDRESS = 0x48; // Set the I2C address of the ADS1115 ADC (Wire.h allows for multiple devices to be connected to the same SDA/SCL pins)
const int RTC_ADDRESS = 0x68; // Set the I2C address of the DS3231 RTC
Adafruit_ADS1115 adc; // Create an Object for the ADC
RTC_DS3231 rtc; // Create an Object for the RTC
File logfile; // Create the Variable for the File data type
// Enum to represent the four different error types----------------------------------------------------------------------------------
enum ErrorType {
ERROR_RTC,
ERROR_ADC,
ERROR_SD_CARD,
ERROR_OPEN_FILE
};
// These are the three functions that will be called upon continuously in the loop---------------------------------------------------
// They check to ensure that the RTC, ADC, and SD Card are still connected/working properly
bool isRTCConnected() {
return rtc.begin(); // Try to initialize the RTC and return the success status
}
bool isADCConnected() {
return adc.begin(ADS1115_ADDRESS); // Try to initialize the ADC and return the success status
}
bool isSDCardConnected() {
return SD.begin(chipSelect); // Try to initialize the SD card and return the success status
}
// Used for flashing LEDS for a way of user debugging--------------------------------------------------------------------------------
void flashLED(int pin, int flashes, int onTime, int offTime) { // This sets up a function to flash the debugging LEDs using the: pin, number of flashes, on time (ms), and off time (ms)
for (int i = 0; i < flashes; i++) { // This creates the loop. For "i" amount (the number of flashes):
digitalWrite(pin, HIGH); // Turn on the LED at the pin
delay(onTime); // For however many milliseconds
digitalWrite(pin, LOW); // Turn off the LED at the pin
delay(offTime); // For however many milliseconds
}
}
// The following section is created for debugging purposes---------------------------------------------------------------------------
void error(char *str, ErrorType errorType) {
digitalWrite(LED1, LOW);
Serial.println();
Serial.print("ERROR: ");
Serial.println(str);
while (!isRTCConnected() || !isADCConnected() || !isSDCardConnected() || errorType == ERROR_OPEN_FILE) {
switch (errorType) {
case ERROR_RTC:
flashLED(LED2, 3, 200, 200);
delay(5000);
break;
case ERROR_ADC:
flashLED(LED2, 6, 200, 200);
delay(5000);
break;
case ERROR_SD_CARD:
flashLED(LED2, 3, 500, 500);
delay(5000);
break;
case ERROR_OPEN_FILE:
flashLED(LED2, 6, 500, 500);
delay(5000);
break;
default:
break;
}
}
}
// Create the file's date&time signature---------------------------------------------------------------------------------------------
void dateTime(uint16_t* date, uint16_t* time) { // This function is used to set the date of the file in your file explorer
DateTime now = rtc.now(); // This initializes the RTC
*date = FAT_DATE(now.year(), now.month(), now.day()); // This returns the date using FAT_DATE macro to format fields
*time = FAT_TIME(now.hour(), now.minute(), now.second()); // This returns the time using FAT_TIME macro to format fields
}
// These are your initial setup parameters-------------------------------------------------------------------------------------------
void setup(void)
{
Serial.begin(9600); // Begins Serial Communication b/t Arduino and Computer
Serial.println(); // Prints a new line
// Set-up the Debugging LEDs
pinMode(LED1, OUTPUT); // Makes the pin that LED1 is connected to an output
pinMode(LED2, OUTPUT); // Makes the pin that LED2 is connected to an output
digitalWrite(LED1, HIGH); // Turns on LED1 to indicate the code is being executed with no problems
Wire.begin(); // Calls upon the Wire.h library to initialize
rtc.begin(); // Initialize the DS3231 RTC
if (!rtc.begin()) { // If initializing the RTC fails:
error("RTC failed", ERROR_RTC); // This defines what will be typed on screen, along with what error type will be called
}
// Un-Comment the following Lines to Adjust the Time on the RTC (then comment them out again once it has been adjusted!)
//DateTime adjustedTime(2023, 7, 6, 8, 22, 00); // Set the desired date and time
//rtc.adjust(adjustedTime); // Adjust the RTC time
adc.begin(ADS1115_ADDRESS); // Initialize the ADS1115 ADC with the desired I2C address
if (!adc.begin(ADS1115_ADDRESS)) { // If initializing the ADC fails:
error("ADC failed", ERROR_ADC); // This defines what will be typed on screen, along with what error type will be called
}
// Initialize the SD Card
Serial.print("Initializing SD card...");
pinMode(10, OUTPUT); // Pin 10 is used to communicate with the SD Card
if (!SD.begin(chipSelect)) { // If the SD Card cannot be connected to:
error("Card failed or not present", ERROR_SD_CARD); // This defines what will be typed on screen, along with what error type will be called
}
Serial.println("card initialized.");
// Create a New File
char filename[13]; // Buffer to hold the filename (8 characters for "YY-MM-DD", 1 for '.', 3 for "CSV", and 1 for null terminator)
DateTime now = rtc.now(); // Sets the DateTime variable to the current time
sprintf(filename, "%02d-%02d-%02d.CSV", now.year() % 100, now.month(), now.day()); // Makes the name of the file "YY-MM-DD"
SdFile::dateTimeCallback(dateTime); // This is used to set the date of the file in your file explorer
// Open the logfile with UTF-8 encoding. This is necessary so that there is not problem with the "°" Symbol
logfile = SD.open(filename, FILE_WRITE | O_WRITE | O_CREAT | O_APPEND);
if (!logfile) { // If there is a problem with opening the file:
error("couldnt create file", ERROR_OPEN_FILE); // This defines what will be typed on screen, along with what error type will be called
}
logfile.print("\xEF\xBB\xBF"); // Write UTF-8 BOM to indicate the encoding
Serial.print("Logging to: ");
Serial.println(filename);
// Output the Labels
Serial.println("Date Time,Oven 46 (V),Oven 47 (V),Oven 48 (V),Oven 49 (V),Oven 46 (°C),Oven 47 (°C),Oven 48 (°C),Oven 49 (°C)");
logfile.println("Date Time,Oven 46 (V),Oven 47 (V),Oven 48 (V),Oven 49 (V),Oven 46 (°C),Oven 47 (°C),Oven 48 (°C),Oven 49 (°C)");
}
// The following is what loops over and over again; in this case, collecting and writing data----------------------------------------
void loop()
{
if (!isRTCConnected()) {
error("RTC failed", ERROR_RTC);
}
if (!isADCConnected()) {
error("ADC failed", ERROR_ADC);
}
if (!isSDCardConnected()) {
error("Card failed or not present", ERROR_SD_CARD);
}
digitalWrite(LED1, HIGH); // Turns on LED1 to indicate the code is executing with no problems
// Read the Current Time from the RTC
DateTime now = rtc.now();
// Convert Date and Time Values to Strings with Leading Zeros
String yearString = now.year() < 10 ? "0" + String(now.year()) : String(now.year());
String monthString = now.month() < 10 ? "0" + String(now.month()) : String(now.month());
String dayString = now.day() < 10 ? "0" + String(now.day()) : String(now.day());
String hourString = now.hour() < 10 ? "0" + String(now.hour()) : String(now.hour());
String minuteString = now.minute() < 10 ? "0" + String(now.minute()) : String(now.minute());
String secondString = now.second() < 10 ? "0" + String(now.second()) : String(now.second());
String ymdhms = yearString + "-" + monthString + "-" + dayString + " " + hourString + ":" + minuteString + ":" + secondString;
// Use an Array to Store Voltage and Temperature Values for All 4 Channels
float voltages[4];
float temperatures[4];
// Loop Through all 4 Channels, Read the Voltages, Convert it to Temperature
for (uint8_t channel = 0; channel < 4; channel++)
{
int16_t adcValue = adc.readADC_SingleEnded(channel); // Read from the current channel
float voltage = adcValue * 0.0001875; // Convert raw value to voltage (6.144V / (2^15bits))
voltages[channel] = voltage; // Sets the voltage to the voltage for each respective channel
float temperature = voltage * (1800 / 5); // Convert the voltage to a temperature (0-5v = 0-1800°C type of scale)
temperatures[channel] = temperature; // Sets the temperature to the temperature for each respective channel
}
// Serial Print data
Serial.print(ymdhms);
Serial.print(",");
for (uint8_t i=0; i<4; i++)
{
Serial.print(voltages[i], 3);
Serial.print(",");
}
for (uint8_t i=0; i<4; i++)
{
Serial.print(temperatures[i], 0);
Serial.print(",");
}
Serial.println();
// Log data to SD Card
logfile.print(ymdhms);
logfile.print(",");
for (uint8_t i=0; i<4; i++)
{
logfile.print(voltages[i], 3);
logfile.print(",");
}
for (uint8_t i=0; i<4; i++)
{
logfile.print(temperatures[i], 0);
logfile.print(",");
}
logfile.println();
// Delay for the amount of time we want between readings
delay((LOG_INTERVAL -1) - (millis() % LOG_INTERVAL));
// Check Passage of Time
if ((millis() - syncTime) < SYNC_INTERVAL) return;
syncTime = millis();
// Turn On Red LED, Write Data to SD Card, Turn Off Red LED
digitalWrite(LED2, HIGH);
delay(500);
logfile.flush();
delay(500);
digitalWrite(LED2, LOW);
}
As you can see, I tried to implement debugging methods into the code wherever possible. I am new to coding, so I will take any advice given.
I have been running into an error over the past several times using the data logger. The datalogger will successfully start and run for hours at a time, even multiple days at the beginning. However, after a while, the datalogger will simply stop executing the code. The first couple of times using the datalogger, it ran for days on end, successfully writing data. However, after the third day when I checked on it, the datalogger would be sitting there idle showing no error codes. Most recently, the datalogger will only run for a couple of hours before it stops executing the code and sits there idle.
During my most recent datalogging session, it ran for just over 2 hours and 10 minutes. I had it connected to my laptop so that I can record the log through the Arduino serial monitor. It ran successfully, until something happened. It stopped writing the date and time, but still wrote the voltages and temperatures. Then after about 2 minutes, it stopped working completely. Here is the data I got from this most recent session:
Initializing SD card...card initialized.
Logging to: 23-08-30.CSV
Date Time,Oven 46 (V),Oven 47 (V),Oven 48 (V),Oven 49 (V),Oven 46 (°C),Oven 47 (°C),Oven 48 (°C),Oven 49 (°C)
2023-08-30 14:26:46,3.317,0.864,0.851,0.542,1194,311,306,195,
2023-08-30 14:26:56,3.313,0.860,0.851,0.541,1193,310,306,195,
2023-08-30 14:27:07,3.321,0.862,0.852,0.527,1195,310,307,190,
2023-08-30 14:27:17,3.314,0.859,0.849,0.538,1193,309,306,194,... (all the data between was perfect) ...
2023-08-30 16:38:17,4.258,2.365,0.851,0.645,1533,851,306,232,
2023-08-30 16:38:27,4.259,2.367,0.851,0.647,1533,852,306,233,
2023-08-30 16:38:36,4.261,2.370,0.851,0.638,1534,853,307,230,
2023-08-30 16:38:47,4.263,2.370,0.851,0.647,1535,853,306,233,
,4.262,2.372,0.851,0.650,1534,854,306,234,
,4.266,2.372,0.851,0.659,1536,854,306,237,
17,4.267,2.376,0.851,0.658,1536,855,306,237,
,4.266,2.378,0.851,0.660,1536,856,306,238,
,4.269,2.381,0.851,0.657,1537,857,306,237,
,4.268,2.382,0.851,0.665,1536,857,306,239,
,4.270,2.384,0.850,0.651,1537,858,306,234,
,4.272,2.384,0.851,0.658,1538,858,306,237,
,4.273,2.388,0.851,0.663,1538,860,306,239,
,4.274,2.390,0.851,0.665,1538,861,306,239,
,4.275,2.391,0.851,0.664,1539,861,306,239,
(end of data)
From 14:26:46 (HH:MM:SS) to 16:38:47, the data was perfect. Then, for two data points, it did not write the date and time at all. Then, interestingly enough, the next data point wrote the seconds value ("17"), along with all of the voltages and temperatures. After that, it continued to only write voltages and temperatures for 8 more data points until it simply stopped recording any more data. It never output an error.
So, my question for you all is what do you think is happening? My first thought is that it is a malfunction with the RTC, but it continuously initialized with it without failure, otherwise it should have output an error.
As an added bonus, the SD Card's data became corrupted. It was not able to be read by my computer; I had to format the card after I turned off the Arduino and unplugged it.
Let me know what you all think. Thanks so much.