Interrupciones en el LPC2000 (I) (draft)

Los controladores LPC2000 están construídos alrededor de un núcleo ARM7-TDMI. Éste núcleo sólo soporta dos fuentes de interrupción: FIQ e IRQ. La diferencia principal entre ellos es que FIQ tiene prioridad sobre IRQ; es decir, si la interrupción IRQ llega al mismo tiempo que la FIQ, entonces ésta última será atendida en primer lugar.

Cada periférico en el LPC2000 es capaz de generar al menos una interrupción, y en algunos casos hasta cuatro. En un chip promedio de dicha familia se tienen más de 20 fuentes de interrupción, pero el núcleo ARM sólo es capaz de atender dos, entonces ¿cómo es posible que se puedan manejar tantas fuentes con tan sólo dos líneas?. He aquí donde aparece en escena el sistema de interrupciones del LPC2000: VIC (Vectored Interrupt Controller).

VIC es un subsistema de la familia LPC2000, que aunque a primera vista parece ser confuso, obscuro y difícil de utilizar, es en realidad muy simple (por supuesto, una vez que se ha entendido cómo funciona). VIC es el encargado de recibir todas las peticiones de interrupción que cada periférico del chip pueda generar, y entonces atenderlas en el órden correcto en el momento correcto. Traduciendo del manual: “El controlador de interrupciones VIC toma 32 peticiones de interrupción y, por software, las clasifica en 3 categorías: FIQ, IRQ vectorizadas, e IRQ no vectorizadas. Este esquema significa que las prioridades de las interrupciones puden ser asignadas y ajustadas en forma dinámica“.

Las prioridades dinámicas y programables es una de las características que diferencían a este microcontrolador y lo hace superior sobre sus similares de 8 bits. Practicamente la mayoría de los controladores de 8 bits tienen fijas sus prioridades, por lo que el programador debe adecuarse a ellas, que aunque no es malo, de vez en cuando es deseable asignar las prioridades según nuestras necesidades. P. ej., si estamos tratando de implementar un RTOS en nuestro chip, entonces sería conveniente que el tick tuviera la prioridad más alta. Normalmente en los chips de 8 bits, el timer, con el cual se genera el tick, tiene una prioridad media, lo que podría causar conflictos si lo que queremos es que se atienda al tick antes que a nadie más en una situación donde dos o más interrupciones se generen al mismo tiempo.

De las tres categorías mencionadas la FIQ es la de más alta prioridad, seguida por las IRQ vectorizadas, y finalmente –con la menor de las prioridades– están las IRQ no vectorizadas. Aunque es posible asignar a la FIQ más de un periférico (o fuente de interrupción) lo más recomendable es que únicamente una de ellas sea asignada, para obtener el beneficio de ser atendida lo más pronto posible.

¿Qué significa “interrupciones vectorizadas?. Que existe un vector dividido en 16 entradas, y a cada entrada le corresponde alguna de las 32 posibles fuentes de interrupción. De estas 16, la entrada 0 (cero) tiene la más alta prioridad (con respecto a este vector), y la entrada 15 (quince) tiene la menor prioridad (otra vez, con respecto a este vector).

¿Que són las “interrupciones no vectorizadas”?. Son todas aquellas que no caen dentro de las dos categorías explicadas; y no sólo eso, sino que son tratadas de forma diferente, ya que cuando una de ellas se activa el primer paso es buscar en un registro qué periférico la generó, y a continuación buscar y ejecutar su ISR. Estos dos pasos consumen tiempo. Cabe mencionar que sería raro encontrar algún sistema que requiera atender más de diecisiete interrupciones (una FIQ y dieciséis IRQ vectorizadas), por lo que en este momento no hace falta preocuparnos por las interrupciones no vectorizadas.

Si se observa el manual, se encontrará con un mundo de registros del sistema VIC. No hay que sentirse intimidado por ello, ya que cada uno de ellos cumple un objetivo específico, como se irá develando a la largo de esta exposición. Voy a utilizar el siguiente diagrama para ir aclarando cómo y cuándo se utilizan cada uno de los registros.

El sistema VIC

He estado asumiendo que el lector está familiarizado con el uso de interrupciones de los microcontroladores (de cualquier marca y familia); también asumiré que el lector ha utilizado (o está por utilizar) las interrupciones en el LPC2000. No es necesario entender a fondo cómo se usan, ya que es posible utilizar código de ejemplo de terceros. El objetivo de este tutorial es que el lector conozca y no sea dependiente de terceros para emplear esta parte del controlador.

1. Habilitar a un periférico para que genere una interrupción

El primer paso consiste en habilitar la interrupción para un periférico en particular. Dado que cada uno de ellos es diferente, se sale del contexto de este tutorial cómo hacerlo, de ahí que asumí que ya se han utilizado. Para el presente ejemplo asumiré que se trata del periférico UART0.

Lo que sigue es buscar en el manual qué canal le corresponde a la UART0. Un canal es un número fijo asignado a cada periférico. Los canales casi nunca varían de familia a familia, sin embargo, hay que consultar el manual para estar seguros. La siguiente figura muestra el mapa de canales para el LPC2129. Noten que a la UART0 le corresponde el canal 6. Este número lo necesitaremos más adelante.

2. ¿FIQ o IRQ?

Después de haberle indicado al chip que la UART0 va a generar una interrupción, el siguiente paso es determinar se si va a tratar de una FIQ o una IRQ (en este momento es irrelevante si es vectorizada o no). Existe un registro, VICIntSelect, en el cual se le indica a VIC de qué tipo va a ser. Si escribimos un ‘1’ le indicaremos que se trata de una FIQ; si le escribimos un ‘0’, le estaremos indicando que será una IRQ. El registro VICIntSelect es de 32 bits (como la mayoría de los registros de VIC), y cada bit en este registro se corresponde con un canal del mapa de canales. Es decir, si quisiéramos que la UART0 fuera FIQ, entonces escribiríamos un ‘1’ en el bit 6; si quisiéramos que el TIMER0 (canal 4) fuera FIQ, entonces escribiríamos un ‘1’ en el bit ‘4’; y así sucesivamente. Es posible que ambas, UART0 y TIMER0, sean FIQ al mismo tiempo, pero repito, se recomienda que sólo una de ellas lo sea.

Para este ejemplo, quiero que la interrupción de la UART0 sea IRQ, por lo que habrá que escribir un ‘0’ en el bit 6, de la siguiente forma:

VICIntSelect&=~(1<<6);
3. Habilitar la interrupción en el VIC

En el paso 1 se le indicó al chip que la UART0 va a generar interrupciones. Bueno, eso no es suficiente, también hay que hacérselo saber al VIC. Existen dos registros para este propósito, VICIntEnable y VICIntClear. Al igual que el registro VICIntSelect,  cada uno de los 32 bits de estos dos registros se corresponden con un canal del mapa de canales.

Es decir, hay que escribir un ‘1’ en el bit 6 del registro VICIntEnable para que el VIC atienda la interrupción cuando ésta sea generada. ¿Y si ya estaba habilitada y la quiero deshabilitar?. Bueno, aquí es donde se usa el registro VICIntClear. Escribir un ‘0’ al registro VICIntEnable no tiene ningún efecto (al contrario de lo que dicta nuestra lógica), por lo que si requerimos deshabilitar la interrupción, entonces hay que escribir un ‘1’ en el bit 6 del registro VICIntClear.

4. Vectorizada o No vectorizada y prioridades

Este paso se divide en dos debido a que se utiliza un mismo registro para propósitos diferentes: asignar la prioridad de la interrupción, e indicar si es vectorizada o no. Existe un conjunto de 16 registros, VICVectAddrx, donde x va desde 0 –la más alta prioridad– hasta 15 –la más baja–, donde en los bits 0 a 4 se escribe el número del canal (del 0 al 31), y en el bit 5 un ‘1’ si es vectorizada, o un ‘0’ si es no vectorizada.

Continuemos con el ejemplo de la UART0 para aclarar estos conceptos . En primer lugar decidí que quiero que sea vectorizada, y por otro lado he escogido arbitrariamente un nivel de interrupción 8. Entonces lo siguiente es indicarle al VIC mis deseos: en el registro VICVectAddr8 hay que escribir:

VICVectAddr8=(6)|(1<<5);
  1. El ‘8’, en el nombre del registro VICVectAddr8, indica que queremos el nivel de prioridad 8 para las interrupciones generadas por la UART0.
  2. El ‘6’ indica el número de canal de la UART0.
  3. La expresión (1<<5) escribe el ‘1’ que necesitamos en el bit 5 para establecer que se trata de una interrupción vectorizada.
  4. La barra ‘|’ es el operador ‘OR’ cuya función es hacer una ‘suma’ entre el número del canal y el valor del bit 5.

Vale la pena apuntar lo siguiente: NO es obligatorio empezar en la prioridad 0. P. ej., supongan que sólo existen dos interrupciones, UART0 y TIMER0, y que a la primera se le asigna una prioridad 2, y a la segunda una prioridad 3. Esto es lo mismo que asignar a la UART0 prioridad 9 y al TIMER0 prioridad 12, ya que al ser las únicas interrupciones en el proyecto, la UART0 siempre va a tener la más alta prioridad.

5. Indicar el código que se hará cargo de la interrupciónla ISR (callback)

El último paso es indicarle al VIC la porción de código que se ejecutará cuando se genere la interrupción. En el argot de los embebidos a esto se le llama ISR handler. Una ISR (Interrupt Service Routine) es una función que devuelve void y no acepta argumentos, y será invocada asíncronamente (las interrupciones son asíncronas) cuando el periférico genere una interrupción.

Existe otro conjunto de 16 registros, VICVectAddrx, donde x va desde 0 hasta 15, en los cuales se escribe la dirección de la ISR. (La dirección de una función se obtiene escribiendo únicamente el nombre de ésta, sin  paréntesis ni argumentos). ¿En cuál de los 16 registros se escribe la dirección de la función?. En el mismo número que su prioridad.

Siguiendo con nuestro ejemplo de la UART0, en el paso anterior se señaló que tendría un nivel de prioridad 8, y suponiendo que la ISR de la uart sea void uart0_isr(), entonces habrá que escribir:

VICVectAddr8=uart0_isr;
  1. El ‘8’ en el nombre del registro VICVectAddr8 es el mismo que el de su prioridad.
  2. La función uart0_isr es la porción de código que se ejecutará cada vez que la UART0 genere una interrupción.
Un ejemplo completo

ejemplo

Anuncios

Un comentario sobre “Interrupciones en el LPC2000 (I) (draft)

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s