DS3231 error 1 min for 10 min period of time

Hello ,
i am using arduino pro micro , and RTC DS3231 .
The task is to make 10 min timer ....

The code work like this :

If the button is pressed
take time now in unixtime .
end_time = time_now + 600 // time now + 600 seconds
loop
if time_now >= end_time
led off .

The problem is , that the led turn off about a minute earlier ...
Does this mean that RTC is not working properly and is incrementing the time faster than normal?
i don`t use delay() or millis() thats why I expected the time to be a bit more accurate ....

any ideas ?

Thanks

The problem is almost certainly in the sketch that you have not posted

1 Like

You don't need an RTC to do that, millis() is still enough for that interval with good precision (and with an RTC you don't have any precision better than one second, while millis have milliseconds...).

:laughing:
Maybe PBKAC sometimes... :wink:


#include "RTClib.h"


const uint32_t SECONDS_PER_MINUTE = 60;
const int duration = 10 ;
const int BUTTON_PIN = 2;
RTC_DS3231 rtc;


bool ledOn = false; 
uint32_t end_time; 
bool timeExpired = false; 

void setup() {
 
  pinMode(5, OUTPUT);
  pinMode(2, INPUT);

  
   if (!rtc.begin()) {
    Serial.println("RTC is NOT running!");
       rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  else {
    Serial.println("RTC is running!");
  }
}

void loop() {
 
   if (digitalRead(BUTTON_PIN) == HIGH) {
         switchOnLED(duration);
  }
      
   if (ledOn && rtc.now().unixtime() >= end_time) {
    digitalWrite(5, LOW);
    ledOn = false;
  }
 
 
}

void switchOnLED(int duration) {
  if (!ledOn) { 
    ledOn = true;
    DateTime now = rtc.now();
    uint32_t start_time = now.unixtime();
    
    end_time = start_time + (duration * SECONDS_PER_MINUTE);
      digitalWrite(5, HIGH);
  }

  if (ledOn && (uint32_t)rtc.now().unixtime() >= end_time) { 
    timeExpired = true;
  }
 }

void switchOffLED() {
  if (ledOn) { 
    digitalWrite(5, LOW);
    ledOn = false;
  }
}

Use millis() and stop shooting flies with a flame thrower... :wink:

precisely because i had big inconsistencies using millis() i decided to use rtc.
But apparently the problem is elsewhere...
I read somewhere that if the crystal is not good, such a discrepancy can occur.
In theory, the error when using millis() for one hour should be 3.6 seconds. Which is quite enough for me. But now I have 1 minute every 10 minutes which is too much...

You need to debounce your button. Also, do you have a pull-up resistor connected to your button? You will need one based on your code. If not, it is better to connect one side of the button to ground and the other to your input pin and declare that pin as INPUT_PULLUP. It will reverse the logic of the button (LOW == pressed)

#include "RTClib.h"


const uint32_t SECONDS_PER_MINUTE = 60;
const int duration = 10 ;
const int BUTTON_PIN = 2;
const int LED_PIN = 5;

RTC_DS3231 rtc;


bool ledOn = false;
uint32_t end_time;
int buttonState;

void setup() {

  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT);


  if (!rtc.begin()) {
    Serial.println("RTC is NOT running!");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  else {
    Serial.println("RTC is running!");
  }
  buttonState = digitalRead(BUTTON_PIN);
}

void loop() {

  int state = digitalRead(BUTTON_PIN);

  if (state != buttonState) {
    // button has changed state
    if ( state == HIGH ) {
      // button was pressed, start timer if not running, else ignore
      if (!ledOn) {
        switchOnLED(duration);
      }
    }
    else {
      // button was released, nothing to do
    }
    delay(20);  // debounce
  }
  buttonState = state;

  // check for expiration
  if (ledOn && (uint32_t)rtc.now().unixtime() >= end_time) {
    switchOffLED();
  }
}

void switchOnLED(int duration) {
  ledOn = true;
  end_time = rtc.now().unixtime() + duration * SECONDS_PER_MINUTE;
  digitalWrite(LED_PIN, HIGH);
}

void switchOffLED() {
  ledOn = false;
  digitalWrite(LED_PIN, LOW);
}

I don't know what kind of "inconsistences" you experienced, the only one you could see is whan the internal counter rolls to zero (being an "unsigned long" this happens once every 49 days of continuous power).
This can be taken in consideration avoiding to test millis() like this:

if (millis() >= startMillis + Interval)

this is the correct syntax:

if (millis() - startMillis >= Interval)

UNO (and others) has ceramic resonators with an accuracy of about ±0.5% and due to some other internal drifts. It means over 10 minutes you could have a precision of around ±5 ms, so this is almost irrilevant.
With micros() you could have a lower drift, unless the interval you need is below 70 minutes.

I'm not aware of any other drawbacks when using millis().

1 Like

yes, you was right. that the problem is not in the crystal, because I wrote a program that, when the microcontroller is turned on, starts an LED for 10 minutes and the time was correct.
This means that I have an error in the calculations.

This code also has timing inaccuracies.
Can you direct me where I'm going wrong please:
Here i send sms with text ON and number in minutes .
Lets say if i send ON10 , the led should light for 10 minutes . But it go off earlier ...

void loop() {
  if (SIM800.available()) {
   
    String incomingMessage = SIM800.readString();
    incomingMessage.toLowerCase(); 
 
    if (incomingMessage.indexOf("on") != -1) {
   
      int ledTime = incomingMessage.substring(incomingMessage.indexOf("on")+2).toInt() * 60;
            digitalWrite(ledPin, HIGH);
      ledOn = true;
      unsigned long startTime = millis();
      unsigned long endTime = startTime + (ledTime * 1000UL);  //if (millis() - startMillis >= Interval)
      while (millis() < endTime) {
     
        if (SIM800.available()) {
          String stopMessage = SIM800.readString();
          stopMessage.toLowerCase(); 
          if (stopMessage.indexOf("stop") != -1) {
            digitalWrite(ledPin, LOW);
            ledOn = false;
            break;
          }
        }
      }
      if (ledOn) {
        digitalWrite(ledPin, LOW);
        ledOn = false;
      }
    }
  }
}

hello , folks .
I found the problem .

 incomingMessage.toLowerCase();  

When i use this for formating the text to lower case , i dont know how and why but it change the numbers too ....
I remove it from the code and now work very well ...

I must find a another solution for lower case ....

Result :slight_smile:

23:14:31.901 -> LED ON for 3600 seconds

00:14:28.022 -> LED TimeOut

Good, but I'm not convinced about that, I still wonder how this could cause that issue, I don't like things I don't understard...

I tried this code on Tinkercad:

void setup()
{
  Serial.begin(9600);
  String incomingMessage = "ON10";

  Serial.print("Incoming=\"");Serial.print(incomingMessage);Serial.println("\"");
  incomingMessage.toLowerCase(); 
  Serial.print("toLowerCase=\"");Serial.print(incomingMessage);Serial.println("\"");
  if (incomingMessage.indexOf("on") != -1) {
    Serial.println("'on' received!");
    int ledTime = incomingMessage.substring(incomingMessage.indexOf("on")+2).toInt() * 60;
    unsigned long startTime = millis();
    unsigned long endTime = startTime + (ledTime * 1000UL);  //if (millis() - startMillis >= Interval)
    
    Serial.print("ledTime=");Serial.println(ledTime);
    Serial.print("startTime=");Serial.println(startTime);
    Serial.print("endTime=");Serial.println(endTime);
  }
}

void loop()
{
}

And I got this result:

Incoming="ON10"
toLowerCase="on10"
'on' received!
ledTime=600
startTime=2
endTime=600002

I can't see anything strange after toLowerCase(), and the resultimg time is correctly a 10 minutes delay (e.g. 600,000 milliseconds)...
What can you see if on your code try this below when receiving a message?

  Serial.print("Incoming=\"");Serial.print(incomingMessage);Serial.println("\"");
  incomingMessage.toLowerCase(); 
  Serial.print("toLowerCase=\"");Serial.print(incomingMessage);Serial.println("\"");

Meanwhile, I don't suggest you to test the end time like this:

while (millis() < endTime) {

because you'll have problems when millis() overlaps.
Always use the format I suggested you on post #8 (and you put inside comments but not applied), in this case something like:

      // ledTime contains the time in minutes as read from the message
      unsigned long ledTime = incomingMessage.substring(incomingMessage.indexOf("on")+2).toInt();
            digitalWrite(ledPin, HIGH);
      // Convert the time to milliseconds
      ledTime = ledTime * 60000;
      ledOn = true;
      unsigned long startTime = millis();
      while (millis() -startTime <= ledTime) {

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