Step 5: Moving in Millimeters — Position Tracking with Real Units
Hello everyone. This is Eric Park, 3D Printer Engineer from South Korea.
We are now at Step 5. Here is a quick recap of where we have been:
- Step 1: Sent 200 pulses, made the motor complete one full revolution
- Step 2: Controlled speed by adjusting the delay between pulses
- Step 3: Rotated the motor by a specific angle using a formula
- Step 4: Added direction control and started tracking angle as a global variable
- Step 5 (today): Forget angles. We move in millimeters.
This step felt like a real turning point for me. For the first time, the motor started speaking the same language as a real machine.
Why Millimeters?
In Steps 3 and 4, I was controlling the motor by angle — 90 degrees, 180 degrees, and so on. That works fine for a rotating shaft.
But a 3D printer or CNC machine does not think in degrees. It thinks in millimeters. When Klipper receives G1 X50, it moves the X axis to 50mm. Not 1000 degrees, not 4000 steps — just 50mm.
So the question is: how do we convert mm to motor steps?
The Key Formula
A stepper motor has a mechanical chain between it and the actual moving part. In my test setup, I am assuming a GT2 timing belt with a 20-tooth pulley. Here is how the math works:
Pulley teeth × belt pitch = distance per revolution
20 teeth × 2mm = 40mm per revolution
Wait — actually in my setup I am using a simpler approximation:
1 revolution = 10mm movement
200 steps = 10mm
1mm = 200 / 10 = 20 steps
So the conversion is:
steps = distance_mm × steps_per_mm
steps = distance_mm × 20
Example:
- Move 5mm → 5 × 20 = 100 steps
- Move 3mm → 3 × 20 = 60 steps
- Move 0.1mm → 0.1 × 20 = 2 steps
This is defined once at the top of the code, and everything else uses it automatically.
What Changed From Step 4
In Step 4, the function looked like this:
rotateAngle(90, clockwise, 2); // rotate 90 degrees, clockwise, delay=2
In Step 5, it looks like this:
moveX(5.0, true, 2); // move 5.0mm in positive direction, delay=2
The interface is now talking about physical distance, not motor rotation. That feels much more natural when you are thinking about building a machine.
The Code
/*
* step5_position_tracking.c
* Step 5: Coordinate-based position tracking (mm units)
*
* Compile: gcc -o step5 step5_position_tracking.c -lwiringPi -lm
* Run : sudo ./step5
*/
#include <wiringPi.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
/* ── Pin definitions (BCM GPIO numbers) ── */
#define STEP_PIN 4
#define DIR_PIN 3
#define ENABLE_PIN 2
/* ── Motor and mechanical settings ── */
#define STEPS_PER_REV 200 /* 360 / 1.8 = 200 steps per revolution */
#define MM_PER_REV 10.0f /* 1 revolution moves 10mm (adjust for your machine) */
#define STEPS_PER_MM (STEPS_PER_REV / MM_PER_REV) /* 20 steps/mm */
/* ── Current position tracking ── */
float currentX = 0.0f; /* unit: mm */
/* Low-level pulse generation */
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);
}
}
/*
* moveX: move by a relative distance in mm
*
* distance_mm : how far to move (always positive)
* positive : true = forward (+X), false = backward (-X)
* speedDelay : delay between pulses (ms)
*/
void moveX(float distance_mm, bool positive, int speedDelay) {
/* Set direction */
if (positive) {
digitalWrite(DIR_PIN, HIGH);
printf("X+ direction: +%.2fmm\n", distance_mm);
} else {
digitalWrite(DIR_PIN, LOW);
printf("X- direction: -%.2fmm\n", distance_mm);
}
/* Convert mm to steps */
int steps = (int)(distance_mm * STEPS_PER_MM);
printf(" -> %d steps\n", steps);
/* Execute */
rotateMotor(steps, speedDelay);
/* Update position */
if (positive) {
currentX += distance_mm;
} else {
currentX -= distance_mm;
}
printf(" -> current position: X = %.2fmm\n\n", currentX);
}
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);
digitalWrite(ENABLE_PIN, LOW);
printf("Motor enabled\n\n");
printf("=== Position tracking test (mm units) ===\n");
printf("Settings: 1 rev = %.1fmm, 1mm = %.0f steps\n\n",
MM_PER_REV, STEPS_PER_MM);
printf("Start: X = %.2fmm\n\n", currentX);
moveX(5.0f, true, 2); /* +5mm */
moveX(3.0f, true, 2); /* +3mm */
moveX(2.0f, false, 2); /* -2mm */
moveX(1.5f, true, 2); /* +1.5mm */
printf("=== Result ===\n");
printf("Final position: X = %.2fmm\n", currentX);
printf("Expected : 5 + 3 - 2 + 1.5 = 7.5mm\n");
digitalWrite(ENABLE_PIN, HIGH);
return 0;
}
Walking Through the Code
STEPS_PER_MM macro:
#define STEPS_PER_MM (STEPS_PER_REV / MM_PER_REV)
This calculates itself from the two values above it. If I change MM_PER_REV to match my actual machine, everything else updates automatically. That is the benefit of defining constants at the top — I only need to change one number.
currentX global variable:
float currentX = 0.0f;
This keeps track of where the motor is right now. Every time moveX() runs, it adds or subtracts from this value. This is the same idea as Step 4's currentAngle, but now in mm.
The conversion inside moveX():
int steps = (int)(distance_mm * STEPS_PER_MM);
This is the only place the formula appears. The rest of the code just calls moveX() with millimeters. Clean separation between "what I want" and "how to achieve it."
What I Discovered During Testing
When I tested moveX(0.1f, true, 2), the motor moved only 2 steps. That is the smallest possible movement. Anything smaller than 1 / STEPS_PER_MM = 0.05mm rounds down to zero and the motor does not move at all.
This is not a bug — it is a real physical limitation. One step equals one minimum increment. With 20 steps/mm, the resolution is 0.05mm per step.
If I need higher resolution, I would switch the driver to microstepping (for example, 1/16 stepping gives 320 steps/mm, or 0.003mm resolution). But for now, 0.05mm is more than enough for learning.
Adjusting for Your Machine
The MM_PER_REV value depends entirely on your mechanical setup. Here are common examples:
| Setup | MM_PER_REV |
|---|---|
| GT2 belt + 20-tooth pulley | 40.0 |
| GT2 belt + 16-tooth pulley | 32.0 |
| Lead screw, 2mm pitch | 2.0 |
| Lead screw, 8mm pitch | 8.0 |
The easiest way to find your actual value: move the motor exactly 200 steps (one revolution) and measure how far the carriage moved with a ruler. That measurement is your MM_PER_REV.
Practice Ideas
- Change
MM_PER_REVto match your actual machine. Measure 1 revolution and set the correct value. - Try
moveX(0.1f, true, 2)— what is the smallest movement you can actually see? - Add a
moveY()function for a second axis with differentsteps_per_mm. - Try calling
moveX()withdistance_mm = 0. What happens?
What's Next
In Step 5, direction is still manual — I have to type true or false to choose which way to go.
In Step 6, I will change the interface to moveToX(target_mm). You give it a destination, and the code figures out direction automatically. That brings us one step closer to how G-code actually works.
See you next time.
Eric Park, from Daegu, South Korea
Tags: raspberrypi c steppermotor 3dprinting beginners embeddedsystems
Top comments (0)