Scroll LCD text without using delay()

I’ve got a clock/timer project that I am working on for a 20x4 LCD display using an ESP8266. Line 1 (0) shows the current time, line 2 (1) shows the current date. I have various texts that I would like to display on lines 3 (2) and 4 (3).

Ideally, I attempted to use a ‘for’ loop, but the text scrolled so fast that you couldn’t even read it. Adding a delay() at any point is unacceptable because it will considerable throw off the time. I finally came to the conclusion of having to do it this way:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);
unsigned long prevMillis = 0;
unsigned long curMillis;

String ns = "Next Sunset:";
int i = 0;

void setup()
{
 Serial.begin(115200);
 lcd.begin(0, 2); 

 lcd.backlight();
 delay(1000);

}

void loop()
{
 curMillis = millis();
 if ((curMillis - prevMillis) == 1000) {
   lcd.setCursor(18, 2);
   lcd.print(ns.substring(0, 1));
 }
 if ((curMillis - prevMillis) == 2000) {
   lcd.setCursor(17, 2);
   lcd.print(ns.substring(0, 2));
 }
 if ((curMillis - prevMillis) == 3000) {
   lcd.setCursor(16, 2);
   lcd.print(ns.substring(0, 3));
 }
 if ((curMillis - prevMillis) == 4000) {
   lcd.setCursor(15, 2);
   lcd.print(ns.substring(0, 4));
 }
 if ((curMillis - prevMillis) == 10000) {
   lcd.clear();
   prevMillis = millis();
 }
}

This is just a sample, but it works perfectly for what I want to do. I just was looking for some other opinions (I am still a noob when it comes to programming) on whether or not there is an easier way to do this. For everything that I am looking to display, it would be hours of coding it this way. Please let me know your thoughts. Thanks!!

Well you could reduce it a little. Notice the pattern in where you set the cursor and where you cut the String?

Remember loop loops.

int i = 0;

void loop(){
  curMillis = millis();
    if( i < 4 ){
       if ((curMillis - prevMillis) == 1000) {
          lcd.setCursor(18 - i , 2);
          lcd.print(ns.substring(0, i + 1));
          prevMillis = curMillis;
          i++;
      }
    }
     else{
          if((curMillis - prevMillis) == 6000) {
              i = 0;
              prevMillis = curMillis;
              lcd.clear();
          }
    }
}

Please read the forum rules (How to use this forum - please read) and learn to put your code in the code tags like I did.

Delta_G has presented you with one alternative. There are always others.

First, the String class is not very efficient in terms of memory usage. Your program uses 6596 bytes. By dropping the String class and using a char array, it drops to 5330 bytes.

Second, if the first test:

if ((curMillis - prevMillis) == 1000) {

is logic True, your code proceeds to make 4 more tests that can’t possibly be true. A cascading if statement block is a common solution to this inefficiency. Delta_G’s solution uses this.

However, you can also use a switch statement block which results in a jump table instead of logic tests and should be marginally faster. The code below compiles to 5170 bytes, which saves you over 1400 bytes and probably executes a little faster.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);
unsigned long prevMillis = 0;
unsigned long curMillis;

char ns[] = "Next Sunset:";
int i = 0;

void setup()
{
  Serial.begin(115200);
  lcd.begin(0, 2);

  lcd.backlight();
  delay(1000);

}

void loop()
{
  static unsigned long diff;
  char temp[6];

  curMillis = millis();
  diff = curMillis - prevMillis;
  switch (diff) {
    case 1000:
      lcd.setCursor(18, 2);
      lcd.print(strncpy(temp, ns, 2));
      break;
    case 2000:
      lcd.setCursor(17, 2);
      lcd.print(strncpy(temp, ns, 3));
      break;
    case 3000:
      lcd.setCursor(16, 2);
      lcd.print(strncpy(temp, ns, 4));
      break;
    case 4000:
      lcd.setCursor(15, 2);
      lcd.print(strncpy(temp, ns, 5));
      break;
    case 10000:
      lcd.clear();
      prevMillis = millis();
      break;
  }
}

Thank you, I appreciate the assistance and advice.

Delta_G’s method worked out perfectly (with some minor changes). This allowed me to finish up this portion of the project.

Econjack – Speed is not really a concern for this. The purpose of this project is to turn on/off some outdoor lights via a connected relay. The complete code updates the time via NTP every hour, and pulls sunrise and sunset times from Weather Underground and updates accordingly. The scrolling text wasn’t entirely necessary, but just something I thought would look cool once completed. The display currently shows the time on the first line and the date on the second line. The 3rd line scrolls text across the screen but scrolls through 4 different strings. The fourth line displays the time that the current text on the third line refers to. I use a switch statement with a counter to determine which text scrolls, and I broke down each ‘set’ of scrolling text into individual functions. Here is just one example:

void scrollSunset() {
  curMillis = millis();
  if (i < 20) {
    if ((curMillis - prevMillis) >= 300) {
      lcd.setCursor(19 - i , 2);
      lcd.print(nss.substring(0, i + 1));
      lcd.setCursor(((20 - astronomy.sunsetTime.length()) / 2), 3);
      lcd.print(astronomy.sunsetTime);
      prevMillis = curMillis;
      i++;
    }
  }
  else {
    if ((curMillis - prevMillis) == 5000) {
      i = 0;
      prevMillis = curMillis;
      lcd.setCursor(0, 2);
      lcd.print("                    ");
      lcd.setCursor(0, 3);
      lcd.print("                    ");
      x++;
    }
  }
}

I know there is almost always more than one way to do things, but I have my code working properly now using several ‘if’ statements. I knew that strings aren’t very memory-friendly, but I wasn’t aware that a char array would be that much smaller. That is definitely something I will look into.

I have one other quick question I’ll ask here, rather than start a new thread: My complete code looks like it is going to be 500+ lines. Is there any problem moving functions onto a different tab?? For example, I currently have a second tab named ‘setbyntp’ and it contains only the functions used in order to update the time. I have another tab named ‘wunderground’ that contains the functions for the scrolling text and updates. The code compiles and runs perfectly. Is this OK, or should ALL of the code be on just one page?

I knew that strings aren't very memory-friendly

Please don't confuse strings with Strings.

This construct if ((curMillis - prevMillis) == 1000) is risky. Much safer isif ((curMillis - prevMillis) >= 1000)

econjack: However, you can also use a switch statement block which results in a jump table instead of logic tests and should be marginally faster.

And I've been wondering what the difference(s) might be between a string of if()s and a switch/case. Thanks!

dougp: And I've been wondering what the difference(s) might be between a string of if()s and a switch/case. Thanks!

The way I understand it is that in a switch/case, the switch only has to read the variable. In an if statement, there has to be some sort of comparison. Switch/case statements should be used when there are a lot of options....it makes it more efficient and easier to write than a ton of if statements.

AWOL: This construct

if ((curMillis - prevMillis) == 1000)

is risky. Much safer is

if ((curMillis - prevMillis) >= 1000)

Oops, I missed that one! After I originally wrote it with '==' I thought "what if it gets hung up at some point and skips over that time??" Thanks, I will update it...

AWOL: Please don't confuse strings with Strings.

...sorry, I meant 'String'.

off topic but you really need to correct this:

lcd.begin(0, 2);

Your display has more than 0 columns and 2 rows. You are lucky that the columns parameter is ignored and in the library you are using there is a bug in the setCursor() code that allows you to set the cursor position to 1 row beyond the number of rows you initialized the library with in begin(). i.e. you told you told the library that have two rows and then are setting the cursor position to row 2 which is the 3rd line on the display.

If you tried to use line 3 with setCursor() it would write on line 1 since you told it there were only 2 lines. (0 and 1) and when the row is too large the code uses the largest row possible.

--- bill

bperrybap: off topic but you really need to correct this:

lcd.begin(0, 2);

Your display has more than 0 columns and 2 rows. You are lucky that the columns parameter is ignored and in the library you are using there is a bug in the setCursor() code that allows you to set the cursor position to 1 row beyond the number of rows you initialized the library with in begin(). i.e. you told you told the library that have two rows and then are setting the cursor position to row 2 which is the 3rd line on the display.

If you tried to use line 3 with setCursor() it would write on line 1 since you told it there were only 2 lines. (0 and 1) and when the row is too large the code uses the largest row possible.

--- bill

The library that I am using specifies that in order to start communication with the lcd, you need to use lcd.begin(sda, scl). In this case, sda is pin 0, scl is pin 2.

sfgelectronics: The library that I am using specifies that in order to start communication with the lcd, you need to use lcd.begin(sda, scl). In this case, sda is pin 0, scl is pin 2.

What's the download URL for your LCD library. I've not seen one that asks for the clock and data lines with the begin() method.

econjack:
What’s the download URL for your LCD library. I’ve not seen one that asks for the clock and data lines with the begin() method.

I was having a problem getting the LCD to display anything via IIC, so I went through several different libraries. Turns out, grounding GPIO0 to get into programming mode prevents the IIC connection from functioning until the ground is removed. The library that I ended with was downloaded from GitHub - HobbytronicsPK/ESP8266-I2C-LCD1602: Library to control LCD1602 Liquid Crystal Displays like from ESP8266 mcu's through I2C bus and is working perfectly for me.

sfgelectronics:
The library that I am using specifies that in order to start communication with the lcd, you need to use lcd.begin(sda, scl). In this case, sda is pin 0, scl is pin 2.

While doing that in begin() “works”, it doesn’t conform to the LiquidCyrstal API and is kind of an odd thing to do, since it is passing parameters to the LCD library which are really for the Wire library.

You could use my hd44780 library. It works on the ESP parts.

By default it uses the pins assigned to the defines SDA and SCL
If you want to change them, you can call ESP API extension Wire.pins(sda,scl) before you call the lcd.begin() method to change them to other pins.

— bill