tamaño: 586422B

Anuncio
Práctica4
Programación de E/S mediante
interrupciones
Luis Piñuel
2
Aviso legal
Este documento está sujeto a una licencia Reconocimiento - NoComercial - CompartirIgual 3.0 Unported de Creative Commons (http://creativecommons.org/licenses/by-ncsa/3.0/deed.es_ES).
Basado en "Prácticas de Estructura de Computadores empleando un MCU
ARM" de Luis Piñuel y Christian Tenllado publicado bajo licencia Reconocimiento - NoComercial - CompartirIgual 3.0 Unported de Creative Commons
(http://creativecommons.org/licenses/by-nc-sa/3.0/deed.es_ES)
Índice general
4.1. Objetivos de la práctica . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
4.2. Gestión de interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
4.2.1. Habilitar/deshabilitar las interrupciones. . . . . . . . . . . . . . . . .
4
4.2.2. Determinar el origen de una interrupción. . . . . . . . . . . . . . . .
5
4.3. Uso de pines GPIO para generación de interrupciones
. . . . . . . . . . . .
6
4.3.1. Lectura sobre pines configurados como entrada . . . . . . . . . . . .
6
4.3.2. Resumen de configuración GPIO . . . . . . . . . . . . . . . . . . . .
7
4.4. Configuración y uso del timer . . . . . . . . . . . . . . . . . . . . . . . . . .
8
4.5. Desarrollo de la Práctica . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
Bibliografía
4.1.
10
Objetivos de la práctica
En esta práctica profundizaremos en el Sistema de Entrada/Salida, en concreto veremos
la programación mediante interrupciones. Los principales objetivos de la práctica son:
Ampliar conocimientos sobre el sistema de E/S.
Programar rutinas de tratamiento de interrupción.
Aprender a programar temporizadores (timers).
El alumno partirá de los códigos elaborados en la práctica anterior y deberá modificarlos
de manera adecuada para cumplir los objetivos de esta práctica.
4.2.
Gestión de interrupciones
Si bien el procesador ARM1176JZF-S que se incluye en la Raspberry Pi tiene soporte
para la gestión vectorizada de interrupciones, el SoC BCM2835 no hace uso de dicho soporte
y obliga a realizar una encuesta para determinar el origen de una interrupción.
La gestión de interrupciones es relativamente sencilla en este chip: basta con habilitar la
interrupción deseada en uno de los tres registros de configuración y habilitar globalmente
3
4
ÍNDICE GENERAL
las interrupciones IRQ en el registro de estado. Posteriormente, cuando se recibe una
interrupción se determinará el origen leyendo los registros de interrupción pendiente (IRQ
pending) hasta dar con el dispositivo que originó la interrupción.
4.2.1.
Habilitar/deshabilitar las interrupciones.
El SoC BCM2835 dispone de tres pares de registros para habilitar/deshabilitar selectivamente las interrupciones de cada dispositivo:
IRQ_ENABLE_IRQS_1 (0x2000B210) - IRQ_DISABLE_IRQS_1 (0x2000B21C)
IRQ_ENABLE_IRQS_2 (0x2000B214) - IRQ_DISABLE_IRQS_2 (0x2000B220)
IRQ_ENABLE_BASIC (0x2000B218) - IRQ_DISABLE_BASIC (0x2000B224)
Las tablas mostradas en las figuras 4.1 y 4.2 recogen las posibles fuentes de interrupción
(ver Capítulo 7 de [bcm]) para periféricos GPU (0-63) y para periféricos básicos respectivamente. Para el objetivo de esta práctica, nos basta con dos tipos de interrupción: timer
y gpio_int.
Figura 4.1: Tabla de interrupciones de periféricos GPU.
Figura 4.2: Tabla de interrupciones de periféricos básicos.
La del timer es considera una interrupción básica y por tanto dispone de un bit específico en el registro Base interrupt entable register. En concreto, para habilitar la interrupción
4.2. GESTIÓN DE INTERRUPCIONES
5
del timer deberemos escribir un ’1’ en el bit 0 del registro IRQ_ENABLE_BASIC (dirección 0x2000B218). Para deshabitar esta interrupción basta con escribir un ’1’ en el bit
correspondiente del registro IRQ_DISABLE_BASIC (0x2000B224).
Para las interrupciones por GPIO (gpio_int) debemos escribir en el registro
IRQ_ENABLE_IRQS_2 que es el que controla las fuentes de interrupción 32-63. Concretamente, debemos escribir ’1’ en los bits 17-20 de dicho registro (esto es, escribir 0xF a
partir del bit 17), ya que la entrada 49 (gpio_int[0] ) se corresponde con el bit 49−32 = 17.
4.2.2.
Determinar el origen de una interrupción.
Una vez que llega una interrupción IRQ, se comenzará a ejecutar la rutina de tratamiento que hayamos registrado. En esa rutina, deberemos consultar los registros de interrupciones pendientes para determinar la(s) fuente(s) de interrupción pendiente(s) y así
poder atenderlas. Los registros implicados en este caso son los siguientes:
IRQ_BASIC (0x2000B200)
IRQ_PEND1 (0x2000B204)
IRQ_PEND2 (0x2000B208)
La figura 4.3 muestra la relación entre estos registros y la figura 4.4 muestra el significado de los bits más relevantes del registro IRQ_BASIC.
Figura 4.3: Registros de interrupciones pendientes.
Para el caso del timer la identificación de la interrupción es sencilla, pues basta con
consultar el bit 0 del registro IRQ_BASIC (0x2000B200). Para otros dispositivos, además
de los bits correspondientes del registro IRQ_PEND2 hay que consultar sus registros
específicos. Para el caso concreto del GPIO, se explica en la sección siguiente.
6
ÍNDICE GENERAL
Figura 4.4: Registro de interrupciones básicas pendientes.
4.3.
Uso de pines GPIO para generación de interrupciones
Como vimos en la práctica anterior, el SoC BCM2835 dispone de 54 pines de uso general
(General Purpose Input/Output), que pueden configurarse como entrada, como salida o
para otras funciones alternativas (ver el Capítulo 6 de [bcm] para más información).
4.3.1.
Lectura sobre pines configurados como entrada
Es preciso recordar que si un pin ha sido configurado como entrada, tenemos varias
alternativas para consular su valor: hacerlo de forma explícita o configurándolo para que
interrumpa al procesador cuando haya un cambio en su nivel lógico.
Lectura explícita. Como hemos visto ya previamente, para conocer el valor lógico de
un pin, basta con realizar una lectura del bit correspondiente en el registro GPLEV0 o
GPLEV1 según corresponda: de forma análoga a las lecturas, para consultar el valor los pines GPIO0 - GPIO31 deberemos realizar una lectura del registro GPLEV0 (0x20200034).
Para el resto de pines, deberemos consultar el registro GPLEV1 (0x20200038).
Generación de interrupciones. Cuando un pin GPIO se establece como entrada puede
configurarse de modo que genere una interrupción cuando se detecte un flanco (de subida
o de bajada). El controlador de GPIO del SoC BCM2835 permite establecer diferentes
circunstancias que conducen a la generación de una interrupción. Aquí sólo enumeraremos
las dos más relevantes para nuestros propósitos:
Generación de interrupción por detección síncrona de un flanco de subida. Se generará una interrupción si se detecta el patrón 011 en el pin. Para configurar un pin de
este modo es necesario escribir en el registro GPREN0/1 (direcciones 0x2020004C y
0x20200050 respectivamente). Así, escribir un ’1’ en el bit 18 del registro GPREN0
implica que se generará una interrupción IRQ (si el controlador IRQ está configurado correctamente) cada vez que en el pin GPIO18 se detecte un flanco de subida
(eliminando rebotes gracias a la detección síncrona).
4.3. USO DE PINES GPIO PARA GENERACIÓN DE INTERRUPCIONES
7
Generación de interrupción por detección síncrona de un flanco de bajada. Se generará una interrupción si se detecta el patrón 100 en el pin. Para configurar un pin de
este modo es necesario escribir en el registro GPFEN0/1 (direcciones 0x20200058 y
0x2020005C respectivamente). Así, escribir un ’1’ en el bit 18 del registro GPFEN0
implica que se generará una interrupción IRQ (si el controlador IRQ está configurado correctamente) cada vez que en el pin GPIO18 se detecte un flanco de bajada
(eliminando rebotes gracias a la detección síncrona).
Cuando se produzca un flanco en un pin configurado como quedó explicado en el párrafo
anterior, se generará la consecuente interrupción por las líneas gpio_int[0-3] 1 y este hecho
quedará reflejado en los registros GPEDS0/1 : por ejemplo, el bit 18 del registro GPEDS0
se pondrá a 1 cuando se detecte el evento configurado (flanco de subida o bajada). Esta
información es relevante pues, como ya hemos explicado, la gestión de interrupciones no es
vectorizada en el SoC BCM2835 y es necesario realizar una encuesta a todos los dispositivos
que puedan generar interrupciones. Los registros GPEDS0/1 son de lectura/escritura: la
lectura nos informa de la ocurrencia de la interrupción y la escritura implica limpiar el bit
correspondiente (esto es, escribir 0x00000011 en el registro GEPDS0 pone a 0 los bits 0 y
4 de dicho registro, notificando así que se ha reconocido ya la interrupción).
4.3.2.
Resumen de configuración GPIO
A modo de resumen de esta sección, la tabla 4.1 muestra todos los registros involucrados
en la configuración y uso del puerto GPIO con un pequeño comentario de su cometido:
Nombre(s)
GPFSEL0-5
Dirección(es)
0x20200000 - 0x20200014
GPTSET0/1
0x2020001C - 0x20200020
GPTCLR0/1
0x20200028 - 0x2020002C
GPLEV0/1
0x20200034 - 0x20200038
GPREN0/1
GPFEN0/1
GPEDS0/1
0x2020004C - 0x20200050
0x20200058 - 0x2020005C
0x20200040 - 0x20200044
GPPUD
GPPUDCLK0/1
0x20200094
0x20200098 - 0x2020009C
Cometido
Configuración de cada GPIO como entrada
o salida
Forzar un 1 lógico en GPIOs configurados
como salida
Forzar un 0 lógico en GPIOs configurados
como salida
Consulta del valor lógico de GPIOs configurados como entrada
Habilitar interrupciones por flanco de subida
Habilitar interrupciones por flanco de bajada
Indica si se ha producido una interrupción en
un GPIO
Configuración pull-up-pull-down
Configuración pull-up-pull-down
Tabla 4.1: Registros de configuración de puertos GPIO
1
La documentación del BMC2835 es confusa a este respecto, para simplificar se puede considerar sólo
gpio_int[3] ya que se activa siempre, sea cual sea el tipo de detección (nivel/flanco).
8
4.4.
ÍNDICE GENERAL
Configuración y uso del timer
El SoC BCM2835 dispone de varios timers: el System Timer, un periférico con varios
canales, y el timer propietario de ARM, basado en el SP804. Nosotros usarmos este último,
documentado en el capítulo 14 de la documentación del SoC BCM2835.
El funcionamiento de un timer es sencillo: se trata de un contador descendente que, al
llegar a 0, produce una interrupción (si así lo hemos configurado, obviamente). La frecuencia
de interrupción viene determinada por los siguientes factores:
Frecuencia de reloj principal. Al módulo timer llega una señal de reloj a una determinada frecuencia. Habitualmente, esta frecuencia es la del bus a la que se conecte
este periférico. En nuestro caso, el reloj del sistema es de 250MHz (no confundir con
el reloj del procesador, que funciona a 700MHz).
Divisor de frecuencia y pre-escalado. La mayoría de los timers disponen de algún
módulo que permite reducir la frecuencia de entrada. La señal de reloj así obtenida es
la que alimenta el contador, controlando de ese modo la frecuencia de interrupciones.
Valor de inicialización del contador. Cada vez que el contador llega a 0, podemos cargar de nuevo en valor para comenzar una nueva cuenta atrás o podemos configurarlo
para que dicha carga se haga automáticamente a un valor por defecto. Obviamente,
a mayor valor del contador, menor frecuencia de interrupción.
En la tabla 4.4 incluimos los registros de control del timer presente en nuestro SoC y,
a continuación, detallamos los más relevantes para esta práctica.
Dirección
0x200B400
0x200B404
0x200B408
0x200B40C
0x200B410
0x200B414
0x200B418
0x200B41C
0x200B420
Descripción
Load
Value (sólo lectura)
Control
IRQ Clear/ACK (sólo escritura)
RAW IRQ (sólo lectura)
Masked IRQ (sólo lectura)
Reload
pre-divider
Contador libre
Tabla 4.2: Registros del timer ARM
Registro de control (ARM_TIMER_CTL) Permite configurar varios aspectos importantes del funcionamiento del timer. Resaltamos los más relevantes (se puede consultar la funcionalidad de cada bit en la documentación del SoC):
bit 1. Selección de contador de 16 o 32 bits
bits 3:2. Configuración del factor de pre-escalado. Se puede escoger entre 1, 16
y 256.
bit 5. Habilitar/deshabilitar la generación de interrupciones por el timer.
4.5. DESARROLLO DE LA PRÁCTICA
9
bit 7. Habilitar/deshabilitar el timer.
En el código entregado este registro se configura habilitando las interrupciones (y
el propio timer, claro está), sin utilizar el factor de pre-escalado y seleccionando el
contador de 32 bits (se escribe el valor 0x003E00A2)
Registro load (ARM_TIMER_LOD). Almacena el valor al que se inicializa el contador.
Registro reload (ARM_TIMER_RLD). Almacena el valor al que se inicializa el contador tras llegar a 0 la cuenta.
Registro divisor de frecuencia (ARM_TIMER_DIV ). En el código se asigna el valor
0x000000F9 (249 en base 10), por lo que la frecuencia de entrada se divide entre
(249 + 1) = 250. En este caso, la frecuencia resultante es 1MHz.
Registro de reconocimiento de interrupción (ARM_TIMER_CLI ). Al escribir
(cualquier valor ) en este registro, el bit de interrupción pendiente se borra, evitando
que se vuelva a reconocer al siguiente ciclo.
Para finalizar, recordar que para que las interrupciones generadas por el timer lleguen
al procesador, es imprescindible configurar correctamente el controlador de interrupciones
escribiendo un 1 en el bit 0 del registro IRQ_ENABLE_BASIC.
4.5.
Desarrollo de la Práctica
Se extenderá la práctica anterior para gestionar dos fuentes de interrupciones: las originadas por los botones y por el timer : se deberá diseñar un sistema en el que el LED
esté parpadeando con una determinada frecuencia inicial. Al pulsar uno de los botones,
dicha frecuencia se incrementará, mientras que al pulsar el otro botón la frecuencia se
decrementará.
En concreto, el alumno deberá completar las siguiente funciones:
1. enable_irq (fichero init.s). Completa esta rutina que deberá habilitar las interrupciones IRQ modificando el bit pertinente del registro de estado 2
2. setup_gpio() (fichero main.c). Esta función deberá configurar los GPIO 23 y 18 como
entrada y el GPIO 17 como salida. Las dos entradas se configurarán en pull-up y de
modo que generen interrupciones en flanco de bajada.
3. setup_irq() (fichero main.c). Esta función habilita las interrupciones de GPIO y de
timer y escribe en el registro de estado para habilitar cualquier interrupción IRQ.
4. isr_irq() (fichero main.c). Esta será la rutina de tratamiento de interrupción de IRQ
y realizará la encuesta para determinar quién originó la interrupción. En primer lugar,
preguntará si la interrupción se debió al timer, para lo que consultará el registro
IRQ_BASIC. Si no es así, comprobará si alguno de los GPIO configurados como
entrada (el 18 y el 23) son el origen de la interrupción (para ello consultará el registro
GPEDS0 ).
2
IMPORTANTE: no se puede ejecutar esta rutina en modo USER por lo que resulta más cómodo dejar
el sistema en modo SVC y no pasar nunca al modo USER
10
ÍNDICE GENERAL
5. irq_timer() (fichero main.c). Función a la que se invocará desde isr_irq() cada vez
que llegue una interrupción del timer. En ella se cambiará el estado del LED (encendido / apagado).
6. irq_gpio23() y irq_gpio18() (fichero main.c). Función a la que se invocará desde
emphisr_irq() cada vez que llegue una interrupción por GPIO23 (resp. GPIO18).
Cada vez que se invoquen se disminuirá (resp. aumentará) la frecuencia del timer.
7. irq_changeFrec(int frecTimer) (fichero main.c). Función que cambia la frecuencia del timer asignando el valor que recibe como argumento en los registros
ARM_TIMER_LOD y ARM_TIMER_RLD.
Bibliografía
[bcm] Bcm2835 arm peripherals.
11
Descargar