40X4 LCD Display Vertical Scrolling

I will get to my question after this intro.
I bought a 4 line by 40 character LCD display module with and I2C module included (and soldered) from AliExpress. (https://www.aliexpress.us/item/2251832722412443.html?spm=a2g0o.productlist.main.1.38f9RBWTRBWTdC&algo_pvid=fa9b48e4-c3f4-4bd6-a4e7-3bada83e51d0&algo_exp_id=fa9b48e4-c3f4-4bd6-a4e7-3bada83e51d0-0&pdp_ext_f={"order"%3A"337"%2C"eval"%3A"1"%2C"fromPage"%3A"search"}&pdp_npi=6%40dis!USD!19.99!10.99!!!19.99!10.99!%402101dedf17818364099575145ef848!66912239675!sea!US!4141740272!X!1!0!n_tag%3A-29919%3Bd%3Af238f64e%3Bm03_new_user%3A-29895&curPageLogUid=uMeVW7h14EbV&utparam-url=scene%3Asearch|query_from%3A|x_object_id%3A32908727195|_p_origin_prod%3A)
The manufacturer is Surenoo. I did see several 40x4 LCD libraries on github, but I had trouble getting them to display 4 lines with 40 characters. Some only displayed two lines. But rather than troubleshoot why my code wasn't doing what I wanted, I looked for and found the manufacturer's documentation and Arduino library.
Surenoo's page for this product is Surenoo 4004 Character LCD Module Display Panel Screen . Norton didn't like that URL, but I told Norton to make an exception.
The software libraries for this are on a Google drive, SLC4004 Series - Google Drive
The Arduino example sketch demonstrates most of the functions for this LCD display. The one feature I thought would be useful but is not in the *.h file is a function that would scroll lines up and down.
So I decided to script a way to do that. My script takes a long string, breaks it up into line-sized substrings and scrolls existing lines up as new line segments are added to the bottom line.

This is my problem: The first line substring does move up to display the second substring. But when the third substring is added, the previous lines move up, but the first line apparently does not display on the second row as expected.
I do see that it does flicker in the correct place, but that line immediately goes blank. The same is true as new lines are added to the bottom. The top two lines briefly flicker the display of the correct substrings but go blank.
So I only have the text showing on the bottom two lines of the display. I added four serial monitor print lines to confirm that I am handling the breaking up of the text into substrings properly. But I cannot see why the top two lines show the correct substrings briefly and then go blank.

Here is my code:

/*

Sketch Name: LCD_40x4_Scroll_Text_Up

Takes a large text string, breaks it into lines of 40 or fewer characters and

scrolls it up the display.

*/

#include <LiquidCrystal_I2C_4004.h>

#include <avr/pgmspace.h>

bool DEBUG = true; // Set to false to disable debugging prints to the serial monitor

const byte lcdAddr = 0x27; // Address of I2C backpack

const byte lcdCols = 40; // Number of character in a row

const byte lcdRows = 4; // Number of lines

LiquidCrystal_I2C lcd(lcdAddr, lcdCols, lcdRows); // set the LCD address to 0x27 for a 16 chars and 2 line display

int currPosition = 0; // the beginning of the next 40-character substring

int currLine = 3; // Put the most recent substring on the last line (3) of the display and scroll up.

String line0 = "";

String line1 = "";

String line2 = "";

String line3 = "";

String thisLine = ""; // The next substring to be added to the dispolay

/* ----------------- A test string to display ----------------- */

String text = "Tomorrow, and tomorrow, and tomorrow, "

          "Creeps in this petty pace from day to day, "

          "To the last syllable of recorded time; "

          "And all our yesterdays have lighted fools "

          "The way to dusty death. Out, out, brief candle! "

          "Life's but a walking shadow, a poor player, "

          "That struts and frets his hour upon the stage, "

          "And then is heard no more. It is a tale "

          "Told by an idiot, full of sound and fury, "

          "Signifying nothing.";

int msgLength = text.length(); // To stop the parsing when the entire input text is processed.

String getNextSubstring(int startPos) { // Get substrings of the message text, breaking at the last space in the next 40 characters.

String thisLine = text.substring(startPos, startPos + 40); // the 40 will be trimmed to the last space in those 40 characters

int lastSpace = thisLine.lastIndexOf(' ');

if (lastSpace == -1) {

thisLine = text.substring(startPos, startPos + thisLine.length());

} else {

thisLine = text.substring(startPos, startPos + lastSpace);  // This is the line to display

}

if (DEBUG) {

Serial.print("startPos = ");

Serial.print(startPos);

Serial.print(" -- lastSpace = ");

Serial.println(lastSpace);

Serial.print("thisLine = ");

Serial.println(thisLine);

}

currPosition = startPos + thisLine.length() + 1; // where the next substring starts

if (DEBUG) {

Serial.print("currPosition = ");

Serial.println(currPosition);

}

return thisLine;

}

void scrollLinesUp(String lastLine) {

if (DEBUG) { Serial.println("------------ Scroll Lines Up ------------------");}

line0 = line1;

line1 = line2;

line2 = line3;

line3 = lastLine;

/* Just to prove that the display lines are being parsed correctly from the sample text.

Serial.print("Line0: ");

Serial.println(line0);

Serial.print("Line1: ");

Serial.println(line1);

Serial.print("Line2: ");

Serial.println(line2);

Serial.print("Line3: ");

Serial.println(line3);

*/

//lcd.clear(0); // Clear the first line

lcd.setCursor(0, 0);

lcd.print(line0);

lcd.clear(1); // Clear the second line

lcd.setCursor(0, 1);

lcd.print(line1);

lcd.clear(2); // Clear the third line

lcd.setCursor(0, 2);

lcd.print(line2);

lcd.clear(3); // Clear the fourth line

lcd.setCursor(0, 3);

lcd.print(line3);

}

void setup() {

lcd.begin();

Serial.begin(9600);

delay(1000);

lcd.home();

lcd.backlight();

lcd.setCursor(0, 0);

Serial.print("text length = ");

Serial.println(text.length());

}

void loop() {

if (DEBUG) {

Serial.print("Main loop --  currPosition = ");

Serial.print(currPosition);

Serial.print(" --  currLine = ");

Serial.println(currLine);

}

if (currPosition < msgLength) { // Stop parsing when the full text has been displayed

String thisLine = getNextSubstring(currPosition);

scrollLinesUp(thisLine);

}

delay(500);

if (currPosition > msgLength) { DEBUG = false; } //Stop printing to the monitor when the message has been completed.

}

What arduino board are you using? I would strongly advise NOT to use Strings but instead use C-strings (char*). Also another piece of advise would be to store your various text in PROGMEM (flash memory). This will cut down on your ram usage as the basic Arduino boards do not have much ram.

The best way to do what you want is to keep track of what row you are on and to use it as an offset to get the next line of text.

Generally a 40x4 display actually uses two HD44780 LCD driver chips, each driving 1/2 of the display. I'm not sure from the library and example sketch whether your display is organized as two 40x2 displays or two 20x4 displays, but I would guess 40x2 if you are getting a complete 40 character line to display with a single lcd.print() command.

The argument to clear() should only be a 0 or 1. To clear the top two lines, use clear(0), for the bottom two lines use clear(1). Using any other value for the argument will result in clearing the top two lines. Fixing that may get your code to function properly, as clear(2) and clear(3) result in clearing the top two lines of the display.

Please follow the advice given in the link below when posting code, in particular the section entitled 'Posting code and common code problems'

Use code tags (the < CODE/ > icon above the compose window) to make it easier to read and copy for examination

Please post your full sketch, using code tags when you do

Posting your code using code tags prevents parts of it being interpreted as HTML coding and makes it easier to copy for examination

In my experience the easiest way to tidy up the code and add the code tags is as follows

Start by tidying up your code by using Tools/Auto Format in the IDE to make it easier to read. Then use Edit/Copy for Forum and paste what was copied in a new reply. Code tags will have been added to the code to make it easy to read in the forum thus making it easier to provide help.

This library: GitHub - gshimansky/LiquidCrystal_PCF8574_4004 · GitHub
Says this: "... two 40x2 displays stacked on top of each other..."

And this library: character_lcd_4004_arduino/README.md at main · upiir/character_lcd_4004_arduino · GitHub
Calls lcd.clear() two times...

  // clear the screen
  lcd.clear(0);
  lcd.clear(1);

Thanks to david_2018 and xfpd. My misunderstanding of the clear(uint8_t) was the problem. I did what xfpd suggested, and did clear(0) and clear(1) before writing to the display and everything works as I had hoped for.

Problem solved.

I want to reply to the others who provided help.

For HazardsMind, I am using a Nano. I didn't use PROGMEM because I was only trying to explore vertical scrolling as an exercise, and not creating a useful sketch. I used strings because I don't quite understand cstr* and pointers and haven't taken the time to educate myself. I understand that you are right in that they are the better way to go.

For UkHeliBob, I tried clicking the button but I didn't get what I was looking for. I am now trying the Edit menu, "Copy for Forum", and am pasting it here. (with the sketch modified with david_2018's corrections).

Again, thanks to all.

/*
Arduino Forum Post: https://forum.arduino.cc/t/40x4-lcd-display-vertical-scrolling/1448969
Sketch Name: LCD_40x4_Scroll_Text_Up
Takes a large text string, breaks it into lines of 40 or fewer characters and
  scrolls it up the display.
*/
#include <LiquidCrystal_I2C_4004.h>
#include <avr/pgmspace.h>
bool DEBUG = true;  // Set to false to disable debugging prints to the serial monitor

const byte lcdAddr = 0x27;                         //clear Address of I2C backpack
const byte lcdCols = 40;                           // Number of character in a row
const byte lcdRows = 4;                            // Number of lines
LiquidCrystal_I2C lcd(lcdAddr, lcdCols, lcdRows);  // set the LCD address to 0x27 for a 16 chars and 2 line display

int currPosition = 0;  // the beginning of the next 40-character substring
int currLine = 3;      // Put the most recent substring on the last line (3) of the display and scroll up.
String line0 = "";
String line1 = "";
String line2 = "";
String line3 = "";
String thisLine = ""; // The next substring to be added to the dispolay

/* ----------------- A test string to display ----------------- */
String text = "Tomorrow, and tomorrow, and tomorrow, "
              "Creeps in this petty pace from day to day, "
              "To the last syllable of recorded time; "
              "And all our yesterdays have lighted fools "
              "The way to dusty death. Out, out, brief candle! "
              "Life's but a walking shadow, a poor player, "
              "That struts and frets his hour upon the stage, "
              "And then is heard no more. It is a tale "
              "Told by an idiot, full of sound and fury, "
              "Signifying nothing.";

int msgLength = text.length();  // To stop the parsing when the entire input text is processed.

String getNextSubstring(int startPos) {                       // Get substrings of the message text, breaking at the last space in the next 40 characters.
  String thisLine = text.substring(startPos, startPos + 40);  // the 40 will be trimmed to the last space in those 40 characters
  int lastSpace = thisLine.lastIndexOf(' ');
  if (lastSpace == -1 || thisLine.length() < 40 ) {
    thisLine = text.substring(startPos, startPos + thisLine.length());
  } else {
    thisLine = text.substring(startPos, startPos + lastSpace);  // This is the line to display
  }

  if (DEBUG) {
    Serial.print("startPos = ");
    Serial.print(startPos);
    Serial.print(" -- lastSpace = ");
    Serial.println(lastSpace);
    Serial.print("thisLine = ");
    Serial.println(thisLine);
  }


  currPosition = startPos + thisLine.length() + 1;  // where the next substring starts

  if (DEBUG) {
    Serial.print("currPosition = ");
    Serial.println(currPosition);
  }
  return thisLine;
}

void scrollLinesUp(String lastLine) {
  if (DEBUG) { Serial.println("------------ Scroll Lines Up ------------------");}

  line0 = line1;
  line1 = line2;
  line2 = line3;
  line3 = lastLine;

  /*  Just to prove that the display lines are being parsed correctly from the sample text.
   Serial.print("Line0: ");
  Serial.println(line0);
  Serial.print("Line1: ");
  Serial.println(line1);
  Serial.print("Line2: ");
  Serial.println(line2);
  Serial.print("Line3: ");
  Serial.println(line3);
*/
  lcd.clear(0); // Clear the first two lines
  lcd.clear(1);  // Clear the last two lines
  lcd.setCursor(0, 0);
  lcd.print(line0);
  lcd.setCursor(0, 1);
  lcd.print(line1);
  lcd.setCursor(0, 2);
  lcd.print(line2);
  lcd.setCursor(0, 3);
  lcd.print(line3);
}

void setup() {
  lcd.begin();
  Serial.begin(9600);
  delay(1000);
  lcd.home();
  lcd.backlight();
  lcd.setCursor(0, 0);

  Serial.print("text length = ");
  Serial.println(text.length());
}

void loop() {
  if (DEBUG) {
    Serial.print("Main loop --  currPosition = ");
    Serial.print(currPosition);
    Serial.print(" --  currLine = ");
    Serial.println(currLine);
  }
  if (currPosition < msgLength) {  // Stop parsing when the full text has been displayed
    String thisLine = getNextSubstring(currPosition);
    scrollLinesUp(thisLine);
  }
  delay(1000);

  if (currPosition > msgLength) { DEBUG = false; }  //Stop printing to the monitor when the message has been completed.
}