VictorJAM.zapto.org
Electrónica  Arduino 

2021-04-17

Encoders

¿Qué es un encoder?

Un encoder es un dispositivo electromecánico que permite codificar el movimiento mecánico de un dispositivo en algún tipo de señal eléctrica, ya sea digital o analógica. De esta manera podemos saber que tipo de movimiento está realizando dicho dispositivo: dirección, velocidad, etc..

Dependiendo del tipo de movimiento podemos separarlos en enconders lineales o giratorios.

En los sensores lineales el sensor mide el desplazamiento a lo largo del dispositivo, indicando la posición lineal en la que se encuentra.

En el segundo tipo la lectura se realiza sobre un disco que gira permitiendo saber la posición angular exacta.

También podemos clasificarlos en encoders de detección incremental o absoluta.

En un encoder incremental cada movimiento se expresa en incrementos, es decir, el número de pulsos que entrega es proporcional a la distancia. Son más fáciles de fabricar y son más baratos pero tienen el inconveniente de que es necesario ajustar su posición inicial.

En un encoder de posición absoluta, como su nombre indica, sabemos la posición exacta. Esto es útil si la máquina que se está moviendo se reinicia, dado que no importa la posición donde esté, el valor del encoder sigue siendo la posición. En uno incremental deberíamos buscar la posición inicial para volver a contar pulsos.

Un ejemplo de encoder de posición absoluta es un potenciómetro. Independientemente de si el potenciómetro es circular o lineal, al cambiar su posición cambiamos el valor de su resistencia, con lo cual sabemos en que posición se encuentra. Este también es un ejemplo de encoder analógico.

Funcionamiento típico de un encoder rotativo incremental.

El principio de funcionamiento es muy similar a tener dos interruptores que vamos a ir cerrando y abriendo en una secuencia definida. Llamemos a los interruptores A y B y los montaremos en configuración PULL-UP, es decir, cuando están sin cerrar la salida será HIGH y al cerrar será LOW.

Cuando se realice un movimiento solo cambiará el estado de un interruptor, nunca los dos a la vez. Y una vez hayamos movido uno, el siguiente que tenemos que mover es el otro. Nunca dos veces el mismo.

En el sentido de la agujas del reloj primero cambiaremos B, luego A, B, A y así sucesivamente.

En el sentido contrario a las agujas del reloj, primero cambiaremos A, luego B, A, B y así sucesivamente.

Así obtenemos la siguiente tabla:

Lo que hemos obtenido es el Código de Gray, un sistema de numeración binario donde dos números consecutivos difieren solo en uno de sus dígitos.

Como para pasar de un número a otro solo se cambia un bit, sirve entre otras cosas para identificar un incremento o decremento en una señal e incluso para detectar errores en protocolos de comunicación.

Así la señal obtenida son dos señales cuadradas que están desplazadas 90º. He aquí como se ve la señal de salida:

A los encoders que funcionan de esta manera se les suele llamar encoders de cuadratura.

Pongamos un ejemplo. En un momento dado leemos el estado de la señales AB y tenemos un valor, digamos 10. En la siguiente lectura tenemos que AB tiene un valor 11. Como vemos hemos cambiado el valor de A, con lo que hemos retrocedido una posición y podemos actuar en consecuencia.

Podemos usar indistintamente A y B. Si desconocemos quien es quien el único problema será que la dirección de movimiento será diferente.

En la secuencia expuesta, el giro es horario. Pero si cambiamos los pines los giros serán anti horarios.

De esta manera podemos saber si el dispositivo que está controlando el encoder se ha movido y la dirección e incluso la velocidad.

Los interruptores A y B pueden implementarse de varias maneras: interruptores mecánicos, sensores ópticos, sensores magnéticos, etc.. Pero el funcionamiento será muy similar. Por ejemplo, en los ratones de ordenador se usa una rueda dentada y sensores ópticos.

Esta caso es el más simple y solo se utilizan dos interruptores generando una señal de 2 bits, pero hay encoders que son capaces de generar señales con más número de bits.

Encoder con pulsador

Uno de los encoder más utilizados por los aficionados a la electrónica son los encoders rotativos con pulsador.

En estos encoders, los interruptores están constituidos por dos escobillas que se desplazan por una pista metálica con divisiones. También disponen de una bola metálica que cierra los contactos.

Cada vez que giramos el encoder, notaremos que lo hace por escalones. Esto se debe a que la bola se queda en una posición donde hay un hueco. Cada vez que hacemos un escalón se realiza la secuencia completa, en reposo los interruptores siempre estarán abiertos. Al girar un escalón realiza la secuencia completa de abrir y cerrar los interruptores (11, 10, 00, 01) volviéndose a quedar abiertos.

Adicionalmente, posee un botón en el mando al que se le puede dar otra utilidad.

Este tipo de encoders es muy común para realizar interfaces de menú y similares.

Podemos encontrar este tipo de encoders en módulos para usar en nuestros proyectos con microcontrolador. He aquí el esquema típico de dichos módulos.

El esquema es bastante simple, del encoder salen las señales A, B y C. C es el común de ambos interruptores. Los pines 1 y 2 representan al botón. Las señales A y B estarán disponibles en el módulo como CLK (reloj) y DT (datos). Ambas líneas tienen una resistencia PULL-UP incorporada.

Uno de los extremos del botón estará puesto a GND y el otro está disponible en el módulo como SW (switch). En este caso no se utiliza resistencia PULL-UP, así que conviene ponerla externa (o usar la interna del microcontrolador).

Encoder industrial H38S600B

El caso anterior de encoder con pulsador, tiene su aplicación muy limitada debido a su constitución. Para medir seriamente, es necesario recurrir a algún encoder de tipo industrial como el H38S600B. Este sensor tiene salidas foto acopladas del tipo NPN y una vuelta de su eje implica 600 pasos.

Podemos identificar cada uno de los cables de dicho sensor con la siguiente tabla:

COLOR CABLESEÑAL
BLANCOVCC
NEGROGND
ROJOA
VERDEB

Como leer un encoder

Para la lectura debemos utilizar necesariamente dos pines uno para cada fase. Hay dos técnicas para poder leer un encoder, por POLLING o mediante INTERRUPCIONES.

El polling consiste simplemente en leer el estado de los pines de fase cada cierto tiempo y ver si han cambiado. En caso de que hayan cambiado miramos la secuencia que ha realizado para determinar el tiempo. El mayor problema del polling es que podemos perder "pulsos" de la señal del encoder. Si nuestro código es lento o contiene retrasos y la señal de pulsos es rápida perderemos irremediablemente pasos.

Lo técnica más recomendada es por interrupciones. Así no tendremos que preocuparnos de si nuestro código es lento o rápido. Cuando las fases cambien se producirá una interrupción, nuestro código se detendrá, podremos mirar los pines y determinar correctamente la secuencia.

Veamos el código usando interrupciones:

/* 
 *  Ejemplo de leer un encoder usando interrupciones.
 *  
 *  Los posibles valores de la secuencia son:
 *  
 *  AB | Valor decimal
 *  ---+------------
 *  11 | 3 (condición inicial: interruptores abiertos.
 *  10 | 2
 *  00 | 0
 *  01 | 1 
 *  
 *  Para reducir el código convertimos la lectura de los pines en un número que
 *  nos indicará el paso de secuencia. Para hacer esto hemos de leer la fase A
 *  y desplazar un bit a la izquierda y sumarle la fase B.
 *
 *  Con ese número sabemos que la secuencia será 3, 2, 0, 1, 3, 2, ... en 
 *  sentido horario y 3, 1, 0, 2 , 3, 1, 0, 2, ... en sentido antihorario.
 */

#define FASEA 2
#define FASEB 3


byte actual, anterior;    // Variables para guardar el valor actual y anterior
                          // la secuencia.
int  posición;            // Contiene la posición actual del encoder.
int  posicionAnterior=-1; // Posición anterior del encoder.

// RUTINA DE INTERRUPCIóN.
void isr() {
  // Leemos la secuencia actual.
  actual = digitalRead(FASEA)<<1 | digitalRead(FASEB);
  // Comparamos según su valor con la anterior para saber si se ha desplazado
  // en un sentido u otro.
  switch ( actual ) {
    case 0:
      if ( anterior==2 ) posicion++;
      if ( anterior==1 ) posicion--;
      break;
    case 1:
      if ( anterior==0 ) posicion++;
      if ( anterior==3 ) posicion--;
      break;
    case 2:
      if ( anterior==3 ) posicion++;
      if ( anterior==0 ) posicion--;
      break;
    case 3:
      if ( anterior==1 ) posicion++;
      if ( anterior==2 ) posicion--;
      break;
    default:
      break;
  }
  // Hemos de guardar la posición actual para tenerla disponible en la próxima
  // secuencia.
  anterior = actual;
}

void setup() {
  Serial.begin(115200);
  pinMode(FASEA, INPUT);
  pinMode(FASEB, INPUT);
  posicion=0;
  actual = 3;
  anterior = actual;
  // Asignamos la rutina a la interrupción. Ambos pines hacen tienen el mismo
  // código, la misma rutina nos vale.
  attachInterrupt(digitalPinToInterrupt(FASEA), isr, CHANGE);
  attachInterrupt(digitalPinToInterrupt(FASEB), isr, CHANGE);
}


void loop() {
  // Si la posición es distinta a la anterior mostramos el resultado y 
  // guardamos la posición para no llegar la pantalla siempre.
  if ( posicion != posicionAnterior ) {
     Serial.println(posicion);
     posicionAnterior = posicion;
  }
}

El mayor problema de usar interrupciones es que tenemos un número limitado de interrupciones en un micro, generalmente disponemos de una a tres interrupciones, por ejemplo, un atTiny84 dispone de una, el atmega328p dispone de 2, un atMega1284p dispone de 3. Conviene mirar el datasheet del micro que vayamos a usar para determinar de cuantas interrupciones disponemos.

La ventaja es que dichos micros tienen otra fuente de interrupción, llamadas interrupción de cambio de puerto, que nos permite asignar una interrupción a un pin cualquiera, con ciertas limitaciones. Por ejemplo, podemos usar los pines cuatro y cinco, pero hemos de tener en cuenta que solo podemos usar CHANGE.

Podemos generar el código a pelo, programando los registros internos del micro, pero ya hay librerías disponibles. que pueden realizarlo:

En lo personal, la más fácil es PinChangeInterrupt y he aquí un ejemplo de como usarla para leer un encoder usando los pines 4 y 5:

#include <PinChangeInterrupt.h>
#include <PinChangeInterruptBoards.h>
#include <PinChangeInterruptPins.h>
#include <PinChangeInterruptSettings.h>

#define FASEA 4
#define FASEB 5

byte actual, anterior;
int  posicion;
int  posicionAnterior=-1;

void isr() {
  actual = digitalRead(FASEA)<<1 | digitalRead(FASEB);
  switch ( actual ) {
    case 0:
      if ( anterior==2 ) posicion++;
      if ( anterior==1 ) posicion--;
      break;
    case 1:
      if ( anterior==0 ) posicion++;
      if ( anterior==3 ) posicion--;
      break;
    case 2:
      if ( anterior==3 ) posicion++;
      if ( anterior==0 ) posicion--;
      break;
    case 3:
      if ( anterior==1 ) posicion++;
      if ( anterior==2 ) posicion--;
      break;
    default:
      break;
  }
  anterior = actual;
}

void setup() {
  Serial.begin(115200);
  pinMode(FASEA, INPUT);
  pinMode(FASEB, INPUT);
  posicion=0;
  actual = 3;
  anterior = actual;
  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(FASEA), isr, CHANGE);
  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(FASEB), isr, CHANGE);
}


void loop() {
  if ( posicion != posicionAnterior ) {
     Serial.println(posicion);
     posicionAnterior = posicion;
  }
}

El resultado será el mismo, y el cambio de código es mínimo, aunque recomiendo leer la documentación de cada librería.

Referencias

Medir el ángulo y sentido de giro con Arduino y encoder rotativo

Codificador rotatorio - Wikipedia