Updating the seconds of a clock with multiple pages

I got a Arduino Leonardo to play with :slight_smile: Without any programming exprience, the first thing I tried to do with it is connecting a RTC and LCD (both I2C) to display date/time/texts in pages.

With help of examples it's working in the basics :slight_smile: It starts with showing "Good morning/midday/evening/night" depending on the hour, a welcome-text, the date and the time. Each page is displayed for 3 seconds.

But now I want the time-page to show the running seconds as well. At this moment the seconds are just static, not updated in the 3 seconds it's showing :frowning:

I want to have the seconds updating during the 3 seconds the time is shown, but I don't know how to do that :frowning: I already have looked at the "BlinkWithoutDelay" example, but doesn't understand how I can use it in my case.

To make it easier to work with, I've moved the code for showing the time into a separated function tijdtonen(). At this moment the code works but the seconds are not updated during the time it's shown.

In essence, I only need it to run the tijdtonen() part for 3 seconds and then continue where it was in the loop().

This is the whole sketch I have now:

/*
Klok met meerdere paginas.
*/

// Include Wire Library for I2C
#include <Wire.h>
// Include NewLiquidCrystal Library for I2C
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
RTC_DS1307 RTC;

// Define LCD pinout
const int  en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;

// Define I2C Address - change if reqiuired
const int i2c_addr = 0x27;

LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

void setup()
{
  // Set display type as 16 char, 2 rows
  lcd.begin(16,2);


    Wire.begin();
    RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));

  }
}
  

void loop()
{

    DateTime now = RTC.now();
    

// Backlight aan
lcd.backlight();

// Dagdeel tonen
lcd.setCursor(2,0);

  if (now.hour() <= 5) {
    lcd.print("Goede nacht!");
  }

  if (now.hour() <= 11 && now.hour() >= 6) {
    lcd.print("Goede ochtend!");
  }

  if (now.hour() <= 17 && now.hour() >= 12) {
    lcd.print("Goede middag!");
  }

  if (now.hour() <= 23 && now.hour() >= 18) {
    lcd.print("Goede avond!");
  }

// Wacht 3 secondes
  delay(3000);

// LCD leegmaken
  lcd.clear();

// Welkom-tekst
  lcd.setCursor(5,0);
  lcd.print("Ik heet");
  lcd.setCursor(3,1);
  lcd.print("U welkom!");

// Wacht 3 secondes
  delay(3000);

// LCD leegmaken
  lcd.clear();

// Datum
  lcd.setCursor(0,0);
  lcd.print("Het is vandaag:");

  lcd.setCursor(1,1);

  // Dag-naam tonen
  if (now.dayOfTheWeek() == 0) {
    lcd.print("zo ");
  }
  if (now.dayOfTheWeek() == 1) {
    lcd.print("ma ");
  }
  if (now.dayOfTheWeek() == 2) {
    lcd.print("di ");
  }
  if (now.dayOfTheWeek() == 3) {
    lcd.print("wo ");
  }
  if (now.dayOfTheWeek() == 4) {
    lcd.print("do ");
  }
  if (now.dayOfTheWeek() == 5) {
    lcd.print("vr ");
  }
  if (now.dayOfTheWeek() == 6) {
    lcd.print("za ");
  }
  
  if (now.day() < 10) {
    lcd.print("0");
  }
  lcd.print(now.day(), DEC);
  lcd.print('-');
  if (now.month() < 10) {
    lcd.print("0");
  }
  lcd.print(now.month(), DEC);
  lcd.print('-');
  lcd.print(now.year(), DEC);

// Wacht 3 secondes
  delay(3000);

// LCD leegmaken
  lcd.clear();

// De tijd 
  tijdtonen();

// Wacht 3 secondes
  delay(3000);
    
// LCD leegmaken
  lcd.clear();

}

void tijdtonen()
  {
DateTime now = RTC.now();
    
 
  lcd.setCursor(4,0);
  lcd.print("De tijd:");
  
  lcd.setCursor(4,1); 
  if (now.hour() < 10) {
    lcd.print("0");
  }
  lcd.print(now.hour(), DEC);
  lcd.print(':');
  if (now.minute() < 10) {
    lcd.print("0");
  }
  lcd.print(now.minute(), DEC);
    lcd.print(':');
  if (now.second() < 10) {
    lcd.print("0");
  }
  lcd.print(now.second(), DEC);

  }

Welcome to the forum

What you must do is to eliminate delay() throughout the sketch., or at least anywhere that 2 or more things are required to happen at the same time

The BlinkWithoutDelay principle is simple but cannot be used a s drop-in replacement for delay(). Basically you save the value of millis() when a period, such as your 3 seconds, starts.

Then, by letting loop() run freely you can do whatever you want in loop() that does not block execution of the code. Each time through loop() you test whether the period has elapsed and if not carry on round loop() until it does

Have you seen
Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

I've read the millis biginners guide multiple times, but reading it is one thing, really understanding it is another :frowning:

It seems like the examples only do one thing in the loop() and let it running over and over again as long as the interval is not passed yet.

But in my case I don't want it to continue with the loop() as long as the interval is not passed yet (because then it will change the display to the next page...). Instead it should repeat a part of the code during the interval.

Theoretically I can put code in the beginning of the tijdtonen() function to store the CurrentMillis and then use "if" to determine if the interval is passed. If yes, it's just the end of tijdtonen() and it will continue with loop() as it now does. If not, put a line "tijdtonen();" to repeat this part. But then there's one big problem... the currentMillis will be reset each run making it run tijdtonen() forever...

Not if you don't let it

Consider using a "state machine". It sounds scary but simply means executing the code only for the state that the system is in until a trigger event, such as a period passing, occurs

A small example

enum states
{
  LED_ON,
  LED_OFF
};

states currentState;
const byte ledPin = 13;

unsigned long periods[] = {1000, 100};

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  currentState = LED_OFF;
  digitalWrite(ledPin, LOW);
}

void loop()
{
  unsigned long currentTime = millis();
  static unsigned long stateStartTime = currentTime;
  switch (currentState)
  {
    case LED_OFF: //execute the code for this state
      if (currentTime - stateStartTime >= periods[currentState]) //test if period ended
      {
        digitalWrite(ledPin, HIGH); //if so turn on LED
        currentState = LED_ON;  //change current state
        stateStartTime = currentTime; //save time this state started
      }
      break;
    case LED_ON: //execute the code for this state
      if (currentTime - stateStartTime >= periods[currentState]) //test if period ended
      {
        digitalWrite(ledPin, LOW); //if so turn off LED
        currentState = LED_OFF;  //change current state
        stateStartTime = currentTime; //save time this state started
      }
      break;
  };
  //other code goes here but it must not block the execution of loop()
}

A full treatment with a finite state machine (FSM) orientated approach would be the best, and learning to do would stand you in very good stead for future undertakings.

Having said that, in this case a half-way measure to accomplish what you want might be all you need. I'll call it a hack, as it leaves the benefits of an FSM unrealized - sooner later you'll want to learn how to let loop() run freely as it is meant to do.

This sequence of two lines

// De tijd 
  tijdtonen();

// Wacht 3 secondes
  delay(3000);

could be replaced with a while statement.

Using millis() and what we can hope you've learned or could learn about it, do this (not code yet, obvsly)

    get the current millis value

    while three seconds has not elapsed since then

         (re)display the current time

The rapid update of the time on the LCD might make it flicker; you could get clever and not bother to update the display if the seconds have not changed.

HTH

a7

I'd advocate a finite state machine too, where each page would be a case in the switch...case section. Changing from state to state (that is, page to page) would be on a 3-second "blink without delay timer". Updating the second would be outside of that, on a different, 1-second "blink without delay timer".

Slight change to my explanation: the seconds are are on the same "blink without delay timer" as the pages, that is start time is captured on the page change, it's just the change in the second displayed is every second and the change to the new page is after 3.

Try this much better explanation: https://www.baldengineer.com/blink-without-delay-explained.html

1 Like

I've read this about a state machine (Arduino State Machine Tutorial | Microcontroller Tutorials), it's still very complicated. And it doesn't seem to automatically repeat code (the example there is a motor controller with is set once after a command is received), so the problem of not updating the seconds won't be solved with it.

Oh, I didn't know about the while-function. This does the trick :smiley:

With this code put in that place it works now, the LCD isn't flickering.

  startMillis = millis();
  currentMillis = millis();
  while (currentMillis - startMillis <= 3000) {
    tijdtonen();
    currentMillis = millis();
  }

And this in the top of the sketch:

// Waardes nodig om Millis te gebruiken
unsigned long startMillis;
unsigned long currentMillis;

There you go.

But you owe it to yourself to keep in mind all the better advices… while you found nothing specific addressing your issue, be assured it is the way to really make good use of the limited resources these small machines have.

And could surely have solved your problem!

Your code would more usually be seen like below, it's an idiom you will see places. Also it's a while statement, not function. As you will discover.

    startMillis = millis();

    while (millis() - startMillis <= 3000)
        tijdtonen();

Ppl will say leave on the braces, even though the syntax allows their omission in the case of a single statement in the while body.

I live dangerously and have a less-ink-is-better attitude, and in case you or anyone is wondering, it does bite me in the you know what form time to time. :expressionless:

a7

1 Like

I'm glad that while() worked for you in this example, but it's a thing that can bite you if used without care. It's what's known as "blocking" since the code sits in the while() while it's busy doing whatever- that means nothing else in the code can happen at the same time. Now that wasn't an issue in this case, but might be, one day.

The state machine approach where you take advantage of the fact that loop() loops and you can get away with if() rather than while() allows you to do loads of things at the same time. (Ok, not really "at the same time" but near enough as to make no practical difference if coded properly.)

As @alto777 says:

2 Likes

If your project is (or contains) a real-time clock, then you can use the time itself to determine the state you should be in. For example, suppose you have a function that shows the time, called showTheTime, and one that shows the date, called showTheDate. Then, you can do this to show the time and date each for 3 seconds:

if ((now.second() % 6) < 3) {
  showTheTime();
}
else {
  showTheDate();
}

I have used this kind of time-based "state machine" to control the chiming of several clocks I have made.

1 Like

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