Arduino como controlador de motor paso a paso
Usar Arduino para controlar motor paso a paso
Hay un par de maneras de realizar este proyecto con Arduino, una es usar un encoder rotativo para mover el motor paso a paso. En este tutorial cambiaremos el encoder rotativo por un potenciómetro ordinario y lo usaremos para controlar la posición o la velocidad del motor paso a paso. Usaremos un código que se puede usar aunque se realice de la otra manera y haremos algunas alteraciones para lograr el comportamiento deseado. El objetivo de este artículo es no utilizar librerías ni shields.
Esta entrada de blog contendrá principalmente dos ejemplos de código con alguna explicación. Debes ser consciente de cómo funcionan los potenciómetros antes de leer este post. Ya escribimos una entrada en el blog sobre este tema, donde también analizamos el filtrado de firmware, que puede ser útil en este tipo de aplicaciones.
Posición
Veamos cómo podemos usar el potenciómetro para controlar la posición del motor paso a paso.
Aquí tenemos dos parámetros importantes:
DRIVER_PULSE_PERIOD_USque establece la velocidad de movimiento.STEPS_PER_ANALOG_VALUEque fija esencialmente el rango de movimiento.
Este código no realiza ninguna calibración, sino que asume que tanto el motor como el potenciómetro están centrados en el arranque del sistema. El código también asume un ADC de 10 bits (1024 valores analógicos).
#define DRIVER_STEP_PIN 10#define DRIVER_DIR_PIN 9#define DRIVER_EN_PIN 8#define POT_PIN A0#define DRIVER_PULSE_PERIOD_US 1600#define STEPS_PER_ANALOG_VALUE 10enum Driver_pulse_state_enum {PULSE_IDLE, PULSE_HIGH, PULSE_LOW};unsigned long time_now = 0;uint16_t driver_pulse_hold_time_us = DRIVER_PULSE_PERIOD_US/2;uint8_t driver_pulse_state = PULSE_IDLE;uint16_t target_pos = 512*STEPS_PER_ANALOG_VALUE;uint16_t actual_pos = 512*STEPS_PER_ANALOG_VALUE;int pos_error = 0;void setup() {pinMode(DRIVER_STEP_PIN, OUTPUT);pinMode(DRIVER_DIR_PIN, OUTPUT);pinMode(DRIVER_EN_PIN, OUTPUT);digitalWrite(DRIVER_EN_PIN, HIGH);}void loop() {target_pos = analogRead(POT_PIN)*STEPS_PER_ANALOG_VALUE;pos_error = target_pos - actual_pos;if((pos_error) && (driver_pulse_state == PULSE_IDLE)){write_pulse_high();}if((micros() - time_now > driver_pulse_hold_time_us) && (driver_pulse_state == PULSE_LOW)){write_pulse_high();}if((micros() - time_now > driver_pulse_hold_time_us) && (driver_pulse_state == PULSE_HIGH)){write_pulse_low();}}void write_pulse_high(void){driver_pulse_state = PULSE_HIGH;if(pos_error > 0){digitalWrite(DRIVER_DIR_PIN, HIGH);actual_pos += 1;}else if(pos_error < 0){digitalWrite(DRIVER_DIR_PIN, LOW);actual_pos -= 1;}digitalWrite(DRIVER_STEP_PIN, HIGH);time_now = micros();}void write_pulse_low(void){digitalWrite(DRIVER_STEP_PIN, LOW);time_now = micros();if(pos_error){driver_pulse_state = PULSE_LOW;}else{driver_pulse_state = PULSE_IDLE;}}Velocidad
Este enfoque es un poco diferente. Aquí es posible hacer funcionar el motor una cantidad infinita en ambos sentidos. El potenciómetro controla la velocidad de marcha del motor y en qué dirección. Cuando el pote está centrado, el motor no se mueve.
Los tres parámetros importantes de este código son:
DEADZONEque establece el rango del potenciómetro central donde el motor está en marcha lenta.MIN_DRIVER_PULSE_PERIOD_USque establece la velocidad máxima.MAX_DRIVER_PULSE_PERIOD_USque establece la velocidad mínima.
#define DRIVER_STEP_PIN 10#define DRIVER_DIR_PIN 9#define DRIVER_EN_PIN 8#define POT_PIN A0#define DEADZONE 50#define MIN_DRIVER_PULSE_PERIOD_US 1000 //max speed#define MAX_DRIVER_PULSE_PERIOD_US 3000 //min speedenum Driver_pulse_state_enum {PULSE_IDLE, PULSE_HIGH, PULSE_LOW};unsigned long time_now = 0;uint16_t driver_pulse_hold_time_us = MIN_DRIVER_PULSE_PERIOD_US/2;uint8_t driver_pulse_state = PULSE_IDLE;int normalized_analog_value = 0;uint8_t idle_flag = 1;void setup() { pinMode(DRIVER_STEP_PIN, OUTPUT); pinMode(DRIVER_DIR_PIN, OUTPUT); pinMode(DRIVER_EN_PIN, OUTPUT); digitalWrite(DRIVER_EN_PIN, HIGH);}void loop() { normalized_analog_value = analogRead(POT_PIN) - 512; if(abs(normalized_analog_value)-DEADZONE < 0){ idle_flag = 1; } else{ idle_flag = 0; } driver_pulse_hold_time_us = map(abs(normalized_analog_value), DEADZONE, 512, MAX_DRIVER_PULSE_PERIOD_US, MIN_DRIVER_PULSE_PERIOD_US)/2; if(!idle_flag && driver_pulse_state == PULSE_IDLE){ write_pulse_high(); } if((micros() - time_now > driver_pulse_hold_time_us) && (driver_pulse_state == PULSE_LOW)){ write_pulse_high(); } if((micros() - time_now > driver_pulse_hold_time_us) && (driver_pulse_state == PULSE_HIGH)){ write_pulse_low(); }}void write_pulse_high(void){ driver_pulse_state = PULSE_HIGH; if(normalized_analog_value > 0){ digitalWrite(DRIVER_DIR_PIN, HIGH); } else if(normalized_analog_value < 0){ digitalWrite(DRIVER_DIR_PIN, LOW); } digitalWrite(DRIVER_STEP_PIN, HIGH); time_now = micros();}void write_pulse_low(void){ digitalWrite(DRIVER_STEP_PIN, LOW); time_now = micros(); if(!idle_flag){ driver_pulse_state = PULSE_LOW; } else{ driver_pulse_state = PULSE_IDLE; }}Conclusiones
El filtrado de paso bajo de la señal analógica es casi crucial en este tipo de configuración, especialmente para el código de control de posición. Esto puede hacerse en hardware o en firmware (por ejemplo, utilizando un filtro de media móvil exponencial). Todavía no tenemos ninguna forma de aceleración en nuestro control de movimiento, por lo que los cambios de velocidad demasiado altos provocarán la pérdida de pasos.
El propósito de estos ejemplos de código es servir de inspiración para otros que están haciendo un proyecto que involucra el control de motores paso a paso. Hay muchas maneras de hacer esto, y probablemente sería mejor usar librerías de control de paso a paso y/o shields o módulos para un movimiento y control más seguro, más robusto y suave.
