ASM
POR AESOFT
Corregido
y pasado a HTML por dek_Oin
LECCIÓN
4:
ENTRADA/SALIDA (COMUNICACIÓN CON
EL HARDWARE I).
INTERRUPCIONES (COMUNICACIÓN CON
EL HARDWARE II).
LA PILA DEL 8086
Hola a todos. En esta lección vamos a tratar aspectos
muy interesantes
de la programación del PC, interrupciones, puertos, etc.
Espero que tengáis claro lo del error que os comentaba
en la lección 1,
acerca de los buses. Si no es as¡, a qué esperáis para
preguntar...
Bueno, seguimos con lo nuevo:
ENTRADA/SALIDA (COMUNICACIÓN
CON EL HARDWARE I)
La comunicación entre un programa y el hardware, es
decir los chips de
apoyo y las tarjetas de ampliación, se efectúa mediante
los llamados
Ports (puertos, en castellano). Estos puertos son zonas
de memoria
de 1 o 2 bytes de tamaño, en las cuales se depositan los
datos que van
a ser utilizados por los chips o tarjetas, y también se
depositan los
datos que devuelven estos chips o tarjetas al procesador.
En el PC, existe una zona de memoria de 64 Ks, ajena a la
memoria
principal, dedicada a los puertos. Es decir, estos 64 Ks
de memoria
no tienen nada que ver con la memoria disponible para los
programas.
Para realizar los movimientos de datos entre puertos y
procesador
existen instrucciones especiales: IN y OUT.
Tanto IN como OUT tienen dos formas de uso, las cuales
utilizan el
registro AL o AX para almacenar los datos a leer o
escribir. La forma
difiere en función de que se quiera acceder a un puerto
menor de 256 o
mayor.
Más todo directo o estético: corresponde cuando se
quiere acceder a un
puerto menor de 256.
IN AL,10H------------->
lee un byte del puerto 10h
IN AX,10H------------->
lee una palabra del puerto 10h
OUT 0FFH,AL--------- > escribe el valor de AL en el
puerto 0FFH
Método indirecto o dinámico: corresponde al caso de
querer acceder a
un puerto mayor de 256, para lo cual se utiliza el
registro DX indicando
el número de puerto.
IN AL,DX-------------->
lee un byte del puerto indicado por DX.
(Antes hemos tenido que introducir en DX la dirección
del puerto).
OUT DX,AX-----------> escribe la palabra contenida en
AX en el puerto DX.
Algunos ejemplos de puerto son:
60H ------------------> acepta entradas de teclado.
61h-------------------> controla el altavoz.
3F0HA3F7H----------> Opera sobre la controladora de
discos.
En el PC cualquier subsistema, exceptuando la memoria,
está controlado por el procesador a través de los puertos.
INTERRUPCIONES (COMUNICACIÓN
CON EL HARDWARE II)
Las interrupciones constituyen la forma en que la
circuitería externa
informa al microprocesador de que algo ha sucedido (como
que se ha pulsado
una tecla, por ejemplo) y solicita que se emprenda alguna
acción.
Pero no acaba ah¡ su utilidad, ya que las interrupciones
además son
el medio principal de comunicación entre las funciones
de la BIOS y el DOS.
En este segundo caso, son mal llamadas interrupciones. Más
bien habría
que decir funciones, ya que nos sirven para hacer una
llamada a una
función BIOS o DOS, como por ejemplo la acción de
cambiar de modo de video,
para la cual se utiliza la interrupción 10h (Driver o
controlador de v¡deo),
con el número adecuado de función. Más adelante
veremos como llamar a una
función.
Al primer tipo de interrupciones se les denomina
interrupciones de
hardware, y son las interrupciones reales. Esto es, que
estando un programa
en ejecución, se interrumpe éste para ejecutar un trozo
de código necesario
para atender a la petición de un dispositivo, como puede
ser el teclado.
Acto seguido, se reanuda la ejecución del programa en
cuestión.
Son las interrupciones que vimos en la lección 3, al
hablar del PIC.
Al segundo tipo de interrupciones se les denomina
interrupciones de
software, y son las 'ficticias', ya que no hay ningún
dispositivo pidiendo
atención del procesador, sino que es el programa del
usuario el que
ejecuta una función BIOS o DOS, mediante una interrupción.
En este caso, no se interrumpe el programa de forma súbita,
sino que es
dicho programa el que lanza una interrupción, la cual
tiene su rutina
de atención a la interrupción (como vimos en la lección
3) 'conectada'
a un grupo de funciones o rutinas.
Veamos las interrupciones con más detalle:
La tabla de vectores:
Toda interrupción aceptada conduce a la ejecución de un
subprograma
específico, como hemos visto. Pero como sabe el
procesador donde empieza
este subprograma, una vez que atiende a la interrupción...
La respuesta
nos la da la tabla de vectores.
Esta tabla de vectores contiene las direcciones de
comienzo o punteros
al subprograma de atención a la interrupción.
La tabla está compuesta de 256 entradas. Es decir, son
posibles 256
interrupciones diferentes en el PC.
Cada una de estas entradas, contiene la dirección de
inicio del código
de atención a una interrupción, en la siguiente forma:
2 primeros bytes (una palabra) que contienen la dirección
base del
segmento, y los 2 últimos bytes que contienen el
desplazamiento.
En total 4 bytes para indicar el comienzo de una
interrupción, en la
forma segmento: desplazamiento.
Ya vimos en la segunda lección como transformar una
dirección segmentada
(segmento: desplazamiento) en una dirección física o
real.
Durante la aceptación de una interrupción, el 8086
carga la dirección
base del segmento en el registro CS y el desplazamiento
en el contador
de programa IP. De esta forma, la siguiente instrucción
a ejecutar, que
viene dada por los registros CS:IP, ser la primera del
subprograma
de atención a la interrupción.
Pines (líneas de bus) para
demandar interrupción desde el exterior
Existen 3 líneas externas jerarquizadas que son, por
orden de prioridades
decrecientes: RESET, NMI e INTR. Sólo INTR es
enmascarable (cuando un
pin de demanda de interrupción está enmascarado e
inhabilitado la
activación del pin, no produce ninguna interrupción).
Es decir, que si se activan los pines RESET o NMI, siempre van a conducir
a la ejecución de una interrupción. Pero si se activa
el pin INTR,
tenemos dos opciones (dependiendo de si está
enmascarado o no), que son
hacer caso omiso de la petición de interrupción, o
atender dicha
interrupción, respectivamente.
Pin INTR:
Una petición de interrupción sobre este pin es
enmascarable mediante
el bit IF (bandera de interrupción) del registro FLAGS o
registro
de estado.
Este bit IF, es la máscara de INTR. Para saber si está
enmascarada o no
la línea INTR, se mira este flag. El cual puede tener
(obviamente) dos
valores: 0 y 1. Enmascarado es 0.
Para manipular este bit, disponemos de dos instrucciones
en ensamblador:
CLI (Clear IF, o borrar flag IF) que lo pone con valor 0.
STI (Set IF, o activar flag IF) que lo pone con valor 1.
La petición de interrupción se realiza activando el pin
INTR con nivel
alto (1) y debe mantenerse as¡ hasta que por el pin INTA
(pin asociado
al pin INTR. Es activo a nivel bajo (0), indicando que se
ha aceptado
la interrupción solicitada por medio del pin INTR) el
8086 indique que
ha sido aceptada.
Entonces... Contamos con el pin INTR para pedir al
procesador atención a
una interrupción, y con el pin asociado INTA que estará
con valor (0)
cuando la interrupción haya sido aceptada.
INTR----> Interrupt Request (petición de interrupción).
INTA----> Interrupt Accepted (interrupción aceptada).
Veamos cómo actúa la CPU desde que se activa el pin
INTR hasta que
se retorna del subprograma de atención a la interrupción:
Debido a que la interrupción interrumpir al
programa en ejecución en
cualquiera de sus instrucciones, es necesario resguardar
el contenido
del registro de estado (FLAGS), para que al volver de la
interrupción,
tengan las banderas el mismo valor.
Y sobre todo, hay que guardar la dirección de la
siguiente instrucción
a ejecutar en el programa actual.
Pero dónde se guardan todos estos datos... En una zona
de memoria
denominada PILA, la pila del procesador. (Explicación en
el último
apartado de esta lección).
Al acto de introducir un dato en la pila se le denomina
apilar, y a
sacarlo de la misma se le denomina desapilar.
Pues bien, el procesador hará lo siguiente:
Apila el contenido del registro de estado (flags)
Apila la dirección de retorno
(contenido de los registros CS e IP).
Inhibe las interrupciones (IF=0 y TF=0, m s adelante
se comenta
la utilidad del flag TF o TRACE)
Esto se hace para que no se produzca otra interrupción
durante la
secuencia de aceptación de la interrupción. Esto es muy
importante.
Activa el pin INTA (lo pone a nivel bajo). El dispositivo
que ha
solicitado la interrupción, al notar el cambio en el pin
INTA,
queda enterado de la aceptación de la interrupción.
Lee el número del vector de interrupción del bus de
datos.
Previamente, el dispositivo lo ha depositado en respuesta
a la
activación del pin INTA.
Obtiene la dirección del subprograma de atención a la
interrupción.
Dicha dirección se encuentra (como hemos visto antes)
almacenada
en la tabla de vectores.
El 8086 ejecuta la subrutina que finaliza con la
instrucción IRET,
o Retorno de Interrupción, cuya ejecución restituye en
CS e IP la
dirección de retorno salvada en la pila, y en el
registro de estado
el valor de los flags.
Al restaurar los flags, se anula la inhibición anterior
de IF y TF,
con lo cual, otra vez se aceptan interrupciones. Pudiendo
as¡
tener interrupciones en cascada.
Repasar el ejemplo de la pulsación de tecla de la lección 3, a ver
si ahora se ve con más claridad.
Pin NMI:
Este pin está reservado a acontecimientos graves, como
puede ser un
corte de corriente, un error de memoria, del bus, etc.
La activación de NMI no conlleva ninguna lectura en el
bus de datos del
vector de interrupción, sino que la CPU directamente
busca el
vector de interrupción número 2.
Pin RESET:
Inicializa el sistema.
En la petición de RESET no se almacena nada en la pila
ni se accede a
la tabla de vectores para conseguir la dirección de
comienzo.
Al activar el pin RESET, el registro de estado queda
borrado (0).
CS =
0FFFFh.
IP =
00000h.
De esta manera, la siguiente instrucción a ejecutar por
el procesador
es la contenida a partir de FFFF:0, código de
reinicialización y carga
del sistema operativo. Son los últimos bytes de la ROM.
El resto de registro de segmentos quedan con valor 0.
DS = 0000
ES = 0000
SS = 0000
Interrupciones internas o desvíos.
El microprocesador 8086 tiene 2 interrupciones internas:
División imposible' y 'funcionamiento paso a paso
(TRACE)'.
División imposible:
Se produce cuando se divide por 0, o cuando el cociente
resultante
de la división no cabe en el registro preparado para
contenerlo.
En ambos casos, se ejecuta la interrupción 0.
Funcionamiento paso a paso:
Si el programador coloca a (1) el bit TF (TRACE) del
registro de estado,
al final de cada instrucción, la CPU bifurcar a la
posición de memoria
indicada por el vector de interrupción n£mero 1.
Esto es lo que utilizan los debuggers o depuradores de c¢digo
para
hacer un seguimiento del programa, instrucción por
instrucción.
Más adelante, cuando hablemos acerca de la programación
de utilidades
residentes, entraremos en la programación práctica de
las interrupciones.
Valga lo dicho hasta ahora como base teórica.
La pila del procesador:
La pila es una característica interna del 8086. Es una
estructura de
datos situada en la RAM. Proporciona a los programas un
lugar donde
almacenar datos de forma segura, pudiendo compartirlos
con otros
procedimientos o programas de forma cómoda y práctica.
La función más importante de la pila es la de mantener
las direcciones
de retorno en las llamadas a procedimientos e
interrupciones, as¡ como
guardar los par metros pasados a estos
procedimientos.
La pila también se utiliza para almacenamiento temporal
de datos dentro
de un programa, y para muchas cosas más que se aprenden
con la práctica.
La pila tiene su nombre por analogía con los montones de
platos apilados
(pilas de platos). Cuando un dato nuevo es introducido en
la pila, se dice
que es apilado (push) debido a que se sitúa por encima
de los demás, es
decir se sitúa en la CIMA de la pila.
Una pila opera en el orden
último
en entrar, primero en salir:
LIFO (LAST IN FIRST OUT) o lo que es lo mismo, el último
en entrar es
el primero en salir.
Esto significa que cuando la pila se utiliza para seguir
la pista de los
retornos de las subrutinas, la primera llamada a
subrutina que se hizo,
es la última que se devuelve. De esta manera, la pila
mantiene ordenado el
funcionamiento del programa, las subrutinas y rutinas de
tratamiento de
interrupción, sin importar la complejidad de la operación.
La pila crece en orden inverso. Es decir, a medida que se añaden nuevos
datos, la cima de la pila se acerca más a posiciones más
bajas de memoria.
Existen 3 registros destinados a gestionar la pila.
Registro de segmento de pila (SS):
que indica la dirección base del
segmento de pila
Puntero de pila (SP): que
apunta a la cima de la pila.
Puntero base de pila (BP):
que se usa para moverse a través de la pila
sin cambiar la cima. Se suele utilizar para acceder a los
distintos
par metros al llamar a una función.
Los elementos que se almacenan en la pila son del tipo
palabra (2 bytes).
Esto quiere decir, entre otras cosas, que el puntero de
pila (SP), as¡ como
el puntero base de pila (BP), incrementan/decrementan en
2 su valor para
apuntar a un nuevo elemento dentro de la pila, fruto de
apilar o desapilar
un elemento.
También conlleva el que si queremos almacenar un byte en
la pila, primero
lo debemos convertir en palabra (2 bytes), y luego
almacenar esa palabra.
Esto es muy sencillo, s¢lo hay que meter ese byte o
registro de 8 bits
en un registro de 16 bits y almacenar este registro.
Las instrucciones para manejar la pila son:
PUSH---------> Guarda un dato en la pila.
Decrementando SP en 2 unidades, para
que
apunte al nuevo elemento a introducir.
Ejemplo:
PUSH AX--> Apila el contenido de AX en la cima de la pila.
POP----------> Obtiene un
dato de la pila. Incrementando SP en 2 unidades, para que apunte al nuevo elemento a
introducir. Ejemplo: POP AX --> Desapila el contenido de la
cima de la pila
en el registro AX. Es decir, AX
contenía el valor que había en la cima de la
pila, y el puntero de pila se actualiza incrementándolo en 2.
PUSHF--------->Guarda el
contenido del registro de estado (FLAGS) en la pila. Decrementando SP en 2
unidades, para que apunte al nuevo elemento a introducir.
No es necesario indicar sobre qué
actúa esta instrucción, lo
lleva implícito en su nombre
PUSHF (PUSH FLAGS).
POPF----------> Introduce en
el registro FLAGS el contenido de la cima de la
pila. Incrementando SP en
2 unidades, para que apunte al nuevo elemento a introducir. Al igual que con la
instrucción anterior, no es necesario indicar sobre qué actúa esta instrucción
POPF (POP FLAGS).
Conviene recordar el hecho de que la pila crece en orden
inverso al
normal, es decir de direcciones de memoria altas a
direcciones bajas.
Por lo tanto es necesario tener en cuenta el uso que se
va a hacer de
la pila en el programa, debido a que si reservamos
espacio en nuestro
programa para una pila peque¤a, en caso de sobrepasarla
haciendo muchos
push seguidos, machacaría nuestro programa.
Hay que tener en cuenta que no sólo es nuestro programa el que utiliza
la pila mediante la instrucción PUSH y mediante llamadas
a procedimientos,
interrupciones, etc. Sino que mientras nuestro programa
corre se están
sucediendo numerosas interrupciones que conllevan muchos
PUSH.
Por ejemplo, 18'2 veces por segundo se produce la
interrupción de reloj,
con lo cual, todas estas veces se está apilando y
posteriormente
quitando información de la pila.
Por regla general, basta con tener una pila de unos 2 KS,
es decir, espacio
para almacenar 1024 elementos. Es muy difícil que se
sobrepase este tamaño.
Bueno... Aquí¡ seguro que hay dudas.
Venga, decidme qué queréis que explique más
detenidamente, que dentro
de un par de lecciones empezamos a programar, y hay que
tenerlo todo claro.
Un saludo.
AESOFT....