How to Start stepper motor using Timer

Hello, I am working on a project that uses a timer to control a system. The system has two NEMA 17 stepper motors and a LED strip. The system is controlled by the timer entered through the keypad and I have trouble figuring out how to turn the motors on when the timer starts. Here is my code that only controls how long the LED strip turns on.

Here is my code.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>


int Blue_LED = 2;

char currentTimeValue[4];
int currentState = 1;
int timerSeconds = 0;
int lpcnt = 0;

const byte rows = 4;
const byte cols = 3;

char keys[rows][cols] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};

byte rowPins[rows] = {6,7,8,9};
byte colPins[cols] = {3,4,5};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
LiquidCrystal lcd(15,14,10,11,12,13);

void setup()
{
  lcd.begin(16,2);

  timeEntryScreen();

  pinMode(Blue_LED, OUTPUT);
  digitalWrite(Blue_LED, LOW);

  currentTimeValue[0]='0';
  currentTimeValue[1]='0';
  currentTimeValue[2]='0'; 
  currentTimeValue[3]='0';
  showEnteredTime();  
  
}

void loop()
{
  int l;
  char tempVal[3];

  char key = keypad.getKey();

  if(int(key) != 0 and currentState == 1) {

    switch (key) {
      case '*':
        lightStatus(false);
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
        break;

      case '#':
      tempVal[0] = currentTimeValue[0];
      tempVal[1] = currentTimeValue[1];
      tempVal[2] = 0;

      timerSeconds = atol(tempVal) * 60;

      tempVal[0] = currentTimeValue[2];
      tempVal[1] = currentTimeValue[3];
      tempVal[2] = 0;

      timerSeconds = timerSeconds + atol(tempVal);
      currentState = 2;
      break;

     default:
      currentTimeValue[0]= currentTimeValue[1];
      currentTimeValue[1]= currentTimeValue[2];
      currentTimeValue[2]= currentTimeValue[3];
      currentTimeValue[3]= key;
      showEnteredTime();
      break;  
    }
  }

  if(currentState == 2){
    if(int(key) != 0) {
      if(key == '*'){
        lightStatus(false); 
        timeEntryScreen();
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
      }
    } else {
      if(lpcnt > 9) {
        lpcnt = 0;
        --timerSeconds;
        showCountdown();

        if(timerSeconds <= 0) {
           currentState = 1;
           lightStatus(false);
           timeEntryScreen();
           showEnteredTime();
        } else {
          lightStatus(true);
        }
      }

      ++lpcnt;
      delay(100);
    }
  }
}

void showEnteredTime()
{
  lcd.setCursor(5,1);
  lcd.print(currentTimeValue[0]);
  lcd.print(currentTimeValue[1]);
  lcd.print(":");
  lcd.print(currentTimeValue[2]);
  lcd.print(currentTimeValue[3]);
}

void lightStatus(bool state)
{
  if(state)
    digitalWrite(Blue_LED, HIGH);
  else
    digitalWrite(Blue_LED, LOW);    
}
 
void showCountdown()
{
  char timest[6];

  lcd.setCursor(0,0);
  lcd.print("** TIME LEFT: **");
  lcd.setCursor(0,1);
  lcd.print("**    ");
  sprintf(timest, "%d:%.2d",(timerSeconds/60),(timerSeconds - ((timerSeconds/60*60))));
  lcd.print(timest);
  lcd.print("    **");
}

void timeEntryScreen()
{
  clearScreen();
  lcd.setCursor(0,0);
  lcd.print("** ENTER TIME **");
}

void clearScreen()
{
  lcd.setCursor(0,0);
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("                ");
}

code is unusable.. please correct your post and use the </> code tags

I have corrected my post.

the timer gets activated and ready to countdown when users presses # and currentState is set to 2

so you could start the motor just the line before

currentState = 2;

and stop the motor where currentState is set to 1 (happens twice as you can cancel operation by pressing star)

currentState = 1;

Okay. Thank you for the help!

So I called the function before

currentState = 2;

and also before

currentState =1;

and made them bool function when calling them. When I enter the time though, the motors do nothing, and I'm not sure if I called the functions correctly. Could you tell me if the functions look correct.
Here is the code.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>


int Blue_LED = 2;

int stepper1_DIR = 17;
int stepper1_STEP = 18;
int stepper2_DIR = 19;
int stepper2_STEP = 20;
int Distance = 0;  // Record the number of steps we've taken

char currentTimeValue[4];
int currentState = 1;
int timerSeconds = 0;
int lpcnt = 0;

const byte rows = 4;
const byte cols = 3;

char keys[rows][cols] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};

byte rowPins[rows] = {6,7,8,9};
byte colPins[cols] = {3,4,5};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
LiquidCrystal lcd(15,14,10,11,12,13);

void setup()
{
  lcd.begin(16,2);

  timeEntryScreen();

  pinMode(Blue_LED, OUTPUT);
  digitalWrite(Blue_LED, LOW);

  pinMode(stepper1_DIR, OUTPUT);    
  pinMode(stepper1_STEP, OUTPUT);
  digitalWrite(stepper1_DIR, LOW);
  digitalWrite(stepper1_STEP, LOW);
  pinMode(stepper2_DIR, OUTPUT);    
  pinMode(stepper2_STEP, OUTPUT);
  digitalWrite(stepper2_DIR, HIGH);
  digitalWrite(stepper2_STEP, LOW);
  
  currentTimeValue[0]='0';
  currentTimeValue[1]='0';
  currentTimeValue[2]='0'; 
  currentTimeValue[3]='0';
  showEnteredTime();  
  
}

void loop()
{
  int l;
  char tempVal[3];

  char key = keypad.getKey();

  if(int(key) != 0 and currentState == 1) {

    switch (key) {
      case '*':
        lightStatus(false);
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        motorStatus(false);
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
        break;

      case '#':
      tempVal[0] = currentTimeValue[0];
      tempVal[1] = currentTimeValue[1];
      tempVal[2] = 0;

      timerSeconds = atol(tempVal) * 60;

      tempVal[0] = currentTimeValue[2];
      tempVal[1] = currentTimeValue[3];
      tempVal[2] = 0;

      timerSeconds = timerSeconds + atol(tempVal);
      motorStatus(true);
      currentState = 2;
      break;

     default:
      currentTimeValue[0]= currentTimeValue[1];
      currentTimeValue[1]= currentTimeValue[2];
      currentTimeValue[2]= currentTimeValue[3];
      currentTimeValue[3]= key;
      showEnteredTime();
      break;  
    }
  }

  if(currentState == 2){
    if(int(key) != 0) {
      if(key == '*'){
        lightStatus(false); 
        timeEntryScreen();
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
      }
    } else {
      if(lpcnt > 9) {
        lpcnt = 0;
        --timerSeconds;
        showCountdown();

        if(timerSeconds <= 0) {
           currentState = 1;
           lightStatus(false);
           timeEntryScreen();
           showEnteredTime();
        } else {
          lightStatus(true);
        }
      }

      ++lpcnt;
      delay(100);
    }
  }
}

void showEnteredTime()
{
  lcd.setCursor(5,1);
  lcd.print(currentTimeValue[0]);
  lcd.print(currentTimeValue[1]);
  lcd.print(":");
  lcd.print(currentTimeValue[2]);
  lcd.print(currentTimeValue[3]);
}

void lightStatus(bool state)
{
  if(state)
    digitalWrite(Blue_LED, HIGH);
  else
    digitalWrite(Blue_LED, LOW);    
}
 
void showCountdown()
{
  char timest[6];

  lcd.setCursor(0,0);
  lcd.print("** TIME LEFT: **");
  lcd.setCursor(0,1);
  lcd.print("**    ");
  sprintf(timest, "%d:%.2d",(timerSeconds/60),(timerSeconds - ((timerSeconds/60*60))));
  lcd.print(timest);
  lcd.print("    **");
}
void motorStatus(bool state)
{
  digitalWrite(stepper1_STEP, HIGH);
  delayMicroseconds(400);         
  digitalWrite(stepper1_STEP, LOW);
  delayMicroseconds(400);
  digitalWrite(stepper2_STEP, HIGH);
  delayMicroseconds(400);         
  digitalWrite(stepper2_STEP, LOW);
  delayMicroseconds(400);
  Distance = Distance + 1;   // record this step

 
  // Check to see if we are at the end of our move
  if (Distance == 3600)
  {
    // We are! Reverse direction (invert DIR signal)
    if (digitalRead(stepper1_DIR) == LOW and digitalRead(stepper2_DIR) == HIGH)
    {
      digitalWrite(stepper1_DIR, HIGH);
      digitalWrite(stepper2_DIR, LOW);
    }
    else
    {
      digitalWrite(stepper1_DIR, LOW);
      digitalWrite(stepper2_DIR, HIGH);
    }
    // Reset our distance back to zero since we're
    // starting a new move
    Distance = 0;
    // Now pause for half a second
    delay(1000);
  }
}
void timeEntryScreen()
{
  clearScreen();
  lcd.setCursor(0,0);
  lcd.print("** ENTER TIME **");
}

void clearScreen()
{
  lcd.setCursor(0,0);
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("                ");
}

your motorStatus() function only takes 1 step so would need to be called continuously during the countdown but it is full of delays (esp the 1s at the end) that will mess up with the countdown

have a look at the accelStepper library, you can just start or stop the stepper and give it a number of steps to complete at a given speed

PS: you missed stopping the stepper when the countdown reaches 0, there are two currentState = 1;where you don't call your motor stuff

Okay thank you.

So I changed the motorStatus function to incorporate the accelStepper library to have no delays and then I also called the functions where I missed when the currentState = 1. The motors still don't work and I am not sure if it is because I am calling the functions incorrectly or if my motorStatus function is incorrect. I tested the motorStatus function in a different sketch and works perfectly fine, but not here.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <AccelStepper.h>


int Blue_LED = 2;

AccelStepper stepper1(AccelStepper::DRIVER, 18, 17);
AccelStepper stepper2(AccelStepper::DRIVER, 20,19);

int pos1 = 3600;
int pos2 = 3600;

char currentTimeValue[4];
int currentState = 1;
int timerSeconds = 0;
int lpcnt = 0;

const byte rows = 4;
const byte cols = 3;

char keys[rows][cols] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};

byte rowPins[rows] = {6,7,8,9};
byte colPins[cols] = {3,4,5};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
LiquidCrystal lcd(15,14,10,11,12,13);

void setup()
{
  lcd.begin(16,2);

  timeEntryScreen();

  pinMode(Blue_LED, OUTPUT);
  digitalWrite(Blue_LED, LOW);

  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(1000);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(1000);
  
  currentTimeValue[0]='0';
  currentTimeValue[1]='0';
  currentTimeValue[2]='0'; 
  currentTimeValue[3]='0';
  showEnteredTime();  
  
}

void loop()
{
  int l;
  char tempVal[3];

  char key = keypad.getKey();

  if(int(key) != 0 and currentState == 1) {

    switch (key) {
      case '*':
        lightStatus(false);
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        motorStatus(false);
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
        break;

      case '#':
      tempVal[0] = currentTimeValue[0];
      tempVal[1] = currentTimeValue[1];
      tempVal[2] = 0;

      timerSeconds = atol(tempVal) * 60;

      tempVal[0] = currentTimeValue[2];
      tempVal[1] = currentTimeValue[3];
      tempVal[2] = 0;

      timerSeconds = timerSeconds + atol(tempVal);
      motorStatus(true);
      currentState = 2;
      break;

     default:
      currentTimeValue[0]= currentTimeValue[1];
      currentTimeValue[1]= currentTimeValue[2];
      currentTimeValue[2]= currentTimeValue[3];
      currentTimeValue[3]= key;
      showEnteredTime();
      break;  
    }
  }

  if(currentState == 2){
    if(int(key) != 0) {
      if(key == '*'){
        lightStatus(false); 
        timeEntryScreen();
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        motorStatus(false);
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
      }
    } else {
      if(lpcnt > 9) {
        lpcnt = 0;
        --timerSeconds;
        showCountdown();

        if(timerSeconds <= 0) {
          motorStatus(false);
           currentState = 1;
           lightStatus(false);
           timeEntryScreen();
           showEnteredTime();
        } else {
          motorStatus(true);
          lightStatus(true);
        }
      }

      ++lpcnt;
      delay(100);
    }
  }
}

void showEnteredTime()
{
  lcd.setCursor(5,1);
  lcd.print(currentTimeValue[0]);
  lcd.print(currentTimeValue[1]);
  lcd.print(":");
  lcd.print(currentTimeValue[2]);
  lcd.print(currentTimeValue[3]);
}

void lightStatus(bool state)
{
  if(state)
    digitalWrite(Blue_LED, HIGH);
  else
    digitalWrite(Blue_LED, LOW);    
}
 
void showCountdown()
{
  char timest[6];

  lcd.setCursor(0,0);
  lcd.print("** TIME LEFT: **");
  lcd.setCursor(0,1);
  lcd.print("**    ");
  sprintf(timest, "%d:%.2d",(timerSeconds/60),(timerSeconds - ((timerSeconds/60*60))));
  lcd.print(timest);
  lcd.print("    **");
}
void motorStatus(bool state)
{
 if (stepper1.distanceToGo() == 0)
  {
     pos1 = -pos1;
    stepper1.moveTo(-pos1);
  }
  if (stepper2.distanceToGo() == 0)
  {
    pos2 = -pos2;
    stepper2.moveTo(pos2);
  }
  stepper1.run();
  stepper2.run();
}
void timeEntryScreen()
{
  clearScreen();
  lcd.setCursor(0,0);
  lcd.print("** ENTER TIME **");
}

void clearScreen()
{
  lcd.setCursor(0,0);
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("                ");
}

you are still only doing 1 step when motorStatus() is called

what you should do in motorStatus() is

  • if the state is true, give the steppers a target number of steps with the moveTo() function
  • if the state is false, you call stop()

And of course for the stepper to step, it need to be pinged as often as possible, so at the start of the loop() you call run() (that will only take a step if the stepper has not reached the target position / is not stopped)

I don't seem to understand what you mean by when I'm only doing one step when I see the distance already. In addition, I also added an if statement where if the bool state is true, go to the loop and if it is false, go to the else statement. I apologize if I keep asking for help. I am just confused why my function doesn't work.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <AccelStepper.h>


int Blue_LED = 2;

AccelStepper stepper1(AccelStepper::DRIVER, 18, 17);
AccelStepper stepper2(AccelStepper::DRIVER, 20,19);


char currentTimeValue[4];
int currentState = 1;
int timerSeconds = 0;
int lpcnt = 0;

const byte rows = 4;
const byte cols = 3;

char keys[rows][cols] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};

byte rowPins[rows] = {6,7,8,9};
byte colPins[cols] = {3,4,5};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);
LiquidCrystal lcd(15,14,10,11,12,13);

void setup()
{
  lcd.begin(16,2);

  timeEntryScreen();

  pinMode(Blue_LED, OUTPUT);
  digitalWrite(Blue_LED, LOW);

  stepper1.setMaxSpeed(1000);
  stepper1.setAcceleration(1000);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(1000);
  
  currentTimeValue[0]='0';
  currentTimeValue[1]='0';
  currentTimeValue[2]='0'; 
  currentTimeValue[3]='0';
  showEnteredTime();  
  
}

void loop()
{
  int l;
  char tempVal[3];

  char key = keypad.getKey();

  if(int(key) != 0 and currentState == 1) {

    switch (key) {
      case '*':
        lightStatus(false);
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        motorStatus(false);
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
        break;

      case '#':
      tempVal[0] = currentTimeValue[0];
      tempVal[1] = currentTimeValue[1];
      tempVal[2] = 0;

      timerSeconds = atol(tempVal) * 60;

      tempVal[0] = currentTimeValue[2];
      tempVal[1] = currentTimeValue[3];
      tempVal[2] = 0;

      timerSeconds = timerSeconds + atol(tempVal);
      motorStatus(true);
      currentState = 2;
      break;

     default:
      currentTimeValue[0]= currentTimeValue[1];
      currentTimeValue[1]= currentTimeValue[2];
      currentTimeValue[2]= currentTimeValue[3];
      currentTimeValue[3]= key;
      showEnteredTime();
      break;  
    }
  }

  if(currentState == 2){
    if(int(key) != 0) {
      if(key == '*'){
        lightStatus(false); 
        timeEntryScreen();
        currentTimeValue[0]='0';
        currentTimeValue[1]='0';
        currentTimeValue[2]='0';
        currentTimeValue[3]='0';
        showEnteredTime();
        motorStatus(false);
        currentState = 1;
        lpcnt = 0;
        timerSeconds = 0;
      }
    } else {
      if(lpcnt > 9) {
        lpcnt = 0;
        --timerSeconds;
        showCountdown();

        if(timerSeconds <= 0) {
          motorStatus(false);
           currentState = 1;
           lightStatus(false);
           timeEntryScreen();
           showEnteredTime();
        } else {
          motorStatus(true);
          lightStatus(true);
        }
      }

      ++lpcnt;
      delay(100);
    }
  }
}

void showEnteredTime()
{
  lcd.setCursor(5,1);
  lcd.print(currentTimeValue[0]);
  lcd.print(currentTimeValue[1]);
  lcd.print(":");
  lcd.print(currentTimeValue[2]);
  lcd.print(currentTimeValue[3]);
}

void lightStatus(bool state)
{
  if(state)
    digitalWrite(Blue_LED, HIGH);
  else
    digitalWrite(Blue_LED, LOW);    
}
 
void showCountdown()
{
  char timest[6];

  lcd.setCursor(0,0);
  lcd.print("** TIME LEFT: **");
  lcd.setCursor(0,1);
  lcd.print("**    ");
  sprintf(timest, "%d:%.2d",(timerSeconds/60),(timerSeconds - ((timerSeconds/60*60))));
  lcd.print(timest);
  lcd.print("    **");
}
void motorStatus(bool state)
{
stepper1.run();
stepper2.run();

 if(true){
  int pos1 = 3600;
  int pos2 = 3600;
  if (stepper1.distanceToGo() == 0)
   {
     pos1 = -pos1;
    stepper1.moveTo(-pos1);
  }
  if (stepper2.distanceToGo() == 0)
  {
    pos2 = -pos2;
    stepper2.moveTo(pos2);
  }
  stepper1.run();
  stepper2.run();
}
else{
  stepper1.stop();
  stepper2.stop();
}
}
void timeEntryScreen()
{
  clearScreen();
  lcd.setCursor(0,0);
  lcd.print("** ENTER TIME **");
}

void clearScreen()
{
  lcd.setCursor(0,0);
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("                ");
}

this is the countdown section:

    } else {
      if (lpcnt > 9) {
        lpcnt = 0;
        --timerSeconds;
        showCountdown();

        if (timerSeconds <= 0) {
          motorStatus(false);
          currentState = 1;
          lightStatus(false);
          timeEntryScreen();
          showEnteredTime();
        } else {
          motorStatus(true);
          lightStatus(true);
        }
      }

      ++lpcnt;
      delay(100);
    }

As you can see, you don't call your motor function while it's counting down, you just stop it when it reaches 0

Also I just noticed the delay(100); at the end, that's bad as it will impact how often you call run()

But doesn't the motorStatus function get called in the else statement right here?

        } else {
          motorStatus(true);
          lightStatus(true);

sorry missed it.
you do call it but only 10 times a second because that's how the code is architected.

you could change that by changing

      if (lpcnt > 9) {
        lpcnt = 0;

that's how they handle 10 loops = 1 second

--> using millis() would be more appropriate for non blocking code but you can try to count to 1000 instead of 10 and delay just for 1ms instead of 100.

By the way, just noticed: don't do if (true) {... in the code, the else will never be executed, you want to do if (state) {...

Okay got it. Thank you so much.

So for this part

      if(lpcnt > 9) {
        lpcnt = 0;

do I change the 9 to 1000 and then add a delay of 1ms after this part?

      }

      ++lpcnt;
    }

I would try

} else {
      if (lpcnt > 999) {
        lpcnt = 0;
        --timerSeconds;
        showCountdown();

        if (timerSeconds <= 0) {
          motorStatus(false);
          currentState = 1;
          lightStatus(false);
          timeEntryScreen();
          showEnteredTime();
        } else {
          motorStatus(true);
          lightStatus(true);
        }
      }

      ++lpcnt;
      delay(1); // 1ms
    }

Okay so I tried that and it seems that the motor is working, but the step seems to be extremely small because I can hear the motor turn every second but it isn't executing the full motor function that I have.

the code probably needs a rewrite to take into account real asynchronous handling of the countdown and your motor stuff...