En este artículo veremos cómo controlar un motor paso a paso usando un ESP32 PLC. Cubriremos los fundamentos de los motores paso a paso, sus características y cómo funcionan en combinación con un driver controlado por las salidas PWM de un ESP32 PLC. El motor y el driver utilizados son el 17HS16-2004S y el TB6600, respectivamente.
Cómo funcionan los motores paso a paso
Los motores paso a paso son dispositivos electromecánicos que convierten señales eléctricas en movimientos mecánicos precisos. A diferencia de los motores convencionales que giran de forma continua, los motores paso a paso se mueven en pasos discretos. Esto los hace ideales para aplicaciones que requieren un control preciso de posición, velocidad y par.
Los pasos discretos corresponden a un desplazamiento angular fijo conocido como ángulo de paso, determinado por el diseño del motor y medido en grados. Los motores paso a paso pueden tener diferentes ángulos de paso, como 1,8° (200 pasos por revolución) o 0,9° (400 pasos por revolución). El 17HS16-2004S usa 200 pasos por revolución.
Tres señales controlan un motor paso a paso: pulso, dirección y habilitación. La señal de pulso determina la frecuencia de paso, la señal de dirección establece el sentido de rotación (horario o antihorario) y la señal de habilitación activa o desactiva el motor. Controlar el tiempo y la secuencia de estas señales permite un posicionamiento y una rotación precisos.
Estas señales no pueden enviarse al motor directamente; se necesita un driver. El driver recibe señales de control de un controlador como un microcontrolador o un PLC y proporciona la potencia y corriente necesarias a las bobinas del motor. Los drivers de motores paso a paso ofrecen características como diferentes modos de control (paso completo, medio paso, micropaso), regulación de corriente y mecanismos de protección. El TB6600 usado en este ejemplo admite hasta 32 micropasos y puede suministrar hasta 4 A de corriente.
Especificaciones del motor NEMA 17 17HS16-2004S
El motor NEMA 17 17HS16-2004S tiene las siguientes especificaciones eléctricas:
| Especificación eléctrica | |
|---|---|
| Bipolar/Unipolar | Bipolar |
| Par de retención | 0,45 Nm |
| Inductancia | 2,6 mH |
| Resistencia de fase | 1,1 Ohm |
| Corriente nominal | 2 A |
| Angulo de paso | 1,8° |
Configuración del driver de micropaso TB6600
El TB6600 admite múltiples modos según la configuración de micropaso y corriente:
- Micropasos: 1, 2, 4, 8, 16, 32
- Corriente (A): 0,5, 1,0, 1,5, 2,0, 2,5, 3,0, 3,5
Para seleccionar los micropasos y la corriente, el driver tiene 6 interruptores DIP. Una tabla en la carcasa del driver muestra la combinación correcta de on/off para cada modo.
Cableado del ESP32 PLC al driver TB6600
Las siguientes conexiones son necesarias para controlar el motor paso a paso desde el ESP32 PLC.
Conexiones del motor al TB6600
- Azul --> A-
- Rojo --> A+
- Negro --> B-
- Verde --> B+
Configuración de pines del ESP32 PLC
- Q0.0 --> PUL+
- Q0.1 --> DIR+
- Q0.2 --> ENA+
- GND --> PUL-, DIR-, ENA-
Las salidas del ESP32 PLC deben configurarse a 5 V: conecta QVdc a 5 V y COM(-) a GND. La misma fuente de alimentación de 12-24 V puede usarse tanto para el ESP32 PLC como para el TB6600.
Diagrama de cableado

Sketches de Arduino para el ESP32 PLC
Para programar el ESP32 PLC necesitas el paquete Industrial Shields ESP32 instalado en el IDE de Arduino. Ambos sketches requieren la librería Adafruit PWM Servo Driver para generar las señales PWM.
Adafruit PWM Servo Driver Library
Las salidas PWM del ESP32 PLC admiten frecuencias entre 24 Hz y 1526 Hz, lo que limita las velocidades disponibles del motor según la configuración de micropaso.
Movimiento a velocidad constante
Este sketch hace que el motor realice movimientos a velocidad constante.
Primero, define la configuración física: pines usados, pasos del motor y configuración de micropaso. En este ejemplo se usan Q0.0, Q0.1 y Q0.2 para las señales de paso, dirección y habilitación respectivamente, aunque puede usarse cualquier salida PWM del ESP32 PLC. La macro STEPS_REV calcula los pasos totales por revolución basados en los pasos del motor y la configuración de micropaso del driver, lo que es útil para los cálculos de velocidad.
La función move_to() recibe una distancia en revoluciones, positiva o negativa según la dirección deseada, y calcula el tiempo que el motor debe funcionar a la velocidad objetivo para recorrer esa distancia.
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#define DEBUG 1
#define STEP_PIN Q0_0
#define DIR_PIN Q0_1
#define ENA_PIN Q0_2
#define STEPS 200
#define MICROSTEPS 2
#define STEPS_REV (STEPS * MICROSTEPS)
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
float target_speed; // RPM
void setup_motor(void) {
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(ENA_PIN, OUTPUT);
pwm.begin();
pwm.setPWMFreq(1500);
pwm.setPWM(STEP_PIN, 0, 2048);
digitalWrite(ENA_PIN, HIGH);
}
void set_speed_rpm(float rpm) {
pwm.setPWMFreq((uint16_t)((rpm/60)*STEPS_REV));
#if DEBUG
Serial.print("set_speed_rmp: freq = ");
Serial.println((uint16_t)((rpm/60)*STEPS_REV));
#endif
}
void move_to(float dist) {
unsigned long init_time;
unsigned long expected_time;
expected_time = abs(dist)*1000*60./target_speed;
#if DEBUG
Serial.print("move_to: expected_time = ");
Serial.println(expected_time);
#endif
set_speed_rpm(target_speed);
digitalWrite(DIR_PIN, dist >= 0 ? HIGH : LOW);
digitalWrite(ENA_PIN, LOW);
init_time = millis();
while ((millis() - init_time) < expected_time);
digitalWrite(ENA_PIN, HIGH);
#if DEBUG
Serial.print("Time: ");
Serial.println(millis() - init_time);
#endif
}
void setup() {
Serial.begin(115200);
setup_motor();
}
void loop() {
target_speed = 50;
move_to(1.45);
delay(100);
move_to(-1.45);
delay(100);
}
Movimiento con aceleración
Este sketch mueve el motor aplicando aceleración y deceleración al inicio y al final de cada movimiento.
En lugar de calcular un tiempo de movimiento fijo, el programa recalcula la distancia recorrida a intervalos fijos de 30 ms. La función calculate_speed() devuelve la velocidad a la que debe girar el motor en cada momento, basada en los perfiles de aceleración y deceleración calculados al inicio por calculate_accel(). De este modo la velocidad se ajusta cada 30 ms. Según el valor de aceleración, el movimiento puede no ser perfectamente suave, ya que la velocidad puede variar significativamente en 30 ms debido al límite de frecuencia de actualización de los chips de expansión interna del ESP32 PLC.
Dentro del bucle principal, la distancia recorrida se actualiza cada 30 ms en función de la velocidad actual, lo que permite al programa rastrear la posición del motor y realizar movimientos de distancia fija.
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#define DEBUG 1
#define STEP_PIN Q0_0
#define DIR_PIN Q0_1
#define ENA_PIN Q0_2
#define STEPS 200
#define MICROSTEPS 2
#define STEPS_REV (STEPS * MICROSTEPS)
#define MIN_SPEED 24 // steps/s
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
float distance; // Revoluciones
float acceleration = 50000; // RPM^2
float target_speed; // RPM
void setup_motor(void) {
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(ENA_PIN, OUTPUT);
pwm.begin();
pwm.setPWMFreq(1500);
pwm.setPWM(STEP_PIN, 0, 2048);
digitalWrite(ENA_PIN, HIGH);
}
void set_speed_rpm(float rpm) {
pwm.setPWMFreq((rpm/60)*STEPS_REV);
#if DEBUG
Serial.print("set_speed_rmp: freq = ");
Serial.println((rpm/60)*STEPS_REV);
#endif
}
float calculate_accel_dist(void) {
float acel_time = target_speed / acceleration;
float acel_dist = 0.5 * acceleration * pow(acel_time, 2);
if ( distance < (acel_dist * 2)) {
target_speed = sqrt( acceleration * distance );
acel_time = target_speed / acceleration ;
acel_dist = 0.5 * acceleration *pow(acel_time, 2);
}
return acel_dist;
}
float calculate_speed(float current_pos, float accel_dist) {
float speed = MIN_SPEED;
float accel_step = accel_dist * STEPS_REV;
if (current_pos <= accel_step) {
speed = sqrt(2 * (acceleration * STEPS_REV / 3600) * current_pos);
}
else if ((distance * STEPS_REV - current_pos ) <= (accel_step)) {
speed = sqrt(2 * (acceleration * STEPS_REV / 3600) * (distance * STEPS_REV - current_pos));
}
else {
speed = target_speed * STEPS_REV / 60;
}
if (speed < MIN_SPEED) speed = MIN_SPEED;
return speed;
}
void move_to(float dist) {
unsigned long init_time;
unsigned long current_time;
distance = abs(dist);
float accel_dist = calculate_accel_dist();
float time = 0;
float current_pos = 0;
float final_pos = distance * STEPS_REV;
float current_speed = 0;
float new_speed;
float time_delay = 30;
digitalWrite(DIR_PIN, dist >= 0 ? HIGH : LOW);
digitalWrite(ENA_PIN, LOW);
init_time = millis();
while (current_pos < final_pos) {
current_time = millis() - init_time;
new_speed = calculate_speed(current_pos, accel_dist);
if (new_speed != current_speed) {
set_speed_rpm(new_speed * 60 / STEPS_REV);
current_speed = new_speed;
}
delay(time_delay);
time += time_delay;
current_pos = current_pos + (current_speed * time_delay / 1000);
}
digitalWrite(ENA_PIN, HIGH);
}
void setup() {
Serial.begin(115200);
setup_motor();
}
void loop() {
target_speed = 100;
move_to(1);
delay(10);
move_to(-1);
delay(10);
}

Control de motor paso a paso con un ESP32 PLC