Large Modular 3D Scanning Turntable – ESP32
Obliczymy ostateczną cenę na podstawie wybranego pliku. Sprawdzisz ją i zaakceptujesz przed płatnością.
Zapisane na tym urządzeniu.
A large, modular motorized turntable designed for 3D scanning and product display. Powered by an 28BYJ-48
- Ocena
- 0 / 5
- Polubienia
- 3
- Pobrania
- 23
- Wydruki
- 0
- Wyświetlenia
- 266
- Oceny
- 0
Aktualizacja u źródła:
Informacje o modelu
- Źródło
- Printables
- Kategoria
- Accessories
- Licencja
- Creative Commons — Public Domain
Overview
This is a DIY motorized turntable designed primarily for 3D scanning, but it can also be used as a product display turntable.
The project was designed in Fusion 360 and built as a weekend project for my personal needs. It works reliably for me, and I’m sharing it in case others find it useful or want to improve on it.
Electronics & Control
The turntable is powered by:
ESP32
28BYJ-48 stepper motor
ULN2003 driver
Rotary encoder for:
Speed control
Direction control
Start/stop by pressing the encoder
Despite the small and inexpensive motor, the turntable is capable of rotating surprisingly heavy objects thanks to the large bearing-supported structure.
➡ Arduino (.ino) code and wiring diagram are included below.
Mechanical Design
Large plate diameter: 430 mm
The plate is split into 4 sections so it can be printed on smaller printers and then glued together.
Uses 608Z bearings (x5) for smooth rotation.
Detachable legs for easy storage and transport.
Legs can be fixed with M3 screws
Or even toothpicks for a quick, tool-free setup
Printing Notes
Plate sections need to be glued after printing
No special tolerances required
Parts Needed
608Z bearings × 5
ESP32 × 1
28BYJ-48 stepper motor × 1
ULN2003 driver × 1
Rotary encoder × 1
Jumper wires
M3 screws × 4 (or 12 for permanent leg mounting)
Future Improvements
This design works well for my use, but it’s not perfect. I may improve it in the future, and I invite anyone to modify, remix, or improve it as they see fit.
If you build it or change something—please share it!
Code and Readme:
#include <Arduino.h>const uint8_t stepPins[4] = {21, 19, 18, 5};
const uint8_t encoderCLK = 15;
const uint8_t encoderDT = 2;
const uint8_t encoderSW = 4;
const uint8_t stepSequence[8][4] = {
{1, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 1, 1},
{0, 0, 0, 1},
{1, 0, 0, 1}
};
volatile int16_t encoderDelta = 0;
int8_t currentStep = 0;
int16_t speedSetting = 0;
int16_t currentSpeed = 0;
const int16_t speedIncrement = 2; // Encoder detent multiplier
const int16_t maxSpeedSetting = 160; // Clamp for stability
const uint32_t maxStepInterval = 4500; // Slowest (µs)
const uint32_t minStepInterval = 900; // Fastest (µs)
const uint8_t accelStep = 1; // Acceleration step
const uint32_t debounceMs = 50;
bool paused = false;
bool lastButtonState = HIGH;
uint32_t lastButtonChange = 0;
uint32_t lastStepMicros = 0;
void IRAM_ATTR handleEncoder() {
static uint8_t lastState = 0;
uint8_t state = (digitalRead(encoderCLK) << 1) | digitalRead(encoderDT);
uint8_t transition = (lastState << 2) | state;
switch (transition) {
case 0b0001:
case 0b0111:
case 0b1110:
case 0b1000:
encoderDelta++;
break;
case 0b0010:
case 0b0100:
case 0b1101:
case 0b1011:
encoderDelta--;
break;
default:
break;
}
lastState = state;
}
void applyStep(int8_t stepIndex) {
for (uint8_t i = 0; i < 4; i++) {
digitalWrite(stepPins[i], stepSequence[stepIndex][i]);
}
}
void stepMotor(int direction) {
currentStep = (currentStep + direction + 8) % 8;
applyStep(currentStep);
}
void setup() {
for (uint8_t pin : stepPins) {
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
}
pinMode(encoderCLK, INPUT_PULLUP);
pinMode(encoderDT, INPUT_PULLUP);
pinMode(encoderSW, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(encoderCLK), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(encoderDT), handleEncoder, CHANGE);
}
void loop() {
int16_t delta;
noInterrupts();
delta = encoderDelta;
encoderDelta = 0;
interrupts();
const bool buttonState = digitalRead(encoderSW);
const uint32_t nowMs = millis();
if (buttonState != lastButtonState && (nowMs - lastButtonChange) > debounceMs) {
lastButtonChange = nowMs;
lastButtonState = buttonState;
if (buttonState == LOW) {
paused = !paused;
if (paused) {
currentSpeed = 0;
}
}
}
if (delta != 0) {
speedSetting += delta * speedIncrement;
speedSetting = constrain(speedSetting, -maxSpeedSetting, maxSpeedSetting);
}
if (paused) {
lastStepMicros = micros();
return;
}
if (currentSpeed != speedSetting) {
if (currentSpeed < speedSetting) currentSpeed += accelStep;
else if (currentSpeed > speedSetting) currentSpeed -= accelStep;
}
if (currentSpeed != 0) {
const int8_t direction = (currentSpeed > 0) ? 1 : -1;
const uint32_t speedLevel = abs(currentSpeed);
const uint32_t interval = constrain(
map(speedLevel, 1, maxSpeedSetting, maxStepInterval, minStepInterval),
minStepInterval,
maxStepInterval
);
const uint32_t now = micros();
if (now - lastStepMicros >= interval) {
lastStepMicros = now;
stepMotor(direction);
}
}
}
ESP32 Turntable Controller
Simple ESP32 sketch that uses a rotary encoder to adjust the speed (and direction) of a 28BYJ-48 stepper motor through a ULN2003 driver board. Pressing the encoder button pauses/resumes motion.
Wiring
Power
- ESP32 VIN (5 V from USB) → ULN2003 VCC
- ESP32 GND → ULN2003 GND → Rotary encoder GND (common ground)
- Rotary encoder + → ESP32 3.3 V
Stepper (via ULN2003 board)
ULN2003 pin
ESP32 GPIO
IN1
21
IN2
19
IN3
18
IN4
5
Motor header
28BYJ-48
Rotary Encoder
Encoder pin
ESP32 GPIO
Notes
CLK
15
Interrupt input
DT
2
Interrupt input
SW
4
Button (pause/resume)
+
3.3 V
Use INTERNAL PULLUP
GND
GND
Share ground
Behavior
- Turning the encoder clockwise increases speed in the forward direction.
- Turning counter-clockwise decreases speed, eventually reversing direction.
- Encoder button toggles pause/resume.
Notes
- Keep the ULN2003 board powered from 5 V; the 28BYJ-48 needs more than 3.3 V.
- Ensure the ESP32, ULN2003, and encoder share ground.
- If you need higher top speed, consider a stronger stepper (e.g., NEMA 17 with DRV8825).