Stepper Motor: inadvertant interuptions and delays

Hey guys, I am not new to Arduino but far from experienced. Please help me figure this project out. I need it for my job.

So, I have a stepper motor that I want to spin at a constant rate for 128 revolutions (I got this part) and then I want to have an OLED screen show me the progress.

The problem is that updating the screen takes time (maybe less than a microsecond) that interrupts the spinning inbetween revolutions.

How can I spin the motor constantly and also update the screen. Take note of the supposedly required for loops.

the two lines (fillRect and display) are the problem. If I comment them out, the motor runs smoothly but then I don’t get a progress par.

if (button1State == HIGH) {
int revs = 0;
display.clearDisplay(); //Prime the top of the screen with constant text
display.setCursor(10, 0);
display.setTextSize(1.5);
display.println(“Cycles Completed:”);
display.display();

for (int n = 0; n < 128; n++) { //n is the number of revolutions
for (int i = 0; i < 400; i++) { //step forward 400 steps (one revolution)
digitalWrite(stepPin, HIGH); //pulse the motor high then low 400 times to achieve one spin
delay(2);
digitalWrite(stepPin, LOW);
delay(rpm * 2);
}
** display.fillRect(n, 15, 1, 38, 1); /progress par fills one sliver after each revolution**
** display.display();**
}

display.fillRect(30, 20, 68, 28, 0); // remove a rectangle from the progress bar
display.setCursor(37, 23);
display.setTextSize(3);
display.println(“128”); //display the number of cycles completed at the end
display.display();
delay(500000);
}

To make a program that is responsive and runs smoothly (non-blocking), do not use delay(). Investigate using millis() (and micros()) for timing. See the beginner's guide to millis() and several things at a time.

Also avoid for (and while) loops unless they run very quickly. The AccelStepper library may help there as the steppers run, kind of, in the background.

Don't update the display every time through loop. No human can see changes that fast. 200 to 500 milliseconds between updates should be fast enough.

The problem is that updating the screen takes time (maybe less than a microsecond)

You will find that updating the display takes far longer than a microsecond.

Find out how long the display takes to update using micros().

If it's less than rpm*2, on the four hundredth iteration of your inner loop, do the display function instead of the delay. You may need a smaller delay afterwards to keep the stepping at the same rate.

The second example in this Simple Stepper Code illsutrates how to use millis() and micros() rather than delay()

Plus what is in Reply #1

...R Stepper Motor Basics

WildBill, that is a great idea, I will try that now

so I made another script to find out how long it took each time

void loop() { timer = micros(); display.fillRect(1, 15, 1, 38, 1); //progress par fills one sliver after each revolution display.display(); Serial.println(timer); }

after averaging the differences between recordings I came up with 37136.26667 microseconds or 37 milliseconds.

unfortunately I am already using a 2 millisecond delay in between the high and low pulses so I'm not sure what to do about that

Any other ideas?

I know that delay is a blocking function, but the delay(_) is not the problem in my script. The stepper works best with delay(2) in between HIGH to LOW pulses.

I need help speeding up or otherwise removing the delay resulting from updating the screen. (I think)

FYI, right now my stepper rotates at about 5rpms

I am already using a 2 microsecond delay in between the high and low pulses

I would not worry too much about that short of a delay(), but 2 milliseconds would be a different stoty.

oops, I fixed that, I mean millisecond, 2 millisecond, which means 37 milliseconds is too long to use as the delay in between the high/low pulses

Which library & display are you using?

Which function is costing you the time? I'll guess display().

The high part of the pulse only needs to be a few microseconds (consult the stepper driver data sheet). The duration of the low part of the pulse is used to determine speed. See Robin2's simple stepper code for an example. He uses 20 microsecons for the high part (A4988 or DRV8825 drivers). On those drivers the step is executed on the low to high transition.

I am using an Adafruit OLED display and a nema 17 with a A4988 driver and the Adafruit GFX library

yes, the display is taking 36 or so milliseconds

here is my code

if (button1State == HIGH) {
display.clearDisplay(); //Prime the top of the screen with constant text
display.setCursor(10, 0);
display.setTextSize(1.5);
display.println(“Cycles Completed:”);
display.display();

for (int n = 0; n < 128; n++) { //n is the number of revolutions
for (int i = 0; i < 400; i++) { //step forward 400 steps (one revolution)
digitalWrite(stepPin, HIGH); //pulse the motor high then low 400 times to achieve one spin
if (i == 399) {
display.fillRect(n, 15, 1, 38, 1); //progress par fills one sliver after each revolution
display.display(); // these two lines take bout 37 millisenconds
}
else {
delay(37);
digitalWrite(stepPin, LOW);
delay(rpm * 2);
}
}
}

display.fillRect(30, 20, 68, 27, 0); // remove a rectangle from the progress bar
display.setCursor(37, 23);
display.setTextSize(3);
display.println(“128”); //display the number of cycles completed at the end
display.display();
delay(500000);
}

During the 37 millisecond delay, nothing else can happen. If you do the timing with millis() you can update the display (and do other things) in between steps.

When posting code, please post the whole program and post in code tags as described in the how to use this forum-please read sticky. Using auto format (ctrl-t or Tools, Auto Format) will make your code easier to follow as will putting each { and } on their own lines.

Like so:

for (int n = 0; n < 128; n++)   //n is the number of revolutions
   {
      for (int i = 0; i < 400; i++)      //step forward 400 steps (one revolution)
      {
         digitalWrite(stepPin, HIGH);    //pulse the motor high then low 400 times to achieve one spin
         if (i == 399)
         {
            display.fillRect(n, 15, 1, 38, 1);    //progress par fills one sliver after each revolution
            display.display();                  // these two lines take bout 37 millisenconds
         }
         else
         {
            delay(37);
            digitalWrite(stepPin, LOW);
            delay(rpm * 2);
         }
      }
   }

HunterHunter: oops, I fixed that, I mean millisecond, 2 millisecond, which means 37 milliseconds is too long to use as the delay

You may need periods of 2 and 37 millisecs but there is no need to use the delay() function to implement them. An Arduino can do a lot of other stuff during an interval of 2 millisecs.

...R

I will upload my whole code here, note that some of the variables used for RPM have yet to be calibrated.

Can one of you explain how I would implement millis() to have the screen update while the stepper is turning? I have read a lot on millis() and still do not understand how to do this.

Entire code:

const int stepPin = 10;   //pin to pulse for steps
const int button1Pin = 4;  // the pin number of button 1
const int button2Pin = 7;  // the pin number of button 2
int button1State = LOW;    // variable for reading the pushbutton status
int rpm = 1;
int timeout = 70;
long int lastDebounceTime;


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  //setup pins
  pinMode(stepPin, OUTPUT);
  pinMode(button1Pin, INPUT);
  pinMode(button2Pin, INPUT);

  Serial.begin(9600);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.display();
  //  delay(2000);
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(1);
  display.setCursor(15, 20);
  display.println("Ready to   Riffle");
  display.display();
  delay(1000);
}

void loop() {             //debounce the button
  button1State = digitalRead(button1Pin);  // read the state of the pushbutton value:
  int button2State = digitalRead(button2Pin);
  long int currentTime = millis();    //get the current time
  if (button2State == LOW) {
    lastDebounceTime = currentTime;
  }
  if (((currentTime - lastDebounceTime) > timeout)) {
    rpm = rpm + 1;
    if (rpm == 4) {
      rpm = 1;
    }
  }
  Serial.println(rpm);

  switch (rpm) {
    case 3:           // menu for the low RPM setting *needs calibration*
      display.clearDisplay();
      display.setCursor(0, 0);
      display.setTextSize(2);
      display.println("RPM set:");
      display.display();
      display.setTextSize(3);
      display.setCursor(0, 20);
      display.println("Low");
      display.display();
      delay(750);
      break;
    case 2:     // menu for the  medium setting *needs calibration*
      display.clearDisplay();
      display.setCursor(0, 0);
      display.setTextSize(2);
      display.println("RPM set:");
      display.display();
      display.setTextSize(3);
      display.setCursor(0, 20);
      display.println("Medium");
      display.display();
      delay(750);
      break;
    case 1:     // menu for the high RPM setting *needs calibration*
      display.clearDisplay();
      display.setCursor(0, 0);
      display.setTextSize(2);
      display.println("RPM set:");
      display.display();
      display.setTextSize(3);
      display.setCursor(0, 20);
      display.println("High");
      display.display();
      delay(750);
      break;
  }

  if (button1State == HIGH) {
    display.clearDisplay(); //Prime the top of the screen with constant text
    display.setCursor(10, 0);
    display.setTextSize(1.5);
    display.println("Cycles Completed:");
    display.display();

    for (int n = 0; n < 128; n++) { //n is the number of revolutions
      for (int i = 0; i < 400; i++) {    //step forward 400 steps (one revolution)
        digitalWrite(stepPin, HIGH);    //pulse the motor high then low 400 times to achieve one spin
        delay(rpm * 2);
        digitalWrite(stepPin, LOW);
        if (i == 399) {
          display.fillRect(n, 15, 1, 38, 1);    //progress par fills one sliver after each revolution
          display.display();                  // these two lines take bout 37 millisenconds
        }
        else {
          delay(37);
        }
      }
    }

//    The following code allows the stepper to turn smoothly at over 10 RPMs and allows for the variable rpms to work
//    for (int n = 0; n < 128; n++) { //n is the number of revolutions
//      for (int i = 0; i < 400; i++) {    //step forward 400 steps (one revolution)
//        digitalWrite(stepPin, HIGH);    //pulse the motor high then low 400 times to achieve one spin
//        delay(2)
//        digitalWrite(stepPin, LOW);
//        delay(rpm * 2);


        display.fillRect(30, 20, 68, 27, 0); // remove a rectangle from the progress bar
        display.setCursor(37, 23);
        display.setTextSize(3);
        display.println("128");   //display the number of cycles completed at the end
        display.display();
        delay(500000);
      }

    }

and the relevant part of the code:

  if (button1State == HIGH) {
    display.clearDisplay(); //Prime the top of the screen with constant text
    display.setCursor(10, 0);
    display.setTextSize(1.5);
    display.println("Cycles Completed:");
    display.display();

    for (int n = 0; n < 128; n++) { //n is the number of revolutions
      for (int i = 0; i < 400; i++) {    //step forward 400 steps (one revolution)
        digitalWrite(stepPin, HIGH);    //pulse the motor high then low 400 times to achieve one spin
        delay(rpm * 2);
        digitalWrite(stepPin, LOW);
        if (i == 399) {
          display.fillRect(n, 15, 1, 38, 1);    //progress par fills one sliver after each revolution
          display.display();                  // these two lines take bout 37 millisenconds
        }
        else {
          delay(37);
        }
      }
    }

//    The following code allows the stepper to turn smoothly at over 10 RPMs and allows for the variable rpms to work
//    for (int n = 0; n < 128; n++) { //n is the number of revolutions
//      for (int i = 0; i < 400; i++) {    //step forward 400 steps (one revolution)
//        digitalWrite(stepPin, HIGH);    //pulse the motor high then low 400 times to achieve one spin
//        delay(2)
//        digitalWrite(stepPin, LOW);
//        delay(rpm * 2);


        display.fillRect(30, 20, 68, 27, 0); // remove a rectangle from the progress bar
        display.setCursor(37, 23);
        display.setTextSize(3);
        display.println("128");   //display the number of cycles completed at the end
        display.display();
        delay(500000);
      }

It looks like the Adafruit library mirrors the screen's data in Arduino RAM. When you call a drawing function, the effect is only local. Nothing changes on the actual display until you call the display() method, which pushes the entire local buffer to the physical OLED device.

And that takes ~37mS.

So a simple solution using millis is not going to cut it.

There may be a way to command the OLED more directly, but without the manual I can't say. There may be a way to send only a subset of the buffer data, but how to do that is unknown too.

If I were trying to solve this, I think I'd tweak the library and pass a pointer to a callback function to display() and have it called periodically during the 36mS data transfer. The callback could check millis and step the motor when necessary. It might work, or it might screw up the timing of the wire transfer. Only experiment would tell.

The optimal answer is a better display library.

However if you must work with the Adafruit library I wonder if using a Hardware Timer to generate an interrupt at the necessary step rate would get around the problem.

Of course it is entirely possible that the Adafruit library is already using other interrupts for its purposes which would just result in a clash of interrupts with the result that everything would work worse.

...R