Controlando un motor NEMA17 con ESP32 y MicroPython
Conectar un motor paso a paso (Stepper) a un microcontrolador usando MicroPython abre la puerta a infinidad de proyectos de robótica, CNC, impresoras 3D o automatización. En este...
Filter by Category
Filter by Author
Conectar un motor paso a paso (Stepper) a un microcontrolador usando MicroPython abre la puerta a infinidad de proyectos de robótica, CNC, impresoras 3D o automatización. En este...
Posted by JMT
Debugging and profiling are essential components of software development. They allow you to identify and correct errors and optimise code performance. In this guide, we will...
Posted by Juanmi Taboada
Proper management of secrets, such as passwords, API keys, and certificates, is crucial for system security. In distributed environments and cloud deployments, mishandling...
Posted by Juanmi Taboada
In the last few days, I have been looking for a 3D Printer to bring some of my inventions to life. After reading and comparing, I went for an Anycubic Kobra 2 Neo. It is a great...
Posted by Juanmi Taboada
Build your own None Cat Auth (none cat is authorized, a respectful repelling cats system). This is a hardware implementation to repel cats in a non-harmful way. It shouldn’t...
Posted by Juanmi Taboada
Precision vs. Creativity: Navigating the Landscape of AI Language Models in Problem-Solving In the ever-evolving landscape of AI language models, precision and creativity stand as...
Posted by Juanmi Taboada
This post contains the conclusions of the Alioli ROV Submarine Drone and shows images and videos of it in the water. I wrote it as a diary so anybody can understand that Alioli...
Posted by Juanmi Taboada
Examples of how to work with JSON, YAML, CSV, and XML files in Python. Today I saw myself preparing some exercises for my student who is learning Python programming language, and...
Posted by Juanmi Taboada
In this post, I describe how my own Arduino Framework for Alioli ROV Submarine Drone works. In my last post about Alioli ROV Submarine Drone, I wrote, “Learn how to Build an...
Posted by Juanmi Taboada
Here is how to make a SIMCOM 7600 module work on an Arduino board (Uno, Mega 2560, Leonardo, Due, Micro, Zero, Nano, Pico), ESP8266, ESP32, Raspberry Pi Pico, MicroPython, or any...
Posted by Juanmi Taboada
Conectar un motor paso a paso (Stepper) a un microcontrolador usando MicroPython abre la puerta a infinidad de proyectos de robótica, CNC, impresoras 3D o automatización. En este artículo te cuento los pasos que he seguido para hacer funcionar un NEMA17 con un ESP32 y un driver A4988, acompañado del diseño de mi propia placa de control.
Los motores paso a paso son un tipo especial de motor eléctrico que gira en pasos discretos en lugar de hacerlo de forma continua como un motor DC. Esto significa que podemos controlar con mucha precisión el ángulo de giro simplemente enviando impulsos de reloj (pulsos eléctricos). Cada pulso equivale a un paso.
Ventajas:
NEMA no es una marca, sino un estándar de dimensiones. El número indica el tamaño: en el caso del NEMA17, el frontal mide 1,7 pulgadas (~42 mm). Son los motores paso a paso más comunes en impresoras 3D y pequeños CNC. Suelen tener un par motor suficiente para mover ejes ligeros con correas o husillos.
Un stepper tiene dos bobinas independientes (A y B). Activando estas bobinas en secuencia, conseguimos que el rotor avance paso a paso.
Para no complicarnos con el control de corriente y la secuencia de fases, se usan drivers especializados como el A4988.
El A4988 es uno de los drivers más populares para steppers bipolares. Su función es muy sencilla:
Puntos clave de conexión:
Pinout del A4988:
Microstepping:
MS1 | MS2 | MS3 | Resolución Microstep |
---|---|---|---|
Bajo | Bajo | Bajo | 1 |
Alto | Bajo | Bajo | 1/2 |
Bajo | Alto | Bajo | 1/4 |
Alto | Alto | Bajo | 1/8 |
Alto | Alto | Alto | 1/16 |
Es importante tener en cuenta que estos pines tienen resistencias pull-down internas que los mantienen en BAJO por defecto. Esto significa que, si se dejan desconectados, el motor funcionará automáticamente en modo de paso completo.
Pines de control:
El pin STEP es la entrada de control principal para mover el motor. Cada vez que se envía una señal ALTA a este pin, el motor avanza un paso. La frecuencia de estos pulsos determina la velocidad de rotación del motor: los pulsos más rápidos resultan en una rotación más rápida.
El pin DIR controla la dirección de giro del motor. Al configurar este pin en ALTA, el motor gira en sentido horario, y al configurarlo en BAJA, gira en sentido antihorario.
Pines de salida al motor:
Normalmente, un motor paso a paso bipolar tiene dos pares de cables. Los pines 1A y 1B se conectan a la primera bobina del motor, mientras que los pines 2A y 2B se conectan a la segunda.
Uno de los puntos más importantes es la posición de los pines del cable que va desde la placa al motor, para ello es necesario “detectar” las 2 bobinas. Aquí tienes 3 métodos sencillos para identificarlas:
Una vez que identifiques ambos pares, conecta un par a 1A y 1B y el otro a 2A y 2B; no te preocupes por el orden.
He elegido un ESP32 por varias razones:
Este es mi diseño de la Microstepper Board que integra el ESP32, el A4988 y dos conectores para el motor NEMA17 (pines para pruebas y conector estándar XH2.54):
El resultado sería el que sigue:
Una vez montada la placa queda bastante compacta:
Con MicroPython podemos controlar el motor fácilmente, pero para obtener un movimiento fluido necesitamos generar pulsos regulares. Ahí entra el RMT del ESP32. El módulo RMT (Remote Control) del ESP32 está pensado para protocolos como IR o WS2812, pero es perfecto para mandar trenes de pulsos estables al pin STEP.
Código fuente del video: ( más ejemplos en https://github.com/juanmitaboada/microstepper )
from machine import Pin import esp32 import time # ------------------------- # Configuración hardware # ------------------------- en_pin = Pin(5, Pin.OUT, value=1) # 1 = deshabilitado al inicio dir_pin = Pin(16, Pin.OUT, value=0) step_pin = Pin(17, Pin.OUT, value=0) ms1 = Pin(27, Pin.OUT, value=0) ms2 = Pin(26, Pin.OUT, value=0) ms3 = Pin(25, Pin.OUT, value=0) # RMT: clock_div=80 -> 1 tick ~ 1 µs rmt = esp32.RMT(0, pin=step_pin, clock_div=80) # ------------------------- # Parámetros motor # ------------------------- MICROSTEP = 1 # 1 para full-step; si pones 2/4/8/16 adapta MS1..3 y este factor STEPS_PER_REV = 200 * MICROSTEP # ------------------------- # Estado # ------------------------- _net_pos = 0 # pasos acumulados (positivos = adelante, negativos = atrás) def enable(): en_pin.value(0) # ENABLE activo en bajo def disable(): step_pin.value(0) en_pin.value(1) def step_motor_rmt(steps, delay_us=1000, direction=1): """ Genera 'steps' pasos con RMT. Actualiza _net_pos para poder volver a 'home'. """ global _net_pos if steps <= 0: return step_pin.value(0) dir_pin.value(direction) time.sleep_us(20) # setup/hold de DIR antes del primer flanco pulses = [delay_us, delay_us] * steps rmt.write_pulses(pulses, 0) # start en bajo -> primer evento es flanco de subida step_pin.value(0) _net_pos += steps if direction == 1 else -steps def clock_sweep(ticks, interval_s, direction=1, delay_us=800): """ Reparte exactamente STEPS_PER_REV pasos en 'ticks' saltos, distribuyendo el resto de forma uniforme (sin perder pasos). """ base = STEPS_PER_REV // ticks # pasos mínimos por tick rem = STEPS_PER_REV % ticks # resto a repartir err = 0 for _ in range(ticks): steps = base err += rem if err >= ticks: steps += 1 err -= ticks step_motor_rmt(steps, delay_us=delay_us, direction=direction) time.sleep(interval_s) def go_home(delay_us=800): """Vuelve a la posición original compensando la desviación acumulada.""" global _net_pos if _net_pos == 0: return if _net_pos > 0: # nos hemos ido adelante -> volver atrás step_motor_rmt(_net_pos, delay_us=delay_us, direction=0) else: # nos hemos ido atrás -> volver adelante step_motor_rmt(-_net_pos, delay_us=delay_us, direction=1) # ------------------------- # Demo básica # ------------------------- try: enable() # Full-step por defecto ms1.value(0); ms2.value(0); ms3.value(0) # 1 vuelta completa hacia adelante step_motor_rmt(STEPS_PER_REV, delay_us=1000, direction=1) time.sleep(1) # 1 vuelta completa hacia atrás step_motor_rmt(STEPS_PER_REV, delay_us=1000, direction=0) time.sleep(1) # 1/2 vuelta adelante step_motor_rmt(STEPS_PER_REV // 2, delay_us=1000, direction=1) time.sleep(1) # 1/2 vuelta atrás step_motor_rmt(STEPS_PER_REV // 2, delay_us=1000, direction=0) time.sleep(1) # 1/4 vuelta adelante step_motor_rmt(STEPS_PER_REV // 4, delay_us=1000, direction=1) time.sleep(1) # 1/4 vuelta atrás step_motor_rmt(STEPS_PER_REV // 4, delay_us=1000, direction=0) time.sleep(1) # 1 vuelta (360º) en 12 pasos adelante (1 salto/segundo) -> SIN perder pasos clock_sweep(ticks=12, interval_s=1.0, direction=1, delay_us=800) time.sleep(1) # 1 vuelta (360º) en 60 pasos atrás (1 salto/0.1 s) -> SIN perder pasos clock_sweep(ticks=60, interval_s=0.1, direction=0, delay_us=800) time.sleep(1) # Siempre volver a la posición original go_home(delay_us=800) finally: disable() def ramp_motion(total_steps, accel_steps, cruise_steps, delay_us_start=2000, delay_us_min=500, direction=1): """ Movimiento con rampa de aceleración + velocidad constante + desaceleración. - total_steps: número total de pasos a cubrir - accel_steps: pasos dedicados a acelerar - cruise_steps: pasos dedicados a velocidad constante - delay_us_start: retardo inicial (velocidad baja) - delay_us_min: retardo mínimo (velocidad máxima) """ global _net_pos # Calcular pasos de aceleración y deceleración accel_range = delay_us_start - delay_us_min step_decrement = accel_range / accel_steps # Aceleración current_delay = delay_us_start for _ in range(accel_steps): step_motor_rmt(1, delay_us=int(current_delay), direction=direction) current_delay -= step_decrement # Crucero for _ in range(cruise_steps): step_motor_rmt(1, delay_us=delay_us_min, direction=direction) # Deceleración current_delay = delay_us_min for _ in range(accel_steps): current_delay += step_decrement step_motor_rmt(1, delay_us=int(current_delay), direction=direction) # ---------------------- # Demo con rampas # ---------------------- try: enable() print("Rampa trapezoidal: 2 vueltas acelerando, 5 vueltas constantes, 2 vueltas decelerando") # 2 vueltas acelerando (200*2 = 400 pasos) # 5 vueltas constantes (1000 pasos) # 2 vueltas decelerando (400 pasos) ramp_motion( total_steps=1800, # total (para referencia) accel_steps=STEPS_PER_REV * 2, cruise_steps=STEPS_PER_REV * 5, delay_us_start=2000, # arranque lento delay_us_min=500, # velocidad máxima direction=1 ) finally: disable() print("Secuencia terminada.")
Una forma de maximizar el rendimiento de un motor paso a paso es suministrarle un voltaje superior al nominal. En particular, usar un voltaje más alto generalmente permite velocidades de paso más altas y un mayor par de paso.
Ejemplo: supongamos que utilizamos un motor paso a paso con una corriente nominal máxima de 1 A y una resistencia de bobina de 5 Ω. Esto indica una alimentación máxima del motor de 5 V (1 A × 5 Ω = 5 V). Usar un motor de este tipo con 12 V permitiría velocidades de paso más altas, pero el problema es que, si se conectan 12 V directamente, el motor intentará consumir demasiada corriente, muy por encima del límite seguro de 1 A. Cuando esto sucede, las bobinas internas del motor pueden sobrecalentarse, lo que puede dañarlo permanentemente.
Para usar de forma segura un voltaje superior al voltaje nominal del motor, es necesario limitar la corriente que fluye a través de las bobinas del motor. Precisamente por eso, el controlador de motor paso a paso A4988 incluye un pequeño potenciómetro limitador de corriente. Este permite ajustar la corriente máxima que fluye a través de las bobinas del motor.
Al ajustar el potenciómetro correctamente, se asegura de que el motor nunca consuma más corriente de la que puede manejar con seguridad, lo que ayuda a prevenir el sobrecalentamiento y lo mantiene seguro.
Para ajustar el límite de corriente en el A4988, activaremos el controlador en modo de paso completo y se usará un multímetro para medir directamente la corriente que fluye a través de una de las bobinas mientras se ajusta el potenciómetro limitador de corriente.
Primero, consulte la documentación de su motor paso a paso para conocer su corriente nominal. Por ejemplo, supongamos que utiliza un motor paso a paso NEMA 17 con 200 pasos por revolución y una potencia nominal de 1,5 A por bobina.
En el programa MicroPython ponga los pines STEP y DIR en ALTA, esto mantiene el motor energizado y en una posición fija. Establece MS1, MS2 y MS3 en BAJA para configurar el controlador en modo paso completo.
A continuación, coloca el amperímetro (o el multímetro en modo corriente) en serie con una de las bobinas del motor para que la corriente fluya desde el amperímetro hasta la bobina. Para ello, desconecta un cable del terminal 1A o 1B del controlador. Conecta una sonda del amperímetro a ese terminal y la otra al cable suelto del motor.
Con todo conectado y encendido, deberías ver una lectura de corriente en el multímetro. Ahora, gira con cuidado el potenciómetro de límite de corriente con un destornillador pequeño mientras observas la lectura del multímetro. Ajústalo hasta que la corriente coincida con el valor nominal del motor, en este caso 1,5 A.
Tenga en cuenta que la corriente que está midiendo es solo el 70% del límite de corriente real configurado, ya que ambas bobinas están siempre encendidas y limitadas a este valor en el modo de paso completo, por lo que si luego habilita los modos de micropasos, la corriente a través de las bobinas podrá superar esta corriente de paso completo medida en un 40% (1/0,7) en ciertos pasos; tenga esto en cuenta cuando utilice este método para establecer el límite de corriente.
Recuerda que el objetivo principal de establecer un límite de corriente es proteger tu motor paso a paso. Hacer funcionar un motor con demasiada corriente puede generar más par a corto plazo, pero también provoca que se caliente más, lo que puede acortar su vida útil. Por otro lado, si configuras la corriente demasiado baja, el motor no se dañará, pero podría no tener suficiente potencia para realizar su función correctamente. La clave está en encontrar el punto óptimo donde tu motor tenga suficiente potencia para realizar sus tareas sin calentarse y con una larga vida útil.
El A4988 simplifica enormemente el control de un motor NEMA17.
El ESP32 con MicroPython y el RMT permiten generar pulsos muy precisos sin preocuparnos por temporizadores manuales.
Ahora ya puedes desarrollar sistemas más complejos como impresoras 3D, CNC, actuadores lineales o robots controlados por WiFi.
In my last post, “Underwater Alioli ROV“, I shared all the information I got from the Internet to build my Underwater ROV. In this post, I will explain how I made the...
In my last post, “Finishing the frame for an Underwater ROV”, I gave all details about the design I used to build the frame for Alioli Underwater ROV. In this post, I...