FluxBench.AIShared project · read-only

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.