After having the screen reset, I wanted to further improve on the Crystal Ball program by having the capability to display messages longer than 16 characters (the length of my LCD screen). Functionality as desired would be to have the message scroll across the bottom line, while the top line continues to have “The ball says: ” at all times. Additionally, the system would reset after the reset interval has passed, so the scrolling function has to be interruptible: no use of delay().

Here’s a video of the program in action!

The full code is located at the bottom. It’s not exactly the prettiest, but it does what I want, which is enough for me.

The first change was to pull all of the code related to actual displaying text on the LCD screen out of the loop() function and then that into it’s own function: displayAnswer(string, unsigned long). Flipping through the rest of the project books shows they never introduce writing your own functions, or what they are used for, which is surprising. Nonetheless, prior experience with C++ has me comfortable with this. The function wants two things: a string containing the answer to be displayed, and the current time.

Previously, the message would be displayed directly from the case statement in loop(). Now, that message is store to a string variable that will pass the message into the display function.

The display function has two objectives: parse the message for scrolling and display the answer. To do this there are a couple of flags. First is to check if an answer is supposed to be displayed. If so, we initialize the message to the screen and set the timer. Then, we check if the message is less than 16 characters. If it is, then we do not want to do anything because display is exactly what we want. Next, we check if the message has more than 16 characters to be displayed.  The check is done by comparing the starting position of the message with how many characters to the end. For example, one of the answers is “Of course the answer is yes” (Is it easier to just write “yes?” Yes. Is it as fun? No.). It has 27 characters.

show of words

If the scrollPosition is 0 at the start, we see that secondLine.length() - scrollPosition > lineLength is 27 - 0 > 16 which is true. This causes the program to increment scrollPosition to 1, and displays the substring of the message starting at position 1: f course the answer is yes. Because there are only 16 characters on the LCD screen, we see f course the ans. This continues until scrollPosition reaches position 11. Then the condition secondLine.length() - scrollPosition > lineLength is 27 - 11 > 16, which is false: 16 is not greater than 16. The program does not enter the increment branch but instead enters the reset branch:secondLine.length() - scrollPosition <= lineLength or 27 - 11 <= 16, which is true! The message displayed is he answer is yes. Having reached the end of the message, scroll position is reset to 0 and the scrolling can begin all over again. I’m pleased with how this function set worked out. The best part is that the function does not care how long the message is; if I decide to add in another message that is longer than 27 characters, I do not need to worry about the logic involved here to make it work: it’ll just work as designed. The only concern is that there are 10 seconds before the message is reset, and it takes 0.4 s for the message to scroll by one character. Meaning you can have message of up to 40 characters before the system resets. (41 characters technically but once the program shows the final character it will reset faster than you can see it.)

Everything else that was changed in the program are primarly flags to keep track of what should be displayed when.

I noticed I have two ifs, directly after each other: checking the scroll position and checking the timer. If I combine them to be

    else if(secondLine.length() - scrollPosition > lineLength && currentTime - previousMessageScrollTime > messageScrollInterval)

instead of

    else if(secondLine.length() - scrollPosition > lineLength){ //If the message is more than 16 characters
      if(currentTime - previousMessageScrollTime > messageScrollInterval){ //Has enough time passed to scroll the message?

the program slows down dramatically. I’m not sure why, it’s something I need to look into.

While writing this, I’ve been improving on the program a little and thinking about how to make it even better. What I would do next is to break up the displayAnswer() function a bit more. All the display answer function should do is display text to the second line. A separate function should parse the message for text, as a boolean function. After parsing the message, if there is an update to be made to the screen, the function returns true and then display message takes the information in to display it. Whereas if there isn’t, such as the interval has not passed yet or the message is less than 16 characters, then it would return false and the display answer function would not do anything. Nearly all of my variables are global so I do not need to worry about returning anything else back to the displayAnswer() function. I’m going to think about this.

Take a look at the full code after the break.

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int switchPin = 6;
int switchState = 0;
int prevSwitchState = 0;
int reply;
int activity;
unsigned long previousTime = 0;
long interval = 10000;
long messageScrollInterval = 400;
long messageScrollPause = 1000;
long previousMessageScrollTime = 0;
bool showAnswer = false;
bool answerShown = false;
String firstLine = "The ball says: ";
String answer;
String blankLine = "                ";
int scrollPosition = 0;
bool flicker = true;
int lineLength = 16;

void setup() {
  lcd.begin(16,2);
  pinMode(switchPin, INPUT);
  lcd.print("Ask the");
  lcd.setCursor(0,1);
  lcd.print("Crystal ball!");
}

void loop() {
  unsigned long currentTime = millis(); //Get the current time
  activity=0; //Assume that there is no activity
  switchState = digitalRead(switchPin); //Get the status of the level sensor
  if(switchState != prevSwitchState){
    if(switchState == LOW){
      showAnswer = true;
      answerShown = false;
      activity = 1; //There is activity so change the status
      previousMessageScrollTime = 0;
      scrollPosition = 0;
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(firstLine);
      reply = random(13); //Choose a message
      switch(reply){
        case 0:
        answer = "Yes";
        break;
        case 1:
        answer = "Most likely";
        break;
        case 2:
        answer = "Certainly";
        break;
        case 3:
        answer = "Outlook good";
        break;
        case 4:
        answer = "Unsure";
        break;
        case 5:
        answer = "Ask again";
        break;
        case 6:
        answer = "Doubtful";
        break;
        case 7:
        answer = "No";
        break;
        case 8:
        answer = "Absolutely not!";
        break;
        case 9:
        answer = "Only if you try";
        break;
        case 10:
        answer = "Of course the answer is yes";
        break;
        case 11:
        answer = "Try again later";
        break;
        case 12:
        answer = "Seems like it to me";
        break;
      }
    }
  }
  displayAnswer(answer, currentTime);
  if(activity == 0){ //If there is no activity in this cycle
    if(currentTime - previousTime > interval){ //If more than the desired time has passed
      lcd.clear(); //Reset the screen
      lcd.setCursor(0,0); //Set cursor to first column, first row.
      lcd.print("Ask the"); //Print
      lcd.setCursor(0,1); //Set cursor to first column, second row.
      lcd.print("Crystal ball!"); //Print
      previousTime = currentTime; //Reset the timer
      showAnswer = false;
      answerShown = false;
    }
  }
  else if(activity = 1){ //If there is activity this cycle
    previousTime = currentTime; //Reset the timer
    showAnswer = true;
  }
prevSwitchState = switchState; //Keep track of the switch
}

void displayAnswer(String secondLine, unsigned long currentTime){
  if(showAnswer){ //Are we supposed to show an answer?
    if(!answerShown){//Has the answer been shown yet? This is meant to initialize the screen with the answer
      lcd.setCursor(0,1); //sets the cursor to the second line
      lcd.print(secondLine); //prints the answer
      previousMessageScrollTime = currentTime; //sets the scroll timer
      answerShown=true; //sets a flag to avoid this branch now that the answer is initialzized
    }
    if(secondLine.length() <= lineLength){
      //Don't do anything, the message is less than 16 chars
    }
    else if(secondLine.length() - scrollPosition > lineLength){ //If the message is more than 16 characters
      if(currentTime - previousMessageScrollTime > messageScrollInterval){ //Has enough time passed to scroll the message?
        previousMessageScrollTime = currentTime; //reset timer
        scrollPosition++; // move scroll position by one
        lcd.setCursor(0,1); //moves cursor to second line
        lcd.print(blankLine); //clears the second line
        lcd.setCursor(0,1); //moves cursor to start of second line again
        lcd.print(secondLine.substring(scrollPosition));//writes the message
      }
    }
    else if(secondLine.length() - scrollPosition <= lineLength){//if we are at the end of the message
      if(currentTime - previousMessageScrollTime > messageScrollPause){//and if enough time has passed to scroll the message
        previousMessageScrollTime = currentTime; //reset the timer
        scrollPosition = 0; //reset the scroll position
        lcd.setCursor(0,1); //same as above, clear the second line and print to it
        lcd.print(blankLine);
        lcd.setCursor(0,1);
        lcd.print(secondLine);
      }
    }
  }
}