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