Encoder x4 con interrupción hardware para Arduino
Antes de nada y para ahorrarme explicaciones te recomiendo ir a esta página para empaparte sobre las interrupciones con Arduino. Es la mejor web que he encontrado y lo hace con todo lujo de detalles. Los dsPIC y los 18Fxx5x tienen más de una interrupción externa y la filosofía del programa sería parecida.
Me ha sorprendido mucho no necesitar acondicionar las señales del encoder óptico porque las entradas digitales del Arduino tienen disparadores Schmitt de 0,5 voltios de histéresis cuando el microcontrolador es alimentado con 5 voltios; los modelos de Arduino que funcionan a 3,3v esa histéresis sería menor.
Las interrupciones no devuelven valores y las variables generales que usemos dentro y fuera de ellas se han de declarar como 'volatile'. En la página que cito arriba explica la razón de ello y cómo hacerlo todo correctamente.
Los pines 2 y 3 son los que utilizo para conectar las señales A y B del encoder, esas dos patillas son las de interrupción externa en el Arduino UNO/Nano.
volatile int contador=0; // Declaramos como 'volatile' las variables que participan dentro y fuera
de la interrupción.
byte ant=0, act=0; // Sólo se utiliza los dos primeros bits de estas variables.
ant=anterior, act=actual.
int n=0; // Variable auxiliar para notar cambios en el contador.
void setup()
{
Serial.begin(115200); // Dará la posición por el terminal serie.
/*
pinMode(2, INPUT); // No es necesario declarar pinMode, "attachInterrupt" se encarga de
convertir en entrada, pero
pinMode(3, INPUT); // los puedes poner si quieres. Los pines 2 y 3 es donde va conectado el
encoder incremental.
*/
attachInterrupt(digitalPinToInterrupt(2), encoder, CHANGE); // En cualquier flanco ascendente o
descendente
attachInterrupt(digitalPinToInterrupt(3), encoder, CHANGE); // en los pines 2 y 3 actúa la
interrupción.
}
void loop()
{
if(n != contador) // Sólo cuando la variable contador cambie de valor se
transmite dicho valor.
{
n=contador;
Serial.println(contador);
}
}
void encoder()
{
ant=act; // Guardamos 'act' para convertirlo
en pasado.
if(digitalRead(2)==1) bitSet(act,0); else bitClear(act,0); // Seteamos los dos primeros bits
de la variable 'act' con
if(digitalRead(3)==1) bitSet(act,1); else bitClear(act,1); // el valor de este instante, como
un número de dos bits.
if(ant==3 && act==1) contador++;
if(ant==1 && act==0) contador++;
if(ant==0 && act==2) contador++;
if(ant==2 && act==3) contador++;
if(ant==1 && act==3) contador--;
if(ant==0 && act==1) contador--;
if(ant==2 && act==0) contador--;
if(ant==3 && act==2) contador--;
}
Una manera de leer el encoder de forma más eficiente sería hacerlo en paralelo en vez de comprobar bit a bit. La función/interrupción encoder() quedaría así:
void encoder()
{
ant=act; // Guardamos el valor 'act' en 'ant' para convertirlo en
pasado.
act=PIND & 12; // Guardamos en 'act' el valor que hay en ese instante en
el encoder y hacemos un
// enmascaramiento para aislar los dos únicos bits que
utilizamos para esta finalidad.
if(ant==12 && act==4) contador++;
if(ant==4 && act==0) contador++;
if(ant==0 && act==8) contador++;
if(ant==8 && act==12) contador++;
if(ant==4 && act==12) contador--;
if(ant==0 && act==4) contador--;
if(ant==8 && act==0) contador--;
if(ant==12 && act==8) contador--;
}
'PIND' es una instrucción para leer el puerto D del Arduino, se corresponde con los pines 0 hasta el 7. Haz clic en este enlace para más información.