tamaño: 2291633B

Anuncio
Práctica3
Modos de ejecución y gestión de
excepciones. Programación de
Entrada/Salida
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
3.1. Objetivos de la práctica . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3.2. Excepciones y modos de ejecución
. . . . . . . . . . . . . . . . . . . . . . .
4
3.2.1. Modos de Ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
3.2.2. Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.3. Mapa de memoria
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3.1. Direcciones físicas del ARM . . . . . . . . . . . . . . . . . . . . . . . 13
3.3.2. Direcciones de bus del SoC BCM2835 . . . . . . . . . . . . . . . . . 13
3.4. Configuración y uso de pines GPIO . . . . . . . . . . . . . . . . . . . . . . . 13
3.4.1. Escritura sobre pines configurados como salida . . . . . . . . . . . . 14
3.4.2. Lectura sobre pines configurados como entrada . . . . . . . . . . . . 14
3.4.3. Configuración de pines como pull-up o pull-down . . . . . . . . . . . 15
3.4.4. Resumen de configuración GPIO . . . . . . . . . . . . . . . . . . . . 15
3.5. Gertboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.5.1. Cableado y colocación de jumpers en la Gertboard . . . . . . . . . . 16
3.5.2. Uso de LEDs y pulsadores . . . . . . . . . . . . . . . . . . . . . . . . 17
3.6. Desarrollo de la Práctica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.6.1. Primera parte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.6.2. Segunda parte
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Bibliografía
3.1.
23
Objetivos de la práctica
En esta práctica finalizaremos el estudio del procesador ARM1176JZF-S analizando sus
modos de ejecución y sus excepciones. Asimismo estudiaremos el sistema E/S compuesto
por el System-on-Chip BCM2835 de Broadcom en el que se integra este procesador y las
placas Raspberry Pi y Gertboard. Los principales objetivos de la práctica son:
Conocer los modos de ejecución del procesador.
Entender el sistema de tratamiento de excepciones.
3
4
ÍNDICE GENERAL
Conocer el sistema de E/S de la placa.
Aprender a programar algunos dispositivos básicos (LEDs y pulsadores).
Concretamente, el alumno deberá preparar dos programas. Un primer programa que
configure correctamente las pilas de los distintos modos de ejecución del ARM y así como
las direcciones de las rutinas de tratamiento de excepción, y que permita comprobar que se
ejecutan correctamente cuando se produce la excepción. Un segundo programa que maneje
a nivel básico los pines GPIO para encender y apagar un LED de mediante pulsadores.
3.2.
Excepciones y modos de ejecución
Una excepción es un mecanismo que permite atender eventos inesperados, con origen
interno (ej: intento de ejecutar una instrucción no definida) o externo (ej: solicitud de
interrupción externa por parte de un dispositivo). Normalmente cuando el origen es externo
se utiliza el nombre de interrupción.
La idea es sencilla, cuando se produce una excepción el procesador interrumpe de forma
controlada su ejecución y pasa a ejecutar una rutina específica (habitualmente denominada
Interrupt Service Routine o simplemente ISR) que tratará esa excepción. Esta rutina no
puede ser diseñada como una subrutina corriente, siguiendo el AAPCS. Como la excepción
es un evento no controlado por el programador, la rutina de tratamiento de la excepción
debe preservar el estado del procesador completo, es decir los registros R0-R151 y el CPSR.
Así, cuando finaliza el tratamiento de la excepción puede restaurarse el estado del procesador y retomar la ejecución del programa en el punto en que se dejó. Además, debemos tener
en cuenta que pueden producirse varias excepciones simultáneamente, por lo que deberán
establecerse prioridades a la hora de atenderlas.
Las excepciones en los procesadores de ARM son por defecto autovectorizadas. Esto
quiere decir que cuando se produce una excepción, el procesador ejecuta automáticamente
la instrucción ubicada en una dirección de memoria específica, que únicamente depende
del tipo de excepción. A esta dirección se la denomina vector de la excepción (o interrupción). Normalmente esta instrucción no es más que un salto al comienzo de la rutina de
tratamiento de la excepción.
El procesador ARM1176JZF-S tiene varios modos de ejecución, en su mayoría dedicados
a atender excepciones. En la siguiente sección describimos estos modos.
3.2.1.
Modos de Ejecución
Todos los programas desarrollados en la práctica anterior se ejecutaban en un modo
de ejecución, aunque no nos hemos preocupado de ello. Sin embargo el ARM1176JZF-S
dispone de 8 modos de ejecución diferentes, que permiten, entre otras cosas, la gestión
eficiente de excepciones.
La figura 3.1 muestra de nuevo los distintos campos del registro de estado, la mayor
parte de ellos fueron descritos en la primera práctica. Los cinco bits menos significativos
1
El registro R15 no se preserva exactamente, en realidad se guarda la dirección en la que se interrumpió
el programa en un registro especial para poder posteriormente reanudar su ejecución a partir de ese punto.
3.2. EXCEPCIONES Y MODOS DE EJECUCIÓN
5
(M[4:0]) codifican el modo actual del procesador, cambiando estos bits el procesador cambia de modo. Sin embargo, la manera más habitual de cambiar de modo es a través de una
excepción.
Flags
Status
31 30 29 28 27 26 25 24 23
N Z C V Q
DNM
J
(RAZ)
20 19
DNM
(RAZ)
Extension
16 15
GE[3:0]
Control
10 9 8 7 6 5 4
DNM
(RAZ)
E A I F T
0
M[4:0]
Greater than
or equal to
Jazelle state bit
Sticky overflow
Overflow
Carry/Borrow/Extend
Zero
Negative/Less than
Mode bits
Thumb state bit
FIQ disable
IRQ disable
Imprecise abort
disable bit
Data endianness bit
Figura 3.1: Descripción del Registro de estado ARMv6 (CPSR).
La tabla 3.1 describe los ocho modos de ejecución del ARM1176JZF-S. Todos los modos
excepto User (usr ) son privilegiados. En los modos privilegiados no tenemos limitaciones
de acceso a los recursos del procesador. En cambio, en los modos no privilegiados algunos recursos pueden estar restringidos. Concretamente, en el caso del ARM1176JZF-S los
modos privilegiados son los únicos en los que tenemos acceso no restringido al registro de
estado. En modo usr en cambio no podemos modificar directamente (con la instrucción
mrs) los bits de modo, y por tanto la única forma de cambiar de modo usr a cualquier otro
modo es mediante una excepción.
Tabla 3.1: Modos del procesador
Modo
usr
fiq
irq
svc
mon
abt
und
sys
Código
10000
10001
10010
10011
10110
10111
11011
11111
Uso
Ejecución de código de usuario
Servicio de int. rápidas
Servicio de int. lentas
Modo protegido para sistema operativo (int. sw)
Modo seguro empleado con extensiones TrustZone
Procesado de fallos de acceso a mem
Manejo de instrucc. indefinidas
Ejecución de tareas del SO
De los modos privilegiados cinco son conocidos como modos de excepción, debido a
que están directamente relacionados con excepciones: FIQ (fiq), IRQ (irq), Supervisor
(svc), Abort (abt) y Undef (und ). System (sys), sólo está disponible en las versiones más
modernas de la arquitectura ARM. A diferencia del resto de modos privilegiados, el paso
a este modo no ocurre mediante una excepción. Lo emplea el sistema operativo cuando
necesita acceder a ciertos recursos del sistema desde fuera de un modo de excepción. El
último modo, Secure Monitor Mode (mon) está relacionado con las TrustZone Security
Extensions y de momento lo obviaremos.
6
ÍNDICE GENERAL
Registros y modos de ejecución
En la práctica anterior, trabajando en modo usuario, hemos manejado 15 registros de
propósito general, el PC y el registro de estado CPSR. Sin embargo, la arquitectura dispone
en realidad de 40 registros de 32 bits, incluyendo el contador de programa. Estos registros
se organizan en bancos parcialmente solapados, y cada modo tiene asignado uno de los
bancos, como ilustra la figura 3.2.
Debemos darnos cuenta de que en todos los bancos los registros de la parte superior
solapan con los del primer banco, y por tanto son los mismos que los registros del modo
usuario. Sin embargo, los modos FIQ, IRQ, Supervisor, Abort, Undefined y Secure Monitor
tienen algunos registros propios, no solapados con los del modo usuario. Por ejemplo, cada
uno tiene su propio puntero de pila SP (R13), lo que permite que cada modo utilice
distintas zonas de memoria para la pila. Además, cada modo tiene su propio registro LR
(R14). Veremos más adelante que cuando se produce una excepción el procesador cambia
de modo y guarda en el LR del modo correspondiente la dirección de retorno, que servirá
para retomar la ejecución del programa después de tratar la excepción. Además, cada
modo, excepto el de usuario, dispone de su propio registro de sombra SPSR. Este registro
se utiliza para salvar el registro de estado del programa cuando se produce una excepción
y se cambia de modo de ejecución. Además, en el modo FIQ los registros R8-R12 son
distintos de los del modo usuario, lo que facilita la preservación del contexto del programa
(que corre en modo usuario). Finalmente, System es un modo privilegiado que utiliza los
mismos registros que el modo usuario.
Como resumen, observemos que en cada modo podemos acceder a 15 registros de propósito general, llamados siempre r0-r14, el registro de estado CPSR, el registro de sombra
SPSR (salvo en modo usuario) y el contador de programa PC (r15).
Cambio de modo de ejecución
Hay dos formas de cambiar de modo de ejecución: mediante una excepción o modificando los bits M[4:0] del registro de estado. El primer mecanismo es el único que permite
el cambio de modo cuando se está en modo usuario y generalmente no es controlado por
el programador. No obstante, mediante la instrucción svc (llamada a supervisor, interrupción software), el programador puede generar una excepción que produce el cambio a modo
supervisor. Éste es el mecanismo utilizado por los sistemas operativos para controlar el acceso a los recursos protegidos, y recibe el nombre de llamada al sistema. Habitualmente,
esta llamada al sistema se realiza a través de una función de la biblioteca estándar de C.
El segundo mecanismo sólo está disponible cuando se está en un modo privilegiado. En
este caso el cambio de modo puede realizarse escribiendo un valor adecuado en los cinco
bits menos significativos del registro de estado. Para ello deben utilizarse las instrucciones
de manipulación del registro de estado mrs y msr2 .
Por ejemplo, una posible secuencia de instrucciones para pasar a modo Undef e inicializar el puntero de pila del modo (si no lo estuviese previamente) sería la siguiente:
.equ
.equ
2
MODEMASK,
UNDEFMODE,
0x1f
0x1b
/* Para selección de M[4:0] */
/* Código de modo Undef */
En ARMv6 existen instrucciones adicionales.
3.2. EXCEPCIONES Y MODOS DE EJECUCIÓN
User &
System
FIQ
7
IRQ
SVC
Undef
Abort
Secure
Monitor
r0
r1
r2
r3
r4
r5
r6
r7
r8
r8
r9
r9
r10
r10
r11
r11
r12
r12
r13 (sp)
r13 (sp)
r13 (sp)
r13 (sp)
r13 (sp)
r13 (sp)
r13 (sp)
r14 (lr)
r14 (lr)
r14 (lr)
r14 (lr)
r14 (lr)
r14 (lr)
r14 (lr)
spsr
spsr
spsr
spsr
spsr
spsr
r15 (pc)
cpsr
Figura 3.2: Registros visibles en cada modo de ejecución (los registros coloreados son privados a cada modo).
.equ
UndefStack, 0x4000
mrs
bic
orr
r0,cpsr
r0,r0,#MODEMASK
r1,r0,#UNDEFMODE
msr
cpsr_cxsf,r1
ldr
sp,=UndefStack
/* Dirección de del comienzo de la pila de Undef */
/* Llevamos el registro de estado a r0 */
/* Borramos los bits de modo de r0 */
/* Añadimos el código del modo Undef y
copiamos en r1 */
/* Escribimos el resultado en el registro de
estado, cambiando de éste los bits del
campo de control, de extension, de estado y
los de flag. */
/* Una vez en modo Undef copiamos la
dirección de comienzo de la pila */
/* Estamos en modo Undef con su pila inicializada */
3.2.2.
Excepciones
La arquitectura ARM1176JZF-S (ARM V6) reconoce, además de la excepción Reset
típica de todos los procesadores, 8 excepciones adicionales. Veamos una breve descripción
(para más información consultar [arm]):
8
ÍNDICE GENERAL
Reset Se produce cuando se activa la señal externa de reset del sistema.
Undef Se produce cuando se intenta ejecutar una instrucción no definida. Si la condición
de la instrucción no se cumple (recordemos que todas las instrucciones son condicionales) entonces la excepción no se produce.
SVC Se produce cuando se ejecuta la instrucción svc (interrupción software o llamada al
sistema).
IRQ Se produce cuando se activa la línea de interrupciones externas IRQ.
FIQ Se produce cuando se activa la línea de interrupciones externas rápidas FIQ.
Abort Se distinguen dos tipos de excepción:
Prefetch Abort (PAbort) Cuando se realiza la búsqueda (fetch) de una instrucción en una dirección no válida. El controlador de memoria es el responsable
de generar la interrupción.
Data Abort (DAbort) Cuando se intenta acceder a memoria en una posición
no válida, para lectura o escritura de datos. Es el controlador de memoria el
responsable de generar la interrupción.
BKPT Se produce cuando se ejecuta una instrucción de breakpoint software (de momento
la ignoraremos).
SMC Se produce cuando se ejecuta una instruccion de llamada al modo Secure Monitor
(SecureMonitorCall ).
La tabla 3.2, ordenada de mayor a menor prioridad, muestra la correspondencia entre las
excepciones, los modos de ejecución y los vectores3 . Observemos que cuando se inicializa
el sistema (Reset) el modo de ejecución es SVC, es decir, el sistema arranca en modo
supervisor, sin restricción alguna.
Tabla 3.2: Correspondencia entre excepciones, modos y vectores.
Prioridad
1
2
3
4
6
7
Excepción
Reset
Data Abort
FIQ
IRQ
Prefetch Abort
Undef
SVC
BKPT
SMC
Modo
SVC
Abort
FIQ
IRQ
Abort
Undef
SVC
Abort
Mon
Vector
0x00
0x10
0x1C
0x18
0x0C
0x04
0x08
0x0C
0x08
Cuando se produce una excepción el procesador realiza automáticamente (por hardware) los siguientes pasos:
3
En al ARM1176JZF-S la dirección de comienzo de los vectores se puede variar mediante un registro
de control especial, que de momento ignoraremos.
3.2. EXCEPCIONES Y MODOS DE EJECUCIÓN
9
1. Almacena la dirección de retorno en el registro r14 propio del modo de ejecución
asociado a la excepción. En realidad el valor almacenado depende del tipo de excepción4 (consultar la sección 2.12 de [arm]) lo que hace que el retorno de cada rutina
de tratamiento de excepción sea distinto5 , como veremos más adelante.
R14_<modo_de_excepcion>
= direccion de retorno
2. Copia el registro de estado (CPSR) en el registro SPSR del modo de ejecución correspondiente a la excepción.
SPSR_<modo_de_excepcion> = CPSR
3. Pone el código del modo de ejecución correspondiente a la excepción en los bits
M[4:0] del registro de estado.
CPSR[4:0] = código del modo de excepción
4. Cambia al estado ARM, si no lo estuviese ya6 .
CPSR[5] = 0 /* Cambiar a estado ARM */
5. Si el modo para el tratamiento de la excepción es Reset o FIQ, el procesador deshabilita las interrupciones rápidas.
if <modo_de_excepcion> == Reset or FIQ then
CPSR[6] = 1 /* Deshabilitar interrupciones rápidas */
/* else CPSR[6] no se cambia */
6. Deshabilita las interrupciones normales.
CPSR[7] = 1 /* Deshabilitar interrupciones normales */
7. Copia en el PC el vector correspondiente a la interrupción.
PC = dirección del vector de excepción
Resumiendo, lo que sucede ante una excepción es que el procesador guarda el registro
de estado en el registro de sombra del modo y ejecuta la instrucción que está almacenada
en memoria en la dirección indicada por el vector de la excepción (ver tabla 3.2). Esta
instrucción debe ser un salto a la rutina encargada de tratar la excepción o, en su defecto,
a una rutina que lea de memoria el lugar donde se encuentra dicha rutina y realice el salto
definitivo a ésta.
4
Cada excepción se detecta en una etapa distinta del procesador.
Dependiendo de la excepción el retorno debe realizarse a la propia instrucción o la siguiente.
6
Las rutinas de tratamiento de excepción no pueden implementarse con el repertorio compacto Thumb.
5
10
ÍNDICE GENERAL
Rutinas de tratamiento de excepción
De la descripción anterior podemos deducir que una rutina de tratamiento de excepción
debe preservar, como mínimo, el valor de los registros arquitectónicos R0-R12, ya que:
No se modifica el registro R14 del modo usuario, debido a que el registro de enlace
utilizado es propio de cada modo de ejecución.
Tampoco se modifica el registro r13 (SP), que también es propio de cada modo.
La preservación del PC se consigue escribiendo correctamente la dirección de retorno,
que podemos obtener a partir de R14_modo.
Sin embargo, en nuestras prácticas optaremos por ser conservadores haciendo que las rutinas de tratamiento de excepción guarden en la pila el valor de todos los registros arquitectónicos.
Como cada modo tiene su propio registro de pila, es habitual que cada modo utilice un
área de memoria distinto para la pila. Para que esto sea posible es necesario inicializar los
registros de pila de cada modo con una dirección distinta.
Hay que tener en cuenta que, para regresar desde una rutina de tratamiento de excepción al punto donde se había interrumpido la ejecución del programa, hay que hacer
simultáneamente (de lo contrario el retorno no sería correcto) dos cosas:
restaurar el valor del CPSR a partir del valor guardado en el SPSR.
escribir en PC la dirección de retorno, que podemos calcular a partir del valor almacenado en LR siguiendo las indicaciones de la tabla 3.3. Observemos que el cálculo
concreto depende de la excepción.
Tabla 3.3: Instrucción de retorno de excepción usual (consultar [arm]).
Excepción
Reset
Data Abort
FIQ
IRQ
Prefetch Abort
BKPT
Undef
SVC
SMC
Inst.
NA
SUBS
SUBS
SUBS
SUBS
SUBS
MOVS
MOVS
MOVS
Retorno
PC,
PC,
PC,
PC,
PC,
PC,
PC,
PC,
R14_abt,
R14_fiq,
R14_irq,
R14_abt,
R14_abt,
R14_und
R14_svc
R14_mon
#8
#4
#4
#4
#4
Los cuadros 1 y 2 muestran dos estructuras alternativas para una rutina de tratamiento de excepción. Cada una utiliza uno de los dos mecanismos válidos para realizar
correctamente el retorno:
Cuadro 1: Se realiza el retorno mediante una instrucción de procesamiento de datos
con el bit S activo (modificación del registro de estado) y empleando como registro
3.2. EXCEPCIONES Y MODOS DE EJECUCIÓN
11
destino PC7 (ej. SUBS PC, LR).
Cuadro 2: Se realiza el retorno mediante mediante una instrucción de load múltiple (LDM) con el bit S activo (modificación del registro de estado) y empleando
PC como uno de los registros destino.Es preciso señalar que para LDM la activación
de S se lleva a cabo poniendo un acento circunflejo al final de la instrucción (ej.
LDMDB FP, {R0-R13, PC}^).
Cuadro 1 Rutina de tratamiento de excepciones para retorno con instrucción aritmética
con bit S activo.
/* prólogo */
str
ip, [sp, -#4]!
@ salvamos ip en la pila
mov
ip, sp
stmdb
sp!, {r0-r10, fp, ip, lr, pc} @ salvamos el resto del contexto
sub
fp, ip, #4
/* cuerpo de la rutina */
/* epílogo */
ldmdb
fp, {r0-r10, fp, sp, lr}
ldmia
sp!, {ip}
subs
pc, lr, #4
@
@
@
@
@
restauramos contexto’
restauramos ip’
retorno copiando SPSR en CPSR
debe sustituirse por la instrucción
correspondiente
Cuadro 2 Rutina de tratamiento de excepciones para retorno con LDM con bit S activo.
/* prólogo */
sub
lr, lr, #4
@ modificamos lr para el retorno
@ debe sustituirse por la instrucción
@ correspondiente
stmdb
sp!, {sp, lr, pc}
@ lo almacenamos en la pila
stmdb
sp!, {r0-r10, fp, ip}
@ salvamos el resto del contexto en la pila
add
fp, sp, #60
@ fijamos fp a partir de sp, si no
@ se guardan todos los registros
@ hay que reajustar esta suma
/* cuerpo de la rutina */
/* epílogo */
ldmdb
fp, {r0-r10, fp, ip, sp, pc}^
7
@ restauramos contexto y retornamos
Cuando se utiliza el PC como destino en una instrucción que modifica el registro de estado, el hardware
automáticamente restaura el valor de CPSR a partir del valor de SPSR
12
ÍNDICE GENERAL
Escritura de rutinas de tratamiento de excepciones en C
Si queremos implementar las rutinas de tratamiento de excepción como funciones de
C, debemos informar al compilador de que la función se utilizará para el tratamiento de
una determinada excepción, de forma que genere el código con la estructura adecuada. En
gcc esto se consigue añadiendo a la declaración de la función una directiva __attribute__
del siguiente modo:
ret_val fun_name( params ) __attribute__((interrupt ( TYPE )));
donde TYPE puede ser IRQ, FIQ, ABORT, UNDEF o SVC.
Procediendo de esta forma gcc creará un rutina con una estructura similar a las descritas
por los cuadros 1-2, en lugar de utilizar el prólogo y el epílogo de una función C.
3.3.
Mapa de memoria
La arquitectura ARM usa E/S mapeada en memoria, por lo que acceder a los dispositivos consiste en ejecutar instrucciones de lectura/escritura (ldr/str) a las direcciones
adecuadas. La figura 3.3 muestra el mapa de memoria del ARM en el System On Chip
(SoC) BCM2835 de Broadcom que usa la Raspberry Pi.
Figura 3.3: Mapa de memoria en el SoC BCM2835 (consultar [bcm].
Además de la MMU (Memory Management Unit encargada de gestionar el mapa de
memoria) propio del ARM, el SoC BCM2835 dispone de una segunda MMU de grano más
grueso para traducir direcciones físicas del ARM a direcciones del bus.
Para el propósito de esta práctica, no discutiremos el significado de las direcciones
virtuales que aparecen en la zona derecha de la figura. Sin embargo, es necesario entender
la diferencia entre las direcciones físicas, propias del ARM, y las direcciones de bus que
utiliza el SoCpines de Broadcom.
3.4. CONFIGURACIÓN Y USO DE PINES GPIO
3.3.1.
13
Direcciones físicas del ARM
La memoria RAM (memoria principal) comienza en la dirección física 0x00000000.
Por tanto, podemos configurar nuestro código para que resida en cualquier dirección del
rango 0x00000000 - 0x20000000 (pues en la revisión 2.0 del Modelo B de la Raspberry Pi
disponemos de 512MB de memoria RAM).
A partir de esa dirección, comienza el rango de direcciones asignados a periféricos: desde
la dirección 0x20000000 hasta la 0x20FFFFFF. Todos los periféricos de la Raspberry Pi
(temporizadores, controladores I2C, SPI, UART, PWM....) se configurarán en ese rango
de direcciones, así como los pines de entrada/salida genérica (GPIO) que, en nuestro caso,
se conectarán a la placa Gertboard para disponer de más dispositivos.
3.3.2.
Direcciones de bus del SoC BCM2835
El SoC de Broadcom en el que se integra el ARM dispone de una segunda MMU que
traduce las direcciones físicas a un nuevo rango. Esto dificulta ligeramente la programación de dispositivos de forma directa, pues la documentación de Broadcom ([bcm]) usa
direcciones de bus para documentar los diferentes dispositivos y la memoria principal.
En este nuevo rango:
La memoria RAM comienza en la dirección 0xC0000000
Los dispositivos de E/S comienzan en la dirección 0x7E000000
A efectos prácticos, esto significa que una dirección de E/S documentada por BCM
como 0x7Ennnnnn habrá que utilizarla como 0x20nnnnnn cuando programemos directamente el dispositivo desde el ARM (es decir, lo que haremos en esta práctica). Hay una
única excepción: si se utiliza el controlador de DMA, las direcciones que especifiquemos al
controlador sí deberán ser direcciones de bus y no físicas de ARM.
3.4.
Configuración y uso de pines GPIO
El SoC BCM2835 dispone de 54 pines de uso general (General Purpose Input/Output).
De acuerdo a la documentación (ver Capítulo 6 de [bcm]), la tabla 3.4 resume los registros
que deben utilizarse para configurar los diferentes pines de GPIO.
Registro
GPFSEL0
GPFSEL1
GPFSEL2
GPFSEL3
GPFSEL4
GPFSEL5
Dirección (ARM)
0x20200000
0x20200004
0x20200008
0x2020000C
0x20200010
0x20200014
Pines GPIO
GPIO0 - GPIO9
GPIO10 - GPIO19
GPIO20 - GPIO29
GPIO30 - GPIO39
GPIO40 - GPIO49
GPIO50 - GPIO53
Tabla 3.4: Configuración de pines de GPIO a través de registros GPFSELn
14
ÍNDICE GENERAL
Cada uno de esos registros almacenará la configuración de 10 pines GPIO; cada una de
estas configuraciones consistirá a su vez en tres bits:
000 GPIO configurado como entrada
001 GPIO configurado como salida
Otras codficaciones: funciones alternativas (ver documentación para más detalles)
De ese modo, para configurar el pin GPIO0 como entrada debemos escribir 000 en
los bits 2-0 del registroGPFSEL0. Para configurar el pin GPIO17 como salida debemos
escribir 001 en los bits 23-21 del registro GPFSEL1.
3.4.1.
Escritura sobre pines configurados como salida
Una vez configurados los pines que queremos usar, podremos actuar sobre ellos. En
caso de que el pin haya sido configurado para salida, podremos forzar un 1 o un 0 lógico
utilizando otros registros del controlador:
Para escribir un 1 lógico en un pin GPIO<n>se escribirá en uno de los registros
GPSET0/1, en las direcciones (ARM) 0x2020001C y 0x20200020, respectivamente.
• Los pines GPIO0 a GPIO31 se modifican desde el registro GPSET0. Por ejemplo, escribiendo un 1 en el bit 17 del registro GPSET0 estaremos forzando un
1 lógico en el GPIO17.
• Los pines GPIO32 a GPIO53 se modifican desde el registro GPSET1. Por
ejemplo, escribiendo un 1 en el bit 8 del registro GPSET1 estaremos forzando
un 1 lógico en el GPIO40.
Para escribir un 0 lógico en un pin GPIO<n>se escribirá en los registros GPCLR0/1,
en las direcciones (ARM) 0x20200028 y 0x2020002C, respectivamente.
• Los pines GPIO0 a GPIO31 se modifican desde el registro GPCLR0. Así, escribiendo un 1 en el bit 17 del registro GPCLR0 estaremos forzando un 0 lógico
en el GPIO17.
• Los pines GPIO32 a GPIO53 se modifican desde el registro GPCLR1. Por
ejemplo, escribiendo un 1 en el bit 8 del registro GPCLR1 estaremos forzando
un 0 lógico en el GPIO40.
3.4.2.
Lectura sobre pines configurados como entrada
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.
3.5. GERTBOARD
15
Lectura explícita. 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).
3.4.3.
Configuración de pines como pull-up o pull-down
En ocasiones, es necesario configurar los pines como pull-up o pull-down en función del
dispositivo que vayamos a conectar a ellos. Para configurar un pin en pull-up o pull-down
es necesario seguir los siguientes pasos:
1. Escribir en el registro GPPUD (dirección 0x20200094 y etiquetado como
GPIO_PULL en el código de partida entregado) para seleccionar el modo al que
querremos configurar un conjunto de pines:
Escribiremos 01 para configurar pines como pull-down
Escribiremos 10 para configurar pines como pull-up
2. Esperar al menos 150 ciclos (llamada a short_wait() en código entregado)
3. Escribir en los registros GPPUDCLK0/1 (direcciones 0x20200098 y 0x2020009C;
etiquetados GPIO_PULLCLK0 en el código entregado) para configurar, selectivamente, los pines deseados. Por ejemplo, escribiendo el valor 0x00000101 en el registro
GPPUDCLK0 configuraremos los pines GPIO0 y GPIO8 a pull-up o pull-down según
lo indicado en el paso 1.
4. Esperar al menos 150 ciclos (llamada a short_wait() en código entregado)
5. Escribir 00 en GPPUD
6. Escribir 0x0 en GPPUDCLK0/1.
3.4.4.
Resumen de configuración GPIO
A modo de resumen de esta sección, la tabla 3.5 muestra todos los registros involucrados
en la configuración y uso del puerto GPIO con un pequeño comentario de su cometido:
3.5.
Gertboard
En nuestro caso los pines GPIO del SoC BCM2835 de la Raspberry Pi se utilizan
para conectar los dispositivos de E/S pertenecientes a una placa de expansión llamada
Gertboard [ger].
Dicha placa dispone de:
12 pines de E/S. Cada pin está conectado a un LED que estará encendido si el pin
está a ’1’ lógico y apagado en caso contrario.
16
ÍNDICE GENERAL
Nombre(s)
GPFSEL0-5
Dirección(es)
0x20200000 - 0x20200014
GPTSET0/1
0x2020001C - 0x20200020
GPTCLR0/1
0x20200028 - 0x2020002C
GPLEV0/1
0x20200034 - 0x20200038
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
Configuración pull-up-pull-down
Configuración pull-up-pull-down
Tabla 3.5: Registros de configuración de puertos GPIO
3 pulsadores.
6 puertos de un Darlington transistor array en colector abierto (50V, 0.5A)
Un controlador de motor (48V, 4A)
28 pines para conectar un microcontrolador ATmega
Conversor Digital-Analógico (8, 19 o 12 bits).
Conversor Analógioc-Digital de 10 bits
La figura 3.4 muestra un diagrama de bloques de la placa y da una idea de su conexión
con la Raspberry Pi.
Figura 3.4: Diagrama de bloques de la placa Gertboard
.
3.5.1.
Cableado y colocación de jumpers en la Gertboard
El voltaje de referencia (el que se considera un 1 lógico) en la Gertboard es 3.3V, si bien
es posible conseguir 5V a partir de la Raspberry Pi. Para enviar la alimentación correcta
(3.3V) a todos los componentes de la placa es imprescindible instalar un jumper sobre
3.5. GERTBOARD
17
los pines marcados como J7. Si no se coloca este jumper, tal y como se indica en la
figura 3.5, ningún componente de la placa funcionará adecuadamente.
Figura 3.5: Instalación de jumper en pines J7
.
La placa Gertboard está conectada a la Raspberry Pi a través del conector J1, de 26
pines. De este modo, es posible comunicar ambas placas a través de los pines GPIO . En
concreto, el conector J2 de la Gertboard indica, qué pines de GPIO de la Raspberry Pi
es posible acceder. La numeración indicada en la placa (GP25, GP24...) es relevante pues
coincide, salvo alguna excepción, con los nombres de señales usados en el SoC BCM2835: la
señal GPIOn de la documentación del SoC BCM2835 se corresponde con el pin etiquetado
GPn en el conector J2 de la Gertboard.8 En la sección 3.5.2 veremos cómo conectar los
pines del J2 de modo que se permita el acceso a los pulsadores y a los LEDs desde la
Raspberry Pi.
3.5.2.
Uso de LEDs y pulsadores
Como se ha mencionado previamente la placa Gertboard dispone de 12 pines/puertos de
E/S conectados a sendos LEDs. La figura 3.6 muestra el diagrama del circuito de conexión
correspondiente a los pines 4 a 12. Para utilizar uno de estos pines como salida es preciso
conectar un jumper en el buffer de salida (output) y para usarlo como entrada es preciso
conectar un jumper en el buffer de entrada (input). Los puertos 1, 2 y 3 están conectados
además a 3 pulsadores (S1, S2, S3) mediante el circuito de la figura 3.7. En este caso,
no hay que conectar el jumper en el buffer de entrada o de lo contrario no se podría leer
el valor del pulsador. Se puede no obstante conectar el buffer de salida para visualizar el
estado del pulsador en el LED.
Configuración de la Gertboard Antes de poder hacer uso de los LEDs y pulsadores
desde la Raspberry Pi es necesario realizar algunas conexiones sobre la Gertboard que
enlacen estos dispositivos con los pines GPIO deseados. Para la realización de esta práctica
realizaremos las conexiones para conseguir la correspondencia que se indica en la tabla 3.5.2.
Si bien disponemos de 12 LEDs y 3 pulsadores, para los objetivos de esta práctica basta
con conectar un LED y dos pulsadores.
8
Salvo para los pines GP0 y GP1 que se corresponden con GPIO2 y GPIO3 respectivamente y el pin
18
ÍNDICE GENERAL
Figura 3.6: Circuito de los puertos de E/S 4-12 de la Gertboard .
Figura 3.7: Circuito de los puertos de E/S 1-3 de la Gertboard .
Para conseguir la asignación detallada en la tabla 3.5.2, deberemos conectar los cables
(straps) y jumpers tal y como se indica en la figura 3.8. Por tanto, una vez realizadas esas
conexiones, podremos enceder/apagar el LED escribiendo en el GPIO17 desde la Raspberry Pi. Del mismo modo, podremos consultar el estado de los pulsadores (pulsado/no
pulsado) o incluso generar una interrupción cada vez que se pulse un botón, configurando
convenientemente los pines GPIO23 y GPIO18.
Uso de LEDs Una vez que hemos asociado un led con el pin GPIO17, basta con configurar dicho pin como salida y escribir en él un 1 lógico para encender el LED, y un ’0’
lógico para apagarlo. La sección 3.4 explica cómo realizar esta configuración.
Uso de pulsadores Siguiendo los pasos anteriores, habremos asociado un botón al pin
GPIO18 y otro al GPIO23. Ahora basta con configurar ambos pines como entradas (ver
sección 3.4). Además, se deben configurar los pines para ser usados con resistencia de
pull-up. Esto implica que el valor leído en el pin si no está pulsado el botón es de un ’1’
lógico.
GP21 que se corresponde con el GPIO27
3.6. DESARROLLO DE LA PRÁCTICA
19
GPIO (Raspberry Pi) GPIO (Gertboard) Pin destino (Gertboard) Dispositivo
17
17
B3
Led
18
18
B2
Pulsador
23
23
B1
Pulsador
Fig. 2: A photograph of the unpopulated Gertboard viewed from above, showing the silver
coloured holes and pads that eventually will be home to the components, as well as the
Tabla 3.6:legends
Asignación
dewhite
dispositivos
a pines
GPIOresist
de coating.
las Raspberry PI
printed in
epoxy ink, and
green solder
Fig. 3: This image is aJumper
diagrammatic representation
of the same photograph
Button strap
LED strap shown in Fig. 2
above. It was generated from the same files that were used to create the physical printed
circuit board. The blue elements in the diagram correspond to the white text and lines on the
photo
and the red elements
correspond
to the silver
and holes
the photo.y un led
Figura 3.8:
Conexiones
y jumpers
necesarios
parapads
utilizar
dosonbotones
5
3.6.
Desarrollo de la Práctica
En esta práctica pondremos en práctica de manera guiada los conocimientos expuestos
en los apartados anteriores.
3.6.1.
Primera parte
El programa contendrá un código en ensamblador que inicialice correctamente el estado
del proceso para todos los modos de ejecución y configure la tabla de direcciones de las
rutinas de tratamiento de excepciones. Al finalizar ejecutará la función main, que generará
tres excepciones (Undef, Dabort y SVC) y terminará. Para ello seguimos los siguientes
pasos:
1. Creamos un proyecto nuevo a partir del esquema proporcionado.
2. El fichero init.s deberá completarse con el código de inicialización para luego poder
ejecutar nuestro programa C. El trabajo principal de inicialización lo realizan dos
fragmentos:
20
ÍNDICE GENERAL
InitVectorTable: Se encarga de copiar las primeras 8 instrucciones junto con
sus datos sobre las tabla de vectores
InitStacks: Se encarga de inicializar las pilas de los distintos modos de ejecución. Para ello, va cambiando de modo de ejecución, manteniendo enmascaradas
las interrupciones, y en cada modo escribe en el registro de pila la dirección de
comienzo correspondiente al modo, que está dada por el símbolo <Modo>Stack.
Tras ejecutar este código, el programa init.s pone a cero el frame pointer e invocará
la función main. A continuación se detalla el código, indicando los fragmentos que se
deben completar.
.global
.global
.global
.global
.global
_start
DoUndef
DoSWI
DoDabort
screen
@ -.equ
.equ
.equ
.equ
.equ
.equ
.text
_start:
Constantes
UserStack,
0x8000-0x100
SVCStack,
0x8000-0x100-256*1
UndefStack, 0x8000-0x100-256*2
AbortStack, 0x8000-0x100-256*3
IRQStack,
0x8000-0x100-256*4
FIQStack,
0x8000-0x100-256*5
@ -- Tabla de vectores de excepción
ldr pc,reset_handler
ldr pc,undefined_handler
ldr pc,svc_handler
ldr pc,prefetch_handler
ldr pc,data_handler
ldr pc,unused_handler
ldr pc,irq_handler
ldr pc,fiq_handler
@ -- Direcciones de las rutinas de tratamiento
reset_handler:
.word reset
undefined_handler: .word ISR_Undef
svc_handler:
.word ISR_SVC
prefetch_handler: .word ISR_Pabort
data_handler:
.word ISR_Dabort
unused_handler:
.word hang
irq_handler:
.word ISR_IRQ
fiq_handler:
.word ISR_FIQ
3.6. DESARROLLO DE LA PRÁCTICA
reset:
21
@ -- Rutina de tratamiento de reset (no retorna)
InitVectorTable:
@ Copia de la Tabla de Vectores de Excepción
mov r0,#0x8000
mov r1,#0x0000
@@@ <copiar la tabla y las direcciones al comienzo de la memoria>
InitStacks: @ Inicialización de los Stacks
@ UndefMode
mov r0,#0xDB
msr cpsr_c,r0
ldr sp,=UndefStack
@@@ <proceder de mismo modo para AbortMode, IRQMode, FIQMode, SVCMod
mov fp,#0
hang:
DoSVC:
@ Salto a la función main()
bl main
b hang
@ Rutinas de generación de exepciones
svc 0x0
mov pc,lr
DoUndef: .word 0xE6000010
mov pc,lr
DoDabort: ldr r0,=0xFFFFFFFF
ldr r0,[r0]
mov pc,lr
screen:
.space 1024
.end
3. Creamos un fichero main.c en el que añadimos la función main y las rutinas de
tratamiento de excepción programadas en C. El código completo viene a continuación:
extern char screen[];
char *Screen = (char*) screen;
void ISR_SVC(void) __attribute__ ((interrupt ("SVC")));
22
ÍNDICE GENERAL
void
void
void
void
void
ISR_Undef(void) __attribute__ ((interrupt ("UNDEF")));
ISR_IRQ(void) __attribute__ ((interrupt ("IRQ")));
ISR_FIQ(void) __attribute__ ((interrupt ("FIQ")));
ISR_Pabort(void) __attribute__ ((interrupt ("ABORT")));
ISR_Dabort(void) __attribute__ ((interrupt ("ABORT")));
void write(char* text, char* address)
{
while( *text != 0 )
{ *address++ = *text++; }
}
void DoUndef();
void DoDabort();
void DoSVC();
void main(void)
{
DoUndef();
DoSWI();
DoDabort();
}
void ISR_Undef(void)
{
write("Undef ",Screen);
}
void ISR_IRQ(void)
{
write("IRQ
",Screen);
}
void ISR_FIQ(void)
{
write("FIQ
",Screen);
}
void ISR_SVC(void)
{
write("SVC
",Screen);
}
void ISR_Pabort(void)
{
write("Pabort",Screen);
3.6. DESARROLLO DE LA PRÁCTICA
23
}
void ISR_Dabort(void)
{
write("Dabort",Screen);
}
4. Compilar el proyecto y subirlo a la placa. IMPORTANTE:
Colocar el breakpoint inicial en reset en lugar de en _start, ya que lo primero
que hará el código será saltar al vector de reset.
Para observar el salto a las rutinas de tratamiento de excepción, colocar también
breakpoints en cada rutina de tratamiento de excepción. La ejecución paso a paso
no siempre permite observar el comportamiento real de la aplicación.
5. Contestad a las siguientes preguntas:
¿Cómo se generan las excepciones Undef, Dabort y SWI?
¿Qué hacen las rutinas de tratamiento de estas excepciones?
Observar el código generado para estas rutinas (desensamblado). ¿En qué se
diferencia de una rutina corriente? Detallar la respuesta.
6. Ejercicio: Modificar el código codificando la rutina write en ensamblador.
3.6.2.
Segunda parte
En esta segunda parte se realizará una primera aproximación básica a la programación
de entrada/salida. Para ello se deberá configurar el sistema de modo que:
Al pulsar un botón se encienda un primer LED y al pulsar otro botón, se encienda
un segundo LED. Este segundo LED sólo debe encenderse en caso de que el primero
esté encendido.
Si se presiona el primer botón de nuevo se apagarán ambos LEDs.
Si se presiona el segundo botón de nuevo se apagará sólo el segundo LED.
El programa principal consistirá en un bucle que consulte constantemente el valor de los
botones y actúe sobre el estado de los LEDs si es preciso.
Bibliografía
[arm] Arm-v6m architecture reference manual.
[bcm] Bcm2835 arm peripherals.
[ger]
Gertboard user manuel (rev 2.0).
24
Descargar