DEV Community

Eric Park
Eric Park

Posted on

Step 6: Moving to an Absolute Position — Let the Code Figure Out the Direction

Step 6: Moving to an Absolute Position — Let the Code Figure Out the Direction

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

Quick recap before we dive in:

  • Step 1: Made the motor spin for the first time. 200 pulses, one full revolution.
  • Step 2: Controlled speed by adjusting the delay between pulses.
  • Step 3: Built rotateAngle() — pass in degrees, the function calculates steps automatically.
  • Step 4: Added direction control and started tracking the current angle as a global variable.
  • Step 5: Switched from angles to millimeters. Introduced moveX(distance, direction) with currentX tracking.
  • Step 6 (today): Switch to absolute coordinates. You give a target position, the code decides direction automatically.

The Problem with Step 5

In Step 5, I wrote a function called moveX(). It looked like this:

moveX(5.0, true, 2);   /* move 5mm in the positive direction */
moveX(3.0, false, 2);  /* move 3mm in the negative direction */
Enter fullscreen mode Exit fullscreen mode

Every call required me to specify:

  1. How far to move
  2. Which direction

That is fine for manual jogging — "go 5mm left," "go 3mm right." But it is not how real motion control systems work.

A G-code command looks like this:

G1 X50.0
Enter fullscreen mode Exit fullscreen mode

It does not say "go 50mm to the right." It says "go to position X=50.0." If the current position is X=30, the controller figures out it needs to move 20mm in the positive direction. If the current position is X=70, it moves 20mm in the negative direction.

That is what Step 6 is about — absolute coordinates.


What Changes

The function signature changes from this:

void moveX(float distance, bool positive, int speedDelay);
Enter fullscreen mode Exit fullscreen mode

To this:

void moveToX(float targetX, int speedDelay);
Enter fullscreen mode Exit fullscreen mode

No direction parameter. You give it a destination, it calculates everything.

Inside the function, two things happen automatically:

  1. Calculate the distance: targetX - currentX
  2. Determine direction from the sign of that result

If the result is positive, move in the positive direction. If negative, move in the negative direction.


The Code

/*
 * step6_absolute.c
 *
 * Step 6: Absolute coordinate movement
 *
 * Machine assumptions:
 *   - One full revolution = 10mm of travel
 *   - 200 steps per revolution
 *   - Therefore: 20 steps per mm
 *
 * Compile: gcc -o step6 step6_absolute.c -lwiringPi -lm
 * Run:     sudo ./step6
 */

#include <wiringPi.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>     /* fabs() */

/* ── Pin definitions (BCM GPIO numbering) ── */
#define STEP_PIN    4
#define DIR_PIN     3
#define ENABLE_PIN  2

/* ── Machine configuration ── */
#define STEPS_PER_REV  200      /* 360 / 1.8 degrees = 200 steps */
#define MM_PER_REV     10.0f   /* depends on your belt/screw setup */
#define STEPS_PER_MM   (STEPS_PER_REV / MM_PER_REV)  /* 20.0 */

/* ── Current position (global) ── */
/*
 * This variable tracks where the motor is right now.
 * It starts at 0.0 (we assume power-on = origin).
 * Every moveToX() call updates it when the move completes.
 */
float g_current_x = 0.0f;

/* ─────────────────────────────────────────────
 * rotateMotor: send step pulses to the driver
 *
 * steps    : how many pulses to send
 * delay_ms : delay between each pulse (controls speed)
 * ───────────────────────────────────────────── */
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);
    }
}

/* ─────────────────────────────────────────────
 * moveToX: move to an absolute X position
 *
 * targetX   : destination in mm (absolute)
 * delay_ms  : speed control (smaller = faster)
 *
 * Direction is calculated automatically.
 * ───────────────────────────────────────────── */
void moveToX(float targetX, int delay_ms) {

    /* Step 1: calculate how far we need to travel */
    /*
     * distance = targetX - g_current_x
     *
     * Examples:
     *   current=0.0,  target=30.0  → distance = +30.0 (move positive)
     *   current=30.0, target=10.0  → distance = -20.0 (move negative)
     *   current=10.0, target=10.0  → distance =   0.0 (already there)
     */
    float distance = targetX - g_current_x;

    /* Step 2: if distance is near zero, skip the move */
    /*
     * fabs() returns the absolute value of a float.
     * We use 0.01mm as the threshold — anything smaller
     * is less than 1 step at 20 steps/mm, so there is
     * nothing meaningful to do.
     */
    if (fabs(distance) < 0.01f) {
        printf("Already at target position: X = %.2fmm\n\n", g_current_x);
        return;
    }

    /* Step 3: determine direction from the sign of distance */
    /*
     * distance > 0  →  positive direction (DIR pin HIGH)
     * distance < 0  →  negative direction (DIR pin LOW)
     *
     * We also need the absolute value for the step count.
     * fabs() gives us that without an if-else.
     */
    bool positive       = (distance > 0.0f);
    float abs_distance  = fabs(distance);

    printf("X%.2f -> X%.2f: moving %.2fmm in %s direction\n",
           g_current_x, targetX, abs_distance,
           positive ? "+" : "-");

    /* Set the direction pin before sending pulses */
    digitalWrite(DIR_PIN, positive ? HIGH : LOW);

    /* Step 4: convert mm to steps */
    /*
     * steps = abs_distance × STEPS_PER_MM
     *
     * Example: 15.0mm × 20.0 steps/mm = 300 steps
     *
     * (int) cast truncates the decimal.
     * At 20 steps/mm, the minimum resolution is 0.05mm.
     * Any fractional mm below that is lost here.
     * This is the same rounding limitation we saw in Step 3.
     */
    int steps = (int)(abs_distance * STEPS_PER_MM);
    printf("  -> %d steps\n", steps);

    /* Step 5: run the motor */
    rotateMotor(steps, delay_ms);

    /* Step 6: update the position tracker */
    /*
     * We set g_current_x to targetX (not += distance).
     * Using targetX directly avoids floating point
     * accumulation errors from repeated additions.
     */
    g_current_x = targetX;
    printf("  -> current position: X = %.2fmm\n\n", g_current_x);
}

/* ─────────────────────────────────────────────
 * main: test the absolute coordinate system
 * ───────────────────────────────────────────── */
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);

    /* Enable the motor driver (active LOW on most drivers) */
    digitalWrite(ENABLE_PIN, LOW);
    delay(100);

    printf("=== Absolute Coordinate Movement Test ===\n");
    printf("Config: %.1fmm/rev, %.1f steps/mm\n\n",
           MM_PER_REV, STEPS_PER_MM);
    printf("Start: X = %.2fmm\n\n", g_current_x);

    /* Test sequence */
    moveToX(10.0f, 2);   /* X0   -> X10  (positive direction) */
    moveToX(25.0f, 2);   /* X10  -> X25  (positive direction) */
    moveToX(5.0f,  2);   /* X25  -> X5   (negative direction, auto!) */
    moveToX(15.5f, 2);   /* X5   -> X15.5 */
    moveToX(15.5f, 2);   /* X15.5 -> X15.5 (already there) */
    moveToX(0.0f,  2);   /* X15.5 -> X0  (back to origin) */

    printf("=== Final result ===\n");
    printf("Final position: X = %.2fmm\n", g_current_x);

    /* Disable the motor driver */
    digitalWrite(ENABLE_PIN, HIGH);

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Walking Through the Key Part

The function moveToX() does its most important work in three lines:

float distance = targetX - g_current_x;
bool positive  = (distance > 0.0f);
float abs_distance = fabs(distance);
Enter fullscreen mode Exit fullscreen mode

Let me trace through a concrete example. Suppose g_current_x = 25.0 and we call moveToX(5.0, 2).

distance    = 5.0 - 25.0 = -20.0
positive    = (-20.0 > 0.0) = false   → DIR pin goes LOW
abs_distance = fabs(-20.0) = 20.0
steps       = (int)(20.0 * 20.0) = 400
Enter fullscreen mode Exit fullscreen mode

The direction is set automatically. I never had to think about it — I just said "go to X=5.0."


One Detail Worth Explaining: Why g_current_x = targetX Instead of += distance

At the end of the function, I write:

g_current_x = targetX;
Enter fullscreen mode Exit fullscreen mode

An alternative would be:

g_current_x += distance;
Enter fullscreen mode Exit fullscreen mode

Both give the same answer mathematically. But with floating point numbers, repeated additions accumulate small rounding errors. If you add and subtract many times, the result drifts slightly from the true value.

Assigning targetX directly avoids that drift. Each move lands exactly on the intended coordinate.


What the Output Looks Like

=== Absolute Coordinate Movement Test ===
Config: 10.0mm/rev, 20.0 steps/mm

Start: X = 0.00mm

X0.00 -> X10.00: moving 10.00mm in + direction
  -> 200 steps
  -> current position: X = 10.00mm

X10.00 -> X25.00: moving 15.00mm in + direction
  -> 300 steps
  -> current position: X = 25.00mm

X25.00 -> X5.00: moving 20.00mm in - direction
  -> 400 steps
  -> current position: X = 5.00mm

X5.00 -> X15.50: moving 10.50mm in + direction
  -> 210 steps
  -> current position: X = 15.50mm

Already at target position: X = 15.50mm

X15.50 -> X0.00: moving 15.50mm in - direction
  -> 310 steps
  -> current position: X = 0.00mm

=== Final result ===
Final position: X = 0.00mm
Enter fullscreen mode Exit fullscreen mode

The third move (X25 → X5) goes in the negative direction automatically. No direction argument, no manual calculation.


Honest Limitation: The Position Tracker Has No Memory

g_current_x only exists in RAM. If the motor skips a step under load — which stepper motors do occasionally — the tracker has no way to know. It still believes the motor is exactly where the math says it should be.

Real systems solve this with:

  • An encoder on the motor shaft for feedback
  • A homing sequence at startup to establish a known reference point
  • Limit switches to detect the physical endpoints

For now, we are assuming the motor never misses a step. That is fine for learning, but it is worth understanding the assumption.


Practice Ideas

  • Call moveToX(0.0f, 2) twice in a row. Does the second call print "already at target position"?
  • Try moveToX(-10.0f, 2). Can the motor go to a negative coordinate? Does g_current_x correctly show -10.0?
  • Change MM_PER_REV to match your actual hardware (GT2 belt + 20-tooth pulley = 40.0mm). Does the motion scale correctly?
  • Add a homeX() function that calls moveToX(0.0f, 2). This is the beginning of a real homing function.

What Is Next

Step 7 will add acceleration and deceleration to moveToX(). Right now the motor starts and stops at full speed, which causes vibration and can lose steps at high speeds.

With a proper velocity profile — slow start, ramp up, ramp down before stopping — the motion becomes smoother and more reliable. That is also the foundation for understanding how Klipper and Marlin handle motion planning.

See you next week.


Eric Park, from Daegu, South Korea

Tags: raspberrypi c steppermotor 3dprinting beginners embeddedsystems

Top comments (0)