DEV Community

Eric Park
Eric Park

Posted on

Step 3: Rotating a Stepper Motor by Exact Angle

Step 3: Rotating a Stepper Motor by Exact Angle

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

We are now at Step 3 of our controller series. If you missed the previous posts, here is a quick recap:

  • Step 1: We made the motor spin for the first time — 200 pulses, one full revolution
  • Step 2: We controlled the speed by changing the delay between pulses
  • Step 3 (today): We tell the motor to rotate by a specific angle — 90 degrees, 45 degrees, whatever we want

This is where things start to feel much more like a real machine.


The Goal

By the end of Step 2, I could control speed. But I still could not tell the motor "rotate exactly 90 degrees." I had to manually calculate how many steps to use, which felt clumsy.

Step 3 is about building a rotateAngle() function. I type in the angle I want, and the code figures out the steps automatically.


The Key Formula

A 1.8-degree stepper motor needs 200 steps to complete one full revolution.

Why 200? Because 360 degrees / 1.8 degrees per step = 200 steps.

So if I want to rotate a specific angle:

steps = (angle / 360.0) * 200
Enter fullscreen mode Exit fullscreen mode

Example:

  • 90 degrees → (90 / 360.0) × 200 = 50 steps
  • 45 degrees → (45 / 360.0) × 200 = 25 steps
  • 180 degrees → (180 / 360.0) × 200 = 100 steps

This formula is the entire idea behind Step 3. Everything else is just implementing it in C.


The Code

/*
 * Step 3: Rotate by exact angle
 *
 * Learning goals:
 * - Understand how angle converts to step count
 * - Implement math formulas in C code
 * - Use ENABLE pin to activate and deactivate the motor
 */

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

#define STEP_PIN      4    // wiringPi pin 4 = GPIO 23
#define DIR_PIN       3    // wiringPi pin 3 = GPIO 22
#define ENABLE_PIN    2    // wiringPi pin 2 = GPIO 27
#define STEPS_PER_REV 200  // 200 steps = 360 degrees (1.8 degree motor)

// Low-level: send N pulses to the motor
void rotateMotor(int steps, int delayMs) {
    for (int i = 0; i < steps; i++) {
        digitalWrite(STEP_PIN, HIGH);
        delay(delayMs);
        digitalWrite(STEP_PIN, LOW);
        delay(delayMs);
    }
}

// High-level: rotate by angle in degrees
void rotateAngle(float angle, int speedDelay) {
    // Convert angle to step count
    int steps = (int)((angle / 360.0) * STEPS_PER_REV);
    printf("%.1f degrees = %d steps\n", angle, steps);
    rotateMotor(steps, speedDelay);
}

int main(void) {
    // Initialize wiringPi using BCM GPIO numbers
    if (wiringPiSetupGpio() == -1) {
        printf("wiringPi init failed\n");
        return 1;
    }

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

    // Activate motor (LOW = enabled for most drivers)
    digitalWrite(ENABLE_PIN, LOW);
    printf("Motor enabled\n");
    delay(100);

    // Set direction (HIGH = clockwise in our setup)
    digitalWrite(DIR_PIN, HIGH);

    printf("Rotating 90 degrees\n");
    rotateAngle(90, 2);
    delay(1000);

    printf("Rotating 45 degrees\n");
    rotateAngle(45, 2);
    delay(1000);

    printf("Rotating 180 degrees\n");
    rotateAngle(180, 2);

    printf("Done!\n");

    // Deactivate motor
    digitalWrite(ENABLE_PIN, HIGH);

    return 0;
}

/*
 * Compile: gcc -o step3 step3_angle_control.c -lwiringPi
 * Run:     sudo ./step3
 */
Enter fullscreen mode Exit fullscreen mode

Walking Through the Code

STEPS_PER_REV

#define STEPS_PER_REV 200
Enter fullscreen mode Exit fullscreen mode

I defined this as a constant at the top. This makes the code easier to read and easier to change. If I ever use a 0.9-degree motor (400 steps/revolution), I only need to change this one line.

If your motor moves the wrong distance, this is the first value to check. Common values are 200 (1.8 degree) and 400 (0.9 degree).

Two separate functions

I split the work into two functions:

void rotateMotor(int steps, int delayMs)  // sends pulses
void rotateAngle(float angle, int speedDelay)  // converts angle, then calls rotateMotor
Enter fullscreen mode Exit fullscreen mode

rotateMotor only knows about pulses. It does not know anything about angles.
rotateAngle only knows about angles. It does the math and passes the result down.

This separation is a good habit. Each function does one thing. If something breaks, I know which layer to look at.

The conversion line

int steps = (int)((angle / 360.0) * STEPS_PER_REV);
Enter fullscreen mode Exit fullscreen mode

The (int) cast truncates the decimal. For example, 33.33 steps becomes 33. This is acceptable for most use cases. The small error is less than one step, which is less than 1.8 degrees.

If you need more precision, a microstepping driver can help — but that is a topic for a later step.

ENABLE pin

In Step 1, I added the ENABLE pin but did not explain it much. Here is the fuller picture.

Most stepper motor drivers have an enable (ENA) input:

  • LOW: motor is energized and holds its position
  • HIGH: motor is de-energized, shaft can spin freely

I set it to LOW at startup and HIGH at the end. During operation, the motor holds position. After the program ends, you can spin the shaft by hand without fighting the motor current.

wiringPiSetupGpio() vs wiringPiSetup()

In Step 1, I used wiringPiSetup(). This uses wiringPi's own pin numbering system. In Step 3, I switched to wiringPiSetupGpio(), which uses BCM GPIO numbers directly.

// wiringPiSetup()   → pin 4 = wiringPi pin 4
// wiringPiSetupGpio() → pin 4 = GPIO 4 (BCM)
Enter fullscreen mode Exit fullscreen mode

I made this change because BCM numbers are what most wiring diagrams and documentation use. It reduces confusion when checking your wiring.


What I Tested

After writing the code, I ran a few tests.

First I confirmed 90 degrees → 50 steps → motor turns a quarter of a revolution. That matched visually.

Then I tried the math edge case: rotateAngle(45, 2) gives 25 steps exactly. No rounding issue.

Then I tried rotateAngle(30, 2): (30 / 360.0) × 200 = 16.666... → (int) truncates to 16 steps → 16 × 1.8 = 28.8 degrees instead of 30.

This is a real limitation. The minimum addressable unit is 1.8 degrees (1 step), so any angle that does not divide evenly into the step size will have a small error. For my project at this stage, that is acceptable.


Practice Ideas

If you are following along, try these:

  • Change STEPS_PER_REV to 400 and see what happens
  • Try rotateAngle(360, 2) and confirm it is exactly one full revolution
  • Try rotateAngle(0, 2) — what does (int)(0 / 360.0 * 200) equal? Does it crash or just do nothing?
  • Change the (int) cast to (int)round(...) — does it improve accuracy for 30 degrees?

What's Next

Step 4 adds direction control. Right now the motor always turns clockwise. In Step 4, I will add the ability to specify a direction — and start tracking where the motor actually is.

That position tracking idea ends up being important later. When we get to Step 7 (acceleration profiles) and eventually G-code parsing, knowing the current position is essential.

See you next Thursday!


Eric Park, from Daegu, South Korea

Tags: raspberrypi c steppermotor 3dprinting beginners embeddedsystems

Top comments (0)