Go Down

Topic: 16x2 LCD One Line Scrolling, One Stationary (Read 4862 times) previous topic - next topic

dedekeyser

I was messing around with my 16x2 LCD display yesterday, and I discovered that there's no truly easy way of scrolling only one line of text while leaving the other stationary. A quick google search proved frustratingly fruitless! A few people in the past have had similar questions, and the answers they received were complex and seemingly condescending, unfortunately.

So, I spent several hours developing some code that allows the user to send strings up to 39 characters long over serial to be displayed (and scrolled if longer than 16 characters) on the LCD screen. At the same time you can also display a short message in the other line that remains stationary.

Code: [Select]

#include <LiquidCrystal.h> //LCD library.

String alwaysDisplay = "From serial:"; //The message that will always be displayed on row 1.
String input; //Where the string sent from the serial monitor is stored.
int inputLength = 0; //Where the length of the input string is stored.
char inputArray[40]; //The input string will be converted to a character array if it is longer than 16 characters,
                     // so that it can be written one character at a time.

LiquidCrystal lcd(12,11,5,4,3,2); //Define the LCD object's pins (RS,E,D4,D5,D6,D7).

void setup() {
  Serial.begin(9600); //Start serial communication.
  lcd.begin(16,2); //Initialize the LCD display's size (16 coloms, 2 rows).
  lcd.clear(); //Clear the LCD screen, just in case!
  while(!Serial) {} //Wait for the user to open the serial monitor.
  Serial.println("Type a message to display on the LCD screen."); //Let the user know that they may begin sending data.
}

void loop() {
  if (Serial.available()) { //Wait for serial data to be sent by the user.
    delay(100); //Wait for the serial data to finish incoming.
    input = Serial.readString(); //Store the incoming data in "input" as a string.
    inputLength = input.length(); //Store the length of the string in "inputLength".
    lcd.clear(); //Clear the LCD screen of data from the last loop.
    lcd.print(alwaysDisplay); //Print the message on row 1.
    Serial.print(input); Serial.print(", "); Serial.println(inputLength); //Print the string and its length in the serial monitor
                                                                          //  [for debugging purposes].
   
    if (inputLength <= 16) { //If the string is up to 16 characters we simply need to move the cursor to row 2 and print the string.
      lcd.setCursor(0,1);
      lcd.print(input);
    }
    else if (inputLength > 39) Serial.println("This string is too long."); //Due to RAM constraints, strings longer than 39 characters
                                                                           //  are too buggy to be output to the display.
    else { //Strings longer than 16 characters, but still within RAM constraints get complicated...
      lcd.setCursor(0,1); //Move the cursor to row 2.
      input.toCharArray(inputArray,40); //Convert the string stored in "input" into a character array.
      for (int i = 0; i <= 16; i++) { //For the first 16 characters, simply print them to the LCD screen.
        lcd.write(inputArray[i]);
      }
      delay(1500); //Delay for 1.5 seconds so the user has time to read.
      for (int j = 17; j <= inputLength; j++) { //Now we begin printing from character 17 onward...
        lcd.write(inputArray[j]); //Write the j-th character (for now it will be off-screen).
        lcd.scrollDisplayLeft(); //Scroll the text left one character-space.

        //This is where things get tricky, because both rows will be scrolled. But we want row 1 to remain stationary!
        lcd.setCursor(j-16,0); //Set the cursor to the first character space on the first row [visually].
                               // cursor space (0,0) has been scrolled off-screen!
        lcd.print(alwaysDisplay); //Re-print the row 1 message.
        lcd.setCursor(j+1,1); //Set the cursor one character space to the right of the last printed character on row 2.
                              //  Which is visually one character space off-screen, in preparation for the next itteration.
        delay(300); //delay for .3 seconds so the user has time to read.
      }
    }
  }
}


This code is fairly verbose, using up 16% of both program storage and dynamic memory. This is okay for a simple example, but I can't imagine it being very integrable into a larger project. However, small snippets of it could be taken and used to display system messages, without the extra IF statements for checking string length and the large character array which is large to accommodate the maximum string size.
The code is fairly versatile, so with a little effort it can be changed to scroll text in either direction, you can decide which line to scroll and which will remain stationary, set varying timing, and you can easily set the stationary message.

If anyone has a more elegant solution to the problem I'd love to know about it! But for now, I hope that this helps people new to 16x2 displays solve a seemingly common issue. 

riemannn

Thanks a lot for this. As a beginner, I've been trying to wrap my head around one line scrolling and this is the best solution I've found so far. But the 39 character limit is a bummer. Have you been able to improve the code to include more than 39 characters? My first thought was, can't we use substr to divide the longer-than-39-character strings and treat each chunk as new input, and display them continuously? Of course that would make the code even more verbose. Just wanted to know what you think. Thanks again.

david_prentice

Just keep a copy of your text in SRAM.   Manipulate as required.    Send the results.

Seriously,   16x2 characters only take a few milliseconds to update the whole screen.
A Uno can afford an 80 byte buffer in SRAM.

David.

odometer

You know what PaulS will say once he sees this code.

riemannn

#4
May 06, 2018, 08:49 am Last Edit: May 06, 2018, 08:55 am by riemannn
I managed to write a code to solve the problem. It works, but almost certainly it isn't the best way to do it. I would love to have suggestions, improvements and corrections!

Code: [Select]

// Code for printing out a fixed string in line one of 16x02 LCD, and a scrolling
// string with unlimited length in line two.

/* For my fellow beginners: In this code, we will make use of the fact
 *  that LCD can store 80 characters (40 in each line). Therefore, in order
 *  to better visualize the algorithm, imagine your LCD as 40x2 instead of
 *  16x2. At any given moment it displays a chosen 16x2 portion of it, but we can move
 *  the area that is being displayed with scrollDisplayLeft and scrollDisplayRight
 *  functions (in this code we will only use scrollDisplayLeft). The way
 *  scrollDisplayLeft works is as follows: In the starting position, LCD displays
 *  between position (0,0) and position (15,1). If you run scrollDisplayLeft function
 *  once, LCD will display between (1,0) and (16,1), and so on. This, combined with
 *  the ability to print into the positions outside of the visible are of the screen, is
 *  the basis of the code. Scrolling a string longer than 40 characters causes the string
 *  to leak into other lines, which makes the LCD display unwanted parts of the string.
 *  In order to get around the 40 character limit per line,
 *  we will divide the string that we want to scroll, into 16-character-long string
 *  chunks, and print two of them at any given time, starting from (0,1) and ending
 *  at (31,1). So, initially, the first chunk will be visible on line two. We will then run
 *  scrollDisplayLeft sixteen times, and have the second chunk display in its entirety.
 *  We will run the code enough times that all the chunks that make up our original
 *  string will run one after the other, creating a smooth scroll.
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Written for LCD with I2C, include you relevant libraries.
const byte lcdAddr = 0x27;
const byte lcdCols = 16;
const byte lcdRows = 2;
LiquidCrystal_I2C lcd(lcdAddr, lcdCols, lcdRows);

void setup() {
  Serial.begin(115200);
  lcd.begin(16, 2);
  lcd.init();
  lcd.backlight();
}

void loop () {
 
  String Line1_fixed = "ShowerThoughts:"; //This is the string that will be fixed in the first line of LCD.
  String Line2_scrolling_base = "Making fun of a fat person at the gym is like making fun of a homeless person that is looking for a job. / It would really suck getting kidnapped with a cold. If they taped your mouth shut, you really wouldn't be able to breathe. / A lot of jobs in the IT field could basically be retitled to 'Professional Googler'."; //This intentionally long string will be scrolled in the second line of LCD.

  lcd.clear();
  lcd.print(Line1_fixed); //Print first line.
  delay(500); //Optional delay. If you omit this, the second line starts scrolling in from bottom right of the display (15,1), rather than entering from outside of the display (16,1). I find the latter visually more appealing, but it is optional.

  String Line2_scrolling =  "                "; // Defining a string consisting of sixteen spaces.
  Line2_scrolling += Line2_scrolling_base; // Adding sixteen spaces in front of the string that we want displayed. This is done so that line 2 will initially be empty in the display. Now we can display the string "Line2_scrolling".

  int Line2_length = Line2_scrolling.length(); // Length of our string (including the sixteen spaces that we added in front).
  int Line2_ChunkNumber = floor(Line2_length / 16 + 2); // Number of sixteen-character chunks in the string, plus one for the last chunk that is less than sixteen characters long.
  int Line2_LastChunkLength = Line2_length - ((Line2_ChunkNumber - 2) * 16); // Length of the last chunk, which is usually less than sixteen.
  int Line2_ChunkLength; // Will be initialized as 16 below, which is the usual length of chunks. But it will be changed if the last chunk is smaller in length.
  if (Line2_LastChunkLength == 0) { // If the string has a character length of a multiple of sixteen, Line2_ChunkNumber is reduced by one, in order to prevent redundant "for" cycles.
    Line2_ChunkNumber--;
  }

  //  Printing every variable on Serial, in case we want to see and verify them. The only remaning variable, Line2_ChunkLength, will be printed later on because it isn't initialized yet.
  //  Serial.print("Line2_length: ");
  //  Serial.println(Line2_length);
  //  Serial.print("Line2_ChunkNumber: ");
  //  Serial.println(Line2_ChunkNumber);
  //  Serial.print("Line2_LastChunkLength: ");
  //  Serial.println(Line2_LastChunkLength);

  String chunk[Line2_ChunkNumber]; // Create an array that can hold all the divided chunks of our scrolling string inside it.

  for (int j = 0; j < Line2_ChunkNumber - 1; j++) { // Assign all chunks inside chunk[].
    chunk[j] = Line2_scrolling.substring(j * 16 , j * 16 + 16);
  }

  for (int k = 0; k < Line2_ChunkNumber - 1; k++) {
    lcd.clear();
    lcd.print(Line1_fixed); // Print it again to avoid flicker.
    lcd.setCursor(0, 1);
    lcd.print(chunk[k]); // Display the string with the chunk number "k", starting from (0,1). This is the entire visible screen area of line 2 of the LCD.
    lcd.print(chunk[k + 1]); // Print the string with the chunk number "k+1", starting from (16,1). This is the area that is immediately to the right of the visible area of line 2 of the LCD.
    if (k != Line2_ChunkNumber - 2) { // If we are not dealing with the last chunk, set the number of iterations in the next "for" cycle to 16. If it is the last chunk and if the length of the last chunk isn't zero, set the number of iterations in the next "for" cycle to size of last chunk.
      Line2_ChunkLength = 16;
    }
    else if (Line2_LastChunkLength != 0) {
      Line2_ChunkLength = Line2_LastChunkLength;
    }

    for (int i = 1; i < Line2_ChunkLength + 1; i++) {
      lcd.scrollDisplayLeft(); // Scroll display left by one position.
      lcd.setCursor(i, 0); // Set cursor on the first line to allign with the scrolling string in the second line of LCD, in order to print the fixed string on the first line above where scrolling string starts. Currently in the algorithm, scrolling string starts at (i,1), hence setting the cursor to (i,0).
      lcd.print(Line1_fixed); // Print the fixed string so that its starting position alligns with the starting position of the scrolling string.
      delay(300); // Delay for visual ease of reading.
    }
  }
  //  Printing out the final variable to Serial for verification.
  //  Serial.print("Line2_ChunkLength: ");
  //  Serial.println(Line2_ChunkLength);
  //  Serial.println("-----------------");
}

bperrybap

#5
May 07, 2018, 10:24 pm Last Edit: May 07, 2018, 10:25 pm by bperrybap
If you wanted to avoid using a ram buffer you could use my hd44780 library (hd44780_pinIO class).
It can read the display ram so you could read & write what is on the display to actually move the characters location in display ram.
You would also need to hook up an extra Arduino pin for the r/w line.


--- bill

Go Up