Libro: Lenguaje máquina avanzado para ZX Spectrum - Obijuan/Learn-zx-spectrum-asm GitHub Wiki
Experimentos y ejemplos del libro Lenguaje Máquina avanzado para ZX Spectrum de David Webb
Contenido
1. Direccionamiento de pantalla
La pantalla del espectrum está formada por celdas de 8x8. En total hay 24 verticales y 32 horizontales (24x32 celdas)
Por el término línea nos referimos a la coordenada y de la celda (0-23). Y por el término Scanline
a las 24x8 sublíneas que componen las celdas. Cada celda tiene 8 Scanlines
Una de las claves para pintar en cualquier posición es calcular la dirección de la memoria de vídeo a partir de las coordenadas en pantalla. Para ello necesitamos saber cómo avanzar desde una scanline a la siguiente, y a la inversa. Esta operación NO ES TRIVIAL en el Spectrum, ya que la relación NO ES LINEAL
- Dirección memoria vídeo: 0x4000 - 0x5FFF. En total 6KB (de pixeles, más 2Kb para los atributos)
La pantalla está dividida en 3 tercios
. Cada tercio tiene 8 líneas (64 scanlines), y ocupa 2KB (0x800)
Tercio | Direccion | Dir binaria |
---|---|---|
0 | 0x4000 | 0100 0000 0000 0000 |
1 | 0x4800 | 0100 1000 0000 0000 |
2 | 0x5000 | 0101 0000 0000 0000 |
Analizando las direcciones en binario vemos que todos los tercios comienzan por 010
(bits 15,14 y 13). Los bits 12 y 11 son los que indican el Tercio en el que estamos
Bit 15,14,13 | Bit 12,11 |
---|---|
101 | TT |
Valor fijo | Tercio |
Para ir fijando ideas voy a hacer programas de pruebas. En este programa se ha creado la rutina dir_tercio
que devuelve la dirección de memoria correspondiente al comienzo de cada tercio. El programa dibuja las scanlines iniciales de los tercios 0, 1 y 2
- 03-tercios.asm
;----------------------------------------------------------------------------
;-- Dibujar la primera scanline de cada tercio
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;--- Poner borde amarillo
ld a, 6
out (BORDER), a
;-- Dibujar la scanline inicial del primer tercio
ld a,0 ;-- Tercio: 0
call dir_tercio
call lineah
;-- Dibujar scanline incial del segundo tercio
ld a,1
call dir_tercio
call lineah
;-- Dibujar scanline inicial del tercer tercio
ld a,2
call dir_tercio
call lineah
ret
;------------------------------------------------------------
;-- DIR_TERCIO: Obtener la direccion de memoria de un tercio
;--
;-- ENTRADA:
;-- * A: Numero de tercio (0-2)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;------------------------------------------------------------
dir_tercio:
;-- Desplazar A 3 bits a la izquierda, para colocar el tercio
add a,a ; A = 2*A (Desplazamiento de 1 bit a la izquierda)
add a,a
add a,a
;-- Añadir a A los 3 bits de mayor peso correspondientes
;-- a la direccion de la memoria de video (010)
ld b,a
ld a, $40
or b
;-- Meter en HL la direccion de video
ld h,a
ld l,0
ret
;------------------------------------------------------------
;-- LINEAH: Pintar una linea horizontal
;--
;-- ENTRADAS:
;-- * HL: Puntero a la memoria de video
;-- MODIFICA:
;-- * Registros AF, B
;------------------------------------------------------------
lineah:
;-- Guardar direccion inicial
push hl
;-- Guardar bc
push bc
;-- Numero de bytes para completar una linea horizontal
ld b, $20
lineah_loop:
ld a, $ff
ld (hl), a
;-- Pausa para ver la linea dibujarse
halt
;-- Apuntar a la siguiente posicion
inc hl
;-- Decrementar contador de bytes
dec b
ld a, b
or a
jr nz, lineah_loop
;-- Recuperar bc
pop bc
;-- Recuperar direccion inicial
pop hl
;-- Retornar
ret
end $8000
Se aprecian perfectamente los 3 tercios. Con esta rutina ya se puede obtener la dirección base de cada tercio, y trabajar de manera independiente con ellos.
Ahora lo siguiente es obtener la dirección base (o el offset) correspondiente al número de línea del tercio actual. Vamos a suponer que estamos en el tercio inicial (0). Como hay 32 celdas (0x20), si sumamos 0x20 pasamos a la siguiente línea. Pero OJO! La linea siguiente está 8 scanlines por debajo de la actual
Necesitamos una rutina para obtener el offset a partir del número de linea. El siguiente programa dibuja lineas horizontales en el comienzo de las líneas indicadas del primer tercio
El numero de linea está codificado en la palabra de memoria en los bits 7,6 y 5:
Bit 7,6,5 | Bit 4,3,2,1,0 |
---|---|
LLL | CCCCC |
Numero linea | Columna |
Es decir, que dentro del tercio actual, el byte bajo de HL contiene el numero de linea, desplazado 5 bits a la izquierda. Por tanto el cálculo que hay que hacer es desplazar L 5 bits a la izquierda
- 04-tercio-linea.asm
;-------------------------------------------------------------
;-- Dibujar las lineas indicadas dentro del tercio actual
;-------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;--- Poner borde amarillo
ld a, 6
out (BORDER), a
;-- Dibujar lineas de separacion entre los tercios, para
;-- comprobar que las lineas siguientes estan ok
call split_tercios
;-- HL: Obtener la direccion del tercio en cuestion
ld a,0 ;-- Tercio: 0
call dir_tercio
;-- Pintar en diferentes lineas
;-- Indicar linea a pintar (0-7)
ld a,1
call offset_linea
call lineah
ld a,7
call offset_linea
call lineah
ld a,2
call offset_linea
call lineah
ret
;---------------------------------------------------------
;-- Dibujar las 3 lineas de separacion de los tercios
;---------------------------------------------------------
split_tercios:
;--- Linea de tercio 0
ld a,0
call dir_tercio
call lineah
;-- Linea de tercio 1
ld a,1
call dir_tercio
call lineah
;-- Linea de tercio 2
ld a,2
call dir_tercio
call lineah
ret
;------------------------------------------------------------
;-- OFFSET_LINEA: Obtener la nueva direccion correspondiente
;-- a la linea indicada en el tercio actual
;--
;-- ENTRADAS:
;-- HL: Direccion base del tercio actual
;-- A: Linea a dibujar (0-7)
;--
;-- SALIDA:
;-- HL: Nueva direccion de la memoria de video
;------------------------------------------------------------
offset_linea:
;-- Hay que desplazarlo 5 bits a la izquierda
add a,a
add a,a
add a,a
add a,a
add a,a
;-- Esto es el nuevo byte bajo de HL
ld l,a
ret
;------------------------------------------------------------
;-- DIR_TERCIO: Obtener la direccion de memoria de un tercio
;--
;-- ENTRADA:
;-- * A: Numero de tercio (0-2)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;------------------------------------------------------------
dir_tercio:
;-- Desplazar A 3 bits a la izquierda, para colocar el tercio
add a,a ; A = 2*A (Desplazamiento de 1 bit a la izquierda)
add a,a
add a,a
;-- Añadir a A los 3 bits de mayor peso correspondientes
;-- a la direccion de la memoria de video (010)
ld b,a
ld a, $40
or b
;-- Meter en HL la direccion de video
ld h,a
ld l,0
ret
;------------------------------------------------------------
;-- LINEAH: Pintar una linea horizontal
;--
;-- ENTRADAS:
;-- * HL: Puntero a la memoria de video
;-- MODIFICA:
;-- * Registros AF, B
;------------------------------------------------------------
lineah:
;-- Guardar direccion inicial
push hl
;-- Guardar bc
push bc
;-- Numero de bytes para completar una linea horizontal
ld b, $20
lineah_loop:
ld a, $ff
ld (hl), a
;-- Pausa para ver la linea dibujarse
halt
;-- Apuntar a la siguiente posicion
inc hl
;-- Decrementar contador de bytes
dec b
ld a, b
or a
jr nz, lineah_loop
;-- Recuperar bc
pop bc
;-- Recuperar direccion inicial
pop hl
;-- Retornar
ret
end $8000
Para poder acceder a las scanlines independientemente nos queda el último paso. En total hay 8 scanlines (3 bits) dentro de cada fila, pero por la disposicion extraña que tienen, estos 3 bits de scanlines están codificados en los bits 8,9 y 10. Por ello, el formato completo de la dirección es el siguiente:
Bit 15,14,13 | Bit 12,11 | Bit 10,9,8 | Bit 7,6,5 | Bit 4,3,2,1,0 |
---|---|---|---|---|
101 | TT | SSS | LLL | CCCCC |
Valor fijo | Tercio | Scanline | Linea | Columna |
Hay que hacer una rutina que establezca en HL el valor del scanline actual, dejando lo demás intacto La operación no es muy coplicada. Basta con poner a 0 los 3 bits de menor peso de H, y hacer un or con el numero de scanline
En este ejemplo dibujamos 3 scanlines consecutivas correspondientes a la linea 1 del tercio 0
- 05-tercio-linea-scanline.asm
;-------------------------------------------------------------
;-- Dibujar las lineas indicadas dentro del tercio actual
;-------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;--- Poner borde amarillo
ld a, 6
out (BORDER), a
;-- Dibujar lineas de separacion entre los tercios, para
;-- comprobar que las lineas siguientes estan ok
call split_tercios
;-- HL: Obtener la direccion del tercio en cuestion
ld a,0 ;-- Tercio: 0
call dir_tercio
;-- Pintar en diferentes lineas
;-- Indicar linea a pintar (0-7)
ld a,1
call offset_linea
call lineah ;-- Se pinta el primer scanline (0)
;-- Pintar varios scanlines consecutivos
;-- Establecer la scanline a pintar
ld a,1 ;-- Scanline 1
call offset_scanline
call lineah
ld a,2 ;-- Scanline 2
call offset_scanline
call lineah
ret
;---------------------------------------------------------
;-- Dibujar las 3 lineas de separacion de los tercios
;---------------------------------------------------------
split_tercios:
;--- Linea de tercio 0
ld a,0
call dir_tercio
call lineah
;-- Linea de tercio 1
ld a,1
call dir_tercio
call lineah
;-- Linea de tercio 2
ld a,2
call dir_tercio
call lineah
ret
;-------------------------------------------------------------
;-- OFFSET_SCANLINE: Obtener la nueva direccion correspondiente
;- al scanline indicado de la linea actual en el tercio actual
;--
;-- ENTRADAS:
;-- HL: Direccion base del tercio actual
;-- A: Scanline a dibujar
;--
;-- SALIDAS:
;-- HL: Nueva direccion de la memoria de video
;----------------------------------------------------------------
offset_scanline:
;-- El scanline lo metemos en b para no perderlo
ld b,a
;-- Borrar los 3 bits de menor peso de H (SSS)
;-- El resultado en A
ld a,$f8
and h
;-- Añadir el scanline a A
or b
;-- Llevarlo a H
ld h,a
ret
;------------------------------------------------------------
;-- OFFSET_LINEA: Obtener la nueva direccion correspondiente
;-- a la linea indicada en el tercio actual
;--
;-- ENTRADAS:
;-- HL: Direccion base del tercio actual
;-- A: Linea a dibujar (0-7)
;--
;-- SALIDA:
;-- HL: Nueva direccion de la memoria de video
;------------------------------------------------------------
offset_linea:
;-- Hay que desplazarlo 5 bits a la izquierda
add a,a
add a,a
add a,a
add a,a
add a,a
;-- Esto es el nuevo byte bajo de HL
ld l,a
ret
;------------------------------------------------------------
;-- DIR_TERCIO: Obtener la direccion de memoria de un tercio
;--
;-- ENTRADA:
;-- * A: Numero de tercio (0-2)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;------------------------------------------------------------
dir_tercio:
;-- Desplazar A 3 bits a la izquierda, para colocar el tercio
add a,a ; A = 2*A (Desplazamiento de 1 bit a la izquierda)
add a,a
add a,a
;-- Añadir a A los 3 bits de mayor peso correspondientes
;-- a la direccion de la memoria de video (010)
ld b,a
ld a, $40
or b
;-- Meter en HL la direccion de video
ld h,a
ld l,0
ret
;------------------------------------------------------------
;-- LINEAH: Pintar una linea horizontal
;--
;-- ENTRADAS:
;-- * HL: Puntero a la memoria de video
;-- MODIFICA:
;-- * Registros AF, B
;------------------------------------------------------------
lineah:
;-- Guardar direccion inicial
push hl
;-- Guardar bc
push bc
;-- Numero de bytes para completar una linea horizontal
ld b, $20
lineah_loop:
ld a, $ff
ld (hl), a
;-- Pausa para ver la linea dibujarse
halt
;-- Apuntar a la siguiente posicion
inc hl
;-- Decrementar contador de bytes
dec b
ld a, b
or a
jr nz, lineah_loop
;-- Recuperar bc
pop bc
;-- Recuperar direccion inicial
pop hl
;-- Retornar
ret
end $8000
Para dejar esta parte ya coprendida, voy a reorganizar las rutinas para trabajar de manera aislada con cada uno de los campos. Estas rutinas no están optimizadas. Se parte de una dirección en HL válida (ya inicializada) y se pueden establecer de manera independiente los diferentes campos:
- C: Posicion horizontal de la Celda (columna): 0 - 31
- L: Linea de la Celda: 0 - 23
- S: Scanline dentro de la liena actual (0-7
- T: Tercio (0-2)
Mediante llamadas a diferentes rutinas se establecen de forma independiente estos campos
- set_tercio(A): Establer el tercio actual
- set_fila(A): Establcer la fila dentor del tercio actual
- set_scanline(A): Establecer la scanline dentro de la fila y tercio actual
- set_col(A): Establecer la columna
Este es el programa de ejemplo:
- 06-video-set-subr.asm
;----------------------------------------------------------------------------
;-- Subrutinas para acceso a las diferentes zonas de la memoria de video
;-- Se parte siempre de una dirección ya inicializada en HL
;-- (por ejemplo HL = 0x4000)
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- HL Apunta a la memoria de video
ld hl, $4000
;-- Establecer tercio 2
ld a, 2
call set_tercio
;-- Pintar
ld a,$FF
ld (hl), a
;-- Establecer linea 1
ld a,1
call set_linea
;-- Pintar
ld a,$FF
ld (hl), a
;- Establecer el scanline 1
ld a,1
call set_scanline
;-- Pintar
ld a,$FF
ld (hl), a
;-- Establecer la columna 1
ld a,1
call set_col
;-- Pintar
ld a,$FF
ld (hl), a
ret
;------------------------------------------------------------
;-- set_tercio: Establecer el tercio a usar
;--
;-- ENTRADA:
;-- * A: Numero de tercio (0-2)
;-- * HL: Direccion de video (cualquiera valida)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;--
;-- Esta funcion SOLO cambia el campo del tercio, dejando el resto
;-- de los componentes de HL como estaban (scanline, linea, col...)
;-- PPPT TSSS LLLC CCCC
;--
;------------------------------------------------------------
set_tercio:
;-- Desplazar A 3 bits a la izquierda, para colocar el tercio
add a,a ; A = 2*A (Desplazamiento de 1 bit a la izquierda)
add a,a
add a,a
;-- B: Numero de tercio ya colocado en su posicion
ld b, a
;-- Dejar en A lo que hay en H pero con los bits T a 0
ld a,$E7 ;-- Mascara para poner a 0 los bits T de HL
and h ;-- A= PPP0 0SSS
;-- Añadir a A el numero de tercio
or b
;-- Actualizar la direccion de video
;-- L se queda como esta
;-- H tiene los nuevos bits de T
ld h,a
ret
;------------------------------------------------------------
;-- set_linea: Establecer a linea a usar
;--
;-- ENTRADA:
;-- * A: Numero de linea (0-7)
;-- * HL: Direccion de video (cualquiera valida)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;--
;-- Esta funcion SOLO cambia el campo de la liena, dejando el resto
;-- de los componentes de HL como estaban (tercio,scanline, col...)
;-- PPPT TSSS LLLC CCCC
;--
;------------------------------------------------------------
set_linea:
;-- Hay que desplazarlo 5 bits a la izquierda
add a,a
add a,a
add a,a
add a,a
add a,a
;-- Esto es el nuevo byte bajo de HL
ld l,a
ret
;------------------------------------------------------------
;-- set_scanline: Establecer el scanline a usar
;--
;-- ENTRADA:
;-- * A: Numero de scanline (0-7)
;-- * HL: Direccion de video (cualquiera valida)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;--
;-- Esta funcion SOLO cambia el campo del scanline, dejando el resto
;-- de los componentes de HL como estaban (tercio,linea, col...)
;-- PPPT TSSS LLLC CCCC
;--
;------------------------------------------------------------
set_scanline:
;-- El scanline lo metemos en b para no perderlo
ld b,a
;-- Borrar los 3 bits de menor peso de H (SSS)
;-- El resultado en A
ld a,$f8
and h
;-- Añadir el scanline a A
or b
;-- Llevarlo a H
ld h,a
ret
;------------------------------------------------------------
;-- set_col: Establecer la columna a usar
;--
;-- ENTRADA:
;-- * A: Numero de columna (0-31)
;-- * HL: Direccion de video (cualquiera valida)
;-- DEVUELVE:
;-- * HL: Direccion de memoria
;--
;-- Esta funcion SOLO cambia el campo de la columna, dejando el resto
;-- de los componentes de HL como estaban (tercio,linea, scanline...)
;-- PPPT TSSS LLLC CCCC
;--
;------------------------------------------------------------
set_col:
;-- Guardar la columna en b
ld b,a
;-- Poner en A lo que hay en L con los bits C a 0
ld a, $E0 ;-- Mascara
and l
;-- Añadir el campo de la columna (que esta en b)
or b
;-- Llevar el resultado a l
ld l, a
ret
end $8000
Este programa ha pintado 4 elementos, cada uno en una zona
Rutina DF-LOC
Ya tengo mis rutina hechas, pero ahora voy a estudiar las creadas por David Webb. La primera es DF-LOC
que devuelve la dirección de la memoria de video correspondiente a una LINEA y una COLUMNA, donde LINEA va entre 0-23 y COLUMNA 0-31
Si esta dirección se almacena en la variable del sistema 0x5C84, esa será la siguiente posicion usada por la rutina de impresión. Y sería un equivalente a un PRINT AT. Esta variable la llama DF_CC
- 07-DF-LOC.asm
;----------------------------------------------------------------------------
;-- Prueba de la subrutina DF-LOC
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Print AT 0,0
ld b, 0 ;-- B = Linea
ld c, 0 ;-- C = columna
call DF_LOC
;-- Pintar
ld a,$FF
ld (hl), a
;-- Print AT 1,1
ld b, 1
ld c, 1
call DF_LOC
;-- Pintar
ld a,$FF
ld (hl), a
;-- Print at 21,31
ld b,21
ld c,31
call DF_LOC
;-- Pintar
ld a,$FF
ld (hl), a
ret
;---------------------------------------------------------------------------
;- DF_LOC: Devolver la memoria de video asociada a la posicon (LNEA,COL)
;-
;-- ENTRADAS:
;-- * B = Linea (0-23)
;-- * C = Col (0 - 31)
;--
;-- SALIDA:
;-- * HL: Direccion de video
;--
;-- Formato de la direccion de video:
;-- 010T TSSS LLLC CCCC
;--
;-- El tercio se toma como parte de la linea para trabajar en el rango 0-21
;-- Así la linea es TTLLL (5-bits). Está formada por dos campos
;---------------------------------------------------------------------------
DF_LOC:
ld a,b ;-- A = Linea (000T TLLL)
and $f8 ;-- Poner a 0 los bits L (dejando solo los T)
;-- A = 000T T000
add a,$40 ;-- A = 0100 0000 --> 010T T000
;-- H completado: Contiene valor corrector y el Tercio establecido (TT)
ld h, a ;-- H = 010T T000
;-- Establecer la parte baja en reg L (LLLC CCCC)
ld a,b ;-- A = linea (000T TLLL)
and $7 ;-- A = 000T TLLL
;-- 0000 0111
;-- A = 0000 0LLL (Nos quedamos con la linea)
rrca ;-- A = L000 00LL
rrca ;-- A = LL00 000L
rrca ;-- A = LLL0 0000 (Tenemos LLL bien posicionado)
;-- Añadir el campo de la columna (que esta en C)
add a,c
;-- Actualizar L
ld l,a
;-- HL contiene la direccion
ret
end $8000
Rutina CLS-DF
La siguiente es una rutina para borrar la pantalla. Deja los atributos como están, simplemente se ponen todos los pixeles a 0. Lo que se hace es copiar un '0' a un bloque de x bytes a partir de la dirección 0x4000 usando la instrucción ldir
Como cada tercio son 2K (0x800), Podemos sólo borrar los tercios indicados
Tercio | Dir | Tamaño |
---|---|---|
0 | 0x4000 | 0x800 |
1 | 0x4800 | 0x800 |
2 | 0x5000 | 0x800 |
Así, para borrar todos los tercios el tamaño a usar es 0x800 + 0x800 + 0x800 = 0x1800 (6Kb)
- 08-CLS-DF.asm
;----------------------------------------------------------------------------
;-- Prueba de la subrutina CLS-DF
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Borrar todos los tercios
call CLS_DF
;-- No retornamos para que se vea la pantalla limpia
inf:
jr inf
;---------------------------------------------------------------------------
;- CLS_DF: Borrar la pantalla
;---------------------------------------------------------------------------
CLS_DF:
ld hl, $4000 ;-- Puntero al comienzo memoria video
;-- Indicar cuantos bytes por encima hay que rellenar
;-- Queremos llegar hasta la dir $57FF ($4000 + 17FF)
ld bc, $17FF
;-- Byte a usar de relleno: 0
;-- Se podria usar: LD (HL), 0 PERO como L es 0, es más rápido
;-- (y ocupa menos) esta instruccion LD (HL),L
ld (hl), l
;-- En DE ponemos la direccion destino para copiar de ($4000)
;-- a ($4001)
ld d,h
ld e,1
;-- La instrucción ldir repite esta operación BC veces
;-- (Incrementa DE y HL, y decrementa BC hasta que llega a 0)
ldir
;-- Terminar
ret
end $8000
Bala
En este ejemplo se practica lo aprendido: aniación de una bala
- 09-bullet.asm
;----------------------------------------------------------------------------
;-- Animación de una bala...
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
init:
;-- Posicion inicial
ld b, 10 ;-- B = Linea
ld c, 0 ;-- C = columna
call DF_LOC
;-- Print AT
ld a, $FF
ld (hl), a
halt
loop:
;-- Guardar posicion actual
ld de, pos_old
ld a,l
ld (de), a
;-- Obtener la nueva posicion
inc c
;-- Si la bala ha llegado al final se termina
ld a,c
cp 32
jr z, fin
;-- Imprimir la bala en la nueva posicion
call DF_LOC
ld a, $FF
ld (hl), a
push hl
;-- Borrar la bala de la posicion anterior
ld de, pos_old
ld a, (de)
ld l,a
xor a ;-- Escribir un 0
ld (hl), a
;-- Recuperar posicion actual
pop hl
;-- Mini-pausa
halt
halt
jr loop
fin:
;-- Borrar la bala de la posicion anterior
ld de, pos_old
ld a, (de)
ld l,a
xor a ;-- Escribir un 0
ld (hl), a
;-- Hacer una pausa mayor
ld b,$40
loop2:
halt
dec b
jr nz, loop2
;-- Repetir
jr init
;-- Guardar la posicion antigua
pos_old: defb 0
ATTLOC
Rutina para obtener la dirección de la memoria de atributos (2K) asociada a una posicion. La dirección de los atributos empieza en la dirección 0x5800
- 11-ATTLOC.asm
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina ATTLOC
;-- Cambio de atributos de varias localizaciones
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Establecer ATTR(0,0)
ld b,0 ;-- Linea
ld c,0 ;-- Col
call ATTLOC
;-- Fijar el atributo
;-- Fondo azul, tinta blanca
ld a, $0F
ld (hl),a
;-- Establecer ATTR(1,1)
ld b,1 ;-- Linea
ld c,1 ;-- Col
call ATTLOC
;-- Fijar el atributo
;-- Fondo azul, tinta blanca
ld a, $0F
ld (hl),a
;-- Establecer ATTR(21,31)
ld b,21 ;-- Linea
ld c,31 ;-- Col
call ATTLOC
;-- Fijar el atributo
;-- Fondo azul, tinta blanca
ld a, $0F
ld (hl),a
ret
;---------------------------------------------------------------------------
;- ATTLOC: Devolver la memoria de video de atributos asociada a la
;- posicion (LINEA, COL)
;-
;-- ENTRADAS:
;-- * B = Linea (0-23)
;-- * C = Col (0 - 31)
;--
;-- SALIDA:
;-- * HL: Direccion de video de atributos
;--
;---------------------------------------------------------------------------
ATTLOC:
ld a,b ;-- A = Linea (000TTLLL)
;-- Obtener los bits asociados al tercio
sra a ;-- A = 0000TTLL
sra a ;-- A = 00000TTL
sra a ;-- A = 000000TT
;-- Añadir comienzo de memoria de atributos (0x5800)
add a,$58 ;-- 58 01011000
;-- A = 010110TT
;-- Ya tenemos H listo
ld h,a
;-- Leer la linea de nuevo
ld a,b ;-- A = 000TTLLL
;-- Aislar el campo LLL
and $7 ; 7 = 00000111
; A = 00000LLL
;-- Colocar LLL en los bits e mayor peso
rrca ; A = L00000LL
rrca ; A = LL00000L
rrca ; A = LLL00000
;-- Añadir la columna
add a,c ; A = LLLCCCCC
;-- Ya tenemos L listo
ld l,a
;-- Terminar
ret
end $8000
Rutina DF-ATT
- 12-DF-ATT.asm
Esta rutina convierte de la dirección de memoria de video a la de atributos
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina DF-ATT
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Escribir un caracter en una posicion cualquiera
ld hl, $4034
ld a, $aa
ld (hl), a
;-- Cambiar los atributos de ese caracter
call DF_ATT ;-- Obtener direccion de los atributos
ld a, $11
ld (de), a ;-- Establecer atributos para el caracter
ret
;---------------------------------------------------------------------------
;- DF-ATT: Convertir la dirección de video a dirección de attributos
;-
;-- ENTRADAS:
;-- * HL = Direccion de memoria de video
;--
;-- SALIDA:
;-- * DE: Direccion de memoria de atributos
;---------------------------------------------------------------------------
DF_ATT:
;-- Aislar el tercio
ld a,h ;-- A = 010TTSSS
rrca ;-- A = S010TTSS
rrca ;-- A = SS010TTS
rrca ;-- A = SSS010TT
and 3 ;-- 3 = 00000011
;-- A = 000000TT
;-- Añadir el prefijo de la direccion de los atributos
or $58 ;-- 58 = 01011000
;-- A = 010110TT
;-- Devolver la direccion en DE
ld d,a
ld e,l
;-- Terminar
ret
end $8000
Rutina ATT-DF
- 13-ATT-DFF.asm
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina ATT-DF
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Establecer los atributos de una posicion aleatoria
ld hl, $5904
ld a, $11
ld (hl),a
;-- Escribir un valor en esa posicion de video
call ATT_DF ;-- Obtener direccion corresponiente en el video
ld a, $55
ld (de), a ;-- Escribir un patron de pixeles
ret
;---------------------------------------------------------------------------
;- ATT-DF: Convertir la dirección de atributos a video
;-
;-- ENTRADAS:
;-- * HL = Direccion de memoria de atributos
;--
;-- SALIDA:
;-- * DE: Direccion de memoria de video
;---------------------------------------------------------------------------
ATT_DF:
ld a, h ;-- A = 010TTSSS
and 3 ;-- 3 = 00000011
;-- A = 000000SS
rlca ;-- A = 00000SS0
rlca ;-- A = 0000SS00
rlca ;-- A = 000SS000
or $40 ;-- 40 = 01000000
;-- A = 010SS000
;-- Meter la direccion de video en DE
ld d,a
ld e,l
;-- Terminar
ret
end $8000
LOCATE
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina LOCATE
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Establecer posicion
;-- LOCATE(10,15)
ld b, 10 ;-- Linea
ld c, 15 ;-- Col
call LOCATE
;-- Atributos
ld a, $32 ;-- Color rojo, fondo amarillo
ld (de), a
;-- Imprimir un patron
ld a, $55
ld (hl), a
ret
;-------------------------------------------------------------------
;-- VARIABLES DEL SISTEMA
;-------------------------------------------------------------------
;-- DFCC: Contiene la direccion de donde imprimir el siguiente
;-- caracter
;-------------------------------------------------------------------
DFCC: EQU $5C84
;---------------------------------------------------------------------------
;-- LOCATE: Devolver la localizacion de una celda (LINEA, COL)
;-- la almacena en DFCC, devuelve la dir. de sus atributos
;-- y los atributos
;-
;-- ENTRADAS:
;-- * B: Linea (0-21)
;-- * C: Col (0-31)
;--
;-- SALIDA:
;-- * HL: Direccion memoria video
;-- * DE: Direccion de los atributos
;-- * A: Attributos ATTR(B,C)
;---------------------------------------------------------------------------
LOCATE:
ld a,b ; A = 000TTLLL (Linea)
;-- Aislar el tercio (TT)
and $18 ; 18 = 00011000
; A = 000TT000
;-- Establecer H para apuntar a la memoria video
ld h, a
set 6, h ; H = 010TT000
;-- Establecer D para apuntar a memoria atributos
rrca ; A = 0000TT00
rrca ; A = 00000TT0
rrca ; A = 000000TT
or $58 ; 58 = 01011000
; A = 010110TT
ld d,a
ld a, b ; A = 000TTLLL (Linea)
and 7 ; 7 = 00000111
; A = 00000LLL
rrca ; A = L00000LL
rrca ; A = LL00000L
rrca ; A = LLL00000
add a,c ; A = LLLCCCCC
;-- HL Listo
ld l,a
;-- DE Listo
ld e,a
;-- A = Atributos
ld a, (de)
;-- Guardar HL en variable sistema
ld (DFCC), hl
;-- Terminar
ret
end $8000
Rutina CLSATT
- 15-CLSATT.asm
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina CLSATT
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Cambiar los atributos de toda la pantalla
;-- b3 = 1011 0011
;-- FBPP PIII Flash=on, bright=off, paper=amarillo, ink=magenta
ld a, $33
call CLSATT
ret
;---------------------------------------------------------------------------
;-- CLSATT: Borrar la memoria de atributos escribiendo un valor
;-
;-- ENTRADAS:
;-- * A: Atributo de pantalla (FBPPPIII) F=Flash, B=Bright, P=paper, I=Ink
;---------------------------------------------------------------------------
CLSATT:
;-- Direccion de memoria de atributos
ld hl, $5800
;-- Tamaño de la memoria de atributos
ld bc, $2ff
ld (hl), a ;-- Establecer atributo en la posicion actual
ld d, h ;-- de = $5801
ld e, 1
ldir
;-- Terminar
ret
end $8000
Rutina CLS
- 16-CLS.asm
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina CLS
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Cambiar los atributos de toda la pantalla
;-- b3 = 1011 0011
;-- FBPP PIII Flash=on, bright=off, paper=amarillo, ink=magenta
ld a, $33
call CLS
inf:
jr inf
;---------------------------------------------------------------------------
;-- CLS: Borrar la pantalla y establecer los atributos
;-- Combinacion de CLSATT y CLS-DF
;-
;-- ENTRADAS:
;-- * A: Atributo de pantalla (FBPPPIII) F=Flash, B=Bright, P=paper, I=Ink
;---------------------------------------------------------------------------
CLS:
;-- Borrar memoria de video
ld hl, $4000 ;-- Direccion base de la memoria de video
ld bc, $1800 ;-- Tamaño de la memoria de video
ld (hl), l ;-- Escribir 0 en memoria video
ld d, h ;-- de = $4001
ld e, 1
ldir
;-- Establecer los atributos
ld (hl), a
ld bc, $2ff ;-- Tamaño memoria atributos
ldir
;-- Terminar
ret
2. Desarrollo de una rutina de impresión
En este capítulo del libro de David Webb se desarrolla una nueva rutina de impresión de caracteres, que es mucho más rápida que la llamada al sistema rst 10h
El proceso está dividido en tres etapas:
- Etapa 1: Obtener la dirección de los datos del caracter
- Etapa 2: Copiar estos datos a la celda indicada
- Etapa 3: Establecer los atributos de la celda
Los atributos de los colores se definen así: Verde, Rojo, Azul
Color | Verde | Rojo | Azul | Valor |
---|---|---|---|---|
Negro | 0 | 0 | 0 | 0 |
Azul | 0 | 0 | 1 | 1 |
Rojo | 0 | 1 | 0 | 2 |
Magenta | 0 | 1 | 1 | 3 |
Verde | 1 | 0 | 0 | 4 |
Cyan | 1 | 0 | 1 | 5 |
Amarillo | 1 | 1 | 0 | 6 |
Blanco | 1 | 1 | 1 | 7 |
Rutina PRINT1
- 17-PRINT1.asm
;----------------------------------------------------------------------------
;-- Ejemplo de uso de la rutina PRINT1
;----------------------------------------------------------------------------
;--- Puerto para establecer el color del Borde
BORDER: EQU $FE
org $8000
;-- Poner el borde amarillo
ld a, 6
out (BORDER), a
;-- Imprimir todos los caracteres disponibles
;-- Desde el $20 hasta el $7F
ld c, $20
loop:
;-- Imprimir caracter actual
ld a,c
call PRINT1 ;-- print1 conserva registro c
;-- Siguiente caracter a imprimir
inc c
;-- Si es $80, hemos terminado
ld a,c
cp $80
jr nz,loop
ret
;----------------------------------------------------------------------------
;-- VARIABLES
;----------------------------------------------------------------------------
;-- Direccion base de la memoria de caracteres
BASE: DEFW $3C00
DFCC: DEFW $4000 ;-- Direccion actual del cursor
ATT: DEFB $39 ;-- Attributos por defecto: papel blanco, tinta negra
MASK: DEFB $0 ;-- Mascara de atributos
;---------------------------------------------------------------------------
;-- PRINT1: Imprimir un caracter en la posicion actual
;-
;-- ENTRADAS:
;-- * A: Caracter a imprimir
;---------------------------------------------------------------------------
PRINT1:
ld l, a ;-- L = Caracter a imprimir
ld h, 0 ;-- H = 0
;-- Multiplicar por 8 el caracter
;-- Cada caracter esta formado por 8 bytes
;-- HL = HL = 0000 0000 CCCC CCCC
add hl,hl ;-- HL = 2*HL. Desplazar un bit a la izquierda
;-- HL = 0000 000C CCCC CCC0
add hl,hl ;-- HL = 0000 00CC CCCC CC00
add hl,hl ;-- HL = 0000 0CCC CCCC C000
;-- Obtener direccion del caracter: Base + car * 8
;-- DE = Direccion base de los caracteres
ld DE, (BASE)
add hl, de ;-- HL: Direccion del caracter
;-- DE= Direccion de la memoria de video donde colocar el caracter
ld DE, (DFCC)
;-- Imprimir el caracter fila por fila
ld B, 8 ;-- Numero de bytes (8 bytes por caracter)
next_row:
ld a, (HL) ;-- Leer byte del caracter
ld (de), a ;-- Escribir byte en memoria de video
inc hl
inc d ;-- Apuntar a la siguiente scanline en la meoria de video
djnz next_row
;-- DE= Direccion del atributo
ld a, d ;-- A = 010TTSSS
rrca ;-- A = S010TTSS
rrca ;-- A = SS010TTS
rrca ;-- A = SSS010TT
dec a ;-- ?
and 3 ;-- 3 = 00000011
;-- A = 000000TT
or $58 ;-- 58 = 01011000
;-- A = 010110TT
ld d,a
ld hl,(ATT) ;-- H=Mascara, L=Atributo
;-- Leer anterior atributo
ld a, (de)
;-- Construir el nuevo atributo
xor l
and h
xor l
;-- Reemplazar atributo
ld (de), a
;- Poner en DFCC la siguiente posicion de impresion
ld HL, DFCC
inc (HL)
ret nz
inc HL
ld a, (hl)
add a,8
ld (hl),a
ret
end $8000
Esta rutina es muy interesante porque la podemos usar para imprimir caracteres definidos por el usuario