Curso Programación ZX SpectrumCursos

Ensamblador para ZX Spectrum – Pong: $02 Hola Mundo

Hola Mundo en ensamblador para ZX Spectrum, punto de partida.

5
(21)

Ensamblador para ZX Spectrum : Hola Mundo

Antes de empezar de lleno con el desarrollo de PorompomPong, vamos a hacer lo que se hace casi cada vez que se inicia el aprendizaje de un lenguaje de programación: vamos a implementar un «Hola Mundo«.

La implementación de nuestro «Hola Mundo», nos va a servir para adquirir los conocimientos necesarios, para el posterior desarrollo de nuestro PorompomPong.

Con «Hola Mundo» vamos a descubrir:

  • Características del microprocesador Zilog Z80, y de sus registros.
  • La distribución de la memoria del ZX Spectrum.
  • Números en distintas notaciones.
  • Etiquetas, variables y constantes en ensamblador.
  • Directivas ORG y END.
  • Instrucciones de carga.
  • Instrucciones RST.
  • Incrementos y decrementos.
  • Operaciones lógicas.
  • Cambios de flujo de programa.
  • Subrutinas.
  • Puertos de entrada y salida.

¿Qué es el Z80?

El Z80 es un microprocesador que salió al mercado en 1976, de la mano de Zilog. El Z80 es el microprocesador que lleva el ZX Spectrum, en todos sus modelos.

El Z80 es una CPU de tipo LOW-ENDIAN. Una CPU de este tipo, cuando almacena en memoria valores de 16 bits, almacena en la primera posición el byte menos significativo, y en la siguiente el más significativo: al cargar el valor $CCFF en la posición $8000, almacena en la posición $8000 el valor $FF y en la $8001 el valor $CC.

Otra característica del Z80 es que no es un microprocesador ortogonal, lo que hace que no todas las operaciones entre registros estén permitidas.

Registros del Z80

Los registros son memoria de alta velocidad y baja capacidad, y están integrados en el microprocesador.

El Z80 dispone de registros de 8 y 16 bits.

Registros de 8 bits

  • A: acumulador. Es el destino de las operaciones aritméticas, lógicas y de comparación de 8 bits. Es el byte más significativo del registro de 16 bits AF.
  • F: flags. Conjunto de banderas que dan información de las operaciones que se están realizando. Es el byte menos significativo del registro de 16 bits AF.
  • B: registro de propósito general que se suele usar en bucles. La instrucción DJNZ lo usa como contador. Es el byte más significativo del registro de 16 bits BC.
  • C: registro de propósito general. Es el byte menos significativo del registro de 16 bits BC.
  • D: registro de propósito general. Es el byte más significativo del registro de 16 bits DE.
  • E: registro de propósito general. Es el byte menos significativo del registro de 16 bits DE.
  • H: registro de propósito general. Es el byte más significativo del registro de 16 bits HL.
  • L: registro de propósito general. Es el byte menos significativo del registro de 16 bits HL.
  • I: registro de interrupción. Permite manejar 128 interrupciones distintas.
  • R: registro de refresco de memoria. Manejado por el Z80, cambia los bits del 0 al 6. Se puede usar para generar números pseudo aleatorios entre 0 y 127.

Registros alternativos

Los registros alternativos sirven para hacer una copia temporal de los registros de 8 bits:

  • A’: registro alternativo de A.
  • F’: registro alternativo de F.
  • B’: registro alternativo de B.
  • C’: registro alternativo de C.
  • D’: registro alternativo de D.
  • E’: registro alternativo de E.
  • H’: registro alternativo de H.
  • L’: registro alternativo de L.

Registros de 16 bits

  • AF: formado por el registro A como byte más significativo y el F como byte menos significativo.
  • BC: formado por el registro B como byte más significativo y el C como byte menos significativo. Se usa como contador en operaciones como LDIR, etcétera.
  • DE: formado por el registro D como byte más significativo y el E como byte menos significativo. Se usa, generalmente, para leer y escribir en una operación única, así como en operaciones como LDIR, etcétera.
  • HL: formado por el registro H como byte más significativo y el L como byte menos significativo. Se usa, generalmente, para leer y escribir en una operación única, así como en operaciones como LDIR, etcétera. El registro HL es el registro acumulador en operaciones de 16 bits.
  • IX: acceso a memoria de forma indexada, LD (IX + desplazamiento), pudiendo ser el desplazamiento un valor entre -128 y 127.
  • IY: acceso a memoria de forma indexada, LD (IY + desplazamiento), pudiendo ser el desplazamiento un valor entre -128 y 127.
  • SP: puntero de pila. Apunta a la posición actual de la cabeza de la pila.
  • PC: contador de programa. Contiene la dirección de la instrucción actual a ejecutar.

Opcodes de los registros

  • 0: B
  • 1: C
  • 2: D
  • 3: E
  • 4: H
  • 5: L
  • 6: (HL)
  • 7: A

Registro F

Cada bit del registro F, flags, tiene un significado propio que cambia automáticamente según el resultado de las operaciones que se realizan:

  • Bit 0: flag C (acarreo). Se pone a 1 si el resultado de la operación anterior necesita un bit extra para ser representado (me llevo una). Ese bit, flag de acarreo, es el bit extra que se necesita.
  • Bit 1: flag N (resta). Se pone a 1 si la última operación fue una resta.
  • Bit 2: flag P/V (paridad/desbordamiento). En operaciones que modifican el bit de paridad, se pone a 1 cuando el número de bits a 1 del resultado es par. En operaciones que modifican el bit de desbordamiento, se pone a 1 cuando el resultado de la operación necesita más de 8 bits para ser representado.
  • Bit 3: no se usa.
  • Bit 4: flag H (acarreo BCD). Se pone a 1 cuando en operaciones BCD existe un acarreo del bit 3 al 4.
  • Bit 5: no se usa.
  • Bit 6: flag Z (cero). Se pone a 1 si el resultado de la operación anterior es 0. Muy útil en bucles.
  • Bit 7: flag S (signo). Se pone a 1 si el resultado de la operación en complemento a dos es negativo.

No se puede acceder directamente al registro F, y no todas las operaciones le afectan.

Bit76543210
FlagSZF5HF3P/VNC
Ensamblador para ZX Spectrum, registro F (flags)

Memoria del ZX Spectrum

La memoria del ZX Spectrum está dividida en dos, en los modelos de 16KB, o en cuatro, en los modelos de 48KB, bloques de 16KB cada uno (16384 bytes):

  • Primer bloque: de la posición $0000 a la $3FFF (0 a 16383). Se corresponde con la ROM y es de solo lectura.
  • Segundo bloque: de la posición $4000 a la $7FFF (16384 a 32767). En este bloque se encuentra el área de pantalla, buffer de impresora, variables de sistema, etcétera, dejando aproximadamente 9KB para los programas, en los modelos de 16 KB.

Los siguientes bloques de memoria solo se encuentran en los modelos de 48KB:

  • Tercer bloque: de la posición $8000 a la $BFFF (32768 a 49151). Es memoria RAM de propósito general.
  • Cuarto bloque: de la posición $C000 a la $FFFF (49152 a 65535). Es memoria RAM de propósito general.

La distribución del segundo bloque, muy por encima, es la siguiente:

  • $4000 – $57FF (16384 a 22527): área de los píxeles de la pantalla. La pantalla del ZX Spectrum tiene una resolución de 256*192 píxeles. Cada byte de este rango de memoria representa ocho píxeles (256*192/8 = 6144 bytes).
  • $5800 – $5AFF (22528 a 23295): área de los atributos de color de la pantalla. La resolución, en este caso, es de 32*24 caracteres. Cada byte especifica el color de una zona de 8*8 píxeles, definiendo en los bits del 0 al 2 el color de tinta (de 0 a 7), en los bits del 3 al 5 el color del fondo (de 0 a 7), en el bit 6 el brillo (de 0 a 1) y en el bit 7 el parpadeo (de 0 a 1). El área ocupa un total de 768 bytes (32*24).
  • $5B00 a $5BFF (23296 a 23551): buffer de impresora. 256 bytes que se pueden usar si no tenemos impresora, o si no la usa el programa.
  • $5C00 a $5CB5 (23552 a 23733): variables de sistema.
  • $7FFF: puntero de la pila. Suele apuntar a esta dirección, y decrece según se ponen cosas en ella.

Decimal, binario, hexadecimal

La representación decimal es en la que estamos acostumbrados a ver los números, en una secuencia de dígitos en los que cada uno puede tener un valor entre 0 y 9. Esta notación se conoce como decimal o en base 10.

En informática es distinto ya que los ordenadores trabajan con dos valores: 0 y 1. Estos números se conocen como binarios o en base 2.

En ensamblador, la forma más común de representar los números es en base 16 (notación hexadecimal). En hexadecimal, cada dígito puede representar un valor del 0 al 15; a partir del 9 se usan letras:

  • A = 10
  • B = 11
  • C = 12
  • D = 13
  • E = 14
  • F = 15

Un dígito hexadecimal representa 4 bits, por lo que a simple vista sabemos de cuantos bits se compone, siendo lo normal hablar de múltiplos de 8 (8, 16, 32, 64…).

Sin una calculadora a mano, la conversión entre distintas bases puede llegar a ser muy tediosa. Resulta de gran ayuda saber el valor de cada bit; en el caso del Z80, números de 8 y 16 bits.

Vamos a usar la siguiente tabla, en la que se muestran los valores de cada bit, para guiarnos en las conversiones:

1514131211109876543210
327181638481924096204810245122561286432168421
Ensamblador para ZX Spectrum, valores de cada bit

Según se ve en esta tabla, solo tenemos que sumar para convertir números de una base a otra, como se puede observar en el ejemplo siguiente:

5FAOh = 0101 1111 1010 0000 = 32+128+256+512+1024+2048+4096+16384 = 24480

Como se puede ver, la conversión hexadecimal/binario es directa, de cuatro en cuatro bits.

F0h = 1111 0000    3Ah = 0011 1010    CCh = 1100 1100    78h = 0111 1000

0001 0000 = 10h    0100 0101 = 45h    1010 1010 = AAh    0010 0011 = 23h

Etiquetas, variables y constantes

Las etiquetas nos permiten hacer referencia a posiciones de memoria a través de ellas, en lugar de tener que calcular y memorizar las direcciones. El programa ensamblador se encarga de sustituir las etiquetas por las direcciones de memoria correctas; este proceso lo realiza al crear el código objeto.

Si no pudiéramos utilizar etiquetas, al modificar alguna parte del código habría que recalcular las direcciones de memoria para todos los JR, JP o CALL. El ensamblador sustituye las etiquetas por las direcciones de memoria de las instrucciones que siguen a las mismas.

Las etiquetas sirven para definir rutinas y datos. En el caso de los datos pueden ser numéricos o texto, y constantes o variables.

Los datos se definen usando las siguientes directivas:

  • EQU: define constantes (nombre EQU valor).
  • DB/DEFB: define bytes (nombre DB 1, $FF, %10101010).
  • DM/DEFM: define message (nombre DEFM «Hola Mundo»).
  • DW/DEFW: define word (nombre DW $0040).
  • DS/DEFS: define space (nombre DEFS $08).

DB, DM, DW o DS no se ensamblan, por lo que es recomendable ponerlas al final del código, ya que se ejecutarán como si fueran instrucciones del Z80. Si el código empezara con:

DB $CD, $00, $00

Al no ensamblarse la directiva DB, esta línea haría un reset; DB $CD, $00, $00 es CALL $0000.

ORG y END

ORG y END son dos de las directivas más importantes, de las que vamos a usar. Con ORG especificamos la dirección de memoria donde cargar el código, pudiéndose poner varios ORG para cargar partes del código en distintas direcciones de memoria.

END sirve para indicar donde finaliza el programa, y una dirección de auto inicio para PASMO.

Con lo que hemos visto hasta ahora, podemos desarrollar nuestro primer programa; no olvidéis abrir el editor de texto para escribir estas líneas:

org     $8000
ret
end     $8000

Grabamos el archivo como «HolaMundo.asm» y compilamos con PASMO (los nombres de los parámetros van precedidos de dos guiones y se escriben junto al segundo guion, sin espacios):

pasmo ––name HolaMundo ––tapbas holamundo.asm holamundo.tap ––public

Este comando, PASMO…, lo vamos a usar siempre para compilar nuestros programas.

Ahora podemos abrir el archivo «holamundo.tap» con un emulador de ZX Spectrum, y vemos que se ejecuta, aunque lo único que hace es salir, pero al menos no hemos roto nada.

Ensamblador para ZX Spectrum - Primer programa
Ensamblador para ZX Spectrum, primer programa

Instrucciones de carga

Estas instrucciones se usan para cargar un valor en un registro, copiar un valor de un registro en otro, cargar un valor en memoria, cargar un registro en memoria y cargar un valor de memoria en un registro.

La sintaxis de las instrucciones de carga es la siguiente:

LD destino, origen

Destino puede ser un registro o una posición de memoria, mientras que el origen puede ser un registro, una posición de memoria o un valor de 8 o 16 bits.

Estas instrucciones no afectan al registro F, a excepción de LD A, I y LD A, R.

Este es el momento de volver a nuestro primer programa donde, justo debajo de ORG, vamos a agregar las siguientes líneas:

ld      hl, $4000
ld      (hl), $ff

Con estas líneas activamos los 8 bits de la primera posición de memoria de la VideoRAM. Compilamos con PASMO y cargamos en el emulador:

pasmo ––name HolaMundo ––tapbas holamundo.asm holamundo.tap ––public
Ensamblador para ZX Spectrum, activamos 8 píxeles de la pantalla
Ensamblador para ZX Spectrum, activamos 8 píxeles de la pantalla

Instrucciones RST

Estas instrucciones son utilizadas para saltar a una dirección concreta a través de una instrucción de un solo opcode.

Existen varias instrucciones RST, aunque solo vamos a usar RST $10 (RST 16), que imprime el ASCII correspondiente al valor que tiene el registro A.

Recuperamos el archivo HolaMundo.asm, quitamos las líneas que habíamos añadido y escribimos las siguientes:

ld      a, 'H'
rst     $10

Compilamos y cargamos en el emulador. La letra H se debe imprimir en pantalla.

Ensamblador para ZX Spectrum, imprimimos una H
Ensamblador para ZX Spectrum, imprimimos una H

Incrementos y decrementos

Sirven para incrementar (INC), o decrementar (DEC), en una unidad el contenido de determinados registros o posiciones de memoria (apuntadas por los registros HL, IX o IY).

Las operaciones permitidas son:

INC rDEC r
INC rrDEC rr
INC (HL)DEC (HL)
INC (IX + n)DEC (IX + n)
INC (IY + n)DEC (IY + n)
Ensamblador para ZX Spectrum, incrementos y decrementos

Estas operaciones, cuando se realizan sobre registros de 16 bits, no afectan al registro F, mientras que, si se realizan sobre registros de 8 bits, afectan de distintas maneras:

InstrucciónSZHPNC
INC r***V0
INC (HL)***V0
INC (ri + n)***V0
INC rr
DEC r***V1
DEC (HL)***V1
DEC (ri + n)***V1
DEC rr
Ensamblador para ZX Spectrum
– = no afecta, * = afecta, 0 = se pone a 0, 1 = se pone a 1, V = overflow

Recuperamos el archivo HolaMundo.asm, y lo dejamos tal y como sigue:

org     $8000       ; Dirección donde se carga el program

ld      hl, msg     ; Carga en HL la dirección de memoria del mensaje
ld      a, (hl)     ; Carga en A el primer carácter
rst     $10         ; Imprime el carácter
inc     hl          ; Apunta HL al carácter siguiente
ld      a, (hl)     ; Carga el carácter en A
rst     $10         ; Imprime el carácter

ret

msg:    defm 'Hola ensamblador ZX Spectrum'

end     $8000

Compilamos y cargamos en el emulador. Ahora veremos «Ho» impreso en pantalla.

Ensamblador para ZX Spectrum, imprimimos Ho
Ensamblador para ZX Spectrum, imprimimos Ho

Operaciones lógicas

La operaciones lógicas se realizan a nivel de bit, comparando dos bits. Hay tres tipos de operaciones lógicas:

  • AND: multiplicación lógica. El resultado solo es 1 si los dos bits están a 1.
  • OR: suma lógica. Si alguno de los dos bits está a 1, el resultado es 1, de lo contrario el resultado es 0.
  • XOR: or exclusivo. Si los dos bits son iguales, el resultado es 0, de lo contrario el resultado es 1.

En la siguiente tabla se muestran los posibles resultados de la operaciones lógicas:

Bit 1Bit 2ANDORXOR
11110
10011
01011
00000
Ensamblador para ZX Spectrum, resultados de las operaciones lógicas

El formato de las instrucciones lógicas es el siguiente:

AND origen
OR  origen
XOR origen

En las instrucciones lógicas, el origen puede ser cualquiera de los registros de 8 bits (a excepción del F), un valor, una posición de memoria apuntada por (HL) o por los registros índice, (IX + n) o (IY + n). El destino siempre es el registro A; las instrucciones lógicas se hacen sobre el valor que contiene el registro A, y el resultado se deja en este mismo registro.

Las instrucciones lógicas afectan al registro A de la siguiente manera:

InstrucciónSZHPNC
AND s***P00
OR s***P00
XOR s***P00
Ensamblador para ZX Spectrum
– = no afecta, * = afecta, 0 = se pone a 0, 1 = se pone a 1, P = paridad

Cambios de flujo de programa

Cambian el flujo del programa (salta), con o sin condiciones, de manera absoluta (JP) o relativa (JR). Estas instrucciones no afectan al registro F.

Los saltos absolutos pueden ser:

  • JP nn: salta a la dirección de memoria nn, que puede ser una etiqueta (en los siguientes casos también).
  • JP (HL): salta a la dirección de memoria del valor que tiene HL; al valor de HL (16 bits), no al valor de la dirección apuntada por HL (8 bits).
  • JP (registro índice): salta a la dirección de memoria del valor que tiene IX + n o IY + n.
  • JP NZ, nn: salta a la dirección de memoria nn, si el flag Z está a cero. El resultado de la última operación no es cero.
  • JP Z, nn: salta a la dirección de memoria nn, si el flag Z está a uno. El resultado de la última operación es cero.
  • JP NC, nn: salta a la dirección de memoria nn, si el flag C está a cero. No hay acarreo.
  • JP C, nn: salta a la dirección de memoria nn, si el flag C está a uno. Hay acarreo.
  • JP PO, nn: salta a la dirección de memoria nn, si el flag P/V está a cero. No hay paridad/desbordamiento.
  • JP PE, nn: salta a la dirección de memoria nn, si el flag P/V está a uno. Hay paridad/desbordamiento.
  • JP P, nn: salta a la dirección de memoria nn, si el flag S está a cero. El resultado de la última operación es positivo.
  • JP M, nn: salta a la dirección de memoria nn, si el flag S está a uno. El resultado de la última operación es negativo.

Los saltos relativos, son relativos a la instrucción actual, y saltan un número de bytes que van desde -128 a 127. Las rutinas con saltos relativos son reubicables, pues no afecta la posición de memoria en la que se cargan. Los saltos relativos pueden ser:

  • JR n: salta a la dirección de memoria que está a n bytes; n puede ser una etiqueta (en los siguientes casos también).
  • JR NZ, n: salta a la dirección de memoria que está a n bytes, si el flag Z está a cero. El resultado de la última operación no es cero.
  • JR Z, n: salta a la dirección de memoria que está a n bytes, si el flag Z está a uno. El resultado de la última operación es cero.
  • JR NC, n: salta a la dirección de memoria que está a n bytes, si el flag C está a cero. No hay acarreo.
  • JR C, n: salta a la dirección de memoria que está a n bytes, si el flag C está a uno. Hay acarreo.

Recuperamos el fichero HolaMundo.asm y vamos a utilizar las instrucciones lógicas, y los cambios de flujo, para imprimir todo el mensaje:

org     $8000       ; Dirección donde se carga el programa

ld      hl, msg     ; Carga en HL la dirección de memoria del mensaje

Bucle:
ld      a, (hl)     ; Carga un carácter de la cadena
or      a           ; Comprueba si A es 0. A or A = 0 solo si A = 0
jr      z, Fin      ; Si A = 0, salta a la etiqueta Fin
rst     $10         ; Imprime el carácter
inc     hl          ; Apunta HL al siguiente carácter
jr      Bucle       ; Vuelve al principio del bucle

Fin:
ret                 ; Sale del programa

msg:    defm 'Hola ensamblador ZX Spectrum', $00
        ; Cadena terminada en 0 = null

end     $8000

Compilamos con PASMO, cargamos en el emulador y vemos los resultados:

Ensamblador para ZX Spectrum, imprimimos todo el mensaje
Ensamblador para ZX Spectrum, imprimimos todo el mensaje

Subrutinas

Las subrutinas son bloques de código, que hacen una acción concreta, y al que se puede llamar en ocasiones múltiples: se usa CALL para saltar a una subrutina y RET para salir y volver al lugar desde el que se ha llamado.

CALL es parecido a JP, pero antes de saltar hace un PUSH de PC para guardar por dónde va el programa. Al hacer RET, se hace POP de PC y el programa vuelve por donde iba.

Se pueden realizar CALL y RET condicionales, al igual que se ha visto con JP y JR:

CALL nnRET
CALL NZ, nnRET NZ
CALL Z, nnRET Z
CALL NC, nnRET NC
CALL C, nnRET C
CALL PO, nnRET PO
CALL PE, nnRET PE
CALL P, nnRET P
CALL M, nnRET M
Ensamblador para ZX Spectrum, CALL y RET

Recuperamos HolaMundo.asm, y gracias a CALL vamos a llamar a alguna rutina de la ROM, para hacer que los resultados sean algo más vistosos:

org     $8000       ; Dirección donde se carga el programa

; Variable de sistema donde están los atributos de la pantalla 1.
; La pantalla 1 es la principal.
; El formato es Flash, Bright, Paper, Ink (FBPPPIII).
ATTR_S: equ $5c8d

; Variable de sistema donde está el atributo actual (FBPPPIII).
ATTR_T: equ $5c8f

; – ----------------------------------------------------------
; Rutina de la ROM similar al AT de Basic
; Posiciona el cursor en las coordenadas especificadas.
; Entrada:  B = Coordenada Y.
;           C = Coordenada X.
; Para esta rutina, la esquina superior izquierda de la pantalla
; es (24, 33).
; Altera el valor de los registros A, DE y HL.
; – ----------------------------------------------------------
LOCATE:	equ	$0dd9

; – ----------------------------------------------------------
; Rutina de la ROM semejante al CLS de Basic.
; Borra la pantalla usando los atributos cargados en la
; variable de sistema ATTR_S.
; Altera el valor de los registros AF, BC, DE y HL.
; – ----------------------------------------------------------
CLS:    equ $0daf

Inicio:
ld      a, $0e      ; Carga en A los atributos de color
ld      hl, ATTR_T  ; Carga en HL la dirección de memoria donde se
                    ; encuentran los atributos
ld      (hl), a     ; Carga en memoria los atributos de la pantalla
                    ; principal
ld      hl, ATTR_S  ; Carga en HL la dirección de memoria donde
                    ; se encuentran los atributos actuales
ld      (hl), a     ; Carga en memoria los atributos actuales

call    CLS         ; Limpia la pantalla usando los atributos de ATTR_T

ld      b, $18-$0a  ; Carga la coordenada Y en B
ld      c, $21-$02  ; Carga la coordenada X en C
call    LOCATE      ; Posiciona el cursor

ld      hl, msg     ; Carga en HL la dirección de memoria del mensaje

Bucle:
ld      a, (hl)     ; Carga un carácter de la cadena
or      a           ; Comprueba si A es 0.
jr      z, Fin      ; Salta a la etiqueta fin si A = 0
rst     $10         ; Imprime el carácter
inc     hl          ; Apunta HL al siguiente carácter
jr      Bucle       ; Bucle hasta que A = 0

Fin:
jr      Fin         ; Bucle infinito

msg:    defm        'Hola ensamblador ZX Spectrum', $00
                    ; Cadena terminada en 0 = null

end     $8000

Compilamos con PASMO, cargamos en el emulador y vemos los resultados:

Ensamblador para ZX Spectrum, con fondo azul
Ensamblador para ZX Spectrum, con fondo azul

Puertos de entrada y salida

Los puertos de entrada y salida se usan, entre otras cosas, para leer el teclado, el joystick, etcétera.

En nuestro caso, por ahora, solo lo vamos a usar para cambiar el color del borde de la pantalla, usando la instrucción OUT y el puerto $FE.

Vamos a realizar un pequeño programa para ver como se cambia el borde:

org     $8000       ; Dirección donde se carga el programa
ld      a, $01      ; Carga el color del borde en A
out     ($fe), a    ; Cambia el color del borde
ret
end     $8000

Compilamos con PASMO, cargamos en el emulador y vemos el resultado:

Ensamblador para ZX Spectrum, cambiamos el color del borde
Ensamblador para ZX Spectrum, cambiamos el color del borde

Con esto ya podemos finalizar nuestro primer programa en ensamblador para ZX Spectrum. Recuperamos el archivo HolaMundo.asm y añadimos las líneas para cambiar el color del borde, justo antes de la llamada a CLS:

org     $8000       ; Dirección donde se carga el programa

; Variable de sistema donde están los atributos de la pantalla 1.
; La pantalla 1 es la principal.
; El formato es Flash, Bright, Paper, Ink (FBPPPIII).
ATTR_S: equ $5c8d

; Variable de sistema donde está el atributo actual (FBPPPIII).
ATTR_T: equ $5c8f

; – ----------------------------------------------------------
; Rutina de la ROM similar al AT de Basic
; Posiciona el cursor en las coordenadas especificadas.
; Entrada:  B = Coordenada Y.
;           C = Coordenada X.
; Para esta rutina, la esquina superior izquierda de la pantalla
; es (24, 33).
; Altera el valor de los registros A, DE y HL.
; – ----------------------------------------------------------
LOCATE:	equ	$0dd9

; – ----------------------------------------------------------
; Rutina de la ROM semejante al CLS de Basic.
; Borra la pantalla usando los atributos cargados en la
; variable de sistema ATTR_S.
; Altera el valor de los registros AF, BC, DE y HL.
; – ----------------------------------------------------------
CLS:    equ $0daf

Inicio:
ld      a, $0e      ; Carga en A los atributos de color
ld      hl, ATTR_T  ; Carga en HL la dirección de memoria donde se
                    ; encuentran los atributos
ld      (hl), a     ; Carga en memoria los atributos de la pantalla
                    ; principal
ld      hl, ATTR_S  ; Carga en HL la dirección de memoria donde
                    ; se encuentran los atributos actuales
ld      (hl), a     ; Carga en memoria los atributos actuales

ld      a, $01      ; Carga el color del borde en A
out     ($fe), a    ; Cambia el color del borde

call    CLS         ; Limpia la pantalla usando los atributos de ATTR_T

ld      b, $18-$0a  ; Carga la coordenada Y en B
ld      c, $21-$02  ; Carga la coordenada X en C
call    LOCATE      ; Posiciona el cursor

ld      hl, msg     ; Carga en HL la dirección de memoria del mensaje

Bucle:
ld      a, (hl)     ; Carga un carácter de la cadena
or      a           ; Comprueba si A es 0.
jr      z, Fin      ; Salta a la etiqueta fin si A = 0
rst     $10         ; Imprime el carácter
inc     hl          ; Apunta HL al siguiente carácter
jr      Bucle       ; Bucle hasta que A = 0

Fin:
jr      Fin         ; Bucle infinito

msg:    defm        'Hola ensamblador ZX Spectrum', $00
                    ; Cadena terminada en 0 = null

end     $8000

Compilamos con PASMO, cargamos en el emulador y vemos los resultados:

Ensamblador para ZX Spectrum, Hola
Ensamblador para ZX Spectrum, Hola

Ya hemos desarrollado nuestro primer programa en ensamblador para ZX Spectrum. A partir de aquí empezamos con el desarrollo de nuestro PorompomPong.

Enlaces de interés

Ficheros

¿Te ha Resultado útil este artículo?

Ayúdanos a mejorar y danos tu opinión:

Mostrar más

5 comentarios

  1. Programar en ensamblador, siempre lo he visto desde la distancia, como muy complicado, pero es donde se obtienen los mejores resultados de rapidez desde luego !!!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicaciones relacionadas

Mira también
Cerrar
Botón volver arriba