I'm creating a data logger and diagnostics device for temperature and the float state in a water tank. I made the averaging code a while ago, which should be fine:
(the cumulative sum of all readings divided by the number of samples taken).
This should be fine, but I'm having trouble with the data on the serial monitor output. This is an example of the serial data:
Time: 14:48:22 | Runtime: 00d 00h 00m 01s | Sample: 1 at 1.00s | Sw: 1 | temp(°C): 21.44 | Ave: 21.44 | Tot: 21.44
Time: 14:48:23 | Runtime: 00d 00h 00m 02s | Sample: 2 at 2.00s | Sw: 1 | temp(°C): 21.44 | Ave: 21.44 | Tot: 42.88
Time: 14:48:24 | Runtime: 00d 00h 00m 03s | Sample: 3 at 3.00s | Sw: 1 | temp(°C): 21.37 | Ave: 21.44 | Tot: 64.31
Time: 14:48:25 | Runtime: 00d 00h 00m 05s | Sample: 4 at 5.00s | Sw: 1 | temp(°C): 21.44 | Ave: 21.44 | Tot: 85.69
Time: 14:48:27 | Runtime: 00d 00h 00m 06s | Sample: 5 at 6.00s | Sw: 1 | temp(°C): 21.44 | Ave: 21.42 | Tot: 107.12
Time: 14:48:28 | Runtime: 00d 00h 00m 07s | Sample: 6 at 7.00s | Sw: 1 | temp(°C): 21.44 | Ave: 21.42 | Tot: 128.56
Time: 14:48:29 | Runtime: 00d 00h 00m 08s | Sample: 7 at 8.00s | Sw: 1 | temp(°C): 21.37 | Ave: 21.43 | Tot: 150.00
Time: 14:48:31 | Runtime: 00d 00h 00m 10s | Sample: 8 at 10.00s | Sw: 1 | temp(°C): 21.44 | Ave: 21.43 | Tot: 171.37
LOG NO.1
| Time(s) | Float Switch State | Average Temperature(°C) |
| 53311 | 1 | 21.43 |
As you can see, the current temperature, average, and total do not match up.
I'm using 12-bit resolution and only 2 decimal places, but that only makes my readings slightly inaccurate due to rounding, which is not the issue.
I've been trying to mess about with this for a while but I'm losing my patience with trying to find the answer.
The code is 700 lines long, but the code for sample averaging is in the datalog and diagnostics function, near the end. This is my code:
#include <Triogen.h>
/* _______________________________________________________________________________________________________________________________________________________________________
ㅤㅤ /\ \ / /\
ㅤ ㅤ|_\ \ DATALOGGER / /_|
ㅤㅤ |__\ \________________________________ 20x4 LCD, SD ADAPTER, DS3231, DS18B20 (5.1kPU), FLOAT SWITCH ________________________________/ /__|
ㅤㅤ |___\ \____________________________________________________________________/ /___|
ㅤㅤ |____\ /____|
ㅤ ㅤ|_____\ _______________________________________________________________________________________________________________________ /_____|
ㅤㅤ \______\ / \ /______/
ㅤㅤ \_____/\______________/ THIS PROGRAM MAY ALSO BE USED AS A GENERAL DATA LOGGER BY MODIFYING THE SENSOR CODE AND INPUTTING THE NECESSARY CODE \______________/\_____/
ㅤ ㅤ \________________________________________________________________________________________________________________________/
___________________________________________________________________________________________________________________________________________________________________________________
ㅤ ㅤ ㅤ ㅤ/ (ALL VCC AND GND TO BE WIRED TO VCC AND GND) \ /
HARDWARE WIRING LIST: / \ PROGRAM DESCRIPTION: /
_____________________/ \_______________________/
ㅤ
_________________________________ _________________________________________________________________
FOR 20X4 LIQUID CRYSTAL DISPLAY: \ / \
............_____________________/ / This program is designed to log data from a DS18B20 temperature \
-VSS GND / | sensor and read a float switch value. Once the SD card is |
-VDD 5V \____ | initialised and the RTC reads the set time, the program will |
-V0 2k RESISTOR \ | create a file in the SD card and name it according to the date |
-RS PIN A0 ____/ | (the date is retrieved using a DS3231 RTC). once the file and |
-RW GND / | file name is created, 8 data samples from all DS18B20 sensors |
-E PIN A1 | | are averaged, formatted and logged within the logging |
-D0 X / | interval into the created file, as well as the time of the day |
-D1 X / | in seconds and the current state of the float switch. If the |
-D2 X \ | program is ran until the next day, data logging will be |
-D3 X \ | stopped, and another file will be created and named according |
-D4 PIN A2 \ | to the date. The liquid crystal display and serial monitor |
-D5 PIN A3 \ | will show all relevant info, and can be used to ensure that the |
-D6 PIN D2 | | program runs correctly, displaying SD card initialisation, RTC |
-D7 PIN D7 / | initialisation, the filename the data is writing to, the time |
-A 10K POT / | the data started logging in DHHMMSS, and the current time of |
-K GND / \ the RTC. If any Errors occur, the LCD or serial monitor will /
_________/ \__________________ display an Error message. __________________/
ㅤ \___________________________/
____________________________
FOR SD CARD ADAPTER MODULE: \
....................________/
-chipSelect PIN D10 \
-MOSI PIN D11 _____/
-MISO PIN D12 /
-SCK PIN D13 /
____________/
_________________
FOR DS3231 RTC: \
..................\
-SCL PIN SCL OR A5 \
-SDA PIN SDA OR A4 |
-SQW X ___________/
_______/
________________________________
FOR DS18B20 TEMPERATURE SENSOR: \
........._______________________/
-POS 5V \
-NEG GND \__
-DQ 5.1kPU \________________
-5.1kPU LINKING PIN D9 AND 5V \______________
-104pF CERAMIC CAPACITOR LIKNING POS AND NEG \
_____________________________________________/
__________________
FOR FLOAT SWITCH: \
............._____/
-POS PIN D4 /
-NEG GND __/
________/
// INCLUDE LIBRARIES AND DEFINED CONSTANTS:
*********************************************************************************************************************************************************************************/
#include <Triogen.h>
#include <LiquidCrystal.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>
#include <FS.h>
#include <RTClib.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Time.h>
#define BLUE 6
#define GREEN 8
#define RED 9
#define chipSelect 21
#define buzzer 17
#define buzzerTogglePin 13
// used as general global variables
/********************************************************************************************************************************************************************************/
LiquidCrystal lcd(1, 2, 3, 4, 5, 10);
File dataFile;
RTC_DS3231 rtc;
time_t now;
struct tm myTimeInfo;
unsigned long tStart = millis();
unsigned long totMillis;
unsigned long lastMillis = 0;
unsigned long logTm = 10000;
unsigned long sampleTm = 1250;
unsigned long tLog = totMillis + logTm;
unsigned long tSample = totMillis + sampleTm;
int year;
int month;
int day;
int hour;
int minute;
int second;
int newDay = day + 1;
int WiFiTimeout;
int buzzerState = 1;
int BTstate;
int buzzerOutState;
byte errCount = 0;
char dirName[9] = "/TANKLOG";
char fileName[16];
char fullPath[22];
const char* NTPserver = "pool.ntp.org";
const long GMToffsetSec = 0;
const int daylightOffsetSec = 3600;
const char* time_zone = PSTR("GMT0BST,M3.5.0/1,M10.5.0");
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", GMToffsetSec);
void (*resetFunc)(void) = 0;
// variables only used for the specific type of datalogging
/********************************************************************************************************************************************************************************/
#include <OneWire.h>
#include <DallasTemperature.h>
#define floatSw 7
#define tempResolution 12
#define ONE_WIRE_BUS 18
OneWire oneWire(ONE_WIRE_BUS);
DeviceAddress tankTempSensor;
DallasTemperature sensors(&oneWire);
// function runs if a hardware error occurs
/********************************************************************************************************************************************************************************/
void hardwareErr() {
while (!SD.begin() || !rtc.begin() || SD.cardType() == CARD_NONE) {
digitalWrite(buzzer, 1);
digitalWrite(GREEN, LOW);
digitalWrite(RED, HIGH);
digitalWrite(BLUE, HIGH);
delay(100);
digitalWrite(buzzer, 0);
digitalWrite(GREEN, LOW);
digitalWrite(RED, LOW);
digitalWrite(BLUE, LOW);
delay(1900);
}
if (SD.begin(chipSelect) && rtc.begin() && SD.cardType() != CARD_NONE) {
lcd.clear();
resetFunc();
}
}
int printResetReason() {
int reason = esp_reset_reason();
Serial.printf("CPU reset reason: %d ", reason);
switch (reason) {
case ESP_RST_UNKNOWN: Serial.println("unknown");
break;
case ESP_RST_POWERON: Serial.println("POWER ON RESET");
break;
case ESP_RST_SW: Serial.println("SOFTWARE RESET");
break;
case ESP_RST_PANIC: Serial.println("PANIC RESET");
break;
case ESP_RST_TASK_WDT: Serial.println("TASK WDT RESET");
break;
case ESP_RST_WDT: Serial.println("OTHER WDT RESET");
break;
case ESP_RST_DEEPSLEEP: Serial.println("EXIT DEEP SLEEP RESET");
break;
case ESP_RST_BROWNOUT: Serial.println("BROWNOUT RESET");
break;
case ESP_RST_SDIO: Serial.println("SDIO RESET");
break;
}
return(reason);
}
void printAddress(DeviceAddress tankTempSensor) {
for (byte i = 0; i < 8; i++) {
if (tankTempSensor[i] < 16) Serial.print("0");
Serial.print(tankTempSensor[i], HEX);
}
}
// initialises most of the hardware
/********************************************************************************************************************************************************************************/
void initialisation() {
pinMode(buzzer, OUTPUT);
pinMode(buzzerTogglePin, INPUT);
pinMode(floatSw, INPUT_PULLUP);
pinMode(RED, OUTPUT);
pinMode(GREEN, OUTPUT);
pinMode(BLUE, OUTPUT);
Wire.begin();
SD.begin(chipSelect);
lcd.begin(20, 4);
sensors.begin();
sensors.setWaitForConversion(false);
sensors.requestTemperatures();
Serial.print("DS18B20 Address: ");
sensors.getAddress(tankTempSensor, 0);
printAddress(tankTempSensor);
Serial.println();
sensors.setResolution(tankTempSensor, tempResolution);
Serial.println();
lcd.clear();
printAll("Initialising SD...");
if (!SD.begin()) {
lcd.clear();
printAll("SD card mount failed");
Serial.println("Check that SD card is mounted and all wiring and connections have been made and are correct.");
hardwareErr();
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
lcd.clear();
printAll("No SD card attached");
hardwareErr();
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
}
else if (cardType == CARD_SD) {
Serial.println("SDSC");
}
else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
}
else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
lcd.setCursor(0, 1);
printAll("Initialising RTC...");
if (!rtc.begin()) {
lcd.clear();
printAll(F("RTC failed."));
Serial.println("Check that the RTC is mounted and all wiring and connections have been made and are correct");
hardwareErr();
}
lcd.clear();
lcd.setCursor(0, 0);
Serial.print("Connecting to WiFi...");
lcd.print("Connecting to WiFi..");
WiFi.begin(Triogen_ssid, Triogen_password);
while(WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
WiFiTimeout+=1;
if(WiFiTimeout == 10) {
Serial.print("\nCannot connect to WiFi. time will be derived from last time set on RTC and offline mode will be used instead.\n\n");
lcd.clear();
lcd.print("No WiFi available");
lcd.setCursor(0,2);
lcd.print("Offline mode will");
lcd.setCursor(0,3);
lcd.print("be used instead");
delay(5000);
DateTime now = rtc.now();
year = now.year() - 2000;
month = now.month();
day = now.day();
hour = now.hour();
minute = now.minute();
second = now.second();
break;
}
}
if(WiFi.status() == WL_CONNECTED){
lcd.setCursor(0, 1);
Serial.println();
printAll("WIFI CONNECTED");
timeClient.begin();
timeClient.update();
lcd.setCursor(0, 3);
Serial.printf("Getting time from %s...", NTPserver);
lcd.setCursor(0, 2);
lcd.print("Getting time from ");
lcd.setCursor(0, 3);
lcd.print(NTPserver);
lcd.print("... ");
time(&now);
localtime_r(&now, &myTimeInfo);
configTime(GMToffsetSec, daylightOffsetSec, NTPserver);
while(!getLocalTime(&myTimeInfo)) {
Serial.print(".");
}
int year = myTimeInfo.tm_year + 1900;
int month = myTimeInfo.tm_mon + 1;
int day = myTimeInfo.tm_mday;
int hour = myTimeInfo.tm_hour;
int minute = myTimeInfo.tm_min;
int second = myTimeInfo.tm_sec;
char NTPtime[70];
rtc.adjust(DateTime(year, month, day, hour, minute, second));
sprintf(NTPtime, "Date: %02d-%02d-%02d Time: %02d:%02d:%02d", day, month, year, hour, minute, second);
Serial.println();
Serial.println(NTPtime);
}
}
// creates the file and name, and prints lcd info if the file is available to write to
/*******************************************************************************************************************************************************************************/
void createFileAndName() {
if(WiFi.status() == WL_CONNECTED) {
year = myTimeInfo.tm_year + 1900 - 2000;
month = myTimeInfo.tm_mon + 1;
day = myTimeInfo.tm_mday;
}
else {
DateTime now = rtc.now();
year = now.year() - 2000;
month = now.month();
day = now.day();
}
sprintf(fileName, "%02d-%02d-%d.dat", day, month, year);
strlcpy(fullPath, dirName, sizeof fullPath);
strlcat(fullPath, "/", sizeof fullPath);
strlcat(fullPath, fileName, sizeof fullPath);
SD.mkdir(dirName);
// INITIALISE LCD DISPLAY DATA
/********************************************************************************************************************************************************************************/
Serial.printf("Full File Path: X:%s \n\n", fullPath);
lcd.clear();
lcd.print("TANKLOG");
lcd.print(" ");
lcd.print(fileName);
lcd.setCursor(0, 1);
lcd.print(F("TEMP:"));
lcd.setCursor(0, 2);
lcd.print(F("TANK LVL:"));
lcd.setCursor(17, 2);
}
void firstWrite() {
byte txt = strlen(fileName)-4;
fileName[txt] = '\0';
dataFile = SD.open(fullPath, FILE_APPEND);
dataFile.printf("Date: %s\n", fileName);
dataFile.println("Time(s),Float Switch,Temp(°C)");
dataFile.close();
}
// DATA AND DIAGNOSTICS:
/********************************************************************************************************************************************************************************/
void DatalogAndDiagnostics() {
// TIME INFO:
/********************************************************************************************************************************************************************************/
char displayTime[24];
DateTime now = rtc.now();
int year = now.year();
int month = now.month();
int day = now.day();
int hour = now.hour();
int minute = now.minute();
int second = now.second();
if (!rtc.begin()) {
lcd.clear();
printAll(F("RTC failed"));
hardwareErr();
}
if (day == newDay) {
createFileAndName();
firstWrite();
newDay+=1;
delay(750);
}
unsigned long totMillis = millis() - tStart;
unsigned long totSeconds = (millis() - tStart) / 1000;
unsigned long totMinutes = totSeconds / 60;
unsigned long totHours = totMinutes / 60;
unsigned long totDays = totHours / 24;
totSeconds %= 60;
totMinutes %= 60;
totHours %= 24;
totDays %= 7;
char chardays[2];
char charhours[3];
char charminutes[3];
char charseconds[3];
dtostrf(totDays, 1, 0, chardays);
dtostrf(totHours, 2, 0, charhours);
dtostrf(totMinutes, 2, 0, charminutes);
dtostrf(totSeconds, 2, 0, charseconds);
sprintf(displayTime, "%02d:%02d:%02d %sd%sh%sm%ss", hour, minute, second, chardays, charhours, charminutes, charseconds);
lcd.setCursor(0, 3);
lcd.print(displayTime);
unsigned long hToM = hour * 60;
unsigned long hToS = hToM * 60;
unsigned long mToS = (minute * 60);
unsigned long sToS = second;
unsigned long tmdayInS = hToS + mToS + sToS;
//SPECIFIC DATALOGGING CODE:
/********************************************************************************************************************************************************************************/
dataFile = SD.open(fullPath, FILE_APPEND);
static int floatSwState;
static byte nSample = 0;
const int highTempTm = 1000;
const int tempErrTm = 1500;
const int floatSwTm = 500;
lcd.setCursor(6, 1);
lcd.print(sensors.getTempCByIndex(0));
lcd.print("\337C");
lcd.setCursor(10, 2);
if (digitalRead(floatSw) == 1) {
lcd.print("OK ");
floatSwState = 1;
}
else if (digitalRead(floatSw == 0)) {
lcd.print("NOT OK");
floatSwState = 0;
if (totMillis - lastMillis > floatSwTm) {
lastMillis = totMillis;
if (buzzerOutState == 1) {
digitalWrite(buzzer, 1);
}
digitalWrite(RED, 0);
digitalWrite(GREEN, 0);
digitalWrite(BLUE, 1);
}
else {
digitalWrite(buzzer, 0);
}
}
if (sensors.getTempCByIndex(0) >= 0 && sensors.getTempCByIndex(0) <= 22) {
digitalWrite(RED, 0);
digitalWrite(GREEN, 1);
digitalWrite(BLUE, 0);
}
else if (sensors.getTempCByIndex(0) > 22 && sensors.getTempCByIndex(0) <= 23) {
analogWrite(RED, 255);
analogWrite(GREEN, 255);
digitalWrite(BLUE, 0);
}
else if (sensors.getTempCByIndex(0) > 23 && sensors.getTempCByIndex(0) <= 24) {
analogWrite(RED, 255);
analogWrite(GREEN, 90);
digitalWrite(BLUE, 0);
}
else if (sensors.getTempCByIndex(0) > 24 && sensors.getTempCByIndex(0) <= 25) {
digitalWrite(RED, 1);
digitalWrite(GREEN, 0);
digitalWrite(BLUE, 0);
}
else if (sensors.getTempCByIndex(0) > 25) {
if (totMillis - lastMillis > highTempTm) {
lastMillis = totMillis;
if (buzzerOutState == 1) {
digitalWrite(buzzer, 1);
}
digitalWrite(RED, 1);
digitalWrite(GREEN, 0);
digitalWrite(BLUE, 0);
}
else {
digitalWrite(buzzer, 0);
digitalWrite(RED, 0);
digitalWrite(GREEN, 0);
digitalWrite(BLUE, 0);
}
}
/* else {
lcd.setCursor(6, 1);
lcd.print("ERROR ");
if (totMillis - lastMillis > tempErrTm) {
lastMillis = totMillis;
if (buzzerOutState == 1) {
digitalWrite(buzzer, 1);
}
digitalWrite(RED, 1);
digitalWrite(GREEN, 1);
digitalWrite(BLUE, 1);
}
else {
digitalWrite(buzzer, 0);
digitalWrite(RED, 0);
digitalWrite(GREEN, 0);
digitalWrite(BLUE, 0);
}
} */
static float aveT1 = sensors.getTempCByIndex(0);
static float totT1 = aveT1;
static char charAveT1[6];
static char charTotT1[8];
static char charTemp[8];
static char charTsampleInS[6];
dtostrf(aveT1, 5, 2, charAveT1);
dtostrf(totT1, 7, 2, charTotT1);
dtostrf(sensors.getTempCByIndex(0), 7, 2, charTemp);
if((sensors.getTempCByIndex(0) == -127 && errCount <=1) || (sensors.getTempCByIndex(0) == 85 && errCount <=1)) {
dataFile.print("ERROR:");
dataFile.close();
errCount+=1;
}
//This part is for code averaging
if (totMillis > tSample) {
nSample +=1;
aveT1 = totT1 / nSample;
totT1 += sensors.getTempCByIndex(0);
Serial.printf("Time: %02d:%02d:%02d | Runtime: %02dd %02dh %02dm %02ds | Sample: %d | Sw: %d | temp(°C):%s | Ave: %s | Tot:%s\n",
hour, minute, second, totDays, totHours, totMinutes, totSeconds,
nSample, floatSwState, charTemp, charAveT1, charTotT1);
tSample += sampleTm;
sensors.requestTemperatures();
}
char aveData[20];
char charTmDayInS[7];
dtostrf(tmdayInS, 5, 0, charTmDayInS);
sprintf(aveData, "%s,%d,%s", charTmDayInS, floatSwState, charAveT1);
if (!dataFile) {
lcd.clear();
printAll(F("File write Err"));
hardwareErr();
}
static int logCount = 0;
if (totMillis > tLog) {
dataFile.println(aveData);
dataFile.close();
logCount+=1;
Serial.printf("\n LOG NO.%d\n", logCount);
Serial.println("| Time(s) | Float Switch State | Average Temperature(°C) |");
Serial.printf("| %s | %d | %s |\n\n",
charTmDayInS, floatSwState, charAveT1);
tLog += logTm;
}
if (nSample > 7) {
nSample = 0;
aveT1 = sensors.getTempCByIndex(0);
totT1 = aveT1;
errCount = 0;
}
// ALARM
/********************************************************************************************************************************************************************************/
static bool buzzerState = 1;
static bool lastBTstate = 0;
unsigned long lastDebounceTime = 0;
const byte debounceDelay = 50;
lcd.setCursor(15, 1);
lcd.print("ALARM");
int newBTstate = analogRead(buzzerTogglePin) < 300 ? 1 : 0;
if (newBTstate != lastBTstate) {
lastDebounceTime = totMillis;
}
if ((totMillis - lastDebounceTime) > debounceDelay) {
if (newBTstate != BTstate) {
BTstate = newBTstate;
if (BTstate == 1) {
buzzerState = !buzzerState;
}
}
}
lcd.setCursor(17, 2);
if (buzzerState == 1) {
buzzerOutState = 1;
lcd.print(" ON");
}
else if (buzzerState == 0) {
buzzerOutState = 0;
lcd.print("OFF");
}
lastBTstate = newBTstate;
}
// SETUP() FUNCTION
/********************************************************************************************************************************************************************************/
void setup() {
Serial.begin(BAUD);
delay(2000);
Serial.print("\n\n\n");
printResetReason();
initialisation();
createFileAndName();
firstWrite();
tStart = millis();
}
// LOOP() FUNCTION
/********************************************************************************************************************************************************************************/
void loop() {
DatalogAndDiagnostics();
}
// END OF PROGRAM
/********************************************************************************************************************************************************************************/