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 (digitobase) 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.