Stepper Camera Slider
ESP32 DevKit (WROOM-32)·Arduino C++·Updated Jul 27, 2026·by FluxBench TeamIncludes Lab + Schematic
A motorized camera slider that glides between four rail positions with real acceleration ramps. AccelStepper drives a NEMA 17 through an A4988 driver (STEP GPIO 26, DIR GPIO 25) in non-blocking mode - slider.run() in loop() - so the button on GPIO 14 can pause and resume mid-move and the OLED keeps updating while the carriage travels. The display shows position in mm (20T GT2 pulley: 200 steps = 40 mm), waypoint number, state and a progress bar; the LED on GPIO 4 is lit while moving. Watch the NEMA 17 shaft turn in the Lab as the carriage glides.
sketch.ino
// Stepper Camera Slider - smooth, accelerated moves with AccelStepper.
// A NEMA 17 stepper (driven by an A4988) carries a camera along a rail,
// gliding between four waypoints with gentle speed ramps instead of jerky
// constant-speed steps. The button pauses/resumes mid-move; the LED is lit
// while the carriage is moving. 20T GT2 pulley: 200 steps = 40 mm of travel.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <AccelStepper.h>
#define STEP_PIN 26
#define DIR_PIN 25
#define BTN_PIN 14
#define LED_PIN 4
Adafruit_SSD1306 display(128, 64, &Wire, -1);
AccelStepper slider(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
long waypoints[] = {0, 800, 400, 1600}; // steps: 0, 160, 80, 320 mm
int wpCount = 4;
int wp = 0;
bool paused = false;
bool wasMoving = false;
unsigned long arrivedAt = 0;
unsigned long lastBtn = 0;
unsigned long lastDraw = 0;
void drawScreen(bool moving) {
float mm = slider.currentPosition() * 0.2;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.print("CAMERA SLIDER wp ");
display.print(wp);
display.setTextSize(2);
display.setCursor(0, 14);
display.print(mm, 1);
display.setTextSize(1);
display.print(" mm");
display.setCursor(0, 36);
if (paused) {
display.print("PAUSED - btn resumes");
} else if (moving) {
display.print("MOVING...");
} else {
display.print("HOLD");
}
int w = (int)(slider.currentPosition() * 120L / 1600);
display.drawRect(0, 50, 124, 10, SSD1306_WHITE);
display.fillRect(2, 52, w, 6, SSD1306_WHITE);
display.display();
}
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
slider.setMaxSpeed(400); // steps per second
slider.setAcceleration(300); // steps per second^2
slider.setCurrentPosition(0);
wp = 1;
slider.moveTo(waypoints[wp]);
Serial.println("Camera slider ready - gliding to waypoint 1");
}
void loop() {
// pause / resume button (active LOW)
if (digitalRead(BTN_PIN) == LOW && millis() - lastBtn > 300) {
lastBtn = millis();
paused = !paused;
if (paused) {
Serial.println("PAUSED - press the button again to resume");
} else {
Serial.println("RESUMED");
}
}
bool moving = false;
if (!paused) {
slider.run(); // non-blocking: one ramped step at a time
moving = slider.distanceToGo() != 0;
if (moving) {
wasMoving = true;
}
if (!moving && wasMoving) { // just arrived at a waypoint
wasMoving = false;
arrivedAt = millis();
float mm = slider.currentPosition() * 0.2;
Serial.print("ARRIVED wp");
Serial.print(wp);
Serial.print(" pos=");
Serial.print(slider.currentPosition());
Serial.print(" steps ");
Serial.print(mm, 1);
Serial.println(" mm");
}
if (!moving && !wasMoving && millis() - arrivedAt > 1500) {
wp = wp + 1;
if (wp >= wpCount) wp = 0;
slider.moveTo(waypoints[wp]);
Serial.print("Heading to wp");
Serial.print(wp);
Serial.print(" (");
Serial.print(waypoints[wp]);
Serial.println(" steps)");
}
}
digitalWrite(LED_PIN, moving ? HIGH : LOW);
if (millis() - lastDraw >= 250) {
lastDraw = millis();
drawScreen(moving);
}
delay(5);
}Built with FluxBench.AI — the AI workbench for embedded projects.