Datalogger: gibberish serial monitor output, retaining EEPROM values on shutdown

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.

Hello

I'm almost sure your problem will be solved if you use char arrays and strcpy, instead of Strings

I'm in agreement with @guix here. You have a lot going on and a limited amount of RAM available. Your serial output looks fine. I suspect that some of your strings have got corrupted resulting in the gibberish that you are seeing.

Thanks, @markd833 and @guix.
I am not entirely comfortable with how use char, strings, etc., but I tried the following with strcpy:

[...]
char TimeStamp[] = "00/00/0000,00:00";    //16 ascii characters (without seconds because they are always zeros on wakeup)
[...]
struct minTemp {
  float temp = 80;
  //float hum;
  char t[17];
};
minTemp minTempRecord;

struct maxTemp {
  float temp = -40;
  //float hum;
  char t[17];
};
maxTemp maxTempRecord;

struct maxHum {
  //float temp;
  float hum = 0;
  char t[17];
};
maxHum maxHumRecord;

struct minHum {
  //float temp;
  float hum = 80;
  char t[17];
};
minHum minHumRecord;
[...]
 if (temp.temperature > maxTempRecord.temp) {
    maxTempRecord.temp = temp.temperature;
    strcpy(maxTempRecord.t, TimeStamp);
    ee.writeBlock(100, (uint8_t *)&maxTempRecord, sizeof(maxTempRecord));
  }

  if (temp.temperature < minTempRecord.temp) {
    minTempRecord.temp = temp.temperature;
    strcpy(minTempRecord.t, TimeStamp);
    ee.writeBlock(0, (uint8_t *)&minTempRecord, sizeof(minTempRecord));
  }

  if (humidity.relative_humidity > maxHumRecord.hum) {
    maxHumRecord.hum = humidity.relative_humidity;
    strcpy(maxHumRecord.t, TimeStamp);
    ee.writeBlock(300, (uint8_t *)&maxHumRecord, sizeof(maxHumRecord));
  }

  if (humidity.relative_humidity < minHumRecord.hum) {
    minHumRecord.hum = humidity.relative_humidity;
    strcpy(minHumRecord.t, TimeStamp);
    ee.writeBlock(200, (uint8_t *)&minHumRecord, sizeof(minHumRecord));
  }

Left the rest as it was. I then forced a new max humidity (and temperature) measurement by blowing at the sensor during startup. Both the new max temp and max hum entries are showing up correctly.

bilde

But I guess the only way to know for sure if it is really working is to wait until I have a new min/max entry, or to remove the following from setup, so that I will have a new min/max for every power-on:

[...]
// 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));
[...]

EDIT: Maybe worth noting that the gibberish in the serial output looks slightly different now, so the code change definetely did something :slight_smile:

putting a String into a struct is a very bad idea.
As variables of type String get assigned a new RAM-adress each time you assign a new value.
There is not much "structuring in a struct that contains the same value as float and as character-sequence.

remove the structs and use the SafeString-library
which offers the same comfort as Strings but does not eat up all memory over time like Strings do

best regards Stefan

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.