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 : )