LibroDePracticas PDF
LibroDePracticas PDF
LibroDePracticas PDF
PRCTICAS DE ENSAMBLADOR
BASADAS EN
RASPBERRY PI
E/S Entrada/Salida
Prlogo xv
1 Introduccin al ensamblador 1
1.1 Lectura previa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.1 Caractersticas generales de la arquitectura ARM . . . . . . . 2
1.1.2 El lenguaje ensamblador . . . . . . . . . . . . . . . . . . . . . 5
1.1.3 El entorno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.4 Configuracin del entorno para realizar las prcticas en casa . 7
1.1.5 Aspecto de un programa en ensamblador . . . . . . . . . . . . 9
1.1.6 Ensamblar y linkar un programa . . . . . . . . . . . . . . . . 14
1.2 Enunciados de la prctica . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2.1 Cmo empezar . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2.2 Enteros y naturales . . . . . . . . . . . . . . . . . . . . . . . . 20
1.2.3 Instrucciones lgicas . . . . . . . . . . . . . . . . . . . . . . . 23
1.2.4 Rotaciones y desplazamientos . . . . . . . . . . . . . . . . . . 25
1.2.5 Instrucciones de multiplicacin . . . . . . . . . . . . . . . . . 28
vii
3.1.2 Convencin AAPCS . . . . . . . . . . . . . . . . . . . . . . . 58
3.2 Ejemplos de aplicacin . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.2.1 Funciones en ensamblador llamadas desde C . . . . . . . . . . 60
3.2.2 Funciones en ensamblador llamadas desde ensamblador . . . . 62
3.2.3 Funciones recursivas . . . . . . . . . . . . . . . . . . . . . . . 64
3.2.4 Funciones con muchos parmetros de entrada . . . . . . . . . 70
3.2.5 Pasos detallados de llamadas a funciones . . . . . . . . . . . . 75
3.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.3.1 Mnimo de un vector . . . . . . . . . . . . . . . . . . . . . . . 76
3.3.2 Media aritmtica, macros y conteo de ciclos . . . . . . . . . . 78
3.3.3 Algoritmo de ordenacin . . . . . . . . . . . . . . . . . . . . . 80
viii
5.2 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
5.2.1 Todo con IRQs . . . . . . . . . . . . . . . . . . . . . . . . . . 142
5.2.2 Alargar secuencia a 10 y parpadeo . . . . . . . . . . . . . . . . 142
5.2.3 Tope de secuencia y limitar sonido . . . . . . . . . . . . . . . 142
5.2.4 Reproductor de meloda sencilla . . . . . . . . . . . . . . . . . 143
Bibliografa 178
ix
x
ndice de figuras
xi
5.6 Agrupacin de puertos de interrupciones . . . . . . . . . . . . . . . . 113
5.7 Interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
5.8 Interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
5.9 Interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
5.10 Interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
5.11 Interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
xii
ndice de Tablas
xiii
Prlogo
xv
xvi Prlogo
xvi
Prlogo xvii
xvii
xviii Prlogo
xviii
Captulo 1
Introduccin al ensamblador
Contenido
1.1 Lectura previa . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.1 Caractersticas generales de la arquitectura ARM . . . . . 2
1.1.2 El lenguaje ensamblador . . . . . . . . . . . . . . . . . . . 5
1.1.3 El entorno . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.4 Configuracin del entorno para realizar las prcticas en casa 7
1.1.5 Aspecto de un programa en ensamblador . . . . . . . . . . 9
1.1.6 Ensamblar y linkar un programa . . . . . . . . . . . . . . 14
1.2 Enunciados de la prctica . . . . . . . . . . . . . . . . . . . 15
1.2.1 Cmo empezar . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2.2 Enteros y naturales . . . . . . . . . . . . . . . . . . . . . . 20
1.2.3 Instrucciones lgicas . . . . . . . . . . . . . . . . . . . . . 23
1.2.4 Rotaciones y desplazamientos . . . . . . . . . . . . . . . . 25
1.2.5 Instrucciones de multiplicacin . . . . . . . . . . . . . . . 28
1
2 1.1. Lectura previa
Registros
La arquitectura ARMv6 presenta un conjunto de 17 registros (16 principales ms
uno de estado) de 32 bits cada uno.
Esquema de almacenamiento
El procesador es Bi-Endian, quiere decir que es configurable entre Big Endian y
Little Endian. Aunque nuestro sistema operativo nos lo limita a Little Endian.
Por tanto la regla que sigue es el byte menos significativo ocupa la posicin ms
baja. Cuando escribimos un dato en una posicin de memoria, dependiendo de si
es byte, half word o word,... se ubica en memoria segn el esquema de la figura 1.2.
La direccin de un dato es la de su byte menos significativo. La memoria siempre se
referencia a nivel de byte, es decir si decimos la posicin N nos estamos refiriendo
al byte N-simo, aunque se escriba media palabra, una palabra,...
1
Es un modo simplificado donde las instrucciones son de 16 bits en lugar de 32 y se acceden a
menos registros (hasta r7), con la ventaja de que el cdigo ocupa menos espacio.
2
Es la forma de nombrar las instrucciones desde ensamblador, normalmente derivadas de una
abreviatura del verbo en ingls. Por ejemplo la instruccin MOV viene de move (mover)
1.1.3. El entorno
Los pasos habituales para hacer un programa (en cualquier lenguaje) son los
siguientes: lo primero es escribir el programa en el lenguaje fuente mediante un edi-
tor de programas. El resultado es un fichero en un lenguaje que puede entender el
usuario, pero no la mquina. Para traducirlo a lenguaje mquina hay que utilizar
un programa traductor. ste genera un fichero con la traduccin de dicho programa,
pero todava no es un programa ejecutable. Un fichero ejecutable contiene el progra-
ma traducido ms una serie de cdigos que debe tener todo programa que vaya a ser
ejecutado en una mquina determinada. Entre estos cdigos comunes se encuentran
las libreras del lenguaje. El encargado de unir el cdigo del programa con el cdigo
de estas libreras es un programa llamado montador (linker) que genera el programa
ejecutable (ver la figura 1.3)
fuente1.s
ENSAMBLADOR
MONTADOR
fuente2.s CARGADOR
MEMORIA
cdigo mquina
fuente3.c (binario)
EJECUTABLE
COMPILADOR
5. Luego nos piden el password, que es raspberry. En este caso y por motivos
de seguridad no se recibe respuesta visual mientras escribimos la contrasea,
ni siquiera aparecen asteriscos.
var1 : .word 3
var2 : .word 4
var3 : .word 0x1234
.text
.global main
Datos
Los datos se pueden representar de distintas maneras. Para representar nmeros
tenemos 4 bases. La ms habitual es en su forma decimal, la cual no lleva ningn
delimitador especial. Luego tenemos otra muy til que es la representacin hexade-
cimal, que indicaremos con el prefijo 0x. Otra interesante es la binaria, que emplea
el prefijo 0b antes del nmero en binario. La cuarta y ltima base es la octal, que
usaremos en raras ocasiones y se especifica con el prefijo 0. S, un cero a la izquierda
de cualquier valor convierte en octal dicho nmero. Por ejemplo 015 equivale a 13 en
decimal. Todas estas bases pueden ir con un signo menos delante, codificando el valor
negativo en complemento a dos. Para representar carcteres y cadenas emplearemos
las comillas simples y las comillas dobles respectivamente.
Smbolos
Como las etiquetas se pueden ubicar tanto en la seccin de datos como en la de
cdigo, la versatilidad que nos dan las mismas es enorme. En la zona de datos, las
etiquetas pueden representar variables, constantes y cadenas. En la zona de cdigo
podemos usar etiquetas de salto, funciones y punteros a zona de datos.
Las macros y las constantes simblicas son smbolos cuyo mbito pertenece al
preprocesador, a diferencia de las etiquetas que pertenecen al del ensamblador. Se
especifican con las directivas .macro y .equ respectivamente y permiten que el cdigo
sea ms legible y menos repetitivo.
Instrucciones
Las instrucciones del as (a partir de ahora usamos as para referirnos al ensam-
blador) responden al formato general:
Etiqueta : Nemot cnico Operando / s /* Comentario */
El Campo etiqueta, si aparece, debe estar formado por una cadena alfanumrica.
La cadena no debe comenzar con un dgito y no se puede utilizar como cadena
alguna palabra reservada del as ni nombre de registro del microprocesador. En el
ejemplo, la etiqueta es main:.
El campo Nemotcnico (ldr en el ejemplo) es una forma abreviada de nombrar
la instruccin del procesador. Est formado por caracteres alfabticos (entre 1 y 11
caracteres).
El campo Operando/s indica dnde se encuentran los datos. Puede haber 0, 1
ms operandos en una instruccin. Si hay ms de uno normalmente al primero se
le denomina destino (salvo excepciones como str) y a los dems fuentes, y deben
ir separados por una coma. Los operandos pueden ser registros, etiquetas, valores
inmediatos o incluso elementos ms complejos como desplazadores/rotadores o in-
dicadores de pre/post-incrementos. En cualquiera de los casos el tamao debe ser
una palabra (32 bits), salvo contadas excepciones como ldr y str donde puede ser
media palabra (16 bits) o un byte (8 bits). En el ejemplo r1 es el operando destino,
de tipo registro, y puntero_var1 es el operando fuente, una etiqueta. Tanto r1 como
puntero_var1 hacen referencia a un valor de tamao palabra (32 bits).
Directivas
Las directivas son expresiones que aparecen en el mdulo fuente e indican al
compilador que realice determinadas tareas en el proceso de compilacin. Son fcil-
mente distinguibles de las instrucciones porque siempre comienzan con un punto.
El uso de directivas es aplicable slo al entorno del compilador, por tanto varan
de un compilador a otro y para diferentes versiones de un mismo compilador. Las
directivas ms frecuentes en el as son:
Directivas de control: .text y .data sirven para delimitar las distintas seccio-
nes de nuestro mdulo. .align alineamiento es para alinear el siguiente dato,
rellenando con ceros, de tal forma que comience en una direccin mltiplos
del nmero que especifiquemos en alineamiento, normalmente potencia de 2.
Si no especificamos alineamiento por defecto toma el valor de 4 (alineamiento
a palabra):
a1 : .byte 25 /* definimos un byte con el valor 25 */
.align /* directiva que rellena con 3 bytes */
a2 : .word 4 /* variable alineada a tama o palabra */
.include para incluir un archivo fuente dentro del actual. .global hace visible
al enlazador el smbolo que hemos definido con la etiqueta del mismo nombre.
Esta macro se llama CuadM1 y tiene tres parmetros (input, aux y output).
Si posteriormente usamos la macro de la siguiente forma:
CuadM1 r1, r8, r0
No hay que confundir las macros con los procedimientos. Por un lado, el cdigo
de un procedimiento es nico, todas las llamadas usan el mismo, mientras que
el de una macro aparece (se expande) cada vez que se referencia, por lo que
ocuparn ms memoria. Las macros sern ms rpidas en su ejecucin, pues
es secuencial, frente a los procedimientos, ya que implican un salto cuando
aparece la llamada y un retorno cuando se termina. La decisin de usar una
macro o un procedimiento depender de cada situacin en concreto, aunque
las macros son muy flexibles (ofrecen muchsimas ms posibilidades de las
comentadas aqu). Esta posibilidad ser explotada en sesiones ms avanzadas.
NOTA: tanto el comando as como el nombre del programa son sensibles a las
maysculas. Por tanto el comando debe ir en minsculas y el nombre como queramos,
pero recomendamos minsculas tambin. Las opcin -o nombreprograma.o puede
ir despus de nombreprograma.s.
El as genera un fichero nombreprograma.o.
Para montar (linkar) hay que hacer:
NOTA: Nuevamente, tanto gcc como el nombre del programa deben estar en
minsculas. Este comando es muy parecido al anterior, podemos poner si queremos
-o nombreprograma detrs de nombreprograma.o. La nica diferencia es que
el archivo no tiene extensin, que por otro lado es una prctica muy recomendable
para ejecutables en Linux.
Una vez hecho sto, ya tenemos un fichero ejecutable (nombreprograma) que
podemos ejecutar o depurar con el gdb.
Comenzaremos con el programa que hemos visto en el listado 1.1, y que se en-
cuentra en el fichero intro1.s. Edtalo con el programa nano para verlo (y practicar
un poco):
nano intro1.s
Dentro del editor tenemos una pequea gua de comandos en la parte inferior.
Tambin podemos acceder a una ayuda ms detallada pulsando F1 dentro del editor.
Estos son los atajos de teclado ms comunes:
Atajo Funcin
Ctrl-x Salir de nano, se pide confirmacin con Y/N
Ctrl-o Salvar cambios
Ctrl-c Muestra panel con informacin sobre nmero de lnea
Alt-g Saltar a un nmero de lnea en concreto
Alt-a Seleccionar texto, mover cursores para definir regin
Ctrl-6
Alt- Copiar seleccin
Ctrl-k Cortar seleccin
Ctrl-u Pegar seleccin
Ctrl-w Buscar texto
Alt-w Repetir ltima bsqueda
Alt-r Buscar y reemplazar
Tabla 1.2: Lista de atajos de teclado para editor nano
Una vez que estis familiarizados con el nano podemos pasar al traductor:
as -o intro1.o intro1.s
Observa que cuando se traduce, aparece una lista de errores, o bien, una indi-
cacin de que el programa se ha traducido correctamente. No se puede pasar a la
etapa de montaje hasta que no se han solucionado los errores de sintaxis.
gcc -o intro1 intro1.o
El gdb ofrece muchas posibilidades, slo explicaremos las que vamos a utilizar.
Nada ms ejecutar el comando anterior, el gdb se encuentra en modo interactivo:
pi@raspberrypi ~ $ gdb intro1
GNU gdb ( GDB ) 7.4.1 - debian
Copyright ( C ) 2012 Free Software Foundation , Inc .
License GPLv3 +: GNU GPL version 3 or later
This is free software : you are free to change and redis ...
Podemos escribir help para acceder a la ayuda integrada, o bien irnos a la pgina
web de documentacin del gdb [7]. El primer comando a aprender es:
( gdb ) quit
Lanzamos de nuevo el depurador gdb intro1. En este momento no hay nada eje-
cutndose. Un primer paso es decirle al depurador que queremos lanzar el programa,
esto es, cargarlo en memoria y apuntar a la primera instruccin del mismo:
( gdb ) start
Temporary breakpoint 1 at 0x8390
Starting program : / home / pi / intro1
Vemos que las instrucciones que hacan referencia a puntero_varX han cam-
biado. De momento lo ignoramos, ya lo explicaremos ms adelante. Observen que
hay una especie de flecha => apuntando a la instruccin que est apunto de eje-
cutarse (no lo ha hecho an). Antes de ejecutarla, veamos los valores de algunos
registros:
Podemos modificar el valor de los registros por medio de la orden print, teniendo
en cuenta los efectos adversos que esto podra ocasionar, ya que estamos alterando
el funcionamiento de nuestro programa. En este caso no pasa nada, puesto que an
no hemos ejecutado ninguna instruccin.
( gdb ) print $r0 = 2
$1 = 2
( gdb ) info registers r0 r1 r2 r3
r0 0x2 2
r1 0xbefffe04 3204447748
r2 0xbefffe0c 3204447756
r3 0x8390 33680
Puedes emplear disas (pero no disa) como comando abreviado. En realidad to-
dos los comandos pueden abreviarse, slo que no lo hemos hecho para os resulte ms
fcil su memorizacin. A partir de ahora pondr versiones abreviadas de comandos
que ya hayamos mostrado.
( gdb ) i r r1
r1 0x3 3
Vamos bien. Ahora ejecutamos hasta la instruccin str, que seran exactamente
4 pasos.
( gdb ) si 4
0x000083a8 in main ()
( gdb ) disas
El depurador nos indica que el cdigo de salida es 07. Este cdigo se lo indicamos
en el registro r0 justo antes de salir del main. Nos salimos del depurador y compro-
bamos que ejecutando el programa directamente, aunque ste no muestre ninguna
salida por pantalla, podemos verificar su cdigo de salida de la siguiente forma:
pi@raspberrypi ~ $ ./ intro1 ; echo $ ?
7
pi@raspberrypi ~ $
Ahora que ya tenemos una idea de las posibilidades del gdb vamos a repasar
unos cuantos conceptos con la ayuda de este programa.
Ejercicio 1.1
Suponemos dos variables de longitud un byte var1 y var2 con los valores binarios
(00110010b ) y (11000000b ), respectivamente. Completa las casillas en blanco.
var2 11000000
Observa que los valores son bien diferentes segn la interpretacin (valor impl-
cito) que se les d.
Ejercicio 1.2
Calcula ahora la suma de los dos nmeros y responde en las casillas en blanco.
= = =
.text
.global main
Si os fijis hemos hecho algunos cambios con respecto a intro1.s. Las variables
son de tipo byte en lugar de word, lo que nos obliga a alinear con .align despus
de cada una. Las cargamos con la instruccin ldrsb, indicando que lo que cargamos
es un byte b al que le extendemos su signo s. Hemos eliminado la variable var3,
al fin y al cabo vamos a obtener el resultado en el registro r0. Por ltimo hemos
simplificado la carga de la direccin de la variables con ldr r1, =var1, de esta forma
el ensamblador se encarga de declarar los punteros automticamente.
Ensmblalo, mntalo y sguelo con el gdb tal y como se ha explicado en la
primera parte de la prctica. Ejecuta slo las 5 primeras instrucciones. Analiza el
resultado del registro r0 y responde al siguiente ejercicio.
Ejercicio 1.3
Ejercicio 1.4
Repite el ejercicio anterior, pero ahora comprobando el resultado de los flags con
lo que habas calculado en el Ejercicio 1.2. Qu ocurre?
Por cierto, desde gdb no hay una forma sencilla de obtener los flags por sepa-
rado. Por suerte son fciles de interpretar a partir del valor hexadecimal de cpsr.
Convertimos a binario el nibble (dgito hexadecimal) ms significativo de cpsr, en
este caso 6 ->0110. Hacemos corresponder 0110 con la secuencia NZCV (debemos
aprenderla de memoria), con lo cual tendramos N=0, Z=1, C=1 y V=0.
La razn por la que no se actualizan los flags es que el ensamblador del ARM no
lo hace a menos que se lo indiquemos con una s detrs de la instruccin. Cambiemos
la lnea 15 del archivo intro2.s por sta.
adds r0, r1, r2 /* r0 <- r1 + r2 */
Ejercicio 1.6
El resultado de la instruccin and est en r0. Cul ser el resultado de hacer un
complemento a uno del mismo?
binario hexa
r0
binario hexa
r0
Ejercicio 1.7
La instruccin tst hace la operacin and entre un registro y una mscara y slo
acta sobre los flags. Cumplimenta las casillas en blanco, teniendo en cuenta que
el flag Z se pone a uno cuando el resultado de la and es cero, y se pone a cero en
caso contrario. Para simplificar indicamos slo los 16 bits menos significativos del
registro r0.
binario hexa
r0 10000000000000000000000000000000 80000000
Comprueba tus respuestas con ayuda del gdb, y examina el resto de flags, observa
qu ocurre con el flag N (flag de signo).
LSR C
0
C
LSL
ASR C
0
Las instrucciones de rotacin tambin desplazan, pero el bit que sale del valor
se realimenta. No existe ninguna instruccin para rotar hacia la izquierda ROL, ya
que puede simularse con la de rotacin a la derecha ROR que s existe. En estas
instrucciones el bit desplazado fuera es el mismo que el que entra, adems de dejar
una copia en el flag C (figura 1.6).
ROR C
(figura 1.7). Estas instrucciones slo rotan un bit, al contrario que las anteriores que
podan rotar/desplazar varios. La rotacin con carry a la derecha es RRX, no existe
la contrapartida RLX porque se puede sintetizar con otra instruccin ya existente
adcs. Con adcs podemos sumar un registro consigo mismo, que es lo mismo que
multiplicar por 2 o desplazar 1 bit hacia la izquierda. Si a esto le aadimos el bit de
carry como entrada y actualizamos los flags a la salida, tendremos exactamente el
mismo comportamiento que tendra la instruccin RLX.
RRX
C
Tambin podemos forzar el flag C o cualquier otro flag al valor que queramos
con la siguiente instruccin.
msr cpsr_f, # valor
Donde para calcular el valor hacemos el paso inverso al explicado en gdb. Quere-
mos cambiar los flags a estos valores: N=0, Z=1, C=1 y V=0. Por el orden memori-
zado de la secuencia NZCV, calculamos el nibble binario, que es 0110. Lo pasamos
a hexadecimal 0110 ->6 y lo ponemos en la parte ms alta de la constante de 32
bits, dejando el resto a cero.
msr cpsr_f, # 0x60000000
Ejercicio 1.8
Examina atentamente el programa intro4.s (listado 1.4). Antes de ejecutarlo
completa el siguiente cuadro, despus comprueba los resultados con el gdb. Observa
la definicin de variable var1: .word 0x80000000.
Instruccin r1 (binario) C
ldr r1, [r0]
LSRs r1, r1, #1
LSRs r1, r1, #3
Instruccin r2 (binario) C
ldr r2, [r0]
ASRs r2, r2, #1
ASRs r2, r2, #3
Instruccin r3 (binario) C
ldr r3, [r0]
RORs r3, r3, #31
RORs r3, r3, #31
RORs r3, r3, #24
Instruccin r4 (binario) C
ldr r4, [r0]
msr cpsr_f, #0
adcs r4, r4, r4
adcs r4, r4, r4
adcs r4, r4, r4
msr cpsr_f, #0x2..
adcs r4, r4, r4
.text
.global main
Las dos siguientes multiplicaciones (umull y smull) son largas, por eso la l del
final, donde el resultado es de 64 bits. Si los operandos son naturales escogemos la
multiplicacin sin signo (unsigned) umull. Por el contrario, si tenemos dos enteros
como factores hablamos de multiplicacin con signo (signed) smull. En ambos ejem-
plos la parte baja del resultado se almacena en r0, y la parte alta en r1. Para hacer
que r1:r0 = r2*r3:
umull r0, r1, r2, r3
smull r0, r1, r2, r3
Ahora veamos smulw*. Es con signo, y el asterisco puede ser una b para selec-
cionar la parte baja del registro del segundo factor, o una t para seleccionar la alta.
Segn el ejemplo r0 = r1*parte_baja(r2).
smulwb r0, r1, r2
Por ltimo tenemos smul** tambin con signo, donde se seleccionan partes alta
o baja en los dos factores, puesto que ambos son de 16 bits. En el ejemplo r0 =
parte_alta(r1)*parte_baja(r2).
smultb r0, r1, r2
En los dos ltimos tipos smulw* y smul** no se permite el sufijo s para actualizar
los flags.
Ejercicio 1.9
Completa los recuadros en blanco con los resultados en hexadecimal emplean-
do calculadora. Luego ensambla el listado 1.5 y comprueba mediante gdb que los
clculos anteriores son correctos.
.text
.global main
main : ldr r0, = var1 /* r0 <- & var1 */
ldr r1, = var2 /* r1 <- & var2 */
ldr r2, = var3 /* r2 <- & var3 */
ldrh r3, [ r0 ] /* r3 <- baja (* r0 ) */
ldrh r4, [ r1 ] /* r4 <- baja (* r1 ) */
muls r5, r3, r4 /* r5 <- r3 * r4 */
ldr r3, [ r0 ] /* r3 <- * r0 */
ldr r4, [ r1 ] /* r4 <- * r1 */
umull r5, r6, r3, r4 /* r6 : r5 <- r3 * r4 */
smull r5, r6, r3, r4 /* r6 : r5 <- r3 * r4 */
ldrh r3, [ r0 ] /* r3 <- baja (* r0 ) */
ldr r4, [ r2 ] /* r4 <- * r2 */
smulwb r5, r3, r4 /* r5 <- r3 * baja ( r4 ) */
smultt r5, r3, r4 /* r5 <- alta ( r3 )* alta ( r4 ) */
Captulo 2
Contenido
2.1 Lectura previa . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.1.1 Modos de direccionamiento del ARM . . . . . . . . . . . . 31
2.1.2 Tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . 36
2.1.3 Instrucciones de salto . . . . . . . . . . . . . . . . . . . . 38
2.1.4 Estructuras de control de alto nivel . . . . . . . . . . . . . 42
2.1.5 Compilacin a ensamblador . . . . . . . . . . . . . . . . . 43
2.1.6 Ejercicios propuestos. . . . . . . . . . . . . . . . . . . . . 46
2.2 Enunciados de la prctica . . . . . . . . . . . . . . . . . . . 48
2.2.1 Suma de elementos de un vector . . . . . . . . . . . . . . 48
31
32 2.1. Lectura previa
[Rx], +Ry
[Rx], -Ry
Igual que antes pero con registro en lugar de inmediato.
[Rx, +Ry]!
[Rx, -Ry]!
Equivale a esto.
add r1, r1, r3, LSL # 2
ldr r2, [ r1 ]
.data
var1 : .word 3
puntero_var1 : .word var1
.text
.global main
main : ldr r0, = puntero_var1
ldr r1, [ r0 ]
ldr r2, [ r1 ]
ldr r3, = var1
bx lr
Incluso en tipos que en C estn basados en punteros como las cadenas, en en-
samblador no es necesario tenerlos almacenados en memoria puesto que podemos
obtener dicho valor en un registro con una nica instruccin ldr.
Vectores. Todos los elementos de un vector se almacenan en un nico bloque de
memoria a partir de una direccin determinada. Los diferentes elementos se alma-
cenan en posiciones consecutivas, de manera que el elemento i est entre los i-1 e
i+1 (figura 2.1). Los vectores estn definidos siempre a partir de la posicin 0. El
propio ndice indica cuntos elementos hemos de desplazarnos respecto del comienzo
del primer elemento (para acceder al elemento cero hemos de saltarnos 0 elementos,
para acceder al elemento 1 hemos de saltarnos un elemento, etc...; En general, para
acceder al elemento con ndice i hemos de saltarnos los i elementos anteriores).
Dado un vector int v[N];, todos los elementos se encuentran en posiciones
consecutivas a partir de la direccin de v[0] (puesto que son int, en este ejemplo,
cada elemento ocupa 4 bytes). Por lo tanto, el acceso al elemento v[i] se consigue
aplicando la siguiente expresin.
bytes de memoria implicado depender del tipo de datos declarado). Cuando nos
queramos refererir al acceso a memoria para la obtencin de un puntero, lo notaremos
como Mref [ ].
v[n-1]
v[n-2]
v[n-3]
N elementos, si cada elemento
... ocupa B bytes, N*B bytes
v[1]
@v[0] v[0]
a) int mat[N][M];
fila i NxM
elementos
N NxMxB
bytes
columna j
@mat
b)
mat[N-1,*]
fila N-1
mat[N-2,M-1]
mat[N-2,M-2]
mat[N-2,*]
fila N-2
mat[N-2,M-3]
mat[N-2,0]
...
mat[0,*]
fila 0
Figura 2.2: (a) Formato de una matriz C con N filas y M columnas y (b) organizacin
por filas
LT (lower than, menor en complemento a dos). Cuando N!=V (N vale not V).
nivel1 : push { lr }
mov r3, # 3
bl nivel2
pop { lr }
bx lr
Como veis, en el ltimo nivel (nivel2) podemos ahorrarnos el tener que alma-
cenar y recuperar lr en la pila.
Las instrucciones de salto en la arquitectura ARM abarcan una zona muy ex-
tensa, hasta 64 Mb (32 Mb hacia adelante y otros 32 Mb hacia atrs). Estos lmites
podemos justificarlos atendiendo al formato de instruccin que podemos ver en el
apndice A. El cdigo de operacin ocupa 8 de los 32 bits, dejndonos 24 bits para
codificar el destino del salto. En principio con 24 bits podemos direccionar 16 Mb
[223 1, 223 1], sin embargo la arquitectura ARM se aprovecha del hecho de que
las instrucciones estn alineadas a direcciones mltiplo de 4 (en binario acaban en
00), por lo que el rango real es de 64 Mb [225 1, 225 1]
En caso de necesitar un salto mayor recurrimos a la misma solucin de la carga
de inmediatos del mov, solo que el registro a cargar es el pc.
ldr pc, = etiqueta
for ( i = vi ; i <= vf ; i ++ ){
/* Cuerpo del bucle */
}
i = vi ;
while ( i <= vf ){
/* Cuerpo del bucle */
i ++;
}
Listado 2.2: Traduccin de las estructuras for y while. Hemos supuesto que el valor
inicial est en la variable vi y el valor final en la variable vf y se ha utilizado el
registro r1 como ndice de las iteraciones i.
ldr r1, = vi
ldr r1, [ r1 ]
ldr r2, = vf
ldr r2, [ r2 ]
bucle : cmp r1, r2
bhi salir
/* Cuerpo
del
bucle */
add r1, r1, # 1
b bucle
salir :
if ( a == b ){
/* C digo entonces */
}
else {
/* C digo sino */
}
int i ;
for ( i = 0; i <5; i ++ ){
printf ( " %d \ n " , i );
}
.text
.global main
main : push { r4, lr }
mov r4, # 0
.L2 : mov r1, r4
ldr r0, = var1
add r4, r4, # 1
bl printf
cmp r4, # 5
bne .L2
pop { r4, pc }
Se simplifican a:
pop { r4, pc }
.text
.global main
main : push { r4, lr }
mov r1, # 0
ldr r4, = var1
mov r0, r4
bl printf
mov r0, r4
mov r1, # 1
bl printf
mov r0, r4
mov r1, # 2
bl printf
mov r0, r4
mov r1, # 3
bl printf
mov r0, r4
mov r1, # 4
pop { r4, lr }
b printf
Se deja como ejercicio explicar porqu pop r4, lr y b printf primero llama a
printf y luego retorna al SO.
Ejercicio 2.2
Escribe el cdigo ensamblador correspondiente a una estructura if en la que no
exista la rama de else.
Ejercicio 2.3
Escribe en ensamblador un cdigo equivalente a ste. Primero haciendo uso de
la instruccin ands y un registro auxiliar, luego simplifica con la instruccin tst.
Ejercicio 2.4
Escribe en ensamblador la estructura de alto nivel switch, aplicndola al si-
guiente ejemplo en C.
suma += vector [ i ];
}
printf ( " La suma es %d \ n " , suma );
}
.text
.global main
/* Salvamos registros */
main : push { r4, lr }
/* Imprimimos resultado */
ldr r0, = var1
bl printf
vez calculada la suma en r1, la mostramos por pantalla mediante una llamada a
printf.
El cdigo del listado 2.9 est en el fichero tipos4.s. Compila y monta el progra-
ma con el as y el gcc. Ahora ejecuta el algoritmo con el gdb. Recuerda empezar con
start. Para ver su funcionamiento, podemos ejecutar un par de iteraciones con si
y ver cmo los valores de los registros van cambiando i r r0 r1 r2 r3 (de vez en
cuando ejecuta disas para saber por dnde vas). Si ejecutamos un par de iteraciones
con si veremos que el hecho de ejecutar instruccin a instruccin resulta poco til.
Para acelerar el proceso, podemos utilizar puntos de parada o breakpoints.
Otro problema que tenemos es que al ejecutar un paso de una instruccin exacta
si nos metemos dentro de la rutina printf, cosa que no nos interesa a no ser que
queramos descubrir las interioridades de la librera. Para evitar esto ejecutamos con
ni, que ejecutar bl printf de un paso sin meterse dentro de la rutina.
Para introducir un breakpoint hay varias maneras, siempre es buena idea in-
vestigar a fondo la ayuda que se nos brinda el propio depurador con help break.
Nosotros pondremos dos puntos de ruptura.
( gdb ) start
Temporary breakpoint 1 at 0x83cc
Starting program : / home / pi / tipos4
Ahora toca continuar la ejecucin del programa hasta el final o hasta llegar
a un punto de ruptura, y esto se hace con continue (de forma abreviada cont).
Tambin podemos mostrar la lista de puntos de ruptura, desactivar temporalmente
Antes de acabar nuestra sesin con gdb depuramos la ltima iteracin del bucle,
y luego dos instrucciones ms para mostrar el texto que emite printf.
( gdb ) i r r0 r1
r0 0x1 1
r1 0xe6 230
( gdb ) disas
Dump of assembler code for function bucle :
= > 0x000083dc <+0 >: ldr r3, [ r2 ] , # 4
0x000083e0 <+4 >: add r1, r1, r3
0x000083e4 <+8 >: subs r0, r0, # 1
0x000083e8 <+ 12 >: bne 0x83dc < bucle >
0x000083ec <+ 16 >: ldr r0, [ pc, # 12 ]
0x000083f0 <+ 20 >: bl 0x82f0 < printf >
0x000083f4 <+ 24 >: pop { r4, lr }
0x000083f8 <+ 28 >: bx lr
End of assembler dump.
( gdb ) ni 4
0x000083ec in bucle ()
( gdb ) i r r1 r3
r1 0x162 354
r3 0x7c 124
( gdb ) ni 2
La suma es 354
0x000083f4 in bucle ()
Ejercicio 2.5
Sabiendo que la suma de los 5 elementos del vector anterior es 2.400.000.050
completa el siguiente cuadro:
Traduce el nmero 2.400.000.050 a binario:
Si has hecho el ejercicio 2.5 puedes ahora comprobar que la suma de los valores
de este vector produce un overflow sobre un int. Por tanto, el programador debera
ir acumulando el resultado de la suma sobre un long long (64 bits), tal y como se
muestra en el siguiente listado.
void main ( void ){
int i ;
long long suma ;
int vector [5]= {1600000000 , -100 , 800000000 , -50 , 200};
.text
.global main
/* Salvamos registros */
/* Imprimimos resultado */
ldr r0, = var1
bl printf
Ejercicio 2.6
Dada la definicin de matriz short mat[4][6]; cul es la frmula para acceder
al elemento mat[i][2]?
suma = 0;
for ( i = 0; i <4; i ++ ){
suma += mat [ i ][2];
}
Ejercicio 2.7
Calcula las frmulas de acceso a mat[i][2] y mat[i+1][2] y halla su diferencia
(resta las dos frmulas).
Contenido
3.1 Lectura previa . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.1.1 La pila y las instrucciones ldm y stm . . . . . . . . . . . . 56
3.1.2 Convencin AAPCS . . . . . . . . . . . . . . . . . . . . . 58
3.2 Ejemplos de aplicacin . . . . . . . . . . . . . . . . . . . . 60
3.2.1 Funciones en ensamblador llamadas desde C . . . . . . . . 60
3.2.2 Funciones en ensamblador llamadas desde ensamblador . . 62
3.2.3 Funciones recursivas . . . . . . . . . . . . . . . . . . . . . 64
3.2.4 Funciones con muchos parmetros de entrada . . . . . . . 70
3.2.5 Pasos detallados de llamadas a funciones . . . . . . . . . . 75
3.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.3.1 Mnimo de un vector . . . . . . . . . . . . . . . . . . . . . 76
3.3.2 Media aritmtica, macros y conteo de ciclos . . . . . . . . 78
3.3.3 Algoritmo de ordenacin . . . . . . . . . . . . . . . . . . . 80
55
56 3.1. Lectura previa
Para sacar elementos de la pila tenemos la operacin pop, que primero extrae el
elemento de la pila y luego incrementa el puntero (la pila decrece hacia arriba). Por
tanto, la instruccin pop es equivalente a:
Listado 3.2: Operacin pop
ldr r0, [ sp ]
add sp, sp, # 4
Un uso muy comn de la pila es salvaguardar una serie de registros, que quere-
mos usar para hacer las operaciones que necesitemos pero que al final tenemos que
restaurar a sus valores originales. En un procesador tpico escribiramos algo as:
1
En este texto usaremos el trmino rutina (o subrutina) como la implementacin a bajo nivel de
lo que en alto nivel se conoce como procedimientos y funciones. La diferencia entre procedimiento
y funcin, radica en que las funciones proporcionan un valor de retorno.
push r1
push r2
push r4
/* c digo que modifica los
registros r1, r2 y r4 */
pop r4
pop r2
pop r1
1. Podemos usar hasta cuatro registros (desde r0 hasta r3) para pasar parme-
tros y hasta dos (r0 y r1) para devolver el resultado.
2. No estamos obligados a usarlos todos, si por ejemplo la funcin slo usa dos
parmetros de tipo int con r0 y r1 nos basta. Lo mismo pasa con el resultado,
podemos no devolver nada (tipo void), devolver slo r0 (tipo int un puntero
a una estructura ms compleja), o bien devolver r1:r0 cuando necesitemos
enteros de 64 bits (tipo long long).
6. La pila debe estar alineada a 8 bytes, esto quiere decir que de usarla para pre-
servar registros, debemos reservar un nmero par de ellos. Si slo necesitamos
preservar un nmero impar de ellos, aadimos un registro ms a la lista dentro
del push, aunque no necesite ser preservado.
mysrand (42);
for ( i = 0; i <5; i ++ ){
printf ( " %d \ n " , myrand ());
}
}
.text
.global myrand, mysrand
myrand : ldr r1, = seed @ leo puntero a semilla
ldr r0, [ r1 ] @ leo valor de semilla
ldr r2, [ r1, # 4 ] @ leo const1 en r2
mul r3, r0, r2 @ r3 = seed * 1103515245
ldr r0, [ r1, # 8 ] @ leo const2 en r0
add r0, r0, r3 @ r0 = r3 + 12345
str r0, [ r1 ] @ guardo en variable seed
/* Estas dos l neas devuelven " seed > > 16 & 0x7fff " .
Con un peque o truco evitamos el uso del AND */
LSL r0, # 1
LSR r0, # 17
bx lr
.text
.global main
/* Salvamos registros */
main : push { r4, r5 }
bl mysrand
Como vis ya no hace falta poner a .global las funciones myrand y mysrand,
puesto que son de uso interno. Sin embargo s lo hacemos con main, ya que ahora s
la implementamos en ensamblador. Al fin y al cabo main es otra funcin ms y por
tanto debe de seguir la normativa AAPCS.
Primero preservamos r4 y r5. En realidad r5 no se modifica y no hara falta
preservarla, pero lo hacemos para alinear a 8 la pila. Luego llamamos a mysrand
con el valor 42 como primer y nico parmetro. Inicializamos a 5 el contador del
bucle, que almacenamos en r4 y comenzamos el bucle. El bucle consiste en llamar
a myrand y pasar el resultado devuelto de esta funcin al segundo parmetro de la
funcin printf, llamar a printf, decrementar el contador y repetir el bucle hasta
que el contador llegue a cero. Una vez salimos del bucle recuperamos los registros
for ( i = 0; i <10; i ++ )
printf ( " %d \ n " , fibonacci ( i ));
}
Lo que vamos a explicar ahora es cmo crear variables locales dentro de una
funcin. Aunque en C no necesitemos variables locales para la funcin fibonacci,
s nos har falta en ensamblador, en concreto dos variables: una para acumular la
suma y otra para mantener el parmetro de entrada.
Para ello vamos a emplear la pila, que hasta ahora slo la dedicbamos para
salvaguardar los registros a partir de r4 en la funcin. La pila tendra un tercer uso
que no hemos visto todava. Sirve para que el llamador pase el resto de parmetros
en caso de que haya ms de 4. Los primeros 4 parmetros (dos en caso de parmetros
de 64 bits) se pasan por los registros desde r0 hasta r3. A partir de aqu si hay ms
parmetros stos se pasan por pila.
Las variables locales se alojan debajo del rea de salvaguarda de registros, para
ello hay que hacer espacio decrementando el puntero de pila una cierta cantidad de
Pues bien, en nuestro caso de la funcin fibonacci necesitamos 0 bytes para paso
de parmetros, 4 bytes para salvaguarda de registros (slo guardaremos lr) y 8
bytes para nuestras dos variables locales. Como la suma es de 12 bytes, que no
es mltiplo de 8, redondeamos a 16 aadiendo una tercera variable local que no
usaremos (tambin podramos haber salvaguardado un segundo registro). Nuestro
mapa particular lo podemos observar en la figura 3.2.
En teora podemos encargarnos nosotros mismos de hacer toda la aritmtica que
conlleva el uso de variables locales, pero en la prctica estamos ms expuestos a
cometer errores y nuestro cdigo es ms ilegible. Las 3 variables locales ocupan 12
bytes, a la primera accedemos con el direccionamiento [sp] y a la segunda con [sp,
#4] (la tercera no la usamos). El cdigo quedara como en el listado 3.7.
Con esta nueva filosofa el cdigo queda menos crptico, como vemos en el listado
3.8.
Listado 3.8: Funcin recursiva fibo (en subrut3.s)
fibo : push { lr } @ salvaguarda lr
sub sp, # length @ hago espacio para v.locales
cmp r0, # 2 @ if n < 2
movlo r0, # 1 @ return 1
blo fib1
.text
.global main
/* Salvo registros */
main : push { r4, lr }
.equ local1, 0
.equ local2, 4 + local1
.equ local3, 4 + local2
.equ length, 4 + local3
Lo nico que nos faltaba era la funcin main. La lista de .equ puede ir al co-
mienzo, pero por claridad la ponemos justo antes de la funcin a la que se va a
aplicar. La funcin main no tiene nada nuevo, salvo que incrementamos el contador
r4 en lugar de decrementarlo porque necesitamos dicho valor como parmetro para
llamar a la funcin fibo.
Para terminar con este ejemplo vamos a hacer una sencilla optimizacin. Observa
un momento la primera rama de la funcin. Si el parmetro es menor de dos tan slo
operamos con un registro, r0, tanto para comparar la entrada como para escribir el
valor de retorno. No se toca ningn registro ms, no hemos modificado lr porque
no hemos llamado a ninguna subrutina, tampoco hemos hecho uso de las variables
locales.
La optimizacin consiste (ver listado 3.10) en procesar la primera rama antes
de las operaciones con la pila, de esta forma nos ahorramos algunos ciclos de reloj.
Es un buen ejemplo para comprobar lo flexibles que pueden ser las funciones: hay
funciones en las que podemos evitar tratar con la pila como en el listado 3.5, otras
en las que no tenemos ms remedio, y un ltimo caso en que podemos tener una
mezcla de ambas alternativas.
Listado 3.10: Parte del cdigo del programa subrut4.s
fibo : cmp r0, # 2 @ if n < 2
movlo r0, # 1 @ return 1
bxlo lr @ salgo de la funci n
push { lr } @ salvaguarda lr
sub sp, # length @ hago espacio para v.locales
sub r0, # 1 @ r0 = n - 1
str r0, [ sp, # local1 ] @ salvo n - 1 en [ sp ]
bl fibo @ fibonacci (n - 1 )
str r0, [ sp, # local2 ] @ salvo salida de fib. (n - 1 )
ldr r0, [ sp, # local1 ] @ recupero de la pila n - 1
sub r0, # 1 @ calculo n - 2
bl fibo @ fibonacci (n - 2 )
ldr r1, [ sp, # local2 ] @ recupero salida de fib (n - 1 )
add r0, r1 @ lo sumo a fib. (n - 1 )
.text
.global main
/* Salvo registros */
main : push { r4, lr }
mov r1, # 2
mov r2, # 3
mov r3, # 4
add sp, # 4
mov r1, r0
ldr r0, = var1
bl printf
Vemos como hemos usado un .equ para facilitar la legibilidad del cdigo, as
accedemos al ndice del quinto parmetro sin tener que hacer clculos. El mapa de
la pila quedara as.
Se pueden combinar los .equ de variables locales con los de parmetros por pila,
por ejemplo si tuvisemos una funcin hipottica con 6 parmetros (dos de ellos
pasados por pila), 3 variables locales y salvaguarda de 3 registros, lo haramos de la
siguiente forma.
.equ local1, 0
.equ local2, 4 + local1
.equ local3, 4 + local2
Como podis observar, las instrucciones ARM son muy potentes, permiten im-
plementar en 5 instrucciones lo que en C nos habra costado 6 multiplicaciones y 4
sumas. Ntese cmo reusamos los registros r2 y r3: al principio son parmetros de
entrada, pero luego los empleamos como registros temporales a medida que no los
necesitamos ms.
Despus de esto acaba la funcin con las habituales pop r4 y bx lr. Ya hemos
terminado la funcin poly3, que ha quedado bastante pequea en tamao. Todo
lo contrario que la funcin main. Sin embargo, la funcin main es larga por varias
razones: hacemos 3 llamadas a poly3, debemos introducir muchas constantes, al-
gunas de ellas en pila, y debemos imprimir los resultados y hacer el equilibrado de
pila. Este equilibrado de pila consiste en incrementar sp despus de la llamada a la
funcin para desalojar los parmetros que previamente habamos introducido en la
misma. Como en nuestro ejemplo pasamos por pila un nico parmetro de 4 bytes,
lo que hacemos es incrementar sp en 4 tras cada llamada a poly3.
Un detalle muy importante que no podemos observar en nuestro ejemplo es que
los parmetros que pasamos por pila se pasan en orden inverso desde el ltimo al
quinto. Esto es as porque la pila crece hacia abajo. Es ms, es aconsejable reusar
los registros r0-r3 para introducir los parmetros por pila. Si tuvisemos que pasar
6 parmetros (constantes del 1 al 6) lo haramos as:
mov r0, # 6
push { r0 }
mov r0, # 5
push { r0 }
mov r0, # 1
mov r1, # 2
2
Tambin es fcil encontrar la especificacin de una instruccin buscando su nemnico en
Google, ya que suele aparecer la ayuda oficial de ARM en el primer resultado de la bsqueda
mov r2, # 3
mov r3, # 4
Como vis no hay una forma clara y legible de introducir los parmetros de una
funcin. Hay que Tener cuidado con los push mltiples, ya que no importa el orden en
que especifiques los registros, el procesador siempre introduce en pila el registro ms
alto y va hacia atrs hasta llegar al primero. Aprovechando esto podemos mejorar
el ejemplo anterior:
mov r0, # 5
mov r1, # 6
push { r0, r1 }
mov r0, # 1
mov r1, # 2
mov r2, # 3
mov r3, # 4
En qu consiste la mejora? Pues que hemos usado el registro basura r12, que es
el nico que podemos emplear sin salvaguardarlo previamente en la lista del push.
Esto nos quitara el push y el pop, aunque en este ejemplo lo hemos reemplazado por
instrucciones sub y add. La razn es que debemos mantener el puntero de pila en
un mltiplo de 8. No obstante las instrucciones que no acceden a memoria siempre
son ms rpidas que las que lo hacen, as que hemos ganado velocidad.
1. Usando los registros r0-r3 como almacn temporal, el llamador pasa por pila
los parmetros quinto, sexto, etc... hasta el ltimo. Cuidado con el orden,
especialmente si se emplea un push mltiple. Este paso es opcional y slo
necesario si nuestra funcin tiene ms de 4 parmetros.
5. Decrementar la pila para hacer hueco a las variables locales. La suma de bytes
entre paso de parmetros por pila, salvaguarda y variables locales debe ser
mltiplo de 8, rellenar aqu hasta completar. Como este paso es opcional, en
caso de no hacerlo aqu el alineamiento se debe hacer en el paso 4.
10. El llamador equilibra la pila en caso de haber pasado parmetros por ella.
3.3. Ejercicios
3.3.1. Mnimo de un vector
Dado un vector de enteros y su longitud, escribe una funcin en ensamblador
que recorra todos los elementos del vector y nos devuelva el valor mnimo. Para
comprobar su funcionamiento, haz .global la funcin y tras ensamblarla, enlzala
con este programa en C.
min= v[0];
for ( i= 1; i<len; i++ )
if( v[i]<min )
min= v[i];
return min;
}
Una vez hecho esto, supn que cada instruccin tarda un ciclo de reloj en eje-
cutarse. Cuenta manualmente el nmero de ciclos que tarda la ejecucin completa
desde la primera instruccin de main hasta la ltima bx lr, incluyendo sta. En
caso de una llamada a subrutina cuenta todas las instrucciones que se ejecutan me-
tindote en la subrutina. La nica excepcin es bl printf, que debes contar como
un nico ciclo.
Haz lo mismo pero usando la herramienta gdb para comprobar el resultado ante-
rior. Recuerda no meterte dentro de los printf con ni. En las llamadas a la funcin
media usa si.
Macros
Hay una forma de acelerar las funciones, aunque slo es prctica para funciones
pequeas que se utilicen mucho. Se trata de escribir el contenido de la funcin en
lugar de llamar a la misma, y para evitar repetir siempre el mismo cdigo utilizamos
la directiva .macro. Con este truco nos ahorramos al menos la ejecucin de las
instrucciones bl funcion y bx lr. El inconveniente es que el tamao del ejecutable
ser mayor.
En el listado 3.14 vemos un ejemplo que usa la funcin abs, pero que con un
simple cambio empleamos la macro del mismo nombre.
Listado 3.14: Parte de subrut8.s
.macro abs
tst r0, r0
negmi r0, r0
.endm
.data
var1 : .asciz " %d \ n "
.text
.global main
/* Salvo registros */
main : push { r4, lr }
Borra el bl antes del abs para probar la versin con macros. Dentro de gdb la
secuencia de comandos para contar los pasos saltndose el bl printf junto con la
cuenta es la siguiente.
start -> si 6 -> ni -> si 5 -> ni ->
-> si 5 -> ni -> si 5 -> ni -> si 2
6 + 1 + (5 + 1) 3 + 2 = 27
Reescribe el ejercicio anterior de la media aritmtica empleando macros en vez
de funciones.
Conteo de ciclos
Completa la siguiente tabla usando los dos tipos de conteo que acabamos de
explicar.
abs 27
Seleccin.
Insercin.
Quicksort.
Contenido
4.1 Lectura previa . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.1.1 Libreras y Kernel, las dos capas que queremos saltarnos . 84
4.1.2 Ejecutar cdigo en Bare Metal . . . . . . . . . . . . . . . 86
4.2 Acceso a perifricos . . . . . . . . . . . . . . . . . . . . . . 88
4.2.1 GPIO (General-Purpose Input/Output) . . . . . . . . . . 89
4.2.2 Temporizador del sistema . . . . . . . . . . . . . . . . . . 95
4.3 Ejemplos de programas Bare Metal . . . . . . . . . . . . 96
4.3.1 LED parpadeante con bucle de retardo . . . . . . . . . . . 96
4.3.2 LED parpadeante con temporizador . . . . . . . . . . . . 99
4.3.3 Sonido con temporizador . . . . . . . . . . . . . . . . . . . 99
4.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4.4.1 Cadencia variable con bucle de retardo . . . . . . . . . . . 101
4.4.2 Cadencia variable con temporizador . . . . . . . . . . . . 101
4.4.3 Escala musical . . . . . . . . . . . . . . . . . . . . . . . . 101
83
84 4.1. Lectura previa
que traducido viene a ser algo como Metal desnudo, haciendo referencia a que
estamos ante la mquina tal y cmo es, sin ninguna capa de abstraccin de por
medio.
Veremos ejemplos de acceso directo a perifricos, en concreto al LED de la pla-
ca auxiliar (ver apndice B) y a los temporizadores, que son bastante sencillos de
manejar.
Ahora veremos un ejemplo en el cual nos saltamos la capa intermedia para comu-
nicarnos directamente con el kernel va llamada al sistema. En este ejemplo vamos
a escribir una simple cadena por pantalla, en concreto "Hola Mundo!".
Listado 4.1: esbn1.s
.data
.text
.global main
La instruccin que ejecuta la llamada al sistema es swi #0, siempre tendr cero
como valor inmediato. El cdigo numrico de la llamada y el nmero de parmetros
podemos buscarlo en cualquier manual de Linux, buscando Linux system call table
en Google. En nuestro caso la llamada write se corresponde con el cdigo 4 y acepta
tres parmetros: manejador de fichero, direccin de los datos a escribir (nuestra
cadena) y longitud de los datos. En nuestro ejemplo, el manejador de fichero es el
1, que est conectado con la salida estndar o lo que es lo mismo, con la pantalla.
En general se tiende a usar una lista reducida de posibles llamadas a sistema, y
que stas sean lo ms polivalentes posibles. En este caso vemos que no existe una
funcin especfica para escribir en pantalla. Lo que hacemos es escribir bytes en un
fichero, pero usando un manejador especial conocido como salida estndar, con lo
cual todo lo que escribamos a este fichero especial aparecer por pantalla.
Pero el propsito de este captulo no es saltarnos una capa para comunicarnos
directamente con el sistema operativo. Lo que queremos es saltarnos las dos capas
y enviarle rdenes directamente a los perifricos. Para esto tenemos prescindir del
sistema operativo, o lo que es lo mismo, hacer nosotros de sistema operativo para
realizar las tareas que queramos.
Este modo de trabajar (como hemos adelantado) se denomina Bare Metal, por-
que accedemos a las entraas del hardware. En l podemos hacer desde cosas muy
sencillas como encender un LED hasta programar desde cero nuestro propio sistema
operativo.
En caso de un programa Bare Metal tenemos que cambiarla por esta otra.
as -o ejemplo.o ejemplo.s
ld -e 0 - Ttext = 0x8000 -o ejemplo.elf ejemplo.o
objcopy ejemplo.elf -O binary kernel.img
Otra caracterstica de Bare Metal es que slo tenemos una seccin de cdigo
(la seccin .text), y no estamos obligados a crear la funcin main. Al no ejecutar
ninguna funcin no tenemos la posibilidad de salir del programa con bx lr, al fin y
al cabo no hay ningn sistema operativo detrs al que regresar. Nuestro programa
debe trabajar en bucle cerrado. En caso de tener una tarea simple que queramos
terminar, es preferible dejar el sistema colgado con un bucle infinito como ltima
instruccin.
El proceso de arranque de la Raspberry Pi es el siguiente:
Apagamos la Raspberry.
Es un proceso sencillo para las prcticas que vamos a hacer, pero para proyectos
ms largos se vuelve bastante tedioso. Hay varias alternativas que agilizan el ciclo
de trabajo, donde no es necesario extraer la SD y por tanto podemos actualizar el
kernel.img en cuestin de segundos. Estas alternativas son:
Los puertos del GPIO estn mapeados en memoria, tomando como base la di-
reccin 0x20200000. Para nuestros propsitos de esta leccin nos basta con acceder
a los puertos GPFSELn, GPSETn y GPCLRn. A continuacin tenemos la tabla con
las direcciones de estos puertos.
GPFSELn
Las 54 seales/pines las separamos en 6 grupos funcionales de 10 seales/pines
cada uno (excepto el ltimo que es de 4) para programarlas mediante GPFSELn.
El LED que queremos controlar se corresponde con la seal nmero 9 del puerto
GPIO. Se nombran con GPIO ms el nmero correspondiente, en nuestro caso sera
GPIO 9. Ntese que la numeracin empieza en 0, desde GPIO 0 hasta GPIO 53.
As que la funcionalidad desde GPIO 0 hasta GPIO 9 se controla con GPFSEL0,
desde GPIO 10 hasta GPIO 19 se hace con GPFSEL1 y as sucesivamente. Nosotros
queremos encender el primer LED rojo de la placa auxiliar. En la figura B.3 vemos
que el primer LED rojo se corresponde con GPIO 9. Para cambiar la funcionalidad de
GPIO 9 nos toca actuar sobre GPFSEL0. Por defecto cuando arranca la Raspberry
todos los pines estn preconfigurados como entradas, con lo que los LEDs de nuestra
placa auxiliar estn apagados. Es ms, aunque lo configuremos como salida, tras el
reset, los pines se inicializan al valor cero (nivel bajo), por lo que podemos presuponer
que todos los LEDs estarn apagados, incluso despus de programarlos como salidas.
El puerto GPFSEL0 contiene diez grupos funcionales llamados FSELx (del 0 al
9) de 3 bits cada uno, quedando los dos bits ms altos sin usar. Nos interesa cambiar
FSEL9, que sera el que se corresponde con el primer LED rojo, el que queremos
encender. Las posibles configuraciones para cada grupo son:
000 = GPIO Pin X es una entrada
001 = GPIO Pin X es una salida
100 = GPIO Pin X toma funci n alternativa 0
101 = GPIO Pin X toma funci n alternativa 1
110 = GPIO Pin X toma funci n alternativa 2
111 = GPIO Pin X toma funci n alternativa 3
011 = GPIO Pin X toma funci n alternativa 4
010 = GPIO Pin X toma funci n alternativa 5
Las funciones alternativas son para dotar a los pines de funcionalidad especficas
como puertos SPI, UART, audio PCM y cosas parecidas. La lista completa est en
la tabla 6-31 (pgina 102) del datasheet [4]. Nosotros queremos una salida genrica,
as que nos quedamos con el cdigo 001 para el grupo funcional FSEL9 del puerto
GPFSEL0 que es el que corresponde al GPIO 9.
GPSETn y GPCLRn
Los 54 pines se reparten entre dos puertos GPSET0/GPCLR0, que contienen los
32 primeros, y en GPSET1/GPCLR1 estn los 22 restantes, quedando libres los 10
bits ms significativos de GPSET1/GPCLR1.
Una vez configurado GPIO 9 como salida, ya slo queda saber cmo poner un
cero o un uno en la seal GPIO 9, para apagar y encender el primer LED de la placa
auxiliar respectivamente (un cero apaga y un uno enciende el LED).
Para ello tenemos los puertos GPSETn y GPCLRn, donde GPSETn pone un 1
y GPCLRn pone un 0. En principio parece enrevesado el tener que usar dos puer-
tos distintos para escribir en el puerto GPIO, pero no olvidemos que para ahorrar
recursos varios pines estn empaquetados en una palabra de 32 bits. Si slo tuvi-
ramos un puerto y quisiramos alterar un nico pin tendramos que leer el puerto,
modificar el bit en cuestin sin tocar los dems y escribir el resultado de nuevo en el
puerto. Por suerte esto no es necesario con puertos separados para setear y resetear,
tan slo necesitamos una escritura en puerto poniendo a 1 los bits que queramos
setear/resetear y a 0 los bits que no queramos modificar.
En la figura 4.4 vemos cmo est hecho el conexionado de la placa auxiliar.
En nuestro primer ejemplo de Bare Metal slo vamos a encender el primer LED
rojo de la placa auxiliar, que como hemos dicho se corresponde con el GPIO 9 as
que tendremos que actuar sobre el bit 9 del registro GPSET0.
Resumiendo, los puertos a los que accedemos para encender y apagar el LED
vienen indicados en la figura 4.5.
El siguiente cdigo (listado 4.2) muestra cmo hemos de proceder.
El acceso a los puertos lo hemos hecho usando la direccin base donde estn
mapeados los perifricos 0x20200000. Cargamos esta direccin base en el registro
r0 y codificamos los accesos a los puertos E/S con direccionamiento a memoria
empleando distintas constantes como desplazamiento en funcin del puerto al que
queramos acceder.
El cdigo simplemente escribe dos constantes en dos puertos: GPFSEL0 y GPSET0.
Con la primera escritura configuramos el LED como salida y con la segunda escritura
lo encendemos, para finalmente entrar en un bucle infinito con infi: b infi.
Otros puertos
Ya hemos explicado los puertos que vamos a usar en este captulo, pero el dis-
positivo GPIO tiene ms puertos.
GPLEVn. Estos puertos devuelven el valor del pin respectivo. Si dicho pin est
en torno a 0V devolver un cero, si est en torno a 3.3V devolver un 1.
GPEDSn. Sirven para detectar qu pin ha provocado una interrupcin en caso
de usarlo como lectura. Al escribir en ellos tambin podemos notificar que ya
hemos procesado la interrupcin y que por tanto estamos listos para que nos
vuelvan a interrumpir sobre los pines que indiquemos.
GPRENn. Con estos puertos enmascaramos los pines que queremos que provo-
quen una interrupcin en flanco de subida, esto es cuando hay una transicin
de 0 a 1 en el pin de entrada.
GPFENn. Lo mismo que el anterior pero en flanco de bajada.
Los comparadores son puertos que se pueden modificar y se comparan con CLO.
En el momento que uno de los 4 comparadores coincida y estn habilitadas las
interrupciones para dicho comparador, se produce una interrupcin y se activa el
correspondiente bit Mx asociado al puerto CS (para que en la rutina de tratamiento
de interrupcin o RTI sepamos qu comparador ha provocado la interrupcin). Los
comparadores C0 y C2 los emplea la GPU internamente, por lo que nosotros nos
ceiremos a los comparadores C1 y C3.
Las interrupciones las veremos en la siguiente leccin. Por ahora slo vamos a
acceder al puerto CLO para hacer parpadear un LED a una frecuencia determinada.
El esquema funcional del System Timer se muestra en la figura 4.9.
Para compilar y ejecutar este ejemplo sigue los pasos descritos en 4.1.2. Al eje-
cutar el kernel.img resultante comprobamos que el LED no parpadea sino que est
encendido con menos brillo del normal. En realidad s que lo hace, slo que nuestro
ojo es demasiado lento como para percibirlo. Lo siguiente ser ajustar la cadencia
del parpadeo a un segundo para que podamos observar el parpadeo. La secuencia
sera apagar el LED, esperar medio segundo, encender el LED, esperar otro me-
dio segundo y repetir el bucle. Sabemos que el procesador de la Raspberry corre a
700MHz por lo que vamos a suponer que tarde un ciclo de este reloj en ejecutar cada
instruccin. En base a esto vamos a crear dos bucles de retardo: uno tras apagar el
LED y otro tras encenderlo de 500ms cada uno. Un bucle de retardo lo nico que
hace es esperar tiempo sin hacer realmente nada.
Si suponemos que cada instruccin consume un ciclo y teniendo en cuenta que el
bucle de retardo tiene 2 instrucciones, cada iteracin del bucle consume 2 ciclos. A
700 MHz (7108 ciclos/segundo) un ciclo consume 1/(7 108 ) segundos que es igual
a 1,42109 s (aproximadamente 1,5 ns). As que cada iteracin en principio consume
3 ns y para consumir 500 ns necesitamos 500 103 /(3 109 ) = 166,66 106 , es
decir ms de 166 millones de iteraciones.
Si usamos ese nmero de iteraciones observaremos como la cadencia del LED
es ms lenta de lo esperado, lo que quiere decir que cada iteracin del bucle de
retardo tarda ms de los dos ciclos que hemos supuesto. Probamos con cronmetro
en mano distintos valores para las constantes hasta comprobar que con 7 millones de
iteraciones del bucle se consigue ms o menos el medio segundo buscado. Haciendo
cuentas nos salen 50 ciclos por iteraccin, bastante ms de los 2 ciclos esperados.
Esto se debe a una dependencia de datos (ya que el flag que altera la orden subs es
requerido justo despus por la instruccin bne) y que los saltos condicionales suelen
ser lentos.
Listado 4.4: Parte de esbn4.s
.set GPBASE, 0x20200000
.set GPFSEL0, 0x00
.set GPSET0, 0x1c
.set GPCLR0, 0x28
.text
ldr r0, = GPBASE
/* guia bits x x 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 0 0 0 */
mov r1, # 0 b 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPFSEL0 ] @ Configura GPIO 9
/* guia bits 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
sin (GPIO 4). Tambin modificamos el tiempo de espera para producir un sonido
audible.
Vamos a producir un tono de 440 Hz. Para ello generamos una onda cuadrada
por dicho pin, que no es ms que una serie de ceros y unos consecutivos de idntica
duracin. A esta duracin la llamamos semi-periodo, y es la que queremos calcular.
Como el periodo es el inverso de la frecuencia, tenemos que periodo = 1/(440s1 ) =
2,272103 s, por lo que el semi-periodo buscado es 2,272103 s/2 = 1,136103 s
o lo que es lo mismo, 1136 microsegundos.
Listado 4.6: Parte de esbn6.s
ldr r0, = GPBASE
/* guia bits x x 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 0 0 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPFSEL0 ] @ Configura GPIO 4
/* guia bits 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
ldr r2, = STBASE
Interrupciones hardware
Contenido
5.1 Lectura previa . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.1.1 El sistema de interrupciones del ARM . . . . . . . . . . . 104
5.1.2 Rutina de tratamiento de interrupcin . . . . . . . . . . . 109
5.1.3 Pasos para configurar las interrupciones . . . . . . . . . . 110
5.1.4 El controlador de interrupciones . . . . . . . . . . . . . . 112
5.1.5 Ejemplo. Encender LED rojo a los 4 segundos . . . . . . . 114
5.1.6 Ejemplos de aplicacin . . . . . . . . . . . . . . . . . . . . 118
5.1.7 Parpadeo de todos los LEDs . . . . . . . . . . . . . . . . . 119
5.1.8 Control de LEDs rojos con pulsadores . . . . . . . . . . . 123
5.1.9 Parpadeo secuencial de LEDs con sonido por altavoz . . . 127
5.1.10 Manejo de FIQs y sonidos distintos para cada LED . . . . 133
5.1.11 Control de luces/sonido con pulsadores en lugar tempori-
zadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
5.2 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
5.2.1 Todo con IRQs . . . . . . . . . . . . . . . . . . . . . . . . 142
5.2.2 Alargar secuencia a 10 y parpadeo . . . . . . . . . . . . . 142
5.2.3 Tope de secuencia y limitar sonido . . . . . . . . . . . . . 142
5.2.4 Reproductor de meloda sencilla . . . . . . . . . . . . . . . 143
103
104 5.1. Lectura previa
conocer de forma detallada cmo funcionan los puertos asociados, ya que ste es el
mecanismo tpico mediante el cual el procesador se comunica con los perifricos.
Hacemos incapi en lo de hardware porque las interrupciones software no son
ms que las llamadas a sistema que vimos en el captulo anterior. Ambas comparten
vector de interrupciones, pero las interrupciones software son ms bien llamadas a
subrutinas.
Cada modo tiene sus propios registros sp, lr y spsr (Saved Program Status Re-
gister) de tal forma que no alteramos la pila ni los flags de la secuencia de programa
que interrumpimos. Incluso el modo FIQ tiene 5 registros generales propios (desde
r8 hasta r12), de esta forma si los empleamos en nuestra rutina de tratamiento
no tendremos que salvaguardarlos en pila. En la figura 5.2 observamos los registros
propios mencionados marcados con un tringulo.
Por ltimo estn las excepciones que nos interesan y que trataremos en este
captulo, que son las interrupciones normales IRQ y las interrupciones rpidas
FIQ.
Puesto que cada interrupcin, N I, lleva asociada una rutina, de alguna forma,
debe haber una correspondencia entre este N I y la ubicacin del vector asociado, que
contiene la instruccin de salto a la rutina que debe ejecutar cuando se produce la
interrupcin. La forma de hacerlo es multiplicar por cuatro el nmero de interrupcin
para obtener un desplazamiento (N I*4). Se multiplica por 4 porque cada vector de
excepcin ocupa 4 bytes (es lo que ocupa una instruccin en ARM).
Cuando se activa una interrupcin, la CPU detiene su trabajo para atenderla.
Despus, contina su trabajo donde lo dej. Los pasos a seguir para que esto sea
posible son:
4. Se ejecuta la rutina.
5. La ltima instruccin de la rutina es subs pc, lr, #4, que se encarga de res-
taurar los flags originales y el modo copiando spsr en cpsr. Adems volvemos
al punto donde se interrumpi copiando de lr a pc (con el desplazamiento
correspondiente).
El registro cpsr contiene 3 flags globales mediante los cuales podemos habilitar
o inhabilitar las interrupciones: uno para Abort llamado A, otro para IRQ llamado
I y el ltimo para FIQ denominado F.
El manejo de estos flags corre a cuenta del usuario, en ningn momento la CPU
enmascara dichos flags. Por esta razn, si queremos dar prioridad a una interrupcin
en particular para no ser interrumpidos nuevamente, debemos enmascarar dichos
flags al comienzo de su RTI.
Vemos que a diferencia de las subrutinas donde salamos con lr, en una RTI
salimos con lr-4 (si es un error en datos sera lr-8), a ello se debe que la ltima
instruccin sea subs en lugar de movs. Y porqu hay un sufijo s al final de la
instruccin sub? Pues porque se trata de instruccin especial que sirve para restaurar
el registro cpsr que haba antes de la interrupcin (copia spsr_irq o spsr_fiq en
cpsr).
Imaginemos que el programa principal est en modo supervisor y que la inte-
rrupcin que esperamos es del tipo IRQ. Cada modo de operacin (en particular
el modo IRQ) tiene 3 registros replicados: sp, lr y spsr. Para evitar confusiones
los nombramos con los sufijos de modo _svc y _irq correspondientes. Cuando ocu-
rre una interrupcin pasamos de modo supervisor a modo IRQ, pero antes hemos
guardado el registro cpsr en spsr_irq.
Los registros sp_svc y lr_svc no se tocan para nada, con lo que no alteramos
ni la pila ni el registro de retorno del modo supervisor. El registro lr_irq se carga
apuntando a la instruccin i+2 siguiente a la que fue interrumpida, pc+8. El resto
de registros debemos salvarlos en pila si tenemos la intencin de modificarlos en
nuestra RTI, al tener registro propio sp_irq se trata de una pila independiente que
no interfiere con la principal sp_svc. Luego se ejecuta el cdigo particular de la RTI,
ejemplos en los que usemos FIQ e IRQ inicializamos la pila de FIQ a 0x4000,
la de IRQ a 0x8000 y la del modo Supervisor a 0x8000000. Como la memoria
de programa empieza en 0x8000 y la pila crece hacia abajo, tendremos 16K
de pila en modo IRQ, otros 16K en modo FIQ y 128Mb a compartir entre
programa principal y pila de programa. El mapa de memoria sera el indicado
en la figura 5.4
Las FIQs slo tienen un puerto de control asociado, quedando todo el detalle en
las IRQs. Hay tres grupos de tres puertos cada uno. El primer grupo (Pending) sirve
para indicar que hay una interrupcin pendiente, el segundo (Enable) es para ha-
bilitar las interrupciones y el tercero (Disable) para deshabilitarlas. Dentro de cada
grupo tenemos un puerto bsico que tiene un resumen sobre el mapa de interrup-
ciones y otros dos puertos que indican con ms detalle la fuente de la interrupcin.
En el puerto bsico hay fuentes individuales GPU IRQ x y bits que engloban a varias
fuentes Bits in PR1, que por ejemplo indica que el origen hay que buscarlo en el
puerto 1. En el puerto 1 estn las primeras 32 posiciones del mapa de interrupciones,
mientras que en el puerto 2 estn las 32 ltimas.
La documentacin oficial sobre el mapa de interrupciones est incompleta, pero
buscando un poco por internet se puede encontrar que las interrupciones asociadas
al System Timer se controlan con los 4 primeros bits de la tabla (uno para cada
comparador).
En la figura 5.6 vemos los puertos ordenados en grupos.
La forma habitual de trabajar es usar el puerto apropiado del grupo Enable para
habilitar la fuente de interrupcin que queramos que nos interrumpa. Luego en el
caso de ser interrumpidos podemos detectar cul ha sido la fuente leyendo el mismo
ndice Fuente
0-63 Interrupciones IRQ 1 y 2 (ver figura 5.5)
64 ARM Timer
65 ARM Mailbox
66 ARM Doorbell 0
67 ARM Doorbell 1
68 GPU0 detenida
69 GPU1 detenida
70 Acceso ilegal de tipo 1
71 Acceso ilegal de tipo 2
bit del grupo Pending y finalmente, si pasamos a otra seccin del programa donde
no queremos que nos interrumpa ms dicha fuente la desactivamos con el grupo
Disable.
A parte del controlador de interrupciones, cada dispositivo tiene su propio me-
canismo de habilitar/deshabilitar y detectar/notificar la fuente de interrupcin. En
el caso del GPIO tenemos los puertos GPRENn, GPFENn, GPHENn, GPLENn, GPARENn y
GPAFENn para habilitar/deshabilitar. Para detectar/notificar estn los GPEDSn.
Para el temporizador tenemos que STCS hace las funciones de deteccin y notifica-
cin. No existen puertos especficos para habilitar/deshabilitar ya que el controlador
de interrupciones permite habilita/deshabilitar cada comparador por separado.
El nico puerto que nos falta por ver es FIQ control INTFIQCON que hemos
mostrado en la figura 5.5. Antes mostraremos la lista de fuentes de interrupcin
aplicables a este puerto.
Son las mismas fuentes que en IRQ pero condensadas en un nico puerto. De 0
a 31 coincide con la tabla IRQ 1, de 32 a 63 con IRQ 2 y de 64 en adelante con IRQ
Basic.
El puerto INTFIQCON se programa con los 8 bits inferiores, indicando en el bit 7
si queremos habilitar la fuente, y en los bits del 0 al 6 ponemos el ndice de la fuente
que se corresponde con la lista. A diferencia de las IRQ, con las FIQ slo podemos
atender a una fuente de interrupcin.
El modo viene indicado en la parte ms baja del registro cpsr, el cual modifica-
remos con la instruccin especial msr. En la figura 5.1 vemos el contenido completo
del registro cpsr. Como cpsr es un registro muy heterogneo, usamos sufijos para
acceder a partes concretas de l. En nuestro caso slo nos interesa cambiar el byte
bajo del registro, aadimos el sufijo _c llamndolo cpsr_c, para no alterar el resto
del registro. Esta parte comprende el modo de operacin y las mscaras globales de
las interrupciones. Otra referencia til es cpsr_f que modifica nicamente la parte
de flags (byte alto). Las otras 3 referencias restantes apenas se usan y son cpsr_s
(Status) para el tercer byte, cpsr_x (eXtended) para el segundo byte y cpsr_csxf
para modificar los 4 bytes a la vez.
En la siguiente tabla vemos cmo se codifica el modo de operacin.
Como las interrupciones globales de IRQ y FIQ estn desactivadas (estado por
defecto tras el reset), mantenemos a 1 dichos bits.
El cdigo que inicializa los punteros de pila es el siguiente:
mov r0, # 0b11010010 @ Modo IRQ, FIQ & IRQ desact
msr cpsr_c, r0
mov sp, # 0x8000
mov r0, # 0b11010011 @ Modo SVC, FIQ & IRQ desact
msr cpsr_c, r0
mov sp, # 0x8000000
Observamos que la RTI es muy sencilla, aparte del esqueleto tenemos tres ins-
trucciones encargadas de encender el LED en cuestin.
Y vamos enumerando, por orden, los pasos que hemos seguido. En primer lugar
apuntamos a nuestra RTI en el vector de interrupciones:
ADDEXC 0x18, irq_handler
Lo siguiente es configurar los pines GPIO asociados a los 6 LEDs como salidas:
ldr r0, = GPBASE
mov r1, # 0 b 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPFSEL0 ]
/* guia bits x x 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 0 0 0 */
ldr r1, = 0 b 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
str r1, [ r0, # GPFSEL1 ]
ldr r1, = 0 b 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
str r1, [ r0, # GPFSEL2 ]
ledst : .word 0
/* Enciendo LEDs 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPSET0 ]
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
str r1, [ r0, # GPFEN0 ]
ldr r0, = INTBASE
/* guia bits 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
str r1, [ r0, # INTENIRQ2 ]
Veamos ahora el aspecto que tiene la RTI. Lo primero es poner los LEDs sus-
ceptibles de encenderse (los LEDs rojos) a cero:
irq_handler :
push { r0, r1 }
ldr r0, = GPBASE
/* Apaga los dos LEDs rojos 5 43 21 0 98 76 54 3 21 09 87 6 54 32 10 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPCLR0 ]
Con esta segunda fuente vamos a controlar el altavoz, como podemos observar en
la figura 5.9. Sacar un tono puro por el altavoz es equivalente a hacer parpadear un
LED, lo nico que cambia es que usamos otro pin distinto GPIO 4 y aumentamos la
frecuencia para que sea audible (a 1 Hz el odo humano no captara sonido alguno).
Utilizaremos la frecuencia estndar de afinacin de 440 Hz, que coincide con el tono
de espera de marcado en telefona fija.
Por otro lado en lugar de hacer parpadear todos los LEDs lo que haremos es
repetir una secuencia de 6 posiciones en la que en todo momento slo uno de los
6 LEDs est encendido, que va cambiando de izquierda a derecha (aparentando
movimiento) y cuando se llegue al sexto LED comenzamos de nuevo desde el primero.
Para dar ms sensacin de movimiento disminuimos el periodo a 200 milisegundos.
La clave de todo est en saber cul de los dos comparadores ha producido la
interrupcin (se puede dar el caso en que salten los dos a la vez). sto se puede
hacer de dos formas distintas: o bien leemos el bit asociado systim_cx en el puerto
IRQ pending 1, o bien leemos el Mx del puerto CS. Elegimos el segundo caso, as no
gastamos otro puerto ms para almacenar INTBASE.
Como es muy parecido al ejemplo de antes, slo vamos a comentar las diferencias
que encontremos. La primera de ellas es que adems de los 6 GPIOs de los LEDs,
configuramos como salida un sptimo pin, el GPIO 4, para manejar el altavoz:
ldr r0, = GPBASE
ldr r1, = 0 b 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPFSEL0 ]
El siguiente cdigo es para incluir el comparador C3 (adems del C1 que haba an-
teriormente), tanto para proporcionar la primera interrupcin como para habilitarla
individualmente:
ldr r0, = STBASE
ldr r1, [ r0, # STCLO ]
add r1, #2
str r1, [ r0, # STC1 ]
str r1, [ r0, # STC3 ]
[ manejo de LEDs ]
Los registros r0 y r1 los hacemos apuntar a la base del System Timer y del GPIO
y no tocamos dichos valores durante toda la interrupcin, vamos a estar constan-
temente leyendo y escribiendo puertos y resulta incmodo tener que cargar la base
cada vez.
Es un error muy habitual suponer que la fuente de la interrupcin slo ha sido
una, aunque la gran mayora de las veces sea as se puede dar el caso de que coincidan
los dos comparadores a la vez. De la misma forma si sabemos que slo hay dos fuentes
y una de ellas no ha provocado la interrupcin, por descarte ha tenido que ser la
otra, podemos ahorrarnos la comprobacin.
El flujo sera el siguiente: leemos M1 para ver si la interrupcin la ha provocado
el comparador de C1, si ha sido as ejecutamos el cdigo de manejo de LEDs; si no,
saltamos directamente al manejo del altavoz (sabemos seguro que la fuente viene de
ah).
Tras el cdigo del manejo de LEDs leemos M3 para saber si adems de C1 ha
saltado tambin el comparador C3. Si no ha saltado, lo ms normal, salimos por
final; si lo ha hecho, procesamos la interrupcin con el cdigo de manejo del altavoz
Es un calco de la rutina que haca parpadear todos los LEDs, cambiando el valor
que se envia a GPCLR0/GPSET0, el comparador que es C3 en lugar de C1, y el valor
que sumamos al temporizador, que se corresponde a 440 Hz en vez de a 1 Hz.
Queremos que FIQ se active con C3, que es el bit 3 del IRQ 1, por tanto ndice 3
para la fuente FIQ. Como veis, la nica pega que tienen las FIQs es que slo admiten
una fuente de interrupcin. Adems del ndice ponemos el bit 7 a uno para indicar
que queremos habilitar dicha fuente, siendo la constante 0b10000011.
Ahora veamos el manejador IRQ (la RTI) que, como hemos adelantado, es ms
sencilla que en el ejemplo anterior:
/* Rutina de tratamiento de interrupci n IRQ */
irq_handler :
push { r0, r1, r2 }
ldr r0, = GPBASE
ldr r1, = cuenta
Seran las notas puras que van despus del LA estndar de 440 Hz (1136), cuyos
semitonos se obtienen multiplicando la frecuencia por raz duodcima de 2, que es
aproximadamente 1,05946. Las notas seran, en hercios: LA (440), SI (493,88), DO
(523,25), RE (587,33), MI (659,26) y FA (698,46).
Finalmente tenemos el manejador de FIQ asociado al altavoz. La eleccin de la
fuente de interrupcin no es arbitraria, hemos escogido FIQ para el altavoz porque
se ejecutar ms veces que el cambio de LEDs, concretamente 220 veces ms con la
nota ms grave. En estos ejemplos no importa, pero en casos reales donde el tiempo
de CPU es un recurso limitado, los ciclos que nos ahorramos con una FIQ en un
proceso crtico pueden ser determinantes:
/* Rutina de tratamiento de interrupci n FIQ */
fiq_handler :
ldr r8, = GPBASE
ldr r9, = bitson
/* Salgo de la RTI */
subs pc, lr, # 4
/* Enciendo LEDs 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPSET0 ]
Lo nuevo que vemos aqu es una escritura en el puerto GPFEN0. De esta forma le
decimos al controlador de interrupciones que esos pines del GPIO sern los nicos
que provoquen interrupciones, concretamente flancos con de bajada sncronos (justo
en el momento en que el botn toca fondo).
El manejador FIQ es idntico al del ejemplo anterior, saca el sonido que corres-
ponde al LED por el altavoz, cambiando C3 por C1.
Lo ms relevante de este ejemplo est en la RTI asociada a la IRQ, que es la
siguiente:
irq_handler :
push { r0, r1, r2 }
ldr r0, = GPBASE
ldr r1, = cuenta
Tenemos una bifurcacin (saltos condicionales) debido a que cada botn es una
fuente distinta de interrupcin y tenemos que distinguir qu botn se ha pulsado.
Aqu por suerte tenemos un puerto totalmente anlogo al STCS de los temporizado-
res. Se llama GPEDS0 (tambin hay otro GPEDS1 para los GPIOs de 32 a 53 que no
necesitamos) y sirve tanto para saber qu fuente ha producido la interrupcin como
para resetear su estado (y as permitir volver a ser interrumpidos por el mismo pin
GPIO).
Con la instruccin ands comprobamos si un determinado bit est a 1 y lo indi-
camos en el flag Z. Tambin podra valer la instruccin tst, que tiene la ventaja de
no destruir el registro a la salida (de la misma forma que cmp es el equivalente no
destructivo de subs).
Y por ltimo debemos sacar la secuencia inversa a la que tenamos para que
al pulsar el botn izquierdo las luces vayan hacia la izquierda y que con el botn
derecho vayan en el otro sentido. Si la secuencia de izquierda a derecha era (6, 5, 4,
3, 2, 1, 6, 5, 4...), la inversa sera (1, 2, 3, 4, 5, 6, 1...). Es decir, incrementamos y
cuando llegamos a 7 lo convertimos en 1. sto se hace con el siguiente fragmento:
add r2, # 1 @ Incremento
cmp r2, # 7 @ Comparo si llego a 7
moveq r2, # 1 @ Si es 7, volver a 1
Ntese que aqu la opcin destructiva subs (en lugar de cmp) no nos vale porque
necesitamos el valor del registro despus. S que podemos cambiarlo por un teq (la
alternativa no destructiva de eors).
5.2. Ejercicios
5.2.1. Todo con IRQs
Modifica el ltimo ejemplo (inter5.s) para controlar el altavoz tambin con
IRQs, prescindiendo totalmente de las interrupciones FIQs.
Duplica la secuencia a 10. Para ello utiliza el cdigo Morse aplicado a los dgitos
(todos tienen longitud 5). Cambia el punto (tono corto) por LED apagado y
el guin (tono largo) por LED encendido. Por supuesto los nuevos cdigos
tendrn su sonido asociado, sigue las notas (saltndote sostenidos y bemoles)
para completar la tabla.
En este ejemplo puedes profundizar todo lo que quieras. Por ejemplo empieza
codificando los silencios, stos son muy importantes y tambin forman parte de
la meloda. Un segundo paso sera codificar la duracin de las notas, si no lo has
hecho ya. Tambin es posible tener varios instrumentos sonando a la vez, aunque
slo dispongamos de un altavoz, busca por internet 1-bit music o beeper music
si quieres saber cmo se hace.
Apndice A
Funcionamiento de la macro
ADDEXC
Contenido
A.1 Finalidad y tipos de salto . . . . . . . . . . . . . . . . . . . 145
A.2 Eleccin: salto corto . . . . . . . . . . . . . . . . . . . . . . 146
A.3 Escribir una macro . . . . . . . . . . . . . . . . . . . . . . . 146
A.4 Codificacin de la instruccin de salto . . . . . . . . . . . 147
A.5 Resultado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Los saltos largos no tienen instruccin propia, se realizan mediante la carga del
registro pc partiendo de un dato en memoria.
145
146 A.2. Eleccin: salto corto
Dividir entre 4
Como todo son constantes en teora podramos implementar la macro con dos
instrucciones. Desgraciadamente el preprocesador que usamos no es muy potente y si
un operando es una etiqueta slo nos permite operar con sumas y restas. No podemos
hacer las divisiones o desplazamientos que necesitamos, con lo que emplearemos una
tercera instruccin para hacer el desplazamiento.
La direccin actual es \vector, la de la RTI es \dirRTI y hay que restarle 8 por
el segmentado de la CPU (ver figura A.2).
instruccin = (\dirRT I \vector 8)/4 + 0xEA000000
instruccin = (\dirRT I \vector)/4 + 0xE9F F F F F E
instruccin = (\dirRT I \vector + 3A7F F F F F 8)/4
instruccin = (\dirRT I \vector + A7F F F F F B)ROR2
Vemos cmo en el ltimo paso hemos transformado una divisin en una rotacin,
donde los 2 bits menos significativos (ambos a 1) pasan a ser los ms significativos
tras la rotacin.
A.5. Resultado
El cdigo final queda como sigue.
.macro ADDEXC vector, dirRTI
ldr r1, =(\ dirRTI -\ vector + 0xa7fffffb )
ROR r1, # 2
str r1, [ r0, #\ vector ]
.endm
Contenido
B.1 Esquema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
B.2 Pinout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
B.3 Correspondencia . . . . . . . . . . . . . . . . . . . . . . . . 151
B.4 Funcionamiento . . . . . . . . . . . . . . . . . . . . . . . . . 152
B.5 Presupuesto . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
B.6 Diseo PCB . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
149
150 B.1. Esquema
B.1. Esquema
B.2. Pinout
El puerto GPIO vara ligeramente dependiendo del modelo de Raspberry. En
nuestro caso la mayor diferencia est entre la revisin 1 y la 2, ya que el modelo B+
es compatible. Al ser idnticos los primeros 26 pines, cualquier perifrico diseado
para la revisin 2 es compatible con el modelo B+ (pero no al contrario).
La zona marcada con un recuadro verde (en la figura B.3) es donde conectaremos
nuestra placa auxiliar.
B.3. Correspondencia
En la siguiente tabla vemos la correspondencia entre puertos del GPIO y com-
ponentes. Los componentes son: 2 pulsadores, 6 LEDs y un altavoz piezoelctrico.
Los nmeros marcados con asterisco tienen otra correspondencia en la revisin 1.
B.4. Funcionamiento
Los LEDs son salidas que se activan (encienden) cuando escribimos un 1 en el
puerto correspondiente. Cuando estn a 0 permanecen apagados. Podemos jugar con
los tiempos de encendido/apagado para simular intensidades de luz intermedias.
El altavoz piezoelctrico es otra salida, conectada al puerto GPIO 4. A diferencia
de los LEDs no basta un 0 un 1 para activarlo, necesitamos enviar una onda
cuadrada al altavoz para que ste suene. Es decir, hay que cambiar rpidamente de
0 a 1 y viceversa, adems a una frecuencia que sea audible (entre 20 y 20000 Hz).
Por ltimo tenemos los pulsadores. Elctricamente son interruptores que conec-
tan el pin a masa cuando estn presionados. Cuando estn en reposo entran en
juego unas resistencias internas de la Raspberry (de pull-up) que anulan el compor-
tamiento de las de pull-up/pull-down que se cambian por software. De esta forma
los pulsadores envian un 0 lgico por el pin cuando estn pulsados y un 1 cuando
estn en reposo.
Los pulsadores y el LED verde de la derecha se corresponden con distintos puertos
segn el modelo de Raspberry. Podemos hacer que nuestro programa sea compatible
con todos los modelos, comprobando a la vez en las distintas entradas en el caso de
los pulsadores, o escribiendo a la vez en ambas salidas en el caso del LED verde.
En la figura B.4 tenemos la correspondencia entre pines, componentes y puertos
GPIO.
Contenido
C.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 155
C.2 Cable USB-serie desde el ordenador de desarrollo . . . . 155
C.3 Cable serie-serie que comunica dos Raspberries . . . . . 157
C.4 Reseteo automtico . . . . . . . . . . . . . . . . . . . . . . 159
C.5 Cdigo fuente del bootloader . . . . . . . . . . . . . . . . 162
C.1. Introduccin
En esta seccin profundizamos sobre dos mtodos para cargar programas en Bare
Metal sin necesidad de insertar y extraer continuamente la tarjeta SD. Existe un
tercer mtodo que no explicamos aqu, el del cable JTAG, pero pueden consultar los
archivos README del repositorio de David Welch[9].
Este apndice est basado en el contenido de dicho repositorio, y el cdigo fuente
del bootloader que mostramos aqu es idntico, el cual reproducimos con permiso
del autor.
155
156 C.2. Cable USB-serie desde el ordenador de desarrollo
simple como un LED parpadeante, por ejemplo el ltimo esbn5.s del captulo 4. A
partir del cdigo fuente generamos el binario en Bare Metal, para lo cual necesitamos
el toolchain (cadena de herramientas) ARM, de la que usaremos el ensamblador as, el
enlazador ld y el copiador de secciones objcopy. A estas herramientas, que generan
binarios o ejecutables para una plataforma diferente a la de la mquina que realiza
la compilacin, se las denomina herramientas de compilacin cruzada.
En estos momentos tenemos el binario Bare Metal generado, llammoslo esbn5.img.
El siguiente paso es enviar este archivo por el puerto serie de forma que se ejecute
en la Raspberry. Pero no lo enviamos de cualquier manera, sino que emplearemos
un protocolo de transferencia que se llama XMODEM. Por suerte es uno de los
protocolos mejor soportados por los emuladores de terminal.
Dependiendo de nuestra plataforma hay distintos emuladores de terminal dispo-
nibles. Para Windows tenemos HyperTerminal o Tera Term, y en Linux tenemos
minicom, aunque para lo que queremos hacer (enviar un archivo) nos basta con el
comando sx.
Antes de nada hay que configurar los parmetros del puerto serie en el emulador
de terminal que estemos empleando. Los valores son: 8 bits de datos, sin paridad, 1
bit de parada, sin flujo de control y velocidad de transferencia de 115200 baudios.
Son todos parmetros por defecto excepto la velocidad, por lo que hay que asegurarse
de cambiar la velocidad antes de proceder a transferir el archivo.
Luego elegimos el protocolo, XMODEM, y le damos a transferir, seleccionando nues-
tro esbn5.img como archivo de origen. Si todo ha ido bien debera aparecer un men-
saje indicndolo en nuestro programa terminal y observaremos el LED parpadenado
en la Raspberry, prueba de que la transferencia ha sido exitosa.
En Linux es fcil automatizar este proceso con el comando sx, que es creando el
siguiente script enviar.
stty -F / dev / ttyUSB0 115200
sx $1 < / dev / ttyUSB0 > / dev / ttyUSB0
Y para enviar el archivo anterior con este script escribimos bajo lnea de coman-
dos lo siguiente.
. / enviar esbn5.img
esta ltima alternativa es que podemos compilar desde la Raspberry sin necesidad
de tener instaladas las herramientas de compilacin cruzada en tu ordenador. Y
otra ventaja es que no necesitas estar fsicamente cerca de la Raspberry ni tener
enchufado el adaptador USB, te puedes conectar inalmbricamente a la Raspberry
mediante un router Wifi (con cable Ethernet entre el router y la Raspberry).
Lo primero es diferenciar las dos Raspberries. A una la llamamos Raspberry
de desarrollo, en la cual tendremos instalado Raspbian y es en la que trabajamos
directamente (con teclado y pantalla) o bien nos conectamos con ella mediante ssh.
A la otra la llamamos Raspberry Bare Metal, en la que sobreescribimos el kernel.img
de la SD con el mismo bootloader de antes. Es en esta Raspberry donde se ejecutan
los programas Bare Metal que vamos a desarrollar y por tanto donde enchufaremos
nuestra placa auxiliar.
La conexin entre ambas Raspberries se hace uniendo ambas masas y cruzando
los cables TXD y RXD de cada puerto serie, como viene indicado en la figura C.2.
Por defecto el puerto serie en Raspbian viene configurado como salida de consola.
Esta configuracin no nos interesa, se usa para diagnosticar errores mostrando por
un terminal los mensajes del arranque. Pero nosotros queremos usarlo como un
puerto serie genrico, para lo cual es necesario hacer los siguientes cambios.
En el archivo /etc/inittab descomentamos la lnea que empieza con T0:23...
y que hace mencin a la cadena ttyAMA0, y guardamos el archivo.
Luego en el archivo /boot/cmdline.txt buscamos los dos sitios (puede haber
uno slo) donde aparece ttyAMA0. Borramos los textos que hay entre espacios y que
incluyen el ttyAMA0, y despus guardamos.
Para comprobar que todo ha ido bien reseteamos la Rasbperry con sudo reboot
y tras el arranque escribimos.
cat / proc / cmdline
Para verificar que el nico proceso que se lista en la salida es el del propio
comando ps y no existen otros.
Llegados a este punto ya tenemos el puerto serie disponible para nosotros. El
resto de pasos seran como en el caso anterior, pero cambiando la referencia que se
hace al puerto. Donde antes apareca /dev/ttyUSB0 (o algo similar) lo cambiamos
por /dev/ttyAMA0.
E inclumos el pulso reset mediante comandos, por ejemplo nuestro script que
compila y enva el archivo (todo en un paso) quedara as.
gpio export 18 out
as -o tmp.o $1
gpio export 18 in
ld -e 0 - Ttext = 0x8000 -o tmp.elf tmp.o
objcopy tmp.elf -O binary tmp.img
stty -F / dev / ttyAMA0 115200
sx tmp.img < / dev / ttyAMA0 > / dev / ttyAMA0
uart_init ();
hexstring (0 x12345678 );
hexstring ( GETPC ());
hexstring ( ARMBASE );
timer_init ();
// SOH 0 x01
// ACK 0 x06
// NAK 0 x15
// EOT 0 x04
block =1;
addr = ARMBASE ;
state =0;
crc =0;
rx = timer_tick ();
while (1)
{
ra = timer_tick ();
if (( ra - rx ) >=4000000)
{
uart_send (0 x15 );
rx +=4000000;
}
if (( uart_lcr ()&0 x01 )==0) continue ;
xstring [ state ]= uart_recv ();
rx = timer_tick ();
if ( state ==0)
{
if ( xstring [ state ]==0 x04 )
{
uart_send (0 x06 );
for ( ra =0; ra <30; ra ++) hexstring ( ra );
hexstring (0 x11111111 );
hexstring (0 x22222222 );
hexstring (0 x33333333 );
uart_flush ();
BRANCHTO ( ARMBASE );
break ;
}
}
switch ( state )
{
case 0:
{
if ( xstring [ state ]==0 x01 )
{
crc = xstring [ state ];
state ++;
}
else
{
// state =0;
uart_send (0 x15 );
}
break ;
}
case 1:
{
if ( xstring [ state ]== block )
{
crc += xstring [ state ];
state ++;
}
else
{
state =0;
uart_send (0 x15 );
}
break ;
}
case 2:
{
Se trata del byte SOH seguido del nmero de bloque, luego tenemos otra vez el
nmero de bloque pero complementado, a continuacin los 128 bytes de datos para
acabar con un ltimo byte de suma de comprobacin. Este ltimo byte es la suma de
todos los anteriores, quedndonos con los 8 bits menos significativos del resultado.
Entonces la Raspberry lleva la cuenta del byte por el que vamos dentro de dicho
paquete a partir de switch(state). De tal forma que si state vale 0, lo que espe-
ramos es SOH o EOT, cualquier otro valor indica que algo va mal por tanto enviamos
un NAK al host y ponemos state a cero.
Para los estados 1 y 2 simplemente comprobamos que el byte recibido coincide
con el nmero de bloque, y reportamos error en caso contrario de la misma forma
que antes (enviando NAK y state=0).
Luego tenemos los estados que van entre 3 y 131, en los que vamos escribiendo
el fichero en memoria e incrementando el puntero, a la vez que vamos calculando el
byte de suma para la comprobacin.
Por ltimo tenemos el estado 131, en el cual ya hemos recibido los bytes de
datos y lo que leemos ahora es el byte de suma de comprobacin. Comparamos que
coincide con el valor esperado, respondiendo con ACK, o notificamos del error como
siempre (con NAK y state=0).
En cuanto el host recibe el ACK del ltimo paquete enviado, ste en lugar de
enviar de nuevo un paquete completo, enva un slo byte, EOT, para indicar a la
Raspberry que ya no quedan ms paquetes por enviar y se acaba la transmisin.
Esta situacin la comprueba la Raspberry al principio de cada paquete, de tal
forma que si recimibos un EOT del host damos por acabada la transmisin y ejecu-
Contenido
D.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 169
D.2 Pulsadores en la placa auxiliar . . . . . . . . . . . . . . . 170
D.3 Ejemplo de aplicacin . . . . . . . . . . . . . . . . . . . . . 170
D.3.1 Pulsador a masa sin cambiar configuracin . . . . . . . . . 170
D.3.2 Pulsador a masa cambiando configuracin . . . . . . . . . 172
D.3.3 Pulsador a Vcc sin cambiar configuracin . . . . . . . . . 175
D.1. Introduccin
En general las resistencias de pull-up y pull-down son resistencias que se ponen
en las entradas para fijar la tensin que de otra forma quedara indeterminada, al
estar en situacin de circuito abierto o alta impedancia. El ejemplo tpico donde se
usa es en un pulsador. Elctricamente un pulsador no es ms que un interruptor
que deja pasar la corriente cuando est pulsado y se queda en circuito abierto en su
posicin de reposo (sin pulsar). De los dos contactos que tiene, uno se conecta a masa
y el otro al pin de entrada de la Raspberry. As que cuando lo pulsamos hacemos
un corto que llevara los cero voltios de la masa al pin de entrada (enviamos un cero
lgico), pero cuando est sin pulsar no enviamos nada al pin, ste se queda en lo
que se denomina alta impedancia.
Todos los pines del GPIO en la Raspberry se pueden configurar por software
para que se comporten como queramos: o bien sin resistencia, o con una resistencia
169
170 D.2. Pulsadores en la placa auxiliar
a Vcc (pull-up) o con una resistencia a masa (pull-down). Este tipo de resistencias
son dbiles (weak) debido a que estn dentro de la pastilla (SoC) y se implementan
con transistores. Se puede anular el efecto de estas resistencias poniendo resistencias
externas.
2. Esperar 150 ciclos. Esto provee el tiempo requerido de set-up para controlar
la seal.
4. Esperar otros 150 ciclos. Con esto le damos tiempo de hold suficiente a la
seal.
6. Escribir un 0 en GPPUDCLK0/1.
Una de las cosas que tenemos que hacer es esperar 150 ciclos (como mnimo).
Como sabemos que un salto condicional tarda al menos dos ciclos en ejecutarse,
nuestra rutina de retardo sera la siguiente.
wait : mov r1, # 50
wait1 : subs r1, # 1
bne wait1
bx lr
Y el cdigo que hace todo lo anterior, para poner a pull-up el GPIO 18 (donde
hemos puesto el pulsador) es el siguiente.
str r1, [ r0, # GPPUD ]
bl wait
/* guia bits 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
str r1, [ r0, # GPPUDCLK0 ]
bl wait
mov r1, # 0
str r1, [ r0, # GPPUD ]
str r1, [ r0, # GPPUDCLK0 ]
De esta forma aprovechamos que ese pin en concreto est conectado a pull-down
tras el reset, por lo que no habra que cambiar la configuracin del pin para obtener
lo que vemos en la figura D.4.
Prcticamente tendramos el mismo cdigo que en apend1.s no nos funcionaba,
la nica diferencia es que cambiamos los streq por strne.
Listado D.3: apend3.s
.include " inter.inc "
.text
ldr r0, = GPBASE
/* guia bits x x 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 0 0 0 */
mov r1, # 0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
[2] rferrer de THINK IN GEEK. Tutorial de asm para raspberry pi. http://
thinkingeek.com/2013/01/09/arm-assembler-raspberry-pi-chapter-1/,
2013-2014.
[5] Qemu - emulating raspberry pi the easy way (linux or windows!). http://
xecdesign.com/qemu-emulating-raspberry-pi-the-easy-way, 2012.
[10] Gerardo Bandera Burgueo, Maria ngeles Gonzlez Navarro, Eladio D. Guti-
rrez Carrasco, Julin Ramos Czar, Sergio Romero Montiel, Maria Antonia Tre-
nas Castro, and Julio Villalba Moreno. Prcticas de Estructura de Computado-
res. Universidad de Mlaga, 2002.
177
[11] Embedded Linux Wiki. Gpio y otros perifricos a bajo nivel. http://elinux.
org/RPi_Low-level_peripherals, 2012-2014.