Last Updated: 25/11/2023

Arduino stepper motor control without delay()

Components/How To >> Arduino stepper motor control without delay()

Stepper motor without delay() or delayMicros()

Nema 17 Stepper Motor

Stepper motors are great for precision movement of items and in the model railway world turntables and traversers can make great use of the.

I have done previous tutorials on state machines and have even covered stepper motors with and without delay before but on a recent project I revisted this subject and felt that the resulting code was simpler as well as working very smoothly.

For those who don't understand why avoiding delay() or delayMicroseconds() is important, the reasin is that when either of these functions is called the Arduino is basically frozen. It cannot receive inputs from buttons or sensors in the normal way.

By writing non blocking codes using millis() of micros() we can keep perfect timing while doing other tasks at the same time. This is important in the model railway world as if I created a turntable, the Arduino could only control the turntable because while that turntable is running it cannot receive any other data. Using a state machine non blocking code the same Arduino can move the turntable while receiving inputs and driving servos or lights at the same time providing everything is written with non blocking code.

Although steppers will run at all sorts of speeds, they do perform best within certain ranges and this varies from stepper to stepper. To help with this example 5 takes the stepper through a range of speeds so you can see and hear what works best for your motor.

Example 1: Standard Stepper Control Code


Click to Download code: Nema17_Stepper_Test_v1.ino

The basic stepper motor example that is used. The code uses delayMicroseconds() to control the steps of the motor. This means the Aruino cannot process anything else

 

 
/*Example sketch to control a stepper motor with A4988 stepper motor driver and Arduino without a library. More info: https://www.makerguides.com */
// Define stepper motor connections and steps per revolution:
#define dirPin 9
#define stepPin 8
#define stepsPerRevolution 500
void setup() {
  // Declare pins as output:
  Serial.begin(9600);
    Serial.println("a4988 stepper test");
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
}
void loop() {
  // Set the spinning direction clockwise:
  Serial.println("clock");
  digitalWrite(dirPin, HIGH);
  // Spin the stepper motor 1 revolution slowly:
  for (int i = 0; i < stepsPerRevolution; i++) {
    // These four lines result in 1 step:
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(500);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(500);
  }
  
  delay(1000);
  // Set the spinning direction counterclockwise:
  digitalWrite(dirPin, LOW);
  Serial.println("anticlock");
  // Spin the stepper motor 1 revolution quickly:
  for (int i = 0; i < stepsPerRevolution; i++) {
    // These four lines result in 1 step:
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(500);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(500);
  }
  delay(1000);
  
}

Example 2: Timing using micros();


Click to Download code: stepperMicrosV1.ino

This sketch demonstrates the basic principles of non blocking code using micros() and millis() for timing.
This allows the stepper to move while the direction is changed at timed intervals rather than a set number of steps.

 

 
/* StepperMicrosV1
    21/11/23

    Controlling stepper motors using micros() nistead of delay()


*/

const int StepPin = 8;// Step pin
const int DirPin = 9;//  Direction pin

int stepDelay = 500;//step timing...adjust to make smooth/increase torque/speed
unsigned long stepMicros;//looks after timing of steps
boolean nextStep; //keeps track of stepPin

unsigned long currentMicros;//the current time in microseconds
unsigned long currentMillis;//the current time in milliseconds

unsigned long directionChangeTimer;// stores value so system knows when to change direction.
byte currentDirection; //keeps track of direction
int directionTime = 2000; //2 seconds between direction changes

void setup() {
  Serial.begin(115200);
  Serial.println("StepperMicrosV1");
  pinMode(StepPin, OUTPUT);
  pinMode(DirPin, OUTPUT);
}

void loop() {
  currentMicros = micros();//get the current micos at the beginning of the loop, it can then be used in multipe places as needed.
  currentMillis = millis();//get current millis() to use anywhere in system

  if (currentMicros - stepMicros >= stepDelay) {
    nextStep = !nextStep;//swap next step state
    digitalWrite(StepPin, nextStep);
    stepMicros = currentMicros;
  }
  if(currentMillis - directionChangeTimer >= directionTime){//is it time to change direction
    if(currentDirection > 0){
      currentDirection = 0;    
    }else{
      currentDirection = 1;  
    }
    digitalWrite(DirPin, currentDirection); // set direction
    directionChangeTimer = currentMillis;
  }
}

Example 3: Step counting while using micros()


Click to Download code: stepperMicrosStepCountV1.ino

This sketch demonstrates the basic principles of non blocking code using micros() and millis() for timing.
This allows the stepper to move while the direction is changed at timed intervals rather than a set number of steps.

 

 
/* stepperMicrosStepCountV1
    21/11/23

    Controlling stepper motors using micros() nistead of delay()
    This sketch changes the number of steps and direction of the stepper at timed intervals

*/

const int StepPin = 8;// Step pin
const int DirPin = 9;//  Direction pin

int stepDelay = 500;//step timing...adjust to make smooth/increase torque/speed
unsigned long stepMicros;//looks after timing of steps
boolean nextStep; //keeps track of stepPin

unsigned long currentMicros;//the current time in microseconds
unsigned long currentMillis;//the current time in milliseconds

unsigned long directionChangeTimer;// stores value so system knows when to change direction.
byte currentDirection; //keeps track of direction
int directionTime = 5000; //2 seconds between direction changes

int stepCount;//keeps track of steps
int stepTarget = 1000;//the number of steps to take.

void setup() {
  Serial.begin(115200);
  Serial.println("stepperMicrosStepCountV1");
  pinMode(StepPin, OUTPUT);
  pinMode(DirPin, OUTPUT);
}

void loop() {
  currentMicros = micros();//get the current micos at the beginning of the loop, it can then be used in multipe places as needed.
  currentMillis = millis();//get current millis() to use anywhere in system
  if (stepCount != stepTarget) {//check we haven't reached the target
    if (currentMicros - stepMicros >= stepDelay) {
      nextStep = !nextStep;//swap next step state
      digitalWrite(StepPin, nextStep);
      stepCount++;//increment the step counter
      stepMicros = currentMicros;
    }
  }




  if (currentMillis - directionChangeTimer >= directionTime) { //is it time to change direction
    Serial.println("dir change");
    if (currentDirection > 0) {
      currentDirection = 0;
    } else {
      currentDirection = 1;
      stepTarget = stepTarget + 1000;//set new target
      if(stepTarget > 10000){//start again from beginning
        stepTarget = 1000;
      }
      Serial.println("steptarget: " + String(stepTarget));
    }
    stepCount = 0;//rest the step counter
    digitalWrite(DirPin, currentDirection); // set direction
    directionChangeTimer = currentMillis;
  }
}

Example 4: Step counting while using micros() in functions


Click to Download code: stepperMicrosStepCountV2.ino

This is the same sketch as above but with the void loop() cleaned up and all the timing put into functions. .

 

 
/* stepperMicrosStepCountV2
    21/11/23
    Same as V1 but code moved into functions to make void loop() cleaner
    Controlling stepper motors using micros() nistead of delay()
    This sketch changes the number of steps and direction of the stepper at timed intervals

*/

const int StepPin = 8;// Step pin
const int DirPin = 9;//  Direction pin

int stepDelay = 500;//step timing...adjust to make smooth/increase torque/speed
unsigned long stepMicros;//looks after timing of steps
boolean nextStep; //keeps track of stepPin

unsigned long currentMicros;//the current time in microseconds
unsigned long currentMillis;//the current time in milliseconds

unsigned long directionChangeTimer;// stores value so system knows when to change direction.
byte currentDirection; //keeps track of direction
int directionTime = 5000; //2 seconds between direction changes

int stepCount;//keeps track of steps
int stepTarget = 1000;//the number of steps to take.

void stepControl() {
  if (stepCount != stepTarget) {//check we haven't reached the target
    if (currentMicros - stepMicros >= stepDelay) {
      nextStep = !nextStep;//swap next step state
      digitalWrite(StepPin, nextStep);
      stepCount++;//increment the step counter
      stepMicros = currentMicros;
    }
  }
}

void setStepsDir() {
  if (currentMillis - directionChangeTimer >= directionTime) { //is it time to change direction
    Serial.println("dir change");
    if (currentDirection > 0) {
      currentDirection = 0;
    } else {
      currentDirection = 1;
      stepTarget = stepTarget + 1000;//set new target
      if (stepTarget > 10000) { //start again from beginning
        stepTarget = 1000;
      }
      Serial.println("steptarget: " + String(stepTarget));
    }
    stepCount = 0;//rest the step counter
    digitalWrite(DirPin, currentDirection); // set direction
    directionChangeTimer = currentMillis;
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("stepperMicrosStepCountV2");
  pinMode(StepPin, OUTPUT);
  pinMode(DirPin, OUTPUT);
}

void loop() {
  currentMicros = micros();//get the current micos at the beginning of the loop, it can then be used in multipe places as needed.
  currentMillis = millis();//get current millis() to use anywhere in system

  stepControl();//Moves stepper if needed
  setStepsDir();//chnages direction, number of steps at times intervals
}

Example 5: Stepper tuning...varying the stepper speed using micros()


Click to Download code: stepperMicrosTuneV1.ino

Although stepper motors should run at any speed there are some speeds that are much smoother than others. This sketch takes the stepper through a range of speeds displaying the stepDelay in the Serial Monitor. It also shows how the speed/timing of the stepper can be changed allowing acceeleration/deceleration.

 

 
/* stepperMicrosTuneV1
    21/11/23
    This sketch not only changes direction but also stepper speed 
    to find the smoothest speed.
    Controlling stepper motors using micros() instead of delay()

    Motor will spin to 2 seconds before changing direction.

*/

const int StepPin = 8;// Step pin
const int DirPin = 9;//  Direction pin

int stepDelay = 10;//step timing...adjust to make smooth/increase torque/speed
unsigned long stepMicros;//looks after timing of steps
boolean nextStep; //keeps track of stepPin

unsigned long currentMicros;//the current time in microseconds
unsigned long currentMillis;//the current time in milliseconds

unsigned long directionChangeTimer;// stores value so system knows when to change direction.
byte currentDirection; //keeps track of direction
int directionTime = 2000; //2 seconds between direction changes

void setup() {
  Serial.begin(115200);
  Serial.println("StepperMicrosV1");
  pinMode(StepPin, OUTPUT);
  pinMode(DirPin, OUTPUT);
}

void loop() {
  currentMicros = micros();//get the current micos at the beginning of the loop, it can then be used in multipe places as needed.
  currentMillis = millis();//get current millis() to use anywhere in system

  if (currentMicros - stepMicros >= stepDelay) {
    nextStep = !nextStep;//swap next step state
    digitalWrite(StepPin, nextStep);
    stepMicros = currentMicros;
  }
  if(currentMillis - directionChangeTimer >= directionTime){//is it time to change direction
    if(currentDirection > 0){
      currentDirection = 0;    
    }else{
      currentDirection = 1;
      stepDelay = stepDelay + 30;
      if(stepDelay > 800){
        stepDelay = 10;    
      }
      Serial.println(stepDelay);
    }
    digitalWrite(DirPin, currentDirection); // set direction
    directionChangeTimer = currentMillis;
  }
}

Additional Resource Links

Arduino Tutorial: Avoiding the Overflow Issue When Using millis() and micros()

Comments


This site has been designed to be child friendly,please make sure that any comments are child friendly in language and style.
To add a comment or ask a question please email the address in this image: and use Arduino stepper motor control without delay() as a reference.