Hello,
I am making a simplified datalogger that should log temperature and humidity (AHT20/DHT20) in regular intervals, save the data to an external EEPROM (24LC512), and output the extremes to an I2C OLED. I will also implement a P-MOSFET latch circuit driven by the DS3231 RTC, which reduces power to a minimum between the logging events. At one point I will also implement a switch that allows me view the logged values on the OLED (and cycle through screens) even if the system is powered down at that point.
Right now I have written a code based on Edward Mallon's (https://thecavepearlproject.org/) data loggers; implementing a sleep/alarm function, logging of time/date and min/max temperature/humidity to the EEPROM, reading the max humidity from the EEPROM and outputting it to the OLED. The sleep functions will be removed after I implement the P-MOSFET latch circuit.
However, I have two very annoying issues. Last night, it seemed that everything was running smoothly, and I was ready to start implementing the P-MOSFET latch circuit.
- The timestamps show up as complete gibberish in the serial monitor. Yesterday, this happened randomly (e.g., only the temperature timestamps would show up correctly, or the minimum temperature/humidity timestamps entries would be correct). After I changed the baud rate (in both the code and monitor) to 9600 it worked. Today, none of the timestamps are correct, no matter what rate I select. I have two further observations about this. When it is not showing up correctly in the serial monitor, it is not showing up at all on the OLED. And the current timestamp (shown on the "sensor readings done:" and "data saved:" lines) shows up correctly. I therefore assume that I am doing something wrong when writing or reading from the EEPROM. However, they did show up correctly most of the time yesterday, and I did not really make any changes to the code.
- I might be wrong, but I am not 100% sure that the entries are actually retained in the EEPROM after powering down the system. In the setup I did add some code to read from the EEPROM when powering on (to prevent overwriting the previously saved values), but I can't be sure if it's working since the timestamps are not showing up at all in the monitor.
Could someone please have a look at the code and see if they notice anything completely wrong - especially regarding the EEPROM? Apologies for the long code, some of it will be trimmed away when implementing the latch circuit. (It should be noted however that I used a much simpler code without sleep cycles yesterday, and then the timestamps also showed up as gibberish from time to time).
// Remains to be implemented:
// Some timestamps are showing up as gibberish
// X Should only write to EEPROM if a new entry is made!
// Does not always keep values in EEPROM when powered down?
// Powerdown and power up with P-MOSFET latch and RTC SQW (need to cut a pulldown resistor on RTC? Or run as in the other GitHub?)
// Turn on twice per day or every hour to log temp/hum
// Do a log if pressing a button
// One button to work the same as the alarm (green)
// One button to cycle through screens (black)
// Only perform logging once after button has been pressed or alarm has been sounded
// For every log/after button press: cycle through the screens
// Implement battery reading at every log, and a conversion to %
// LED to be blue when logging (and green, yellow and red when low battery)
// Works:
// Saves min/max temp/hum to EEPROM, able to show (one entry) on OLED when the date is not gibberish
// Keeps time when unpowered (needed to re-run RTC set time sketch)
// include the library code:
#include <Adafruit_AHTX0.h>
#include <Wire.h>
#include <avr/power.h> // for peripheral shutdown to lower runtime current
#include <avr/sleep.h>
#include <LowPower.h> // library from https://github.com/rocketscream/Low-Power
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include "MrAlvinRTClib.h"
#include "I2C_eeprom.h"
//===============================================================
// LOGGER OPERATING PARAMETERS: Adjust these to suit your build!
//===============================================================
#define ECHO_TO_SERIAL // prints readings to screen: ONLY for USB tethered debugging!
// COMMENT out ECHO_TO_SERIAL DURING BATTERY POWERED OPERATION or system wastes a LOT of power waiting for a serial response that never arrives
// also starts the run with no sample interval sync delay so timestamps are not aligned
// NOTE: intervals get set in setup via Serial input
uint8_t SampleIntervalMinutes = 5; // Allowed values: 1,2,3,5,10,15,20,30 or 60 - must divide equally into 60!
//uint8_t SampleIntervalSeconds = 0; // used for rapid burn tests only, minutes must be zero for intervalseconds
//#define SampleIntervalMinutes 1
// Make sure your sensor readings don't take longer than your sample interval!
// If you over-run your next alarm you will have to wait 24hours for next wakeup
// This code assumes you have a common cathode RGB led as the indicator on your logger
// you can read only one LED color channel as a light sensor by disabling the other two defines here
// At low light levels it can take several seconds for each channel to make a reading
// So use a sampling interval longer than 1 minute if you read all three colors or you could over-run the wakeup alarm (which causes a 24hour sleep interval)
//#define readRedLED ON // enabling readLEDsensor define ADDS LED AS A SENSOR readings to the loggers default operation
//#define readGreenLED ON // enabling readLEDsensor define ADDS LED AS A SENSOR readings to the loggers default operation
//#define readBlueLED ON // enabling readLEDsensor define ADDS LED AS A SENSOR readings to the loggers default operation
#define LED_GROUND_PIN 3 // to use the indicator LED as a light sensor you must ground it through a digital I/O pin from D3 to D7
#define RED_PIN 4 //change these numbers to suit the way you connected the indicator LED
#define GREEN_PIN 5
#define BLUE_PIN 6
// Note: I always turn on indicator LEDs via INPUT_PULLUP, rather than simply setting the pin to OUTPUT & HIGH,
// this saves power & adds short circuit safety in case the LED was connected without limit resistor - but the light is dim
#define InternalReferenceConstant 1125200L //1126400L is default value! = 1100mV internal vref * 1024
// adding/subtracting 400 from the constant raises/lowers the 'calculated' result from
// readBattery() by ~1 millivolt, simply read the rail with a DVM while running on UART power
// change the constant, reload & restart with ECHO_TO_SERIAL on until the displayed voltage matches your DVM reading
//uncomment ONLY ONE of following -> depending on how you are powering your logger
//#define voltageRegulated // if you connect the battery supply through the Raw & GND pins & use the ProMini's regulator
//#define unregulated2xLithiumAA // define this if you've removed the regulator and are running directly from 2xAA lithium batteries
#define unregulated3xAlkaliAA
//#define unregulated4xNiMHAA
//// PINS AND I2C ADDRESSES ETC. ////
RTC_DS3231 rtc; // creates an RTC object in the code
#define DS3231_ADDRESS 0x3C
#define DS3231_I2C_ADDRESS 0x68
#define DS3231_STATUS_REG 0x0F
#define DS3231_CONTROL_REG 0x0E
#define DS3231_TMP_UP_REG 0x11
#define RTC_INTERRUPT_PIN 2 //DS3231's SQW connected to pin D2 on the arduino
Adafruit_AHTX0 aht;
I2C_eeprom ee(0x50, I2C_DEVICESIZE_24LC512);
#define oled_I2C_address 0x3C
SSD1306AsciiWire oled; // create a library object called ‘oled’
char CycleTimeStamp[] = "00/00/0000,00:00"; //16 characters without seconds!
uint8_t t_second; //= sec; used in RTC_DS3231_getTime()
uint8_t t_minute; //= min;
uint8_t t_hour; //= hour;
uint8_t t_day; //= day;
uint8_t t_month; //= month;
uint16_t t_year; //= year //yOff = raw year; //need to add 2000
byte Alarmhour;
byte Alarmminute;
byte Alarmday;
char TimeStamp[] = "00/00/0000,00:00"; //16 ascii characters (without seconds because they are always zeros on wakeup)
volatile boolean clockInterrupt = false; //this flag is set to true when the RTC interrupt handler is executed
//float rtc_TEMP_degC;
#define opdEEpromI2Caddr 0x50 // default: 0x57 for 4k EEprom on RTC module with all address pins pulled high
#define opdEEbytesOfStorage 65536 // default: 4096 for 4k EEprom, 65536 for 64k
#define sensorEEpromI2Caddr 0x50
#define sensorEEbytesOfStorage 65536
// OPD & SENSOR can be set to different physical eeproms or the same eeprom
// AT24c256 (red YL-90 module) is 32768 @0x50 // OR // AT24c512 (via chip swap) is 65536 bytes @0x50
int BatteryReading = 9999; //often read from a 10M/3.3M voltage divider, but could aso be same as VccBGap when unregulated
int safetyMargin4SDsave = 100; // grows dynamically after SD save events - tends to get larger as batteries age
int systemShutdownVoltage = 2850; // updated later depending on how you power your logger
//if running from 2x AA cells (with no regulator) the input cutoff voltage should be ~2850 mV (or higher)
//if running a unit with the voltage regulator the absolute minumum input cutoff voltage is 3400 mV (or higher)
#ifdef voltageRegulated
#define BatteryPin A0 //only used if you have a voltage divider to put input on A0 - change to suit your actual connection
#endif
//Global variables :most are used for temporary storage during communications & calculations
//===========================================================================================
//bool bitBuffer; // for fuctions that return an on/off true/false state
byte bytebuffer1 = 0; // for functions that return a byte - usually comms with sensors
byte bytebuffer2 = 0; // second buffer for 16-bit sensor register readings
int integerBuffer = 9999; // for temp-swapping ADC readings
float floatbuffer = 9999.9; // for temporary float calculations
#define analogInputPin A0 //for analog pin reading
int analogPinReading = 0;
//Sensor specific variables & defines:
//====================================
struct minTemp {
float temp = 80;
//float hum;
String t;
};
minTemp minTempRecord;
struct maxTemp {
float temp = -40;
//float hum;
String t;
};
maxTemp maxTempRecord;
struct maxHum {
//float temp;
float hum = 0;
String t;
};
maxHum maxHumRecord;
struct minHum {
//float temp;
float hum = 80;
String t;
};
minHum minHumRecord;
//======================================================================================================================
// * * * * * * SETUP * * * * *
//======================================================================================================================
// NOTE: problems in setup call error() routine which halts the system!
void setup() {
// put your setup code here, to run once:
// builds that jumper A4->A2 and A5->A3 to bring the I2C bus to the screw terminals MUST DISABLE digital I/O on these two pins
// If you are doing only ADC conversions on the analog inputs you can disable the digital buffers on those pins, to save power
bitSet(DIDR0, ADC0D); // disable digital buffer on A0
bitSet(DIDR0, ADC1D); // disable digital buffer on A1
bitSet(DIDR0, ADC2D); // disable digital buffer on A2
bitSet(DIDR0, ADC3D); // disable digital buffer on A3
//Once the input buffer is disabled, a digitalRead on those A-pins will always be zero.
#ifdef voltageRegulated
systemShutdownVoltage = 3400; // 3400 is the minimum allowd input to the Mic5205 regulator - alkalines often drop by 200mv or more under load
#endif
#if defined(unregulated2xLithiumAA) || defined(ECHO_TO_SERIAL) // two situations with no voltage on the A6 resistor divider
systemShutdownVoltage = 2800; // minimum Battery voltage when running from 2x LITHIUM AA's
#endif
#if defined(unregulated3xAlkaliAA) // two situations with no voltage on the A6 resistor divider
systemShutdownVoltage = 3000; // minimum Battery voltage when running from 3x Alkali AA's
#endif
#if defined(unregulated4xNiMHAA) // two situations with no voltage on the A6 resistor divider
systemShutdownVoltage = 3900; // minimum Battery voltage when running from 4x NiMH AA's
#endif
// 24 second time delay - stabilizes system after power connection
// the 104 cap on the main battery voltage divider needs > 2s to charge up (on regulated systems)
// delay also prevents writing multiple file headers with brief accidental power connections
digitalWrite(BLUE_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, LOW);
#ifdef LED_GROUND_PIN
digitalWrite(LED_GROUND_PIN, LOW); //another pin to sink current - depending on the wireing
pinMode(LED_GROUND_PIN, OUTPUT); //units using pre-made LED boards sometimes need to set
#endif
pinMode(RED_PIN, INPUT_PULLUP); // Using INPUT_PULLUP instead of HIGH lets you connect a raw LED safely
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_ON);
digitalWrite(RED_PIN, LOW);
pinMode(BLUE_PIN, INPUT_PULLUP);
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_ON);
digitalWrite(BLUE_PIN, LOW);
pinMode(GREEN_PIN, INPUT_PULLUP); //green led is usually 4x as bright as the others
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_ON);
digitalWrite(GREEN_PIN, LOW);
pinMode(RED_PIN, INPUT_PULLUP); // red is usually dimmest color
Serial.begin(9600); // Always serial.begin because if 'anything' in some random library tries to print without it you get a HARD system freeze // change to 115200?
while (!Serial) {} // Loop until USB is ready // added to see if this removes gibberish characters
Wire.begin(); // enables internal 30-50k pull-up resistors on SDA & SCL by default // Start the i2c interface
rtc.begin(); // RTC initialization:
rtc.turnOffAlarm(1);
//rtc.clearAlarm(1);
clearClockTrigger(); // Function stops RTC from holding interrupt line low after power reset
pinMode(RTC_INTERRUPT_PIN, INPUT_PULLUP); //not needed if you have hardware pullups on SQW, most RTC modules do but some don't
DateTime now = rtc.now();
sprintf(TimeStamp, "%02d/%02d/%04d %02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute());
enableRTCAlarmsonBackupBattery(); // only needed if you cut the pin supplying power to the DS3231 chip
//#if defined(unregulated2xLithiumAA) || defined(ECHO_TO_SERIAL)
// BatteryReading = getRailVoltage(); //If you are running from raw battery power (with no regulator) VccBGap IS the battery voltage
#if defined(unregulated3xAlkaliAA) || defined(ECHO_TO_SERIAL)
BatteryReading = getRailVoltage(); //If you are running from raw battery power (with no regulator) VccBGap IS the battery voltage
//#if defined(unregulated4xNiMHAA) || defined(ECHO_TO_SERIAL)
// BatteryReading = getRailVoltage(); //If you are running from raw battery power (with no regulator) VccBGap IS the battery voltage
#else // #ifdef voltageRegulated:
analogReference(DEFAULT);
analogRead(BatteryPin);
delay(10); //throw away the first reading when using high impedance voltage dividers!
floatbuffer = float(analogRead(BatteryPin));
floatbuffer = (floatbuffer + 0.5) * (3.3 / 1024.0) * 4.030303; // 4.0303 = (Rhigh+Rlow)/Rlow for a 10M/3.3M voltage divider combination
BatteryReading = int(floatbuffer * 1000.0);
#endif
if (BatteryReading < (systemShutdownVoltage + safetyMargin4SDsave + 50)) {
error_shutdown(); //if the battery voltage is too low to create a log file, shut down the system
} //a 50mv dip on the battery supply is quite normal after 100mA SDwrite loads, but can go up to 200-300mv as batteries age
// Here there was a lot of SD stuff I didn't copy over
//====================================================================
//OLED initialization
//====================================================================
oled.begin(&Adafruit128x64, oled_I2C_address);
oled.setFont(System5x7);
//====================================================================
//DHT initialization
//====================================================================
if (!aht.begin()) {
Serial.println("Could not find AHT? Check wiring");
while (1) delay(10);
}
//setting UNUSED digital pins to INPUT_PULLUP reduces noise & risk of accidental short
//pinMode(7,INPUT_PULLUP); //only if you do not have anything connected to this pin
//pinMode(8,INPUT_PULLUP); //only if you do not have anything connected to this pin
//pinMode(9,INPUT_PULLUP); // NOT if you are you using this pin for the DS18b20!
#ifndef ECHO_TO_SERIAL
pinMode(0, INPUT_PULLUP); //but not if we are connected to usb
pinMode(1, INPUT_PULLUP); //then these pins are needed for RX & TX
#endif
//==================================================================================================================
//Delay logger start until alarm times are in sync with sampling intervals
//this delay prevents a "short interval" from occuring @ the first hour rollover
#ifdef ECHO_TO_SERIAL
Serial.println(F("Timesync startup delay is disabled when ECHO_TO_SERIAL enabled"));
Serial.flush();
#else //sleep logger till alarm time is sync'd with sampling intervals
Alarmhour = now.hour();
Alarmminute = now.minute();
int syncdelay = Alarmminute % SampleIntervalMinutes; // 7 % 10 = 7 because 7 / 10 < 1, e.g. 10 does not fit even once in seven. So the entire value of 7 becomes the remainder.
syncdelay = SampleIntervalMinutes - syncdelay; // when SampleIntervalMinutes is 1, syncdelay is 1, other cases are variable
Alarmminute = Alarmminute + syncdelay;
if (Alarmminute > 59) { // check for roll-overs
Alarmminute = 0;
Alarmhour = Alarmhour + 1;
if (Alarmhour > 23) { Alarmhour = 0; }
}
RTC.setAlarm1Simple(Alarmhour, Alarmminute);
RTC.turnOnAlarm(1); //purple indciates logger is in delay-till-startup state
pinMode(RED_PIN, INPUT_PULLUP);
pinMode(BLUE_PIN, INPUT_PULLUP); // red&blue combination is ONLY used for this
attachInterrupt(0, rtcISR, LOW); // program hardware interrupt to respond to D2 pin being brought 'low' by RTC alarm
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_ON); //this puts logger to sleep
detachInterrupt(0); // disables the interrupt after the alarm wakes the logger
RTC.turnOffAlarm(1); // turns off the alarm on the RTC chip
#endif //#ifdef ECHO_TO_SERIAL
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW); //turn off all indicators
// Start out with reading the stored EEPROM values
ee.readBlock(0, (uint8_t *)&minTempRecord, sizeof(minTempRecord));
ee.readBlock(100, (uint8_t *)&maxTempRecord, sizeof(maxTempRecord));
ee.readBlock(200, (uint8_t *)&minHumRecord, sizeof(minHumRecord));
ee.readBlock(300, (uint8_t *)&maxHumRecord, sizeof(maxHumRecord));
//====================================================================================================
} // terminator for setup
//=====================================================================================================
// ========================================================================================================
// * * * * * * MAIN LOOP * * * * * *
//========================================================================================================
void loop() {
// put your main code here, to run repeatedly:
pinMode(BLUE_PIN, INPUT_PULLUP);
DateTime now = rtc.now(); // reads time from the RTC
sprintf(TimeStamp, "%02d/%02d/%04d %02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute());
//loads the time into a string variable - don’t record seconds in the time stamp because the interrupt to time reading interval is <1s, so seconds are always ’00’
#ifdef ECHO_TO_SERIAL
Serial.print("System taking a new reading at: "); //(optional) debugging message
Serial.println(TimeStamp);
Serial.flush();
#endif
/*
// read the RTC temp register - Note: the DS3231 temp registers only update every 64seconds
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0x11); //the register where the temp data is stored
Wire.endTransmission(); // nothing really happens until the complier sends the .endTransmission command
Wire.requestFrom(DS3231_I2C_ADDRESS, 2); //ask for two bytes of data
if (Wire.available()) {
byte tMSB = Wire.read(); //2’s complement int portion
byte tLSB = Wire.read(); //fraction portion
rtc_TEMP_degC = ((((short)tMSB << 8) | (short)tLSB) >> 6) / 4.0; // Allows for readings below freezing: thanks to Coding Badly
//rtc_TEMP_degC = (rtc_TEMP_degC * 1.8) + 32.0; // To Convert Celcius to Fahrenheit
}
else {
rtc_TEMP_degC = 999.9; //if rtc_TEMP_degC contains 999.9, then you had a problem reading from the RTC!
}
#ifdef ECHO_TO_SERIAL
Serial.print(F(" TEMPERATURE from RTC is: "));
Serial.print(rtc_TEMP_degC);
Serial.println(F(" Celsius"));
Serial.flush();
#endif
*/
digitalWrite(BLUE_PIN, LOW); //end of RTC communications
pinMode(GREEN_PIN, INPUT_PULLUP); //green indicates sensor readings taking place
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON); //optional delay here to make indicator pip more visible
/*
//============================================================
// Read Analog Input
analogReference(DEFAULT);analogRead(analogInputPin); //always throw away the first ADC reading
delay(10); //10msec delay gives Aref capacitor time to adjust
//now you can do a single analog reading one time
analogPinReading = analogRead(analogInputPin);
// OR you can read the analog input line multiple times, and feed those readings into an averaging or smoothing filter
// One of my favorites for removing "single spike" errors from noisy sensor inputs is median3 which takes three values/readings as input
// analogPinReading = median_of_3( analogRead(analogInputPin), analogRead(analogInputPin), analogRead(analogInputPin));
//you can use this filter with any sensor that generates only positive integer values
*/
//=====================================
//Read the AHT temp/hum Sensor:
sensors_event_t humidity, temp;
aht.getEvent(&humidity, &temp);
#ifdef ECHO_TO_SERIAL
//Serial.print(now.toString(full_format));
Serial.print(F("Sensor readings done: "));
Serial.print(TimeStamp);
Serial.print(" ");
Serial.print(temp.temperature, 2);
Serial.print(" °C ");
Serial.print(humidity.relative_humidity, 2);
Serial.println("%");
#endif
digitalWrite(GREEN_PIN, LOW);
//========================================================
//Read Light Level with indicator LED color channels
// Modfied from //https://playground.arduino.cc/Learning/LEDSensor I added PIND for speed
// An explaination of the reverse-bias LED reading technique https://www.sparkfun.com/news/2161
// these logarithmic 'discharge time' readings get smaller as the amount of light increases
#ifdef readRedLED
uint32_t redLEDreading = readRedLEDchannel(); //call the function which reads the RED led channel
#ifdef ECHO_TO_SERIAL
Serial.print(F("RedLED = "));
Serial.print(redLEDreading);
Serial.flush();
#endif
#endif //readRedLED
#ifdef readGreenLED
uint32_t greenLEDreading = readGreenLEDchannel(); //call the function which reads the GREEN led channel
#ifdef ECHO_TO_SERIAL
Serial.print(F("GreenLED= "));
Serial.print(greenLEDreading);
Serial.flush();
#endif
#endif //readGreenLED
#ifdef readBlueLED
uint32_t blueLEDreading = readBlueLEDchannel(); //call the function which reads the BLUE led channel
#ifdef ECHO_TO_SERIAL
Serial.print(F("BlueLED= "));
Serial.print(blueLEDreading);
Serial.flush();
#endif
#endif //readBlueLED
pinMode(RED_PIN, INPUT_PULLUP); //indicate EEPROM saving
// ========== Pre EEPROM (here: SD) saving battery checks ==========
#if defined(unregulated3xAlkaliAA) || defined(ECHO_TO_SERIAL)
int preSDsaveBatterycheck = getRailVoltage(); //If you are running from raw battery power (with no regulator) VccBGap IS the battery voltage
#else // #ifdef voltageRegulated:
analogReference(DEFAULT);
analogRead(BatteryPin);
delay(5); //throw away the first reading when using high impedance voltage dividers!
floatbuffer = float(analogRead(BatteryPin));
floatbuffer = (floatbuffer + 0.5) * (3.3 / 1024.0) * 4.030303; // 4.0303 = (Rhigh+Rlow)/Rlow for a 10M/3.3M voltage divider combination
int preSDsaveBatterycheck = int(floatbuffer * 1000.0);
#endif
if (preSDsaveBatterycheck < (systemShutdownVoltage + safetyMargin4SDsave + 50)) { //extra 50 thrown in due to 1.1vref uncertainty
error_shutdown(); //shut down the logger because the voltage is too low for SD saving
}
//========== If battery is OK then it's safe to write the data ===========
delay(10);
#ifdef ECHO_TO_SERIAL
Serial.println("Writing to EEPROM...");
Serial.flush();
#endif
if (temp.temperature > maxTempRecord.temp) {
maxTempRecord.temp = temp.temperature;
maxTempRecord.t = TimeStamp;
ee.writeBlock(100, (uint8_t *)&maxTempRecord, sizeof(maxTempRecord));
}
if (temp.temperature < minTempRecord.temp) {
minTempRecord.temp = temp.temperature;
minTempRecord.t = TimeStamp;
ee.writeBlock(0, (uint8_t *)&minTempRecord, sizeof(minTempRecord));
}
if (humidity.relative_humidity > maxHumRecord.hum) {
maxHumRecord.hum = humidity.relative_humidity;
maxHumRecord.t = TimeStamp;
ee.writeBlock(300, (uint8_t *)&maxHumRecord, sizeof(maxHumRecord));
}
if (humidity.relative_humidity < minHumRecord.hum) {
minHumRecord.hum = humidity.relative_humidity;
minHumRecord.t = TimeStamp;
ee.writeBlock(200, (uint8_t *)&minHumRecord, sizeof(minHumRecord));
}
delay(10);
/*
#ifdef ECHO_TO_SERIAL
Serial.print(F("Max temperature: "));
Serial.print(maxTempRecord.t);
Serial.print(" ");
Serial.print(maxTempRecord.temp, 2);
Serial.println(" °C");
Serial.print(F("Min temperature: "));
Serial.print(minTempRecord.t);
Serial.print(" ");
Serial.print(minTempRecord.temp, 2);
Serial.println(" °C");
Serial.print(F("Max humidity: "));
Serial.print(maxHumRecord.t);
Serial.print(" ");
Serial.print(maxHumRecord.hum, 2);
Serial.println("%");
Serial.print(F("Min humidity: "));
Serial.print(minHumRecord.t);
Serial.print(" ");
Serial.print(minHumRecord.hum, 2);
Serial.println("%");
Serial.println("");
#endif
*/
//========== Reading previous data from EEPROM and outputting on OLED ===========
oled.clear(); // erases anything displayed on the screen
delay(10);
ee.readBlock(0, (uint8_t *)&minTempRecord, sizeof(minTempRecord));
ee.readBlock(100, (uint8_t *)&maxTempRecord, sizeof(maxTempRecord));
ee.readBlock(200, (uint8_t *)&minHumRecord, sizeof(minHumRecord));
ee.readBlock(300, (uint8_t *)&maxHumRecord, sizeof(maxHumRecord));
oled.setCursor(0, 0);
oled.set1X();
oled.print(F("Max humidity:"));
oled.setCursor(0, 1);
oled.print(maxHumRecord.t);
oled.setCursor(0, 3); // move cursor to column 46, but remain in same row
oled.set2X(); // set double-row font height for readability
oled.print(maxHumRecord.hum, 2); // a float variable, limited to two decimal places
oled.print(F("%"));
delay(10);
#ifdef ECHO_TO_SERIAL
Serial.println(F("Reading from EEPROM..."));
Serial.print(F("Min temp from EEPROM: "));
Serial.print(minTempRecord.t);
Serial.print(" ");
Serial.print(minTempRecord.temp);
Serial.println(" °C");
Serial.print(F("Max temp from EEPROM: "));
Serial.print(maxTempRecord.t);
Serial.print(" ");
Serial.print(maxTempRecord.temp);
Serial.println(" °C");
Serial.print(F("Min humidity from EEPROM: "));
Serial.print(minHumRecord.t);
Serial.print(" ");
Serial.print(minHumRecord.hum);
Serial.println("%");
Serial.print(F("Max humidity from EEPROM: "));
Serial.print(maxHumRecord.t);
Serial.print(" ");
Serial.print(maxHumRecord.hum);
Serial.println("%");
Serial.println("");
#endif
//========== POST SD/EEPROM saving battery check ===========
//the SD card can pull up to 200mA, and so a more representative battery reading is one taken AFTER this load
#if defined(unregulated3xAlkaliAA) || defined(ECHO_TO_SERIAL)
BatteryReading = getRailVoltage(); //If you are running from raw battery power (with no regulator) VccBGap IS the battery voltage
#else // #ifdef voltageRegulated:
analogReference(DEFAULT);
analogRead(BatteryPin);
delay(5); //throw away the first reading when using high impedance voltage dividers!
floatbuffer = float(analogRead(BatteryPin));
floatbuffer = (floatbuffer + 0.5) * (3.3 / 1024.0) * 4.030303; // 4.0303 = (Rhigh+Rlow)/Rlow for a 10M/3.3M voltage divider combination
BatteryReading = int(floatbuffer * 1000.0);
#endif
//Note: SD card controllers sometimes generate "internal housekeeping events" that draw MUCH more power from the batteries than normal data saves
//so the value in SDsaveVoltageDelta usually increases after these occasional 'really big' power drain events
//that delta also increases as your batteries run down, AND if the temperature falls low enough to reduce the battery voltage
if ((preSDsaveBatterycheck - BatteryReading) > safetyMargin4SDsave) {
safetyMargin4SDsave = preSDsaveBatterycheck - BatteryReading;
}
if (BatteryReading < systemShutdownVoltage) {
error_shutdown(); //shut down the logger if you get a voltage reading below the cut-off
}
digitalWrite(RED_PIN, LOW); // SD saving is over
pinMode(BLUE_PIN, INPUT_PULLUP); // BLUE to indicate RTC events
// OPTIONAL debugging output: only if ECHO_TO_SERIAL is defined
#ifdef ECHO_TO_SERIAL
Serial.print("Data Saved: ");
Serial.print(TimeStamp);
Serial.print(", ");
Serial.print(BatteryReading);
Serial.print(", ");
Serial.print(safetyMargin4SDsave);
//Serial.print(", ");
//Serial.print(analogPinReading);
Serial.println(",");
Serial.flush();
#endif
//============Set the next alarm time =============
Alarmhour = now.hour();
Alarmminute = now.minute() + SampleIntervalMinutes;
Alarmday = now.day();
// check for roll-overs
if (Alarmminute > 59) { //error catching the 60 rollover!
Alarmminute = 0;
Alarmhour = Alarmhour + 1;
if (Alarmhour > 23) {
Alarmhour = 0;
// put ONCE-PER-DAY code here -it will execute on the 24 hour rollover
}
}
// then set the alarm
rtc.setAlarm1Simple(Alarmhour, Alarmminute);
rtc.turnOnAlarm(1);
if (rtc.checkAlarmEnabled(1)) {
//you would comment out most of this message printing
//if your logger was actually being deployed in the field
#ifdef ECHO_TO_SERIAL
Serial.print(F("Alarm Enabled! Going to sleep for : "));
Serial.print(SampleIntervalMinutes);
Serial.println(F(" minute(s)")); // println adds a carriage return
Serial.flush(); //waits for buffer to empty
#endif
}
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
//=======================================================================
// NOW sleep the logger and wait for next RTC wakeup alarm on pin D2
//=======================================================================
// do-while loop keeps the processor trapped until clockInterrupt is set to true
// if 'anything else' wakes the logger then it just goes right back to sleep
clockInterrupt = false;
bitSet(EIFR, INTF0); // clears any old 'EMI noise triggers' from the Interrupt0 FLAG register
do {
attachInterrupt(0, rtcISR, LOW); // Enable interrupt on pin2 & attach it to rtcISR function:
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_ON); // the RTC alarm wakes the processor from this sleep
detachInterrupt(0); // immediately disable the interrupt on waking
} while (clockInterrupt == false); // if rtc flag is still false then go back to sleep
EIFR = EIFR; // this memory register command clears leftover events from ‘BOTH’ d2&d3 hardware interrupts
// We set the clockInterrupt in the ISR, deal with that now:
if (clockInterrupt) {
if (rtc.checkIfAlarm(1)) { //Is the RTC alarm still on?
rtc.turnOffAlarm(1); //then turn it off.
}
clockInterrupt = false; //reset the interrupt flag to false
} // terminates: if (clockInterrupt)
//now go back to the start of the MAIN loop and start the cycle over again
//====================================================================================================
} // terminator the MAIN LOOP
//====================================================================================================
//====================================================================================
// Stand alone functions called from the main loop:
//====================================================================================
// This is the Interrupt subroutine that only executes when the RTC alarm goes off:
void rtcISR() { //called from attachInterrupt(0, rtcISR, LOW);
clockInterrupt = true;
}
//====================================================================================
void clearClockTrigger() // from http://forum.arduino.cc/index.php?topic=109062.0
{
Wire.beginTransmission(0x68); //Tell devices on the bus we are talking to the DS3231
Wire.write(0x0F); //Tell the device which address we want to read or write
Wire.endTransmission(); //Before you can write to and clear the alarm flag you have to read the flag first!
Wire.requestFrom(0x68, 1); //Read one byte
bytebuffer1 = Wire.read(); //In this example we are not interest in actually using the byte
Wire.beginTransmission(0x68); //Tell devices on the bus we are talking to the DS3231
Wire.write(0x0F); //Status Register: Bit 3: zero disables 32kHz, Bit 7: zero enables the main oscilator
Wire.write(0b00000000); //Bit1: zero clears Alarm 2 Flag (A2F), Bit 0: zero clears Alarm 1 Flag (A1F)
Wire.endTransmission();
clockInterrupt = false; //Finally clear the flag we used to indicate the trigger occurred
}
//====================================================================================
// Enable Battery-Backed Square-Wave Enable on the DS3231 RTC module:
/* Bit 6 (Battery-Backed Square-Wave Enable) of DS3231_CONTROL_REG 0x0E, can be set to 1
* When set to 1, it forces the wake-up alarms to occur when running the RTC from the back up battery alone.
* [note: This bit is usually disabled (logic 0) when power is FIRST applied]
*/
void enableRTCAlarmsonBackupBattery() {
Wire.beginTransmission(DS3231_I2C_ADDRESS); // Attention RTC
Wire.write(DS3231_CONTROL_REG); // move the memory pointer to CONTROL_REG
Wire.endTransmission(); // complete the ‘move memory pointer’ transaction
Wire.requestFrom(DS3231_I2C_ADDRESS, 1); // request data from register
byte resisterData = Wire.read(); // byte from registerAddress
bitSet(resisterData, 6); // Change bit 6 to a 1 to enable
Wire.beginTransmission(DS3231_I2C_ADDRESS); // Attention RTC
Wire.write(DS3231_CONTROL_REG); // target the register
Wire.write(resisterData); // put changed byte back into CONTROL_REG
Wire.endTransmission();
}
//========================================================================================
void error_shutdown() {
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
// spend some time flashing red indicator light on error before shutdown!
for (int CNTR = 0; CNTR < 100; CNTR++) {
pinMode(RED_PIN, INPUT_PULLUP);
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_ON);
digitalWrite(RED_PIN, LOW);
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_ON);
}
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_ON); //BOD is left on here to protect the processor
}
//====================================================================================
int getRailVoltage() // modified from http://forum.arduino.cc/index.php/topic,38119.0.html
{
int result; // gets passed back to main loop
int value; // temp variable for the conversion to millivolts
// ADC configuration command for ADC on 328p based Arduino boards // REFS1 REFS0 --> 0 1, AVcc internal ref. // MUX3 MUX2 MUX1 MUX0 -->1110 sets 1.1V bandgap
ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0);
// Note: changing the ADC reference from the (default) 3.3v rail to internal 1.1V bandgap can take up to 10 msec to stabilize
for (int i = 0; i < 7; i++) { // loop several times so the capacitor connected to the aref pin can discharge down to the 1.1v reference level
ADCSRA |= _BV(ADSC); // Start an ADC conversion
while (((ADCSRA & (1 << ADSC)) != 0))
; // makes the processor wait for ADC reading to complete
value = ADC; // value = the ADC reading
delay(1); // delay time for capacitor on Aref to discharge
} // for(int i=0; i <= 5; i++)
ADMUX = bit(REFS0) | (0 & 0x07);
analogRead(A0); // post reading cleanup: select input channel A0 + re-engage the default rail as Aref
result = (((InternalReferenceConstant) / (long)value)); //scale the ADC reading into milliVolts
return result;
} // terminator for getRailVoltage() function
//====================================================================================
// Separate functions to read light Level with the 3 RGB LED color channels
//===================================================================================================
// Modfied from //https://playground.arduino.cc/Learning/LEDSensor I added PIND for speed
// An explaination of the reverse-bias LED reading technique at https://www.sparkfun.com/news/2161
// the readings get smaller as the amount of light increases and the response is logarithmic
// Note: do not try to sleep the processor during the counting loop - when sleeping, any GPIO that
// is not used an an interrupt input has its input buffer disconnected from the pin and is clamped LOW by the 'sleep' MOSFET.
#ifdef readRedLED
//==========================
uint32_t readRedLEDchannel() {
power_all_disable(); //disable all the chip peripherals to reduce spurious interrupts
uint32_t loopTime;
byte gndPin = (1 << LED_GROUND_PIN);
//'discharge'all LED channels before reading
digitalWrite(LED_GROUND_PIN, LOW);
pinMode(LED_GROUND_PIN, OUTPUT);
pinMode(BLUE_PIN, INPUT_PULLUP);
pinMode(GREEN_PIN, INPUT_PULLUP);
pinMode(RED_PIN, INPUT_PULLUP);
digitalWrite(BLUE_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, LOW);
pinMode(RED_PIN, OUTPUT);
pinMode(LED_GROUND_PIN, INPUT_PULLUP); //Reverses Polarity to charge LED's internal capacitance
_delay_us(24); //alternative to delayMicroseconds()//calls to __builtin_avr_delay_cycles(), which are compiled into delay loops.
digitalWrite(LED_GROUND_PIN, LOW);
for (loopTime = 0; loopTime < 1200000; loopTime++) { // Counts how long it takes the LED to fall to the logic 0 voltage level
if ((PIND & gndPin) == 0) break; // equivalent to: "if (digitalRead(LED_GROUND_PIN)=LOW) stop looping"
//PIND uses port manipulation so executes much faster than digitalRead-> increasing the resolution of the sensor
}
power_all_enable();
pinMode(RED_PIN, INPUT); //not needed all pins in input now
pinMode(LED_GROUND_PIN, OUTPUT); //back to normal 'ground' pin operation
return loopTime;
} // terminator
#endif readRedLED
#ifdef readGreenLED //this function READs Green channel of 3-color indicator LED
//=============================
uint32_t readGreenLEDchannel() {
power_all_disable(); // stops All TIMERS, ADC, TWI, SPI, USART
uint32_t loopTime; //uint32_t max of 4 294 967 295
byte gndPin = (1 << LED_GROUND_PIN);
//'discharge' all LED channels before reading
digitalWrite(LED_GROUND_PIN, LOW);
pinMode(LED_GROUND_PIN, OUTPUT);
pinMode(BLUE_PIN, INPUT_PULLUP);
pinMode(RED_PIN, INPUT_PULLUP);
pinMode(GREEN_PIN, INPUT_PULLUP);
digitalWrite(BLUE_PIN, LOW);
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
pinMode(GREEN_PIN, OUTPUT); //Reverse Polarity on the color channel being read
pinMode(LED_GROUND_PIN, INPUT_PULLUP); //to charge LED's internal capacitance
_delay_us(24); //alternative to delayMicroseconds()//calls to __builtin_avr_delay_cycles(), which are compiled into delay loops.
digitalWrite(LED_GROUND_PIN, LOW);
for (loopTime = 0; loopTime < 1200000; loopTime++) { // on my led 1.2M goes to approximately 0 LUX
if ((PIND & gndPin) == 0) break; // equivalent to: "if (digitalRead(LED_GROUND_PIN)=LOW) stop looping"
//this loop takes almost exactly one microsecond to cycle @ 8mhz
}
power_all_enable();
pinMode(GREEN_PIN, INPUT);
pinMode(LED_GROUND_PIN, OUTPUT); //back to normal 'ground' pin operation
return loopTime;
} // terminator for readGreenLEDchannel() function
#endif //if readGreenLED
#ifdef readBlueLED //this function READs BLUE channel of 3-color indicator LED
//============================
uint32_t readBlueLEDchannel() {
power_all_disable(); // stops All TIMERS, ADC, TWI, SPI, USART
uint32_t loopTime = 0;
uint64_t startTime = 0;
byte gndPin = (1 << LED_GROUND_PIN);
//'discharge' all LED channels before reading
digitalWrite(LED_GROUND_PIN, LOW);
pinMode(LED_GROUND_PIN, OUTPUT);
pinMode(BLUE_PIN, INPUT_PULLUP);
pinMode(GREEN_PIN, INPUT_PULLUP);
pinMode(RED_PIN, INPUT_PULLUP);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
pinMode(BLUE_PIN, OUTPUT);
pinMode(LED_GROUND_PIN, INPUT_PULLUP); //Reverses Polarity to charge LED's internal capacitance
_delay_us(24); //alternative to delayMicroseconds()//calls to __builtin_avr_delay_cycles(), which are compiled into delay loops.
digitalWrite(LED_GROUND_PIN, LOW);
for (loopTime = 0; loopTime < 1200000; loopTime++) { //loopTime prevents us from counting forever if pin does not fall
if ((PIND & gndPin) == 0) break; // equivalent to: "if (digitalRead(LED_GROUND_PIN)=LOW) stop looping"
//this loop takes almost exactly one microsecond to cycle @ 8mhz
}
power_all_enable(); //re-enable our peripherals
pinMode(BLUE_PIN, INPUT);
pinMode(LED_GROUND_PIN, OUTPUT); //back to normal 'ground' pin operation
return loopTime;
} // terminator for readBlueLEDchannel() function
#endif //readBlueLED
//================================================================================================
// SIGNAL PROCESSING FUNCTIONS
//================================================================================================
/*
This median3 filter is pretty good at getting rid of single NOISE SPIKES from flakey sensors
(It is better than any low pass filter, moving average, weighted moving average, etc.
IN TERMS OF ITS RESPONSE TIME and its ability to ignore such single-sample noise spike outliers.
The median-of-3 requires very little CPU power, and is quite fast.
*/
// pass three separate positive integer readings into this filter:
// for more on bitwise xor operator see https://www.arduino.cc/reference/en/language/structure/bitwise-operators/bitwisexor/
int median_of_3(int a, int b, int c) { // created by David Cary 2014-03-25
int the_max = max(max(a, b), c);
int the_min = min(min(a, b), c);
int the_median = the_max ^ the_min ^ a ^ b ^ c;
return (the_median);
} // teriminator for median_of_3
/*
// for continuous readings, drop oldest int value and shift in latest reading before calling this function:
oldest = recent;
recent = newest;
newest = analogRead(A0);
*/
//================================================================================================
// NOTE: for more complex signal filtering, look into the digitalSmooth function with outlier rejection
// by Paul Badger at http://playground.arduino.cc/Main/DigitalSmooth works well with acclerometers, etc
PS: I know most of this could be implemented in a simpler way than using an EEPROM. However, the logger will be in a basement without WiFi, and I have no interest in reading a lot of data from the system (so no SD card etc.). I also wanted to experiment with using an external EEPROM, as it is something new to me.