Traducciones de esta página:

Herramientas de usuario

Herramientas del sitio


es:capítulo_1_-_subrutinas

¡Esta es una revisión vieja del documento!


CAPÍTULO 1

SUBRUTINAS

Las subrutinas son una herramienta muy poderosa de programación, ya que permiten diseñar un programa que realice una cierta tarea y tenerlo en memoria una sola vez, pero utilizándolo cuantas veces se quiera. Por ejemplo, diseñamos una subrutina que imprima en la pantalla un carácter del código ASCII, concretamente aquel cuyo código ASCII corresponda al número que se encuentre en el registro A. Pues bien, para imprimir una letra bastará con cargar su código ASCII en A y llamar a la subrutina. Y más aún, para escribir una frase completa bastará con ir cargando cada letra en el registro A y llamado a la subrutina una vez para cada letra. Si no dispusiéramos del mecanismo de llamadas a subrutina, tendríamos que escribir la rutina de escritura de una letra para cada letra de la frase, lo que resulta muy tedioso y ocupa muchísima memoria.

La característica principal de una subrutina es que (salvo que cometamos un error muy grave al escribirla), siendo llamada desde cualquier punto del programa que se esté ejecutando o programa principal, siempre retornará al punto desde el que se la llamó.

El sistema operativo CP/M está estructurado de forma que podemos hacer muchas cosas llamando a una única subrutina, situada en la dirección de memoria 0005H. Para informar a esta rutina de qué es lo que esperamos que haga, cargamos en el registro C un número que llamaremos “número de función”. Normalmente puede ser necesario darle algunos datos a la subrutina, que generalmente irán en el registro A y en el registro DE. Además, a veces es necesario que la subrutina devuelva datos al programa que la llamó, y esto se suele hacer en el registro A y el HL.

Vamos a ver un ejemplo muy sencillo para aclarar el uso del sistema operativo CP/M. Este programa escribirá una letra A en la pantalla:

MVI E,41H ;41H=Código ASCII de la letra 'A'.
MVI C,02H ;Número de la función “imprimir una letra”.
CALL 0005H ;Llama a la subrutina “Sistema Operativo”.
RET ;Termina el programa.

Las dos primeras instrucciones ya las conocéis del capítulo anterior, y simplemente cargan los registros correspondientes con el número situado detrás de la coma. La tercera instrucción es la instrucción de llamada a subrutina, y lo que hace es seguir la ejecución a partir de la dirección especificada (0005H) hasta que encuentra una instrucción RET, con lo cual vuelve al programa principal, a la siguiente instrucción a la de llamada, que es la cuarta, en este caso una instrucción RET (Retorno de subrutina) que nos devuelve al CP/M (aparecerá de nuevo el inductor del sistema “A>”).

En el ejemplo anterior, la primera instrucción se podría haber escrito también así:

MVI E, 'A'.

y el programa ensamblador se encarga de sustituir la letra 'A' por su código ASCII.

El mecanismo de las subrutinas

Si repasáis el capítulo anterior, recordaréis qué dábamos la descripción de los registros del microprocesador 8080; quiero que os fijéis especialmente en los registros SP y PC.

Vamos a suponer que el ejemplo anterior comienza en la dirección de memoria 0100H, con lo que la instrucción CALL 0005H estaría situada en la dirección 0104H, y formada por tres bytes:

0104H 0CDH
0105H 05H
0106H 00H

El código 0CDH es el código de la instrucción CALL, y los dos siguientes forman el dato, en este caso la dirección 0005H. Observad que en la memoria se guarda primero el byte bajo (el de la derecha) y luego el byte alto (el de la izquierda).

¿Y qué hace el 8080 con esto? Vamos a verlo. En primer lugar, cuando el registro PC llega al valor 0104H, el microprocesador toma el contenido de esa dirección (0CDH) y lo examina. Ve que es el de llamada a subrutina, y piensa: “Tengo que leer la dirección de la subrutina, que está formada por los dos bytes siguientes”; así que incrementa el registro PC (con lo que PC=0105H) y lee el byte contenido en esa dirección (05H), almacenándolo en un registro interno. De nuevo incrementa el registro PC (PC=0106H) y lee el byte correspondiente (00H), que almacena en un registro interno, formando con el anterior la dirección 0005H.

Ahora viene lo interesante: de nuevo se incrementa el registro PC (PC=0107H). Ahora este valor del PC se guarda en un registro interno, y se pasa al PC el valor de SP.

Para este ejemplo vamos a suponer que el registro SP contiene 0100H. Pues bien, a partir de esa dirección, y de forma descendente, se va a guardar la dirección de retorno. Como hemos pasado su contenido al PC, PC vale ahora 0100H.

El siguiente paso es guardar la dirección de retorno. Esto lo hace así:

  • 1.°) Decrementa el valor del PC (PC=00FFH).
  • 2.º) Guarda en esa dirección el byte alto de la dirección de retorno (07H).
  • 3.°) Decrementa de nuevo el valor del PC (PC=00FEH).
  • 4.°) Guarda en esa dirección el byte bajo de la dirección de retorno (07H).
  • 5.°) Pasa al registro SP el contenido del PC (SP=00FEH).
  • 6.º) Pasa al registro PC la dirección de llamada, que se guardó previamente en registros internos (PC=0005H).

A partir de aquí sigue el funcionamiento normal del microprocesador, leyendo instrucciones y ejecutándolas. Cuando se encuentra una instrucción RET se desarrolla el proceso inverso:

  • 1.°) Guarda el registro PC en un registro interno.
  • 2.°) Pasa al registro PC el contenido del registro SP (PC=00FEH).
  • 3.º) Toma el byte contenido en esa dirección (07H) y lo guarda en un registro interno.
  • 4.°) Incrementa el registro PC (PC=00FFH).
  • 5.°) Toma el byte contenido en esa dirección (01H) y lo guarda en un registro interno.
  • 6.°) Incrementa el registro PC (PC=0100H).
  • 7.°) Pasa el registro PC al SP (SP=0100H).
  • 8.°) Pasa al registro PC la dirección leída y guardada en registros internos (PC=0107H).

De este modo, el programa principal sigue por donde lo dejamos al llamar a la subrutina. Lo más mavarilloso del mecanismo de llamadas a subrutina y retornos de subrutina es que el programador no tiene que preocuparse por nada, ya que es el microprocesador el que realiza todas esas operaciones de forma automática.

Usando las herramientas

Son varias las herramientas de que disponemos para la programación en ensamblador, así que empezaremos por las más sencillas de manejar: MAC.COM y HEXCOM.COM.

MAC es un macroensamblador, lo que significa que es capaz de ensamblar instrucciones “normales” y macroinstrucciones“. De momento dejaremos de lado las macroinstrucciones y nos quedaremos con las instrucciones normales.

Para poder usar el ensamblador hemos de crear primero un fichero con el código fuente (las instrucciones de lenguaje ensamblador). En el PCW se puede hacer con Locoscript, y en los CPC será necesario servirse de ED.COM o algún procesador de textos que genera ficheros ASCII, como TASWORD o ED80; Eamsword no sirve.

                                             LISTADO 1

             ORG 0100H
             
             LXI H,TEXTO       ; HL apunta al comienzo del texto
   BUCLE:    MOV A,M           ; Coge en A el caracter al que apunta HL
             ORA A             ; Prueba si A es cero
             RZ                ; Y si es cero, termina el programa
             MOV E,A           ; Pasa el caracter a E
             MVI C,02H         ; Función 2: imprimir letra
             PUSH H            ; Guarda en la pila el registro HL
             CALL 0005H
             POP H             ; Recupera de la pila el registro HL
             INX H             ; Apunta al siguiente caracter
             JMP BUCLE         ; y sigue 
             
   TEXTO:    DB 'Hola, mundo'
             DB 00H
             
             END  
                                             LISTADO 2

             ORG 0100H
             
             LXI D,TEXTO       ; DE apunta al comienzo del texto
             MVI C,09H         ; Función 9: Imprime cadena de texto
             CALL 0005H
             RET

   TEXTO:    DB 'Hola, mundo$'
             
             END  

Los que usen PCW deben crear un fichero y modificar el formato (opción “f7”) para que queden las tabulaciones cada ocho columnas. Volviendo al modo de edición teclead uno de los listados ejemplo y salid de la edición con la opción “Terminar edición”. Una vez en el gestor de discos, con el cursor sobre el nombre del fichero recién creado, pulsar “f7” y elegir la opción “Hacer fichero ASCII”. Elegid d grupo 0 como grupo de destino, y dadle al fichero ASCII el nombre EJEMPLO1.ASM o EJEMPLO2.ASM, según hayáis tecleado el listado 1 o el 2. La extensión ”.ASM“ es obligatoria para que MAC reconozca el fichero.

Una vez creado el fichero fuente, hay que cargar el CP/M y utilizar MAC. Para ello, cuando aparezca el inductor del Sistema Operativo “A>” escriba MAC EJEMPLO1 'opciones'.

Donde dice 'opciones' no hay que escribir esta palabra, sino una lista de opciones precedidas por el signo '$'. Estas opciones indican dónde debe ir cada uno de los ficheros que se generan, y son éstas:

  • A Disco donde está el fichero fuente ASM.
  • H Disco a donde irá el fichero HEX.
  • L Disco donde está el fichero LIB con las definiciones de Macroinstrucciones (de momento la dejaremos aparte).
  • P Disco a donde irá el fichero PRN.
  • S Disco a donde irá el fichero SYM.

El fichero HEX es el que luego convertiremos en ejecutable con HEXCOM. El fichero PRN es el resultado del ensamblaje de forma que se pueda listar, y contiene el propio listado fuente con el código generado y los errores que se hayan podido producir. El fichero SYM contiene una lista de las etiquetas utilizadas en el listado fuente y las direcciones asociadas a ellas.

Etiquetas son nombres de un máximo de seis caracteres y seguidas del símbolo ”:“, que se utilizan para hacer referencias a direcciones de memoria. En el listado 1, BUCLE: y TEXTO: son etiquetas.

En todas las opciones citadas la unidad de disco puede ser una letra entre la A y la O, aunque en la práctica, y en el mejor de los casos, serán la A, la B o la M. Además, en la opción H puede ser una Z, que significa “no generar fichero”. En las opciones L, P y S puede ser también Z, X o P. X significa enviar a la pantalla y P enviar a la impresora. Por ejemplo, en el PCW 8512, con el que se está trabajando este artículo, hemos usado esta línea para ejecutar MAC:

MAC EJEMPLO1 $AB HB PX SM

donde AB quiere decir “tomar el fichero fuente EJEMPLO1.ASM del disco B”, HB “escribir el fichero EJEMPLO1.HEX en el disco B”, PX “escribir el fichero EJEMPLO1.PRN en la pantalla” y SM “escribir el fichero EJEMPLO1.SYM en el disco M”.

  OPCIÓN                SIGNIFICADO

    A           Disco en que está el fichero ASM (A-O)
    H           Disco a donde irá el fichero HEX (A-O, Z)
    L           Disco en el que está el fichero de macro librería LIB llamado por la sentencia MACLIB (A-O)
    P           Disco a donde irá el fichero PRN (A-O, X, P, Z)
    S           Disco a donde irá el fichero SYM (A-O, X, P, Z)

Si en el proceso de ensamblaje se produce algún error, en el fichero PRN aparecerá una letra indacando el tipo de error justo a la izquierda de la línea en la que se cometió el error. En este caso será necesario reeditar el fichero ASM, corregir el error, volver a generar el fichero ASCII y volver a ejecutar MAC. La figura 2 muestra los mensajes de error de MAC.

                                             CÓDIGOS DE ERROR DE MAC 

Si el ensamblador encuentra errores en el fichero fuente, los muestra en la pantalla y los incluye en el fichero
".PRN", con la forma:

Código de error - Dirección - Código máquina - Etiqueta - Mnemónico - Operando - Comentario 

  B Error de balance: MACRO o ensamblado condicional no termina adecuadamente.
  C La coma no se ha utilizado para separar términos de forma adecuada.
  D El dato no puede ser situado en el área de datos (puede que sea demasiado largo).
  E Error de expresión (mal formada o demasiado larga).
  I Carácter no válido. Se ha encontrado un carácter no imprimible.
  L Error de etiqueta (normalmente se produce cuando la etiqueta se ha definido más de una vez en el listado).
  M Error de sobrepasamiento de MACRO: sobrepasamiento de la tabla interna de expansión de MACROS.
  N No implementado. Se ha utilizado una directiva de ensamblador que no funciona en RMAC.
  O Sobrepasamiento (expresión muy compleja o el número de etiquetas ha sobrepasado 9999).
  P Error de fase (etiqueta definida dos veces o con valores diferentes en cada pasada).
  R Error de registro (el valor especificado no es compatible con el código de operación).
  S Error de sintaxis: sentencia mal formada.
  U Etiqueta no definida (la etiqueta no existe, es decir, no ha sido definida).
  V Error de valor (operando impropio, causado a menudo por un error al teclear). 

Si el ensamblaje se terminó sin errores, llega el momento de utilizar HEXCOM. De nuevo basta escribir HEXCOM EJEMPLO1, siempre y cuando el fichero EJEMPLO1.HEX esté en el mismo disco que HEXCOM.COM. Nosotros utilizamos esta línea:

HEXCOM B:EJEMPLO1

ya que teníamos EJEMPLO1.HEX en el disco B. Este proceso genera un fichero EJEMPLO1.COM en el mismo disco en que esté EJEMPLO1.HEX.

                  - Fichero EJEMPLO1.PRN resultante del ensamblado sin errores del listado 1


  0100                    ORG 0100H
  
  0100 211201             LXI H,TEXTO     ; HL APUNTA AL COMIENZO DEL TEXTO
  0103 7E        BUCLE:   MOV A,M         ; COGE EN A EL CARCATER AL QUE APUNTA HL
  0104 B7                 ORA A           ; PRUEBA SI A ES CERO
  0105 C8                 RZ              ; Y SI ES CERO, TERMINA EL PROGRAMA
  0106 5F                 MOV E,A         ; PASA EL CARCATER A E
  0107 0E02               MVI C,02H       ; FUNCION 2: IMPRIMIR LETRA
  0109 E5                 PUSH H          ; GUARDA EN LA PILA EL REGISTRO HL
  010A CD0500             CALL 0005H
  010D E1                 POP H           ; RECUPERA DE LA PILA EL REGISTRO HL
  010E 23                 INX H           ; APUNTA AL SIGUIENTE CARACTER
  010F C30301             JMP BUCLE       ; Y SIGUE
  
  0112 486F6C612CTEXTO:   DB 'Hola, mundo'
  011D 00                 DB 00H
  
  011E                    END

Y ahora llega el gran momento de la prueba: escribid EJEMPLO1 y pulsad [RETURN]. Deberíais observar en la pantalla la aparición de este texto:

Hola, Mundo

y de nuevo el inductor del sistema “A>”. Si no es así es que hubo algún error en la fase de ensamblado y no os distéis cuenta.

Si todo fuese bien, probad a seguir el mismo proceso con el listado 2, cambiando el nombre EJEMPLO1 por EJEMPLO2. Si llegáis hasta el final y lo probáis, veréis que hace exactamente lo mismo que el listado 1. La diferencia está en que el listado 1 utiliza la función del CP/M “escribir carácter” para imprimir cada letra del texto, mientras que el listado 2 utiliza la función del CP/M “escribir cadena de texto”, con lo que resulta bastante más corto. Fijaos también que en el listado 1 hemos utilizado el caracter cero (no “0”, código ASCII 30H, sino el carácter cuyo código ASCII es cero) para marcar el final de la cadena, y que podríamos haber utilizado cualquier otro que quisiéramos, mientras que en el listado 2 usamos como marcador del final de la cadena el carácter “$”, que es el que el CP/M requiere para usar la función “escribir cadena de texto”.

Escribiendo el código fuente

Si echáis un vistazo al listado 1 veréis términos nuevos. ORG no es una instrucción del 8080, sino una directiva de ensamblador, que le indica a éste la dirección de memoria en la que debe empezar el programa. En CP/M esta dirección ha de ser siempre 0100H, LXI H carga el par HL con el valor que va detrás de la coma, en este caso el valor de la etiqueta TEXTO. ORA A, realiza la función lógica OR entre A y sí mismo, y nos sirve para saber si el contenido de A es cero, RZ es “retorno de subrutina si cero”, es igual que RET pero sólo se ejecuta si una instrucción anterior dio resultado cero. PUSH H presenta en la pila el contenido del par HL, y POP H recupera el contenido del par HL de la pila. INX H suma uno al contenido del par HL y, por último, JMP BUCLE hace que la ejecución continúe en la dirección indicada, en este caso la correspondiente a la etiqueta BUCLE.

DB es otra directiva del ensamblador, y significa Definición de Byte, es decir, lo que sigue a esa directiva ha de interpretarse como número, no como instrucciones del microprocesador. Si se trata de texto se puede poner éste entre comillas simples, y el ensamblador lo sustituye por los bytes correspondientes a los códigos ASCII de cada letra (excluidas las comillas simples). Por fin, END es otra directiva de ensamblador, que indica el final del listado.

No os preocupéis demasiado por entender las instrucciones nuevas ni cómo funciona el listado 1. Poco a poco lo iréis aprendiendo. Ahora es más importante que aprendáis a utilizar MAC y HEXCOM, y a teclear un listado. Y, sobre todo, que veáis la utilidad de las llamadas a subrutinas.

es/capítulo_1_-_subrutinas.1468154793.txt.gz · Última modificación: 2016/07/10 15:46 por jevicac