Estamos rodeados de encoders rotatorios sin darnos cuenta de ello, ya que estos se utilizan en muchos artículos del día a día, desde impresoras a cámaras, maquinas CNC y robots. La aplicación más común de los encoders rotatorios son los utilizados en el volumen de mando de la radio de un auto.

Un encoder rotatorio es un tipo de sensor de posición que convierte la posición angular(rotación) de un mando a una señal de salida que puede ser usada para determinar cuál dirección es retornada por el mando.

Los encoders rotatorios se pueden clasificar en dos tipos: Absoluto e incremental. El encoder absoluto reporta la posición exacta del mando en grados, mientras que el encoder incremental reporta el número de incrementos del eje que se ha movido. El encoder rotatorio utilizado en este tutorial es del tipo Incremental.

ENCODERS ROTATORIOS Vs POTENCIOMETROS

Los encoders rotatorios son el equivalente digital y moderno de los potenciómetros y son más versátiles, Los encoders pueden rotar 360° sin parar, mientras que los potenciómetros solo pueden rotar ¾ de la circunferencia.

Los potenciómetros son usados en situaciones donde tu necesites saber la posición exacta del mando. Los Encoders rotatorios por otro lado son usados en situaciones donde necesitas saber el cambio de posición más que la posición exacta.

¿Como funcionan los encoders rotatorios?

Dentro del encoder hay un disco ranurado que esta conectado al pin C, el común de tierra también tiene 2 pines compactos A y B, como se muestra en la imagen de abajo.

Cuando giras el mando, A y B haces contacto con el pin común de tierra C, en orden especifico dependiendo en cual dirección tu gires el mando, Cuando hacen contacto con la común tierra, 2 señales son generadas.

Estas señales son de 90 grados fuera de fase porque uno pin hace contacto con el GND antes que el otro. Es referido como cuadratura de codificación.

Cuando el mando es girado en sentido horario, el pin A es conectado a tierra antes que el pin B.

Cuando el mando es girado en sentido antihorario, el pin B se conecta a tierra antes que el pin A.

Mientras monitoreamos cada pin que se conecta o desconecta a tierra, podemos determinar la dirección en cual el mando ha sido rotado. Esto se puede lograr simplemente observando cuando A cambia el estado de B.

Cuando A cambia de estado

If B! = A then el mando es girado en sentido horario.

if B = A, then el mando es girado en sentido antihorario

Salidas del Encoder Rotatorio

Las salidas del módulo del encoder rotatorio son las siguientes

GND es la conexión a tierra

VCC es la alimentación de voltaje positivo, la cual es típicamente entre 3.3 volts y 5 volts.

SW es la salida del interruptor del botón pulsador (activación baja). Cuando el mando es presionado el voltaje baja

DT (Output B) es similar a la salida CLK, pero tiene retardos por detras del CLK por una fase de cambio de 90°. Esta salida es usada para determinar la rotación

CLK (Output A) es el pulso de salida primaria es usada para determinar la cantidad de rotación cada vez que el mando es girado en cualquier dirección, por solo una click, la salida ‘CLK’ va a través de ciclos de altos y bajos.

Conexiones de un Encoder Rotatorio a arduino

¡Ahora que comprendemos como el encoder rotatorio trabaja, Es tiempo de poner este componente en uso!

 Vamos a conectar el encoder a Arduino. Las conexiones son muy simples. Comienza por conectar el pin positivo del módulo a la salida de 5 V de Arduino y el pin GND a tierra.

Ahora conectemos los pines CLK y DT a los pines digitales #2 y #3, respectivamente. Finalmente, conecte el pin SW al pin digital #4.

La siguiente imagen muestra las conexiones

Ejemplo de codigo #1 leyendo un encoder rotatorio

Nuestro primer ejemplo es muy directo, simplemente detecta la dirección de rotación del encoder Cuando el botón es presionado. Primero prueba este ejemplo y luego veremos más detalles.

// Entradas del encoder rotatorio
#define CLK 2
#define DT 3
#define SW 4
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;

void setup() {
        
// ajusta los pines del encoder como entradas
	pinMode(CLK,INPUT);
	pinMode(DT,INPUT);
	pinMode(SW, INPUT_PULLUP);

// ajusta el Monitor Serial
	Serial.begin(9600);

// lee el estado inicial del CLK
	lastStateCLK = digitalRead(CLK);
}

void loop() {
        
// Lee el estado actual del CLK
       currentStateCLK = digitalRead(CLK);
// si los ultimos estado actuales del CLK son diferentes entonces ocurrió un pulso
// Reacciona solo a 1 cambio de estado para evitar un doble conteo 

if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){

// si el estado DT es diferente al estado del CLK 
// entonces el encoder de rotación tiene un CCW y esto significa que
//está en sentido antihorario CCW es decir Esta decrementando
		if (digitalRead(DT) != currentStateCLK) {
			counter --;
			currentDir ="CCW";
		} else {
			// } CW sentido Horario así que incrementa
			counter ++;
			currentDir ="CW";
		}
		Serial.print("Direction: ");
		Serial.print(currentDir);
		Serial.print(" | Counter: ");
		Serial.println(counter);
	}
// guardar el ultimo estado de CLK
	lastStateCLK = currentStateCLK;
// lee el estado del boton
	int btnState = digitalRead(SW);
//si nosotros detectamos una senal baja, presionamos el boton
	if (btnState == LOW) {
//si han pasado 50ms desde la ultimo pulso bajo,
//significa que el botón ha sido presionado,suéltelo y presione otra //vez
		if (millis() - lastButtonPress > 50) {
			Serial.println("Button pressed!");
		}
		// guarda el ultimo evento de pulsación del boton
		lastButtonPress = millis();
	}
// Ponga un ligero retardo para ayudar a eliminar el rebote de la //lectura
	delay(1);
}

Deberías ver una salida similar en el monitor serial

Explicación del Código

El siguiente ejemplo comienza por declarar los pines de Arduino por los cuales los pines del encoder CLK, DT y SW son conectados.

#define CLK 2
#define DT 3
#define SW 4

Lo siguiente que definiremos son algunas variables

  • Variable counter que incrementara cada vez que el mando se gira dando un click.
  • Las variables currentStateCLK y lastStateCLK almacenan el estado de salida CLK y son usados para calcular la cantidad de rotación.
  • Un string llamadocurrentDir se usará para mostrar la dirección actual de rotación en el monitor serial.
  • La variable lastButtonPress es usada para activar un interruptor.
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;

En la sección del Void Setup, configuraremos primero las conexiones del encoder rotatorio como entradas, y entonces habilitaremos las entradas pull-up para el pin SW, también ajustaremos el monitor serial.

Finalmente, leeremos el valor actual del pin del CLK que se almacenara en la variable lastStateCLK.

pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW, INPUT_PULLUP);

Serial.begin(9600);

lastStateCLK = digitalRead(CLK);

En la sección del void loop , revisaremos el estado del CLK otra vez y comparamos eso con el valor del lastStateCLK, si ellos difieren entre quiere decir que el mando ha sido girado .También revisaremos si el currentStateCLK  es 1 para reaccionar a un solo cambio de estado y evitar la doble contabilidad.

currentStateCLK = digitalRead(CLK);

if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){

Dentro del condicional if la dirección de rotación se determina. Para lograr eso nosotros simplemente leemos el pin DT Y lo comparamos con el estado actual del CLK.

  • Si esos dos valores difieren, eso indicaría que el mando ha sido girado de manera antihoraria el contador entonces decrementa, y el currentDir es ajustado a CCW
  • Si esos dos valores  son idénticos eso significa que el mando ha sido girado en sentido horario el  counter entonces incremente y el  currentDir es ajustado a“CW”
•	if (digitalRead(DT) != currentStateCLK) {
•	    counter --;
•	    currentDir ="CCW";
•	} else {
•	    counter ++;
•	    currentDir ="CW";•	}

A continuación, los resultados se imprimen en el monitor serial.

Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);

Lo Siguiente es que el condicional if, actualiza el lastStateCLK con el estado actual de CLK.

lastStateCLK = currentStateCLK;


El siguiente paso consiste en leer y eliminar el rebote del interruptor de botón. Primero leemos el estado actual del botón, y cuando cambia a BAJO, esperamos 50 ms para que el botón rebote.

Si el botón permanece BAJO durante más de 50 ms, indica que realmente se ha presionado. Como resultado, imprimimos “¡Botón presionado!” al monitor serie.

int btnState = digitalRead(SW);

if (btnState == LOW) {
    if (millis() - lastButtonPress > 50) {
        Serial.println("Button pressed!");
    }
    lastButtonPress = millis();
}

Entonces el proceso se repite

Ejemplo de codigo #2 Usando interrupciones

Para leer el encoder rotatorio, debemos constantemente monitorear los cambios en la señal del DT y CLK.

Una forma de ver esos cambios es probarlos continuamente, como hicimos en nuestro ejemplo anterior, sin embargo, esa no es la mejor solución por las siguientes razones.

  • Debemos llevar a cabo frecuentes revisiones para ver si el valor ha cambiado. Si el nivel de la señal no cambia, habrá una pérdida de los ciclos.
  • Existe la posibilidad de latencias entre el tiempo que el evento ocurrió y el tiempo que nosotros lo revisamos, Si necesitamos reaccionar rápidamente tendremos un retardo debido a esta latencia.
  • Si la duración del cambio es corta, es posible perder completamente el cambio de la señal

Una manera para tartar con esto es usar interrupciones

Con las interrupciones no hay necesidad de revisar continuamente un evento especifico, Eso libera a Arduino de realizar otras tareas sin perder un evento. Para conocer en detalle el uso de interrupciones, te recomendamos leer el siguiente tutorial Interrupciones del procesador con Arduino

Conexiones

Debido a que la mayoría de tarjetas Arduino incluyen Arduino uno solo tenemos dos interrupciones externas, solo podemos monitorear los cambios en la señal DT y CLK. Sin Embargo, podemos remover el pin de conexión SW.

La actualización de la conexión es la siguiente

Codigo en arduino

Aquí un ejemplo de como leer un encoder rotatorio con interrupciones

// Entradas del encoder rotatorio 
#define CLK 2
#define DT 3

int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";

void setup() {

	// Configurar los pines como entradas
	pinMode(CLK,INPUT);
	pinMode(DT,INPUT);

	// Configurar el Monitor serial 
	Serial.begin(9600);

	// leer el estado inicial del CLK
	lastStateCLK = digitalRead(CLK);
	
	// LLamar a updateEncoder() cuando un high o un low haya cambiado 
	// sobre la interrupcion 0 (pin 2), o interrupcion 1 (pin 3)
	attachInterrupt(0, updateEncoder, CHANGE);
	attachInterrupt(1, updateEncoder, CHANGE);
}

void loop() {
    //puede Crear su propia configuración de código aquí
}

void updateEncoder(){
	// leer el estado actual del CLK
	currentStateCLK = digitalRead(CLK);

// Si el ultimo estado actual del CLK es distinto, entonces ocurrió un pulso 
// reacciona solo a un cambio de estado para evitar un conteo doble
	if (currentStateCLK! = lastStateCLK && currentStateCLK == 1){

// si el estado del DT es diferente que el estado del CLK 
// entonces el encoder está rotando en sentido antihorario CCW asi que //decrementa		
if (digitalRead(DT) != currentStateCLK) {
			counter --;
			currentDir ="CCW";
		} else {
// Encoder está rotando en sentido horario CW así que incrementa
			counter ++;
			currentDir ="CW";
		}

		Serial.print("Direction: ");
		Serial.print(currentDir);
		Serial.print(" | Counter: ");
		Serial.println(counter);
	}

	// guarda el ultimo estado del CLK 
	lastStateCLK = currentStateCLK;
}

Observe que el loop principal de este programa se ha dejado vacío, así que Arduino no hará nada.

Ahora es su turno de girar el mando, debería ver una salida similar a la imagen de abajo sobre el monitor serial.

Explicación del Código

Este simple ejemplo monitorea los pines digitales 2 correspondiente a interrupción 0 y 3 correspondiente a interrupción 1 para los cambios de señal. En otras palabras, esto detecta cuando el voltaje cambia de un nivel Alto a uno Bajo o de un nivel Bajo a uno Alto como resultado de girar el mando.

Cuando sucede el cambio, Arduino inmediatamente detecta esto y guarda ese estado de ejecución, Luego ejecuta la función updateEncoder() también conocido como  servicio de interrupción rutinario o ISR,y  finalmente esto nos regresa a lo que sea que hayamos  estado haciendo antes.

Las siguientes 2 líneas configuran las interrupciones. La función attachInterrupt() ordena al Arduino que pin monitorear ,cual ISR ejecutar cuando la interrupción es disparada ,y que tipo de disparo buscar

attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);

Ejemplo de codigo #3 Controlando un servo motor con un encoder rotatorio

En el siguiente ejemplo, usaremos el encoder rotatorio para el control de la posición del servo motor.
Este Proyecto puede ser útil en una variedad de situaciones, Por ejemplo, si tu quieres operar un brazo robótico, eso puede ayudarte a posicionar el brazo y su agarre y precisión.

Si no estás familiarizado con los servos por favor lee el siguiente tutorial Como utilizar un servo motor con Arduino

Conexiones

Vamos a incluir un servo motor en nuestro proyecto, Conecta el cable rojo al servo motor para la alimentación de 5 volts, el cable negro o café lo conectamos a tierra, y el cable naranjo al pin digital disponible pwm pin 9.

Por supuesto puedes usar la salida de 5 volt , ,pero ten en claro que el servo podría inducir un ruido eléctrico sobre la alimentación de 5 volts, lo cual podría dañar tu Arduino , por lo tanto te aconsejo usar una fuente de alimentación externa.

Código Arduino

Aquí les dejo el código para usar el encoder rotatorio para precisar el control de del servo motor .Cada vez que el mando es girado en un click , la posición del brazo del servo motor  cambia 1 grado. ñ

// Incluye la librería del servo motor
#include <Servo.h>

// Entradas del encoder rotatorio
#define CLK 2
#define DT 3

Servo servo;
int counter = 0;
int currentStateCLK;
int lastStateCLK;

void setup() {

	// Ajuste de entrada del encoder rotatorio 
	pinMode(CLK,INPUT);
	pinMode(DT,INPUT);
	
	// Ajuste del monitor serial 
	Serial.begin(9600);
	
	// Attach servo sobre pin 9 para crear el objeto servo
	servo.attach(9);
	servo.write(counter);
	
	// leer el estado inicial del CLK
	lastStateCLK = digitalRead(CLK);
}

void loop() {
        
	// Read the current state of CLK
	currentStateCLK = digitalRead(CLK);
	
// si los ultimos estados actuales del CLK son distintos un puso ha ocurrido
// reacciona solo a un solo cambio de estado para evitar un doble conteo 
	if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){
		
// Si el estado del DT es diferente que el estado del CLK s then
// entonces el encoder rota de manera antihorario CCW eso quiere //decir que decrementa 
		if (digitalRead(DT) != currentStateCLK) {
			counter --;
			if (counter<0)
				counter=0;
		} else {
// si el ecoder rota en sentido horario CW significa que  incrementa
			counter ++;
			if (counter>179)
				counter=179;
		}
// mueve el servo
		servo.write(counter);
		Serial.print("Position: ");
		Serial.println(counter);
	}
	
// guarda el ultimo estado del CLK 
	lastStateCLK = currentStateCLK;
}

Explicación del Código

Si comparas este ejemplo con nuestro primer ejemplo, notaras unos pequeños cambios, pero son bastante similares

En principio, nosotros incluimos el constructor en la librería de Arduino servo y creamos un objeto llamado servo para representar nuestro servo motor

#include <Servo.h>

Servo servo;
En el void Setup, posicionemos el objeto servo a el pin 9 por el cual controlamos el pin del servo motor que esta conectado 
servo.attach(9);

En el void loop , nosotros limitamos el rango del contador de 0 A 179 debido a que el servo motor acepta valores cerca de este rango

if (digitalRead(DT) != currentStateCLK) {
    counter --;
    if (counter<0)
        counter=0;
} else {
    counter ++;
    if (counter>179)
        counter=179;
}

Finalmente, el valor del Contador es utilizado para posicionar el servo motor.
servo.write(counter);