Sim. Multi-Thread Function ( Look for input while scrolling ) <Maybe Fixed >

I want to scroll instructions on a 16 by 2 LCD display for new users but they take 18 seconds to complete. I have it scroll only on the top line. I would like for more experienced users to use the 4 by 4 membrane to jump ahead and enter key presses while the scrolling instructions are still running.
I tried my own ideas and then I looked around the web and found the following code on the Arduino.cc forum by Efficens Systems . Perhaps I could go in a different direction but this looks like it is the closes to what I want to do. I believe that it was intended to be a starting point to be customized. Unfortunately, I couldn’t get it to work. I got it to compile but I didn’t get responses to key entry. Perhaps I need to include a Timer function, I don’t know. I didn’t want to hijack the thread where I found the code so I am entering it here..
I want to interrupt the scroll and move on if I get a key push but I think that I can work out the details of that if I can get notice of the read.
Does anyone have any input that could help?

// My inclusions
// My declarations
// My variables
typedef struct Timer 
{
unsigned long start;
unsigned long timeout;
};

char TimerExpired ( struct Timer * timer ) 
{
if ( millis () > timer->start + timer->timeout ) 
return true;

return false; 
}

void TimerStart ( struct Timer * timer ) 
{
timer->start = millis ( );
}

//Display task running every 3000 milliseconds 
Timer timerDisplay = { 0, 3000 };
//Keyboard task running every 100 milliseconds 
Timer timerKeyboard = { 0, 100 };

void setup (void) 
{
// Mmy particular setup stuff
}

void loop (void) 
{
if ( TimerExpired ( & timerDisplay ) )
{
taskDisplay ( );                      //   I changed to scrollInstructions(); 
TimerStart ( & timerDisplay );
}

if ( TimerExpired ( & timerKeyboard ) ) 
{
taskKeyboard ( );                 )   //  I changed to checkKeypadEntry()
TimerStart ( & timerKeyboard );
}
}

void taskKeyboard ( void )   //  I changed to checkKeypadEntry()
{
//  If/ When key(s) are pressed, kill scroll, change display  and do the next think
}

void taskDisplay ( void )       //   I changed to scrollInstructions()
{                                                 //   Scrolls only the top line
}
//  Several  other Voids forOtherTasksToDo()
//  {
//  }

You really want the Blink Without Delay idea.
Check the time, move scroll the text if enough time elapsed since the last scroll, otherwise do other stuff, like like reading from the keypad.
Some people use an interrupt to signal when to scroll.

void loop(){
currentMillis = millis();
elapsedMillis1 = currentMillis - previousMillis1;
if (elapsedMillis1 >=holdTime1){  // holdTime1 = 1000UL for 1/second updates for example
previousMillis1 = previousMillis1 + holdTime1;  // all time elements are unsigned long
// make text scroll
} // end time check
// do other stuff, like update the array that holds the text to be displayed
elapsedMillis2 = currentMillis - previousMillis2;
if (elapsedMillis2 >= duration2){  // duration2 is some other timed event
previousMillis2 = previousMillis2 + duration2;
// do other timed, cyclic event
}
// do other stuff when not doing the timed, cyclic stuff
} // end loop

Similar to what you attempted, without all the function calls.

Can I use the natural timing of the scrolling FOR loops?

void scrollInstructions()
{
for (int i=16; i > -16; i--)
{
lcd.setCursor(0,0);
lcd.print("Do This");
lcd.setCursor(i, 1);
//check?
Repeat about 5 more FORs with:
lcd.print("Then do this”);
//check?
lcd.print(“And that”);
//check?
lcd.print(“and finally”);
//check?
lcd.print(“Enter THIS.”);
// With no delay()s
// Finishes after 18 seconds
}
}

Depends how responsive you want to be to other stuff.
Each lcd.print takes a 1/2 second?
I would keep a pointer in the timing loop (basically do this part manually - for (int i=16; i > -16; i--) and each 1/2 second do the next lcd.print, then be doing other stuff until the next 1/2 second came around.

All I would need to do is check for a key press and come back and I don’t know how long that takes but if that gets done before the next loop starts then I hope that it wouldn’t slow down much.

If that is true, how can I do it? I tried and I couldn’t get the read to show up inside the FOR so that I could exit. I used globals but that didn’t help.

Like I explained - you get rid of the For:

int i = 16;
void loop(){
currentMillis = millis();
elapsedMillis1 = currentMillis - previousMillis1;
if (elapsedMillis1 >=holdTime1){  // holdTime1 = 5000UL for 1/2 second updates for example
previousMillis1 = previousMillis1 + holdTime1;  // all time elements are unsigned long

// now the broken up 'for' loop
lcd.setCursor(0,0);
lcd.print("Do This");
lcd.setCursor(i, 1);

//and 'manually' keeping track of where in the 'for' loop
i=i-1;
if (i > -16){i = 16;} // reset after the end

// make text scroll
} // end time check
// do other stuff, like update the array that holds the text to be displayed
elapsedMillis2 = currentMillis - previousMillis2;
if (elapsedMillis2 >= duration2){  // duration2 is some other timed event
previousMillis2 = previousMillis2 + duration2;
// do other timed, cyclic event
}
// do other stuff when not doing the timed, cyclic stuff
} // end loop

I am really sorry but I don’t have enough experience to figure this out. I broke a string like this:

“Do This And that and finally ENTER THIS.”

Into several ‘for’ loops that follow each other, inside one function. Each loop scrolls a part of the line to look like one long one. It doesn’t loop for just 16 interations. It loops for …say 40 clicks.

How do I get rid of the set of ‘for’s?

Is thee any reason you aren't showing us your entire code for scrolling? It's difficult to give advice while tryint to guess what you're doing.

Yes I am at work but I am leaving right now. I will be there at about 6:30.

This the code that I use to scroll a message. I wanted to be able to leave this prior to finishing if the user enters a keypad button push.

void scrollInstructons()
{
         for (int i=16; i > -1; i--)
          {   
          lcd.setCursor(i, 0);
          lcd.print("Enter Number");   
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
         for (int i=16; i > -1; i--)
          {         
          lcd.setCursor(i, 0);
          lcd.print("of Steps. ");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
         for (int i=16; i > -1; i--)
          {         
          lcd.setCursor(i, 0);
          lcd.print("Then  Press ");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }    
         for (int i=16; i > -1; i--)
          {         
          lcd.setCursor(i, 0);
          lcd.print("< or > ");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
         for (int i=16; i > -1; i--)
          {        
          lcd.setCursor(i, 0);
          lcd.print("to Start");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
          lcd.setCursor(0,0);
          lcd.print("Enter #");  
}

This is how I look to key presses.

void loop()
{
 keyPadFunction();
}
void keyPadFunction()
{
//Serial.println("keyPadFuction");  
  char key = keypad.getKey();
  
  if (key)
   {
   in = key;

  switch (in) 
     {
    case '1':
       Serial.println("I read the number 1*");
       if(menL == mainMenu)
         {                    
         menL = autoMenu;
         makeMenu();
         break;
         }
       else if(menL == autoMenu)
         {                    
         Serial.println("Resume 1 Done");
         }
       else if(menL == manualMenu)
         {
         Serial.println("manualMenu");
         tempLocation = totalSteps;
         Serial.println(tempLocation);         
         menL = makeDistanceMenu;         
         makeMenu();
         }         
       else if(menL == makeDistanceMenu)
         { 
          makeDistance(in);
          menL = makeDistanceMenu;
         }
       else if(menL == manualStepMenu)
         { 
         }            
      break;
     case '2':
//Followed by cases 3 through 9, 0, A, B, C, D, * and #

I guess I wasn't fully clear. Could you post your ENTIRE code? If the keyboard code isn't in the same sketch as the lcd scrolling code, please post the entire code that includes the scrolling code. Snippets just don't work well for trying to get teh whole picture.

First half
This is not elegant. It's just brute force.
If you were to set it up for a normal LCD and a keypad and compile it, you would want to press '#' then '2' and then '1'. At that point you can punch in 1 to 4 digits but no more. Then you would hit C or D.
Of course it may be easier to just follow the code through the key presses.

#include <Keypad.h>
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

int i;
int in;
int DIR;
//int keyCheck = 0;
int xLocation;
int yLocation;
int tempLocation;
int totalSteps = 10;
int stepsToNextStation;
int manualStepsToNextStation;
int autoInputFork;
int inputDigitCount = 0;
String getDistance;
//       can't use 0        1         2           3               4                 5        6
enum menuLevel { UNDEF, mainMenu, autoMenu, manualMenu, manualStepMenu, makeDistanceMenu, keyCheck };
menuLevel menL = mainMenu;

const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char key;
char key2;
char keys[ROWS][COLS] = 
{
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte rowPins[ROWS] = {45, 43, 41, 39}; //connect to the row pinouts of the keypad 37, 35, 33, 31
byte colPins[COLS] = {37, 35, 33, 31}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

void setup()
{
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.setCursor(0,0);       
  lcd.print("Running");  
}

void loop()
{

 keyPadFunction();

}

void keyPadFunction()
{
//Serial.println("keyPadFuction");  
  char key = keypad.getKey();
  
  if (key)
   {
   in = key;

  switch (in) 
     {
    case '1':
       Serial.println("I read the number 1*");
       if(menL == mainMenu)
         {                    
         menL = autoMenu;
         makeMenu();
         break;
         }
       else if(menL == autoMenu)
         {                    
         Serial.println("Resume 1 Done");
         }
       else if(menL == manualMenu)
         {
         Serial.println("manualMenu");
         tempLocation = totalSteps;
         Serial.println(tempLocation);         
         menL = makeDistanceMenu;         
         makeMenu();
         }         
       else if(menL == makeDistanceMenu)
         { 
          makeDistance(in);
          menL = makeDistanceMenu;
         }
       else if(menL == manualStepMenu)
         { 
         }            
      break;
     case '2':
       Serial.println("I read the number 2");
        if(menL == mainMenu)
         {
         menL = manualMenu;
         makeMenu();         
         }
       else if(menL == autoMenu)
         {                    
         Serial.println("Re-Start 2 Done");
         }
       else if(menL == manualMenu)
         {           
         menL = manualStepMenu;         
         makeMenu();
         }            
        else if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }        
      break;
     case '3':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }
         Serial.println("I read the number 3");
      break;
     case '4':
        if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }    
         Serial.println("I read the number 4");
      break;
     case '5':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }     
       Serial.println("I read the number 5");
      break;
     case '6':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }     
       Serial.println("I read the number 6");
      break;
     case '7':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }     
        Serial.println("I read the number 7");
      break;
     case '8':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }     
       Serial.println("I read the number 8");
      break;
     case '9':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }     
       Serial.println("I read the number 9");
      break;
     case '0':
       if(menL == makeDistanceMenu)
         {
          makeDistance(in);
          menL = makeDistanceMenu;
         }     
       Serial.println("I read the number 0");
      break;
      case 'A':
       Serial.println("I read the number A");
        if( menL == manualStepMenu )
         {
         raiseTheBoom();
         }
       break;
      case 'B':
       Serial.println("I read the number B");
       if( menL == manualStepMenu )
        {
       lowerTheBoom();
        }
       break;
      case 'C':
       Serial.println("I read the number C");
       DIR = -1;
       if( menL == makeDistanceMenu )
        {
       show();
        }
       if( menL == manualStepMenu )
        {
        goLeft();
        }       
       break;
      case 'D':
       Serial.println("I read the number D");
       DIR = 1;
       if( menL == makeDistanceMenu )
        {
        show();
        }
       if( menL == manualStepMenu )
        {
        goRight();
        }                
       break;
      case '*':
       Serial.println("I read the number STOP*");
       doAnAllStop();
       break;
      case '#':
       Serial.println("I read the number #");
       Serial.println(menL);
       if(menL == makeDistanceMenu)
       {
        Serial.println("Go to Start Point"); 
        yLocation = tempLocation - totalSteps;
        yLocation = abs(yLocation); 
        Serial.println(xLocation);
        Serial.println(yLocation);
          if(xLocation >= 0 ) 
            {
            DIR = -1;
            } 
          else 
            {
            DIR = 1;
            }
        stepsToNextStation = yLocation;  
        moveToNextStation(stepsToNextStation, DIR);
        Serial.println("Am I back? #");
        menL = mainMenu;
        makeMenu();
       }
       else
       {
       manualEntryPoint();
       }       
       break;
      default:      
       break;
     }
   }
}

Second part

void manualEntryPoint()   // <<<<<<<<<<<<<<<<<<<<<<<<<
{
    stepsToNextStation = 0;
    inputDigitCount = 0;    
    lcd.setCursor(0,0);
    lcd.print("                ");    
    lcd.setCursor(0,0);
    lcd.print("Automatic Manual");   
    lcd.print("                ");
    lcd.setCursor(0,1);     
    lcd.println("   1   or   2   ");
    menL = mainMenu;
    inputDigitCount = 0;
    stepsToNextStation = 0;
    DIR = 0;
    
}

void doAnAllStop() 
{
   allStop();
}

void tooManyDigits()
{
  lcd.setCursor(0,0);
  lcd.print("                ");
  lcd.setCursor(0,1);  
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("                ");  
  lcd.setCursor(0,0);
  lcd.print("Only 4 digits   ");
  delay(1000);
  menL = makeDistanceMenu;
  makeMenu();   
}

void goLeft() 
{
    Serial.println("Manual Left");
    DIR = 1;
    moveToNextStation(stepsToNextStation, DIR);
    inputDigitCount = 0;    
    stepsToNextStation = 0;
}

void goRight()    
{
    Serial.println("Manual Right");  
    DIR = -1;
    moveToNextStation(stepsToNextStation, DIR);    
    inputDigitCount = 0;
    stepsToNextStation = 0; 
}

void makeDistance(int in)
{
    lcd.setCursor(12,0);
    lcd.print("    ");
        
    if ( in >='0' && in <='9' ) 
     {
       if(inputDigitCount >= 4 )
        {
        tooManyDigits();
        }
        else
        {
        inputDigitCount = inputDigitCount + 1;       
        stepsToNextStation = stepsToNextStation * 10 + (in - '0');
        lcd.setCursor(12,0);
        lcd.print("    ");
        lcd.setCursor(12,0);        
        lcd.print(stepsToNextStation);         
        }  
     }  
}

void show() 
    {
//    manualControl(manualStepsToNextStation, DIR);  
    moveToNextStation(stepsToNextStation, DIR);
    inputDigitCount = 0;
    stepsToNextStation = 0;
    DIR = 0;     
    lcd.setCursor(12,0);
    lcd.print("    ");    
    }
    
void makeMenu()
{ 
  switch (menL) 
     {
    case mainMenu:
         clearLCD();
         lcd.print("Automatic Manual");   
         lcd.print("                ");
         lcd.setCursor(0,1);     
         lcd.println("   1   or  2    ");
         break;               
    case autoMenu:         
         clearLCD();         
         lcd.print("Resume    1");     
         lcd.setCursor(0,1);
         lcd.print("Re-Start  2");
         break;         
    case manualMenu:         
         clearLCD();         
         lcd.print("Number of Stps 1");                  
         lcd.setCursor(0,1);
         lcd.print("Manual Mode    2");          
         break;        
    case manualStepMenu:    
         clearLCD();         
         lcd.print("Enter Direction ");                  
         lcd.setCursor(0,1);
         lcd.print("A=^ B=V C=< D=> ");
         break;
    case makeDistanceMenu:
         clearLCD();
         lcd.setCursor(0,1);         
         lcd.print("<=C or D=> to Go");
         scrollInstructons();     
         break;
  }    
 }

void scrollInstructons()
{
         for (int i=16; i > -1; i--)
          {   
          lcd.setCursor(i, 0);
          lcd.print("Enter Number");   
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
         for (int i=16; i > -1; i--)
          {         
          lcd.setCursor(i, 0);
          lcd.print("of Steps. ");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
         for (int i=16; i > -1; i--)
          {         
          lcd.setCursor(i, 0);
          lcd.print("Then  Press ");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }    
         for (int i=16; i > -1; i--)
          {         
          lcd.setCursor(i, 0);
          lcd.print("< or > ");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
         for (int i=16; i > -1; i--)
          {        
          lcd.setCursor(i, 0);
          lcd.print("to Start");
          lcd.setCursor(0,0);    
          lcd.print("                ");    
          }
          lcd.setCursor(0,0);
          lcd.print("Enter #");  
}
 
// These calls are in the main program and are simulated below
// so that this program can compile and flow.


void manualControl(int manualStepsToNextStation, int DIR)
{
Serial.println(tempLocation);
stepsToNextStation + manualStepsToNextStation;
moveToNextStation(stepsToNextStation, DIR);
}

//void moveToNextStation()
void moveToNextStation(int stepsToNextStation, int DIR)
{

  //This will be removed when incorporated into bigger program.
  Serial.println(String("Translate ") + int(stepsToNextStation) + String(" steps, multiplied by ") + int(DIR) + String(" for polarity."));
  for(int i=stepsToNextStation; i > 0; i--)
    {
     totalSteps = totalSteps + DIR;
     Serial.println(tempLocation);
     Serial.println(totalSteps);
     Serial.println(i);     
     delay(1000);     
    }
}

void raiseTheBoom() 
 {
 Serial.println("Raised the Boom");
 }
 
void lowerTheBoom() 
 {
 Serial.println("Lowered the Boom");
 }
 
 void allStop()
 {
 Serial.println("ALL STOP");  
 }
 
void clearLCD()
{
    lcd.setCursor(0,0);       
    lcd.print("                ");
    lcd.setCursor(0,1);       
    lcd.print("                ");    
    lcd.setCursor(0,0); 
    
}

This is the key stroke map. '#' start and re-start

OK, here's a little example program. It's the way I'd do the scrolling instructions. I used delay() for convenience, but you should use millis(). Start a new sketch load this up, and try it. The serial monitor won't scroll of course, but it will give you the idea. Make the changes noted in the program to output to the LCD.

char instructions[] = {  "           Enter Number of Steps. Then Press < or >    " };
char scrollstring[17] = {  '\0' };

void setup() {
  Serial.begin(115200);
  Serial.println();
}

void loop() {
  scrollinstuctions();
  Serial.println("Got a Key");
  while (1) {
  }
}

void scrollinstuctions() {
  bool keypressed = false;
  while (! keypressed ) {
    for (int i = 0; i < sizeof(instructions) - 16; i++) {
      strncpy(scrollstring, &instructions[i], 16);
      // lcd.setCursor(0,0)           //uncomment this
      Serial.println(scrollstring);   // change this to lcd.print(scrollstring)
      delay(200);
      if (Serial.available()) {  // change this to   if (keypad.keyStateChanged()) {
        keypressed = true;
        break;
      }
    }
    Serial.println();   //change this  to an lcd operation to clear the line
  }
}

Work this technique or something similar into your menu/keypad stuff.
Feel free to ask me about any of it.

I have got to say, that is cool. Now I have a lot to work through.
Thank you

I do have one question. You suggest that I use this:

void loop() {
  scrollinstuctions();
  Serial.println("Got a Key");
  while (1) {
  }

but I am now using this:

void loop()
{

 keyPadFunction();

}

Can they co-exist? Do You just put it after the while (1) {...} ? I will try it.
I put it on top and on bottom with no luck.

Oh and the:

void setup() {
  Serial.begin(115200);
  Serial.println();  // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
}

Is needed?

Well, if you put keyPadFunction(); right after scrollinstuctions();, it will work just fine, in that you will first scroll the instructions until the user hits a key (in this demo, the key is sent from the Serial Monitor), then the keypad function will do its thing. Then it will do it all over again. It's really up to you to place keyPadFunction(); into an appropriate place in your code.

Can they code exist? Do You just put it after the while (1) {...} ? I will try it.

Bear in mind that this is a demonstration only. loop() would just keep going to the scrollinstructions function, so the while(1) is there to stop the serial output so you can look at it when you send a character in from the Serial Monitor. Remember, as well, that the serial output is just to show you what happens. In your case, you'll want all the output to go to the LCD, and all the input to come from the keypad.

You could put that keyPadFunction(); call right there in the scrollinstructions(); function, where you detect a keyChangeState. Just keep referring to your plane, and the paths trhough it, and ensure that everything you are wanting to call can be called at tha appropriate time, and that you can get back to the right places in the code.

Oh and the:

void setup() {

Serial.begin(115200);
  Serial.println();  // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
}




Is needed?

No. I just put it there to give me a blank line on the Serial Monitor. Force of habit, as sometimes the Serial Monitor comes up with a bit of garbage at first.

While I have so em bud to chase down, the program is converted and works well. There is on problem that I can't get around.

void scrollinstuctions() 
{
  bool keypressed = false;
  while (! keypressed ) 
  {
    for (int i = 0; i < sizeof(instructions) - 16; i++) 
    {
      strncpy(scrollstring, &instructions[i], 16);
      // lcd.setCursor(0,0)           //uncomment this
      Serial.println(scrollstring);   // change this to lcd.print(scrollstring)
      delay(200);
/*  I can't get the next line to read the keypad. It works great if I enter a number into the serial window and hit enter on my extended keyboard. I have tried to use the keyPadFunction() or  char key = keypad.getKey() and got nothing. I have tried 'if( key )' by itself and with the previous char key... but nothing happened. ' if ( in )' is to active and stops right away and  if (keyStateChanged()) does nothing. Nothing works for me except input from the main keypad. What can I use?   */
      if (Serial.available()) {  // change this to   if (keyStateChanged()) {  //  <<<<<<
        keypressed = true;
        break;
      }
    }
    Serial.println();   //change this  to an lcd operation to clear the line
  }
}