Step 4: Controlling Direction — and Starting to Track Position
Hello everyone. This is Eric Park, 3D Printer Engineer from South Korea.
Quick recap before we start:
- Step 1: Made the motor spin for the first time — 200 pulses, one full revolution.
- Step 2: Controlled speed by changing the delay between pulses.
-
Step 3: Built a
rotateAngle()function — enter an angle, the code calculates the steps. - Step 4 (today): The motor can now rotate both clockwise and counterclockwise. And we start tracking the current angle.
What Was Still Missing After Step 3
After Step 3, I had a working rotateAngle() function. But it always rotated in the same direction. My motor was stuck going one way.
For anything useful — a printer head moving left and right, a robotic arm returning to start — you need both directions. Step 4 adds that.
And once you have bidirectional movement, a natural question comes up: where is the motor right now? Step 4 introduces a simple way to track that.
How Direction Control Works
The A4988 driver has a DIR pin. The logic is simple:
DIR pin HIGH = clockwise (+ direction)
DIR pin LOW = counterclockwise (- direction)
Set the pin before sending step pulses, and the motor follows.
In code, I represent direction with a bool:
bool clockwise = true; /* + direction */
bool clockwise = false; /* - direction */
And the direction pin gets set once before the loop:
digitalWrite(DIR_PIN, clockwise ? HIGH : LOW);
That one line replaces a whole if-else block. I found the ternary operator a little strange at first, but it reads well once you get used to it: "if clockwise, set HIGH, otherwise set LOW."
The Code
#include <wiringPi.h>
#include <stdio.h>
#include <stdbool.h> /* bool, true, false */
#define STEP_PIN 4
#define DIR_PIN 3
#define ENABLE_PIN 2
#define STEPS_PER_REV 200
/*
* g_current_angle: tracks the motor's current angle in degrees
*
* This is a global variable. It starts at 0 and gets updated
* after every call to rotateAngle().
*
* It is not perfectly accurate (rounding errors accumulate over time),
* but it is good enough for learning purposes at this stage.
*/
float g_current_angle = 0.0;
/*
* rotateAngle: rotate by a specific angle in a specific direction
*
* angle : how many degrees to rotate (positive number)
* delay_ms : delay between pulses in milliseconds
* clockwise : true = clockwise (+), false = counterclockwise (-)
*/
void rotateAngle(float angle, int delay_ms, bool clockwise) {
/* Convert angle to step count */
int steps = (int)(angle / 360.0 * STEPS_PER_REV);
/* Set direction pin BEFORE sending pulses */
digitalWrite(DIR_PIN, clockwise ? HIGH : LOW);
printf("Rotating %.1f deg, %s -> %d steps\n",
angle,
clockwise ? "CW" : "CCW",
steps);
for (int i = 0; i < steps; i++) {
digitalWrite(STEP_PIN, HIGH);
delay(delay_ms);
digitalWrite(STEP_PIN, LOW);
delay(delay_ms);
}
/* Update position tracking */
if (clockwise) {
g_current_angle += angle;
} else {
g_current_angle -= angle;
}
printf("Current angle: %.1f deg\n\n", g_current_angle);
}
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);
delay(100);
printf("--- Step 4: Direction Control ---\n\n");
/* Rotate 90 degrees clockwise */
rotateAngle(90.0, 2, true);
delay(500);
/* Rotate 90 degrees counterclockwise — back to start */
rotateAngle(90.0, 2, false);
delay(500);
/* Go forward 180 degrees, then come back */
rotateAngle(180.0, 2, true);
delay(500);
rotateAngle(180.0, 2, false);
delay(500);
printf("Final position: %.1f degrees\n", g_current_angle);
digitalWrite(ENABLE_PIN, HIGH);
return 0;
}
Walking Through the Code
#include <stdbool.h>
C does not have a built-in bool type by default. This header adds bool, true, and false. Without it, the compiler does not recognize those keywords.
The direction pin:
digitalWrite(DIR_PIN, clockwise ? HIGH : LOW);
This must be set before the step pulses start. If you change direction in the middle of a pulse sequence, the motor gets confused. Set direction first, then pulse.
The global variable g_current_angle:
float g_current_angle = 0.0;
After each rotation, the function adds or subtracts the angle from this variable. It gives us a running record of where the motor is.
I named it with a g_ prefix. This is a naming convention to make global variables easy to spot in the code. When you see g_current_angle anywhere in the file, you know immediately it is a global — not a local variable inside a function.
Updating position after movement:
if (clockwise) {
g_current_angle += angle;
} else {
g_current_angle -= angle;
}
Clockwise adds to the angle, counterclockwise subtracts. After the main() test sequence — 90 CW, 90 CCW, 180 CW, 180 CCW — g_current_angle should be exactly 0.0. The motor ends up where it started.
A Limitation I Noticed
The rounding issue from Step 3 still applies here, and it now accumulates.
If I call rotateAngle(30.0, ...) ten times in the same direction, the motor actually moves 28.8 degrees each time (16 steps × 1.8°). After ten calls, g_current_angle says 300 degrees, but the motor has really moved 288 degrees. A 12-degree drift.
For short sequences this is not a problem. But for longer runs, it matters. The real solution is a homing sequence — a physical reference point the motor returns to so position can be reset from reality, not from software tracking alone. We will get to that later in the series.
Practice Ideas
- Add a third call in
main()to rotate 45 degrees CW, then 45 degrees CCW. Does it return to exactly 0.0? - Try calling
rotateAngle(360.0, 2, true)thenrotateAngle(360.0, 2, false). Watch the motor go one full revolution and come back. - Change
delay_msto 1 for CW and 3 for CCW. Does the speed actually change by direction? - Print
g_current_angleafter every call. Can you predict what it will say before running?
What Is Next
Step 5 introduces a coordinate system — tracking not just angle, but position in millimeters. We will define a steps_per_mm value based on the motor and lead screw specs, and the code will start using real physical units.
This is where things start to look like actual machine control.
See you next time.
Eric Park, from Daegu, South Korea
Tags: raspberrypi c steppermotor 3dprinting beginners embeddedsystems
Top comments (0)