How-To-Tutorials · September 22, 2025

How to Implement a PID Controller for NEMA 17 Stepper Motor with Arduino

PID Control for Smooth NEMA 17 Stepper Motor Acceleration with Arduino and AccelStepper

Stepper motors are open-loop by nature — they go where you tell them without checking if they actually got there. That works fine at low speeds, but push a NEMA 17 too hard and it'll skip steps, stall, or vibrate like crazy. A PID controller closes that loop. It continuously adjusts motor speed based on the difference between where the motor is and where it should be, giving you buttery-smooth acceleration profiles.

We'll combine Arduino's PID library with AccelStepper to build a position-tracking control system. The PID output drives the target position dynamically, so the motor ramps up and settles without overshoot or jerky motion.

Prerequisites

  • Comfortable writing Arduino sketches (variables, functions, loops)
  • Basic understanding of PID control — what proportional, integral, and derivative terms do
  • Arduino IDE 2.x installed
  • Basic wiring and breadboard skills

Parts/Tools

  • NEMA 17 stepper motor (1.8° per step typical)
  • Arduino Uno (or any AVR-based Arduino board)
  • Stepper motor driver — A4988 or DRV8825 (the DRV8825 handles higher current and has 32 microstep resolution)
  • Power supply matched to your motor (typically 12V, 1-2A for NEMA 17)
  • AccelStepper library
  • PID_v1 library (Brett Beauregard's classic)
  • Jumper wires and breadboard

Steps

  1. Wiring the Hardware
    1. Connect the NEMA 17's four wires to the stepper driver's motor outputs (A1, A2, B1, B2). Check the motor datasheet for coil pairings — getting this wrong won't damage anything, but the motor will just vibrate in place instead of spinning.
    2. Wire the driver to the Arduino:
      • STEP pin on the driver to a digital pin on the Arduino (pin 3 in our example).
      • DIR pin on the driver to another digital pin (pin 4).
      • ENABLE pin — you can leave this disconnected for now. Pulling it LOW enables the driver (it's active-low on most boards).
    3. Connect your power supply to the driver's VMOT and GND terminals. Watch out: never connect or disconnect motor wires while the driver is powered — you can fry the driver's H-bridge instantly.
    4. Set the current limit on your driver with the onboard potentiometer before powering up. For an A4988, measure the voltage on the Vref pin and use the formula: Current = Vref / (8 × Rsense). Skipping this step risks overheating your motor or underdriving it.
  2. Installing Libraries
    1. Open Arduino IDE 2.x.
    2. Go to Sketch > Include Library > Manage Libraries.
    3. Search for "AccelStepper" by Mike McCauley and install it.
    4. Search for "PID" and install the "PID" library by Brett Beauregard (PID_v1). There are several PID libraries — make sure you grab the right one.
  3. Writing the Arduino Code

    Start a new sketch and pull in both libraries:

    #include <AccelStepper.h>
    #include <PID_v1.h>

    Define your pins and set up the stepper and PID objects:

    const int stepPin = 3;
    const int dirPin = 4;
    
    AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);
    
    // PID variables — all must be doubles for the PID library
    double setpoint, input, output;
    double Kp = 2, Ki = 5, Kd = 1;
    PID myPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);

    In setup(), configure the motor limits and kick the PID into automatic mode:

    void setup() {
        Serial.begin(115200); // Useful for debugging PID response
        stepper.setMaxSpeed(1000);    // steps/sec
        stepper.setAcceleration(500); // steps/sec^2
        setpoint = 1000; // Target position in steps
        myPID.SetMode(AUTOMATIC);
        myPID.SetOutputLimits(-1000, 1000); // Match your max speed range
    }

    The loop() reads the current position, feeds it through PID, and drives the motor toward the computed target:

    void loop() {
        input = stepper.currentPosition();
        myPID.Compute();
        stepper.moveTo((long)output);
        stepper.run();
    }

    A practical tip: print input, output, and setpoint over Serial so you can plot them in Arduino IDE 2.x's Serial Plotter. Visualizing the PID response makes tuning dramatically easier than guessing.

  4. Tuning the PID Parameters
    1. Upload the sketch and open the Serial Plotter.
    2. Start with only Kp (set Ki and Kd to 0). Increase Kp until the motor responds promptly to setpoint changes. If it oscillates around the target, you've gone too high.
    3. Add Ki gradually. This eliminates steady-state error — the gap where the motor sits a few steps away from the target and never quite reaches it. Too much Ki causes slow, looping oscillations.
    4. Finally, add a small amount of Kd to dampen overshoot. Kd reacts to the rate of change, so it applies braking as the motor approaches the target. Start small — excessive Kd amplifies noise and can make the motor jittery.
    5. There's no universal set of PID values. Your optimal numbers depend on motor inertia, load, driver microstepping setting, and mechanical friction. Expect to spend some time here.

Troubleshooting

  • Motor doesn't move at all: Verify your STEP and DIR pin wiring. Check that the driver has power on VMOT (not just logic power). Confirm the ENABLE pin isn't being held HIGH.
  • Motor vibrates but won't spin: Your coil wiring is likely swapped. Swap one pair of motor wires on the driver side.
  • Erratic or jerky movement: Your PID gains are off. Zero out Ki and Kd, tune Kp alone first, then layer in the other terms one at a time.
  • Driver overheating: The current limit is set too high, or your motor is stalling under load. Reduce the Vref voltage on the driver's trim pot and check that the motor can actually handle the speed you're asking for.
  • Arduino not responding: Double-check that you've selected the right board and COM port in Arduino IDE 2.x. Also confirm the baud rate in Serial Monitor matches your Serial.begin() value.

Where to Go from Here

You've now got a closed-loop PID controller smoothing out your NEMA 17's acceleration profile. This same pattern scales to more complex setups — add an encoder for real position feedback instead of relying on AccelStepper's internal step count, or extend the system to control speed rather than position. For multi-axis projects (CNC, 3D printers, camera sliders), each axis gets its own PID instance with independent tuning. The fundamentals stay the same.