Encoder con un 16F876.

Este lector de encoder usa la interrupción externa RB0/INT. Cuando se produce un cambio de estado en la patilla RB0/INT, el micro deja lo que estaba haciendo y saltar a un procedimiento de forma inmediata. Terminado ese procedimiento vuelve al programa principal.

Funciona de la siguiente manera: Podemos hacer que la interrupción se active con un flanco de subida o de bajada, esto es programable modificando el registro INTEDG. Cuando ocurre una interrupción por flanco de subida hemos de programar la siguiente interrupción para que se active por flanco de bajada e incrementamos el contador si RB1 está a 1. Y viceversa, si ocurre una interrupción por flanco de bajada hemos de programar la siguiente interrupción por flanco de subida y decrementamos el contador si RB1 está a 1.

Una cosa muy buena que tiene esta forma de funcionar es que mientras el PIC no recibe interrupciones (RB0/INT en este caso) puede dedicarse a hacer otra cosa, como por ejemplo, calcular velocidades y cosas así. Como contrapartida, las interrupciones tienen un problema, y es que si intentas hacer comunicaciones por el puerto serie, podría perturbar los tiempos de temporización que rige la comunicación y hacer que falle. Además, recomiendo que cuando se use la interrupción RB0/INT no se use otras interrupciones (sea internas o externas), porque si ha de contar, esas otras interrupciones puede impedir el contaje correcto.

Uso el PIC 16F876A, pero puede ser cualquier PIC que tenga RB0/INT y conozcas bien. He escogido este PIC porque es muy conocido, económico y tiene suficientes entradas/salidas para poner los LED como puerto independiente y así la programación se hace más sencilla de comprender.

Si añades unas pocas líneas de programación podrás aumentar la resolución de salida aprovechando las patillas no usadas.

Expongo dos versiones del programa PIC Encoder: en CCS y en Proton IDE.

Código PIC Encoder en CCS:

The translation could modify the code. Use the code without translating.
#include <16F876A.h>

#FUSES NOWDT, XT, PUT, NOPROTECT, NODEBUG, NOBROWNOUT, NOLVP, NOCPD, NOWRT
#use delay(clock=4000000)
#ZERO_RAM

#byte porta = 0x05        // Asignamos PortA (No lo usamos).
#byte portb = 0x06        // Asignamos PortB (Usamos RB0 y RB1).
#byte portc = 0x07        // Asignamos PortC (8 salidas a LED).

// ------ Variable Global ------
int8   x=0;               //Declaramos el valor de X como byte, es decir, 8 bits.
// ---------- Interrupción ----------

#INT_EXT 

void IntRB0()                     // Si te da error en esta línea, sustituye "void IntRB0()" por
                                  // "void int_ext_isr(void)"
{
     if (bit_test(portb,0))       // Si RB0 se ha puesto a 1 (flanco de subida),
     {  
         ext_int_edge(H_TO_L);    // entonces activar la siguiente interrupción por
                                  // flanco de bajada.
         if (bit_test(portb,1))   // Si RB1 está 1,
         {
            x++;                  // entonces incrementar el contador X.
         }
     }
     else                         // Si RB0 se ha puesto a 0 (flanco de bajada),
     {  
         ext_int_edge(L_TO_H);    // entonces activar la siguiente interrupción por
                                  // flanco de subida.
         if (bit_test(portb,1))   // Si RB1 está 1,
         {
            x--;                  // entonces decrementar el contador X.
         }
     }
}

// ---------- Programa Principial ----------

void main()
{
   port_b_pullups(FALSE);
   setup_adc_ports(NO_ANALOGS);
   setup_adc(ADC_CLOCK_DIV_2);
   setup_spi(SPI_SS_DISABLED);
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_timer_1(T1_DISABLED);
   setup_timer_2(T2_DISABLED,0,1);
   setup_comparator(NC_NC_NC_NC);
   setup_vref(FALSE);
   
   //---- Fin de la configuración del 16F876A ----
   
   enable_interrupts(int_ext);    //Activar Interrupcion Externa. 
   ext_int_edge(L_TO_H);          //Inicialmente Interrupción por Flaco de Subida. 
   enable_interrupts(GLOBAL);     //Interrupciones Generales Activadas. 
   set_tris_a(0b111111);          //Puerto A como entrada (No usado).
   set_tris_b(0b11111111);        //Puerto B como entrada (sólo usamos RB0 y RB1).
   set_tris_c(0b00000000);        //Puerto C todo como salida (Salida a LED, 8 bits).

   While (true)
   {
          portc = x;               //El valor de X sale por el puerto C, 8 LED de salida. 
   }
}

Código PIC Encoder en Proton IDE:

The translation could modify the code. Use the code without translating.
Device=16F876A
REMINDERS = FALSE
     Config XT_OSC, PWRTE_ON, CPD_OFF, WDT_OFF, BODEN_OFF, LVP_OFF
REMINDERS = TRUE
Symbol INTF   = INTCON.1         ' RB0 External Interrupt Flag.
Symbol INTE   = INTCON.4         ' RB0 External Interrupt Enable.
Symbol GIE    = INTCON.7         ' Global Interrupt Enable.
Symbol INTEDG = OPTION_REG.6     ' Flag = 0 Flanco bajada. Flag = 1 Flanco subida.
On_INTERRUPT GoTo Interrupcion   ' Interrupción por externa (es la más rápida).
GIE    = 1                       ' Activa interrupciones generales.
INTE   = 1                       ' Activa la interrupción externa RB0/INT.
INTEDG = 1                       ' Hace que inicialmente la interrupción se dispare
                                 ' por flanco de subida.
ALL_DIGITAL = TRUE               ' Todas las entradas y salidas son digitales.
                                                                              
TRISA  = %111111
TRISB  = %11111111               ' Puerto A y B todo entradas.
TRISC  = %00000000               ' Puerto C como salida para visualizar a través de los LED.
           
Dim x As  Byte                   ' Variable X ---> contador de posición actual.
x=0                              
 
While 1=1                        ' |------ Programa Principal ------|
     
     PORTC = x                   ' El contenido de X se visualiza en el Puerto C a través de los LED.
Wend                             ' |--------------------------------|
             
End                              
Interrupcion:                 '-------- Decodificador de Encoder --------------
       
    Context SAVE              ' Salva en contexto de los registros antes de operar
                              ' con la interrupción.
    
    If PORTB.0 = 1    Then    ' Si RB0 se ha puesto a 1 (flanco de subida),
       INTEDG  = 0            ' entonces activar la siguiente interrupción por flanco de bajada.
       If PORTB.1 = 1 Then    ' Si RB1 está a 1,
          Inc x               ' entonces incrementar el contador X.
       EndIf
    EndIf
    
    If PORTB.0 = 0    Then    ' Si RB0 se ha puesto a 0 (flanco de bajada),
       INTEDG  = 1            ' entonces activar la siguiente interrupción por flanco de subida.
       If PORTB.1 = 1 Then    ' Si RB1 está 1,
          Dec x               ' entonces decrementar el contador X.
       EndIf
    EndIf
     
    INTF = 0                  ' Borra el "flag" de la interrupción RB0/INT.     
                              ' para poder permitir la siguiente interrupción.
    Context Restore           ' Restablece el contexto de los registros tal como estaban antes de la
                              ' interrupción.

Este programa funcionará perfectamente con un acondicionador de señales de tecnología TTL tipo 74LS14 ó 7414 para los encoders ópticos, usando por ejemplo dos TCST 1103. También puedes hacerlo con sensores Hall digitales, entonces puedes eliminar los dos 74LS14 que hacen de acondicionador. No recomiendo usar encoder mecánicos (tipo potenciómetro) cuando se usa interrupción externa como en este caso. Para cualquier tipo de duda sobre la construcción o acondicionar señales de encoders haz clic aquí.