LCD Timer and Stepper Motor Help!

Hi all, first time Arduino user and first time forum poster here.

I'm currently working on a project that requires a motor to be running while a continuous count-up or count down timer runs on the LCD screen. I've tried a couple different approaches, one using millis(), one creating a separate timer method, and one that attempted to integrate the timer within my motor method. The millis() method doesn't constantly refresh to the LCD screen, and since the timer is supposed to be launched by a key press it oftentimes is inaccurate because there'll be a delay between resetting the system and pressing the button that millis() takes into account. The timer method didn't work because I couldn't have the motor and timer methods running simultaneously. The one that integrated the timer into the method looked promising at first, but after awhile I realized that my "seconds" weren't exactly equal to one second, and when I came back to troubleshoot it the next day, the motor wouldn't run while the same code was in play.

code is in the second comment, please, if anyone has any suggestions or overall critiques I'm open to anything and appreciate all feedback.

Please help!

best,
-KM

#include <LiquidCrystal.h>
#include <Keypad.h>
int dirpin = 9;
int steppin = 8;
int MS1 = 7;
int MS2 = 6;
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
    {'1','2','3','A'},
    {'4','5','6','B'},
    {'7','8','9','C'},
    {'*','0','#','D'}
};
byte rowPins[ROWS] = {22, 24, 26, 28}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {31, 33, 35, 37}; //connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
void setup(){
    Serial.begin(9600);
    keypad.addEventListener(keypadEvent); // Add an event listener for this keypad
    lcd.begin(16, 2);
    pinMode(dirpin, OUTPUT);
    pinMode(steppin, OUTPUT);
    pinMode(MS1, OUTPUT);
    pinMode(MS2, OUTPUT);
}
void loop(){
char key = keypad.getKey();
int i;
    if (key) {
        Serial.println(key);
    }
// Taking care of some special events.
void keypadEvent(KeypadEvent key){
 switch (keypad.getState()){ //just focusing on one button to work for now
    case PRESSED:
        if (key == '1') {
           lcd.setCursor(0,0); 
           lcd.print (" P-1"); //Displays "Program 1"
           lcd.setCursor(13,1); //Displays arrow in direction that motor is turning
            lcd.print("-->");          
           //countUp();
           setMotorClockwise(5000);
           lcd.setCursor(13,1);
           lcd.print("<--");
           setMotorCCW(5000);
       }
        break;
    }
}
//these are the methods that are called in my loop
 int start = millis();
 int sec = 0;
 int m= 0;
 int h = 0;
void setMotorClockwise(int s){ // this is only gonna work if the step between is 1 second
  int steps = s;
 int pause = 1000;
 pinMode(dirpin, OUTPUT);
 pinMode(steppin, OUTPUT);
   lcd.setCursor(0,1);
    lcd.print((sec/1000));
    lcd.print(" revs");
    digitalWrite(dirpin, HIGH); //Writes the direction to the EasyDriver DIR pin. (HIGH is clockwise).
  for (int i = 0; i < steps; i++){
    sec+=1;// This is to calculate the number of revolutions in one cycle
    digitalWrite(steppin, HIGH);
    sec+=1;
    delayMicroseconds(pause);
    sec+=1;
    digitalWrite(steppin, LOW);
    sec+=1;
    delayMicroseconds(pause);
    sec+=1;
  }
  lcd.setCursor(0,1);
  lcd.print(sec/1000);
  lcd.print(" revs");
  delay(0);  
  /*digitalWrite(dirpin, LOW); //Writes the direction to the EasyDriver DIR pin. (LOW is counter clockwise).
  //Turns the motor fast 1600 steps
    for (int i = 0; i < steps; i++){
    lcd.setCursor(0,1);
    lcd.print(h);
    lcd.print(":");
    lcd.print(m);
    lcd.print(":");
    lcd.print(sec/1000);
    sec++;
    digitalWrite(steppin, HIGH);
    sec++;
    delayMicroseconds(pause);
    sec++;
    digitalWrite(steppin, LOW);
    sec++;
    delayMicroseconds(pause);
    sec++;
  }
    lcd.setCursor(0,1);
    lcd.print(h);
    lcd.print(":");
    lcd.print(m);
    lcd.print(":");
    lcd.print(sec/1000);
    */
}
void setMotorCCW(int s){
  int steps = s;
 int pause = 1000;
 pinMode(dirpin, OUTPUT);
 pinMode(steppin, OUTPUT);
    digitalWrite(dirpin, LOW); //Writes the direction to the EasyDriver DIR pin. (HIGH is clockwise).
  for (int i = 0; i < steps; i++){
    sec++;
    digitalWrite(steppin, LOW);
    sec++;
    delayMicroseconds(pause);
    sec++;
    digitalWrite(steppin, HIGH);
    sec++;
    delayMicroseconds(pause);
    sec++;
  }
  lcd.setCursor(0,1);
  lcd.print(sec/1000);
  lcd.print(" revs");
  lcd.setCursor(8,0);
  lcd.print((int)((sec/1000)/2.5));
  delay(0); 
}
 void countUp() 
{ 
  int s=0;
  int m=0;
  int h=0;
    while(h<24){
       lcd.setCursor(0,1);  
       lcd.print(h); 
       if(m<10){
       lcd.print(":0"); 
       lcd.print(m);
       }
       else{
        lcd.print(":");
        lcd.print(m);
       }
       if(s<10){ 
       lcd.print(":0"); 
       lcd.print(s); 
       }
       else
       {
       lcd.print(":");
       lcd.print(s);
       }
       s+=1;
       delay(1000); 
       if(s==59){ 
        s=0; 
        m=m+1; 
       } 
       if(m==59) 
       { 
        m=0; 
        h=h+1; 
       }  
} 
}
void countDown(int h, int m, int s)
{
  int S=s;
  int M=m;
  int H=h;
 //lcd.setCursor(2,1);
 if(H<10)
  lcd.print("0");
 lcd.print(H);
 lcd.print(":");
 if(M<10)
  lcd.print("0");
 lcd.print(M);
 lcd.print(":");
 if(S<10)
  lcd.print("0");
 lcd.print(S);
 /*lcd.setCursor(5,1);
 lcd.print(":");*/
 while (S>-1)
 {
 S--;
 delay(1000);
 if(S<0)
 {
 M--;
 S=59;
 }
 if(M<0)
 {
 H--;
 M=59;
 }
 if(H<0)
 {
 H=23;
 M=59;
 S=59;
 }
 if(M>9)
 {
 lcd.setCursor(3,1);
 lcd.print(M);
 }
 else
 {
 lcd.setCursor(3,1);
 lcd.print("0"); 
 lcd.setCursor(4,1);
 lcd.print(M);
 lcd.setCursor(5,1);
 lcd.print(":");
 }
 if(S>9)
 {
 lcd.setCursor(6,1);
 lcd.print(S);
 }
 else
 {
 lcd.setCursor(6,1);
 lcd.print("0"); 
 lcd.setCursor(7,1);
 lcd.print(S);
 lcd.setCursor(8,1);
 lcd.print(" ");
 }
 if(H>9)
 {
 lcd.setCursor(0,1);
 lcd.print (H);
 }
 else
 {
 lcd.setCursor(0,1);
 lcd.print("0"); 
 lcd.setCursor(1,1);
 lcd.print(H);
 lcd.setCursor(2,1);
 lcd.print(":");
 }
}
}

Post the millis() style code you tried. That's what you're going to have to do. Would be better to fix that one.

#include <LiquidCrystal.h>
#include <Keypad.h>
int dirpin = 9;
int steppin = 8;
int MS1 = 7;
int MS2 = 6;
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
    {'1','2','3','A'},

    {'4','5','6','B'},

    {'7','8','9','C'},

    {'*','0','#','D'}
}
;byte rowPins[ROWS] = {22, 24, 26, 28}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {31, 33, 35, 37}; //connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
void setup(){
    Serial.begin(9600);
    keypad.addEventListener(keypadEvent); // Add an event listener for this keypad
    lcd.begin(16, 2);
    pinMode(dirpin, OUTPUT);
    pinMode(steppin, OUTPUT);
    pinMode(MS1, OUTPUT);
    pinMode(MS2, OUTPUT);
}
void loop(){
char key = keypad.getKey();
int i;
    if (key) {
       Serial.println(key);
    }
// Taking care of some special events.
void keypadEvent(KeypadEvent key){
}
 switch (keypad.getState()){
    case PRESSED:
        if (key == '1') {
           lcd.setCursor(0,0); 
           lcd.print (" P-1"); //Displays "Program 1"
           lcd.setCursor(13,1); //Displays arrow 
            for (int i = 0; i < 5; i++){
            lcd.print("-->");
           setMotorClockwise(2000);
           lcd.setCursor(13,1);
           lcd.print("<--");
           setMotorCCW(2000);
        }
        break;
    }
}
}
 int sec = 0;
void setMotorClockwise(int s){ // this is only gonna work if the step between is 1 second
  int steps = s;
 int pause = 1000;
 pinMode(dirpin, OUTPUT);
 pinMode(steppin, OUTPUT);
sec = millis();
    lcd.setCursor(0,1);
    lcd.print(sec/1000);
    digitalWrite(dirpin, HIGH); 
  for (int i = 0; i < steps; i++){
    sec++;
    digitalWrite(steppin, HIGH);
    delayMicroseconds(pause);
    sec++;
    digitalWrite(steppin, LOW);
    delayMicroseconds(pause);
    sec++;
  } 
  lcd.setCursor(0,1);
    lcd.print(sec/1000);
  delay(0);
}
void setMotorCCW(int s){
  int steps = s;
 int pause = 1000;
 pinMode(dirpin, OUTPUT);
 pinMode(steppin, OUTPUT);
    digitalWrite(dirpin, LOW);  
    lcd.setCursor(0,1);
    lcd.print(sec/1000);
  for (int i = 0; i < steps; i++){
    sec++;
    digitalWrite(steppin, LOW);
    delayMicroseconds(pause);
    sec++;
    digitalWrite(steppin, HIGH);
    delayMicroseconds(pause);
    sec++;
  }
    lcd.setCursor(0,1);
    lcd.print(sec/1000);
  delay(0); 
}
void keypadEvent(KeypadEvent key){
}
 switch (keypad.getState()){

You seem to have picked up an extra closing brace there. So keypadEvent is empty and that code after it is outside of any function and illegal.

The whole point of millis based code is that you don't tie the code up waiting on anything to happen. Let the loop keep running. On each turn of loop it might or might not be time to do something. The loop function needs variables to keep track of what step of the process it is on and what time the last thing happened. Then you need logic to use those to decide if it is time to do something and if so what to do.

In good non-blocking code there will be no while loops or for loops that wait on something physical to happen.

Delta_G:
The whole point of millis based code is that you don't tie the code up waiting on anything to happen.

So if I'm relying on something physical (in this case, a button press) to trigger a continuous timer that runs simultaneously with my motor, would millis() not be a good avenue to pursue?

It will spit out how much time has elapsed after each loop of the motor running code, but it won't run in the traditional sense of the timer, which is what my supervisor wants.

In short, what I'm aiming for is code that'll maybe set a variable and increment it after every delay, and after every delay print it to the LCD to create the illusion of a timer, but the few times I write code like that, my motor will freak out and, while the timer works, the motor won't spin as needed.

I've been stuck on this particular bug for longer than I'd care to admit, and I'm somewhat at a loss for where to go from here.

edit: would this code within the for loop maybe work better?

    int start = millis();
    int current = 0;
    for (int i = 0; i < steps; i++){
    current = millis();
    if(current-start>=1000)
    sec++;
    lcd.setCursor(0,1);
    lcd.print(sec/1000);
    digitalWrite(steppin, HIGH);
    delayMicroseconds(pause);
    digitalWrite(steppin, LOW);
    delayMicroseconds(pause);

or would setting start to millis() constantly update start with the current value of millis?

kristenm:
So if I'm relying on something physical (in this case, a button press) to trigger a continuous timer that runs simultaneously with my motor, would millis() not be a good avenue to pursue?

millis is the only way the Arduino is going to tell time. It's the only game in town.

kristenm:
It will spit out how much time has elapsed after each loop of the motor running code, but it won't run in the traditional sense of the timer, which is what my supervisor wants.

supervisor? You're not at work trying to get the Arduino forum to bail you out of getting into a job over your head are you?

Can you tell us what it is that we're building here? That might help to figure out what it is that you actually need to do.

kristenm:
In short, what I'm aiming for is code that'll maybe set a variable and increment it after every delay, and after every delay print it to the LCD to create the illusion of a timer, but the few times I write code like that, my motor will freak out and, while the timer works, the motor won't spin as needed.

That's a horrible way to handle timing. Let the millis timer keep track of the time. That's what it is for. And it runs on interrupt power so it keeps time a lot more accurately than what you describe.

kristenm:
I've been stuck on this particular bug for longer than I'd care to admit, and I'm somewhat at a loss for where to go from here.

Go to the top of this message board and find the useful links thread. Do some reading in there. Lots of good stuff you sound like you need to learn.

I'm a college student in a volunteer research position that was described as "no experience necessary" working with Arduino to create a motor that runs for a certain amount of time & can switch directions for lab experiments... hence the supervisor (grad student) who enjoys the term "just figure it out." I've been reading all the material about similar timing issues and have kept running into walls– this includes the useful links thread.

I understand that millis is my best bet for telling time, that's what I've been trying to use for the most part. I'm just trying to account for the time in the delay between device reset and between pressing the button that triggers the motor to run in a way that doesn't interfere with my motor, and in a way that it continuously prints to the LCD screen.

In fact, I think I have the millis thing figured out in terms of keeping accurate time, it's just the continuous LCD "timer" visual that's eluding me. That's my struggle, more than having something that is keeping track of time.

Does that make any sense?

In short– freshman in college working with arduino for the first time (and responsible for the hardware and electrical components that I also had no experience with) with impatient grad students expecting results faster than I can debug my program. I can keep accurate time, but in a way that it's only refreshed to the screen every 8 seconds or so, not in a continuous 1...2...3 way that you might imagine like the screen of a stopwatch.
Looking for help on how to get this to happen at the same time that my motor is running, if that's even possible.

first eliminate ALL delay from your sketch.

Get the time from millis when you start. Subtract that from the current time at any moment to see how long has passed. Use that to determine what to print to your screen.

Also use that time to determine what your motor needs to do.

There will be no delays or for loops in the finished product. Let the loop function be the only loop. Set it up like a checklist that just goes down seeing what state you're in and what action needs to be taken in that state.

Will I be able to print that time constantly to my LCD screen so that it looks like a traditional timer? That is the current challenge.

kristenm:
Will I be able to print that time constantly to my LCD screen so that it looks like a traditional timer? That is the current challenge.

You don't want to print it constantly. It will look like it is flashing if you keep overwriting it too fast. You should only really overwrite it on the screen when it changes.

Yes, you could make it look like a traditional timer. You could do that and have it showing 1000ths of a second. But you CAN'T do it if your program contains delay statements. You have to leave the processor free to handle the screen and not tie it up doing nothing but waiting.

Delta_G:
You could do that and have it showing 1000ths of a second. But you CAN'T do it if your program contains delay statements.

So if the timer is running continuously on the screen I won't be able to have my motor running at the same time?

kristenm:
So if the timer is running continuously on the screen I won't be able to have my motor running at the same time?

Sure you can. You just can't use delay statements to control the timing. You have to let the loop function run freely and handle all the timing by watching the millis or micros timers.

Instead of take a step and wait, take a step and wait you have to write code that says "see if it is time to take a step and take it. and meanwhile check to see if it is time to update the screen and if so update it. "

Delta_G:
write code that says "see if it is time to take a step and take it. and meanwhile check to see if it is time to update the screen and if so update it. "

so it would maybe look something like

int startTime = millis();
if(millis()-startTime%1000 == 0) //I'm most familiar with modulo in Java, it would be whatever the equivalent is in Arduino
{
lcd.print((millis()-startTime)/1000);
}
if(millis()-startTime == 200) //whatever time i want between steps here
{
   //move the motor forward?
}
if(millis()-startTime%1000 == 0)

Are you absolutely certain that this will get checked on exactly the millisecond that it is true? Using modulo and == probably aren't the best idea here:

if(millis()-startTime == 200)

Same here. Are you absolutely certain that this gets checked on the exact millisecond when it is true? If not then you'll have to wait 50 days for it to be true again. You might want to use >= here instead.

  lcd.setCursor(0,1);
  int sec = millis()/1000;
  lcd.print(sec);

this is the code I currently have. In the loop, it works fine, but when I try to incorporate it into my cases things get messy. Is this because I'm switching to a method that exists outside of the scope of "loop" while the motor is running? Should I rewrite each case so it's no longer method based?

edit: the counter still keeps time while the motor is running, it just doesn't update on the LCD screen. So, it will go 1...2...3...4 button is pressed to start motor then remains at 4 for 10 seconds (arbitrary time based on my motor stuff) then after the motor finishes running the LCD screen prints 14...15...16.
So it is taking into account the time, just not updating it to the LCD while the motor runs. Is it possible to do that? I basically want the opposite of what's happening.. for it to hold onto the value for time elapsed before the button is pressed but not change what's happening on screen, then start a count when the button is pressed