Módulo de Memoria Síncrono de dos Vías

Anuncio
MEMORIA DE PRÁCTICA ENSAMBLADOR
CALCULADORA
POR
JOSÉ LUÍS TORRES GARCÍA
 mailto: Autor 
Asignatura:
Curso:
Profesor:
BUSES y PERIFÉRICOS
2006 / 2007
Ricardo J. Pérez García
Enunciado:
eLink a la asignatura:
Web ByP - ITIS - E.U.I.
Web ByP - ITIS - E.U.I.
(Práctica)
Introducción:
Ver Objetivos
Página 02
Pantalla:
- Colores
- Códigos Color
Ver especificaciones
Explicación
ver Tabla
Página 03
Página 03
Página 05
Ensamblador:
- Programación
- Versión ASM
- versión Montador
- versión Depurado
Lenguaje Máquina vs ASM
programación ensamblador
Turbo Assembler
Turbo Link
Turbo debugger
Página 06
Página 07
Página 07
Página 08
Página 08
Algorítmica:
Idea personal:
Agradecimientos:
Consideraciones previas:
Datos:
Código:
Organigrama general:
Etiquetas:
Partes del Programa:
Software incluido:
resumen de ideas
ver explicación
ver probadores del programa
ver mas ideas
variables empleadas
explicación del desarrollo
ver gráfica
nombre y significado
ver descomposición modular
ver ficheros adjuntos
Página 09
Página 09
Página 10
Página 11
Página 12
Página 14
Página 14
Página 15
Página 17
Página 20
(Inicio)
Introducción
El objetivo de esta práctica es presentar en pantalla una pequeña calculadora,
sirviéndose para ello de las Interrupciones, tanto BIOS (10h) como DOS (21h).
Dicha calculadora ha de operar en diferentes bases: Binario, Octal, Decimal y
Hexadecimal, para ser más exactos. Cada base estará representada por un color
diferente, pudiendo cambiar/elegir base mediante sucesivas pulsaciones de la tecla de
tabulación, siguiendo siempre un orden creciente por cada pulsación, hasta la base
mayor (Hexadecimal, en este caso) y de esta, saltar de nuevo a la base menor (binario).
Como ya se ha comentado, en cada base todos los caracteres mostrados tienen el color
característico a su base.
Las operaciones que soporta nuestra pequeña, pero bonita calculadora son las cuatro
básicas: Suma, Resta, Multiplicación y División.
Cada vez que se cambie de base, se resetea también la operación, poniendo la entrada
y el resultado a cero, esto nos ahorra la típica tecla C de las calculadoras para borrar,
pero también, desafortunadamente, nos impide usar la calculadora como un conversor
de bases. Sin embargo una sencilla modificación del código permitiría esta ampliación
para una posterior versión de nuestra calculadora que no deba ceñirse a los
requerimientos de la práctica actual.
Mientras se teclea la entrada el resultado ha de permanecer inalterado (cero al
comienzo o tras un cambio de base, y el resultado de la operación anterior cuando se
esta entrando el operando de la siguiente).
El formato interno es el binario, esto quiere decir que los datos se almacenan y
operan en base dos, independientemente de la base actual, han de convertirse los
números cada vez que sea necesario para mostrarlos por pantalla, ya sea cuando se leen
del teclado, o cuando se operan entre sí y producen un resultado. Claro está, el proceso
de conversión para mostrar los números varía mucho de una base a otra; por ejemplo,
véase como un mismo número se muestra en las distintas bases:
Base 2: 10110011
Base 8: 263
Base 10: 179
Base 16: B3
No sólo hemos de preocuparnos de transformar el valor, sino además de adecuar el
tamaño diferente, ya que el número de caracteres a mostrar varía, además de tener en
cuenta que en cada base hay un conjunto de caracteres imprimibles diferentes, con la
complicación añadida en el caso del Hexadecimal, que no solo admite caracteres
numéricos, sino además algunas letras, debiendo además controlar si estas son
mayúsculas o minúsculas. Durante la pulsación de teclas, los caracteres leídos, que no
pertenezcan al conjunto de la base actual, se ignorarán.
Pantalla
La pantalla se divide en tres secciones claramente diferenciables; estas son, la base
actual expresada en letras y en el color adecuado, la entrada que leeremos del teclado y
el resultado que se mostrará cuando se efectúe alguna operación.
En la imagen precedente, puede apreciarse el resultado diseñado, donde se ha
añadido un marco explicativo, que da idea de las teclas que pueden usarse en la
calculadora, además de las numéricas, claro está.
La Pantalla de nuestro programa, no ocupará el total del monitor, sino que será un
cuarto de este; si tomamos el tamaño del monitor en modo texto como 25 líneas de 80
columnas de caracteres, nos da un rectángulo aproximadamente de 6x20, permitamos
mejor que sean 7x20 en aras de una mejor legibilidad, redondeando el seis y pico,
bastante superiormente, hacia arriba. Dicha pantalla podrá ser ubicada en la posición
del monitor que se desee, tomando lo más cómodo y simple, la posición de inicio será
la (0,0) o lo que es lo mismo, esquina superior izquierda de la pantalla. Es una fácil
adaptación cambiar el programa para que la esquina inicial, se sitúe en cualquier otro
punto de la pantalla, ya que tan solo es necesario, sumar los nuevos valores de fila y
columna al inicial. La referencia, no obstante, siempre será la esquina superior
izquierda de la calculadora.
Fijándonos en que tenemos 20 columnas por fila para escribir en nuestra pequeña
pantalla, y hemos tomado 2 para poner un borde ‘|’ a cada lado, nos quedan 18
posiciones, decidimos que el resultado mostrará ocho caracteres, como hacen la gran
mayoría de calculadoras en todo el mundo; vemos que en las restantes 10 posiciones la
palabra ‘Resultado:’ encajaría demasiado ajustada, dando una impresión visual
desagradable, por lo que es mejor cambiar esta por ‘ Resulta: ’ que significa lo mismo,
y se alinea con ‘ Entrada: ’ dejando además un espacio intermedio, que como veremos
mas adelante, nos será de utilidad.
Decidido pues que la pantalla se verá en modo texto, 25 filas x 80 columnas, nos resta
decidir los colores de la misma. Estos, inicialmente, serán: fondo negro, caracteres en
blanco, por lo que el blanco se asignará como el color característico de la base por
defecto, en nuestro caso, binario. Además de estos dos colores básicos, necesitamos tres
mas, para las otras bases, otro para el marco externo, y aún nos reservamos un color
mas, con lo que serían al menos siete colores diferentes, por lo cual optamos por el
modo de 16 colores.
Mas sobre Colores
Por los requerimientos queda perfectamente identificado el modo de video como:
- Modo Texto, 80x25, 16 colores, CGA-EGA-MCGA-VGA
Así pues, describiremos algo mas acerca de cómo usar los atributos de caracteres
imprimibles en este modo, y en general.
Lo primero que llama la atención, puede verse ya en esta memoria, al observar la
imagen de nuestra calculadora pegada en este documento <ver> ¿Por qué no se ve
exactamente como en el modo texto? ¿Por qué veo la imagen diferente en distintos
equipos, si el documento es el mismo?
Windows nos proporciona la siguiente herramienta de imagen para ajustar el
contraste, intensidad y brillo de la imagen, no obstante, es impredecible el efecto exacto
en cada caso, ¿Por qué?.
La respuesta, como suele ser habitual, puede encontrarse en la historia. Si seguimos
la evolución de los estándares de video, vemos que ya desde el primero, el mda, que
sólo permite modo texto, monocromo a 720x350, aparece el concepto de atributo,
existe una “memoria de pantalla” o RAM de Video conteniendo la información que se
ha de volcar sobre el Generador de señales de video para que el monitor físico,
reproduzca dicha información. Centrándonos en el modo carácter, o texto, dicha
memoria, contiene una información doble sobre el dato (carácter en este caso) a escribir
en la posición de cursor actual. Por un lado está que carácter vamos a escribir, si
usamos un byte, tendremos una tabla de 2 8 = 256 caracteres (ASCII) posibles. La
segunda información es el atributo del citado carácter, usando también un byte,
tenemos 8 bits posibles de información, y aquí es donde está el quid de la cuestión, ya
que existe una multitud de opciones diferentes para interpretarlos. Opciones comunes
del atributo, son el color de fondo y de escritura, si tomamos la mitad del espacio para
cada uno nos da 24 =16 colores posibles, pero recordemos que el primer estándar de
video mda, es monocromo, por lo que no necesita todos los bits del atributo para el
color, así que aporta un significado especial al 7 bit (más significativo) del atributo,
siendo este que si el 7º bit es un uno, el carácter se muestra intermitentemente, y si es
un cero, el color del carácter es de intensidad. El estándar HCG, incorpora el modo
gráfico y una 2ª pagina de visualización, pero mantiene compatibilidad con el anterior.
Con CGA llega el color (16 colores en modo texto y 4 en modo gráfico, 2 sí se usa la
máxima resolución). EGA, VGA y SuperVGA añaden mas opciones, colores,
resoluciones y nº de páginas, pero siguen manteniendo la compatibilidad con los
estándares anteriores.
Así pues, en el punto actual, estamos en un modo de 16 colores, tomados del byte de
atributo del carácter a mostrar, estos colores se interpretan de la siguiente manera:
0 = Negro (0000)
1 = Azul
(0001)
2 = Verde (0010)
3 = Aqua
(0011)
4 = Rojo
(0100)
5 = Violeta (0101)
6 = Amarillo (0110)
7 = Blanco (0111)
8 = Gris
(1000)
9 = Azul claro
(1001)
A = Verde claro (1010)
B = Aqua claro
(1011)
C = Rojo claro
(1100)
D = Violeta claro (1101)
E = Amarillo claro (1110)
F = Blanco brillante (1111)
Los primeros cuatro bits, dan el color del fondo y el segundo cuarteto el del carácter.
por ejemplo: 07h significa fondo Negro, texto Blanco.
Si nos fijamos en la equivalencia entre las dos columnas, y recordamos que debemos
de dar compatibilidad a todos los estándares de video, hemos de tener en cuenta, que si
el fondo, es un color perteneciente a la columna de la derecha, hemos de interpretar ese
uno en el 7º bit como intensidad, si queremos que el texto pueda escribirse como un
color de su misma fila; es decir, si interpretamos ese bit como intermitencia, no
podríamos distinguir entre el blanco y el blanco brillante, o el verde y el verde claro,
negro y gris, etc... Esto ocurre porque en realidad, sólo hay ocho colores, pero jugando
con su intensidad, podemos obtener el doble, uno claro y otro oscuro.
A partir de los estándares de video superVGA, la diversidad y la falta de uniformidad
en las distintas técnicas aplicadas para aumentar el número de colores, nos da las
respuestas a las preguntas planteadas al inicio del capítulo, aunque finalmente el
estándar VESA consiga alejar al usuario de los detalles implementados en cada caso,
por ello mismo, es imposible saber exactamente que criterios se están aplicando para
determinar la intensidad del color, aunque los valores básicos estén garantizados, a
medida que aumenta la tecnología, se ha de dotar al usuario de controles extra para
poder establecer los matices del color que se ganan con el adelanto tecnológico.
Lenguaje Máquina y Ensamblador
Todo procesador, grande o pequeño, desde el de una calculadora hasta el de un
supercomputador, ya sea de propósito general o específico, posee un lenguaje único que
es capaz de reconocer y ejecutar. Por razones que resultan obvias, este lenguaje ha sido
denominado Lenguaje Máquina y más que ser propio de un computador pertenece a su
microprocesador. El lenguaje máquina está compuesto por una serie de instrucciones,
que son las únicas que pueden ser reconocidas y ejecutadas por el microprocesador.
Este lenguaje es un conjunto de números que representan las operaciones que realiza el
microprocesador a través de su circuitería interna. Estas instrucciones, por decirlo así,
están grabadas en el hardware y no pueden ser cambiadas. El nivel más bajo al que
podemos aspirar a llegar en el control de un microprocesador es precisamente el del
lenguaje de máquina.
Ahora bien, siendo el lenguaje máquina un conjunto de números, ¿cómo es capaz el
microprocesador de saber cuándo un número representa una instrucción y cuándo un
dato? El secreto de esto reside en la dirección de inicio de un programa y en el estado
del microprocesador. La dirección de inicio nos indica en qué localidad de memoria
comienza un programa, y en consecuencia que datos deberemos considerar como
instrucciones. El estado del microprocesador nos permite saber cuándo éste espera una
instrucción y cuándo un dato.
Mientras que con el lenguaje de máquina, nosotros obtenemos un control total del
microprocesador, la programación en este lenguaje resulta muy difícil y resulta muy
fácil cometer errores. No tanto por el hecho de que las instrucciones son sólo números,
sino porque se debe calcular y trabajar con las direcciones de memoria de los datos, los
saltos y las direcciones de llamadas a subrutinas, además de que para poder hacer
ejecutable un programa, se deben enlazar las rutinas de run-time y servicios del sistema
operativo. Este proceso es al que se le denomina ensamblado de código. Para facilitar la
elaboración de programas a este nivel, se desarrollaron los Ensambladores y el
Lenguaje Ensamblador.
Existe una correspondencia 1 a 1 entre las instrucciones del lenguaje de máquina y
las del lenguaje ensamblador. Cada uno de los valores numéricos del lenguaje de
máquina tiene una representación simbólica de 3 a 5 letras como instrucción del
lenguaje ensamblador. Adicionalmente, este lenguaje proporciona un conjunto de
pseudo-operaciones (también conocidas como directivas del ensamblador) que sirven
para definir datos, rutinas y todo tipo de información para que el programa ejecutable
sea creado de determinada forma y en determinado lugar. Dotándonos de herramientas
para ayudarnos en la edición de programas, así como la posibilidad de crear las propias
(Macros).
Así pues el proceso de programar en Ensamblador, consta de tres partes, edición del
código del programa, ensamblado del código fuente a código objeto y linkaje de todos
los módulos en el programa final ejecutable.
Programación
Primeramente, se usará un editor de texto plano, o lo que es lo mismo, que no
contenga características de formateo de texto, del tipo del EDIT de MS-DOS para
generar el código fuente, una vez desarrollado el programa se guarda con una extensión
‘.ASM’ para indicar al compilador que se trata de un programa en Ensamblador y se
procede a la compilación del mismo.
Para ello nos serviremos del Turbo-Assembler (TASM.EXE) o Turbo Ensamblador
Usaremos al compilador/ensamblador con la opción “TASM /zi prac3.asm” que nos
permitirá obtener información de depuración (debug). El resultado de este proceso es
convertir el fichero fuente en código objeto “prac3.obj”.
Para el segundo paso necesitaremos montar o linkar el código objeto con todas las
funciones y llamadas al sistema invocadas por el código fuente. Esta función la
realizará el Turbo Linkador (TLINK.EXE) que deberá ser empleado con la opción
“TLINK /v prac3.obj” para permitir obtener la información de depurado que
necesitaremos al final. Este paso da como resultado el fichero ejecutable “prac3.exe” y
como dato intermedio el fichero “prac3.map” con la información para el depurado.
Si bien este resultado podría ser el resultado final, en la práctica esto jamás es así,
por tanto debemos realizar una fase de depurado y control de errores, antes de dar
nuestro programa por terminado, nos valdremos para ello del Turbo debugger
(TD.EXE) que nos ofrece la posibilidad de ejecutar nuestro programa en un entorno
simulado, a prueba de fallos, para preservar al sistema de posibles errores de graves
consecuencias, ya que programando a bajo nivel puede saltarse el nivel de protección
del sistema; repitiendo una y otra vez el proceso desde el inicio, hasta que el resultado
de nuestro programa sea el deseado. Este método “TD prac3.exe” nos proporciona
además, múltiples facilidades para hacer trazas del programa o detectar errores, evalúa
resultados intermedios y modifica datos erróneos.
Capturas para mostrar herramientas empleadas en el desarrollo de la práctica:
En las dos capturas precedentes, pueden verse las versiones y opciones del montador y depurador
usados, así como la del compilador/ensamblador en la página anterior.
Los archivos, copiados todos ellos, del directorio de la asignatura ByP son los siguientes:
DPMIMEM.DLL, DPMILOAD.EXE, DPI16BI.OVL, TASM.EXE, TLINK.EXE,TD.EXE, TDHELP.TDH
Algorítmica
Llegado a este punto, pasamos a pensar exactamente que es lo que vamos a hacer
para desarrollar el programa que nos ocupa, luego tendremos tiempo de “traducir”
nuestras ideas a código ensamblador.
Resumiremos lo que queremos hacer:
- Inicialización de valores.
- Presentar la imagen de la calculadora en una zona de pantalla concreta.
- Leer Teclas, ...
- Mientras sean números admitidos en la base actual mostrarlos y actualizar variables.
- Si es una operación realizarla y mostrarla, actualizando variables.
- Si es tabulador, cambiar de base y color, resetear variables y mostrar nueva imagen.
- Si es la tecla de escape, salir del programa y volver a DOS.
- Cualquier otra tecla se ignora y se sigue leyendo teclas.
Como vemos, un esquema bastante sencillo. Antes de mostrar un organigrama, un
poco menos resumido, hablaremos de nuestra idea personal de funcionamiento para la
calculadora a diseñar.
Teniendo en cuenta las teclas que nos permitimos distinguir, puede llamar la
atención la ausencia de una tecla de ‘=’ o resultado; esto es algo que particularmente
me agrada, pues en muchas calculadoras, puede ocurrir un conflicto en el caso, por
ejemplo, de ir realizando operaciones sucesivas y totalizar, como es un proceso
diferente, aunque en apariencia igual, en muchos casos puede llevar al usuario a error;
resolviendo esto, las calculadoras científicas, mediante el uso de paréntesis, con lo que
se complica mas la lógica, o también, es frecuente el uso de ‘RM’ una tecla para
“recordar” un resultado concreto, evidentemente, todas los métodos funcionan sin error,
pero lían al usuario de tal forma, que es él el que se equivoca, pues no recuerda cual era
el sub-resultado recordado, o si le dio a sumar o totalizar, o que es lo que estaba
introduciendo hace un momento. En nuestra mecánica, aunque inicialmente diferente,
todos estos problemas se atajan sin que lleguen a ocurrir, con lo que se simplifica aún
mas nuestro problema, al ahorrarnos otra (u otras) tecla(s).
La calculadora, siempre efectúa una operación en el instante en que se pulse la tecla
correspondiente, tomando como operandos el resultado mostrado actualmente y la
entrada mostrada, dejando el valor de entrada a cero, listo para leer números de nuevo y
un nuevo resultado mostrado en pantalla.
Nuevo_Resultado = Antiguo_Resultado <Operación> Entrada
Puede resultar extraño el hecho de ahorrarnos un operando, pero aprovechando que
los valores iniciales son cero, y que este es el elemento neutro de la suma; puede verse
como que necesitamos “cargar” el primer operando en el resultado; por ejemplo,
supóngase que la primera operación que queremos hacer es 3x5, inicialmente tanto
Entrada como Resultado están a cero, por lo que debemos trasladar el 3 al resultado;
esto se hace pulsando “3+” que se interpretaría como [3=0+3] en ese instante la
pantalla mostraría:
Base: DECIMAL
Entrada: _
Resulta:
3
En realidad, el cero no se ve, sino al cursor esperando por un número, sobre el campo
en blanco, lo que equivale a decir, que Entrada vale cero. Ahora ya estamos listos para
teclear “5*” y realizar la operación [15=3*5] que era el objetivo inicial.
Base: DECIMAL
Entrada: 5
Resulta:
3
pulsación
*
Base: DECIMAL
Entrada: _
Resulta:
15
Mi método obliga a una operación inicial extra, si ninguno de los operandos fuese
cero, pero a partir de ahí, es más eficiente que las demás calculadoras del mercado,
puesto que ya está “cargado” el primer operando en el resultado anterior. Por ejemplo,
para restarle dos a quince, se pulsaría “2-” y el nuevo resultado mostraría “13”.
Cuando menos es una curiosa forma de representar lo mas fielmente posible la
especificación gráfica de la calculadora, aprovechando que hay dos líneas.
Personalmente prefiero mi método al tradicional, pero entendiendo que puede ser
extraño para otras personas, he creado por ello un equipo de pruebas para testear el
manejo de la calculadora en distintas personas y de paso ver si ocurrían errores
inesperados, o situaciones confusas para los distintos usuarios, teniendo un resultado
del 100% de éxito, a pesar de la aparente dificultad inicial, al ver el programa por
primera vez. Quisiera mostrar mi agradecimiento a estas personas aquí, en lugar de
tener un apartado específico para ello, como suele ser habitual en la bibliografía.
- Francisca García Pérez,
64 años
- María José Oliva Moreno, 39 años
- Mari Paz Malvido Torres, 13 años
Más consideraciones previas.
Otras carencias importantes de esta pequeña calculadora, son por ejemplo:
- Ausencia de decimales: así por ejemplo la división, será una división entera, sin
redondeo y desechando el resto. La coma, o punto decimal no es un símbolo de entrada
admitido.
- Ausencia de números negativos: podría tomarse este, como el complemento a dos del
número en binario, pero claro no hay forma de distinguir entre sí 11111111(/2) es –1 ó
255 en decimal y claro en el resto de las bases, no es nada intuitivo. El signo menos, no
se acepta como carácter a imprimir, aunque sí como operación (resta).
- Limitación del resultado: este es el gran talón de Aquiles de nuestra calculadora, y una
posible causa de ampliación en una versión posterior. Como ya se ha mencionado en
los objetivos, al inicio de la memoria; el tamaño máximo de caracteres a mostrar es
ocho, esto quiere decir que el número más alto representable en binario es el 255
decimal, ó FF hexadecimal, ó 377 octal. También tenemos, por los requerimientos
impuestos, que operar y almacenar los datos en binario, así que se decidió por
simplicidad usar como unidad de almacenamiento un byte (8 bits, que se corresponden
con los 8 dígitos binarios), desgraciadamente, esto limita también drásticamente el
máximo resultado posible en las otras bases, aún en el caso de usar una palabra (doble
capacidad) aunque se desperdicie espacio en el caso del binario, el resultado máximo en
Hexadecimal, seguiría siendo pequeño: FFFF, en lugar de FF, habría que usar una
doble-palabra (dw) para almacenar entrada y resultado, desperdiciando así tres cuartas
partes del espacio en binario, y perdiendo también el efecto de “darle la vuelta al
marcador” en las sumas, o complemento a dos en las restas, ya que al no ocupar el total
de la capacidad, los resultados tienen un hueco donde “desbordarse”. Además
necesitaríamos el doble de esta capacidad para realizar una multiplicación, como es
imposible manejar una operación de cuádruple palabra directamente en ensamblador,
deberíamos implementar nuestras propias funciones para realizar la multiplicación, o
división. Como el objetivo de esta pequeña calculadora, es más didáctico que
matemático, y está orientado al manejo de interrupciones, se acepta esta gran limitación
del resultado, que aporta además un nuevo elemento a tener en cuenta: El
Desbordamiento. Ya no sólo se produce en el resultado de operaciones elevadas, sino
también puede ocurrir en la entrada de números (excepto en base dos) al sobrepasar el
máximo valor almacenable; se puede continuar leyendo teclas, mientras sean números
pertenecientes a la base actual, pero sólo se almacenará un byte de información.
- No hay “scrolling” horizontal, ni tecla de borrado: Como ya se ha comentado
anteriormente, se admitirán ocho dígitos como máximo, así que tras introducir esos
ocho dígitos, se ignorará cualquier pulsación de tecla numérica, aunque sea un dígito
perteneciente a la base. No está permitido rectificar una pulsación, debiendo, en caso de
error, resetear la operación mediante la tecla tabulador. Así mismo, si se quiere poner el
resultado a cero, puede usarse el tabulador, o si no se quiere cambiar de base, puede
usarse el sencillo truco de teclear [0*] esto multiplicará por cero y “reseteará” el
resultado.
Datos
Llega el momento de hablar de las variables que usaremos en la realización de
nuestro proyecto, nuestro DATA SEGMENT, se llamará DATOS y contiene en este
orden, las siguientes variables:
entrada
resultado
colo
fila
colu
resto
error
op1
op2
BINA
OCTA
DECI
HEXA
RAYA
ENTR
RESU
DB
DB
DB
DB
DB
DB
DB
DW
DW
DB
DB
DB
DB
DB
DB
DB
0
0
07
0
0
0
0
0
0
"| BASE: Binario
|",'$'
"| BASE: Octal
|",'$'
"| BASE: Decimal
|",'$'
"| BASE: Hexadecimal|",'$'
"+------------------+",13,10," ",'$'
"| Entrada:
|",13,10," ",'$'
"| Resulta:
|",13,10," ",'$'
-entrada: es un byte que contiene en todo momento el 2º operando de la posible
operación a realizar, que es el que se introduce por teclado, inicialmente está a cero y se
actualiza en cada pulsación de un digito perteneciente a la base actual. Siguiendo la
siguiente formula: [entrada=entrada*base+AL] siendo “AL” el valor binario del último
dígito leído; como la interrupción de captura del teclado, nos devuelve en el registro
AL el código ASCII del carácter tecleado, previamente hemos de hacer AL-48, ya que
48 es el código ASCII del cero. Al cambiar de base (pulsación del tabulador) vuelve a
tomar el valor inicial de cero.
-resultado: otro byte, que contiene tanto el resultado de la última operación realizada,
como el primer operando de la próxima operación a realizar; se actualiza al pulsar
cualquier tecla correspondiente a una operación ( ‘+’, ‘-’, ‘*’, ‘/’ ) siguiendo la fórmula:
[resultado=resultado<operación>entrada]. Al igual que la entrada se resetea al cambiar
de base.
-colo: este byte, inicializado a 07 es de gran importancia en nuestro programa, contiene
tanto el color como la base actual, ya que hay una equivalencia directa entre estos. El
07 nos da el color y la base por defecto, esto es: 0=fondo negro, 7=texto blanco y el
blanco equivale al binario. La explicación de los códigos del color ya se ha realizado
ver_Tabla_Codigos_Colores previamente. Y la equivalencia color-base, es la siguiente:
7 Blanco
8 Gris
9 Azul
A Verde
Binario
base 2
Octal
base 8
Decimal
base 10
Hexadecimal base 16
“colo” sólo se actualiza al cambiar de base, y normalmente su valor suele estar
reflejado en el registro BL, donde se coloca el atributo al invocar a la interrupción
BIOS de escritura de caracteres. Con la excepción de la impresión de símbolos
indicadores de error, en que por un momento toma el color rojo, para destacar estos
eventos ‘%’: división por cero y ‘↔’: desbordamiento.
-fila y colu: estos dos bytes están íntimamente relacionados con el registro DX,
almacenan temporalmente, la fila y columna actuales del cursor en pantalla. El
movimiento del cursor está gobernado por los registros DH (fila) y DL (columna)
variando estos registros, desplazaremos al cursor, pero como no siempre podemos
mantener libres de uso dichos registros, nos valdremos de estas dos variables de
respaldo. Es importante saber, en el caso de una posterior modificación del código para
variar la posición inicial de la pantalla, por ejemplo; que hay que modificar las
operaciones sobre DX, no sobre fila y colu; se necesitarían entonces dos nuevas
variables para almacenar la posición inicial, además de las dos mencionadas.
-resto: como su propio nombre indica, contiene el resto de una división, sólo se usa en
el proceso de conversión binario-decimal para mostrar el resultado de una operación en
esta base, debido al elevado uso de operaciones y registros que se necesitan en este
proceso.
-error: que no lleve a engaño el nombre de esta variable, se usa para distinguir desde
donde se ha llegado al desbordamiento, y aunque tome distintos valores (1 suma, 2
resta, 3 multiplicación, ó cero) sólo nos interesa saber si es cero o no, en el primer caso
indica que se ha producido desbordamiento al teclear teclas e intentar actualizar la
variable “entrada”, y en los demás casos el desbordamiento se produce al tratar de
realizar una operación para actualizar la variable “resultado” el número (1,2 ó 3) nos
indica en que operación se produjo el resultado, pero esto sólo es a titulo anecdótico,
pues en esta implementación no tiene ningún efecto. El caso de la división no puede
producir desbordamiento, y el error de división por cero se trata de forma diferente.
-op1 y op2: son dos palabras que salvaguardan los valores de los dos operandos, en el
caso de algunos errores de desbordamiento, ya que se necesita operar al doble de la
capacidad normal, aunque el resultado final sea truncado a un solo byte.
-el resto de las variables, todas con nombre en mayúsculas, no son mas que strings para
formar la imagen de la calculadora, mediante Interrupciones DOS. De las cuatro
primeras, sólo se escribirá una, dependiendo de la base actual y tras ella se recoloca el
cursor, es por ello que no incluyen el retorno de carro y el line-feed como las otras,
estás aparecen igual cada vez que se muestra la calculadora, ya sea al inicio, o tras un
cambio de base.
Nuestro STACK SEGMENT, se llama PILA y contiene 127 bytes, inicializados con
el carácter ‘p’ de pila. Al “Stack pointer” lo hemos llamado apuntador.
Usaremos la pila para salvaguardar Registros y Flags en las llamadas a rutinas y en
algún caso de tratamiento de desbordamiento, de ahí el uso de op1 y op2 ya que son
words y no podemos insertarlas directamente en la pila que es de bytes.
Además de todas estas posiciones de memoria empleadas, se ha tratado de usar en la
mayor medida posible, los cuatro registros de propósito general que el ensamblador del
8086 pone a nuestra disposición, aumentando con ello la eficiencia y rapidez de
ejecución de nuestro código. Por ello se ha tratado de reservar el uso de ellos para
funciones especificas, sin embargo muchas instrucciones precisan de registros
específicos, y además en algunas situaciones se precisan mas variables que las que nos
permiten los registros, pero se ha tratado que estas sean en casos infrecuentes, como es,
por ejemplo el tratamiento de un error por desbordamiento.
Registro AX: desgraciadamente, este registro esta implicado en muchísimas
instrucciones, por lo que no se puede reservar, en la medida de lo posible se ha
mantenido en AL el carácter leído por teclado, o su conversión a número en binario.
Por su parte, AH suele contener la función a ejecutar en las interrupciones, que al irse
alternando hacen que AH contenga un valor impredecible, por lo que se suele usar
como variable* temporal, o también para swap o intercambio.
Registro BX: salvo contadas excepciones no se modificará este registro, BH contiene el
número de página a imprimir con las interrupciones tanto BIOS como DOS (cero en
nuestro caso) y BL tiene el atributo del carácter a imprimir.
Registro CX: se usa como contador de caracteres a imprimir en la interrupción BIOS
para imprimir en pantalla, por lo tanto nos deja dos variables* disponibles para su uso
indiscriminado: CH y CL.
Registro DX: es el que almacena la posición actual del cursor, DH la fila y DL la
columna; cuando nos vemos obligados a darle otro uso, deberemos usar las variables
“fila” y “colu” respectivamente.
*Aclaración: La Palabra “variable” en estos casos, se refiere a que se usan como tal, no que sean
posiciones de memoria ocupadas.
Desarrollo del código
Ya se ha explicado, muy brevemente la idea general del programa, después de haber
ahondado en las variables que usaremos, podemos pasar a detallar el funcionamiento
del código diseñado. Empezaremos por un organigrama general e iremos
profundizando en cada una de sus partes.
Si se desea, podemos seguir el desarrollo sobre el código fuente, este se puede
desplegar desde el objeto incrustado a continuación, haciendo doble click sobre el
icono de “calcolo.asm”, pero la lectura del código fuente, es mejor acometerla mas
adelante cuando se conozca un poco mas el cuerpo del programa.
Partiremos de la idea inicial, si recordamos el concepto, de ahí obtenemos el
organigrama inicial:
Ruti
salta
INICIO
Inicialización
de
valores
Mostrar
imagen
calculadora
dígito
base
?
Leer
Tecla
Si
Se
actualiza
entrada
No
No
FIN
Salir del
programa
Si
pulsado
escape
?
Ruti
salta
No
es
cambio
de base
?
Si
Cambiar
Color
No
Es
operación
válida
?
Si
Nuevo
resultado
Hay que destacar, que el orden de la toma de decisiones no es el mostrado, este se ha
elegido por adaptarse mejor visualmente, además de coincidir, con las ideas generales
expresadas en el resumen de las ideas al principio del capítulo Algorítmica. El orden
real, equivalente a este, es: ¿Pulsado Escape? (así en caso afirmativo termina el
programa) ¿Cambio de base? ¿Operación válida? (+,-,*,/) ¿dígito pertenece a base?
La salida de la última pregunta, (la flecha curva, hacia Leer Tecla) es efectivamente,
la opción de retorno que atañe a todas las preguntas, si ninguna se satisface, entonces se
ignora el carácter leído y se repite el bucle.
Etiquetas
Visto ya el cuerpo del programa, y antes de adentrarnos en sus partes, comentaremos
las etiquetas del código fuente, ya que son una buena referencia espacial y lógica sobre
el programa final, además de que no pertenecen exclusivamente a una parte, sino que su
sentido y significado se expande al resto del listado, facilitando la comprensión del
mismo.
Se explicará el significado de cada etiqueta, en el orden “espacial” de aparición en el
listado del código fuente, empezando por el programa principal, siguiendo a
continuación por el procedimiento.
Iniciar: Esta etiqueta nos marca el punto de entrada del segmento de código.
Video: Sólo nos sirve como directiva condicional de salto, si el modo de video activo,
coincide con el que nos interesa, saltamos y no lo activamos. Esto se hace, porque con
algunas tarjetas gráficas puede dar problemas al activar el mismo modo que el actual.
Tecla: Esta es una etiqueta importante, marca el bucle principal del programa, es donde
leemos una tecla del teclado, para ver si es válida y procesarla en ese caso.
Las siguientes etiquetas se usan todas ellas para evitar saltos largos como salida
a una instrucción condicional, debiendo emplear para dicho salto largo, la instrucción
“JMP” (Jump o Salto) que si los permite, y además otra etiqueta que tiene el mismo
significado que la anterior.
sal1:
cb1:
sumar1:
restar1:
multi1:
divi1:
JMP salida
JMP CambioBase
JMP sumar2
JMP restar2
JMP multi2
JMP divi2
Si el carácter pulsado es <escape> salimos al DOS
Si se ha pulsado el Tabulador cambiamos de base
Si la tecla pulsada es ‘+’ pasamos a sumar
Si la tecla pulsada es ‘-’ se resta
Si la tecla pulsada es ‘*’ multiplicamos
Si la tecla pulsada es ‘/’ dividimos los operandos
nobase2: Llegado a este punto se trata de comprobar si el carácter leído es un dígito
perteneciente a la base, por lo que se compara la variable “colo” con el color
correspondiente a binario (8), si es mayor que eso, sabemos que no es base 2 y saltamos
a comparar con la siguiente base.
nobase8, nobase10: Exactamente lo mismo para los dígitos en Octal y Decimal.
Minuscula, Mayuscula: Si la base es mayor, ya es Hexadecimal seguro, y hay que
controlar el caso especial de los caracteres A-F, que pueden estar en mayúscula o
minúscula, además del hecho de que ya no son códigos ASCII correlativos como en los
dígitos del cero al nueve.
chas: Esta es otra etiqueta importante, indica que la tecla introducida por teclado es
correcta (digitobase) y pasamos a mostrarlo en pantalla y actualizar entrada.
nume y numer: Otras dos salidas de condicional, para volver a controlar, en el caso de
base Hexadecimal si la tecla es un número (0-9) ó una letra (A-F, a-f).
desborda1, desborda y desborda2: Estas tres etiquetas representan tres diferentes
posibilidades de desbordamiento, las dos primeras al actualizar entrada, o al operar y
obtener el resultado en la tercera.
otraTecla: Es lo mismo que Tecla, se usa para evitar el salto largo desde una directiva
condicional.
Salida, CambioBase, sumar2, restar2, multi2, divi2: Ver sal1, cb1, sumar2, restar2,
multi2. Estas etiquetas se corresponden con las flechas de selección afirmativa, en cada
pregunta del organigrama.
VaBin: Dentro del proceso cíclico de cambio de base, se usa para saltar de la última
base a la primera (binario)
mulmul: Se usa en el proceso de multiplicación, para continuar cuando la operación se
realiza normal, o correctamente, permitiendo el control de desbordamiento.
divcero: Se salta a esta etiqueta, desde el proceso de división para tratar el caso de error
en división por cero.
mostrar: Después de haber realizado una operación y con el resultado en el registro AH
se salta a esta etiqueta para actualizar la variable resultado y mostrarlo en pantalla.
ve1, ve2: Estas dos etiquetas se refieren a lo mismo, la corrección de las letras, esto
quiere decir, que un carácter se convierte en numero restándole a su código ASCII 48,
pues 48 es el código ASCII del carácter ‘0’ y son todos sucesivos, excepto en las letras,
donde hay un salto de 7. El carácter ‘9’ es el 57, el siguiente en secuencia es el ‘A’=65,
pero debería ser el 58, para ser sucesivo. Por eso las letras se muestran siempre en
mayúsculas, independientemente de que se tecleen en mayúsculas o minúsculas. De ahí
que si la entrada es una letra, le sumamos 7 a AL (contiene el código ASCII del
carácter a imprimir), y si es un número no.
bo y bd: Su uso es el evitar un salto largo, al saltar a las etiquetas bobo y bdbd en los
casos de base octal y decimal respectivamente.
bb: Al ser el primer caso, no hay salto largo. Se trata de que la base es binario, dentro
del proceso de mostrar el resultado.
bbbb: Independientemente de cual sea la base actual, al final se salta aquí, para
continuar, sólo se realiza una opción de entre bb, bo y bd (binario, octal y decimal) el
caso del hexadecimal no necesita etiqueta, al ser la primera base tratada.
string: Esta es la única etiqueta del procedimiento, ya que sólo hay una cosa variable en
él, la base actual, dada por la variable global colo, que además indica el color en el que
escribir. Tenemos cuatro posibles cadenas de las que sólo se imprimirá una, y luego
saltamos a la etiqueta para continuar imprimiendo líneas.
Tras conocer la idea, el algoritmo general, los datos y las etiquetas del programa,
será muy simple comprender el detalle de cada parte del programa.
Así como con el código fuente en ensamblador, <ir a Objeto> también podemos, si se
desea, desplegar el organigrama general desde el objeto incrustado a continuación,
haciendo doble click sobre el icono “Organigrama.bmp” para tenerlo así disponible a
un toque de ratón, facilitando aún mas la comprensión.
Inicialización de valores: Las primeras instrucciones nos apuntan a nuestros segmentos
de datos y de pila, utilizando al registro AX, ya que ambos apuntadores DS, SS no
pueden ser inicializados directamente.
A continuación tratamos de activar el modo de video que nos interesa, por medio de
la interrupción BIOS (10h, 00h), modo 3 (Texto, 80x25, 16 colores). Previamente,
comprobamos cual es el modo de video activo (INT 10h, 0Fh) pues se detectan
problemas en algunas ocasiones al activar el mismo modo que ya está activo, por lo
cual si el modo activo ya es el 3, no activamos el modo de video.
Elegimos entre parpadeo o intensidad (INT 10h, 10h, 03) decantándonos por
intensidad.
Cargamos en el registro BL el atributo de impresión en pantalla.
Mostrar imagen calculadora: Esto se realiza a través del procedimiento borra_pant,
hacemos la llamada desde el programa principal, teniendo como variables globales
colo, entrada, resultado y error. Lo primero que hacemos es guardar los registros y
flags en la pila para preservar su estado y pasamos a inicializar las tres últimas
variables, ya que cada vez que se borra la pantalla, ya sea por mostrarla la primera vez,
o por cambiar de base, deben ponerse a cero estos tres valores.
Dibujamos un marco en el color inverso al de fondo del atributo (fondo negro, marco
blanco brillante), con los teclas que se pueden usar escritas en él. La Interrupción usada
es la INT(10h, 0600h). Obsérvese el método de escribir, siempre será el mismo.
Posicionamos cursor INT(10h, 2) escribimos carácter INT(10h 9), con la salvedad de
las líneas de texto que usan la interrupción DOS.
Tras el marco, dibujamos la calculadora, que siempre muestra la misma imagen,
excepto por el color y los cuatro strings que denotan la base actual; usamos para ello la
misma interrupción y el mismo método que con el marco, excepto que esta vez
usaremos la interrupción DOS (21h, 09h) al imprimir las strings BINA, OCTA, DECI o
HEXA y a partir de esta línea todas las demás strings, que ya aparecen con el retorno de
carro y line-feed.
Después de imprimir la última “RAYA” se coloca el cursor en la posición de
resultado y se muestra un cero, pasando a posicionarlo en la posición de la entrada,
preparándolo así para la lectura del teclado.
Finalmente se recuperan los registros y se retorna al programa principal, donde se
carga la posición del cursor en el registro DX.
Leer Tecla: A través de la interrupción DOS (21h, 8) se obtiene en el registro AL el
código ASCII del carácter leído del teclado, no tenemos mas que compararlo con las
distintas opciones que le hemos permitido (esc, tab, +,-,*,/) para, en caso afirmativo, ir
saltando a los distintos módulos del programa, y en caso negativo, seguir con la
siguiente pregunta hasta la de ver si es un dígito perteneciente a la base. El proceso aquí
es el siguiente: si el código es menor que el del cero (48), lo ignoramos directamente y
repetimos el bucle (ya que no podría pertenecer a ninguna base). Si ya hay ocho teclas
pulsadas (se sabe por la columna actual DL que ha avanzado 8 puestos del 12 al 20)
también ignoramos el teclado, ya que no caben mas dígitos en la visualización.
El proceso ahora es el siguiente: comparamos con las bases de menor a mayor,
preguntamos por el carácter, que coincida con el digito mas alto permitido, si es mayor
lo ignoramos (saltamos a Tecla), sino saltamos a mostrarlo (chas). Excepto en el caso
Hexadecimal que hay que tener en cuenta el salto de 7 hasta las letras mayúsculas o
minúsculas.
Se actualiza entrada: Como el propio nombre del módulo indica se actualiza la variable
entrada siguiendo la siguiente fórmula: entrada=entrada*base+AL donde AL es el
ultimo dígito tecleado, ya correctamente transformado a binario. Debido a que el
resultado de la multiplicación cae sobre el registro AX, doblando la capacidad, si AH es
mayor que cero, tenemos el primer caso de desbordamiento desborda1 al sumar el
ultimo dígito puede desbordar si aparece acarreo en la suma tenemos el segundo caso
de desbordamiento desborda. Para mostrar ese dígito hay que reconvertir a ASCII y
recontrolar que no es una letra. Se imprime un carácter, se avanza el cursor una
columna, y se repite el bucle, saltando a la etiqueta Tecla.
Desbordamiento: Como el organigrama es ideal, no muestra esta parte de recuperación
ante un error. Sólo hay que controlar desde donde se llegó de las tres formas posibles y
antes de mostrar el carácter, poner un símbolo característico de color rojo, indicativo
del error, teniendo especial cuidado, si llegamos por desborda2 el tercer caso de
desbordamiento, producido en la operación, no en la entrada de caracteres, que se
distingue fácilmente por la variable error mayor que cero. Si vinimos de ahí, saltamos a
mostrar el resultado, sino a seguir leyendo teclas, aparte de mostrar el símbolo
pertinente, el único tratamiento es realizar la operación ignorando el desborde. en
binario, octal y hexadecimal, es muy gráfico de ver, pero en decimal, hay que saber que
desborda en números múltiplos de 255, por ejemplo si introducimos 1589 el resultado
es 053, ya que si 256 es 0, => (255*6=1530, 1530+53=1589) que no es nada intuitivo.
Salir del programa: No hacemos mas que posicionar el cursor debajo de la posición de
la imagen de nuestra calculadora e invocar la interrupción DOS (21h, 4Ch) para
retornar al sistema.
Cambiar Color: Sencillamente se incrementa BL, que es el registro que contiene el
atributo, si se sobrepasa el valor de la última base, se vuelve a poner 7, que correspone
con el binario, se actualiza colo que es la variable verdadera, se invoca al
procedimiento para que resetee el resto de las variables y muestre la calculadora con el
nuevo color y se repite el bucle de leer teclas.
Nuevo resultado:
Dependiendo de la tecla pulsada se entra por la opción
correspondiente, la acción es siempre la misma, aunque la operación dependa de la
opción: resultado=resultado<operación>entrada
Si no hay acarreo en AX, entonces mostramos el resultado, sino saltamos primero a
desborda2, para tratar el desbordamiento. La multiplicación tiene un pequeño matiz
explicado en la etiqueta mulmul y el error de la división se trata aparte, como reza la
etiqueta divcero
Mostrar es la parte más compleja pues depende de la base.
El binario es el caso más sencillo, se muestra bit a bit, sumando 48 para convertir a
ASCII y haciendo un SHIFT hacia la derecha para ir escribiendo los ocho bits de
derecha a izquierda.
En octal, se agrupan los bits de tres en tres, para escribir tres caracteres de derecha a
izquierda.
En hexadecimal, se agrupan de cuatro en cuatro para mostrar dos caracteres de derecha
a izquierda.
Pero en decimal es algo más complejo.
Escribiremos tres caracteres de izquierda a derecha, empezando por los más
significativos, así serán Centena, Decena y Unidad. Centena se calcula fácilmente,
tomando la división entera del resultado entre 100, para Decena, hemos de restar
primero al resultado el valor previo de Centena multiplicado por 100, y luego tomar la
división entera entre 10, finalmente Unidad es el resultado restándole los valores
previos de Centena multiplicado por cien y Decena por 10. Así tenemos tres números
que sumándoles 48 los convertimos en caracteres ASCII.
Aquí acaba el segmento de código, el programa fuente en ensamblador es autoexplicativo, y está comentado ampliamente. Los módulos aquí subrayados, están
explicados en el orden de aparición sobre el código fuente, pero con el concepto
expresado, en el organigrama, se recomienda seguir la explicación con los dos objetos
incrustados desplegados, pues esto facilita enormemente la comprensión de las ideas
que hicieron surgir el código.
Se acompaña con esta memoria los siguientes ficheros:
* CALCOLO.ASM El código fuente en ensamblador.
* COLORIN.BAT Un lanzador del ejecutable, con una pantalla de instrucciones usada
en la fase de desarrollo con el equipo de pruebas.
* CALCOLO.EXE
* Mem-ASM.DOC
El programa ejecutable.
Esta memoria.
Descargar