Interrupción por Cambio en Puerto

Sabemos que el microcontrolador tiene múltiples fuentes de interrupción, ya estudiamos la interrupción por Pin Externo, ahora es el turno para la interrupción por Cambio en Puerto. Esta funciona de un modo similar a la anterior, con diferencia que podemos detectar el cambio de estado en cualquiera, algunos, o todos los pines de un puerto, para nuestro caso, el puerto B. Es decir que podemos conectar más de un botón (o distintos tipos de sensores digitales) y detectar cuál fue el que disparó la interrupción.

Para configurar la interrupción por Cambio en Puerto debemos volver al registro INTCON.

Registro INTCON

GIE PEIE TMR0IE INTE IOCIE TMR0IF INTF IOCIF
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

En el bit 3 encontramos a IOCIE, este bit es el encargado de habilitar o deshabilitar la interrupción por Cambio en Puerto. En el bit 0 está IOCIF que es la bandera, esta se pone en 1 al momento que se dispare la interrupción.

IOCIE: Interrupción por cambio de estado en puerto.

 1 = Habilita la interrupción por cambio de estado en puerto.

 0 = Deshabilita la interrupción por cambio de estado en puerto.

IOCIF: Bandera de interrupción por cambio de estado en puerto.

1 = Uno de los pines ha cambiado de estado.

0 = Ninguno de los pines ha cambiado de estado.

Recordemos que para poder usar cualquier interrupción debe estar habilitado el bit 7 GIE.

El cambio de estado que se puede detectar es por flanco de bajada o por flanco de subida, y podemos configurarlo independientemente, es decir, para algunos bits interrupción por cambio de estado en flanco de subida y para otros por flanco de bajada. Esta configuración la hacemos en los registros IOCBN para flanco de bajada  e IOCBP para flanco de subida, poniendo en 1 el bit correspondiente al pin del puerto se habilitara para cada uno. Algo que debemos tener en cuenta, es que si vamos a configurar ambos registros, hay que ser precavidos de no sobrescribir el registro anterior, se recomienda enmascarar la asignación al registro o simplemente configurar uno a uno cada bit.

En el ejemplo anterior usamos un solo botón con resistencia pull-up para mantener la entrada en 1 y por medio del botón pasar a 0. Esta resistencia es una resistencia física, que ocupa espacio en el circuito, para este ejemplo vamos a usar las resistencias pull-up internas que tiene el microcontrolador, de ese modo ahorramos espacio y aprovechamos un poco más las características que nos ofrece el microcontrolador.

Para habilitarlas debemos poner en 0 el bit WPUEN del registro OPTION_REG, luego de hacerlo en el registro WPUB, ponemos en 1 los bits correspondientes a los pines que queremos que tengan las resistencias de pull-up habilitadas, las demás las dejamos en 0.

Hasta este punto sabemos configurar el microcontrolador de modo que pueda disparar las interrupciones, ahora debemos entender cómo atenderlas, no basta con saber que la bandera IOCIF está en 1, necesitamos también conocer cuál de todos los pines del puerto fue el que originó la interrupción, para esto debemos ir al registro IOCBF, en él están las banderas independientes por cada pin del puerto estas se ponen en 1 señalando por cuál de los pines se generó la interrupción.

Al final de cada interrupción se deben poner en 0 nuevamente todas las banderas.

Orden de configuración, ejecución y atención de una interrupción por Cambio en Puerto:

  • Configurar el Puerto B como entradas digital.
  • En el registro INTCON poner en 1 el bit IOCIE.
  • Establecer el flanco por el cual se disparará la interrupción en los registros IOCBN y/o IOCBP.
  • Habilitar las interrupciones globalmente escribiendo un 1 en el bit GIE del registro INTCON.
  • Al presentarse la interrupción por Cambio en Puerto la bandera IOCIF se pondrá en 1 automáticamente.
  • El programa del microcontrolador “salta” a la función asociada al vector de interrupciones, comúnmente llamada isr.
  • Dentro de la función se debe de asegurar si la bandera IOCIF está en 1, preguntando con un if.
  • Una vez confirmado, se debe revisar el registro IOCBF para determinar por qué pin se originó la interrupción.
  • Ejecutar el código correspondiente que atienda la interrupción, se recomienda que sea muy breve.
  • Poner en 0 manualmente la bandera IOCIF al igual que las banderas en IOCBF.
  • Luego de terminar la rutina del vector de interrupciones, el programa regresa al punto donde se quedó al momento de presentarse la interrupción.

En la anterior lista de pasos no se tuvo en cuenta la configuración de las resistencias de Pull-up internas, dado que no es obligatorio usarlas, es opcional por parte del diseñador del circuito y programa.

En el siguiente ejemplo vamos a usar un circuito similar al anterior, para este caso tendremos 4 botones conectados a los primeros 4 bits del puerto B, no se usaran las resistencias de pull-up físicas, sino que se configuraran por software (resistencias internas).

El programa consisten en un display de 7 segmentos que enciende y apaga con el número 0, cada que se pulsa uno de los botones, se genera la interrupción que hace que por dos segundos se muestre en el display el número correspondiente al botón.

Este es el circuito:

Y este es el código:

#include <xc.h>

#pragma config FOSC = XT        // Oscilador con cristal de cuarzo de 4MHz conectado en los pines 15 y 16
#pragma config WDTE = OFF       // Perro guardian (WDT deshabilitado)
#pragma config MCLRE = ON       // Master clear habilitado (pin reset)

#define _XTAL_FREQ 4000000      // Oscilador a 4MHz

// Definimos macros
#define DISPLAY PORTA           // Display de 7 segmentos conectado a los bits
                                // menos significativos del puerto A

void interrupt isr(void);       // Se declara la función que ejecuta al momento
                                // de presentarse cualquier interrupción

void main(){
    // Configuración de puertos
    TRISA = 0XF0;               // RA0, RA1, RA2 Y RA3 SALIDAS
                                // RA4, RA5, RA6 Y RA7 ENTRADAS
    ANSELA = 0X00;              // Puerto A digital
    TRISB = 0X0F;               // Puerto B con RB0 a RB3 como entradas
    ANSELB = 0X00;              // Puerto B digital
    PORTA = 0;                  // Limpiamos puerto A
    PORTB = 0;                  // Limpiamos puerto B
    
    
    OPTION_REGbits.nWPUEN = 0;  // Habilitamos el uso de resistencias pull-up internas
    WPUB = 0X0F;                // pull-up internas para RB0, RB1, RB2 y RB3
    
    // Configuración de la interrupción
    INTCONbits.IOCIE = 1;       // Habilitamos la interrupción por cambio en puerto
    IOCBN = 0X0F;               // Interrupción por flanco de bajada, en los pines RB0, RB1, RB2 y RB3.
    INTCONbits.GIE = 1;         // Habilitamos las interrupciones
    
    // Este ciclo mostrara en el display un parpadeo del numero cero.
    while(1){                   // Programa que se ejecuta continuamente
        DISPLAY = 0;            // Enviamos al diplay un 0
        __delay_ms(500);        // Esparamos 500 milisegundos
        DISPLAY = 15;           // Enviamos al diplay un 15, que para el integrado 7448
                                // representa la todos los segmentos apagados.
        __delay_ms(500);        // Esperamos 500 milisegundos
    }
}

// Función que se llama cada que aparezca una interrupción
void interrupt isr(void){
    // Preguntamos si la bandera de interrupción por cambio en puerto se disparó.
    if(INTCONbits.IOCIF){
        // Ahora debemos preguntar que bit tuvo el cambio.
        // Verificamos las banderas en IOCBF.
        if(IOCBF == 1){                 // Bandera para boton en RB0
            DISPLAY = 1;                // Enviamos un 1 al display
            __delay_ms(2000);           // Por dos segundo
        }else if(IOCBF == 2){           // Bandera para boton en RB1
            DISPLAY = 2;                // Enviamos un 2 al display
            __delay_ms(2000);           // Por dos segundo
        }else if(IOCBF == 4){           // Bandera para boton en RB2
            DISPLAY = 3;                // Enviamos un 3 al display
            __delay_ms(2000);           // Por dos segundo
        }else if(IOCBF == 8){           // Bandera para boton en RB3
            DISPLAY = 4;                // Enviamos un 4 al display
            __delay_ms(2000);           // Por dos segundo
        }
        // Al final debemos volver a 0 todas la banderas.
        IOCBF = 0;                      // Banderas de interrupción en pin de un puerto a 0.
        IOCIF = 0;                      // Bandera de interrupción por cambio en puerto a 0.
    }
}