En este tutorial aprenderás a construir una base giratoria con un diámetro de 16 cm controlada con la UNIT Pulsar ESP32-C6, utilizando un motor a pasos 28BYJ-48, su controlador ULN2003, un potenciómetro para regular la velocidad y un botón para cambiar el sentido de giro.
El proyecto combina diseño e impresión 3D, electrónica y programación, dando como resultado una plataforma giratoria compacta y portátil que puedes utilizar para exhibir productos, grabar videos de objetos en 360°, realizar fotografía, crear pequeños proyectos de automatización o experimentar con motores a pasos.
La base incorpora una batería Li-Ion 18650 de 3.7 V, un módulo de carga y elevación de voltaje, puerto USB Tipo C e interruptor general, permitiendo utilizarla sin necesidad de mantenerla conectada a una computadora o fuente externa.
MATERIALES
Manos a la obra! Para construir la base giratoria necesitaremos los siguientes materiales:
- 1 × UNIT Pulsar ESP32-C6.
- 1 × Motor a pasos 28BYJ-48 con Controlador ULN2003.
- 1 × Potenciómetro de aproximadamente 50 kΩ.
- 1 × PBS-33B Botón pulsador.
- 1 × Batería Li-Ion 18650 de 3.7 V.
- 1 × Porta batería para 18650.
- 1 × TP4056 con Boost Step Up Cargador de Baterías Li-Ion / LiPo.
- 1 × Conector USB Tipo C para la entrada de carga, dependiendo del diseño utilizado.
- 1 × KCD11-2P Interruptor ON OFF.
- Cables para conexión.
- Protoboard o PCB 6x4cm perforada para realizar las conexiones internas.
- 4 x 608ZZ-7 Rodamiento 8 x 22 x 7 mm.
- Tornillería.
Para facilitar el armado del proyecto, puedes consultar y adquirir lista materiales en el siguiente link:
Las piezas que forman la estructura de la base están diseñadas para impresión 3D. Puedes descargar los archivos e imprimirlos directamente:
¿No cuentas con una impresora 3D? Puedes utilizar nuestro servicio de impresión 3D para fabricar las piezas necesarias para este proyecto:
DIAGRAMA DE CONEXIÓN
A continuación se muestra el diagrama de conexión para la base giratoria. Se divide en 3 faces:
- Alimentación de energia
- Control de giro y velocidad
- Tarjeta de control

Utiliza el diagrama de conexión como referencia para realizar el cableado del proyecto, o sigue los siguientes recomendaciones de conexión para cada elemento que integra la base giratoria.
Alimentación
Para que la base sea portátil utilizamos una batería recargable Li-Ion 18650 de 3.7 V.
La batería se conecta al módulo de carga TP4056 con Boost Step Up:

| Batería 18650 | Módulo TP4056 con Boost Step Up |
|---|---|
| Positivo | BAT+ |
| Negativo | BAT− |
Carga de batería
Para la carga de la base se utiliza un Conector USB Tipo C de chasis el cual se conecta:

| USB-C | Módulo TP4056 con Boost Step Up |
|---|---|
| Positivo | IN+ |
| Negativo | IN− |
El Módulo TP4056 con Boost Step Up debe ajustarse a 5V DC de salida con la ayuda del potenciómetro que integra y un multímetro, ya que esta salida se VOUT+ de 5V será la encargada de alimentar:
- UNIT Pulsar ESP32-C6

- Controlador ULN2003
Es importante que ajustes el Módulo TP4056 con Boost Step Up a 5V de salida de lo contrario dañaras la tarjeta UNIT Pulsar.

Encendido y Apagado
El interruptor ON/OFF se coloca en serie con la alimentación general para desconectar el sistema cuando no esté en uso.

Conexión del motor 28BYJ-48 y ULN2003
El motor 28BYJ-48 se conecta directamente al conector de cinco terminales del controlador ULN2003. Posteriormente, las cuatro entradas del ULN2003 se conectan a la UNIT Pulsar de la siguiente manera:

| ULN2003 | Pulsar ESP32-C6 | GPIO |
|---|---|---|
| IN1 | D5 | GPIO19 |
| IN2 | D6 | GPIO20 |
| IN3 | D7 | GPIO21 |
| IN4 | D10 | GPIO18 |
| VCC / + | 5 V | — |
| GND | GND | — |
Botón para control de giro
El botón cambia el sentido de giro de la plataforma. Conéctalo de la siguiente manera:

| Botón | Pulsar ESP32-C6 |
|---|---|
| Terminal 1 | D4 / GPIO15 |
| Terminal 2 | GND |
No es necesario colocar una resistencia pull-up externa porque utilizaremos:
pinMode(buttonPin, INPUT_PULLUP);
Cuando el botón está libre tendremos un estado HIGH. Cuando presionamos el botón, D4 se conecta a GND y obtenemos un estado LOW.
Control de velocidad
El potenciómetro será el encargado de establecer la velocidad de giro. Realiza las conexiones de la siguiente manera:

| Potenciómetro | Pulsar ESP32-C6 |
|---|---|
| Terminal lateral | 3.3 V |
| Terminal central | A1 / D15 / GPIO1 |
| Terminal lateral | GND |
La entrada A1 corresponde a GPIO1 y ADC1_CH1 en la Pulsar ESP32-C6.
Importante: Aunque el motor y el ULN2003 trabajan con una alimentación de 5 V, no debemos aplicar esos 5 V directamente a A1.
ENSAMBLE DE LA BASE GIRATORIA
Una vez realizadas las conexiones podemos comenzar con el montaje mecánico.
El diseño está formado principalmente por una base inferior, donde se colocan todos los componentes electrónicos, y una plataforma superior giratoria.
1. Ensamble de rodamientos
Antes de ensamblar la electrónica primero debemos ensamblar los rodamiento608ZZ-7 internos en la base inferior con los poste de impresión 3D como se muestra en el siguiente video de referencia:
2. Ensamble de la electrónica
Ya que ensamblaste los rodamientos 608ZZ-7 podrás ensamblar la electrónica, en el siguiente video tendrás la referencio de como ubicar la electrónica:
Nota: El video es una referencia pero te recomendamos conectar el motor a pasos y ULN2003 en sus barrenos correspondientes, puedes utilizar tornillos M3 para sujetarlos a los barrenos. De igual forma puedes utilizar una PCB perforada de 6x4cm la cual te recomendamos para montar la pulsar con hearder hembra, utilizar borneras o conectores JTS en la PCB para facilitar la conexión del potenciómetro, botón, interruptor lo demás lo puedes conectar soldando alambre de protoboard o cables dupont.
3. Ensamble de tapa superior
Finalmente instala el rodamiento 608ZZ-7 en tapa superior y después ensámblala en la base inferior, como se muestra en el siguiente video:
CÓDIGO
Una vez ensamblada la base giratoria y realizadas todas las conexiones, es momento de programarla. Antes de cargar el código en la UNIT Pulsar ESP32-C6, realiza las siguientes configuraciones:
1. Configuración de entorno de programación
Usaremos Arduino IDE como entorno de desarrollo. Sigue estos pasos:
- Instala el Arduino IDE
Descárgalo desde el sitio oficial:
👉 https://www.arduino.cc/en/software
⚠️(Si ya lo tienes instalado, puedes omitir este paso).
- Agrega la placas de desarrollo UNIT Electronics ESP32 a Arduino IDE
Abre el IDE y ve a Archivo > Preferencias
En el campo “Gestor de URLs adicionales de tarjetas”, agrega la siguiente dirección:
👉 https://raw.githubusercontent.com/UNIT-Electronics/Uelectronics-ESP32-Arduino-Package/main/package_Uelectronics_esp32_index.json

- Instala las placas UNIT Electronics ESP32 desde el Gestor de placas de Arduino IDE.
Después la URL al preferencias dirígete a la pestaña Herramientas > Placa > Gestor de placas, busca “UNIT Electronics ESP32” e instala las placas. En la siguiente imagen tienes la referencia

Una ventaja de este proyecto es que no necesitamos instalar ninguna librería adicional para controlar el motor. La secuencia del 28BYJ-48 se genera directamente desde código.
Es importante que cuando programes la base giratoria la tarjeta UNT Pulsar ESP32 C6 debe estar desconectada de la base, solo debe de estar conectada al puerto USB de tu computadora y laptop después sigue estos pasos:
- Selecciona la placa UNIT Pulsar ESP32 C6 en Arduino IDE
- Antes de compilar y cargar el código, verifica que tengas seleccionada la siguiente tarjeta:
- Herramientas > Placa > UNIT Electronics ESP32-C6 > Pulsar ESP32 C6
- Selecciona el puerto COM que te asigno la computadora.

Con estas configuraciones completadas, tu entorno estará listo para compilar y cargar el siguiente código en la Pulsar.
// --------------------------------------------------
// Pines de la UNIT Pulsar ESP32-C6
// --------------------------------------------------
// Potenciómetro conectado a A1 / D15 / GPIO1
const uint8_t potPin = 1;
// Botón conectado a D4 / GPIO15
const uint8_t buttonPin = 15;
// ULN2003
const uint8_t motorPins[4] = {
19, // D5 -> IN1
20, // D6 -> IN2
21, // D7 -> IN3
18 // D10 -> IN4
};
// --------------------------------------------------
// Configuración del motor 28BYJ-48
// --------------------------------------------------
// Número aproximado de medios pasos por vuelta
const uint16_t pasosPorVuelta = 4096;
// Secuencia de medio paso para el ULN2003
const uint8_t secuencia[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}
};
int indicePaso = 0;
bool sentidoHorario = true;
unsigned long tiempoUltimoPaso = 0;
unsigned long intervaloPaso = 1000;
// --------------------------------------------------
// Variables del botón
// --------------------------------------------------
int lecturaAnteriorBoton = HIGH;
int estadoEstableBoton = HIGH;
unsigned long tiempoAntirrebote = 0;
const unsigned long retardoAntirrebote = 50;
// --------------------------------------------------
// Variables de control
// --------------------------------------------------
int velocidadRPM = 1;
unsigned long tiempoMonitorSerial = 0;
const unsigned long intervaloMonitorSerial = 300;
void setup() {
Serial.begin(115200);
// Botón con resistencia pull-up interna
pinMode(buttonPin, INPUT_PULLUP);
// Configura los cuatro pines del ULN2003 como salidas
for (uint8_t i = 0; i < 4; i++) {
pinMode(motorPins[i], OUTPUT);
digitalWrite(motorPins[i], LOW);
}
// ADC de 12 bits: valores de 0 a 4095
analogReadResolution(12);
Serial.println("Control de motor 28BYJ-48");
Serial.println("UNIT Pulsar ESP32-C6");
}
void loop() {
leerBoton();
// Promedio de varias lecturas para reducir variaciones
uint16_t valorPotenciometro = leerPotenciometro();
// Convierte la lectura analógica a una velocidad de 1 a 15 RPM
velocidadRPM = map(
valorPotenciometro,
0,
4095,
1,
15
);
velocidadRPM = constrain(velocidadRPM, 1, 15);
// Calcula el tiempo entre cada medio paso
intervaloPaso =
60000000UL /
((unsigned long)pasosPorVuelta * velocidadRPM);
moverMotor();
// Muestra los datos sin saturar el monitor serial
if (millis() - tiempoMonitorSerial >= intervaloMonitorSerial) {
tiempoMonitorSerial = millis();
Serial.print("ADC: ");
Serial.print(valorPotenciometro);
Serial.print(" | Velocidad: ");
Serial.print(velocidadRPM);
Serial.print(" RPM");
Serial.print(" | Sentido: ");
Serial.println(
sentidoHorario ? "Horario" : "Antihorario"
);
}
}
// --------------------------------------------------
// Movimiento no bloqueante
// --------------------------------------------------
void moverMotor() {
unsigned long tiempoActual = micros();
if (tiempoActual - tiempoUltimoPaso >= intervaloPaso) {
tiempoUltimoPaso = tiempoActual;
if (sentidoHorario) {
indicePaso++;
if (indicePaso > 7) {
indicePaso = 0;
}
} else {
indicePaso--;
if (indicePaso < 0) {
indicePaso = 7;
}
}
aplicarPaso(indicePaso);
}
}
void aplicarPaso(uint8_t paso) {
for (uint8_t i = 0; i < 4; i++) {
digitalWrite(motorPins[i], secuencia[paso][i]);
}
}
// --------------------------------------------------
// Lectura del botón con antirrebote
// --------------------------------------------------
void leerBoton() {
int lecturaActual = digitalRead(buttonPin);
if (lecturaActual != lecturaAnteriorBoton) {
tiempoAntirrebote = millis();
lecturaAnteriorBoton = lecturaActual;
}
if (millis() - tiempoAntirrebote >= retardoAntirrebote) {
if (lecturaActual != estadoEstableBoton) {
estadoEstableBoton = lecturaActual;
// El botón está presionado cuando su estado es LOW
if (estadoEstableBoton == LOW) {
sentidoHorario = !sentidoHorario;
}
}
}
}
// --------------------------------------------------
// Promedio de lectura analógica
// --------------------------------------------------
uint16_t leerPotenciometro() {
uint32_t suma = 0;
const uint8_t muestras = 8;
for (uint8_t i = 0; i < muestras; i++) {
suma += analogRead(potPin);
}
return suma / muestras;
}
¿CÓMO FUNCIONA EL CÓDIGO?
Configuración de los pines
Primero definimos los pines que utilizaremos:
const uint8_t potPin = 1; const uint8_t buttonPin = 15;
GPIO1 corresponde a A1 y recibe la señal analógica del potenciómetro.
GPIO15 corresponde a D4 y recibe el estado del botón.
Posteriormente declaramos las cuatro señales que controlan el ULN2003:
const uint8_t motorPins[4] = {
19,
20,
21,
18
};
Estas corresponden a:
D5 → IN1
D6 → IN2
D7 → IN3
D10 → IN4
CONTROL DEL 28BYJ-48
El motor 28BYJ-48 contiene cuatro fases que deben energizarse siguiendo una determinada secuencia.
En este proyecto utilizamos una secuencia de medio paso de ocho estados:
const uint8_t secuencia[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}
};
Cada fila representa las bobinas que deben permanecer activadas durante un medio paso.
Este método nos permite obtener un movimiento más suave y una mayor resolución que utilizando únicamente pasos completos.
Para simplificar el control consideramos aproximadamente:
const uint16_t pasosPorVuelta = 4096;
Es decir, aproximadamente 4096 medios pasos para completar una vuelta del eje de salida del 28BYJ-48.
CONTROL DE VELOCIDAD CON EL POTENCIÓMETRO
La Pulsar utiliza su ADC de 12 bits para leer el potenciómetro.
analogReadResolution(12);
Por lo tanto obtenemos valores comprendidos entre:
0 → Potenciómetro al mínimo 4095 → Potenciómetro al máximo
Posteriormente utilizamos:
velocidadRPM = map( valorPotenciometro, 0, 4095, 1, 15 );
De esta manera convertimos la lectura del potenciómetro en una velocidad aproximada entre:
1 RPM y 15 RPM.
Si necesitas una base todavía más lenta puedes modificar fácilmente estos valores. Por ejemplo:
1, 8
para trabajar entre 1 y 8 RPM.
Esto puede ser útil cuando se colocan objetos más pesados o se busca un movimiento especialmente suave para fotografía.
¿CÓMO SE CALCULA LA VELOCIDAD DEL MOTOR?
Para controlar la velocidad calculamos cuánto tiempo debe transcurrir entre cada medio paso:
intervaloPaso = 60000000UL / ((unsigned long)pasosPorVuelta * velocidadRPM);
60,000,000 representa la cantidad de microsegundos que existen en un minuto.
Por ejemplo, si seleccionamos:
1 RPM
el programa distribuye aproximadamente 4096 pasos durante un minuto.
Si aumentamos a:
10 RPM
el intervalo entre pasos disminuye y el motor gira más rápidamente.
MOVIMIENTO SIN BLOQUEAR EL PROGRAMA
Una característica importante de este código es que no utiliza delay() para generar el movimiento.
En su lugar se utiliza:
micros();
para saber cuándo corresponde realizar el siguiente paso.
Esto permite que, mientras el motor está girando, la Pulsar pueda seguir:
- Leyendo el potenciómetro.
- Detectando el botón.
- Actualizando el Monitor Serial.
- Ejecutando futuras funciones.
Este método también facilita ampliar posteriormente el proyecto con Wi-Fi, Bluetooth BLE, sensores o una interfaz web sin detener el movimiento del motor.
CAMBIO DEL SENTIDO DE GIRO
Cada vez que presionamos el botón ejecutamos:
sentidoHorario = !sentidoHorario;
Si el motor estaba girando en sentido horario pasará a antihorario y viceversa.
La dirección se determina incrementando o disminuyendo la posición dentro de la secuencia:
if (sentidoHorario) {
indicePaso++;
} else {
indicePaso--;
}
Por ello no necesitamos realizar ninguna modificación física en el cableado para invertir el movimiento.
ANTIRREBOTE DEL BOTÓN
Los botones mecánicos no producen una transición eléctrica perfecta cuando se presionan. Durante algunos milisegundos pueden cambiar varias veces entre HIGH y LOW.
Para evitar que una sola pulsación cambie varias veces el sentido del motor utilizamos un tiempo de antirrebote de:
const unsigned long retardoAntirrebote = 50;
De esta forma solo se acepta el cambio cuando la señal se mantiene estable.
FILTRADO DEL POTENCIÓMETRO
Las entradas analógicas pueden tener pequeñas variaciones eléctricas.
Para conseguir un movimiento más estable realizamos ocho lecturas:
const uint8_t muestras = 8;
Posteriormente obtenemos su promedio:
return suma / muestras;
Esto reduce pequeñas fluctuaciones que podrían provocar cambios constantes en la velocidad del motor aunque no estemos moviendo el potenciómetro.
CONCLUSIÓN
Con este proyecto aprendimos a construir una base giratoria utilizando la UNIT Pulsar ESP32-C6 y un motor a pasos 28BYJ-48. Gracias al potenciómetro podemos ajustar fácilmente la velocidad de giro y, mediante el botón, cambiar la dirección de movimiento.
Además de ser un proyecto práctico para fotografía, exhibición de productos o proyectos DIY, también es una excelente forma de aprender sobre motores a pasos, control de velocidad, electrónica y programación con ESP32-C6. A partir de este diseño puedes seguir experimentando y agregar nuevas funciones como control mediante Bluetooth o Wi-Fi.

