Ir al contenido principal

Capítulo 3: La pantalla del ZX Spectrum

Lo fundamental en un videojuego es mostrar en la pantalla de la televisión los escenarios y los personajes que formaran parte del juego. Antes de empezar a dibujar tenemos que entender cómo funciona el Spectrum para que le podamos indicar cómo pintar algo en la pantalla.

El Spectrum gestiona la pantalla como una matriz de direcciones de memoria donde por cada byte que apunta una dirección mostraremos unos pixeles, y con los atributos daremos color a los pixeles y al fondo.

Dibujar en Pantalla

La pantalla, como decíamos, es una matriz de 24 filas x 32 columnas, que corresponde al direccionamiento de memoria que va desde la dirección 16384, en hexadecimal $4000, hasta la dirección 22527 cuyo valor en hexadecimal es $5AFF.

Pongamos ahora el valor 16384 en binario, esto lo podemos obtener con la propia calculadora de Windows: 

0100 0000 0000 0000 (16384)

0101 0111 1111 1111 (22527)

Esto ahora así, nos dice poco o nada, pero vamos a seguir avanzando. Ahora nos vamos al interprete de Basic del Spectrum y con el comando POKE, que sirve para poner un valor en una dirección de memoria, vamos a pintar en pantalla el byte completo direccionado por la dirección 16384, para esto ejecutamos la siguiente línea de código: 

10 POKE 16384, 255
RUN

Nos muestra el siguiente resultado:
 


Tal como esperábamos, se pinta en pantalla los 8 pixeles que identifican las 8 posiciones a 1 del byte direccionado por la dirección 16384, cuyo valor en binario es 1111 1111 y en decimal es 255. Si quisiéramos pintar solo el primer pixel de los 8 que se direcciona con la dirección 16384, hubiéramos puesto el valor en decimal 128 que corresponde al valor en binario 1000 0000. Para comprobarlo ejecutamos en nuestro Spectrum lo siguiente: 

10 POKE 16384,128
RUN

Con el siguiente resultado: 



Ahora, vamos a pintar el último pixel de la primera fila, para esto tenemos que identificar la dirección del último byte de la primera fila. Esto lo calculamos sumando 31 a la dirección 16384, ya que esta dirección representa la posición 0 de la fila, por lo que la posición 31 sería la dirección 16415. Entonces si queremos pintar el ultimo pixel, el byte sería 0000 0001 y su valor decimal es 1. 

10 POKE 16415,1
RUN



Si nos fijamos, la dirección que apunta la primera y última columna de la primera fila son: 

0100 0000 0000 0000 (16384)

0100 0000 0001 1111 (16415)

Con los últimos 5 bits (en negrita) se direccionan las 32 columnas (2^5) de la posición 0 (00000) a la 31 (11111).

La dirección por la que comienza la primera columna de la siguiente fila en un principio debería ser la 16416 (16384+32). Vamos a ejecutar el siguiente programa para mostrar el primer pixel de la primera fila y el primer pixel de la segunda fila: 

10 POKE 16384, 128
20 POKE 16416, 128
RUN



Se puede observar en la imagen que en lugar de aparecer los dos puntos juntos hay un hueco entre ellos, este hueco es de 7 bits, por lo que para poder pintar los pixeles juntos habría que sumar 256(2^8) a 16384, que nos daría un resultado de 16640, que es la posición en pantalla de la línea inferior y contigua al pixel de la fila superior. Lo comprobamos ejecutando el siguiente programa:

10 POKE 16384,128
20 POKE 16640,128
RUN



En este caso sí que ha pintado los dos pixeles juntos. Vamos a ver los valores en binario de las 8 primeras filas contiguas: 

0100 0000 0000 0000 (16384)
0100 0001 0000 0000 (16640)
0100 0010 0000 0000 (16896)
0100 0011 0000 0000 (17152)
0100 0100 0000 0000 (17408)
0100 0101 0000 0000 (17664)
0100 0110 0000 0000 (17920)
0100 0111 0000 0000 (18176)

Si una dirección contiene 16 bits, el bit menos significativo, el de más a la derecha, es el 0 y el más significativo, el de más a la izquierda, es el 15, se puede observar que los bits 10, 9 y 8 identifican la fila en la que se encuentra el pixel dentro de una celda de 8x8 pixels:
 
000 (0)
001 (1)
010 (2)
011 (3)
100 (4)
101 (5)
110 (6)
111 (7)

Los bits que están subrayados indican en qué fila de la sección nos encontramos, una sección de pantalla se compone de 8 filas. La pantalla del Spectrum está divida en tres secciones:
  • Primera sección: de la dirección 16384 ($4000) a 18431 ($47FF) donde se encuentran las filas 0 a 7: 
    • 16384: 0100 0000 0000 0000
    • 18431: 0100 0111 1111 1111
  • Segunda sección: de la dirección 18432 ($4800) a 20479 ($4FFF), donde se encuentran las filas de 8 a 15:
    • 18432: 0100 1000 0000 0000
    • 20479: 0100 1111 1111 1111
  • Tercera sección: de la dirección 20480 ($5000) a 22527 ($57FF), donde se encuentran las filas 16 a 31
    • 20480: 0101 0000 0000 0000
    • 22527: 0101 0111 1111 1111
Si observamos las direcciones en binario para identificar en qué sección de la pantalla nos queremos situar se manejan los bits 12 y 11:

00 (0) : Primera sección de pantalla.
01 (1) : Segunda sección de pantalla.
10 (2) : Tercera sección de pantalla. 

Los 8 últimos bits desde 0 al 7 representan los pixeles de una fila entera (32 columnas x 8 bits=256=2^8.

También, se puede observar que los bits 15, 14 y 13 siempre están al mismo valor, 010, por lo que si ponemos todo los demás bits a 0 nos daría el valor 16384 que es la primera dirección de pantalla: 

0100 0000 0000 0000 (16384)

Esto puede parecer algo enrevesado, pero para entenderlo un poco mejor vamos a poner el siguiente ejemplo: Queremos pintar el primer pixel en la fila 20 columna 16, recordamos que la pantalla del Spectrum se compone de 24 filas x 32 columnas y la forma que tenemos para posicionarnos en la pantalla es con las posiciones de 0 a 23 (filas) y de 0 a 31 (columnas). 

Partimos que nuestra dirección tiene que empezar por 010, por lo que nuestro primer valor siempre va a ser 16384. Para identificar a qué sección pertenece la fila 20 dividimos este valor entre 8 y nos quedamos con el valor entero del resultado, 20:8=2,5, al ser 2 la parte entera del resultado, esto nos indica que la fila está situada en la tercera sección, porque como hemos visto antes, la tercera sección se identificaba con el valor en binario 10 (valor 2 en decimal) de los bits 12 y 11, entonces sumamos lo siguiente: 

0100 0000 0000 0000 (16384)
                                                +
0001 0000 0000 0000 (4096)
-----------------------------------
0101 0000 0000 0000 (20480)

En la segunda sección tenemos que identificar la fila en concreto, cogemos el resultado de 2 y lo multiplicamos por 8 = 16, la razón de multiplicarlo por 8 es porque una sección tiene 8 filas, y se lo restamos a la fila 20, dándonos el resultado de 4 (20-16=4) que multiplicamos a 32 para obtener la posición de memoria de la fila (4*32=128).

0000 0000 1000 0000 (128)

Ahora se lo sumamos a 20480

0101 0000 0000 0000 (20480)
                                                +
0000 0000 1000 0000 (128)
-----------------------------------
0101 0000 1000 0000 (20608)

Ya solo nos queda sumar la columna para obtener la dirección, en este caso la 16, 20608+16=20624. 

Se puede dar el caso que queramos pintar una línea vertical que parte de esta esté en una sección y la otra parte en la siguiente. Por ejemplo, vamos a pintar una línea de 16 pixeles, que empiece en la fila 15 y continúe en la 16, siendo la columna la 15.
 
Siguiendo el cálculo realizado en el ejemplo anterior, empezamos calculando la dirección de la memoria de la fila 15: 
  • Se calcula la sección: 15/8=1,875-->1 (valor entero del resultado)--> Sección 2 -->0100 1000 0000 0000 (18432)
  • Se calcula la fila: 1x8=8-->15-8=7-->7x32=224-->18432+224=18656
  • Se obtiene la dirección de memoria sumando la columna 15: 18656+15=18671
Ahora calculamos la dirección de memoria de la fila 16: 
  • Sección: 16/8=2-->2 (valor entero del resultado)-->Sección 3 --> 0101 0000 0000 0000 (20480)
  • Fila: 2x8=16-->16-16=0-->0x32=0-->20480+0=20480
  • Dirección de memoria: se suma a 20480 la columna 15-->20480+15=20495
Para comprobarlo realizamos el siguiente programa en Basic: 



Si lo ejecutamos obtenemos la línea vertical que atraviesa dos secciones de la pantalla: 



Por el momento no nos hemos metido con el lenguaje en ensamblador para realizar todas estas acciones, aunque sé que ya tienes ganas, pero tienes que entender que para aprender el funcionamiento de la pantalla es mejor explicarlo en Basic ya que en ensamblador es más complicado y más lioso, ya habrá tiempo, pero por el momento no quiero perderte, aunque confió en ti y sé que no me vas a abandonar siguiendo fiel hasta el final.

Atributos en pantalla

Hemos visto como pintar pixeles en pantalla pero solo de color negro, por lo que ha llegado el momento de meterle color al asunto. Para esto tenemos los atributos, que también son posiciones de memoria que referencian una celda de la matriz 24x32 a la cual podemos ponerle un fondo, un color de tinta y otros atributos. 

En este caso los atributos no se aplican a los pixeles, sino a cada una de las 24x32 celdas, es decir, que si queremos dar un color de fondo o de tinta, no aplica a un pixel en concreto sino que aplica a los 8x8 pixeles de la celda referenciada. Por lo tanto son 768 bytes de posiciones de memoria que vamos a utilizar, desde la posición 22528 ($5800) hasta la dirección 23295 ($5AFF), y a cada uno de ellas le podemos dar la siguiente configuración: 

Bit:         7        6        5        4        3        2        1        0
            Flash    Brillo   |---- Paper ----|      |------ Ink------|

Esta parte es la más fácil de entender, pero con el siguiente ejemplo espero dejarlo más claro todavía. Anteriormente calculamos la dirección de memoria que correspondía a la fila 20 y columna 16 de la pantalla era la 20624. Si ejecutamos la siguiente instrucción en Basic, nos pintará un punto de color negro: 

10 POKE 20624,128
RUN

Pero si queremos que ese punto sea de color azúl, hay que cambiar el atributo de la celda 20x16 donde se encuentra ese punto. Para esto vamos a tener en cuenta la paleta de colores del Spectrum: 

Decimal      Binario        Color
0                000            negro
1                001            Azul
2                010            Rojo
3                011            Magenta
4                100            Verde
5                101            Cian
6                110            Amarillo
7                111            Blanco

Ahora calculamos la dirección de memoria del atributo que le corresponde al pixel que hemos pintado en pantalla:

(20x32)+16+22528=23184

Una vez calculada la posición de memoria del atributo, calculamos el valor que le vamos a asignar: 

Bit :         7        6        5        4        3        2        1        0
               0        0        1        1        1        0        0        1

Es decir, que no queremos flash (0), ni brillo (0), el fondo es blanco (111) y la tinta de color azul (001):
 
El valor binario  0011 1001 en decimal es 57. 

Por lo que para pintar el pixel de color azul ejecutamos el siguiente programa:

10 POKE 20624,128
20 POKE 23184,57
RUN



Ahora vamos a indicar que el fondo sea de color amarillo, es decir, que nos quedaría un pixel azul dentro de una celda de tamaño 8x8 pixeles de color amarillo. Para esto el valor que le tenemos que dar al atributo es: 

Bit :         7        6        5        4        3        2        1        0
               0        0        1        1        0        0        0        1

El valor binario 0011 0001 en decimal es 49. Por lo que ahora ejecutamos el siguiente programa en Basic: 

10 POKE 20624,128
20 POKE 23184,49
RUN



Como podemos observar, todos los pixeles que pintemos en la celda 20x16 el color de la tinta será azul y tendrá el fondo de color amarillo. 
Podríamos dibujar en la posición de memoria contigua de la matriz un pixel con un color de tinta y fondo distinto, por ejemplo, fondo azul y color de tinta amarillo:
 
10 POKE 20624,128
20 POKE 23184,49
30 POKE 20625,128
40 POKE 20185,9
RUN




Comentarios

  1. Estupendo trabajo para abrir boca hacia el código máquina. En relación con la pantalla del Spectrum hace algunos años escribí una utilidad de ingeniería inversa a la que llamé ZXDRAW y que nos permite generar código ZX BASIC a medida que vamos dibujando en una pantalla simulada de Spectrum, con esta herramienta cruzada (corre en Windows x86) podemos comprender el método de dibujo en Básico y también acelerar nuestros trabajos de gorma drástica. La web de esta utilidad pueden encontrarla tecleandola en Google odirectamente o buscando mi proyecto ZXOPENSOURCE para Spectrum.

    Saludos!
    Rafa Lome

    ResponderEliminar
    Respuestas
    1. Les dejo enlace a la web oficial del programa ZXDRAW:

      https://calentamientoglobalacelerado.net/ZXdraw/

      Eliminar

Publicar un comentario

Entradas populares de este blog

El botiquín del Spectrum: Componentes de repuesto

Debido a la avanzada edad de nuestros Spectrum, hay que estar prevenidos ante cualquier imprevisto que pueda surgir en forma de avería. Como hemos podido ver en alguna de las reparaciones que he publicado en este blog, me ha hecho falta una serie de componentes electrónicos, que en algún caso no los tenía, y he tenido que esperar a conseguirlos yendo a una tienda de electrónica o comprándolos por internet.  Para no encontrarnos en esta situación, he identificado cuales son las averías más frecuentes y qué componentes harían falta para poner de nuevo en marcha a nuestro Spectrum.  CPU La CPU que nos podemos encontrar en el ZX Spectrum puede ser Zilog Z80A de 4MHz o NEC D780-C, ambas totalmente compatibles. Aunque el cristal de cuarzo para generar una señal de reloj estable es de 14MHz, realmente la señal de reloj que utiliza la CPU es la marcada por el pin 32 (CLK) de la ULA. Esta señal tiene una frecuenca de 3,5MHZ, que es el resultado de dividir por 4 la señal de relo...

Cargar juegos con la App PlayZX (ZX81, ZX Spectrum, +2A/B y +3)

Existen varias alternativas para cargar juegos de forma casi instantánea en formatos .TAP y .TZX, como DivIDE, Dandanator, etc... A los más nostálgicos que nos sigue gustando cargar desde cinta y esperar esos minutos mientras contemplamos el screen de turno, tenemos que buscar otras alternativas que sean parecidas para así dar descanso a las cintas de casete que tan buenos momentos nos dieron, y de esta forma proporcionarles ese merecido retiro en una estantería junto a sus viejas compañeras donde podamos contemplarlas y admirar sus carátulas. La alternativa más popular es TZXduino, que junto a una tarjeta de memoria sd podemos reproducir los juegos como si los cargásemos desde un casete. Esta opción nos obliga a tener otro dispositivo, y dada la situación, por un poco más de lo que puede valer casi sería mejor opción comprarse un Divide, aunque en concepto son dispositivos muy distintos.  Otra opción sería utilizar la aplicación para teléfonos móviles Android PlayZX . En la web de...

Mi primer Amstrad CPC 6128: Puesta a punto y cómo sacarle provecho en la actualidad

El Amstrad CPC era el ordenador que siempre quise tener de niño. Recuerdo que, por el año 1986, en el centro comercial Alcampo de mi barrio tenían una especie de "corner" donde se exhibían los ordenadores de Amstrad que se podían tocar y hacer cosas con ellos, una idea muy brillante para aquella época, ya que no era un método muy habitual para captar compradores, y sin embargo, hoy en día se sigue aplicando y funcionando. Al pillarme cerca del instituto nos acercábamos a la hora del recreo y nos poníamos a hacer nuestros pinitos en Basic, de esta forma surgió mi gusto y admiración por este potente ordenador. Sin embargo, al final, en mi casa optamos por comprar el ZX Spectrum +2A, por varias razones, el precio era casi la mitad, eso sí, sin incluir monitor, en el instituto aprendíamos Basic en ordenadores Sinclair, como el de 48K o el QL, y lo más importante, casi todos mis amigos tenían ordenadores Spectrum. Todo esto influyó en la decisión final, viendo como se alejaba la o...