DEV Community

Eric Park
Eric Park

Posted on

Step 2: Controlling the Speed of a Stepper Motor

Step 2: Controlling the Speed of a Stepper Motor

Hello everyone. This is Eric Park, 3D Printer Engineer from South Korea.

Welcome back to the series. Quick recap from Step 1:

  • We set up the GPIO pins on the Raspberry Pi
  • We connected the A4988 driver to a NEMA17 motor
  • We sent 200 pulses to the motor and watched it complete one full revolution for the first time

That first spin felt surprisingly satisfying. But the motor was just running at one fixed speed. Step 2 is about changing that.


What Controls Speed?

In Step 1, the code looked something like this:

for (int i = 0; i < 200; i++) {
    digitalWrite(STEP_PIN, HIGH);
    delay(2);
    digitalWrite(STEP_PIN, LOW);
    delay(2);
}
Enter fullscreen mode Exit fullscreen mode

The delay(2) is a 2 millisecond pause after each pulse. The motor waits for that pause before receiving the next pulse.

So the relationship is simple:

shorter delay  =  less waiting  =  faster motor
longer delay   =  more waiting  =  slower motor
Enter fullscreen mode Exit fullscreen mode

That is the entire idea behind Step 2. We are not changing anything complicated — just the number we pass to delay().


Calculating Speed

Let me put a number to this.

Each full revolution requires 200 steps. If each step takes 2ms (HIGH) + 2ms (LOW) = 4ms total, then one revolution takes:

200 steps × 4ms = 800ms = 0.8 seconds per revolution
Enter fullscreen mode Exit fullscreen mode

If I change the delay to 1ms:

200 steps × 2ms = 400ms = 0.4 seconds per revolution
Enter fullscreen mode Exit fullscreen mode

Twice as fast. If I change the delay to 4ms:

200 steps × 8ms = 1600ms = 1.6 seconds per revolution
Enter fullscreen mode Exit fullscreen mode

Half as fast. The delay value directly controls how long each revolution takes.


A Practical Limit: Too Fast = Missed Steps

I was curious how fast I could push the motor, so I tried very small delays — 0.5ms, then 0.1ms.

The motor started making a grinding noise and vibrating in place instead of rotating. This is called step loss (or missing steps). The motor's internal magnets need a minimum amount of time to physically move between positions. If the pulses arrive faster than the motor can mechanically respond, it misses them.

For a typical NEMA17 at 12V with an A4988 driver, around 1ms delay is usually the practical minimum before step loss starts. The exact limit depends on the motor's current setting and the load it is carrying.

This was an interesting thing to discover just by experimenting. No special equipment needed — the sound alone tells you when the motor is being pushed too hard.


The Code

#include <wiringPi.h>
#include <stdio.h>

#define STEP_PIN    4
#define DIR_PIN     3
#define ENABLE_PIN  2

#define STEPS_PER_REV  200

/*
 * rotateMotor: spin the motor a given number of steps at a given speed
 *
 * steps    : how many steps to execute
 * delay_ms : delay between pulses in milliseconds
 *            smaller = faster, larger = slower
 */
void rotateMotor(int steps, int delay_ms) {
    for (int i = 0; i < steps; i++) {
        digitalWrite(STEP_PIN, HIGH);
        delay(delay_ms);
        digitalWrite(STEP_PIN, LOW);
        delay(delay_ms);
    }
}

int main(void) {

    if (wiringPiSetupGpio() == -1) {
        printf("wiringPi init failed\n");
        return 1;
    }

    pinMode(STEP_PIN,   OUTPUT);
    pinMode(DIR_PIN,    OUTPUT);
    pinMode(ENABLE_PIN, OUTPUT);

    /* Motor ON (ENABLE is active LOW on A4988) */
    digitalWrite(ENABLE_PIN, LOW);
    delay(100);

    printf("--- Step 2: Speed Control ---\n\n");

    /* Slow */
    printf("Slow (delay = 5ms)\n");
    rotateMotor(STEPS_PER_REV, 5);
    delay(1000);

    /* Medium */
    printf("Medium (delay = 2ms)\n");
    rotateMotor(STEPS_PER_REV, 2);
    delay(1000);

    /* Fast */
    printf("Fast (delay = 1ms)\n");
    rotateMotor(STEPS_PER_REV, 1);
    delay(1000);

    /* Motor OFF */
    digitalWrite(ENABLE_PIN, HIGH);

    printf("Done.\n");
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Walking Through the Code

The delay_ms parameter:

In Step 1, the delay was a hardcoded number inside the loop. Here I moved it into a function parameter. Now I can call rotateMotor(200, 5) for slow or rotateMotor(200, 1) for fast, without changing anything inside the function.

This is a small change, but it is an important habit. When a value is likely to change, give it a name or make it a parameter. Hardcoded numbers buried inside loops are difficult to find and adjust later.

#define STEPS_PER_REV 200:

Same idea. The number 200 comes from the motor spec (1.8 degree step angle → 360 / 1.8 = 200). If I ever switch to a different motor, I only need to update that one line.

The timing per step:

Each step takes delay_ms × 2 milliseconds total — one delay after HIGH, one after LOW. So delay_ms = 2 means 4ms per step, not 2ms. I made this mistake when I first calculated the revolution time and got a number that did not match what I measured with a stopwatch. Worth keeping in mind.


What I Noticed During Testing

Running the three speeds back to back — slow, medium, fast — makes the difference very obvious. The motor sound changes noticeably. At 5ms it sounds deliberate and measured. At 1ms it has a higher pitch and feels more tense.

I also tried going below 1ms by calling delay(0). The motor vibrated and did not complete the revolution at all. So there is a real physical floor here, not just a software one.

One more thing: between the three test runs, there is a delay(1000) pause. Without this, the transitions happen so fast that it is hard to tell where one speed ends and the next begins. Small details like this matter when you are trying to actually observe what the code is doing.


Practice Ideas

  • Change the three delay values and find the minimum before your motor starts losing steps.
  • Try calling rotateMotor(STEPS_PER_REV, 3) and time one revolution with a stopwatch. Does the calculated time (200 × 3ms × 2 = 1200ms) match what you measure?
  • Add a fourth test run between slow and medium — something like delay_ms = 3. Can you hear the difference?
  • Try STEPS_PER_REV / 2 steps (half a revolution) at different speeds. Does the motor stop at exactly 180 degrees?

What Is Next

Step 3 adds angle control. Right now I have to manually decide how many steps to use. In Step 3, I will build a function where I enter the angle I want — 90 degrees, 180 degrees, whatever — and the code calculates the steps automatically.

See you next time.


Eric Park, from Daegu, South Korea

Tags: raspberrypi c steppermotor 3dprinting beginners embeddedsystems

Top comments (0)