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)withcurrentXtracking. - 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 */
Every call required me to specify:
- How far to move
- 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
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);
To this:
void moveToX(float targetX, int speedDelay);
No direction parameter. You give it a destination, it calculates everything.
Inside the function, two things happen automatically:
- Calculate the distance:
targetX - currentX - 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;
}
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);
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
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;
An alternative would be:
g_current_x += distance;
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
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? Doesg_current_xcorrectly show-10.0? - Change
MM_PER_REVto match your actual hardware (GT2 belt + 20-tooth pulley = 40.0mm). Does the motion scale correctly? - Add a
homeX()function that callsmoveToX(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)