PROGRAMACION EN ASSEMBLER

En este capítulo se está ya en condiciones de saber qué es la programación en ASSEMBLER; esto es, escribir una serie de códigos entendibles por el usuario que posteriormente serán convertidos en código de máquina entendible por el microprocesador, en este caso el Z-80.

La programación en ASSEMBLER requiere cuidados especiales si se desea sacar el máximo rendimiento, por ejemplo, ante dos instrucciones que obtengan el mismo resultado se debe elegir aquella que tenga menos ciclos de máquina o de reloj, o aquella que ocupe menos posiciones de memoria; incluso en algunos casos habrá que elegir entre ocupar menos posiciones o ser más rápidos, en función de las necesidades que se tengan. Esto no quiere decir que sea necesario conocer de memoria los ciclos de cada instrucción; un manual de ASSEMBLER debe contener toda la información necesaria, con un método de acceso fácil, a pesar de que en algún caso resulte redundante. Se pretende que este curso, además de como tal, pueda servir en el futuro como un manual de consulta rápida, por lo que en algunos casos, es posible que el lector encuentre información reiterada.

Otra buena costumbre cuando se programa en ASSEMBLER es poner comentarios; siempre hay una manera de ponerlos en cada instrucción o intercalados entre ellas. Los comentarios sólo ocupan lugar en el código simbólico o programa fuente; cualquier ensamblador los ignora cuando convierte el programa en código de máquina, lo cual quiere decir que no ocupará más un programa absoluto porque su simbólico tenga comentarios, pero tampoco irá más despacio. Cuando pase el tiempo y queramos modificar alguna parte del programa y se haya olvidado el porqué de cada instrucción, los comentarios serán de gran ayuda.

Siguiendo con la exposición de buenas costumbres nos referiremos, por último, al empleo de subrutinas. Ya veremos cómo se hacen y cómo se accede a ellas, pero hay que irse mentalizando a su uso. Esto es importante en este momento porque se trata de un problema de estructura del programa. Las ventajas son múltiples; una estructura de subrutinas es más fácil de entender, por lo tanto de modificar. Se da con frecuencia el caso de necesitar en un programa operaciones iguales o semejantes a las de otro, por lo tanto, con limitarse a copiar totalmente estas partes o como mucho, adaptarlas algo a las características del nuevo programa, saldríamos adelante.

Resumiendo, hay que acostumbrarse desde un principio a estos métodos y a la realización del organigrama, sobre el cual daremos algunas orientaciones al final de este capítulo. Toda esta información, más los diseños que se hagan de pantalla, de campos o de tablas, se guardan juntas en una carpeta o cosidas con grapa y se conseguirá una buena documentación de cada programa, documentación que nos será muy útil en el futuro.

Realización de un programa

Existen una serie de pasos que se deben seguir para dar por definitiva la realización de un programa. Cuando más detallada sea la organización de estos pasos, más fácil será la realización del siguiente. El concepto programa abarca a todo lo que se puede realizar en una corriente de instrucciones dentro de un ordenador, por lo cual, tan programa es un juego, como llevar la contabilidad de una casa o la solución de problemas científicos; la metodología a seguir es la misma.

PASOS A SEGUIR:

a) Planteamiento del problema: Lo que en la informática profesional se llama análisis funcional. Es la definición de lo que se quiere realizar, la información de que se dispone, la información que se quiere obtener, la información que se quiere volver a utilizar, los formatos de pantalla deseados; todas estas definiciones se deben hacer con el máximo detalle, como si se pretendiese explicar a otra persona cuál es nuestro problema y cómo nos gustaría que nos lo solucionase.

b) Viabilidad de resolver el problema con un ordenador: Lo que en la informática profesional se llama análisis técnico u orgánico. En este paso se valorará la posibilidad de resolver el problema con el ordenador de que se dispone, así como de la periferia a nuestro alcance (impresora, microdrive, etc.). Se definirá toda la información del paso anterior como datos entendibles para el ordenador, indicando en qué código irán, cuánta memoria ocuparán, en qué lugar han de almacenarse y cómo se procesarán; lo mismo para los diseños de pantalla. En este paso es donde se debe decidir qué procesos se hacen por medio de subrutinas, y ,como curiosidad, si el programa se hace en BASIC o en ASSEMBLER.

c) Realización del programa: Es decir, la programación propiamente dicha. Este paso se divide en dos partes: la construcción del organigrama y la codificación. Estos dos pasos son complementarios, cuanto más se detalle el organigrama menos vueltas se da a la codificación, y viceversa.

  1. El organigrama es una construcción gráfica del flujo o caminos posibles que tiene el programa.

  2. La codificación es la escritura del lenguaje simbólico contemplando todas las alternativas posibles definidas en el organigrama.

Por supuesto, hay muchos programadores que nunca hacen un organigrama, pero también hay muchos que se olvidan de codificar una rama. En cualquier caso, para un programa sencillo o bien para un programador experto el organigrama puede no ser muy detallado o bien omitirse.

d) Prueba del programa: En este paso se preparan unos datos de entrada al programa de los cuales ya se conocen los resultados que obtienen, contemplando, si es posible, todos los casos. Por ejemplo, si se hace un programa para resolver reíces cuadradas, se introducirán una serie de valores de los que ya se conoce el resultado.

e) Documentación: Una vez concluidos los pasos anteriores, se reúne todo el material e incluso se comentan los problemas o dificultades encontrados y cómo se han solucionado. Dependerá de lo ordenado que sea cada cual el obtener como resultado una buena documentación. Ver FIGURA 4-1.

[Figura 4-1]
Figura 4-1. Pasos para la realización de un programa.

Todo lo anteriormente descrito se puede hacer o no, pero son métodos ya muy estudiados que producen unos buenos resultados. La metodología anteriormente descrita es una orientación de la más elemental; se pueden usar técnicas más complicadas pero que se salen un poco de lo que es un micro-ordenador. Se dice que en un programa vale todo, siempre y cuando funcione. Si existe un método que lleve a un buen resultado, no sólo que funcione, sino que sea lo más rápido posible, que no se repitan procesos, que ocupe la menor cantidad de memoria y que esté claro, ¿por qué no usarlo?

Formatos de instrucción en código máquina

La memoria del SPECTRUM está dividida en octetos. Un octeto es una agrupación de 8 bits y un bit es la unidad más pequeña de información; sólo puede informar de dos estados: o está activo o no. Cuando de un SPECTRUM se dice que tiene 16K indica que tiene, aproximadamente, 16.000 octetos de memoria disponible, que son exactamente en decimal 16.384; para uno de 48K se multiplica esta cantidad por 3.

Los 48K de memoria disponible en RAM más los 16K que usa el programa monitor en ROM, suman 64K, que son 65.535 octetos. Esto es, desde la posición 0 de memoria a la 65.535, que es en hexadecimal FFFFh y en binario 1111111111111111b y a su vez la máxima cantidad que se puede escribir en dos octetos.

Las instrucciones en el SPECTRUM pueden ocupar 1, 2, 3 o 4 octetos en función del código de operación y básicamente, tiene los formatos que se ven en la FIGURA 5 del CAPITULO 3.

Como se vé, lo que es común a todos los formatos es que los primeros tienen el código de operación y los últimos el operando. Sobre el operando hay que tener en cuenta que cuando son dos octetos el microprocesador espera encontrar el octeto de orden inferior en el primer octeto del operando, y el de orden superior en el segundo, de tal forma que al referirse la valor de operando 7F3Bh el ensamblador almacenará la instrucción en memoria de la siguiente manera:

CODIGO OPERACION
CODIGO OPERACION
0 0 1 1 1 0 1 1
0 1 1 1 1 1 1 1
 
 
3Bh
7Fh

Existe una combinación diferente de bits para cada instrucción en el código de operación, a pesar de que la única diferencia sea el registro usado. Por lo tanto, habrá una parte fija y otra variable en el código de operación en función de dichos registros.

Otra cosa muy curiosa es ver cómo el microprocesador va leyendo una serie de octetos llenos de instrucciones cuando estos pueden tener cuatro longitudes distintas. Es muy sencillo. Se recordará que el microprocesador tiene un registro PC que indica la dirección de la memoria donde está la próxima instrucción a ejecutar. Cuando la lee, en función del código de operación, sabe cuantos octetos ocupa; en ese momento sólo tiene que incrementar el registro PC en esa cantidad, mientras va leyendo los restantes octetos de la instrucción, para que cuando vaya a leer la siguiente instrucción, este registro ya la tenga apuntada. Ver FIGURA 4-2.

[Figura 4-2]
Figura 4-2. Forma de avanzar el registro PC.

Necesidad de conocer el código máquina

Podríamos preguntarnos: voy a escribir mi programa en el lenguaje simbólico ASSEMBLER, tengo un ensamblador magnífico que me lo va a traducir, ¿qué necesidad tengo de conocer que bit tiene que estar activo y cual no?

Desde luego, el que tenga un buen ensamblador y escriba un programa de principio a final, buena gana de estar descifrando códigos y pasándolos de binario a hexadecimal. Pero no siempre se dispone de un buen ensamblador o se quiere estar cargándolo para pocas instrucciones, con lo cual se meterían en la memoria los valores de las instrucciones y se saltaría a esa posición con lo que el microprocesador empezaría a trabajar. Además, existen algunas técnicas en programación que hacen necesario este conocimiento, por ejemplo:

  1. Programas automodificables: La primera vez que pasan por una rutina pasan por una instrucción que ejecuta una operación; posteriormente, esa instrucción se modifica con lo que la próxima vez que pase ejecuta una operación distinta.

  2. Parches: Durante la prueba del programa se encuentra un error; conociendo las posiciones de memoria donde está cargado el programa se puede modificar directamente y seguir la prueba, incluso si la modificación ocupa más espacio, se deja una zona del programa definida a ceros binarios, se hace sobre ella la modificación terminando con un retorno y desde la posición del error, se hace una llamada a esa subrutina.

Por otro lado, no es necesario tener un ensamblador para poder utilizar el Spectrum en código máquina; es posible escribir el programa en Assembler y traducirlo a código máquina, manualmente; si el programa no es muy largo, la labor no resulta excesivamente tediosa. De hecho, es preferible aprender primero a ensamblar «a mano», ya que esto proporciona un conocimiento más profundo del Assembler, y facilita la utilización posterior de un programa ensamblador.

En este mismo curso, describimos en detalle la utilización del que consideramos el mejor ensamblador que se ha escrito para el Spectrum, el "GENS 3", que desde ahora, recomendamos a nuestros lectores; mientras tanto, indicaremos con todo detalle la forma de ensamblar "a mano" cada instrucción y todos los ejemplos que demos, se podrán introducir en el ordenador sin necesidad de ensamblador, por lo que para seguir este curso, sólo es necesario disponer de un Spectrum.

Formatos de instrucción en lenguaje simbólico

Como se recordará, el lenguaje simbólico es aquél en el que escribimos el programa fuente. Los códigos nemotécnicos que se han utilizado para el microprocesador Z-80, son abreviaturas de las palabras inglesas que definen la operación que realizan.

El formato de la instrucción y sus normas de sintaxis pueden variar algo en función del ensamblador elegido; pero siempre hay unas reglas mínimas que suelen cumplir y a esas nos referiremos hasta el capítulo que trate más profundamente el ensamblador.

El formato normal es el siguiente:

ETIQUETA
 
NEMOTECNICO
 
OPERANDOS
 
; COMENTARIOS

ETIQUETA. La etiqueta es opcional, sólo debe ponerse cuando sea necesario referirse a esta instrucción desde otra, bien para saltar a ella o para modificarla. Como etiqueta sirve cualquier sucesión de letras o números siempre que empiece por una letra; los espacios no son significativos, por lo cual se usa el símbolo "_" para separar palabras. Sólo los seis primeros caracteres son tratados como etiqueta.

Ejemplos:

ETIQUETA
ETIQUE
etiqueta
etique
MUEVE_DATOS
MUEVE_
2
(ilegal)
DOS
DOS
UN_2
UN_2

NEMOTECNICO. Es el código de la instrucción y siempre estará presente pues es el que propiamente la define. Consta de 1 a 4 letras mayúsculas que recuerdan en parte la operación que realizan.

Ejemplo:

Instrucción de carga
en inglés LOAD
nemotécnico LD

OPERANDOS. Este es el campo más variable de la instrucción. Muchas instrucciones no tienen necesidad de que se les definan operandos, ya que estos están implícitos en su operación. Otras tienen necesidad de tener definidos dos operandos, en este caso irán separados por coma ",". Operando podrá ser un número, una etiqueta, un registro o un par de registros. Cuando el valor del ooperando se refiera al contenido de la posición de memoria indicada, el operando se pondrá entre paréntesis.

Ejemplos:

HL
 
Valor del par de registros HL.
(HL)
 
Contenido de la posición de memoria direccionada por HL.
36FAh
 
Valor hexadecimal 36FAh.
(36FA)
 
Contenido de la posición de memoria 36FAH.

COMENTARIOS. En un número limitado de caracteres, es una explicación del porqué y para qué de esta instrucción. Va separado por ";" de los operandos.

Ejemplo:

PERIODO
 
LD
 
A,30
 
; DIAS DEL MES

Contador de posición

El ensamblador, en tiempo de ensamblaje (mientras está ensamblando), mantiene un contador de posición (location counter). Este contador tiene el valor de la dirección de la instrucción que se está ensamblando. Es posible acceder a este valor usando el símbolo "$" (dólar) que lo representa. Este símbolo se usa como una etiqueta en el campo de operando de la instrucción, de tal forma que si se quiere saltar a diez posiciones de memoria más adelante, se saltaría a "$+10". Cuando se use esta facilidad hay que tener en cuenta el número de octetos de cada instrucción.

Generación de palabras de datos

Por la misma razón que en el formato de instrucciones, pueden existir diferencias entre ensambladores cuando se definen datos; por lo tanto, seguiremos en la línea de lo que suele ser habitual. La definición de datos se hace por medio de unos códigos seudonemotécnicos, también llamados directivos, que actúan de una manera similar a las instrucciones. Estos directivos sólo tienen valor en tiempo de ensamblaje (en realidad son comandos del ensamblador y no tienen código de operación), generan una o varias palabras de datos y quedan definidas dentro del programa absoluto. El formato es el siguiente:

ETIQUETA
 
SEUDO-NEMOTECNICO
 
EXPRESION

ETIQUETA: Sigue las mismas normas que para instrucciones, y su uso está justificado por la necesidad de acceder a los datos. Sólo es obligatorio con el directivo EQU.

SEUDO-NEMOTECNICO: Son una serie de caracteres en mayúsculas, basados en el idioma inglés, que recuerdan el tipo de dato que definen. Como más usuales citaremos:

EQU expresión

Tiene que estar precedido por una etiqueta. Pone la etiqueta igual al valor de la expresión. La expresión no puede contener una etiqueta que no haya sido previamente valorada.

DEFB expresión, expresión...

Cada expresión tiene que tener un valor que entre en un octeto. Coloca el valor de cada expresión en octetos consecutivos a partir del contador de posición.

DEFW expresión, expresión...

Cada expresión tiene que tener un valor que entre en dos octetos. Coloca el valor de cada expresión en pares de octetos consecutivos a partir del contador de posición.

DEFS expresión

Reserva un bloque de memoria igual al valor de la expresión.

DEFM 's'

Define el contenido del octeto con el valor en código ASCII de las letras colocadas entre comillas.

Diagrama de flujo

Conocidos también como organigramas u ordinogramas, son una construcción gráfica del programa. Un buen organigrama facilita la codificación posterior y proporciona una representación visual de todas las situaciones o ramas del programa.

Si se utilizan los símbolos estándar, cualquier otro usuario podrá entenderlo; por lo tanto, se definirán a continuación los más utilizados, que son suficientes para la realización de los organigramas del SPECTRUM.

Como normas generales se tendrá en cuenta:

a) El tamaño de los símbolos es variable, sólo se deben mantener las proporciones.

b) En el interior de los símbolos se debe escribir claro y conciso.

c) Salvo que se indique lo contrario, la dirección del flujo en el organigrama es: de izquierda a derecha y de arriba a abajo.

Símbolos básicos

[Símbolo PROCESO]

Representa el proceso a realizar. Ejemplo: Operación aritmética, cambio de valores, actualización de una variable, etcétera.

[Símbolo FLECHAS]

Representa la dirección del flujo del programa. Estas flechas o líneas unen los símbolos del organigrama. Las direcciones de arriba a abajo y de izquierda a derecha no es necesario señalarlas, las otras sí.

[Símbolo COMENTARIO]

Se usa para añadir comentarios o anotaciones marginales de tipo aclaratorio.

Símbolos especializados de entrada/salida

[Símbolo DOCUMENTO]

Representa una función de entrada/salida por medio de un documento. Por ejemplo, una impresión.

[Símbolo ENTRADA MANUAL]

Representa una función de entrada/salida en la que la entrada es manual en tiempo de proceso. Ejemplos: teclado, interruptores, pulsado de botones, etc.

[Símbolo PRESENTACION]

Representa una función de entrada/salida en la que la información es presentada para uso humano en tiempo de proceso. Ejemplo: indicadores, pantalla de vídeo.

[Símbolo CINTA MAGNETICA]

Representa una función de entrada/salida sobre una cinta magnética. Por ejemplo, el cassette.

[Símbolo DISCO MAGNETICO]

Representa una función de entrada/salida sobre un disco magnético.

Símbolos especializados

[Símbolo DECISION]

Representa una decisión dando paso a las alternativas que pueden ser seguidas.

[Símbolo PROCESOS PREDEFINIDOS]

Representa el nombre de un proceso que consiste en una o más operaciones. Por ejemplo, las subrutinas.

[Símbolo CONECTOR]

Representa una conexión dentro del organigrama, tanto de salida hacia, como de entrada por. Normalmente se pone una letra o un número para indicar hacia dónde se dirige o el nombre de la entrada. Una flecha marcará el sentido.

Ejemplos:

[Símbolo EJEMPLO CONECTOR 1]

La decisión "Y" se dirigirá a "B", que estará definido en otra parte del organigrama.

[Símbolo EJEMPLO CONECTOR 2]

El flujo de programa llega desde el punto "B", donde se le mandó aquí.

[Símbolo TERMINAL]

Representa un punto terminal en el programa. Por ejemplo: el comienzo, el final, un punto de espera, un alto, una interrupción, etc.

Otros símbolos usados

Básicos

[Símbolo ENTRADA SALIDA]

Especializados de entrada/salida

[Símbolo MEMORIA MAGNETICA] [Símbolo ENLACE DE COMUNICACIONES] [Símbolo ALMACENAMIENTO MASIVO]
[Símbolo TARJETA PERFORADA] [Símbolo CINTA PERFORADA]
[Símbolo TAMBOR MAGNETICO]

Este símbolo podría usarse para diferenciar entre el cassete y el microdrive.

Especializados de proceso

[Símbolo OPERACION MANUAL] [Símbolo OPERACION AUXILIAR] [Símbolo UNIR]
[Símbolo EXTRAER] [Símbolo ORDENAR] [Símbolo COTEJAR]

Una tabla de saltos se puede representar de la siguiente manera:

[Símbolo TABLA SALTOS]

En la FIGURA 4-3, se puede ver un ejemplo de lo que podría ser un organigrama que representara las actividades básicas de una persona. Creemos que el ejemplo es bastante ilustrativo de cómo se hace un organigrama. Esperamos, no obstante, que ninguno de nuestros lectores rija su existencia por un bucle de tan escasas posibilidades.

[Figura 4-3]
Figura 4-3.

Presentación de las instrucciones

A partir del próximo capítulo iremos estudiando por grupos, todas las instrucciones que usa el Z-80. Veremos la forma de utilizarlas en Assembler, y la forma de ensamblarlas en código máquina para aquellos que no dispongan de ensamblador. También veremos una serie de ejemplos que irán creciendo en complejidad, y que el lector podrá teclear en su ordenador para irse habituando al uso de este lenguaje.

Las instrucciones se presentarán de la siguiente manera:

1) Código simbólico: En el operando se usarán las siguientes claves:

r,r'
=
Uno de los registros A, B, C, D, E, H o L.
n
=
Una expresión o número, cuyo valor no supere el tamaño de un octeto. Entre 0 y 255.
nn
=
Una expresión o número, cuyo valor no supere el tamaño de dos octetos. Entre 0 y 65535.
d
=
Una expresión o número con valores comprendidos desde -128 a +127
b
=
Una expresión o número con valores comprendidos entre 0 y 7.
e
=
Una expresión o número con valores comprendidos entre -126 y +129.
cc
=
Estado de los indicadores de condición en las instrucciones que los usan.
qq
=
Cualquiera de los pares de registros BC, DE, HL o AF.
ss
=
Cualquiera de los pares de registros BC, DE, HL o SP.
pp
=
Cualquiera de los pares de registros BC, DE, IX o SP.
rr
=
Cualquiera de los pares de registros BC, DE, IY o SP.
s
=
Cualquier r, n, (HL), (IX+d) o (IY+d).
m
=
Cualquier r, (HL), (IX+d) o (IY+d).

2) Objeto: Donde se describirá la operación que realizará.

3) Código de máquina: Donde se representará el código binario de la instrucción y el hexadecimal, si es posible.

4) Indicadores de condición que afecta: Siempre que la instrucción afecte a los indicadores de condición se indicará cuáles y cómo los afecta.

Estos son:
C
=
acarreo
N
=
suma/resta
P/V
=
paridad/desbordamiento
H
=
semi-acarreo
Z
=
cero
S
=
signo

5) Número de ciclos de máquina: Número de veces que el microprocesador accede a la memoria.

6) Número de ciclos de reloj: Número de ciclos de reloj que necesita la instrucción para ejecutarse.

7) Ejemplos: Con cada instrucción se dará un ejemplo que muestre sobre el papel la forma en que actúa y cómo modifica los registros y las posiciones de memoria a las que afecta.

Por otro lado, tambíen veremos ejemplos que se podrán introducir en el ordenador, y cuya realización explicaremos de forma exhaustiva. De cada ejemplo, se dará el listado Assembler, para que quien lo desee, pueda teclearlo por medio de un ensamblador. Para quienes no dispongan de ensamblador, se acompañará cada ejemplo de un programa en Basic (también explicado), que introduzca el código en memoria y lo ejecute.

Ejecución de código máquina en el Spectrum

Quienes dispongan de ensamblador, deberán mirar las instrucciones del mismo, para ver cómo deben introducir sus programas en memoria. En cualquier caso, en un capítulo posterior, estudiaremos en profundidad el manejo de ensambladores, y concretamente, del GENS 3, que a pesar de todo, tiene el pequeño inconveniente de traer las instrucciones en inglés.

Por ahora, aprenderemos a utilizar el código máquina desde el Basic, construyendo pequeños programas cargadores de C/M.

Para introducir en el Spectrum un programa en C/M, empezaremos por escribirlo en Assembler sobre un papel. Una vez decidido en qué lugar de la memoria lo vamos a cargar, lo ensamblaremos a mano siguiendo las normas que daremos en los siguientes capítulos. El resultado, será una serie de números, comprendidos entre 0 y 255, que constituyen el código máquina propiamente dicho.

Mediante un bucle FOR ... NEXT en Basic, vamos introduciendo estos números en sucesivas posiciones de memoria a partir de la RAMTOP (que previamente habremos bajado). Y finalmente, utilizaremos la función USR para ejecutarlo.

Veamos un ejemplo: Supongamos que el programa que deseamos cargar, está representado por los números: 12, 65, 87, 80, 68, 91, 18, 71, 33, y 27 (en este ejemplo, los números son aleatorios, así que no se moleste nadie en desensamblarlo, por que no tiene sentido). Supongamos también, que lo queremos introducir a partir de la dirección 50000 y que se ejecuta a partir de 50005 (un programa en C/M no tiene por qué ejecutarse siempre desde la primera dirección).

Nuestro programa en Basic, empezaría por:

10 CLEAR 49999

A continuación, utilizaremos un bucle FOR ... NEXT para introducir el código.

20 FOR n=50000 TO 50009
30 READ a: POKE n,a
40 NEXT n
50 DATA 12,65,87,80,68
60 DATA 91,18,71,33,27

Ahora, sólo nos queda ejecutar el programa; para ello utilizaremos la función USR, que como todos saben, nos devuelve en el retorno, el contenido del par de registros BC (como regla nemotécnica, acuérdese de "Basic Comunicator", -comunicador con el Basic-). USR, como toda función, debe ir precedida de un comando, el que utilicemos, dependerá de lo que queramos hacer con el resultado; si no nos importa el valor de BC en el retorno, podemos hacer RANDOMIZE USR ... que sólo ocupa dos bytes. Si queremos imprimir el resultado, podemos hacer PRINT USR ... y si queremos asignar el resultado a una variable, para luego trabajar con él, podemos hacer LET a=USR ... En cualquier caso, detrás de USR deberá ir la dirección a partir de la cual se debe ejecutar nuestro programa. Supongamos que en nuestro ejemplo, no nos importa el resultado, así que haríamos:

70 RANDOMIZE USR 50005

Con lo que el Sistema Operativo pasa el control a nuestro programa en C/M, hasta que el microprocesador se encuentre una instrucción de retorno, ya que el S/O (Sistema Operativo) trata a nuestro programa como si se tratase de una subrutina suya; esto se verá más claramente cuando estudiemos el capítulo dedicado a las subrutinas.

Codificación hexadecimal

Con el procedimiento visto hasta ahora, utilizamos 10 números metidos en DATAs, para representar un programa de 10 bytes de longitud. Estas DATAs, nos ocuparán cerca de 70 bytes de memoria dentro del programa Basic; si tuviéramos que representar en DATAs un programa de 2K (2048 bytes), probablemente, no nos cabrían los DATAs en un 16K. Para evitar esta forma de malgastar la memoria, existe un procedimiento al que quizá esté acostumbrado el lector por los listados de nuestra revista, este procedimiento consiste en codificar el programa en hexadecimal, e introducirlo como una cadena de caracteres, que sólo ocupará en DATAs el doble de la longitud del programa. Veámoslo con un ejemplo:

Primero haríamos:

10 CLEAR 49999

De la misma forma que antes, pero esta vez, definiremos una función que nos ayude a decodificarlo.

20 DEF FN a(a$,n)=16+(CODE a$(
n)-48-7*(a$(n)>"9"))+(CODE a$(n+
1)-48-7*(a$(n+1)>"9"))

Puede parecer complicado, pero esta función nos ayuda a pasar los números de hexa a decimal antes de POKEarlos en las direcciones de memoria.

Usaremos también, una suma de comprobación (checksum) para detectar si nos equivocamos al teclear los DATAs. El programa seguiría:

30 READ a$,s: LET cs=0
40 FOR n=1 TO LEN a$-1 STEP 2
50 LET a=FN a(a$,n)
60 LET cs=cs+a
70 POKE 49999+n,a
80 NEXT n
90 IF cs<>s THEN PRINT "Error": STOP
100 RANDOMIZE USR 50005
110 DATA "0C415750445B1247211B"
120 DATA 552: REM Checksum

La línea 30 lee toda la cadena, la suma de comprobación y pone a cero el contador de checksum.

El bucle entre las líneas 40 y 80, va leyendo los caracteres de la cadena de checksum y finalmente, los introduce en la dirección adecuada.

En la línea 90, se detectan los posibles errores, comparando el contador de checksum con la suma correcta que está en la línea 120. Finalmente, la línea 100 ejecuta el programa de l misma forma que en el caso anterior.

La cadena de la línea 110, está compuesta por la representación hexadecimal de los números que componen el código máquina que queríamos introducir en el ordenador.

Dónde ubicar un programa en C/M

En principio, un programa en código máquina se puede colocar en cualquier lugar de la memoria, de hecho, existen programas comerciales que la ocupan prácticamente por completo. No obstante, para nuestros fines existen zonas más adecuadas que otras.

Se supone que un programador aficionado, utilizará rutinas en C/M combinadas con un programa principal en Basic, por lo que habrá que respetar una zona de memoria para que el Basic pueda trabajar.

Básicamente, existen cuatro zonas donde situar nuestros programas:

  1. Por encima de la RAMTOP.
  2. En el buffer de impresora.
  3. En el archivo de pantalla.
  4. Dentro del programa Basic.

Veámoslas una por una:

1. Por encima de la RAMTOP: Es la zona más adecuada para colocar un programa en C/M, ya que queda protegido de borrados por el sistema Basic. En primer lugar, deberemos bajar la RAMTOP con el uso de CLEAR, como se veía en el ejemplo anterior. Una vez cargado nuestro programa, no podrá ser borrado ni siquiera con NEW; para volver a la situación inicial, deberemos teclear:

RANDOMIZE USR 0

Que sí borrará el programa en C/M y todo lo que haya en la memoria del ordenador. Otra forma de destruir nuestro programa sería volver a subir la RAMTOP.

2. En el buffer de impresora: Existe en la RAM, una zona reservada de 256 bytes, que empieza en la dirección 23296 (5B00h) y acaba en la 23551 (5BFFh); esta zona la utiliza el Spectrum cuando trabaja con una impresora tipo ZX-Printer (Alphacom-32 o Seikosha GP-50S); si no va a utilizar ninguna de estas impresoras, puede almacenar en esta zona una rutina corta (256 bytes máximo) que no le ocupará, por tanto, memoria en la zona de programa. Tenga en cuenta, no obstante, que su rutina será borrada si utiliza los comandos: NEW, LPRINT, LLIST y COPY.

3. En el archivo de pantalla: En casos especiales, se utiliza el archivo de pantalla para almacenar programas en C/M, es una técnica usada en algunos copiadores para no ocupar memoria útil. Si no desea "ensuciar" la pantalla, puede poner los atributos correspondientes al mismo color de tinta y papel, con lo que los bytes no se visualizarán en forma de pixels. Cuando utilice esta técnica, tenga en cuenta que su programa puede ser corrompido por el uso de NEW, CLEAR y cualquier comando que afecte a la pantalla. El archivo de pantalla va desde 16384 (4000h) hasta 22527 (57FFh).

4. Dentro del programa Basic: Esta era la técnica usada en el ZX-81, consiste en hacer que la primera línea del programa sea una línea REM, con tantos espacios, como bytes tenga el programa C/M a almacenar. La dirección de inicio de esta zona es (PROG)+5. Este método tiene la ventaja de poder salvar juntos el Basic y su Código Máquina, si bien, su empleo no es recomendable si se tiene conectado el INTERFACE 1, ya que este dispositivo desplaza el programa Basic, y por tanto, nuestra rutina en C/M, a menos que esta sea reubicable y entremos en ella, calculando cada vez la dirección de entrada a partir del contenido de la variable PROG. En este caso, nuestra rutina sólo se borra editando la línea, o borrando el programa Basic con NEW.

De todos éstos, el sistema usado con más frecuencia es el primero, y es el que usaremos en nuestros ejemplos. Si se tiene conectado un interfce de impresora INDESCOMP, ha de tenerse en cuenta que su software ocupa los 1000 bytes más altos de la memoria.