DS3231: 3600000 milliseconds = 1 hour - 3 seconds?

I want to make sensor readings every hour and use a millis() timer with a 3600000 millisecond (1 hour = 3.600.000 milliseconds) interval.
Yet when I review the readings I receive results every hour - 3 seconds.

The code inside the timer loop consists of approx 20 lines of simple code without loops, nor calculations nor anything to justify this 3 second reduction in the timing of a day.

Why would this be?

#include <SD.h>
#include <SPI.h>
#include <DS3231.h>
#include <Wire.h>
#include "Adafruit_HTU21DF.h"
Adafruit_HTU21DF htu = Adafruit_HTU21DF();
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>

//Setup connection of the sensor
Adafruit_BMP280 bmp; // I2C

File data_file;
DS3231  rtc(SDA, SCL);
const int lm35_pin = A0; 
int temperature;  
int chip_select_pin = 4;     //pin 53 for arduino mega 2560: CS for SD card reader
unsigned long previousMillis = 3600000;
const long interval = 3600000; //3600000 = elk uur
int pressure;   //To store the barometric pressure (Pa)
// float temperature;  //To store the temperature (oC)
int altimeter;    //To store the altimeter (m) (you can also use it as a float variable)


void setup() {

  bmp.begin();
  Serial.begin(9600);
  rtc.begin();  
  pinMode(lm35_pin, INPUT);
  pinMode(chip_select_pin, OUTPUT);
  if (SD.begin())
  {
    Serial.println("Initialization Successful. Ready to use");
  } else
  {
    Serial.println("Initialization failed. Check your pin connections or change your SD card");
    return;
    
   if (!htu.begin()) {
    Serial.println("Couldn't find sensor!");
    while (1);
  }
  }
}
void loop() {

  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
  previousMillis = currentMillis;

  
  

  data_file = SD.open("test.txt", FILE_WRITE);
  if (data_file) {    
    Serial.print(rtc.getTimeStr());
    data_file.print(rtc.getTimeStr());
    Serial.print(" , ");
    data_file.print(",");  
    Serial.print(rtc.getDateStr());
    data_file.print(rtc.getDateStr());
    Serial.print(" , ");
    data_file.print(",");
    temperature = analogRead(lm35_pin);
    temperature = (temperature*500)/1023;  
    Serial.print("Temp: "); 
    Serial.print(temperature);
    data_file.print(temperature);
    Serial.print(" , ");
    data_file.print(",");
    pressure = bmp.readPressure()/100;
    temperature = bmp.readTemperature();
    altimeter = bmp.readAltitude (1050.35); //Change the "1050.35" to your city current barrometric pressure (https://www.wunderground.com)
    Serial.print(temperature);
    data_file.print(temperature);
    data_file.print(",");
    Serial.print(" , ");
    Serial.print(F("Pressure: "));
    Serial.print(pressure);
    data_file.print(pressure);
    data_file.print(",");
    Serial.print(" hPa");
    data_file.print(",");
    Serial.print(" , ");
    Serial.print(htu.readTemperature());
    data_file.print(htu.readTemperature());
    Serial.print(" , ");
    data_file.print(",");
    Serial.print("Hum: "); Serial.println(htu.readHumidity());
    data_file.println(htu.readHumidity());
    data_file.close();
  }
  
  else {
    Serial.println("error opening your SD card file. Try again");
  }
  }

}

You are using the millis for an action but the rtc for time printing. Consider that RTC has its own crystal while the millis() is driven by main clock and main crystal or resonator which is not accurate as RTC. In case of genuine Arduinos, the resonator is used which is pretty inaccurate.
The millis() is sufficiently accurate for some purposes but not for time measurement. You should use RTC directly if your APP requires it.
You can use !INT/SQW output from DS3231 to drive the time or seconds counter. The DS3231 has 32768Hz signal on it by default which can be used to drive Timer2 on the ATmega MCU to obtain accurate 1s interrupts. In case of Arduino UNO and ATmega328P (you didn't mention which one you have) the pins for the Timer2 and main clock crystal are shared so it is unable to use the Timer2. The solution is to use HW interrupt and to change !INT/SQW output on DS3231 to be 1Hz thus 1s interrupt can be obtained.

Budvar10:
The solution is to use HW interrupt and to change !INT/SQW output on DS3231 to be 1Hz thus 1s interrupt can be obtained.

That is a solution. It is not necessarily the solution.

Another possibility would be to use the millis() timer to time, for example, 58 minutes, then check the RTC to find out how many seconds over or under 58 minutes we really were. If it turned out that our "58 minutes" was really 57 minutes and 55 seconds, then we would know to wait 2 minutes and 5 seconds to finish up the hour. If instead our attempt at 58 minutes was really 58 minutes and 10 seconds, then we would know to wait only 1 minute and 50 seconds to finish up the hour.

Yet another possibility would be to use the built-in alarm feature of the DS3231. I know it exists, but I've never actually used it and I don't know how to use it, so you're on your own there.

@odometer
Ooo, the solution which you suggested is poor, sorry. I didn't say that my one is the only. Of course there is a lot of ways, better or worse, but we would be a bit level.

Budvar10:
You are using the millis for an action but the rtc for time printing. Consider that RTC has its own crystal while the millis() is driven by main clock and main crystal or resonator which is not accurate as RTC. In case of genuine Arduinos, the resonator is used which is pretty inaccurate.
The millis() is sufficiently accurate for some purposes but not for time measurement. You should use RTC directly if your APP requires it.
You can use !INT/SQW output from DS3231 to drive the time or seconds counter. The DS3231 has 32768Hz signal on it by default which can be used to drive Timer2 on the ATmega MCU to obtain accurate 1s interrupts. In case of Arduino UNO and ATmega328P (you didn't mention which one you have) the pins for the Timer2 and main clock crystal are shared so it is unable to use the Timer2. The solution is to use HW interrupt and to change !INT/SQW output on DS3231 to be 1Hz thus 1s interrupt can be obtained.

Hi Budvar10, this is with the Uno and ATmega328.
I have never used HW interrupts, or any interrupts whatsoever. Can you please let me know what I have to wire and what to change in my program?
Thank you!
Erik

Edit: I can see a 32k pin and a SQW pin on the DS3231.

It's easier than all that - just continually read the hour from the RTC and take a reading whenever it changes.

aarg:
It's easier than all that - just continually read the hour from the RTC and take a reading whenever it changes.

Now that's a nice elegant solution.

aarg:
It's easier than all that - just continually read the hour from the RTC and take a reading whenever it changes.

Instead of using millis() I then use

int currentMillis = rtc.getHourStr();
if (previousMillis < currentMillis) {
previousMillis = rtc.getHoursStr();

where I use currentMillis and previousMillis as int variable?

Edit: changed the code above a bit

But to get back to Budvar10's solution, I am still interested in his proposal beacuse it uses a hardware input to the Arduino, and that takes some of the burden out of software: how to use program the SQW output from the DS3231, andhow to use that in the Arduino Uno?

Hi Erik,
probably lots of similar setups exists and they are presented on the internet, just google for it. Anyway, I don't want to provide such simple advice, so...

DS3231 has fixed 32768Hz square wave on the 32k pin. It can drive 8-bit timer on the MCU to obtain accurate 1s interrupts. As I wrote, the UNO has no available Timer2 for this purpose since the pins are occupied by resonator for the main clock, so I am suggesting to use SQW and some of HW interrupts e.g. There are available INT0 and INT1. INT0 have higher priority and it is on Arduino's pin #2. BTW there is many pinout diagrams on the web also here on the UNO page - how the ATmega328P is mapped into Arduino's pins. Now it leads me to idea that you would study little bit DS3231 datasheet etc. :frowning:

Target config could be:

  1. to connect DS3231 SQW to INT0 (with pull-up, of course)
  2. to configure 1Hz on the SQW pin
  3. to create INT0 service function for time (seconds) update.
    Just in case look for the time library.

For the first test you can connect 32k (SQW is not configured by default) output to the #2 of arduino but pull-up resistor is required since both 32k and SQW are open-drain outputs. Then, you can connect function to INT0 e.g. rising edge with counter to 32768 and with update of counter for seconds when it overflow. It could be nice start.

Robert

I am at a loss to understand why you don't just use the RTC for timing 1 hour.

Why does the OP want to decrease software burden by changing hardware? Many people want to do the opposite.

UKHeliBob:
I am at a loss to understand why you don't just use the RTC for timing 1 hour.

Me too now after receiving this input :slight_smile:

Thanks guys,
Erik

vaj4088:
Why does the OP want to decrease software burden by changing hardware? Many people want to do the opposite.

Because basically I am a hardware guy :slight_smile:

Budvar10:
Hi Erik,
probably lots of similar setups exists and they are presented on the internet, just google for it. Anyway, I don't want to provide such simple advice, so...

DS3231 has fixed 32768Hz square wave on the 32k pin. It can drive 8-bit timer on the MCU to obtain accurate 1s interrupts. As I wrote, the UNO has no available Timer2 for this purpose since the pins are occupied by resonator for the main clock, so I am suggesting to use SQW and some of HW interrupts e.g. There are available INT0 and INT1. INT0 have higher priority and it is on Arduino's pin #2. BTW there is many pinout diagrams on the web also here on the UNO page - how the ATmega328P is mapped into Arduino's pins. Now it leads me to idea that you would study little bit DS3231 datasheet etc. :frowning:

Target config could be:

  1. to connect DS3231 SQW to INT0 (with pull-up, of course)
  2. to configure 1Hz on the SQW pin
  3. to create INT0 service function for time (seconds) update.
    Just in case look for the time library.

For the first test you can connect 32k (SQW is not configured by default) output to the #2 of arduino but pull-up resistor is required since both 32k and SQW are open-drain outputs. Then, you can connect function to INT0 e.g. rising edge with counter to 32768 and with update of counter for seconds when it overflow. It could be nice start.

Robert

Very right, your advice will be followed, including study of DS3231 datasheet :slight_smile:

Very right, your advice will be followed, including study of DS3231 datasheet

For goodness sake just read the time from the RTC and use that to determine that an hour has passed.

UKHeliBob:
For goodness sake just read the time from the RTC and use that to determine that an hour has passed.

Ok ok, I had such good advice from you in the past, I will heed this too! :slight_smile:

There are at least 3 DS3231 libraries that I know of: DS3231.h, DS3232RTC.h and RTC.h: which one is to be used when?
What difference is there between them?

brice3010:
There are at least 3 DS3231 libraries that I know of: DS3231.h, DS3232RTC.h and RTC.h: which one is to be used when?
What difference is there between them?

If you just want to get at the date and time, you can use my code from here:
https://forum.arduino.cc/index.php?topic=439759.msg3029413#msg3029413
No libraries needed.

odometer:
If you just want to get at the date and time, you can use my code from here:
DS3231, getDateStr query - #4 by odometer - Programming Questions - Arduino Forum
No libraries needed.

Thank you for that input.

Still the wire.h is needed: how much "overhead" is that?

What amount of memory does a typical DS3231 library actually use? I was led to believe that when a library is loaded in a program that not only the .h and .cpp gets loaded. What else then too?

I was led to believe that when a library is loaded in a program that not only the .h and .cpp gets loaded.

You were misled.

When you #include a header file, the only memory that gets used, or code that gets generated, is to deal with any global variables that the header file defines.

If the header file defines a class, and you create an instance of the class, then, obviously, more code is generated and made part of the hex file that is uploaded.

Nothing else is "loaded".