Espamática
Ensamblador Z80RetroZX Spectrum

La pantalla del ZX Spectrum

Distribución de la pantalla

En este capítulo vamos a hablar sobre la distribución de la pantalla del ZX Spectrum.

Tabla de contenidos

Área gráfica del ZX Spectrum

La pantalla del ZX Spectrum tiene una resolución de 256*192 píxeles, o lo que es lo mismo 32 columnas y 192 scanlines. Con cada byte podemos configurar 8 píxeles, los que están a uno tendrán el color de la tinta (INK) y los que están a cero el color del fondo (PAPER); 256 píxeles / 8 píxeles por byte = 32 columnas. El área gráfica ocupa 6.144 bytes ($1800) y comienza en la dirección de memoria 16.384 ($4000).

Una de las particularidades del ZX Spectrum es la forma en la que se distribuye el área gráfica. Cada carácter se compone de 64 píxeles, o lo que es lo mismo 8×8. A cada una de las líneas del carácter se la denomina scanline. Si pintamos en el primer scanline de un carácter y luego sumamos 32 a la posición de memoria, la lógica nos dice que nos situamos en el siguiente scanline del carácter, pero no es así ya que en realidad nos estamos situando en el primer scanline del carácter situado justo debajo del que estamos.

Si pintamos la posición $4000, primer byte de la pantalla, y luego queremos pintar en el primer scanline de la línea 8 de la pantalla, la lógica dice que solo habría que sumar 8 veces 32 ($20) a la posición $4000, por lo que el primer scanline del primer carácter de la línea 8 estaría en la posición $4100. Pero esto no es así ya que la posción $4100 corresponde con el segundo scanline del primer carácter de la pantalla.

Este comportamiento es debido a que la pantalla del ZX Spectrum está dividida en tercios. Cada uno de los tercios tiene 8 líneas y cada una de las líneas 8 scanlines. El primer tercio va desde la posición de memoria $4000 hasta la $47FF, el segundo tercio desde la $4800 hasta la $4FFF y el tercer tercio desde la $5000 hasta la $57FF.

Aunque en principio esto resulte extraño, cuando se ve la composición que toman los bytes a la hora de conformar una posición de memoria, todo cobra sentido. La composición de los bytes es la siguiente:

010T TSSS LLLC CCCC

Donde 010 es siempre fijo, TT representa el tercio (de 0 a 2), SSS representa el scanline dentro del carácter (de 0 a 7), LLL representa la línea dentro del tercio (de 0 a 7) y CCCCC representa la columna (de 0 a 31).

Si nos fijamos en la línea, de 0 a 23, los bits 0, 1 y 3 siempre indican la línea dentro del tercio, y los bits 4 y 5 el tercio, por lo que TTLLL es equivalente a la línea en coordenadas de carácter (de 0 a 23).

Línea 0:  0000 0000     Línea 0 del tercio 0
Línea 7:  0000 0111     Línea 7 del tercio 0
Línea 8:  0000 1000     Línea 0 del tercio 1
Línea 15: 0000 1111     Línea 7 del tercio 1
Línea 16: 0001 0000     Línea 0 del tercio 2
Línea 23: 0001 0111     Línea 7 del tercio 2

Para ver todo lo expuesto, vamos a realizar un sencillo ejemplo que rellena los bytes de la primera columna con un sencillo patrón $AA (1001 1001).

ASM
org  $8000
ld   hl, $4000    ; HL = dirección de inicio de la VideoRAM
ld   bc, $20      ; BC = valor a sumar para pasar al siguiente carácter
ld   a, $08       ; A = para dibujar el primer scanline del primer tercio
loop:
ld   (hl), $aa    ; Carga en pantalla el patrón 10011001
add  hl, bc       ; Avanza HL a la siguiente línea
dec  a            ; Decrementa A
jr   nz, loop     ; Bucle hasta que A llegue a 0
ret               ; Vuelve al Basic
end  $8000

Si ejecutamos el programa, dibuja el patrón $AA (10101010) en el primer scanline de las 8 primeras líneas.

Para ver la distribución de la pantalla del ZX Spectrum, solo hay que ir cambiado la línea LD A, $08 e ir cargando múltiplos de $08; $08, $10, $18, $20, $28, $30, $38, $40, $48…

Hasta que no lleguemos a $48, no se verá como se pinta el primer scanline de las 8 líneas del segundo tercio.

Área de atributos del ZX Spectrum

A continuación del área gráfica (píxeles) encontramos el área de atributos, que va desde la dirección 22.528 ($5800) hasta la dirección 23.295 ($5AFF), con una longitud de 768 ($300) bytes.

Cada byte del área de atributos establece los atributos de un carácter (8×8 píxeles) siendo este el motivo del famoso attribute clash.

El formato de cada byte del área del atributos es el siguiente:

FBPPPIII

Donde F es FLASH, B = BRIGHT, P = PAPER e I = INK.

En el caso del área de atributos la distribución sí es lineal, por lo que si estamos en el byte de los atributos de la columna 0, fila 0 de la pantalla y sumamos 32 nos situamos en los atributos de la columna 0, fila 1.

La composición de las direcciones de memoria del área de atributos es la siguiente:

0101 10TT LLLC CCCC

Donde 0101 10 es siempre fijo, TT representa el tercio (de 0 a 2), LLL representa la línea dentro del tercio (de 0 a 7) y CCCCC representa la columna (de 0 a 31). Una vez más TTLLL es equivalente a la línea en coordenadas de carácter (de 0 a 23).

Rutinas

A partir de aquí, vamos a ir viendo rutinas que os pueden ayudar a la hora de trabajar con la pantalla del ZX Spectrum. Tened en cuenta que estas rutinas no controlan si las coordenadas están fuera del área de la pantalla.

Dirección de memoria desde coordenada de carácter

La rutina CharCoord2PointerHR, toma unas coordenadas de carácter y retorna la dirección de memoria correspondiente.

ASM
; ----------------------------------------------------------------
; CharCoord2PointerHR: Obtiene la dirección de memoria 
; correspondiente a las coordenadas de carácter especificadas. 
; 
; Entrada: B -> Coordenada Y (0 a 23).
;          C -> Coordenada X (0 a 31).
; 
; Salida:  HL -> Dirección de memoria que corresponde a las
;               coordenadas. 010T TSSS LLLC CCCC. 
;
; Altera el valor de los registros AF y HL.
; ----------------------------------------------------------------- 
CharCoord2PointerHR:
ld	 a, b      ; A = línea. Bits 0, 1 y 2 línea. Bits 3 y 4 tercio.
and  $18       ; Se queda con el tercio 0001 1000. 
               ; Scanline siempre a 0.
or 	 $40       ; Agrega la parte fija 0100 0000.
ld 	 h, a      ; H = A. Parte alta de la dirección calculada.
ld 	 a, b      ; A = línea.
and  $07       ; Se queda con la línea dentro del tercio.
rrca
rrca
rrca           ; Rota tres veces para pasar valor a bits 5, 6 y 7.
or 	 c         ; Agrega la columna.
ld 	 l, a      ; L = A. Parte baja de la dirección calculada.
ret

Para probar esta rutina podemos ir pasándole posiciones y luego cargando un patrón en la dirección devuelta y así ver donde dibuja.

ASM
org $8000

ld   b, $10                   ; Carga en B la línea.
ld   c, $10                   ; Carga en C la columna.
call CharCoord2PointerHR      ; Calcula la posición de memoria.
ld   (hl), $ff                ; Activa todos los píxeles en la
                              ; posición de memoria.

inc  b                        ; Incrementa la línea.
inc  c                        ; Incrementa la columna.
call CharCoord2PointerHR      ; Calcula la posición de memoria.
ld   (hl), $ff                ; Activa todos los píxeles en la
                              ; posición de memoria.

inc  b                        ; Incrementa la línea.
dec  c                        ; Decrementa la columna.
call CharCoord2PointerHR      ; Calcula la posición de memoria.
ld   (hl), $ff                ; Activa todos los píxeles en la
                              ; posición de memoria.

ret

end	 $8000

Coordenada de carácter desde dirección de memoria

La rutina PointerHR2CharCoord, toma una dirección de memoria y retorna las coordenadas de carácter correspondientes.

ASM
; ---------------------------------------------------------------- 
; PointerHR2CharCoord: Obtiene las coordenadas de carácter
; correspondientes a la dirección de memoria especificada.
;
; Entrada: HL -> Puntero a memoria. 010T TSSS LLLC CCCC.
;
; Salida:  B -> Coordenada Y (0 a 23).
;          C -> Coordenada X (0 a 31).
;
; Altera el valor de los registros AF y BC.  
; ---------------------------------------------------------------- 
PointerHR2CharCoord: 
ld   a, h     ; A = parte alta de la dirección de memoria. 010T TSSS.
and  $18      ; Se queda con el tercio. Bits 4 y 5 de la
              ; coordenada Y. Scanline siempre a 0, son coordenadas
              ; de carácter.
ld   b, a     ; B = A.

ld   a, l     ; A = parte baja de la dirección de memoria. LLLC CCCC.
and  $e0      ; Se queda con la línea dentro del tercio.
rrca
rrca
rrca          ; Rota tres veces para pasar valor a bits 0, 1 y 3.
or   b        ; Agrega los bits 4 y 5.
ld   b, a     ; B = A. Coordenada Y calculada.

ld   a, l     ; A = parte baja de la dirección de memoria. LLLC CCCC.
and  $1f      ; Se queda con la columna.
ld   c, a     ; C = A. Coordenada X calculada.

ret

Dirección de memoria de la siguiente línea

La rutina PointerHRNextLine, toma una dirección de memoria y retorna la dirección de memoria correspondiente a la línea siguiente.

ASM
; ----------------------------------------------------------------
; PointerHRNextLine: obtiene la dirección de memoria
; correspondiente a la siguiente línea.
;
; Entrada: HL -> Dirección actual. 010T TSSS LLLC CCCC.
;
; Salida:  HL -> Dirección de la línea siguiente.
;               010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerHRNextLine:
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
add  a, $20   ; Añade una línea (LLLC CCCC + 0010 0000).
ld   l, a     ; L = A.
ret  nc       ; Si no hay acarreo, la línea sigue entre 0 y 7, sale.

; Hay acarreo, hay que cambiar el tercio de la pantalla.
ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
add  a, $08   ; Añade un tercio (010T TSSS + 0000 1000).
ld   h, a     ; H = A.

ret

Dirección de memoria de la línea anterior

La rutina PointerHRPreviousLine, toma una dirección de memoria y retorna la dirección de memoria correspondiente a la línea anterior.

ASM
; ----------------------------------------------------------------
; PointerHRPreviousLine: obtiene la dirección de memoria
; correspondiente a la línea anterior de carácter.
;
; Entrada: HL -> Dirección actual. 010T TSSS LLLC CCCC.
;
; Salida:  HL -> Dirección de la línea anterior.
;                010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerHRPreviousLine:
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
and  $e0      ; Se queda con la parte de la línea (1110 0000).
jr   z, PointerHRPreviousLine_continue   ; Si línea es 0,
                                       ; cambio de tercio.

; No hay cambio de tercio.
ld   a, l     ; A = parte baja de la dirección, LLLC CCCC.
sub  $20      ; Resta una línea (LLLC CCCC - 0010 0000).
ld   l, a     ; L = A. Dirección calculada.

ret

PointerHRPreviousLine_continue:
; Hay que cambiar el tercio.
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
or   $e0      ; Pone la línea a 7 (LLLC CCCC or 1110 0000).
ld   l, a     ; L = A. Parte baja de la dirección calculada.

ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
sub  $08      ; Resta uno al tercio (010T TSSS - 0000 1000).
ld   h, a     ; H = A. Parte baja de la dirección calculada.

ret
; ----------------------------------------------------------------
; PointerHRPreviousLine: obtiene la dirección de memoria
; correspondiente a la línea anterior de carácter.
;
; Entrada: HL -> Dirección actual. 010T TSSS LLLC CCCC.
;
; Salida:  HL -> Dirección de la línea anterior.
;                010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerHRPreviousLine:
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
and  $e0      ; Se queda con la parte de la línea (1110 0000).
jr   z, PointerHRPreviousLine_continue ; Si línea es 0,
                                       ; cambio de tercio.

; No hay cambio de tercio.
ld   a, l     ; A = parte baja de la dirección, LLLC CCCC.
sub  $20      ; Resta una línea (LLLC CCCC - 0010 0000).
ld   l, a     ; L = A. Dirección calculada.

ret

PointerHRPreviousLine_continue:
; Hay que cambiar el tercio.
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
or   $e0      ; Pone la línea a 7 (LLLC CCCC or 1110 0000).
ld   l, a     ; L = A. Parte baja de la dirección calculada.

ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
sub  $08      ; Resta uno al tercio (010T TSSS - 0000 1000).
ld   h, a     ; H = A. Parte baja de la dirección calculada.

ret

Siguiente scanline

La rutina PointerHRNextScanLine, toma una dirección de memoria y devuelve la del siguiente scanline.

ASM
; ----------------------------------------------------------------
; PointerHRNextScanLine: obtiene la dirección de memoria
; correspondiente al siguiente scanline.
;
; Entrada: HL -> dirección actual. 010T TSSS LLLC CCCC.
;
; Salida:  HL -> dirección del siguiente scanline.
;                010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerHRNextScanLine:
ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
and  $07      ; Se queda con el scanline.
cp   $07      ; Comprueba si el scanline es 7.
jr   z, PointerHRNextScanLine_continue ; Es 7, cambio de línea.

; El scanline no es 7.
inc  h       ; Aumenta en 1 el scanline y sale.

ret

PointerHRNextScanLine_continue:
; Hay que cambiar la línea.
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
add  a, $20   ; Añade una línea (LLLC CCCC + 0010 0000).
ld   l, a     ; L = A.
ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
jr   nc, PointerHRNextScanLine_end ; Si no hay acarreo, salta
                                   ; para terminar el cálculo.

; Hay acarreo, hay que cambiar el tercio.
add  a, $08   ; Añade uno al tercio (010T TSSS + 0000 1000).

PointerHRNextScanLine_end:
and  $f8      ; Se queda con la parte fija y el tercio.
              ; Deja el scanline a 0.
ld   h, a     ; H = A. Dirección calculada.

ret

Para probar la rutina, vamos a utilizar un pequeño programa para ver como dibuja. Al contrario de lo que vimos en el primer ejemplo, ahora si se pinta seguido, un scanline seguido del que va justo detrás.

ASM
org  $8000

ld   hl, $4000    ; HL = dirección de inicio de la VideoRAM.
ld   b, $08       ; B = para dibujar 8 scanlines.

loop:
ld   (hl), $aa    ; Carga en pantalla el patrón $AA.
call PointerHRNextScanLine  ; Avanza al siguiente scanline.
djnz loop         ; Bucle hasta que B llegue a 0.

ret

end	 $8000

Si ejecutamos el programa, dibuja el patrón $AA en el primer carácter. El primer ejemplo lo hacía en el primer scanline de las 8 primeras líneas.

Al igual que hicimos en el primer ejemplo, podemos ir cargando múltiplos de $08 en B para ver como va dibujando el programa; $08, $10, $18, $20, $28, $30, $38, $40, $48…

Scanline anterior

La rutina PointerHRPreviousScanLine, toma una dirección de memoria y retorna la dirección de memoria correspondiente al scanline anterior.

ASM
; ----------------------------------------------------------------
; PointerHRPreviousScanLine: obtiene la dirección de memoria
; correspondiente al scanline anterior.
;
; Entrada: HL -> dirección actual. 010T TSSS LLLC CCCC.
;
; Salida:  HL -> Dirección del scanline anterior.
;                010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerHRPreviousScanLine:
ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
and  $07      ; Se queda con el scanline.
or   a        ; Comprueba si está a 0.
jr   z, PointerHRPreviousScanLine_continue  ; Si es 0, cambiar línea.

; No hay cambio de línea.
dec  h        ; Decrementa el scanline y sale.

ret

PointerHRPreviousScanLine_continue:
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
and  $e0      ; Se queda con la parte de la línea.
or   a        ; Comprueba si está a cero.
jr   z, PointerHRPreviousScanLine_change  ; Si es 0, cambiar tercio.

; No hay que cambiar tercio.
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
sub  $20      ; Resta un línea, (LLLC CCCC - 0010 0000).
ld   l, a     ; L = A.
jr   PointerHRPreviousScanLine_end  ; Salta a fin de cálculo.

PointerHRPreviousScanLine_change:
; Hay que cambiar de tercio.
ld   a, l     ; A = parte baja de la dirección. LLLC CCCC.
or   $e0      ; Pone la línea en 7.
ld   l, a     ; L = A.

ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
sub  $08      ; Resta uno al tercio.
ld   h, a     ; H = A.

PointerHRPreviousScanLine_end:
ld   a, h     ; A = parte alta de la dirección. 010T TSSS.
and  $F8      ; Se queda con la parte fija y el tercio.
or   $07      ; Pone el scanline a 7.
ld   h, a     ; H = A.

ret

Y para poder probar la rutina, otro sencillo programa.

ASM
org  $8000

ld   hl, $57ff    ; HL = dirección de fin de la VideoRAM.
ld   b, $08       ; B = para dibujar 8 scanlines.

loop:
ld   (hl), $aa    ; Carga en pantalla el patrón $AA.
call PointerHRPreviousScanLine  ; Retrocede al scanline anterior.
djnz loop         ; Bucle hasta que B llegue a 0.

ret

end	 $8000

Dirección de atributo desde coordenadas de carácter

La rutina CharCoord2PointerAttr toma unas coordenadas de carácter y obtiene la dirección de memoria de atributo correspondiente.

ASM
; ----------------------------------------------------------------
; CharCoord2PointerAttr: toma unas coordenadas de carácter y obtiene
; la dirección de memoria del atributo correspondiente.
;
; Entrada: B -> coordenada Y
;          C -> coordenada X
;
; Salida:  HL -> Dirección de memoria del atributo.
;                0101 10TT LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
CharCoord2PointerAttr:
ld   a, b    ; Carga la línea en A
and  $18     ; Se queda con los bits del tercio
rrca
rrca
rrca         ; Los pasa a los bits 0 y 1
or   $58     ; Añade los bits fijos de la parte alta de la dirección
ld   h, a    ; H = 0101 10TT
ld   a, b    ; Carga la línea en A
and  $07     ; Se queda con los bits de la línea
rrca
rrca
rrca         ; Los pasa a los bits 5, 6, y 7
or   c       ; Añade los bits de la columna
ld   l, a    ; L = LLLC CCCC

ret          ; HL = 0101 10TT LLLC CCCC (dirección del atributo)

Las redes sociales pueden ser maravillosas; NathanielSalis me hizo ver que en esta rutina nos podemos ahorrar bytes y ciclos de reloj prescindiendo de tres rotaciones. El código quedaría de la siguiente manera:

ASM
; ----------------------------------------------------------------
; CharCoord2PointerAttr: toma unas coordenadas de carácter y obtiene
; la dirección de memoria del atributo correspondiente.
;
; Entrada: B -> coordenada Y
;          C -> coordenada X
;
; Salida:  HL -> Dirección de memoria del atributo.
;                0101 10TT LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
CharCoord2PointerAttr:
ld   a, b    ; Carga la línea en A
; Antes de quedarnos con los bits del tercio, realizamos las rotaciones
rrca
rrca         ; Pasa los bits del tercio a los bits 0 y 1
rrca         ; y los de la línea a los bits 5, 6 y 7
ld   l, a    ; Carga el resultado en L
and  $03     ; A = bits del tercio
or   $58     ; Añade los bits fijos de la parte alta de la dirección
ld   h, a    ; H = 0101 10TT
ld   a, l    ; A = línea en bits 5, 6 y 7 y tercio en bits 0 y 1
and  $e0     ; Se queda con los bits de la línea
or   c       ; Añade los bits de la columna
ld   l, a    ; L = LLLC CCCC

ret          ; HL = 0101 10TT LLLC CCCC (dirección del atributo)

La primera versión de la rutina ocupa 18 bytes y tarda 75 ciclos en ejecutarse, mientras que la segunda ocupa 16 bytes y tarda 67 ciclos en ejecutarse, de manera que nos ahorramos dos bytes y 8 ciclos.

Coordenadas de carácter desde dirección de atributo

La rutina PointerAttr2CharCoord toma una dirección de memoria de un atributo y retorna las coordenadas de carácter correspondientes.

ASM
; ----------------------------------------------------------------
; PointerAttr2CharCoord: toma una dirección de memoria de atributo 
; y devuelve las coordenadas de carácter correspondientes.
;
; Entrada:  HL -> Dirección de memoria del atributo.
;                0101 10TT LLLC CCCC.
;
; Salida:   B -> coordenada Y
;           C -> coordenada X
;
; Altera el valor de los registros AF y BC.
; ---------------------------------------------------------------- 
PointerAttr2CharCoord:
ld   a, h    ; Carga la parte alta de la dirección en A
and  $03     ; Se queda con los bits del tercio
rlca
rlca
rlca         ; Los pasa a los bits 3 y 4
ld   b, a    ; B = 000TT000
ld   a, l    ; Carga la parte baja de la dirección en A
and  $e0     ; Se queda con los bits de la línea
rlca
rlca
rlca         ; Los pasa a los bits 0, 1 y 2
or   b       ; Añade el tercio
ld   b, a    ; B = TTLLL (coordenada Y)
ld   a, l    ; Carga la parte baja de la dirección en A
and  $1f     ; Se queda con los bits de la columna
ld   c, a    ; C = 000CCCCC (coordenada X)

ret          ; BC = coordenadas de carácter

Dirección de atributo desde dirección de área gráfica

La rutina PointerHR2PointerAttr toma una dirección de memoria del área gráfica y retorna la dirección de memoria de atributo correspondiente.

ASM
; ----------------------------------------------------------------
; PointerHR2PointerAttr: toma una dirección de memoria del área gráfica 
; y devuelve la dirección de memoria del atributo correspondiente.
;
; Entrada: HL -> Dirección de memoria del área gráfica.
;                010T TSSS LLLC CCCC.
;
; Salida:  HL -> Dirección de memoria del atributo.
;                0101 10TT LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerHR2PointerAttr:
ld   a, h    ; Carga en A la parte alta de la dirección
and  $18     ; Se queda con los bits del tercio
rrca
rrca
rrca         ; Los pasa a los bits 0 y 1
or   $58     ; Añade la parte fija
ld   h, a    ; A = 010110TT

ret          ; HL = dirección de memoria del atributo

Dirección de área gráfica desde dirección de atributo

La rutina PointerAttr2PointerHR toma una dirección de memoria de atributo y devuelve la dirección de memoria del área gráfica correspondiente al scanline 0.

ASM
; ----------------------------------------------------------------
; PointerAttr2PointerHR: toma una dirección de memoria de atributo
; y devuelve la dirección de memoria del área gráfica correspondiente
; al primer scanline.
;
; Entrada: HL -> Dirección de memoria del atributo.
;                0101 10TT LLLC CCCC.
;
; Salida:  HL -> Dirección de memoria del área gráfica.
;                010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- 
PointerAttr2PointerHR:
ld   a, h    ; Carga en A la parte alta de la dirección
and  $03     ; Se queda con los bits del tercio
rlca
rlca
rlca         ; Los pasa a los bits 3 y 4
or   $40     ; Añade la parte fija
ld   h, a    ; A = 010TT000

ret          ; HL = dirección del primer scanline

Conclusión

Todo lo que he puesto aquí, lo he aprendido siguiendo el curso de ensamblador que se encuentra en speccy.org y leyendo el libro Código Máquina del ZX-Spectrum de Jesús Alonso Cano.

Las rutinas aquí expuestas son los ejercicios que he realizado para practicar lo aprendido.

Un comentario en «La pantalla del ZX Spectrum»

  • La visualizacion en una pantalla PAL estaria sujeta a la correccion gamma y por ello los valores sin brillo parecerian mas claros. Cada modelo de ZX Spectrum uso tensiones diferentes para los colores, por lo que los valores aqui mostrados son solo indicativos.

    Respuesta

Deja una respuesta

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

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

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

ACEPTAR
Aviso de cookies

Descubre más desde Espamática

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo