Saltar al contenido
Control Automático Educación

Motor DC con Encoder – Velocidad – Posición

En esta entrada vamos a aprender como funciona el Motor DC con Encoder el cual nos va a permitir saber el sentido de giro del motor, conocer la posición en la que se desplaza el motor y finalmente la velocidad con la que gira el motor. Esto lo podemos acoplar a cualquier sistema embebido, como por ejemplo nuestra placa de desarrollo del Arduino.

Antes de comenzar, te invito para que le des un vistazo a nuestro CURSO GRATUITO DE ARDUINO DESDE CERO.

Y también para que te suscríbas al canal, si te gusta la programación de dispositivos embebidos, la programación y la teoría del control.

Lista de Materiales

  • Placa de Desarrollo de Arduino
  • Motor DC con Encoder de 5v o 12v
  • Driver de Potencia Puente H (L298)
  • Potenciometro 5k (o cualquiera)
  • 2 Pulsadores
  • Cables
  • Fuente de Alimentción de 5v o 12v dependiendo del motor empleado.

Como funciona un encoder de cuadratura de Efecto Hall

Un Encoder (codificador) funciona a través de la detección de los cambios en el campo magnético creado por un imán conectado al eje del motor. A medida que el motor gira, las salidas del Encoder se dispararán periódicamente.

Generalmente contamos con 2 sensores que transforman el giro del motor (trasductor) a 2 señales cuadradas que presentan un desfase de 90°. Gracias a este desfase, a estos codificadores se les denomina como encoders en cuadratura, ocupando un cuadrante del círculo de 360°

Cuando el imán gira en el sentido de las agujas del reloj, la salida del sensor A se activará primero. Cuando se gira en sentido antihorario, por otro lado, la salida del sensor B se activará primero

Este efecto lo podemos ver facilmente en nuestro Arduino, para eso implementamos el siguiente circuito.

Motor DC Encoder Cuadratura

En el circuito anterior se esta empleando un módulo Shield L298, se puede usar cualquier otro módulo o montar directamente el circuito.

El L298 es un controlador de puente completo dual que tiene un amplio rango de voltaje operativo y puede manejar corrientes de carga de hasta 3A. El IC también cuenta con voltaje de saturación bajo y protección contra sobretemperatura.
En el circuito, el diodo D1 a D4 son diodos de protección. El condensador C2 es el filtro de fuente de alimentación lógica y el condensador C2 es el filtro de tensión de alimentación. El estado del motor dependerá del nivel lógico de los pines 10, 11, 12 y se describe en la tabla que se muestra debajo del esquema del circuito.

Circuito para motor

El código en Arduino es:

//***************************************************//
//***************************************************//
//*****   Posición y Velocidad de Motor DC      *****//
//*****                                         *****//
//***** by: Sergio Andres Castaño Giraldo       *****//
//***** https://controlautomaticoeducacion.com/ *****//
//*****                                         *****//
//***************************************************//
//***************************************************//

#define ENCODER_A       2 // Amarillo
#define ENCODER_B       3 // Verde
#define BUTTON_FORWARD  4
#define BUTTON_BACKWARD 5

// Pines de Control Shield
const int E1Pin = 10;
const int M1Pin = 12;
const int E2Pin = 11;
const int M2Pin = 13;

typedef struct{
  byte enPin;
  byte directionPin;
}Motor;

//Creo el motor
const Motor motor = {E1Pin, M1Pin};

const int Forward = LOW;
const int Backward = HIGH;

void setup(){
  Serial.begin(9600);
  //Encoders como entradas
  pinMode(ENCODER_A, INPUT);
  pinMode(ENCODER_B, INPUT);
  //Pulsadores
  pinMode(BUTTON_FORWARD, INPUT_PULLUP);
  pinMode(BUTTON_BACKWARD, INPUT_PULLUP);
  //Configura Motor
  pinMode(motor.enPin, OUTPUT);
  pinMode(motor.directionPin, OUTPUT);
}

void loop(){
  // Pulsador hacia adelante
  if(! digitalRead(BUTTON_FORWARD)){
    digitalWrite(motor.directionPin, Forward);
    digitalWrite(motor.enPin, HIGH);
    imprimir_cuadratura();
  }
  else if(! digitalRead(BUTTON_BACKWARD)){
    digitalWrite(motor.directionPin, Backward);
    digitalWrite(motor.enPin, HIGH);
    imprimir_cuadratura();
  }
  else{
    digitalWrite(motor.enPin, LOW);
  }
}

void imprimir_cuadratura(){
  int a = digitalRead(ENCODER_A);
  int b = digitalRead(ENCODER_B);
  Serial.print(a*5);
  Serial.print(" ");
  Serial.println(b*5);
}

La señal de cuadratura observada por el serial plotter:

Interrupciones con microcontrolador PIC

Interrupciones PIC

Medir la velocidad y posición motor DC

El hecho de disponer el encoder acoplado al rotor del motor de corriente continua, nos brinda la posibilidad de poder medir su posición y velocidad para ser empleado en muchas aplicaciones de robótica.

Para esta práctica, vamos a modificar levemente el circuito presentado anteriormente, donde solo dejaremos un solo pulsador y agregaremos un potenciometro.

Motor DC Velocidad y Posición

De esta manera con el pulsador podemos seleccionar el modo de operación: Modo de regulación de velocidad, o modo de posición. Ambos controlados por la lectura ADC con Arduino del Potenciometro.

Medición de posición

Para la lectura de la posición, vamos a tener que recurrir a las interrupciones con Arduino, donde escogeremos el Sensor 1 (A) para que active la interrupción con el flanco de subida. En la interrupción se incrementa una variable que llamamos theta (si la salida del encoder B es alta) o se resta uno (si la salida del encoder B es baja).

Para garantizar que la variable theta se almacene de modo que pueda ser leída con precisión por las funciones de loop y de interrupción, debe utilizar el calificador volátil . 

Además, se necesita una macro ATOMIC_BLOCK para acceder a la variable de posición. La macro ATOMIC_BLOCK evita que la interrupción cambie parte de la variable theta mientras se está leyendo. 

Finalmente se establece una velocidad fija en el motor DC, con un PWM de 200, y se pregunta por la lectura ADC del potenciometro para poder ejercer un control aproximado de la posición del motor. Este control se logra con dos condicionales donde mantenemos una tolerancia de más o menos 2 grados, con el fin que el rotor pueda detenerse en ese pequeño umbral o threshold.

Medición de velocidad

La medición de velocidad es muy similar a la de posición, solo que en este caso, se debe configurar el arduino para que quede capturando los datos de cualquiera de los dos sensores del encoder (A o B) por un periodo de tiempo que sea conocido, para realizar posteriormente el cálculo de la velocidad.

Como ya se está leyendo la interrupción con el sensor 1 (A) lo que hacemos es incrementar una variable entera volatil llamada pulsos.

Dentro del void loop, haremos uso de la función millis() del Arduino, para contabilizar de una forma precisa 1 segundo.

Una vez ha transcurrido 1 segundo, entramos en el condicional y dentro de la macro ATOMIC calculamos las RPM del motor con base a 1 segundo, empleando la resolución del encoder (Numero de pulsos por giro) en este caso es de 374.22. Actualizamos la variable timeold para que millis() vuelva y contabilice nuevamente 1 segundo y ceramos el contador pulsos para realizar una nueva medición.

Código de Arduino

El siguiente será el código que vamos a implementar para esta práctica y el cual nos será muy útil para otros proyectos que haremos posteriormente en el sitio web, por lo tanto es importante que lo entiendan a la perfección.

//***************************************************//
//***************************************************//
//*****   Posición y Velocidad de Motor DC      *****//
//*****                                         *****//
//***** by: Sergio Andres Castaño Giraldo       *****//
//***** https://controlautomaticoeducacion.com/ *****//
//*****                                         *****//
//***************************************************//
//***************************************************//

// this library includes the ATOMIC_BLOCK macro.
#include <util/atomic.h>

#define ENCODER_A       2 // Amarillo
#define ENCODER_B       3 // Verde
#define BUTTON_MOD      4


// Pin del Potenciometro
const int pot = A0;

// Pines de Control Shield
const int E1Pin = 10;
const int M1Pin = 12;
const int E2Pin = 11;
const int M2Pin = 13;

//Variable global de posición compartida con la interrupción
volatile int theta = 0;

//Variable global de pulsos compartida con la interrupción
volatile int pulsos = 0;
unsigned long timeold;
float resolution = 374.22;

//Variable Global Velocidad
int vel = 0;

//Variable Global Posicion
int ang = 0;

//Variable Global MODO
bool modo = false;

//Estructura del Motor
typedef struct{
  byte enPin;
  byte directionPin;
}Motor;

//Creo el motor
const Motor motor = {E1Pin, M1Pin};

//Constantes de dirección del Motor
const int Forward = LOW;
const int Backward = HIGH;

void setup(){
  // set timer 1 divisor to  1024 for PWM frequency of 30.64 Hz
  TCCR1B = TCCR1B & B11111000 | B00000101;
  Serial.begin(9600);
  //Encoders como entradas
  pinMode(ENCODER_A, INPUT);
  pinMode(ENCODER_B, INPUT);
  //Pulsadores
  pinMode(BUTTON_MOD, INPUT_PULLUP);
  //Configura Motor
  pinMode(motor.enPin, OUTPUT);
  pinMode(motor.directionPin, OUTPUT);
  //Configurar Interrupción
  timeold = 0;
  attachInterrupt(digitalPinToInterrupt(ENCODER_A),leerEncoder,RISING);
}

void loop(){
  float posicion;
  float rpm;
  int value,dir=true;

  //Lee el Valore del Potenciometro
  value = analogRead(pot);
  
  //Cambia de Modo Velociadad o Posición
  if(debounce(BUTTON_MOD)){
    modo = !modo;
    theta = 0;
  }

  if(modo){
    //Transforma el valor del Pot a velocidad
    vel = map(value,0,1023,0,255);

    //Activa el motor dirección Forward con la velocidad
    setMotor(motor, vel, false);

    //Espera un segundo para el calculo de las RPM
    if (millis() - timeold >= 1000)
    {
      //Modifica las variables de la interrupción forma atómica
      ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
        //rpm = float(pulsos * 60.0 / 374.22); //RPM
        rpm = float((60.0 * 1000.0 / resolution ) / (millis() - timeold) * pulsos);
        timeold = millis();
        pulsos = 0;
      }
      Serial.print("RPM: ");
      Serial.println(rpm);
      Serial.print("PWM: ");
      Serial.println(vel);
    }
  }
  else{
    //Transforma el valor del Pot a ángulo
    ang = map(value,0,1023,0,360);  

    //Modifica las variables de la interrupción forma atómica
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
      posicion = (float(theta * 360.0 /resolution));
    }

    //Posiciona el ángulo con tolerancia +- 2
    if(ang > posicion+2){
      vel = 200;
      dir = true;
    }
    else if(ang < posicion-2){
      vel = 200;
      dir = false;
    }
    else{
      vel = 0;
    }
    setMotor(motor, vel, dir);
  }
}

//Función para dirección y velocidad del Motor
void setMotor(const Motor motor, int vel, bool dir){
  analogWrite(motor.enPin, vel);
  if(dir)
    digitalWrite(motor.directionPin, Forward);
  else
    digitalWrite(motor.directionPin, Backward);
}

//Función anti-rebote
bool debounce(byte input){
  bool state = false;
  if(! digitalRead(input)){
    delay(200);
    while(! digitalRead(input));
    delay(200);
    state = true;
  }      
  return state;   
}

//Función para la lectura del encoder
void leerEncoder(){
  //Lectura de Velocidad
  if(modo)
    pulsos++; //Incrementa una revolución
    
  //Lectura de Posición  
  else{
    int b = digitalRead(ENCODER_B);
    if(b > 0){
      //Incremento variable global
      theta++;
    }
    else{
      //Decremento variable global
      theta--;
    }
  }
}

Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:

👉 Invitar a Sergio a un Café ☕️

Que esten muy bien, nos vemos en la siguiente entrada.

Entradas relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Comentarios (18)

Como hago la conexion con el driver l298n?

Responder

Se Puede sustituir el modulo por un L293d

Responder

Si se puede.

Responder

que programa utilizaste para simular los componentes?

Responder

Uso el Proteus principalmente

Responder

Estupendo tutorial. Muy claro e instructivo.
He visto que en tu web hay varios tutoriales PID, ¿has publicado este mismo codigo de para arduino pero incorporando el algoritmo PID para actuar sobre el setMotor de forma que se mantenga la velocidad constante?
Muchas gracias

Responder

Buenas noches señor Sergio Andres Castaño Giraldo,tengo una pregunta .
En el codigo de hallar velocidad del motor DC con encoder,hay una variable llamada resolution (resolucion) ¿que es resolution?¿porque la variable resolution=374.22? ¿De donde se obtendira el valor de la variable resolution?¿Es una caracteristica del motor que se encuentra en el datashhet?¿En que parte del datasheet se encontraria?

//rpm = float(pulsos * 60.0 / 374.22); //RPM
rpm = float((60.0 * 1000.0 / resolution ) / (millis() – timeold) * pulsos);

Responder

Hola Ronaldo, eso esta explicado ahi arriba antes del código, esa variable es la resolución del encoder (Numero de pulsos por giro) en este caso es de 374.22. Ese valor te lo dá el datasheet del motor. Saludos.

Responder

Buenos días señor Sergio Andres Castaño Giraldo
Tengo una inquietud
¿Donde se puede conseguir la librería ?
En el Arduino no viene incluida la librería necesaria para el control de posición y velocidad del motor DC
¿Usted me podría responder esta pregunta?,por favor señor Sergio ,he buscado en muchos lugares y no encuentro la libreria

Responder

Realmente desconozco si existe una librería. De igual forma, en esta entrada mostramos como manipular el motor bien sea por velocidad o por posición, con esto puedes implementar un controlador PID para regular alguna de estas dos variables. Acá en el sitio web tenemos varias entradas sobre el controlador PID. Saludos.

Responder

La libreria que uso no es ATOMIC BLOCK sino la encuentro en el Arduino una libreria parecida que se puede descargar desde el Arduino llamada SIMPLY ATOMIC

Responder

Cómo se sustituye la shield por el módulo?

Responder

Venden módulo que no necesariamente son Shields. O también puedes montar tu propio circuito, en el post coloque un circuito mostando como realizarlo.

Responder

Cómo se sustituye la shield por el módulo?

Responder

Muchas gracias ingeniero Sergio

Responder

De nada Luis, que bueno que te ha gustado. Gracias por comentar. Saludos!

Responder

Muchas gracias, esta buenísimo todo el proceso.

Responder

De nada Adeluna100, Saludos!!

Responder