Is there a better way to cycle through text ?

I have a simple LCD screen and I'm trying to cycle throught a simple text word by word each time I press a switch.
I bruteforced it using if statements but I'm sure there's a better way to do this, can someone guide me in the right direction ?

int ButtonPin=2;
int LastButton;
int CurrentButton;
int texte=0;
bool printer=false;
#include <LiquidCrystal.h>;
LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

void setup() {
 pinMode(ButtonPin,INPUT);
  // put your setup code here, to run once:
  Serial.begin(9600);
  lcd.begin(16, 2);
}

void loop() {
CurrentButton=digitalRead(ButtonPin);
if (CurrentButton==HIGH && CurrentButton!=LastButton){
  texte++;
  printer = true;
  }
if (texte==1 && printer==true){
  lcd.setCursor(0, 0);
  lcd.clear();
    lcd.print("Coucou");
    printer = false;}
if (texte==2 && printer==true){
  lcd.clear();
  lcd.print("tu");
  printer = false;}    
if (texte==3 && printer==true){
  lcd.clear();
  lcd.print("veux");
  printer = false;}    
if (texte==4 && printer==true){
  lcd.clear();
  lcd.print("manger");
  printer = false;}    
if (texte==5 && printer==true){
  lcd.clear();
  lcd.print("quoi");
  printer = false;}    
if (texte==6 && printer==true){
  lcd.clear();
  lcd.print("ce");
  printer = false;}    
if (texte==7 && printer==true){
  lcd.clear();
  lcd.print("soir ? :)");
  printer = false;}    

  if (texte>7){
    lcd.clear();
  texte=0;}
   LastButton=CurrentButton;
   Serial.print(texte);

}

Hello Niokay

An array for the text is your friend.

1 Like

And I wanna know who is Coucou?

a7

"Hello!" If you choose an array, rather than storing "Coucou" in word[1] when texte = 1, the word should be stored in word[0] when texte = 0 (and the other words in [1] through [6]) and use texte = 7 as lcd.clear(); You will also want a software or hardware "button debounce."

1 Like

I mean, does it work as intended? If so and works as intended over time, then great!

If you are also asking about different techniques you might have used that will be less cumbersome to build upon or sort of think about in your plan for another project, you have already heard of @paulpaulson array suggestion and @xfpd mission critical suggestion to use a button debounce. @alto777 suggested to me one time to use a button library that iirc, in alto's words "doesn't suck", namely
https://github.com/ArduinoGetStarted/button

Another technique you might look into is to make a finite state machine by using switch/case instead of if/else. There are examples of course in the IDE. I find it much easier to keep track of this way, especially as your projects grow or you maybe made something like you did (is it for a cat feeder?) that you may want to import to another project one day.

I like to think of state machines as the secret behind every video game, because they kind of are. Is the door to the castle open or closed? are you on level 1 stage 1 or level 8 stage 8 and so on, such as in Super Mario Bros for the NES.

Every one of these variables is its own state machine, including a general wrapper for whether you are on the splash screen waiting to start the game or whether or are actually in the game and playing it.

Thus, you might imagine a completed game such as Super Mario Bros as a big tree with various branches of state machines, with other twigs off those branches that are also state machines, and so on.

Here is a very simple example that illustrates the point and requires no extra hardware. I broke off all the code in each time state into its own function. You might replace the free running clock time based state changing mechanism with your button push. I used discrete functions to make changing just certain things easier if such a project would grow in the future, avoiding spaghetti code in void loop().

You can even nest state machines inside state machines, although the code below doesn't do that and it opens a door to spaghetti code if you aren't careful. Anywho...


/* April 03, 2024
   revised December 11, 2024 to make the concept a bit more obvious
    Hallowed31

    State/Mode Machine - events/functions driven by built in 
    millis timer and nothing else
    It's like a game, only much less fun. Just watch the game play itself!

    Circuit: just an Arduino

    Set Serial monitor to 9600 baud, no line ending
    
    Working as intended. Tested on Uno R3
*/

byte level = 0;  // you could have 0 - 255 levels if you wanted.
unsigned long currentTime;
unsigned long lastTimeAround;

void setup() {
  Serial.begin(9600);
  // just the sketch name so I know what's loaded on my Arduino
  Serial.println(F("automaticByteDrivenStateMachine"));
  delay(500);
  Serial.println();
  level = 0;
  currentTime = 0;
  lastTimeAround = 0;
}

void loop() {
  // start free running timer, this never stops running in the "game"
  currentTime = millis();
  /* set conditional statement whose condition is to 
     check built in millis clock and announce what "game"
    level it's on every two seconds */
  if (currentTime - lastTimeAround > 2000) {
    switch (level) {
      case 0:
        zero();
        break;
      case 1:
        one();
        break;
      case 2:
        two();
        break;
      case 3:
        three();
        break;
      case 4:
        four();
        break;
    }
    // sync the time marker to the free running clock
    lastTimeAround = currentTime;
  }
}
void zero() {
  // you might think of this as reset or awaiting players
  Serial.println("level 0");
  level += 1;  // increment level by one, to level 1
}
void one() {
  // this might be level one
  Serial.println("level 1");
  level += 1;  // exit to level 2
}
void two() {
  // level two
  Serial.println("level 2");
  level += 1;
}
void three() {
  // and so on
  Serial.println("level 3");
  level += 1;
}
void four() {
  Serial.println("level 4");
  level = 0;  // reset to level 0
}

int ButtonPin = 2;
int LastButton;
int CurrentButton;
int texte = 0, texteOld;
bool printer = false;
#include <LiquidCrystal.h>;
LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

char words[][9] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)"}; // 9 = length of longest

void setup() {
  pinMode(ButtonPin, INPUT_PULLUP);
  Serial.begin(115200);
  Serial.print(texte);

  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("Mash button");
}

void loop() {
  CurrentButton = digitalRead(ButtonPin);
  delay(100); // "debounce" the button

  if (CurrentButton == LOW && CurrentButton != LastButton) {
    texte++;
    printer = true;
  }

  for (int i = 0; i < 8; i++) {
    if (texte == i && printer == true) {
      lcd.setCursor(0, 0);
      lcd.clear();
      lcd.print(words[i - 1]); // show array word
      printer = false;
    }
  }

  if (texte > 7) {
    lcd.clear();
    texte = 0;
  }

  LastButton = CurrentButton;
  
  if (texteOld != texte) {
    texteOld = texte;
    Serial.print(texte);
  }
}

I didn't check the logic, but this looks suspicious:

Ooh. It was to make texte = 0 and Coucou line up. I guess I did not finish. "finished"

int ButtonPin = 2;
int LastButton;
int CurrentButton;
int texte = -1, texteOld;
bool printer = false;
#include <LiquidCrystal.h>;
LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

char words[][9] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)"}; // 9 = length of longest

void setup() {
  pinMode(ButtonPin, INPUT_PULLUP);
  Serial.begin(115200);

  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("Mash button");
}

void loop() {
  CurrentButton = digitalRead(ButtonPin);
  delay(100); // "debounce" the button

  if (CurrentButton == LOW && CurrentButton != LastButton) {
    texte++;
    printer = true;
  }

  for (int i = 0; i < 8; i++) {
    if (texte == i && printer == true) {
      lcd.setCursor(0, 0);
      lcd.clear();
      lcd.print(words[i]); // show array word
      printer = false;
    }
  }

  if (texte > 7) {
    lcd.clear();
    texte = 0;
  }

  LastButton = CurrentButton;

  if (texteOld != texte) {
    texteOld = texte;
    if (texte >= 0)
      Serial.print(texte);
  }
}
diagram.json for wokwi
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": 4.8, "left": -0.5, "attrs": {} },
    { "type": "wokwi-lcd1602", "id": "lcd1", "top": -159.77, "left": 6.4, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -13,
      "left": 172.8,
      "attrs": { "color": "green", "xray": "1" }
    }
  ],
  "connections": [
    [ "nano:7", "lcd1:RS", "green", [ "v0" ] ],
    [ "nano:8", "lcd1:E", "green", [ "v-24", "h18.7" ] ],
    [ "nano:9", "lcd1:D4", "green", [ "v-14.4", "h66.7" ] ],
    [ "nano:10", "lcd1:D5", "green", [ "v-14.4", "h105.1" ] ],
    [ "nano:11", "lcd1:D6", "green", [ "v-14.4", "h133.9" ] ],
    [ "nano:12", "lcd1:D7", "green", [ "v-14.4", "h143.5", "v-19.2" ] ],
    [ "nano:GND.3", "btn1:2.l", "black", [ "v0" ] ],
    [ "nano:2", "btn1:1.l", "green", [ "v0" ] ]
  ],
  "dependencies": {}
}

Google Translate provides an English version of the French definition of the word coucou, which is

An insectivorous climbing bird, the size of a pigeon, with ash-grey plumage barred with black, whose female lays her eggs in the nests of other birds.

So the answer to the question of "what do you want for dinner?" could be "bugs".
:wink:

char *words[] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)"};

plus:

if (texte>0 && texte<=7 && printer==true){
  lcd.setCursor(0, 0);
  lcd.clear();
    lcd.print(words[texte-1]);
    printer = false;
}

makes

int ButtonPin=2;
int LastButton;
int CurrentButton;
int texte=0;
bool printer=false;
#include <LiquidCrystal.h>;
LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

char *words[] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)"};

void setup() {
 pinMode(ButtonPin,INPUT);
  // put your setup code here, to run once:
  Serial.begin(9600);
  lcd.begin(16, 2);
}

void loop() {
  CurrentButton = digitalRead(ButtonPin);
  if (CurrentButton == HIGH && CurrentButton != LastButton) {
    texte++;
    printer = true;
  }
  if (texte > 0 && texte <= 7 && printer == true) {
    lcd.setCursor(0, 0);
    lcd.clear();
    lcd.print(words[texte - 1]);
    printer = false;
  }
  if (texte > 7) {
    lcd.clear();
    texte = 0;
  }
  LastButton = CurrentButton;
  Serial.print(texte);
}

The behavior when texte==0 is a little weird. And I'd likely move the texte value-guarding and the Serial.print(texte); inside of the one-shot button if() where texte is changed.

This is a little better

int ButtonPin = 2;
int LastButton;
int CurrentButton;
int texte = 0;
bool printer = false;
#include <LiquidCrystal.h>;
LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

char words[][9] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)"}; // 9 = length of longest

void setup() {
  pinMode(ButtonPin, INPUT_PULLUP);
  Serial.begin(115200);

  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("Mash button");
}

void loop() {
  CurrentButton = digitalRead(ButtonPin);
  delay(100); // "debounce" the button

  if (CurrentButton == LOW && CurrentButton != LastButton) {  
    printer = true;
  }

  for (int i = 0; i < 8; i++) {
    if (texte == i && printer == true) {
      lcd.setCursor(0, 0);
      lcd.clear();
      lcd.print(words[i]); // show array word
      printer = false;
      Serial.print(texte);
      texte++;
    }
  }

  if (texte > 7) {
    lcd.clear();
    texte = 0;
  }
  LastButton = CurrentButton;  
}

What happens when texte==7?

The for loop will satisfy, so it will lcd.print(words[7]) which is undefined.

The guarding should guard against texte > 6

Or better, count the words in the array into a constant, and use that:

const int NUM_MESSAGES = sizeof(words)/sizeof(words[0]);
...
for (i =0 ; < NUM_MESSAGES; i++){

...

if(texte > NUM_MESSAGES){
...

...

My research left me two choices. A greeting as in "hello" or "hey there", or the name of a cat.

a7

Yeth. That's button library ezButton small e small z big B button, and it does not suck. It truly is easy to use.

But do set the debounce time, or it doesn't debounce. And call its loop() method more than once per debounce interval or it misses events.

I have discovered this and may stop saying it doesn't suck, but prolly wouldn't go so far as to say that it does…

Meanwhile, I have discovered madleech's button library. I don't think you can get it through the library manager, but it is worth knowing how to install a library without. Find it here:

The default debounce time is 100 milliseconds. It reacts on the first edge of events so that long period doesn't introduce sluggish response, but 100 ms does limit the speed you can pound away on a button. Fortunately there is a constructor that takes an argument that sets the debounce time.

It's easier to use than ezButton, a bit. It has no loop() method and does not care if you call it frequently or not so frequently.

What can I say except it sucks less than ezButton? :expressionless:

If it were an official library it would be the one I'd tell ppl about.

a7

2 Likes
char words[][9] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)", "Meow"};

:wink:

We use coucou to say hello casually. The text is (in poor expression style in french)

Hey, what do you feel like eating tonight?


Probably better if words don’t change to use

const char * words[] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)"};

Or in better French

const char * words[] = {"Coucou", "que", "veux", "tu", "manger", "ce", "soir ? :)"};

Well, the for() statement is redundant, as it just prints the current ("texte") index number from the words array.
You can replace it entirely with just a single "if()":

    if (printer) {
      lcd.setCursor(0, 0);
      lcd.clear();
      lcd.print(words[texte++]);
      printer = false;
    }

@docdoc

Lets do away with 'printer' even...

int ButtonPin = 2;
int LastButton;
int CurrentButton;
int texte = 0;
#include <LiquidCrystal.h>;
LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

char words[][12] = {"Coucou", "tu", "veux", "manger", "quoi", "ce", "soir ? :)", "Mash button"}; // 9 = length of longest

void setup() {
  pinMode(ButtonPin, INPUT_PULLUP);
  Serial.begin(115200);

  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print(words[7]);
}

void loop() {
  CurrentButton = digitalRead(ButtonPin);
  delay(100); // "debounce" the button

  if (CurrentButton == LOW && CurrentButton != LastButton) {
    Serial.print(texte);
    lcd.setCursor(0, 0);
    lcd.clear();
    lcd.print(words[texte]);
    if (++texte > 7) texte = 0;
  }
  LastButton = CurrentButton;
}

a little simpler,
easier to change the list of words without changing hard coded values

// menu sequencer

# include <LiquidCrystal.h>;
int ButtonPin = 2;

LiquidCrystal lcd (7, 8, 9, 10, 11, 12);

int lastButton;
int texte = 0;

const char *words [] = {
    "Coucou", "tu", "veux",      "manger",
    "quoi",   "ce", "soir ? :)", "Mash button"
};
const int Nwords = sizeof(words)/sizeof(words [0]);

// -----------------------------------------------------------------------------
void loop ()
{
    byte but = digitalRead (ButtonPin);

    if (lastButton != but)  {           // state change
        lastButton  = but;
        delay (100);                    // "debounce" the button

        if (LOW == but)  {              // pressed
            if (Nwords <= ++texte)
                texte = 0;
            Serial.print (texte);

            lcd.setCursor (0, 0);
            lcd.clear     ();
            lcd.print     (words[texte]);
        }
    }
}

// -----------------------------------------------------------------------------
void setup () {
    pinMode (ButtonPin, INPUT_PULLUP);
    lastButton = digitalRead (ButtonPin);

    Serial.begin (115200);

    lcd.begin     (16, 2);
    lcd.setCursor (0, 0);
    lcd.print     (words[texte]);
}
1 Like

The call to setCursor() is not necessary if you call clear()