Interrupciones en MSP430

MSP430 Interrupciones

En el artículo anterior exploramos los mecanismos básicos de entrada/salida del microcontrolador MSP430. Para ello usábamos una técnica llamada polling o sondeo en la que explícitamente preguntábamos en cada instante el estado del pulsador para poder actuar en consecuencia. Esta técnica es sencilla y directa, pero tiene algún que otro inconveniente. Desde el punto de vista del programador, el código puede complicarse si tenemos que controlar varias señales de E/S y además el código suele ser propenso a errores si no tenemos cuidado. Por otro lado, desde el punto de vista del consumo energético es bastante ineficiente, ya que el micro se pasa todo el tiempo sumido en un bucle infinito preguntando por el estado del pulsador. Huelga decir que en un sistema autónomo alimentado con baterías puede hacer que nuestro diseño sea inviable. Para solventar esta situación se utilizan, entre otras técnicas, las interrupciones.

El mecanismo de las interrupciones permite que los periféricos, tanto internos como externos, informen a la CPU que ha ocurrido un evento (por ejemplo, la pulsación de un botón). Cuando esto ocurre, el procesador interrumpe el código que se estaba ejecutando y se invoca a una función especial llamada Rutina de Tratamiento de Interrupción (RTI) o Interrupt Service Routine (ISR) que puede ser creada por el programador.

Sin entrar en demasiados detalles, hay interrupciones que pueden activarse y desactivarse por parte del programador. Son las llamadas interrupciones enmascarables. Por otra parte, están las interrupciones no enmascarables (NMI) sobre las que el programador no tiene control. Por ejemplo, la que gestiona el proceso de RESET. Por supuesto, nos vamos a centrar en las primeras, que pueden ser activadas de forma individual para los diferentes puertos de E/S y a nivel global mediante el bit GIE del registro de estado.

Para saber dónde encontrar la rutina de tratamiento de interrupción asociada a cada tipo de interrupción el microcontrolador usa una tabla de vectores de interrupción. En el caso del MSP430 es la que se muestra a continuación y que he obtenido del datasheet.

Tabla de vectores de interripción

Como vemos, hay 64 vectores de interrupción, cada uno asociado a una fuente de interrupción (primera columna) y a una o varias interrupciones (segunda columna). Esto quiere decir que una misma fuente de interrupciones puede generar varios tipos de interrupción diferente. Por ejemplo, la fuente I/O Port 1 (es decir el puerto P1 de E/S) puede generar 8 interrupciones diferentes; una por cada uno de los 8 pines que componen el puerto P1.

En la tercera columna se indica si la interrupción es o no enmascarable. La cuarta columna es la dirección donde se almacena el vector que apunta a la rutina de tratamiento de interrupción (en esa dirección almacenamos los 2 bytes que apuntan a la rutina de tratamiento). La quinta y última columna indica la prioridad de la interrupción (un número mayor indica mayor prioridad). Si se activan más de una interrupción a la vez se procesan según el orden de prioridad. En todo caso, no se sigue un esquema apropiativo, lo que quiere decir que si llega una interrupción más prioritaria que la que se está ejecutando, ésta no se interrumpe, sino que acaba y seguidamente se atiende a la siguiente con más prioridad.

En este artículo vamos a centrarnos en dos fuentes de interrupción: los puertos de E/S P1 y P2 (en próximos artículos nos ocuparemos de otras fuentes). Si hacemos memoria, el MSP430 tiene 8 puertos de E/S, sin embargo, sólo los puertos P1 y P2 son fuente de interrupciones. Esto quiere decir que ninguno de los restantes puertos pueden generarlas.

 

Programar las interrupciones

En el artículo anterior vimos cómo se gestionaban los registros de E/S para cada puerto. En concreto había tres de los que no habíamos hablado y que están relacionados con la gestión de interrupciones. Son los siguientes:

PxIES
Los puertos 1 y 2 permiten el uso de interrupciones. Este registro permite seleccionar si las interrupciones son disparadas en el flanco de subida o de bajada.

PxIFG
Se activa cuando ocurre una interrupción en el puerto x.

PxIE
Permite habilitar las interrupciones en el puerto x.

 

El registro PxIE nos permite activar y desactivar las interrupciones en un pin concreto de los puertos P1 o P2. Por ejemplo, si queremos activar las interrupciones en el pin P1.1 activaríamos el flag con la siguiente instrucción.

P1IE |= BIT1;

Si además queremos que la interrupción se dispare durante el flanco de bajada, usamos la instrucción

P1IES |= BIT1;

Para que se dispare durante el flanco de subida pondremos el bit a 0.

P1IES &= ~BIT1;

Para saber si se ha activado una interrupción concreta podemos consultar el valor del registro PxIFG. Por ejemplo, para saber si hay una interrupción pendiente en el puerto p1.1 podemos usar el siguiente condicional.

if (P1IFG & BIT1) { ... }

Este registro puede escribirse y de hecho es importante borrar el flag de interrupción manualmente después de haber tratado la interrupción. Hay fuentes de interrupción que la borran automáticamente si la fuente sólo puede disparar un tipo de interrupción, pero para no equivocarnos, mejor limpiar siempre el bit.

Para activar las interrupciones a nivel global hay que poner a 1 el bit GIE del registro de estados, lo que podemos hacer con

__bis_SR_register(GIE);

o alternativamente con

__enable:interrupt();

 

La rutina de tratamiento de interrupción (RTI)

Ya sabemos cómo configurar el microcontrolador para activar una interrupción concreta, pero nos falta definir una rutina de tratamiento de interrupción que la gestione. Una RTI es una función normal en la que tendremos que tener algunas precauciones. La regla más importante es salir de la rutina lo más rápidamente posible, así que prohibidos bucles largos o de espera. Para indicar que una función es una RTI usamos las siguientes directivas del preprocesador de C (en el compilador de TI. Otros pueden usar otras directivas diferentes).

#pragma vector=[fuente de interrupción]

__interrupt

Si queremos usar como fuente de interrupciones el puerto P1 usaríamos

#pragma vector=PORT1_VECTOR

Y para el puerto P2

#pragma vector=PORT2_VECTOR

Con el siguiente código de ejemplo quedarán las cosas más claras.

 

Código de ejemplo

El siguiente código tiene el mismo comportamiento que el ejemplo del artículo anterior, sólo que ahora se hace con interrupciones en lugar de polling. Se activa la interrupción del puerto P1.1, que es donde está conectado el pulsador y se entra en un bucle de espera infinito. Sólo cuando el botón se pulsa se ejecuta la RTI, que se encarga de encender el LED conectado a P1.0 mientras el botón permanece pulsado.

#include <msp430.h>
int main(void)
{
  WDTCTL = WDTPW + WDTHOLD; // Detiene el watchdog
  P1DIR |= BIT0;            // P1.0 (LED) como salida digital
  P1REN |= BIT1;	    // Resistencia en P1.1 (pulsador derecho)
  P1OUT |= BIT1;	    // Modo pull-up
  P1IE |= BIT1;		    // habilita la interrupción de P1.1
  P1IES |= BIT1;	    // Interrupción por flanco de bajada
  P1IFG &= ~(BIT1);	    // baja flag de interrupción de P1.1

  // Activar interrupciones
  // también puede usarse: __enable_interrupt()
  __bis_SR_register(GIE);   

  // bucle infinito. Poco eficiente energéticamente. 
  // Ya lo mejoraremos.
  while (1)
    __no_operation();
}

#pragma vector=PORT1_VECTOR
__interrupt
void P1_ISR(void) {
  if (P1IFG & BIT1) {	// comprobar fuente de la interrupción P1.1
    if (P1IN & BIT1) {	// Estado de P1.1: si está a 1 (no pulsado)
      P1OUT &= ~BIT0;   // Apaga LED
      P1IES |= BIT1;	// activación por flanco de bajada
    } else {            // y si no (pulsado),
      P1OUT |= BIT0;    // Enciende LED
      P1IES &= ~BIT1;	// activación por flanco de subida
    }
    P1IFG &= ~BIT1;	// puesta a cero del flag de interrupción P1.1
  }
}

 

Los comentarios del código son bastante claros, por lo que no deberías tener problema en seguirlo. Sólo algunas notas:

La primera instrucción de la RTI comprueba si realmente la fuente de interrupción es la que esperamos. Es importante cerciorarse ya que la interrupción podría haber sido generada por cualquier otro pin del puerto. En este caso sólo es posible que llegue desde P1.1, pero es buena práctica hacer la comprobación. Existe otra forma de comprobar la procedencia, pero lo veremos próximamente.

Para evitar problemas de rebote del pulsador, se ha activado la interrupción en el flanco de bajada cuando el botón no está pulsado. Tras la pulsación, se modifica la interrupción para que se dispare en el flanco de subida, es decir, cuando se suelte el botón, ya que si no, la interrupción sólo se volvería a activar cuando ocurriera otra pulsación, y no cuando se suelte el pulsador, que es el evento que nos interesa detectar tras la pulsación del botón.

Por último antes de salir de la RTI es importante borrar el flag de interrupción del puerto P1.1, ya que si no lo borramos, se volvería a disparar la interrupción nada más salir de la RTI y el programa quedaría ejecutando esta rutina indefinidamente.

Puede que te estés preguntando si realmente hay mejor gestión del consumo energético en este código que en el del artículo anterior. La respuesta es que no, ya que seguimos teniendo un bucle infinito que mantiene al microcontrolador ocupado dando saltos de un lado a otro. En próximos artículos solucionaremos este “pequeño” detalle cuando usemos los modos de bajo consumo junto con las interrupciones para obtener diseños mucho más eficientes energéticamente.

 

CompartirShare on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

Sé el primero en comentar en "Interrupciones en MSP430"

Deja un comentario.

Tu dirección de correo no será publicada.


*