domingo, 28 de agosto de 2016

Encoder de cuadratura y Arduino. Tutorial sobre un motor con un encoder de cuadratura y Arduino.

En este tutorial voy a mostrar y estudiar un motor con un encoder de cuadratura o encoder incremental. Este motor con un encoder ya integrado se ha implementado en el Robot Andromina OFF ROAD. De esta manera podemos calcular la velocidad de giro de las 4 ruedas del robot y el sentido de giro de estas, independientemente en cada motor. Este encoder tiene muy alta resolución por vuelta de la rueda. Al final de esta entrada hay un pequeño "sketck" de Arduino para calcular la velocidad de las ruedas del robot. Estos motores son perfectos para implementar un control PID de bucle cerrado o un control de velocidad del motor por PWM con Arduino MEGA.
En la foto siguiente se puede ver el encoder en el motor, integrados en una rueda de las 4 ruedas del robot:
Motor con encoder de cuadratura o encoder incremental 
Vista del encoder integrado en el motor y la rueda del robot.
A continuación vista frontal del robot, con las dos ruedas giradas. donde se pueden ver los dos motores delanteros con los dos encoders cuadráticos.
Robot con 4 encoders de cuadratura
Vista del robot con la ruedas giradas, donde se puede ver los dos encoders delanteros.
1-El motor : Es uno micro motor de Corriente Continua con caja de engranajes y un encoder de cuadratura acoplado al eje del motor (Marca DF ROBOT y modelo "Micro DC Motor 120:1" con un Encoder-SJ01). Es un motor con una caja reductora con una relación de reducción de i =120:1 y obtenemos unas 160 Revoluciones Por Minuto, a 6 voltios (Voltaje nominal).
Con una placa Arduino y una placa de control de motores se puede implementar un control PID de bucle cerrado para controlar la velocidad del motor. Este motor es una opción ideal para cualquier tipo de robot y también para el robot Andromina OFF ROAD.

Eje de metal
Eje de metal del motor, con rosca de  M-3, para atornillar la rueda al motor.
Tal y como puede verse en la foto superior, el eje de salida (donde se acopla la rueda) es de cobre y tiene una rosca de M-3. Este eje has sido mejorado para lograr un eje muy resistente, al ser de metal. Esto mejora mucho la unión entre la rueda y el eje de salida del motor. También incluye dos conectores eléctricos reforzados para la alimentación del motor y para la conexión del encoder. Un conector de dos cables para el motor y un conecto de 4 cables para el encoder.
Encoder de cuadratura y su conector eléctrico
Foto de los dos conectores del encoder y de alimentación del motor .
En la gráfica siguiente he hecho un estudio del motor. He ido variando el voltaje de alimentación del motor y he obtenido las RPM según el voltaje de alimentación. Esta prueba se ha realizado sin carga en la rueda. Como puede verse es una gráfica lineal. Como puede observarse a 3 voltios tenemos 80 rpm. Todas las páginas Webs que hablan sobre este motor comenten el mismo error, dicen que a 3 voltios tendría que dar 60 rpm y realmente da 80 rpm
Gráfica del micro motor con reducción i = 120:1
Mas información sobre el Micro DC Motor with Encoder-SJ01_SKU:_FIT0450

2-Especificaciones técnicas del motor :
  • Relación de transmisión i = 120:1
  • Velocidad de rotación si carga a 6V = 160 rpm
  • Velocidad de rotación si carga a 3V =  80 rpm
  • Consumo sin carga a  a 6V = 0,17 Amp
  • Consumo sin carga a  a 3V = 0,14 Amp
  • Consumo máximo de bloqueo = 2,8 Amp.
  • Par máximo de bloqueo = 0,8 kgf.cm
  • Par nominal =  0,2 kgf.cm
  • Tensión de funcionamiento del motor = 3~7.5V (Tensión nominal 6V)
  • Voltaje de funcionamiento del encoder: 4.5 ~ 7.5V (Tensión nominal 5V)
  • Temperatura ambiente de funcionamiento: -10 ~ + 60 ℃
  • Peso del motor: 50 gramos
3-El encoder de cuadratura : Este encoder tiene dos sensores de efecto Hall que crean los pulsos digitales gracias a un disco magnético giratorio, montado en el eje trasero del motor. En la foto anterior puede verse este disco magnético en color negro.
En este ejemplo tenemos una señal con dos pulsos por vuelta.
Principio básico de un sensor d efecto Hall.
Estos dos sensores se van activando y desactivando en una secuencia que nos permite saber la dirección y el numero de desplazamientos que han ocurrido en el encoder. En la imagen siguiente también se puede ver como es la secuencia de activaciones dependiendo el sentido de giro. De hecho este tipo de secuencia esta en código Gray de dos bits (cosa muy útil para la detección de errores). 
Esquema de pulsos de un encoder incremental
Esquema de los pulsos del ecnoder incremental.
Gracias a este código podemos saber la dirección del giro que toma el encoder, para ilustrarlo mejor, veámoslo en forma de matriz siguiente:
Tabla de un encoder incremental
Matriz de un encoder incremental.
Como podemos intuir si asumimos la salida “A” como el bit menos significativo y la salida “B” como el bit mas significativo, en la tabla de verdad podemos saber la dirección de giro, si comparamos la posición actual con la posición anterior.
En la imagen anterior, también se ve una matriz a la que llamaremos “Matriz de incrementos” donde podemos comparar ese estado previo con el actual y saber como fue el desplazamiento. Por ejemplo si el estado previo fue “01” y el estado actual del encoder es “00” (2 en binario) podemos saber que el encoder estaba “girando” en sentido anti-horario, ya que la coordenada (01,00) (primero se avanza verticalmente y después horizontal) resulta en un “-1”, o bien si el estado previo era “10” y el actual es “00”, en la matriz resulta “+1” que indica sentido horario. Como ven esta matriz es muy útil para saber en pocos pasos que ocurre con el encoder. Esta matriz también nos puede servir para detectar errores de nuestra señal o del encoder. Ya que hay 4 combinaciones de coordenadas que no pueden aparecer en nuestra señal, como por ejemplo la combinación(11,00), marcada con una E en la matriz. 

Los dos sensores cambian de estado (de mayor a menor o de menor a mayor) 8 veces por cada revolución del eje del motor. Por lo tanto tenemos dos canales, el A y el B o el 1 y el 2. Cada uno proporciona 8 pulsos por vuelta del motor, obteniendo una resolución de 16 pulsos por cada revolución del motor. Lo que nos da 16 pulsos x 120 = 1920 pulsos por cada vuelta de la rueda. Como puede verse esta combinación de motor y encoder tiene una muy alta resolución de pulsos por vuelta de la rueda. Puede que asta demasiado resolución para un Arduino normal. Ya que si el robot tiene 4 encoder de cuadratura. Tenemos que el Arduino tiene que contar por ejemplo 1920 x 4 x 3 rev/segundos = 23.040 pulsos/segundo. El programador que quiera usar este encoder deberá de optimizar el uso de los pulsos que se envíen al Arduino. Para no saturar al Arduino con demasiadas interrupciones.
Encoder-SJ01
Vista del encoder-SJ01 con su disco en el eje del motor. Motor acoplado al robot.
Para más información sobre este encoder de cuadratura ver este enlace.

4-Estudio de los dos pulsos del encoder :
En este apartado voy a analizar, con un osciloscopio, los pulsos generados por los dos sensores del encoder. Para comprobar que la señal del encoder es correcta y no tiene interferencias.
En la foto siguiente se muestra las dos señales del encoder de cuadratura, una encima de la otra. La señal 1 arriba y la señal 2 abajo. Como puede verse en pantalla las dos señales digitales están desfasadas una respecto a la otra. Esta es la principal característica de un encoder de cuadratura. Esto se hace para poder determinar la dirección de giro del encoder.
Las dos señales del encoder de cuadratura
El osciloscopio nos muestra el desfase de las dos señales del encoder.
En la foto siguiente se puede ver un pulso aislado del cana1 1 del encoder. En el centro de la señal aparece siempre un pequeño pico de voltaje (hacia arriba). Pero parece que esta interferencia no afecta a la lectura del Arduino.
Un pulso del canal 1
La señal del encoder del canal 1.
En la foto inferior se puede ver un pulso aislado del cana1 2 del encoder. En el centro de la señal también aparece siempre un pequeño pico de voltaje (hacia abajo). Pero parece que esta interferencia tampoco afecta a la lectura del Arduino.
Un pulso del canal 2
La señal del encoder del canal 2.
En la foto siguiente se puede ver la rampa de subida de un pulso del cana1 1 del encoder. Aquí no hay ningún tipo de interferencia ( ni de rebotes) y la rampa de subida es muy correcta y limpia.
Rampa de subida de un pulso.
La rampa de subida de la señal del encoder.
En la foto inferior se puede ver la rampa de bajada de un pulso del cana1 1 del encoder. Aquí tampoco hay ningún tipo de interferencia ( ni de rebotes) y la rampa de bajada es muy correcta y limpia.
Rampa de bajada de un pulso.
La rampa de bajada de la señal del encoder.
Hecho el análisis de la señal del encoder ahora puedo decir que la señal que nos suministra no tiene interferencias. Esta señal no nos puede dar una falsa lectura del pulsos. La señal parece muy correcta para ser leída por el Arduino.

5-Esquema de conexión : En la foto siguiente se aprecia un esquema muy básico para conectar el encoder a la placa de Arduino UNO.
Esquema de conexión del encoder
Esquema de conexión del motor a Arduino UNO.
6-"Sketch" Nº1 de Arduino para el encoder SJ-01:
Programa que cuenta los pulsos de uno de los dos sensores del encoder del micro-motor de DFROBOT "Micro DC Motor 120:1" con un Encoder-SJ01 de cuadratura. Programa probado con un Arduino UNO o MEGA. El motor se ha conectado según el esquema anterior y se ha alimentado con una corriente continua constante de 3 voltios y hemos obtenido en IDE de Arduino los datos siguientes:
Tabla de valores de salida del encoder y del Arduino
Datos del uno de los sensores del encoder que nos calcula y muestra este "Sketch".
En la tabla superior se muestra los parámetros calculados; Pulsos del motor(encoder), Revoluciones Por Minuto RPM del motor, Revoluciones Por Minuto RPM de la rueda y velocidad de una rueda de ø96mm.
Se tiene que tener especial cuidado al definir la fórmula que calcula las RPM. Ya que el tipo de valores calculados en la fórmula y el tipo variables declaradas tiene que coincidir. En este caso hemos tenido que usado variables tipo "float" ya que los valores que nos salían en el cálculo eran con decimales. Al trabajar con variables tipo "float" provoca que Arduino tenga que hacer cálculos más lentos y complejos. Una mejor opción solución sería usar variables tipo "int", variables enteras, pero en este caso no he podido.

--------------------------------------------------------------------------------------------------------------------------
"Sketch" de Arduino para el encoder-SJ01 de cuadratura;
 ////// Parameters // Variables ///////////////////////////////////////////////////
int encoder_pin = 2;            // The encoder pin is connected  // Pin 2, donde se conecta el encoder    
float rpm = 0;                     // rpm reading // Revoluciones por minuto calculadas.
float velocity = 0;              // Velocity // Velocidad en [Km/h]
volatile float pulses = 0;       // Number of pulses in one second // Número de pulsos leidos por el Arduino en un segundo
unsigned long timeold = 0;       // Time // Tiempo
unsigned int pulsesperturn = 8;  // The number of pulses per motor revolution, depends od the encoder!! // Número de pulsos por vuelta del motor, por canal = 8.
unsigned int wheel_diameter = 66;// Diámetro de la rueda pequeña[mm]
int ratio = 120;                  // Relación de transmisión de los engranajes i = 120:1    
/////////////////////////// Function // Función que cuenta los pulsos buenos ///////////////////////////////////////////
 void counter(){
        pulses++; }  // Suma el pulso bueno que entra.
//// Arduino configuration /////////////////////////////////////////////////////////
void setup(){
   Serial.begin(9600); // Configuración del puerto serie
   pinMode(encoder_pin, INPUT); // Use status Pin to flash along with interrupts // Configuración del pin nº2
   attachInterrupt(0, counter, RISING); // Interrupt 0 is digital pin 2, so that is where the IR detector is connected. Triggers on FALLING (change from HIGH to LOW) // Configuración de la interrupción 0, donde esta conectado el encoder HC-020K.
   // parameters Initialize // Inicialización de los parametros
   pulses = 0;
   rpm = 0;
   timeold = 0;
   Serial.println("");
   Serial.print("            MOTOR       ");Serial.print("MOTOR       ");Serial.print("WHEEL       "); Serial.println("WHEEL");
   Serial.print("Seconds     ");Serial.print("Pulses      ");Serial.print("RPM         ");Serial.print("RPM         ");Serial.println("Velocity[Km/h]");
 }
////  Main programe // Programa principal ///////////////////////////////////////////////////////////////////////
 void loop(){
   if (millis() - timeold >= 1000){  // Uptade every one second, this will be equal to reading frecuency (Hz). // Se actualiza cada segundo
      noInterrupts(); //Don't process interrupts during calculations // Desconectamos la interrupción para que no actué en esta parte del programa.
      rpm = 60 * pulses / pulsesperturn * 1000 / (millis() - timeold) ; // Note that this would be 60*1000/(millis() - timeold)*pulses if the interrupt. happened once per revolution // Calculamos las revoluciones por minuto
      //Ojo con la fórmula de arriba, la variable rpm tiene que ser tipo float porque salen decimales en medio de la operación.
      velocity = rpm/ratio * 3.1416 * wheel_diameter * 60 / 1000000; // Cálculo de la velocidad de la rueda en [Km/h]
      Serial.print(millis()/1000); Serial.print("          "); // Write it out to serial port RPM = 39 pulses // Se envia al puerto serie el valor de tiempo, de las rpm y los pulsos.
      Serial.print(pulses,0); Serial.print("         ");
      Serial.print(rpm,0); Serial.print("         ");
      Serial.print(rpm/ratio,1); Serial.print("           ");
      Serial.println(velocity,2);
      pulses = 0;  // Inicializamos los pulsos.
      timeold = millis(); // Almacenamos el tiempo actual.
      interrupts(); // Restart the interrupt processing // Reiniciamos la interrupción
   }
  }
--------------------------------------------------------------------------------------------------------------------------
7-"Sketch" Nº2 de Arduino para el encoder SJ-01:
Este otro programa que cuenta los dos pulsos de los dos sensores del encoder del micro-motor de DFROBOT "Micro DC Motor 120:1" con un Encoder-SJ01 de cuadratura. Programa probado con un Arduino UNO y MEGA. El motor se ha conectado según el esquema anterior y se ha alimentado con una corriente continua constante de 3 voltios y hemos obtenido en IDE de Arduino los datos siguientes:
Datos de los 2 sensores del encoder, que nos calcula y muestra este "Sketch".
En la tabla anterior se muestra los parámetros calculados; Pulsos de los sensores A y B (encoder), Revoluciones Por Minuto RPM de los sensores A y B, Revoluciones Por Minuto RPM de la rueda y velocidad de una rueda de ø96mm.
Con este "Sketck" de Arduino podemos comparar los pulsos de dos sensores del encoder. Como podemos ver en la tabla, los valores son idénticos en casi todas veces y hay alguna vez que difieren un poco debido  al terminar el cálculo un pulso se queda a medias y no es leído. Pero estos datos son normales y muy correctos.
--------------------------------------------------------------------------------------------------------------------------
"Sketch" de Arduino para el encoder-SJ01 de cuadratura;
//// Parameters // Variables ///////////////////////////////////////////////////
int encoder_pin [] = {2,3};       // The encoder pin is connected  // Pin 2, donde se conecta el encoder   float rpm [] = {0,0};             // rpm reading // Revoluciones por minuto calculadas.
float velocity [] = {0,0};        // Velocity // Velocidad en [Km/h]
volatile float pulses [] = {0,0}; // Number of pulses in one second // Número de pulsos leidos por el Arduino en un segundo
unsigned long timeold = 0;        // Time // Tiempo
unsigned int pulsesperturn = 8;   // The number of pulses per motor revolution, depends od the encoder!! // Número de pulsos por vuelta del motor, por canal = 8.
unsigned int wheel_diameter = 66; // Diámetro de la rueda pequeña[mm]
int ratio = 120;                  // Relación de transmisión de los engranajes i = 120:1    
/////////////////////////// Function 1 // Función 1 que cuenta los pulsos buenos ///////////////////////////////////////////
 void counter_1(){
        pulses [0] ++; }  // Suma el pulso bueno que entra.
        /////////////////////////// Function 1 // Función 1 que cuenta los pulsos buenos ///////////////////////////////////////////
 void counter_2(){
        pulses [1] ++; }  // Suma el pulso bueno que entra.
//// Arduino configuration /////////////////////////////////////////////////////////
void setup(){
   Serial.begin(9600); // Configuración del puerto serie
   pinMode(encoder_pin [0], INPUT); // Use status Pin to flash along with interrupts // Configuración del pin nº2
   pinMode(encoder_pin [1], INPUT); // Use status Pin to flash along with interrupts // Configuración del pin nº2
   attachInterrupt(0, counter_1, RISING); // Interrupt 0 is digital pin 2, so that is where the IR detector is connected. Triggers on FALLING (change from HIGH to LOW) // Configuración de la interrupción 0, donde esta conectado el encoder HC-020K.
   attachInterrupt(1, counter_2, RISING);
   Serial.println("");
   Serial.print("           | SENSOR A    | ");Serial.print("SENSOR B  | ");Serial.print("SENSOR A   | ");Serial.print("SENSOR B  | ");Serial.print("WHEEL     | "); Serial.print("WHEEL     | ");Serial.print("ROBOT            | ");Serial.println("ROBOT |");
   Serial.print(" Seconds   | ");Serial.print("Pulses      | ");Serial.print("Pulses    | ");Serial.print("RPM        | ");Serial.print("RPM       | ");Serial.print("RPM       | ");Serial.print("RPM       | ");Serial.print("Velocity[Km/h]   | "); Serial.println("Velocity[Km/h]");
   Serial.println("-------------------------------------------------------------------------------------------------------------------------");
 }
////  Main programe // Programa principal ///////////////////////////////////////////////////////////////////////
 void loop(){
   if (millis() - timeold >= 1000){  // Uptade every one second, this will be equal to reading frecuency (Hz). // Se actualiza cada segundo
      for (int i = 0 ; i < 2 ; i++){
        noInterrupts(); //Don't process interrupts during calculations // Desconectamos la interrupción para que no actué en esta parte del programa.
        rpm [i]= 60 * pulses [i] / pulsesperturn * 1000 / (millis() - timeold) ; // Note that this would be 60*1000/(millis() - timeold)*pulses if the interrupt. happened once per revolution // Calculamos las revoluciones por minuto
        //Ojo con la fórmula de arriba, la variable rpm tiene que ser tipo float porque salen decimales en medio de la operación.
        velocity [i]= rpm[i]/ratio * 3.1416 * wheel_diameter * 60 / 1000000; // Cálculo de la velocidad de la rueda en [Km/h]
      }
      Serial.print(" ");
      Serial.print(millis()/1000); Serial.print("         | "); // Write it out to serial port RPM = 39 pulses // Se envia al puerto serie el valor de tiempo, de las rpm y los pulsos.
      Serial.print(pulses[0],0); Serial.print("        | ");
      Serial.print(pulses[1],0); Serial.print("      | ");
      Serial.print(rpm[0],0); Serial.print("       | ");
      Serial.print(rpm[1],0); Serial.print("      | ");
      Serial.print(rpm[0]/ratio,1); Serial.print("      | ");
      Serial.print(rpm[1]/ratio,1); Serial.print("      | ");
      Serial.print(velocity[0],2); Serial.print("             | ");
      Serial.println(velocity[1],2);
      pulses [0]= 0;   // Inicializamos los pulsos.
      pulses [1]= 0;   // Inicializamos los pulsos.
      timeold = millis(); // Almacenamos el tiempo actual.
      interrupts(); // Restart the interrupt processing // Reiniciamos la interrupción
   }
  }
//// The end of Main programe / Fin del programa principal //////////////////////////////////////////////////////////////////////////////////
--------------------------------------------------------------------------------------------------------------------------

8 comentarios :

  1. Disculpa si quiero controlar un motor DC de 12V con arduino, el motor me da unas 8000 rpm aprox a 12V como podria hallar la resolución con un encoder de 20 ranuras y 4 estados.

    ResponderEliminar
    Respuestas
    1. Son altas revoluciones ,Tendrias que usar un buen encoder.

      Eliminar
    2. Hola buenos días Angel. ¿Me puedes dar más información sobre el encoder y del motor? Modelo y referencia etc... No compredo cuando dices 20 ranuras y 4 estados.

      Eliminar
  2. En ese circuito el motor va a funcionar siempre a su máxima velocidad, cierto?
    Se le puede acoplar un potenciómetro al circuito para regular la velocidad?

    ResponderEliminar
    Respuestas
    1. Si siempre funciona a una velocidad constante. Puedes añadirle un potenciómetro.

      Eliminar
  3. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  4. oye en la línea de codigo 19 donde se configura el pinMode del encoder faltaba el pullup, la linea quedaría así:

    pinMode(encoder_pin, INPUT_PULLUP); // Use status Pin to flash along with interrupts // Configuración del pin nº2

    así obtendrás los valores de la imagen (monitor serie)

    ResponderEliminar
  5. Programa probado con un Arduino UNO y MEGA. El motor se ha conectado según el esquema anterior y se ha alimentado con una corriente continua constante de 3 voltios y hemos obtenido en IDE de Arduino los datos siguientes: coaching-mastery.com/juegos-2d-para-pc/

    ResponderEliminar

Google analytics