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_US
que establece la velocidad de movimiento.STEPS_PER_ANALOG_VALUE
que 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 10
enum
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:
DEADZONE
que establece el rango del potenciómetro central donde el motor está en marcha lenta.MIN_DRIVER_PULSE_PERIOD_US
que establece la velocidad máxima.MAX_DRIVER_PULSE_PERIOD_US
que 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 speed
enum
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.