Ensamblador para ZX Spectrum – Pong: $02 Hola Mundo
Hola Mundo en ensamblador para ZX Spectrum, punto de partida.
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.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Flag | S | Z | F5 | H | F3 | P/V | N | C |
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:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
32718 | 16384 | 8192 | 4096 | 2048 | 1024 | 512 | 256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
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.
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
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.
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:
Instrucción | S | Z | H | P | N | C |
INC r | * | * | * | V | 0 | – |
INC (HL) | * | * | * | V | 0 | – |
INC (ri + n) | * | * | * | V | 0 | – |
INC rr | – | – | – | – | – | – |
DEC r | * | * | * | V | 1 | – |
DEC (HL) | * | * | * | V | 1 | – |
DEC (ri + n) | * | * | * | V | 1 | – |
DEC rr | – | – | – | – | – | – |
– = 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.
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 1 | Bit 2 | AND | OR | XOR |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
0 | 1 | 0 | 1 | 1 |
0 | 0 | 0 | 0 | 0 |
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ón | S | Z | H | P | N | C |
AND s | * | * | * | P | 0 | 0 |
OR s | * | * | * | P | 0 | 0 |
XOR s | * | * | * | P | 0 | 0 |
– = 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:
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 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:
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:
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:
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
- Notepad++.
- Visual Studio Code.
- Sublime Text.
- ZEsarUX.
- PASMO.
- Git.
- Curso de ensamblador Z80 de Compiler Software.
- Z80 instruction set.
una currada de curso de programación, un gran trabajo 👌👌👌
Espero que os guste y os anime a iniciaros en el ensamblador.
Solo los programadores lengendarios saben usan ensamblador jeje
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 !!!
Así andamos todos. Todo es empezar.