LCD Scroll Text

So I am playing with a 16x2 lcd shield, and I got some nice scrolling action happening, on the second line, which works great, but I would like a stationary text on the top line. I am having some issues getting the top line to remain stationary. I can sort of see why it is scrolling as well, but I have no clue how to keep it stationary. Here is my code

#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7);

void setup()
{

  lcd.begin(16,2);
  /* try to keep this stationary*/
  lcd.setCursor(5,0);
  lcd.print("TEST STRING");

}

void loop()
{

  String myString = "Just a string";
  lcd.setCursor(16,1);
  lcd.print(myString);

  for (int scrollCounter = 0; scrollCounter < 28; scrollCounter++) 
  { 

    lcd.scrollDisplayLeft(); 

    delay(250);
  }

  lcd.clear();

}

Thank you in advance for all advice , direction, and help

Scrolling is handled by the LCD itself and it does not offer (as far as I can see) any option to scroll the rows independantly. You'll have to manage it yourself. A quick and dirty way might be to use the existing scrolling functionality and then rewrite the entire top row.

Roll your own scroll example.

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

const byte i2cAddress = 0x27;
const byte backlightPin= 3;
char * message = "Scroll me";

LiquidCrystal_I2C lcd(i2cAddress);  //NOTE library changed by BB to hard code pin numbers

void setup()
{
  lcd.begin (16,2);
  Serial.begin(115200);
  lcd.setBacklightPin(backlightPin ,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.setCursor(3,0);
  lcd.print("Stay Still");
}

void loop()
{
  for (int printStart = 15; printStart >= 0; printStart--)  //scroll on from right
  {
    showLetters(printStart, 0); 
  }

  for (int letter = 1; letter <= strlen(message); letter++)  //scroll off to left
  {
    showLetters(0, letter);
  }
}

void showLetters(int printStart, int startLetter)
{
  lcd.setCursor(printStart,1);
  for (int currentLetter = startLetter; currentLetter < strlen(message); currentLetter++)
  {
    lcd.print(message[currentLetter]);
  } 
  lcd.print(" ");
  delay(250); 
}

Note that this is written for an I2C LCD so the initialisation will be need to be changed to suit yours, and as currently written the scrolling message cannot be wider than the LCD, but that could be fixed.

Heh Yeah that's the fix. I got up this morning, after spending a lot of time looking for the answer, and realizing as you said, the lcd cannot individually control rows, and I figured I would count and reset start point in a different location. Yes it works, here is the modified code

#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7);
char * message = "Scroll me";

void setup()
{
  lcd.begin (16,2);
  lcd.setCursor(3,0);
  lcd.print("Stay Still");
}

void loop()
{
  for (int printStart = 15; printStart >= 0; printStart--)  //scroll on from right
  {
    showLetters(printStart, 0); 
  }

  for (int letter = 1; letter <= strlen(message); letter++)  //scroll off to left
  {
    showLetters(0, letter);
  }
}

void showLetters(int printStart, int startLetter)
{
  lcd.setCursor(printStart,1);
  for (int currentLetter = startLetter; currentLetter < strlen(message); currentLetter++)
  {
    lcd.print(message[currentLetter]);
  } 
  lcd.print(" ");
  delay(250); 
}

Thank you for the help. This is a nice clean example of what can be done, when something cannot be done;)

OH now I have another question, strlen() is a C function but I noticed we don't have to include string.h …Apparently there are some tricks I am unaware of. Perhaps someone can clarify why the include is not required?

Hi,

I'm using the last version of the sketch of this thread :

#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7);
char * message = "Scroll me";

void setup()
{
 lcd.begin (16,2);
 lcd.setCursor(3,0);
 lcd.print("Stay Still");
}

void loop()
{
 for (int printStart = 15; printStart >= 0; printStart--)  //scroll on from right
 {
   showLetters(printStart, 0); 
 }

 for (int letter = 1; letter <= strlen(message); letter++)  //scroll off to left
 {
   showLetters(0, letter);
 }
}

void showLetters(int printStart, int startLetter)
{
 lcd.setCursor(printStart,1);
 for (int currentLetter = startLetter; currentLetter < strlen(message); currentLetter++)
 {
   lcd.print(message[currentLetter]);
 } 
 lcd.print(" ");
 delay(250); 
}

But there is a limitation that I don't understand. If the scrolling message is longer than 25 characters, the end of the message appears at the beginning of the first line. Here is a video :

The only limitation I can assume is from the LiquidCrystal.h

Any ideas to make it work with more than 25 characters ?

Thanks in advance !

Rutherberg:
If the scrolling message is longer than 25 characters, the end of the message appears at the beginning of the first line. Here is a video :

Dropbox - File Deleted - Simplify your life

The only limitation I can assume is from the LiquidCrystal.h

Any ideas to make it work with more than 25 characters ?

Thanks in advance !

The "problem" here is that the method prints the scrolling string without taking into account lcd size nor scrolling string length: it starts moving the cursor to the border of the lcd and, from there, prints the string in its entire length.
Not properly managing lcd size, string length and cursor position often results in random behaviour: on some lcd, and depending on lcd configuration (libs you include to manage it and how you initialize it), you can set cursor position (and print from there) out of the lcd width and you will not see anything displayed untill you scroll it in; with other settings instead you may see the text displayed on the next row, as if rows were treated like a circular buffer...

To recap: it's quite a mess heh, and I guess the variety of possible configurations is one of the reasons that makes people saying LiquidCrystal scroll functions are so broken.

I've spent a decent amount of time trying to write code that should work no matter what the lcd size or the scrolling string length are : )

I'll post two working sketches (provided that who uses them includes the correct lib, sets lcd size constant and initialize lcd accordingly with lcd type in possession).

This one is optimized for ram occupation & fragmentation:

#include <LiquidCrystal_I2C.h> // include lib according to your lcd type

// Constant for my lcd size, adjust to your lcd
#define LCDWIDTH 20
#define LCDHEIGHT 4

// Used lcd: mine is 0x27 type
LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup() {
  lcd.begin(20, 4); // initialize with your correct lcd size here
  lcd.init();
  lcd.clear();
  lcd.backlight();

  char * pinnedString = (char *) F("by Goet");
  char * scrollingString = (char *) F("I don't like being long-winded type of guy, so I think i'll go straight to the point: this is a very long, long text scrolling on your lcd display :)");
  int pinnedRow = LCDHEIGHT / 2;
  int scrollingRow =  LCDHEIGHT / 2 - 1;
  int scrollingSpeed = 200;
  pinAndScrollText(pinnedString, pinnedRow, scrollingString, scrollingRow, scrollingSpeed);

  delay(2000);
  lcd.clear();
  lcd.print(F("Back"));

}

void loop() {
}

/* This procedure pins a given text in the center of a desired row while scrolling from right to left another given text on another desired row.
    Parameters:
    char * pinnedText: pinned char string
    int pinnedRow: desired row for pinned String
    char * scrollingText: scrolling char string
    int scrollingRow: desired row for scrolling String
    int v = scrolling speed expressed in milliseconds
*/
void pinAndScrollText(char * pinnedText, int pinnedRow, char * scrollingText, int scrollingRow, int v) {
  if (pinnedRow == scrollingRow || pinnedRow < 0 || scrollingRow < 0 || pinnedRow >= LCDHEIGHT || scrollingRow >= LCDHEIGHT || strlen(pinnedText) > LCDWIDTH || v < 0) {
    lcd.clear();
    lcd.print(F("Error"));
    while (1);
  }
  int l = strlen(pinnedText);
  int ls = strlen(scrollingText);
  int x = LCDWIDTH;
  int n = ls + x;
  int i = 0;
  int j = 0;
  char c[ls + 1];
  flashCharSubstring(pinnedText, c, 0, l);
  lcd.setCursor(l % 2 == 0 ? LCDWIDTH / 2 - (l / 2) : LCDWIDTH / 2 - (l / 2) - 1, pinnedRow);
  lcd.print(c);
  while (n > 0) {
    if (x > 0) {
      x--;
    }
    lcd.setCursor(x, scrollingRow);
    if (n > LCDWIDTH) {
      j++;
      i = (j > LCDWIDTH) ? i + 1 : 0;
      flashCharSubstring(scrollingText, c, i, j);
      lcd.print(c);
    } else {
      i = i > 0 ? i + 1 : 0;
      if (n == ls) {
        i++;
      }
      flashCharSubstring(scrollingText, c, i, j);
      lcd.print(c);
      lcd.setCursor(n - 1, scrollingRow);
      lcd.print(' ');
    }
    n--;
    if (n > 0) {
      delay(v);
    }
  }
}

/* This procedure makes a char substring based on indexes from a given char string stored in progmem.
   The caller have to set its own buffer and pass its reference
   to the procedure. The procedure will fill the buffer with
   desired substring based on given indexes. Caller buffer size have
   to be at least big as desired substring length (plus null terminating char) of course.
   As a general rule, caller buffer should be: char buf[strlen(str) + 1].
   Example:
   char * string = (char*)F("Hello world!"); // or char string[] = (char[])F("Hello world!");
   char buf[strlen(string) + 1];
   flashCharSubstring(string, buf, 0, 5);
   Serial.println(buf); // output: "Hello"
    Parameters:
    const char *str: string from where to extract the substring
    char *buf: reference of the caller buffer.
    int inf: lower bound, included
    int sup: upper bound, excluded
*/
void flashCharSubstring(const char *str, char *buf, int inf, int sup) {
  int l = sup - inf;
  for (int i = 0; i < l; i++) {
    buf[i] = pgm_read_byte_near(&str[i + inf]);
  }
  buf[l] = '\0';
}

Basically, we have two procedures: pinAndScrollText and flashCharSubstring. The first one takes care of scrolling, while the second one extracts char progmem'ed substring for the first one.
As I said, everything is optimized to reduce memory occupation & fragmentation, so the calling instruction uses flash memory stored strings casted to char *

Of course it's possible to simplify everything, ignoring ram occupation & heap fragmentation using String objects, like this:

#include <LiquidCrystal_I2C.h> // include lib according to your lcd type

// Constant for my lcd size, adjust to your lcd
#define LCDWIDTH 20
#define LCDHEIGHT 4

// Used lcd: mine is 0x27 type
LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup() {
  lcd.begin(20, 4); // initialize with your correct lcd size here
  lcd.init();
  lcd.clear();
  lcd.backlight();

  String pinnedString = "by Goet";
  String scrollingString = "I don't like being long-winded type of guy, so I think i'll go straight to the point: this is a very long, long text scrolling on your lcd display :)";
  int pinnedRow = LCDHEIGHT / 2;
  int scrollingRow =  LCDHEIGHT / 2 - 1;
  int scrollingSpeed = 200;
  pinAndScrollText(pinnedString, pinnedRow, scrollingString, scrollingRow, scrollingSpeed);

  delay(2000);
  lcd.clear();
  lcd.print("Back");

}

void loop() {
}

/* This procedure pins a given text in the center of a desired row while scrolling from right to left another given text on another desired row.
    Parameters:
    const String &pinnedText: pinned String
    int pinnedRow: desired row for pinned String
    const String &scrollingText: scrolling String
    int scrollingRow: desired row for scrolling String
    int v = scrolling speed expressed in milliseconds
*/
void pinAndScrollText(const String &pinnedText, int pinnedRow, const String &scrollingText, int scrollingRow, int v) {
  if (pinnedRow == scrollingRow || pinnedRow < 0 || scrollingRow < 0 || pinnedRow >= LCDHEIGHT || scrollingRow >= LCDHEIGHT || pinnedText.length() > LCDWIDTH || v < 0) {
    lcd.clear();
    lcd.print("Error");
    while (1);
  }
  int l = pinnedText.length();
  lcd.setCursor(l % 2 == 0 ? LCDWIDTH / 2 - (l / 2) : LCDWIDTH / 2 - (l / 2) - 1, pinnedRow);
  lcd.print(pinnedText);
  int x = LCDWIDTH;
  int n = scrollingText.length() + x;
  int i = 0;
  int j = 0;
  while (n > 0) {
    if (x > 0) {
      x--;
    }
    lcd.setCursor(x, scrollingRow);
    if (n > LCDWIDTH) {
      j++;
      i = (j > LCDWIDTH) ? i + 1 : 0;
      lcd.print(scrollingText.substring(i, j));
    } else {
      i = i > 0 ? i + 1 : 0;
      if (n == scrollingText.length()) {
        i++;
      }
      lcd.print(scrollingText.substring(i, j));
      lcd.setCursor(n - 1, scrollingRow);
      lcd.print(' ');
    }
    n--;
    if (n > 0) {
      delay(v);
    }
  }
}

To conclude: advices & improvement are welcome : )

I was trying to find a solution for the LCD scrolling but was not able to. Your code works perfect for me. Thanks!

Rutherberg:
Hi,

I'm using the last version of the sketch of this thread :

#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,4,5,6,7);
char * message = "Scroll me";

void setup()
{
lcd.begin (16,2);
lcd.setCursor(3,0);
lcd.print("Stay Still");
}

void loop()
{
for (int printStart = 15; printStart >= 0; printStart--)  //scroll on from right
{
  showLetters(printStart, 0);
}

for (int letter = 1; letter <= strlen(message); letter++)  //scroll off to left
{
  showLetters(0, letter);
}
}

void showLetters(int printStart, int startLetter)
{
lcd.setCursor(printStart,1);
for (int currentLetter = startLetter; currentLetter < strlen(message); currentLetter++)
{
  lcd.print(message[currentLetter]);
}
lcd.print(" ");
delay(250);
}




But there is a limitation that I don't understand. If the scrolling message is longer than 25 characters, the end of the message appears at the beginning of the first line. Here is a video :

https://www.dropbox.com/s/jtbm40j5de3sm45/VID_20170328_184738.mp4?dl=0

The only limitation I can assume is from the LiquidCrystal.h

Any ideas to make it work with more than 25 characters ?

Thanks in advance !

I was able to fix the 'problem' by an efficient padding and controlling how many characters to print in Line #2 at a time. Here is the sketch.

I couldn't use the I2C example as I only have 1602 LCD without I2C.

#include <LiquidCrystal.h>
// BS E D4 D5 D6 D7
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
char * messagePadded = " Twinkle Twinkle Little star how I wonder what you are, up above the world so high, like a diamond in the sky. ";

void setup()
{
lcd.begin (16, 2);
lcd.setCursor(3, 0);
lcd.print("Sing Along");
}

void loop()
{
for (int letter = 0; letter <= strlen(messagePadded) - 16; letter++) //From 0 to upto n-16 characters supply to below function
{
showLetters(0, letter);
}
}

void showLetters(int printStart, int startLetter)
{
lcd.setCursor(printStart, 1);
for (int letter = startLetter; letter <= startLetter + 15; letter++) // Print only 16 chars in Line #2 starting 'startLetter'
{
lcd.print(messagePadded[letter]);
}
lcd.print(" ");
delay(250);
}