0x00 Ensamblador ZX Spectrum Pong – Hola Mundo

Antes de entrar de lleno con el desarrollo de nuestro Pong en Ensamblador ZX Spectrum Pong, PorompomPong a partir de ahora, vamos implementar Hola Mundo, o lo que es lo mismo, hacer lo que se hace casi cada vez que se inicia el aprendizaje de un lenguaje de programación.

Ensamblador ZX Spectrum Pong – 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. Es el microprocesador que lleva el ZX Spectrum, en todos sus modelos.

El Z80 es una CPU de tipo «Little 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 (indicadores). Conjunto de indicadores 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, LDDR, 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 registro de destino en operaciones como LDIR, LDDR, 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 registro de origen en operaciones como LDIR, LDDR, 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.

Códigos de operación de los registros (opcodes)

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

Estos códigos de operación se utilizan para calcular el código de operación de las instrucciones en las que el parámetro es un registro:

  • LD A, r: 0x78 + rb
  • LD C, r: 0x48 + rb

Siendo rb el código de operación de los registros que se cargan, en este caso en A o en B.

Registro F

Cada bit del registro F, indicadores, 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 representarse (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 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 representarse.
  • 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.

Registro F – indicadores de flags

Bit76543210
SZF5HF3P/VNC
Ensamblador ZX Spectrum, registro F

Memoria del ZX Spectrum

La memoria está divida en dos, en los modelos 16K, o cuatro, en los modelos 48K, bloques de 16 KiB 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 encuentran el área de la pantalla, el buffer de impresora, las variables de sistema, etcétera, dejando aproximadamente 9 KiB para los programas, en los casos de los modelos 16K.

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

  • 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). En 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): búfer de impresora. 256 bytes que se pueden usar si no tenemos impresora, o si no lo usa el programa.
  • $5C00 a $5CB5 (23552 a 23733): variables de sistema.
  • $7FFF: puntero de 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 contener un valor entre 0 y 9. Esta notación tambié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: 14

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

Sin una calculadora a mano, la conversión de números 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
327681638481924096204810245122561286432168421
Ensamblador 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:

5FA0h = 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.
  • DB/DEFB: define bytes.
  • DM/DEFM: define message.
  • DW/DEFW: define word.
  • DS/DEFS: define space.
nombre EQU  valor
nombre DB   1, $FF, %10101010
nombre DEFM "Hola Mundo"
nombre DW   $0040
nombre DEFS $08

DB, DEFB, DM, DEFM, DW, DEFW, DS o DEFS 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 ya que 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 las 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 dónde finaliza el programa, y una dirección de autoinicio 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:

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.

ZX Spectrum Pong, Hola Mundo, el primer programa
Ensamblador ZX Spectrum, el primer programa

Instrucciones de carga

Estas instrucciones se utilizan para cargar un valor en un registro, copiar el 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 dirección de memoria de la pantalla, en adelante VideoRAM. Compilamos con PASMO y cargamos en el emulador:

pasmo --name HolaMundo --tapbas holamundo.asm holamundo.tap --public
Hola Mundo, primeros píxeles
Ensamblador ZX Spectrum, primeros píxeles

Instrucciones RST

Estas instrucciones son utilizadas para saltar a una dirección concreta a través de una instrucción de un solo código de operación (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 dos 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 la pantalla.

Hola Mundo, imprime la H
Ensamblador ZX Spectrum, imprime la 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 r               DEC r
INC rr              DEC rr
INC (HL)            DEC (HL)
INC (IX + n)        DEC (IX + n)
INC (IY + n)        DEC (IY + n)

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:

Flags
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 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     $800        ; Dirección donde carga el programa

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.

Hola Mundo, imprime Ho
Ensamblador ZX Spectrum, imprime Ho

Operaciones lógicas

Las 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 ZX Spectrum, resultados de las operaciones lógicas

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

AND    origen
OR     origen
XOR    origen

En las operaciones 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; la operaciones lógicas se hacen sobre el valor que contiene el registro A, y el resultado se deja en este mismo registro.

La operaciones lógicas afectan al registro F de la siguiente manera:

Flags
InstrucciónSZHPNC
AND s***P00
OR s***P00
XOR s***P00
Ensamblador 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 o IY.
  • JP NZ, nn: salta a la dirección nn si el falg 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 saltos 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á 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á 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á 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á n bytes si el flag C está a cero; no hay acarreo.
  • JR C, n: salta a la dirección de memoria que está n bytes si el flag C está a uno; hay acarreo.

Recuperamos el fichero holamundo.asm y vamos a utilizar las operaciones 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 el los resultados.

Hola Mundo, Hola ensamblador ZX Spectrum
Hola ensamblador ZX Spectrum

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 nn             RET
CALL NZ, nn         RET NZ
CALL Z, nn          RET Z
CALL NC, nn         RET NC
CALL C, nn          RET C
CALL PO, nn         RET PO
CALL PE, nn         RET PE
CALL P, nn          RET P
CALL M, nn          RET M

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 permanentes 
; 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 actuales
ld		(hl), a		; Carga en memoria los atributos actuales
ld		hl, ATTR_S	; Carga en HL la dirección de memoria donde
					; se encuentran los atributos permanentes
ld		(hl), a		; Carga en memoria los atributos permanentes

call	CLS			; Limpia la pantalla usando los atributos de ATTR_S

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.

Hola Mundo, fondo y tinta
Ensamblador ZX Spectrum, fondo azul y tinta amarilla

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 cómo 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.

Hola Mundo, borde
Ensamblador ZX Spectrum, borde azul

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, quedando el código de la siguiente manera:

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

; Variable de sistema donde están los atributos permanentes
; 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 actuales
ld		(hl), a		; Carga en memoria los atributos actuales
ld		hl, ATTR_S	; Carga en HL la dirección de memoria donde
					; se encuentran los atributos permanentes
ld		(hl), a		; Carga en memoria los atributos permanentes

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

call	CLS			; Limpia la pantalla usando los atributos de ATTR_S

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.

Hola Mundo, aspecto final
Hola ensamblador ZX Spectrum, aspecto final

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

Podéis descargar todo el código que hemos generado.

Enlaces de interés

Vídeo

Si lo prefieres, puedes ver el vídeo que grabamos de esta sesión.

Ensamblador ZX Spectrum, Hola Mundo

Ensamblador para ZX Spectrum PONG por Juan Antonio Rubio García.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartitIgual 4.0 Internacional License.
Correcciones al texto original realizadas por Joaquín Ferrero.
Este tutorial ha sido publicado con anterioridad en AUA y se han grabado vídeos que están publicados a través de Retro Parla.

No olvides visitar las webs amigas.

AUA

Aquí puedes ver más cosas que he desarrollado para .Net, y aquí la desarrolladas en ensamblador para Z80.

Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.

Deja una respuesta

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies
A %d blogueros les gusta esto: