← SHEET 02 · ASSEMBLIES EL-008
Stepper Camera Slider Controller
| PART NO | EL-008 |
|---|---|
| MATL / SYSTEM | ESP32 · A4988 · NEMA17 |
| TOOLS | Arduino IDE · Wokwi · C++ |
This one bridges the mechanism side of the portfolio and the electronics side: the same belt-and-carriage kinematics behind the folding staircase linkage apply here to a single translating axis, but the interesting problem moves from geometry to timing — how do you get a stepper from rest to a cruise speed and back to rest smoothly, without a motion library doing the math for you. This project answers that with its own trapezoidal step-timing engine.
OVERVIEW & MOTIVATION
Most hobby slider builds pull in AccelStepper and stop thinking about motion
profiles. This one deliberately doesn’t: runTrapezoidMove() derives every
step’s timing from s = 1/2 a t^2 directly, so the accel ramp, cruise, and decel
are all provable from the same one-line kinematic identity rather than borrowed
from a library’s internals. Layered on top of that engine: homing against a
microswitch endstop with a fast-seek/back-off/slow-creep sequence for repeatable
zeroing, soft travel limits measured from that zero, and an OLED + rotary-encoder
menu that exposes move distance/speed and a time-lapse mode (N frames, M-second
interval, opto-isolator shutter pulse per frame). The target platform is a
GT2-belt NEMA17 slider — the same building block behind 3D-printer axes and
DIY camera dollies.
COMPONENTS & BOM
| REF | COMPONENT | SPEC | ROLE |
|---|---|---|---|
| U1 | ESP32 DevKit-C V4 | dual-core, 3.3 V logic | controller, runs the profile generator + UI |
| U2 | A4988 stepper driver | up to 1/16 microstep, current-limited | drives the NEMA17 from STEP/DIR pulses |
| M1 | NEMA17 stepper | 1.8°/step (200 steps/rev), ~1.2 A/phase | belt drive motor |
| SW1 | Microswitch / pushbutton endstop | normally-open, active LOW | homing reference at one end of travel |
| U3 | SSD1306 OLED | 128×64, I2C, addr 0x3C | menu / status display |
| ENC1 | KY-040 rotary encoder | quadrature + push switch | menu navigation and value entry |
| OPT1 | Opto-isolator (e.g. 4N35) | GPIO-driven LED side | isolated camera remote-shutter trigger |
| — | GT2 belt + 20T pulley | 2 mm pitch | translates rotation to carriage travel |
| PSU1 | 12 V supply | sized to NEMA17 current | A4988 VMOT motor power (not MCU-connected) |
WIRING
| MCU PIN | NET | PERIPHERAL PIN |
|---|---|---|
| GPIO26 | STEP | A4988 STEP |
| GPIO27 | DIR | A4988 DIR |
| GPIO25 | ENABLE | A4988 ENABLE (active LOW) |
| GPIO33 | MS1 | A4988 MS1 |
| GPIO32 | MS2 | A4988 MS2 |
| GPIO14 | MS3 | A4988 MS3 |
| 3V3 | LOGIC 3V3 | A4988 VDD, OLED VCC, encoder VCC |
| GND | GND RAIL | A4988 GND, OLED GND, encoder GND, endstop GND |
| GPIO4 | ENDSTOP_SW | endstop switch (INPUT_PULLUP, active LOW) |
| GPIO18 | ENC_CLK | KY-040 CLK |
| GPIO19 | ENC_DT | KY-040 DT |
| GPIO5 | ENC_SW | KY-040 SW |
| GPIO21 | I2C_SDA | OLED SDA |
| GPIO22 | I2C_SCL | OLED SCL |
| GPIO23 | SHUTTER | opto-isolator input (camera remote trigger) |
MOTION CONTROL
Steps/mm. GT2 belt (2 mm pitch) on a 20-tooth pulley, NEMA17 at 1.8°/step (200 full steps/rev), A4988 at 1/16 microstepping:
microsteps/rev = 200 x 16 = 3200
mm/rev (belt) = 20 teeth x 2 mm pitch = 40
STEPS_PER_MM = 3200 / 40 = 80.0
Trapezoidal profile, from first principles. Constant-acceleration kinematics
give s = 1/2 a t^2, so treating a step count as the distance gives the exact
elapsed time to complete step i directly: t(i) = sqrt(2i/a). The interval
between consecutive steps is just the difference of consecutive timestamps — no
motion library, no lookup table:
float rampIntervalUs(long i, float accelSps2) {
if (i <= 0) return 0.0f;
float t_i = sqrtf(2.0f * (float)i / accelSps2);
float t_im1 = sqrtf(2.0f * (float)(i - 1) / accelSps2);
return (t_i - t_im1) * 1.0e6f; // seconds -> microseconds
}
A move of steps picks nAccel = min(vMax^2 / (2*accel), steps/2) (the /2 cap
falls back to a triangular profile — no cruise phase — when the target is too
short to ever reach vMax), mirrors that same count for nDecel, and fills the
remainder with a constant-interval cruise at 1e6/vMax µs. Deceleration reuses
rampIntervalUs() with a mirrored index (nDecel - d + 1), so the ramp down is
the exact time-reverse of the ramp up rather than an approximation of it:
if (i <= nAccel) {
intervalUs = rampIntervalUs(i, accelSps2); // ramp up
} else if (i <= nAccel + nCruise) {
intervalUs = cCruiseUs; // constant speed
} else {
long d = i - nAccel - nCruise;
intervalUs = rampIntervalUs(nDecel - d + 1, accelSps2); // mirrored ramp down
}
Step timing itself is enforced with plain unsigned long micros()-arithmetic
comparisons (wraparound-safe), so while the ramp math is float, the time-critical
wait loop is integer-only. Homing runs a fast seek toward the endstop (cut
short the instant the switch closes), a fixed back-off, then a slow creep back in
for a repeatable zero; soft limits ([0, MAX_TRAVEL_STEPS]) are measured from
that zero and refuse any move — single or time-lapse — until homing has
succeeded. Time-lapse mode splits a total travel span across N frames,
firing the opto-isolator shutter pulse and waiting out the remainder of the
M-second interval between each incremental move.
SIMULATION
Runs end-to-end in Wokwi against diagram.json. Watch the
wokwi-stepper-motor turn through the homing sequence at boot (fast seek, back
off, slow creep), then drive the KY-040 to page through the OLED menu — SET DISTANCE, SET SPEED, MOVE, SET FRAMES, SET INTERVAL, RUN TIME-LAPSE —
and watch the motor step out each move with visibly different accel/cruise/decel
speed, and the shutter LED blink on each time-lapse frame. To run it: open
wokwi.com, start a new ESP32 project, paste sketch.ino and diagram.json over
the defaults, and press Play.
Substitutions: all core motion parts (board-esp32-devkit-c-v4,
wokwi-a4988, wokwi-stepper-motor, wokwi-pushbutton, wokwi-ssd1306,
wokwi-ky-040) are real Wokwi parts, no stand-ins needed. The camera shutter
line is represented by a wokwi-led + resistor (shutterLed1/r1) standing in
for the real opto-isolator’s input LED, so the pulse is visible in sim; on real
hardware GPIO23 drives an actual opto-isolator module instead. Per Wokwi’s
wokwi-a4988 reference, VMOT/motor-power GND aren’t simulated (the sim turns
the motor from STEP/DIR alone), so those pins are left unconnected in
diagram.json and only matter on the real build.
STATUS
Design and firmware are complete and self-consistent — pinout matches exactly
across sketch.ino, diagram.json, and this page — and the whole flow (homing,
menu, single move, time-lapse) has been exercised in the Wokwi simulator only.
No physical hardware has been assembled: no bench measurements, no real motor
current tuned on an A4988 trimpot, no belt/pulley cut and mounted, no photos.
Every number above (STEPS_PER_MM, SLIDER_TRAVEL_MM, accel/speed defaults,
homing speeds) is a design value or an expected simulator behavior, not a
measured result. Next step: cut the rail and belt, build the carriage, and bring
up this exact firmware unchanged on the real slider.
USE CASES & APPLICATIONS
The trapezoidal step-timing engine here is axis-agnostic — the same accel-ramp math drives any single stepper axis: 3D-printer and CNC linear axes, pick-and- place gantries, and lab XY stages, in addition to motorized camera rigs. The homing-against-an-endstop-then-soft-limit pattern is the standard way any of those machines establishes a safe, repeatable coordinate system at power-up. The time-lapse-specific piece — incremental moves paced against a fixed shutter interval — generalizes to any “move, settle, trigger” sequencing problem: automated inspection stations stepping a part under a camera, or a scanning stage advancing between exposures.
FILES
sources/electronics/camera-slider/sketch.ino— full ESP32 firmware: the trapezoidal step-timing engine, homing routine, encoder/OLED menu, single-move and time-lapse execution.sources/electronics/camera-slider/diagram.json— Wokwi wiring diagram, pinout matches the firmware exactly.sources/electronics/camera-slider/README.md— Wokwi run steps, library list, A4988 current-setting math, and real-build wiring notes.