cursos:ensamblador:gfx1_vram

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:gfx1_vram [04-11-2010 23:00] – [Videomemoria: Área de atributos] sromerocursos:ensamblador:gfx1_vram [21-01-2024 18:43] (actual) – [Efectos sobre la imagen y los atributos] sromero
Línea 1: Línea 1:
- 
 ====== Gráficos (I): la videomemoria del Spectrum ====== ====== Gráficos (I): la videomemoria del Spectrum ======
  
Línea 9: Línea 8:
  Para comprender el funcionamiento de la videomemoria del Spectrum debemos empezar por comprender cómo el monitor o TV CRT genera las imágenes.  Para comprender el funcionamiento de la videomemoria del Spectrum debemos empezar por comprender cómo el monitor o TV CRT genera las imágenes.
  
- Los monitores/televisiones CRT (de Cathode Ray Tube, o Tubo de Rayos Catódicos), que incluye tanto a los televisores como a los monitores estándar que no sean de tecnología TFT/LED/PLASMA, funcionan mediante un "bombardeo" de electrones que excitan los elementos fosforescentes de pantalla. + Los monitores/televisiones CRT (de Cathode Ray Tube, o Tubo de Rayos Catódicos), que incluye tanto a los televisores como a los monitores estándar que no sean de tecnología TFT/LED/PLASMA, funcionan mediante un "bombardeo" de electrones que excitan los elementos fosforescentes de pantalla.
  
  Simplificando el proceso, podría decirse que el monitor o pantalla es una //matriz de elementos fosforescentes// (los //píxeles//) protegidos y delimitados por una //máscara// (que determina la "posición" de los mismos) y el CRT un //cañón de electrones// capaz de "iluminar" el pixel al que apunta. Este pixel se mantiene "excitado" y por tanto "encendido" un tiempo limitado, ya que se va apagando progresivamente cuando el haz de electrones deja de excitarlo. Esto implica que hay que volver a bombardear dicho punto de nuevo para que se encienda durante otro período de tiempo. Realizando esta operación suficientes veces por segundo (50 veces por segundo en sistemas PAL y 60 en sistemas NTSC), dará la sensación óptica de que el pixel no se apaga nunca.  Simplificando el proceso, podría decirse que el monitor o pantalla es una //matriz de elementos fosforescentes// (los //píxeles//) protegidos y delimitados por una //máscara// (que determina la "posición" de los mismos) y el CRT un //cañón de electrones// capaz de "iluminar" el pixel al que apunta. Este pixel se mantiene "excitado" y por tanto "encendido" un tiempo limitado, ya que se va apagando progresivamente cuando el haz de electrones deja de excitarlo. Esto implica que hay que volver a bombardear dicho punto de nuevo para que se encienda durante otro período de tiempo. Realizando esta operación suficientes veces por segundo (50 veces por segundo en sistemas PAL y 60 en sistemas NTSC), dará la sensación óptica de que el pixel no se apaga nunca.
Línea 30: Línea 29:
  La ULA tiene definidos los colores del Spectrum con unas componentes de color concretas que podemos ver aproximadamente en la siguiente tabla y en la imagen donde se representan:  La ULA tiene definidos los colores del Spectrum con unas componentes de color concretas que podemos ver aproximadamente en la siguiente tabla y en la imagen donde se representan:
  
 +|< 50% >|
 ^ Valor ^ Color ^ Componentes RGB ^ ^ Valor ^ Color ^ Componentes RGB ^
 | 0 | Negro | (0, 0, 0 ) | | 0 | Negro | (0, 0, 0 ) |
Línea 49: Línea 49:
  
 \\  \\ 
-{{ cursos:ensamblador:colores_zx.png | Gama de colores del Spectrum }}+{{ cursos:ensamblador:colores_zx.png?580 | Gama de colores del Spectrum }}
 ;#; ;#;
 //La gama de colores del Spectrum// //La gama de colores del Spectrum//
Línea 59: Línea 59:
  Como ya vimos en el capítulo dedicado a las interrupciones, el haz de electrones de una pantalla CRT comienza su recorrido en la esquina superior izquierda del monitor y avanza horizontalmente hacia a la derecha retrazando lo que se conoce como un "scanline" (una línea horizontal). Al llegar a la derecha del monitor y tras haber trazado todos los píxeles de la primera línea, se desactiva el bombardeo de electrones y se produce un retorno a la parte izquierda de la pantalla y un descenso al scanline inferior. Al llegar aquí, mediante la sincronización con una señal HSYNC monitor-dispositivo, se "activa" de nuevo el trazado de imagen para redibujar el nuevo scanline con la información que le suministra el dispositivo que está conectado al monitor.  Como ya vimos en el capítulo dedicado a las interrupciones, el haz de electrones de una pantalla CRT comienza su recorrido en la esquina superior izquierda del monitor y avanza horizontalmente hacia a la derecha retrazando lo que se conoce como un "scanline" (una línea horizontal). Al llegar a la derecha del monitor y tras haber trazado todos los píxeles de la primera línea, se desactiva el bombardeo de electrones y se produce un retorno a la parte izquierda de la pantalla y un descenso al scanline inferior. Al llegar aquí, mediante la sincronización con una señal HSYNC monitor-dispositivo, se "activa" de nuevo el trazado de imagen para redibujar el nuevo scanline con la información que le suministra el dispositivo que está conectado al monitor.
  
- El haz de electrones traza pues, scanline a scanline, toda la pantalla hasta llegar a la parte inferior derecha, momento en el que el haz de electrones vuelve a la parte superior izquierda dejando de bombardear electrones durante el retorno, sincronizándose con el dispositivo al que esté conectado (la ULA y el modulador de vídeo del Spectrum en este caso) mediante una señal VSYNC. + El haz de electrones traza pues, scanline a scanline, toda la pantalla hasta llegar a la parte inferior derecha, momento en el que el haz de electrones vuelve a la parte superior izquierda dejando de bombardear electrones durante el retorno, sincronizándose con el dispositivo al que esté conectado (la ULA y el modulador de vídeo del Spectrum en este caso) mediante una señal VSYNC.
  
 \\  \\ 
Línea 65: Línea 65:
 ;#; ;#;
 //Proceso de retrazado de la imagen// //Proceso de retrazado de la imagen//
-;#;\\ +;#; 
 +\\ 
  
  Este proceso se repite continuamente (a razón de 50 ó 60 veces por segundo según el sistema de televisión de nuestra región) y no se puede interrumpir ni variar (ni el tiempo de avance de la señal de televisión en horizontal ni el tiempo total que se tarda en retrazar un cuadro.  Este proceso se repite continuamente (a razón de 50 ó 60 veces por segundo según el sistema de televisión de nuestra región) y no se puede interrumpir ni variar (ni el tiempo de avance de la señal de televisión en horizontal ni el tiempo total que se tarda en retrazar un cuadro.
Línea 78: Línea 79:
 ===== La videomemoria del Spectrum ===== ===== La videomemoria del Spectrum =====
  
- Cuando comenzamos nuestro curso de ensamblador vimos la organización del mapa de memoria del Spectrum, con la ROM mapeada entre $0000 y $3FFFF, y los 16 o 48KB de memoria a continuación de la misma. A partir de la dirección de memoria $4000 y hasta $7FFF nos encontramos un área de memoria etiquetada como "videoram" o "videomemoria"+ Cuando comenzamos nuestro curso de ensamblador vimos la organización del mapa de memoria del Spectrum, con la ROM mapeada entre $0000 y $3fffF, y los 16 o 48KB de memoria a continuación de la misma. A partir de la dirección de memoria $4000 y hasta $7fff nos encontramos un área de memoria etiquetada como "videoram" o "videomemoria"
- +
  Este área de aprox. 7 KB de memoria es donde podemos encontrar la representación digital de la imagen que estamos viendo en el monitor y que la ULA lee regularmente para poder generar la señal de vídeo que requiere el retrazar la imagen.  Este área de aprox. 7 KB de memoria es donde podemos encontrar la representación digital de la imagen que estamos viendo en el monitor y que la ULA lee regularmente para poder generar la señal de vídeo que requiere el retrazar la imagen.
  
Línea 86: Línea 87:
 ;#; ;#;
 //La videoram en el mapa de memoria del Spectrum// //La videoram en el mapa de memoria del Spectrum//
-;#;\\ +;#; 
 +\\ 
  
  Las rutinas de la ROM o de BASIC que dibujan puntos, líneas, rectángulos o caracteres de texto, lo que realmente hacen internamente es escribir datos en posiciones concretas y calculadas de la videoram ya que estos datos escritos se convertirán en píxeles en el monitor cuando la ULA los recoja en su proceso de envío de datos al monitor y éste los dibuje en la pantalla.  Las rutinas de la ROM o de BASIC que dibujan puntos, líneas, rectángulos o caracteres de texto, lo que realmente hacen internamente es escribir datos en posiciones concretas y calculadas de la videoram ya que estos datos escritos se convertirán en píxeles en el monitor cuando la ULA los recoja en su proceso de envío de datos al monitor y éste los dibuje en la pantalla.
Línea 129: Línea 131:
 ; Ejemplo de escritura de un grafico con forma de A ; Ejemplo de escritura de un grafico con forma de A
  
-  ORG 50000+    ORG 50000
  
-  LD HL, 18514       ; Scanline 0 en Y=96 +    ld hl, 18514           ; Scanline 0 en Y=96 
-  LD A, 60           ; 00111100b +    ld a, 60               ; 00111100b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 18770       ; Scanline 1 en Y=97 +    ld hl, 18770           ; Scanline 1 en Y=97 
-  LD A, 66           ; 01000010b +    ld a, 66               ; 01000010b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 19026       ; Scanline 2 en Y=98 +    ld hl, 19026           ; Scanline 2 en Y=98 
-  LD A, 66           ; 01000010b +    ld a, 66               ; 01000010b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 19282       ; Scanline 3 en Y=99 +    ld hl, 19282           ; Scanline 3 en Y=99 
-  LD A, 126          ; 01111110b +    ld a, 126              ; 01111110b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 19538       ; Scanline 4 en Y=100 +    ld hl, 19538           ; Scanline 4 en Y=100 
-  LD A, 66           ; 01000001b +    ld a, 66               ; 01000001b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 19794       ; Scanline 5 en Y=101 +    ld hl, 19794           ; Scanline 5 en Y=101 
-  LD A, 66           ; 01000001b +    ld a, 66               ; 01000001b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 20050       ; Scanline 6 en Y=102 +    ld hl, 20050           ; Scanline 6 en Y=102 
-  LD A66           01000001b +    ld a%01000001        También se puede usar binario 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  LD HL, 20306       ; Scanline 7 en Y=103 +    ld hl, 20306           ; Scanline 7 en Y=103 
-  LD A, 0            ; 00000000b +    ld a, 0                ; 00000000b 
-  LD (HL), A         ; Escribir+    ld (hl), a             ; Escribir
  
-  RET +    ret 
-END 50000+ 
 +    END 50000
 </code> </code>
  
Línea 170: Línea 173:
  
 \\  \\ 
-{{ cursos:ensamblador:letra_a.png | Trazando una letra A en pantalla }}+{{ cursos:ensamblador:letra_a.png?616 | Trazando una letra A en pantalla }}
 \\  \\ 
  
Línea 188: Línea 191:
  
 \\  \\ 
-  * **El área de imagen**: Es el área de memoria que va desde $4000 (16384) hasta $57FF (22527). Este área de memoria de 6 KB almacena la información gráfica de 256x192 píxeles, donde cada byte (de 8 bits) define el estado de 8 píxeles (en cada bit del byte se tiene el estado de un pixel, con 1=activo, 0=no activo), de forma que se puede codificar cada línea de 256 pixeles con 256/8=32 bytes. Utilizando 32 bytes por línea, podemos almacenar el estado de una pantalla completa con 32*192 = 6144 bytes = 6 KB de memoria. Por ejemplo, la celdilla de memoria 16384 contiene el estado de los 8 primeros píxeles de la línea 0 de la pantalla, desde (0,0) a (7,0).+  * **El área de imagen**: Es el área de memoria que va desde $4000 (16384) hasta $57ff (22527). Este área de memoria de 6 KB almacena la información gráfica de 256x192 píxeles, donde cada byte (de 8 bits) define el estado de 8 píxeles (en cada bit del byte se tiene el estado de un pixel, con 1=activo, 0=no activo), de forma que se puede codificar cada línea de 256 pixeles con 256/8=32 bytes. Utilizando 32 bytes por línea, podemos almacenar el estado de una pantalla completa con 32*192 = 6144 bytes = 6 KB de memoria. Por ejemplo, la celdilla de memoria 16384 contiene el estado de los 8 primeros píxeles de la línea 0 de la pantalla, desde (0,0) a (7,0).
  
-  * **El área de atributos**: Es el área de memoria comprendida entre $5800 (22528) y $5AFF (23295). Cada uno de estos 768 bytes se denomina **atributo** y almacena los colores de pixel activo (tinta) y no activo (papel) de un bloque de 8x8 de la pantalla. Por ejemplo, la celdilla de memoria 22528 almacena el atributo de color del bloque (0,0) que se corresponde con los 64 píxeles desde las posiciones de pantalla (0,0) hasta (7,7).+  * **El área de atributos**: Es el área de memoria comprendida entre $5800 (22528) y $5aff (23295). Cada uno de estos 768 bytes se denomina **atributo** y almacena los colores de pixel activo (tinta) y no activo (papel) de un bloque de 8x8 de la pantalla. Por ejemplo, la celdilla de memoria 22528 almacena el atributo de color del bloque (0,0) que se corresponde con los 64 píxeles desde las posiciones de pantalla (0,0) hasta (7,7).
 \\  \\ 
  
Línea 198: Línea 201:
  
  En la siguiente imagen podemos ver un ejemplo simplificado de cómo se produce la generación de la imagen como "superposición" de la información gráfica en alta resolución y la información de color en baja resolución:  En la siguiente imagen podemos ver un ejemplo simplificado de cómo se produce la generación de la imagen como "superposición" de la información gráfica en alta resolución y la información de color en baja resolución:
- 
  
 \\  \\ 
Línea 223: Línea 225:
 <code basic> <code basic>
 10 BORDER 1: PAPER 1: INK 7: CLS 10 BORDER 1: PAPER 1: INK 7: CLS
-20 FOR = 10 TO 70 STEP 10 : CIRCLE 128, 96, R : NEXT R+20 FOR = 10 TO 70 STEP 10 : CIRCLE 128, 96, R : NEXT R
 30 PAUSE 0 30 PAUSE 0
 40 INK 2 : PLOT 30, 30 : DRAW 220, 120 40 INK 2 : PLOT 30, 30 : DRAW 220, 120
Línea 231: Línea 233:
  
 \\  \\ 
-{{ cursos:ensamblador:cclash_01.png | Círculos concéntricos }}+{{ cursos:ensamblador:cclash_01.png?600 | Círculos concéntricos }}
 \\  \\ 
  
- A continuación pulsamos una tecla y se ejecuta el "INK 2 + PLOT + DRAWque traza una línea diagonal roja. Como en una misma celdilla de 8x8 no pueden haber 2 colores de tinta diferentes, cada pixel rojo que dibuja la rutina DRAW afecta a los 8x8 píxeles del recuadro al que corresponde. Cada nuevo pixel dibujado modifica los atributos de su correspondiente bloque en baja resolución, por lo que se alteran también los colores de los círculos allá donde coincidan con la línea:+ A continuación pulsamos una tecla y se ejecuta el ''INK 2'' ''PLOT'' ''DRAW'' que traza una línea diagonal roja. Como en una misma celdilla de 8x8 no pueden haber 2 colores de tinta diferentes, cada pixel rojo que dibuja la rutina DRAW afecta a los 8x8 píxeles del recuadro al que corresponde. Cada nuevo pixel dibujado modifica los atributos de su correspondiente bloque en baja resolución, por lo que se alteran también los colores de los círculos allá donde coincidan con la línea:
  
 \\  \\ 
-{{ cursos:ensamblador:cclash_02.png | Añadimos una línea }}+{{ cursos:ensamblador:cclash_02.png?592 | Añadimos una línea }}
 \\  \\ 
  
Línea 243: Línea 245:
  
 \\  \\ 
-{{ cursos:ensamblador:cclash_03.png | Attribute clash }}+{{ cursos:ensamblador:cclash_03.png?500 | Attribute clash }}
 \\  \\ 
  
Línea 249: Línea 251:
  
 \\  \\ 
-{{ cursos:ensamblador:altbeast.png | Attribute Clash en Altered Beast }}+{{ cursos:ensamblador:altbeast.png?800 | Attribute Clash en Altered Beast }}
 ;#; ;#;
 //"Ligero" Attribute Clash en Altered Beast// //"Ligero" Attribute Clash en Altered Beast//
Línea 258: Línea 260:
  
 \\  \\ 
-{{ cursos:ensamblador:hate.gif | Area de juego monocolor en H.A.T.E. }}+{{ cursos:ensamblador:hate.gif?512 | Area de juego monocolor en H.A.T.E. }}
 ;#; ;#;
 //Area de juego monocolor en H.A.T.E.// //Area de juego monocolor en H.A.T.E.//
Línea 269: Línea 271:
  
 \\  \\ 
-{{ cursos:ensamblador:buen_disenyo.gif | Excelente diseño gráfico que disimula la colisión de atributos }}+{{ cursos:ensamblador:buen_disenyo.gif?1028 | Excelente diseño gráfico que disimula la colisión de atributos }}
 ;#; ;#;
 //Excelente diseño gráfico que disimula la colisión de atributos// //Excelente diseño gráfico que disimula la colisión de atributos//
Línea 278: Línea 280:
  
  A continuación veremos una descripción más detallada de cada una de estas 2 áreas de memoria.  A continuación veremos una descripción más detallada de cada una de estas 2 áreas de memoria.
- 
  
 \\  \\ 
 ===== Videomemoria: Área de Imagen ===== ===== Videomemoria: Área de Imagen =====
  
- El área de imagen del Spectrum es el bloque de 6144 bytes (6KB) entre 16384 ($4000) y 22527 ($57FF). Cada una de las posiciones de memoria de este área almacenan la información de imagen (estado de los píxeles) de 8 píxeles de pantalla consecutivos, donde un bit a 1 significa que el pixel está encendido y un valor de 0 que está apagado.+ El área de imagen del Spectrum es el bloque de 6144 bytes (6KB) entre 16384 ($4000) y 22527 ($57ff). Cada una de las posiciones de memoria de este área almacenan la información de imagen (estado de los píxeles) de 8 píxeles de pantalla consecutivos, donde un bit a 1 significa que el pixel está encendido y un valor de 0 que está apagado.
  
  Como veremos cuando hablemos del área de atributos, que los píxeles estén a ON o a OFF no implica que la ULA sólo dibuje los píxeles activos. Si el pixel está activo (bit a 1), la ULA lo traza en pantalla utilizando el color de tinta actual que corresponda a ese píxel mientras que un bit a 0 significa que el pixel no está encendido y que la ULA debe de dibujarlo con el color de papel actual.  Como veremos cuando hablemos del área de atributos, que los píxeles estén a ON o a OFF no implica que la ULA sólo dibuje los píxeles activos. Si el pixel está activo (bit a 1), la ULA lo traza en pantalla utilizando el color de tinta actual que corresponda a ese píxel mientras que un bit a 0 significa que el pixel no está encendido y que la ULA debe de dibujarlo con el color de papel actual.
Línea 291: Línea 292:
  Tomemos como ejemplo la primera celdilla de memoria del área de imagen, la $4000 o 16384. Los diferentes bits de esta celdilla de memoria se corresponden con el estado de los píxeles desde (0,0) hasta (7,0):  Tomemos como ejemplo la primera celdilla de memoria del área de imagen, la $4000 o 16384. Los diferentes bits de esta celdilla de memoria se corresponden con el estado de los píxeles desde (0,0) hasta (7,0):
  
 +|< 60% >|
 ^ Bits de (16384) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^ ^ Bits de (16384) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^
 | Pixel | (0,0) | (1,0) | (2,0) | (3,0) | (4,0) | (5,0) | (6,0) | (7,0) | | Pixel | (0,0) | (1,0) | (2,0) | (3,0) | (4,0) | (5,0) | (6,0) | (7,0) |
Línea 312: Línea 314:
  Si avanzamos a la siguiente celdilla de memoria, la $4001 (o 16385), tendremos el estado de los siguientes píxeles de la misma línea horizontal:  Si avanzamos a la siguiente celdilla de memoria, la $4001 (o 16385), tendremos el estado de los siguientes píxeles de la misma línea horizontal:
  
 +|< 60% >|
 ^ Bit de (16385) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^ ^ Bit de (16385) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^
 | Pixel | (8,0) | (9,0) | (10,0) | (11,0) | (12,0) | (13,0) | (14,0) | (15,0) | | Pixel | (8,0) | (9,0) | (10,0) | (11,0) | (12,0) | (13,0) | (14,0) | (15,0) |
Línea 317: Línea 320:
  De nuevo, avanzando 1 byte más en memoria, avanzamos otros 8 píxeles horizontalmente:  De nuevo, avanzando 1 byte más en memoria, avanzamos otros 8 píxeles horizontalmente:
  
 +|< 60% >|
 ^ Bit de (16386) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^ ^ Bit de (16386) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^
 | Pixel | (16,0) | (17,0) | (18,0) | (19,0) | (20,0) | (21,0) | (22,0) | (23,0) | | Pixel | (16,0) | (17,0) | (18,0) | (19,0) | (20,0) | (21,0) | (22,0) | (23,0) |
Línea 322: Línea 326:
  Así, hasta que llegamos al byte número 32 desde 16384, es decir, a la celdilla 16415, donde:  Así, hasta que llegamos al byte número 32 desde 16384, es decir, a la celdilla 16415, donde:
  
 +|< 60% >|
 ^ Bit de (16415) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^ ^ Bit de (16415) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^
 | Pixel | (248,0) | (249,0) | (250,0) | (251,0) | (252,0) | (253,0) | (254,0) | (255,0) | | Pixel | (248,0) | (249,0) | (250,0) | (251,0) | (252,0) | (253,0) | (254,0) | (255,0) |
Línea 329: Línea 334:
 <code basic> <code basic>
 10 CLS 10 CLS
-20 FOR I=0 TO 31 : POKE 16384+I, 170 : NEXT I+20 FOR i=0 TO 31 : POKE 16384+I, 170 : NEXT I
 30 PAUSE 0 30 PAUSE 0
 </code> </code>
Línea 336: Línea 341:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_poke0.png | 32 bytes 170 desde 16384 }}+{{ cursos:ensamblador:gfx1_poke0.png?606 | 32 bytes 170 desde 16384 }}
 \\  \\ 
  
Línea 343: Línea 348:
  Por desgracia, esto no es así, y los 32 bytes a partir de 16416 no hacen referencia a la segunda línea de pantalla sino a la primera línea del segundo "bloque" de caracteres, es decir, a los píxeles desde (0,8) a (255,8), por lo que realmente, los bits de 16416 representan:  Por desgracia, esto no es así, y los 32 bytes a partir de 16416 no hacen referencia a la segunda línea de pantalla sino a la primera línea del segundo "bloque" de caracteres, es decir, a los píxeles desde (0,8) a (255,8), por lo que realmente, los bits de 16416 representan:
  
 +|< 60% >|
 ^ Bit de (16416) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^ ^ Bit de (16416) ^ 7 ^ 6 ^ 5 ^ 4 ^ 3 ^ 2 ^ 1 ^ 0 ^
 | Pixel | (0,8) | (1,8) | (2,8) | (3,8) | (4,8) | (5,8) | (6,8) | (7,8) | | Pixel | (0,8) | (1,8) | (2,8) | (3,8) | (4,8) | (5,8) | (6,8) | (7,8) |
Línea 350: Línea 356:
 <code basic> <code basic>
 10 CLS 10 CLS
-20 FOR I=0 TO 63 : POKE 16384+I, 170 : NEXT I+20 FOR i=0 TO 63 : POKE 16384+I, 170 : NEXT I
 30 PAUSE 0 30 PAUSE 0
 </code> </code>
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_poke1.png | 64 bytes 170 desde 16384 }}+{{ cursos:ensamblador:gfx1_poke1.png?608 | 64 bytes 170 desde 16384 }}
 \\  \\ 
  
Línea 381: Línea 387:
    * (etc...)    * (etc...)
  
- Así hasta el byte 18331 o $47FF (cuando hemos avanzado 32*8*8  = 32 bytes por 8 líneas de cada una de las 8 filas de caracteres), que contiene el estado de los 8 píxeles del bloque de baja resolución (31,7) de la pantalla.+ Así hasta el byte 18331 o $47ff (cuando hemos avanzado 32*8*8  = 32 bytes por 8 líneas de cada una de las 8 filas de caracteres), que contiene el estado de los 8 píxeles del bloque de baja resolución (31,7) de la pantalla.
  
 \\  \\ 
Línea 390: Línea 396:
 \\  \\ 
  
- ¿Qué quiere decir esto? Que los primeros 2KB de videoram (entre $4000 y $47FF) contienen la información de todos los píxeles de los 8 primeros bloques en baja resolución de pantalla, de forma que primero vienen todas las líneas horizontales 0 de cada bloque, luego todas las líneas horizontales 1 de cada bloque, líneas horizontales 2 de cada bloque, etc, hasta que se rellenan las últimas líneas horizontales (líneas 7) de los 8 primeros caracteres. Esto produce que el rellenado de los 2 primeros KB de la videoram rellene un área de pantalla entre (0,0) y (255,63), lo que se conoce como el **primer tercio** de la pantalla.+ ¿Qué quiere decir esto? Que los primeros 2KB de videoram (entre $4000 y $47ff) contienen la información de todos los píxeles de los 8 primeros bloques en baja resolución de pantalla, de forma que primero vienen todas las líneas horizontales 0 de cada bloque, luego todas las líneas horizontales 1 de cada bloque, líneas horizontales 2 de cada bloque, etc, hasta que se rellenan las últimas líneas horizontales (líneas 7) de los 8 primeros caracteres. Esto produce que el rellenado de los 2 primeros KB de la videoram rellene un área de pantalla entre (0,0) y (255,63), lo que se conoce como el **primer tercio** de la pantalla.
  
 \\  \\ 
-  * **Primer tercio**: Los 2 primeros KB de la videoram (de $4000 a $47FF) cubren los datos gráficos de los primeros 64 scanlines de la pantalla (líneas 0 a 7). +  * **Primer tercio**: Los 2 primeros KB de la videoram (de $4000 a $47ff) cubren los datos gráficos de los primeros 64 scanlines de la pantalla (líneas 0 a 7). 
-  * **Segundo tercio**: Los siguientes 2KB de la videoram (de $4800 a $4FFF) cubren los datos gráficos de los siguientes 64 scanlines de la pantalla (líneas 8 a 15). +  * **Segundo tercio**: Los siguientes 2KB de la videoram (de $4800 a $4fff) cubren los datos gráficos de los siguientes 64 scanlines de la pantalla (líneas 8 a 15). 
-  * **Tercer tercio**: Los siguientes 2KB de la videoram (de $5000 a $57FF) cubren los datos gráficos de los últimos 64 scanlines de la pantalla (líneas 16 a 23).+  * **Tercer tercio**: Los siguientes 2KB de la videoram (de $5000 a $57ff) cubren los datos gráficos de los últimos 64 scanlines de la pantalla (líneas 16 a 23).
 \\  \\ 
- +
  Y, resumiendo en un sólo párrafo la organización de cada tercio:  Y, resumiendo en un sólo párrafo la organización de cada tercio:
  
Línea 413: Línea 419:
  Con el programa de ejemplo del apartado //Explorando el área de imagen con un ejemplo// podremos comprobar experimentalmente la organización de la videomemoria y la división de la pantalla en tercios de 8 "caracteres" de 8 scanlines cada uno.  Con el programa de ejemplo del apartado //Explorando el área de imagen con un ejemplo// podremos comprobar experimentalmente la organización de la videomemoria y la división de la pantalla en tercios de 8 "caracteres" de 8 scanlines cada uno.
  
- Mientras tanto, sabiendo que entre $4000 y $57FF (6144 bytes) tenemos el área de imagen de la pantalla, donde cada byte representa el estado de 8 píxeles, podemos realizar la siguiente rutina útil que sirve para rellenar toda la pantalla con un patrón de píxeles determinado (CLS con patrón):+ Mientras tanto, sabiendo que entre $4000 y $57ff (6144 bytes) tenemos el área de imagen de la pantalla, donde cada byte representa el estado de 8 píxeles, podemos realizar la siguiente rutina útil que sirve para rellenar toda la pantalla con un patrón de píxeles determinado (CLS con patrón):
  
 <code z80> <code z80>
Línea 421: Línea 427:
 ;------------------------------------------------------- ;-------------------------------------------------------
 ClearScreen: ClearScreen:
-  PUSH HL +    push hl 
-  PUSH DE +    push de 
-   +    push bc 
-  LD HL, 16384          ; HL = Inicio de la videoram + 
-  LD (HL),            ; Escribimos el patron A en (HL) +    ld hl, 16384          ; HL = Inicio de la videoram 
-  LD DE, 16385          ; Apuntamos DE a 16385 +    ld (hl),            ; Escribimos el patron A en (HL) 
-  LD BC, 192*32-1       ; Copiamos 192*32-1 veces (HL) en (DE) +    ld de, 16385          ; Apuntamos DE a 16385 
-  LDIR                  ; e incrementamos HL y DL. Restamos 1 +    ld bc, 192*32-1       ; Copiamos 192*32-1 veces (HL) en (DE) 
-                        ; porque ya hemos escrito en 16384. +    ldir                  ; e incrementamos HL y DL. Restamos 1 
-  POP DE +                          ; porque ya hemos escrito en 16384. 
-  POP HL + 
-  RET                   +    pop bc 
 +    pop de 
 +    pop hl 
 +    ret
 </code> </code>
  
  De esta forma, podemos llamar a nuestra rutina ClearScreen colocando en A el patron con el que rellenar la pantalla, que puede ser 0 para "limpiarla" o 1 para activar todos los píxeles a 1.  De esta forma, podemos llamar a nuestra rutina ClearScreen colocando en A el patron con el que rellenar la pantalla, que puede ser 0 para "limpiarla" o 1 para activar todos los píxeles a 1.
 +
 + Los ''PUSH'' y ''POP'' están puestos para preservar los valores de los registros, pero podemos quitarlos si vamos a llamar a esta función siempre de forma controlada y no queremos preservarlos (o se pueden preservar antes de llamarla).
 +
  
 \\  \\ 
 ==== Explorando el área de imagen con un ejemplo ==== ==== Explorando el área de imagen con un ejemplo ====
  
- Veamos un sencillo programa (vramtest.asm) que nos va a permitir verificar de forma experimental la teoría sobre la videomemoria que hemos visto en los apartados anteriores. + Veamos un sencillo programa (vramtest.asm) que nos va a permitir verificar de forma experimental la teoría sobre la videomemoria que hemos visto en los apartados anteriores.
  
  Este programa carga HL con el inicio del área de imágen de la videomemoria (16384 o $4000), y escribe bloques de 32 bytes (todo un scanline horizontal) con el valor 255 (11111111b o, lo que es lo mismo, los 8 píxeles de ese bloque activos).  Este programa carga HL con el inicio del área de imágen de la videomemoria (16384 o $4000), y escribe bloques de 32 bytes (todo un scanline horizontal) con el valor 255 (11111111b o, lo que es lo mismo, los 8 píxeles de ese bloque activos).
Línea 455: Línea 467:
   ; Mostrando la organizacion de la videomemoria   ; Mostrando la organizacion de la videomemoria
  
-  ORG 50000+    ORG 50000
  
   ; Pseudocodigo del programa:   ; Pseudocodigo del programa:
-  ; +  ;
   ; Limpiamos la pantalla   ; Limpiamos la pantalla
   ; Apuntamos HL a 16384   ; Apuntamos HL a 16384
Línea 468: Línea 480:
  
 Start: Start:
-  LD A, 0 +    ld a, 0 
-  CALL ClearScreen           ; Borramos la pantalla+    call ClearScreen           ; Borramos la pantalla
  
-  LD HL, 16384               ; HL apunta a la VRAM +    ld hl, 16384               ; HL apunta a la VRAM 
-  LD B, 192                  ; Repetimos para 192 lineas+    ld b, 192                  ; Repetimos para 192 lineas
  
 bucle_192_lineas: bucle_192_lineas:
-  LD D                   ; Nos guardamos el valor de D para el +    ld d                   ; Nos guardamos el valor de D para el 
-                             ; bucle exterior (usaremos B ahora en otro) +                               ; bucle exterior (usaremos B ahora en otro) 
-  LD B, 32                   ; B=32 para el bucle interior+    ld b, 32                   ; B=32 para el bucle interior
  
-                             ; Esperamos que se pulse y libere tecla +    call Wait_For_Key          ; Esperamos que se pulse y libere tecla
-  CALL Wait_For_Keys_Pressed +
-  CALL Wait_For_Keys_Released+
  
-  LD A, 255                  ; 255 = 11111111b = todos los pixeles+    ld a, 255                  ; 255 = 11111111b = todos los pixeles
  
 bucle_32_bytes: bucle_32_bytes:
-  LD (HL),                 ; Almacenamos A en (HL) = 8 pixeles +    ld (hl),                 ; Almacenamos A en (HL) = 8 pixeles 
-  INC HL                     ; siguiente byte (siguientes 8 pix.) +    inc hl                     ; siguiente byte (siguientes 8 pix.) 
-  DJNZ bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline+    djnz bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline
  
-  LD B                   ; Recuperamos el B del bucle exterior +    ld b                   ; Recuperamos el B del bucle exterior
-  +
-  DJNZ bucle_192_lineas      ; Repetir 192 veces +
-   +
-  JP Start                   ; Inicio del programa +
-  +
  
-;----------------------------------------------------------------------- +    djnz bucle_192_lineas      Repetir 192 veces 
-; Esta rutina espera a que haya alguna tecla pulsada para volver. + 
-;----------------------------------------------------------------------- +    jp Start                   Inicio del programa
-Wait_For_Keys_Pressed: +
-  XOR A    +
-  IN A, (254) +
-  OR 224 +
-  INC A +
-  JR Z, Wait_For_Keys_Pressed +
-  RET +
-  +
-;----------------------------------------------------------------------- +
-; Esta rutina espera a que no haya ninguna tecla pulsada para volver. +
-;----------------------------------------------------------------------- +
-Wait_For_Keys_Released: +
-  XOR A +
-  IN A, (254) +
-  OR 224 +
-  INC A +
-  JR NZ, Wait_For_Keys_Released +
-  RET+
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
Línea 524: Línea 511:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ClearScreen: ClearScreen:
-  LD HL, 16384 +    ld hl, 16384 
-  LD (HL), A +    ld (hl), a 
-  LD DE, 16385 +    ld de, 16385 
-  LD BC, 192*32-1 +    ld bc, 192*32-1 
-  LDIR +    ldir 
-  RET+    ret 
 + 
 +    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
Línea 537: Línea 526:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_vramtest01.png | Nuestro programa de ejemplo tras 6 pulsaciones }}+{{ cursos:ensamblador:gfx1_vramtest01.png?608 | Nuestro programa de ejemplo tras 6 pulsaciones }}
  
  Comentemos esta captura de pantalla: como cabría esperar ahora que conocemos la organización del área de imagen de la VRAM, aunque hemos escrito en la memoria de forma lineal (desde 16384 hasta 16384+(32*6)-1), los scanlines en pantalla no son consecutivos, ya que no hemos cubierto los 6 primeros scanlines de la pantalla sino el primer scanline de los 6 primeros bloques 8x8 del primer tercio.  Comentemos esta captura de pantalla: como cabría esperar ahora que conocemos la organización del área de imagen de la VRAM, aunque hemos escrito en la memoria de forma lineal (desde 16384 hasta 16384+(32*6)-1), los scanlines en pantalla no son consecutivos, ya que no hemos cubierto los 6 primeros scanlines de la pantalla sino el primer scanline de los 6 primeros bloques 8x8 del primer tercio.
Línea 544: Línea 533:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_vramtest02.png | Nuestro programa de ejemplo tras muchas pulsaciones }}+{{ cursos:ensamblador:gfx1_vramtest02.png?618 | Nuestro programa de ejemplo tras muchas pulsaciones }}
  
  Recomendamos al lector que continue la ejecución del programa hasta recorrer toda la pantalla y que trate de anticiparse mentalmente acerca de dónde se mostrará la siguiente línea antes de realizar la pulsación de teclado.  Recomendamos al lector que continue la ejecución del programa hasta recorrer toda la pantalla y que trate de anticiparse mentalmente acerca de dónde se mostrará la siguiente línea antes de realizar la pulsación de teclado.
  
- Es probable que la pauta de rellenado de la pantalla de nuestro ejemplo le resulte más que familiar al lector: efectivamente, es el mismo orden de relleno que producen las pantallas de carga de los juegos cargadas a partir de un //LOAD "" SCREEN$//. La carga de pantalla desde cinta con "LOAD "" SCREEN$no es más que la lectura desde cinta de los 6912 bytes de una pantalla completa (6144 bytes de imagen y 768 bytes de atributos) y su almacenamiento lineal en $4000. + Es probable que la pauta de rellenado de la pantalla de nuestro ejemplo le resulte más que familiar al lector: efectivamente, es el mismo orden de relleno que producen las pantallas de carga de los juegos cargadas a partir de un ''LOAD "" SCREEN$''. La carga de pantalla desde cinta con ''LOAD "" SCREEN$'' no es más que la lectura desde cinta de los 6912 bytes de una pantalla completa (6144 bytes de imagen y 768 bytes de atributos) y su almacenamiento lineal en $4000.
  
  La lectura secuencial desde cinta y su escritura lineal en videomemoria resulta en la carga de los datos gráficos en el mismo orden de scanlines en que nuestro programa de ejemplo ha rellenado la pantalla, seguida de la carga de los atributos, que en un rápido avance (sólo 768 bytes a cargar desde cinta) dotaba a la pantalla de carga de su color.  La lectura secuencial desde cinta y su escritura lineal en videomemoria resulta en la carga de los datos gráficos en el mismo orden de scanlines en que nuestro programa de ejemplo ha rellenado la pantalla, seguida de la carga de los atributos, que en un rápido avance (sólo 768 bytes a cargar desde cinta) dotaba a la pantalla de carga de su color.
  
- Del mismo modo, un simple //SAVE "imagen" SCREEN$// //SAVE "imagen" CODE 16384, 6912// toma los 6912 bytes de la videoram y los almacena en cinta. El lector puede acudir al capítulo dedicado a //Rutinas de SAVE y LOAD// para refrescar la información acerca de la carga de datos desde cinta e inclusión de pantallas gráficas completas en sus programas.+ Del mismo modo, un simple ''SAVE "imagen" SCREEN$'' ''SAVE "imagen" CODE 16384, 6912'' toma los 6912 bytes de la videoram y los almacena en cinta. El lector puede acudir al capítulo dedicado a //Rutinas de SAVE y LOAD// para refrescar la información acerca de la carga de datos desde cinta e inclusión de pantallas gráficas completas en sus programas.
  
- Muchos programas comerciales trataban de evitar la carga de la pantalla visible scanline a scanline, para lo que cargaban los datos de SCREEN$ en un área de memoria libre y después transferían rápidamente esta pantalla a videoram con instrucciones LDIR.+ Muchos programas comerciales trataban de evitar la carga de la pantalla visible scanline a scanline, para lo que cargaban los datos de SCREEN$ en un área de memoria libre y después transferían rápidamente esta pantalla a videoram con instrucciones ''LDIR''.
  
- Este concepto, el de Pantalla Virtual, resulta muy interesante: podemos utilizar un área de memoria alta para simular que es la pantalla completa o una zona (la de juego) de la misma. Esto permitía dibujar los sprites y gráficos sobre ella (sin que el jugador viera nada de estos dibujados, puesto que dicha zona de RAM no es videoram), y volcarla regularmente sobre videoram tras un HALT. De esta forma se evita que el jugador pueda ver parpadeos en el dibujado de los sprites o la construcción de la pantalla "a trozos". La utilización de una pantalla virtual implicará el consumo de casi 7KB de memoria para almacenar nuestra "vscreen", por lo que lo normal sería sólo replicar el área de juego (evitando marcadores y demás) si pensamos utilizar esta técnica.+ Este concepto, el de Pantalla Virtual, resulta muy interesante: podemos utilizar un área de memoria alta para simular que es la pantalla completa o una zona (la de juego) de la misma. Esto permitía dibujar los sprites y gráficos sobre ella (sin que el jugador viera nada de estos dibujados, puesto que dicha zona de RAM no es videoram), y volcarla regularmente sobre videoram tras un ''HALT''. De esta forma se evita que el jugador pueda ver parpadeos en el dibujado de los sprites o la construcción de la pantalla "a trozos". La utilización de una pantalla virtual implicará el consumo de casi 7KB de memoria para almacenar nuestra "vscreen", por lo que lo normal sería sólo replicar el área de juego (evitando marcadores y demás) si pensamos utilizar esta técnica.
  
  
Línea 566: Línea 555:
  Esta organización de memoria tiene como objetivo el facilitar las rutinas de impresión de texto, algo que podemos ver en las posiciones de inicio de las diferentes líneas de un mismo carácter:  Esta organización de memoria tiene como objetivo el facilitar las rutinas de impresión de texto, algo que podemos ver en las posiciones de inicio de las diferentes líneas de un mismo carácter:
  
 +|< 50% >|
 ^ Scanline del carácter ^ Dirección de memoria ^ ^ Scanline del carácter ^ Dirección de memoria ^
 | 0 | $4000 | | 0 | $4000 |
Línea 576: Línea 566:
 | 7 | $4700 | | 7 | $4700 |
  
- Tal y como está organizada la videoram, basta con calcular la dirección de inicio del bloque en baja resolución donde queremos trazar un carácter, imprimir los 8 píxeles que forman su scanline (con la escritura de un único byte en videomemoria), y saltar a la siguiente posición de videomemoria donde escribir. Como se puede apreciar en la tabla anterior, este salto a la siguiente línea se realiza con un simple INC del byte alto de la direccion (INC H en el caso de que estemos usando HL para escribir). De esta forma se simplifican las rutinas de trazado de caracteres y UDGs de la ROM.+ Tal y como está organizada la videoram, basta con calcular la dirección de inicio del bloque en baja resolución donde queremos trazar un carácter, imprimir los 8 píxeles que forman su scanline (con la escritura de un único byte en videomemoria), y saltar a la siguiente posición de videomemoria donde escribir. Como se puede apreciar en la tabla anterior, este salto a la siguiente línea se realiza con un simple INC del byte alto de la direccion (''inc h'' en el caso de que estemos usando HL para escribir). De esta forma se simplifican las rutinas de trazado de caracteres y UDGs de la ROM.
  
  Pensemos que los antecesores del ZX Spectrum (ZX80 y ZX81) tenían una videomemoria orientada al texto en baja resolución, y con la visión del software de la época y la potencia de los microprocesadores existentes lo normal era pensar en el Spectrum como un microordenador orientado a programar en BASIC y realizar programas "de gestión", más que pensar en él como una máquina de juegos. En este contexto, potenciar la velocidad de ejecución del trazado de texto era crucial.  Pensemos que los antecesores del ZX Spectrum (ZX80 y ZX81) tenían una videomemoria orientada al texto en baja resolución, y con la visión del software de la época y la potencia de los microprocesadores existentes lo normal era pensar en el Spectrum como un microordenador orientado a programar en BASIC y realizar programas "de gestión", más que pensar en él como una máquina de juegos. En este contexto, potenciar la velocidad de ejecución del trazado de texto era crucial.
Línea 584: Línea 574:
 ===== Videomemoria: Área de atributos ===== ===== Videomemoria: Área de atributos =====
  
- El área de atributos es el bloque de 768 bytes entre $5800 (22528) y $5AFF (23295), ambas celdillas de memoria incluídas. Cada una de las posiciones de memoria de este área almacenan la información de color (color de tinta, color de papel, brillo y flash) de un bloque de 8x8 píxeles en la pantalla.+ El área de atributos es el bloque de 768 bytes entre $5800 (22528) y $5aff (23295), ambas celdillas de memoria incluídas. Cada una de las posiciones de memoria de este área almacenan la información de color (color de tinta, color de papel, brillo y flash) de un bloque de 8x8 píxeles en la pantalla.
  
  El tamaño de 768 bytes de este área viene determinado por la resolución del sistema de color del Spectrum: Hemos dicho que el sistema gráfico dispone de una resolución de 256x192, pero el sistema de color divide la pantalla en bloques de 8x8 píxeles, lo que nos da una resolución de color de 256/8 x 192/8 = 32x24 bloques. Como la información de color de cada bloque se codifica en un único byte, para almacenar la información de color de toda una pantalla se requieren 32 x 24 x 1 = 768 bytes.  El tamaño de 768 bytes de este área viene determinado por la resolución del sistema de color del Spectrum: Hemos dicho que el sistema gráfico dispone de una resolución de 256x192, pero el sistema de color divide la pantalla en bloques de 8x8 píxeles, lo que nos da una resolución de color de 256/8 x 192/8 = 32x24 bloques. Como la información de color de cada bloque se codifica en un único byte, para almacenar la información de color de toda una pantalla se requieren 32 x 24 x 1 = 768 bytes.
  
- Sabemos ya pues que hay una correspondencia directa entre los 32x24 bloques de 8x8 píxeles de la pantalla y cada byte individual del área de atributos, pero ¿cómo se estructura esta información? + Sabemos ya pues que hay una correspondencia directa entre los 32x24 bloques de 8x8 píxeles de la pantalla y cada byte individual del área de atributos, pero ¿cómo se estructura esta información?
  
  La organización lógica del área de atributos es más sencilla y directa que la del área de imagen. Aquí, los 32 primeros bytes del área de atributos se corresponden con los 32 primeros bloques horizontales de la pantalla. Es decir, la celdilla 22528 se corresponde con el bloque (0,0), la 22529 se corresponde con (1,0), la 22530 con (2,0), y así hasta llegar a la celdilla 22559 en (31,0). La siguiente celdilla en memoria, 22560, se corresponde con el siguiente bloque en pantalla, el primero de la segunda línea, (0,1), y así de forma sucesiva.  La organización lógica del área de atributos es más sencilla y directa que la del área de imagen. Aquí, los 32 primeros bytes del área de atributos se corresponden con los 32 primeros bloques horizontales de la pantalla. Es decir, la celdilla 22528 se corresponde con el bloque (0,0), la 22529 se corresponde con (1,0), la 22530 con (2,0), y así hasta llegar a la celdilla 22559 en (31,0). La siguiente celdilla en memoria, 22560, se corresponde con el siguiente bloque en pantalla, el primero de la segunda línea, (0,1), y así de forma sucesiva.
Línea 599: Línea 589:
 \\  \\ 
  
- Se puede decir que el área de atributos es totalmente lineal; consta de 768 bytes que se corresponden de forma consecutiva con el estado de cada bloque y de cada fila horizontal de bloques de pantalla: Los primeros 32 bytes del área se corresponden con la primera fila horizontal de bloques, los siguientes 32 bytes con la segunda, los siguientes 32 bytes con la tercera, hasta los últimos 32 bytes, que se corresponden con los de la línea 23. El byte alojado en la última posición ($5AFF) se corresponde con el atributo del bloque (31,23).+ Se puede decir que el área de atributos es totalmente lineal; consta de 768 bytes que se corresponden de forma consecutiva con el estado de cada bloque y de cada fila horizontal de bloques de pantalla: Los primeros 32 bytes del área se corresponden con la primera fila horizontal de bloques, los siguientes 32 bytes con la segunda, los siguientes 32 bytes con la tercera, hasta los últimos 32 bytes, que se corresponden con los de la línea 23. El byte alojado en la última posición ($5aff) se corresponde con el atributo del bloque (31,23).
  
  A continuación podemos ver una tabla que muestra los inicios y fin de cada línea de atributos en pantalla:  A continuación podemos ver una tabla que muestra los inicios y fin de cada línea de atributos en pantalla:
  
 +|< 50% >|
 ^ Línea ^ Inicio (carácter 0,N) ^ Fin (carácter 31,N) ^ ^ Línea ^ Inicio (carácter 0,N) ^ Fin (carácter 31,N) ^
-| 0 | $5800 | $581F +| 0 | $5800 | $581f 
-| 1 | $5820 | $583F +| 1 | $5820 | $583f 
-| 2 | $5860 | $585F +| 2 | $5860 | $585f 
-| 3 | $5840 | $587F +| 3 | $5840 | $587f 
-| 4 | $5880 | $589F +| 4 | $5880 | $589f 
-| 5 | $58A0 | $58BF +| 5 | $58a0 | $58bf 
-| 6 | $58C0 | $58DF +| 6 | $58c0 | $58df 
-| 7 | $58E0 | $58FF +| 7 | $58e0 | $58ff 
-| 8 | $5900 | $591F +| 8 | $5900 | $591f 
-| 9 | $5920 | $593F +| 9 | $5920 | $593f 
-| 10 | $5940 | $595F +| 10 | $5940 | $595f 
-| 11 | $5960 | $597F +| 11 | $5960 | $597f 
-| 12 | $5980 | $599F +| 12 | $5980 | $599f 
-| 13 | $59A0 | $59BF +| 13 | $59a0 | $59bf 
-| 14 | $59C0 | $59DF +| 14 | $59c0 | $59df 
-| 15 | $59E0 | $59FF +| 15 | $59e0 | $59ff 
-| 16 | $5A00 | $5A1F +| 16 | $5a00 | $5a1f 
-| 17 | $5A20 | $5A3F +| 17 | $5a20 | $5a3f 
-| 18 | $5A40 | $5A5F +| 18 | $5a40 | $5a5f 
-| 19 | $5A60 | $5A7F +| 19 | $5a60 | $5a7f 
-| 20 | $5A80 | $5A9F +| 20 | $5a80 | $5a9f 
-| 21 | $5AA0 | $5ABF +| 21 | $5aa0 | $5abf 
-| 22 | $5AC0 | $5ADF +| 22 | $5ac0 | $5adf 
-| 23 | $5AE0 | $5AFF |+| 23 | $5ae0 | $5aff |
  
  Esta organización del área de atributos es muy sencilla y permite un cálculo muy sencillo de la posición de memoria del atributo de un bloque concreto de pantalla. Es decir, podemos encontrar fácilmente la posición de memoria que almacena el atributo que corresponde a un bloque concreto en baja resolución de pantalla mediante:  Esta organización del área de atributos es muy sencilla y permite un cálculo muy sencillo de la posición de memoria del atributo de un bloque concreto de pantalla. Es decir, podemos encontrar fácilmente la posición de memoria que almacena el atributo que corresponde a un bloque concreto en baja resolución de pantalla mediante:
  
 <code> <code>
- Direccion_Atributo(x_bloque,y_bloque) = 22528 + (y_bloque*32) + x_bloque  + Direccion_Atributo(x_bloque,y_bloque) = 22528 + (y_bloque*32) + x_bloque 
-</code> +</code>
  
  O, con desplazamientos:  O, con desplazamientos:
  
 <code> <code>
- Direccion_Atributo(x_bloque,y_bloque) = 22528 + (y_bloque<<5) + x_bloque  + Direccion_Atributo(x_bloque,y_bloque) = 22528 + (y_bloque<<5) + x_bloque 
-</code> +</code>
  
  Si en vez de una posición de bloque tenemos una posición de pixel, podemos convertirla primero a bloque dividiendo por 8:  Si en vez de una posición de bloque tenemos una posición de pixel, podemos convertirla primero a bloque dividiendo por 8:
Línea 645: Línea 636:
 <code> <code>
  Direccion_Atributo(x_pixel,y_pixel) = 22528 + ((y_pixel/8)*32) + (x_pixel/8)  Direccion_Atributo(x_pixel,y_pixel) = 22528 + ((y_pixel/8)*32) + (x_pixel/8)
-</code> +</code>
  
  Con desplazamientos:  Con desplazamientos:
Línea 651: Línea 642:
 <code> <code>
  Direccion_Atributo(x_pixel,y_pixel) = 22528 + ((y_pixel>>3)<<5) + (x_pixel>>3)  Direccion_Atributo(x_pixel,y_pixel) = 22528 + ((y_pixel>>3)<<5) + (x_pixel>>3)
-</code> +</code>
  
  La información en cada byte de este área se codifica de la siguiente manera:  La información en cada byte de este área se codifica de la siguiente manera:
  
 \\  \\ 
 +|< 40% >|
 ^  Bit  ^  7  ^  6  ^  5 - 4 - 3  ^  2 - 1 - 0  ^ ^  Bit  ^  7  ^  6  ^  5 - 4 - 3  ^  2 - 1 - 0  ^
 |  Valor  |  FLASH  |  BRIGHT  |  PAPER  |  INK  | |  Valor  |  FLASH  |  BRIGHT  |  PAPER  |  INK  |
Línea 669: Línea 661:
  Recordemos que estos colores básicos son:  Recordemos que estos colores básicos son:
  
 +|< 30% >|
 ^ Valor ^ Color ^ ^ Valor ^ Color ^
 | 0 | Negro | | 0 | Negro |
Línea 681: Línea 674:
  A estos colores se les puede activar el bit de brillo para obtener una tonalidad más cercana a la intensidad máxima de dicho color. Examinando de nuevo la captura que veíamos al principio del artículo:  A estos colores se les puede activar el bit de brillo para obtener una tonalidad más cercana a la intensidad máxima de dicho color. Examinando de nuevo la captura que veíamos al principio del artículo:
  
-{{ cursos:ensamblador:colores_zx.png | Los colores del Spectrum }}+{{ cursos:ensamblador:colores_zx.png?580 | Los colores del Spectrum }}
  
  La relación de los colores con su "identificador numérico" está basada en el estado de 4 bits: BRILLO, COMPONENTE_R, COMPONENTE_G y COMPONENTE_B:  La relación de los colores con su "identificador numérico" está basada en el estado de 4 bits: BRILLO, COMPONENTE_R, COMPONENTE_G y COMPONENTE_B:
  
 +|< 60% >|
 ^ Valor ^ Bits "BRILLO R G B" ^ Color ^ Componentes RGB ^ ^ Valor ^ Bits "BRILLO R G B" ^ Color ^ Componentes RGB ^
 | 0 | 0000b | Negro | (0, 0, 0 ) | | 0 | 0000b | Negro | (0, 0, 0 ) |
Línea 728: Línea 722:
  
 <code z80> <code z80>
-  ; Mostrando la organizacion de la videomemoria (atributos) +; Mostrando la organizacion de la videomemoria (atributos) 
- +    ORG 50000
-  ORG 50000+
  
   ; Pseudocodigo del programa:   ; Pseudocodigo del programa:
-  ; +  ;
   ; Borramos la pantalla   ; Borramos la pantalla
   ; Apuntamos HL a 22528   ; Apuntamos HL a 22528
Línea 743: Línea 736:
  
 Start: Start:
-  LD A, 0 +    ld a, 0 
-  CALL ClearScreen           ; Borramos la pantalla+    call ClearScreen           ; Borramos la pantalla
  
-  LD HL, 22528               ; HL apunta a la VRAM +    ld hl, 22528               ; HL apunta a la VRAM 
-  LD B, 24                  ; Repetimos para 192 lineas+    ld b, 24                   ; Repetimos para 192 lineas
  
 bucle_lineas: bucle_lineas:
-  LD D                   ; Nos guardamos el valor de D para el +    ld d                   ; Nos guardamos el valor de D para el 
-                             ; bucle exterior (usaremos B ahora en otro) +                               ; bucle exterior (usaremos B ahora en otro) 
-  LD B, 32                   ; B=32 para el bucle interior+    ld b, 32                   ; B=32 para el bucle interior
  
-                             ; Esperamos que se pulse y libere tecla +    call Wait_For_Key          ; Esperamos que se pulse y libere tecla
-  CALL Wait_For_Keys_Pressed +
-  CALL Wait_For_Keys_Released+
  
-  LD A, (papel)              ; Cogemos el valor del papel +    ld a, (papel)              ; Cogemos el valor del papel 
-  INC A                      ; Lo incrementamos +    inc a                      ; Lo incrementamos 
-  LD (papel),              ; Lo guardamos de nuevo +    ld (papel),              ; Lo guardamos de nuevo 
-  CP 8                       ; Si es == 8 (>7), resetear +    cp 8                       ; Si es == 8 (>7), resetear 
-  JR NZ,no_resetear_papel+    jr nz,no_resetear_papel
  
-  LD A, 255 +    ld a, 255 
-  LD (papel),              ; Lo hemos reseteado: lo guardamos +    ld (papel),              ; Lo hemos reseteado: lo guardamos 
-  XOR A                      ; A=0+    xor a                      ; A=0
  
 no_resetear_papel: no_resetear_papel:
  
-  SLA A                      ; Desplazamos A 3 veces a la izquierda +    sla a                      ; Desplazamos A 3 veces a la izquierda 
-  SLA A                      ; para colocar el valor 0-7 en los bits +    sla a                      ; para colocar el valor 0-7 en los bits 
-  SLA A                      ; donde se debe ubicar PAPER (bits 3-5).+    sla a                      ; donde se debe ubicar PAPER (bits 3-5).
  
 bucle_32_bytes: bucle_32_bytes:
-  LD (HL),                 ; Almacenamos A en (HL) = attrib de 8x8 +    ld (hl),                 ; Almacenamos A en (HL) = attrib de 8x8 
-  INC HL                     ; siguiente byte (siguientes 8x8 pixeles.) +    inc hl                     ; siguiente byte (siguientes 8x8 pixeles.) 
-  DJNZ bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline de bloques+    djnz bucle_32_bytes        ; 32 veces = 32 bytes = 1 scanline de bloques
  
-  LD B                   ; Recuperamos el B del bucle exterior +    ld b                   ; Recuperamos el B del bucle exterior
-  +
-  DJNZ bucle_lineas          ; Repetir 24 veces+
  
-  JP Start                   Inicio del programa+    djnz bucle_lineas          Repetir 24 veces
  
-papel  defb   255            Valor del papel +    jp Start                   Inicio del programa
- +
- +
-;----------------------------------------------------------------------- +
-; Esta rutina espera a que haya alguna tecla pulsada para volver. +
-;----------------------------------------------------------------------- +
-Wait_For_Keys_Pressed: +
-  XOR A    +
-  IN A, (254) +
-  OR 224 +
-  INC A +
-  JR Z, Wait_For_Keys_Pressed +
-  RET +
-  +
- +
-;----------------------------------------------------------------------- +
-; Esta rutina espera a que no haya ninguna tecla pulsada para volver. +
-;----------------------------------------------------------------------- +
-Wait_For_Keys_Released: +
-  XOR A +
-  IN A, (254) +
-  OR 224 +
-  INC A +
-  JR NZ, Wait_For_Keys_Released +
-  RET+
  
 +papel  DEFB   255              ; Valor del papel
  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
Línea 817: Línea 783:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ClearScreen: ClearScreen:
-  LD HL, 16384 +    ld hl, 16384 
-  LD (HL), A +    ld (hl), a 
-  LD DE, 16385 +    ld de, 16385 
-  LD BC, 192*32-1 +    ld bc, 192*32-1 
-  LDIR +    ldir 
-  RET+    ret 
 + 
 +    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
Línea 830: Línea 798:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_attr.png | Explorando el área de atributos }}+{{ cursos:ensamblador:gfx1_attr.png?604 | Explorando el área de atributos }}
 \\  \\ 
  
Línea 838: Línea 806:
  
  La memoria de atributos difiere de la de imagen en cuanto a que es totalmente lineal y que cada byte representa al bloque inmediatamente siguiente. Al llegar a la esquina derecha de la pantalla, el siguiente byte se corresponde con el primero de la siguiente línea.  La memoria de atributos difiere de la de imagen en cuanto a que es totalmente lineal y que cada byte representa al bloque inmediatamente siguiente. Al llegar a la esquina derecha de la pantalla, el siguiente byte se corresponde con el primero de la siguiente línea.
- 
  
  Con esta información, nos podemos crear la siguiente rutina para establecer el valor de atributos de toda la pantalla:  Con esta información, nos podemos crear la siguiente rutina para establecer el valor de atributos de toda la pantalla:
Línea 849: Línea 816:
 ;------------------------------------------------------- ;-------------------------------------------------------
 ClearAttributes: ClearAttributes:
-  PUSH HL +    push hl 
-  PUSH DE +    push de 
-   +    push bc 
-  LD HL, 22528          ; HL = Inicio del area de atributos + 
-  LD (HL),            ; Escribimos el patron A en (HL) +    ld hl, 22528          ; HL = Inicio del area de atributos 
-  LD DE, 22529          ; Apuntamos DE a 22528 +    ld (hl),            ; Escribimos el patron A en (HL) 
-  LD BC, 24*32-1        ; Copiamos 767 veces (HL) en (DE) +    ld de, 22529          ; Apuntamos DE a 22528 
-  LDIR                  ; e incrementamos HL y DL. Restamos 1 +    ld bc, 24*32-1        ; Copiamos 767 veces (HL) en (DE) 
-                        ; porque ya hemos escrito en 22528. +    ldir                  ; e incrementamos HL y DL. Restamos 1 
-  POP DE +                          ; porque ya hemos escrito en 22528. 
-  POP HL +    pop bc 
-  RET                   +    pop de 
 +    pop hl 
 +    ret 
 +</code> 
 + 
 + Dado lo habitual que puede ser llamar a ''ClearScreen'' y ''ClearAttributes'', podemos desarrollar una función ''ClearScreenAttributes'' que realice ambas funciones en una misma llamada: 
 + 
 +<code z80> 
 +;----------------------------------------------------------------------- 
 +; Limpiar la pantalla con el patron de pixeles y atributos indicado. 
 +; Entrada:  H = atributo, L = patron 
 +;----------------------------------------------------------------------- 
 +ClearScreenAttrib: 
 +    push de 
 +    push bc 
 +    push bc 
 + 
 +    ld a, h               ; A = el atributo 
 +    ex af, af             ; Nos guardamos el atributo en A' 
 +    ld a, l               ; Cargamos en A el patron 
 +    ld hl, 16384          ; HL = Inicio del area de imagen 
 +    ld (hl), a            ; Escribimos el valor de A en (HL) 
 +    ld de, 16385          ; Apuntamos DE a 16385 
 +    ld bc, 192*32-1       ; Copiamos 6142 veces (HL) en (DE) 
 +    ldir 
 + 
 +    ex af, af             ; Recuperamos A (atributo) de A' 
 +    inc hl                ; Incrementamos HL y DE 
 +    inc de                ; para entrar en area de atributos 
 +    ld (hl), a            ; Almacenamos el atributo 
 +    ld bc, 24*32-1        ; Ahora copiamos 767 bytes 
 +    ldir 
 + 
 +    pop bc 
 +    pop bc 
 +    pop de 
 +    ret
 </code> </code>
  
Línea 869: Línea 872:
  El área gráfica de 256x192 píxeles está centrada en el centro de la pantalla o monitor, dejando alrededor de ella un marco denominado BORDE. Este borde tiene 64 píxeles en las franjas horizontales y 48 píxeles en las verticales.  El área gráfica de 256x192 píxeles está centrada en el centro de la pantalla o monitor, dejando alrededor de ella un marco denominado BORDE. Este borde tiene 64 píxeles en las franjas horizontales y 48 píxeles en las verticales.
  
- El borde tiene un color único que la ULA utiliza para retrazar todos y cada uno de los píxeles de este marco. Podemos cambiar este color accediendo en el Z80 al puerto de la ULA que controla el borde. + El borde tiene un color único que la ULA utiliza para retrazar todos y cada uno de los píxeles de este marco. Podemos cambiar este color accediendo en el Z80 al puerto de la ULA que controla el borde.
  
- El conocido comando de BASIC "BORDERllama a la rutina de la ROM //BORDER// en $2294, la cual realiza el cambio del color del borde mediante el acceso a la ULA y además actualiza la variable del sistema BORDCR en 23624d.+ El conocido comando de BASIC ''BORDER'' llama a la rutina de la ROM ''BORDER'' en $2294, la cual realiza el cambio del color del borde mediante el acceso a la ULA y además actualiza la variable del sistema ''BORDCR'' en 23624d.
  
- Concretamente, basta con escribir un valor en el rango 0-7 en el puerto $FE (254) para que la ULA utilice ese valor desde ese instante como color del borde. Las típicas líneas "de carga" en el borde que podemos ver durante las rutinas de LOAD y SAVE son cambios del color del borde realizados rápidamente como indicadores de la carga mientras la ULA está dibujando el cuadro actual. Si se cambia el borde con la suficiente rapidez, la ULA cambiará el color con que lo está dibujando cuando todavía no ha acabado la generación del cuadro de imagen actual. El valor 0-7 representa el identificador de color a utilizar de la paleta de 8 colores de la ULA, y este valor lo almacena internamente la ULA (no el Z80), ya que requiere de acceso instantáneo a él durante la generación del vídeo.+ Concretamente, basta con escribir un valor en el rango 0-7 en el puerto $fe (254) para que la ULA utilice ese valor desde ese instante como color del borde. Las típicas líneas "de carga" en el borde que podemos ver durante las rutinas de ''LOAD'' ''SAVE'' son cambios del color del borde realizados rápidamente como indicadores de la carga mientras la ULA está dibujando el cuadro actual. Si se cambia el borde con la suficiente rapidez, la ULA cambiará el color con que lo está dibujando cuando todavía no ha acabado la generación del cuadro de imagen actual. El valor 0-7 representa el identificador de color a utilizar de la paleta de 8 colores de la ULA, y este valor lo almacena internamente la ULA (no el Z80), ya que requiere de acceso instantáneo a él durante la generación del vídeo.
  
- En el capítulo dedicado a los Puertos de Entrada / Salida pudimos ya observar un ejemplo de cambio de color del borde, que ahora vamos a modificar para separar el OUT en una función //SetBorder// propia:+ En el capítulo dedicado a los Puertos de Entrada / Salida pudimos ya observar un ejemplo de cambio de color del borde, que ahora vamos a modificar para separar el OUT en una función ''SetBorder'' propia:
  
 <code z80> <code z80>
 ; Cambio del color del borde al pulsar espacio ; Cambio del color del borde al pulsar espacio
-  ORG 50000 +    ORG 50000 
-  + 
-  LD B, 6              ; 6 iteraciones, color inicial borde+    ld b, 6              ; 6 iteraciones, color inicial borde
  
 start: start:
- +
 bucle: bucle:
-  LD A, $7F            ; Semifila B a ESPACIO +    ld a, $7f            ; Semifila B a ESPACIO 
-  IN A, ($FE)          ; Leemos el puerto +    in a, ($fe)          ; Leemos el puerto 
-  BIT 0,             ; Testeamos el bit 0 (ESPACIO) +    bit 0,             ; Testeamos el bit 0 (ESPACIO) 
-  JR NZ, bucle         ; Si esta a 1 (no pulsado), esperar +    jr nz, bucle         ; Si esta a 1 (no pulsado), esperar 
-  + 
-  LD A             ; A = B +    ld a             ; A = B 
-  CALL SetBorder       ; Cambiamos el color del borde +    call SetBorder       ; Cambiamos el color del borde 
-  + 
-suelta_tecla:          ; Ahora esperamos a que se suelte la tecla +suelta_tecla:            ; Ahora esperamos a que se suelte la tecla 
-  LD A, $7F            ; Semifila B a ESPACIO +    ld a, $7f            ; Semifila B a ESPACIO 
-  IN A, ($FE)          ; Leemos el puerto +    in a, ($fe)          ; Leemos el puerto 
-  BIT 0,             ; Testeamos el bit 0 +    bit 0,             ; Testeamos el bit 0 
-  JR Z, suelta_tecla   ; Saltamos hasta que se suelte +    jr z, suelta_tecla   ; Saltamos hasta que se suelte 
-  + 
-  DJNZ bucle           ; Repetimos "B" veces +    djnz bucle           ; Repetimos "B" veces 
-  LD B, 7 +    ld b, 7 
-  JP start             ; Y repetir +    jp start             ; Y repetir 
- +
 salir: salir:
-  RET +    ret 
- +
 ;------------------------------------------------------------ ;------------------------------------------------------------
 ; SetBorder: Cambio del color del borde al del registro A ; SetBorder: Cambio del color del borde al del registro A
 ;------------------------------------------------------------ ;------------------------------------------------------------
 SetBorder: SetBorder:
-  OUT (254), A +    out ($fe), a 
-  RET+    ret
  
-  END 50000            ; Ejecucion en 50000+    END 50000            ; Ejecucion en 50000
 </code> </code>
  
Línea 920: Línea 923:
  
 \\  \\ 
-{{ cursos:ensamblador:borde.png | Cambio del color del borde }}+{{ cursos:ensamblador:borde.png?640 | Cambio del color del borde }} 
 +\\ 
  
- + Si por algún motivo necesitaramos actualizar la variable del sistema ''BORDCR'' (porque vayamos a llamar a rutinas de la ROM que lo puedan manipular), bastará con modificar SetBorder para que almacene el valor del borde en la posición de memoria (23624) colocando primero el valor 0-7 en la posición de bits de papel y estableciendo la tinta a negro si el brillo está activo:
- Si por algún motivo necesitaramos actualizar la variable del sistema BORDCR (porque vayamos a llamar a rutinas de la ROM que lo puedan manipular), bastará con modificar SetBorder para que almacene el valor del borde en la posición de memoria (23624) colocando primero el valor 0-7 en la posición de bits de PAPEL y estableciendo la tinta a negro si el brillo está activo:+
  
 <code z80> <code z80>
Línea 931: Línea 934:
 ;------------------------------------------------------------ ;------------------------------------------------------------
 SetBorder: SetBorder:
-  OUT (254),           ; Cambiamos el color del borde +    out ($fe),           ; Cambiamos el color del borde 
-  RLCA +    rlca 
-  RLCA +    rlca 
-  RLCA                   ; A = A*8 (colocar en bits PAPER) +    rlca                   ; A = A*8 (colocar en bits PAPER) 
-  BIT 5,               ; Mirar si es un color BRIGHT +    bit 5,               ; Mirar si es un color BRIGHT 
-  JR NZ, SetBorder_fin   ; No es bright -> guardarlo +    jr nz, SetBorder_fin   ; No es bright -> guardarlo 
-                         ; Si es bright +                           ; Si es bright 
-  XOR 7                  ; -> cambiar la tinta a 0 +    xor %00000111          ; -> cambiar la tinta a 0 
-  +
 SetBorder_fin: SetBorder_fin:
-  LD (23624),        ; Salvar el valor en BORDCR +    ld (23624),        ; Salvar el valor en BORDCR 
-   + 
-  RET+    ret
 </code> </code>
  
- Mantener actualizado BORDCR puede ser útil si pretendemos llamar a la rutina de la ROM BEEPER (en $03B65), ya que el puerto que se utiliza para controlar el altavoz es el mismo que el del borde (salvo que se utiliza el bit 4 del valor que se envía con OUT $FE). La rutina BEEPER carga el valor de BORDCR para, además del manipular el bit 4 del puerto, cargar los bits 0, 1 y 2 con el borde actual para que éste no cambie. Si no estuviera almacenado el valor del borde en BORDCR y BEEPER no lo incluyera en los bits 0-2 de su OUT, lo establecería en negro (000) con cada cambio del estado del speaker.+ Mantener actualizado ''BORDCR'' puede ser útil si pretendemos llamar a la rutina de la ROM ''BEEPER'' (en $03b65), ya que el puerto que se utiliza para controlar el altavoz es el mismo que el del borde (salvo que se utiliza el bit 4 del valor que se envía con OUT $fe). La rutina BEEPER carga el valor de ''BORDCR'' para, además del manipular el bit 4 del puerto, cargar los bits 0, 1 y 2 con el borde actual para que éste no cambie. Si no estuviera almacenado el valor del borde en ''BORDCR'' ''BEEPER'' no lo incluyera en los bits 0-2 de su OUT, lo establecería en negro (000) con cada cambio del estado del speaker.
  
  Finalmente, recomendamos al lector que elimine del programa anterior la necesidad de pulsar y soltar una tecla. De esta forma podrá verificar qué sucede cuando se cambia el color del borde mientras la ULA lo está dibujando:  Finalmente, recomendamos al lector que elimine del programa anterior la necesidad de pulsar y soltar una tecla. De esta forma podrá verificar qué sucede cuando se cambia el color del borde mientras la ULA lo está dibujando:
Línea 952: Línea 955:
 <code z80> <code z80>
  ; Cambio del color del borde mientras la ULA dibuja  ; Cambio del color del borde mientras la ULA dibuja
-  ORG 50000 +    ORG 50000 
-  + 
-  LD B, 6              ; 6 iteraciones, color inicial borde+    ld b, 6              ; 6 iteraciones, color inicial borde
  
 start: start:
- +
 bucle: bucle:
-  LD A             ; A = B +    ld a             ; A = B 
-  CALL SetBorder       ; Cambiamos el color del borde +    call SetBorder       ; Cambiamos el color del borde 
-  + 
-  DJNZ bucle           ; Repetimos "B" veces +    djnz bucle           ; Repetimos "B" veces 
-  LD B, 7 +    ld b, 7 
-  JP start             ; Y repetir +    jp start             ; Y repetir 
- +
 salir: salir:
-  RET+    ret 
 + 
 +    END 50000
 </code> </code>
  
Línea 973: Línea 978:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx_border_np.png | Cambiando el borde, sin pausa }}+{{ cursos:ensamblador:gfx_border_np.png?632 | Cambiando el borde, sin pausa }}
 \\  \\ 
  
Línea 979: Línea 984:
  
 <code basic> <code basic>
-10 FOR I=0 TO 7 : BORDER I : NEXT I : GOTO 10+10 FOR i=0 TO 7 : BORDER I : NEXT I : GOTO 10
 </code> </code>
  
Línea 985: Línea 990:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx_border_basic.png | Cambiando el borde, sin pausa }}+{{ cursos:ensamblador:gfx_border_basic.png?628 | Cambiando el borde, sin pausa }}
 \\  \\ 
  
- Cabe destacar que en esta ocasión BASIC es todavía más rápido de lo normal pues la ejecución de BORDER I acaba resultando en la llamada a la función de la ROM "BORDERque apenas tiene 12 instrucciones (parecida a nuestra SetBorder), lo que deja todavía más en evidencia la velocidad de lo que es el intérprete de BASIC en sí.+ Cabe destacar que en esta ocasión BASIC es todavía más rápido de lo normal pues la ejecución de ''BORDER I'' acaba resultando en la llamada a la función de la ROM ''BORDER'' que apenas tiene 12 instrucciones (parecida a nuestra ''SetBorder''), lo que deja todavía más en evidencia la velocidad de lo que es el intérprete de BASIC en sí.
  
-  
 \\  \\ 
-===== El "atributo actual" ATTR-T =====+===== El "atributo actual temporal" ATTR-T =====
  
- Ahora que conocemos el formato de una celdilla de atributo podemos hablar de la variable del sistema ATTR-T (dirección de memoria 23695), la cual almacena el atributo actual que las rutinas de la ROM del Spectrum como nuestra conocida RST 16.+ Ahora que conocemos el formato de una celdilla de atributo podemos hablar de la variable del sistema ''ATTR-T'' (dirección de memoria **$5c8f** o **23695**), la cual almacena el atributo actual temporal que las rutinas de la ROM del Spectrum como nuestra conocida rst 16.
  
- A continuación tenemos un ejemplo que imprime cadenas con diferentes atributos de color. Para ello se ha creado una rutina PrintString basada en imprimir caracteres mediante RST 16, que utiliza el valor de ATTR-T.+ A continuación tenemos un ejemplo que imprime cadenas con diferentes atributos de color. Para ello se ha creado una rutina PrintString basada en imprimir caracteres mediante ''rst 16'', que utiliza el valor de ''ATTR-T''.
  
 <code z80> <code z80>
-  Mostrando la organizacion de la videomemoria (atributos) +Prueba ATTR-P 
- +    ORG 50000
-  ORG 50000 +
- +
-  ; Pseudocodigo del programa: +
-  ;  +
-  ; Borramos la pantalla +
-  ; Apuntamos HL a 22528 +
-  ; Repetimos 24 veces: +
-  ;    Esperamos pulsacion de una tecla +
-  ;    Repetimos 32 veces: +
-  ;       Escribir un valor de PAPEL 0-7 en la direccion apuntada por HL +
-  ;       Incrementar HL+
  
 Start: Start:
  
-  LD A, 1                    ; Borde azul +    ld a, 1                    ; Borde azul 
-  CALL SetBorder +    call BORDER
-  LD A, 0 +
-  CALL ClearScreen           ; Borramos la pantalla +
-  LD A, 8+4                  ; Atributos: rojo sobre azul +
-  CALL ClearAttributes+
  
-  LD HLlinea1 +    ld a8+4 
-  CALL PrintString+    ld (CLS_COLOR),
 +    call CLS
  
-  LD A12                   ; Atributos: verde sobre azul +    ld a56                   ; Negro sobre gris 
-  LD (23695), A+    ld (ATTR_T), a             ; Cambiamos ATTR-T
  
-  LD HLlinea2 +    ld delinea1 
-  CALL PrintString+    call PrintString
  
-  LD A64+2+9              Atributos: magenta sobre cyan + brillo. +    ld a12                   Verde sobre azul 
-  LD (23695), A+    ld (ATTR_T), a             ; Cambiamos ATTR-T
  
-  LD HL, linea2 +    ld de, linea2 
-  CALL PrintString+    call PrintString
  
-  Esperamos que se pulse y libere tecla +    ld a, 64+2+9               Atributos: m + brillo. 
-  CALL Wait_For_Keys_Pressed +    ld (ATTR_T), a             ; Cambiamos ATTR-T
-  CALL Wait_For_Keys_Released+
  
-  RET                      ; Fin del programa+    ld de, linea2 
 +    call PrintString
  
-;----------------------------------------------------------------------- +    call Wait_For_Key          Esperamos que se pulse y libere tecla
-; Esta rutina espera a que haya alguna tecla pulsada para volver. +
-;----------------------------------------------------------------------- +
-Wait_For_Keys_Pressed: +
-  XOR A    +
-  IN A, (254) +
-  OR 224 +
-  INC A +
-  JR Z, Wait_For_Keys_Pressed +
-  RET+
  
-;----------------------------------------------------------------------- +    ret                        Fin del programa
-; Esta rutina espera a que no haya ninguna tecla pulsada para volver. +
-;----------------------------------------------------------------------- +
-Wait_For_Keys_Released: +
-  XOR A +
-  IN A, (254) +
-  OR 224 +
-  INC A +
-  JR NZ, Wait_For_Keys_Released +
-  RET+
  
-;----------------------------------------------------------------------- +ATTR_T    EQU   $5c8f
-; Limpiar la pantalla con el patron de pixeles indicado. +
-; Entrada:  A = patron a utilizar +
-;----------------------------------------------------------------------- +
-ClearScreen: +
-  LD HL, 16384          ; HL = Inicio del area de imagen +
-  LD (HL), A            ; Escribimos el valor de A en (HL) +
-  LD DE, 16385          ; Apuntamos DE a 16385 +
-  LD BC, 192*32-1       ; Copiamos 6142 veces (HL) en (DE) +
-  LDIR +
-  RET +
- +
-;------------------------------------------------------------------------- +
-; Establecer los colores de la pantalla con el byte de atributos indicado. +
-; Entrada:  A = atributo a utilizar +
-;------------------------------------------------------------------------- +
-ClearAttributes: +
-   +
-  LD HL, 22528          ; HL = Inicio del area de atributos +
-  LD (HL), A            ; Escribimos el patron A en (HL) +
-  LD DE, 22529          ; Apuntamos DE a 22529 +
-  LD BC, 24*32-1        ; Copiamos 767 veces (HL) en (DE) +
-  LDIR                  ; e incrementamos HL y DL. Restamos 1 +
-                        ; porque ya hemos escrito en 22528. +
-  RET                    +
- +
-;------------------------------------------------------------------------- +
-; SetBorder: Cambio del color del borde al del registro A +
-; Se establece BORDCR tal cual lo requiere BASIC. +
-;------------------------------------------------------------------------- +
-SetBorder: +
-  OUT (254), A           ; Cambiamos el color del borde +
-  RLCA +
-  RLCA +
-  RLCA                   ; A = A*8 (colocar en bits PAPER) +
-  BIT 5, A               ; Mirar si es un color BRIGHT +
-  JR NZ, SetBorder_fin   ; No es bright -> guardarlo +
-                         ; Si es bright +
-  XOR 7                  ; -> cambiar la tinta a 0 +
-   +
-SetBorder_fin: +
-  LD (23624), A          ; Salvar el valor en BORDCR +
-  RET +
- +
-;------------------------------------------------------------------------- +
-; PrintString: imprime una cadena acabada en valor cero (no caracter 0). +
-; HL = direccion de la cadena de texto a imprimir. +
-;------------------------------------------------------------------------- +
-PrintString: +
-  +
-printstrloop: +
-  LD A, (HL)             ; Obtener el siguiente caracter a imprimir +
-  CP 0                   ; Comprobar si es un 0 (fin de linea) +
-  RET Z                  ; Si es cero, fin de la rutina +
-  RST 16                 ; No es cero, imprimir caracter o codigo +
-                         ; de control (13=enter, etc). +
-  INC HL                 ; Avanzar al siguiente caracter +
-  JP printstrloop        ; Repetir bucle          +
-  ret +
- +
  
 ;------------------------------------------------------------------------- ;-------------------------------------------------------------------------
 ; Datos ; Datos
 ;------------------------------------------------------------------------- ;-------------------------------------------------------------------------
-linea1 defb 'Impreso con ATTR-T actual', 13130 +linea1  DEFB 'Impreso con ATTR-T actual', _CR_CR_EOS 
-linea2 defb 'Esto es una prueba',13,'cambiando los atributos', 13130+linea2  DEFB 'Esto es una prueba', _CR,'cambiando los atributos', _CR_CR_EOS
  
 +    INCLUDE "utils.asm"
  
-END 50000+    END 50000
 </code> </code>
  
- Con nuestra nueva rutina de PrintString trazaremos en pantalla 1 línea con los atributos actuales seguida de 2 líneas con diferentes atributos. Nótese como RST 16 entiende e interpreta en las cadenas los códigos de control como por ejemplo 13 (retorno de carro).+ Con nuestra nueva rutina de ''PrintString'' trazaremos en pantalla 1 línea con los atributos actuales seguida de 2 líneas con diferentes atributos. Nótese como rst 16 entiende e interpreta en las cadenas los códigos de control como por ejemplo 13 (retorno de carro).
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_attrt.png | Ejemplo que modifica ATTR-T }}+{{ cursos:ensamblador:gfx1_attrt.png?640 | Ejemplo que modifica ATTR-T }}
 \\  \\ 
  
- Nótese que dado lo habitual que puede ser llamar a ClearScreen y ClearAttributes, podemos desarrollar una función ClearScreenAttributes que realice ambas funciones en una misma llamada: + Cuando tratemos las fuentes de texto como sprites de carácteres en baja resolución utilizaremos rutinas de impresión de cadenas más rápidas (y con juegos de caracteres personalizados) al no tener que interpretar éstas los diferentes códigos de control que se pueden insertar en las mismas.
- +
- +
-<code z80> +
-;----------------------------------------------------------------------- +
-; Limpiar la pantalla con el patron de pixeles y atributos indicado. +
-; Entrada:  H = atributo, L = patron +
-;----------------------------------------------------------------------- +
-ClearScreenAttrib: +
-  PUSH DE +
-  PUSH BC +
- +
-  LD A, H               ; A = el atributo +
-  EX AF, AF'            ; Nos guardamos el atributo en A' +
-  LD A, L               ; Cargamos en A el patron +
-  LD HL, 16384          ; HL = Inicio del area de imagen +
-  LD (HL), A            ; Escribimos el valor de A en (HL) +
-  LD DE, 16385          ; Apuntamos DE a 16385 +
-  LD BC, 192*32-1       ; Copiamos 6142 veces (HL) en (DE) +
-  LDIR +
-   +
-  EX AF, AF'            ; Recuperamos A (atributo) de A' +
-  INC HL                ; Incrementamos HL y DE +
-  INC DE                ; para entrar en area de atributos +
-  LD (HL), A            ; Almacenamos el atributo +
-  LD BC, 24*32-1        ; Ahora copiamos 767 bytes +
-  LDIR +
- +
-  POP BC +
-  POP DE +
-  RET +
-</code> +
- +
- +
- Por otra parte, cuando tratemos las fuentes de texto como sprites de carácteres en baja resolución utilizaremos rutinas de impresión de cadenas más rápidas (y con juegos de caracteres personalizados) al no tener que interpretar éstas los diferentes códigos de control que se pueden insertar en las mismas.+
  
 \\  \\ 
 ===== Efectos sobre la imagen y los atributos ===== ===== Efectos sobre la imagen y los atributos =====
  
- Ahora ya conocemos la organización de la zona de imagen y atributos y sabemos (del capítulo sobre rutinas de SAVE/LOAD) cargar en ella datos gráficos desde cinta o incluir los datos gráficos en nuestro propio programa y volcarlos con instrucciones LDIR. Estamos pues en disposición de realizar pequeñas y sencillas rutinas de borrado de pantalla o de aparición de los datos en la misma de diferentes formas, como por ejemplo:+ Ahora ya conocemos la organización de la zona de imagen y atributos y sabemos (del capítulo sobre rutinas de SAVE/LOAD) cargar en ella datos gráficos desde cinta o incluir los datos gráficos en nuestro propio programa y volcarlos con instrucciones ''LDIR''. Estamos pues en disposición de realizar pequeñas y sencillas rutinas de borrado de pantalla o de aparición de los datos en la misma de diferentes formas, como por ejemplo:
  
   * Efectos de fundido de los atributos de pantalla a negro.   * Efectos de fundido de los atributos de pantalla a negro.
Línea 1189: Línea 1069:
   * Zoom o reducción de alguna zona de pantalla.   * Zoom o reducción de alguna zona de pantalla.
   * (etc...).   * (etc...).
- 
  
  Por ejemplo, la siguiente rutina vacía el contenido de una pantalla (preferentemente monocolor) haciendo una rotación de los píxeles de cada bloque de pantalla. Los bloques 0-15 verán sus píxeles rotados a la izquierda y los bloques 16-31 a la derecha:  Por ejemplo, la siguiente rutina vacía el contenido de una pantalla (preferentemente monocolor) haciendo una rotación de los píxeles de cada bloque de pantalla. Los bloques 0-15 verán sus píxeles rotados a la izquierda y los bloques 16-31 a la derecha:
Línea 1197: Línea 1076:
   ; Fundido de los pixeles a cero con una cortinilla   ; Fundido de los pixeles a cero con una cortinilla
  
-  ORG 50000+    ORG 50000
  
 Start: Start:
 +    ; Rellenamos la VRAM de pixeles copiando 6 KB de la ROM
 +    ld hl, 0
 +    ld de, 16384
 +    ld bc, 6144
 +    ldir
  
-  ; Rellenamos la VRAM de pixeles copiando 6 KB de la ROM+    call Wait_For_Key 
 +    call FadeScreen
  
-  LD HL, 0 +    ret
-  LD DE, 16384 +
-  LD BC, 6144 +
-  LDIR+
  
-  CALL Wait_For_Keys_Pressed 
-  CALL Wait_For_Keys_Released 
-  CALL FadeScreen 
- 
-  RET 
- 
-;----------------------------------------------------------------------- 
-Wait_For_Keys_Pressed: 
-  XOR A    
-  IN A, (254) 
-  OR 224 
-  INC A 
-  JR Z, Wait_For_Keys_Pressed 
-  RET 
-  
-;----------------------------------------------------------------------- 
-Wait_For_Keys_Released: 
-  XOR A 
-  IN A, (254) 
-  OR 224 
-  INC A 
-  JR NZ, Wait_For_Keys_Released 
-  RET 
-  
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 ; Fundido de pantalla decrementando los pixeles de pantalla ; Fundido de pantalla decrementando los pixeles de pantalla
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 FadeScreen: FadeScreen:
-   PUSH AF +    push af 
-   PUSH BC +    push bc 
-   PUSH DE +    push de 
-   PUSH HL                      ; Preservamos los registros +    push hl                      ; Preservamos los registros 
-    + 
-   LD B, 9                      ; Repetiremos el bucle 9 veces +    ld b, 9                      ; Repetiremos el bucle 9 veces 
-   LD C, 0                      ; Nuestro contador de columna+    ld c, 0                      ; Nuestro contador de columna
  
 fadegfx_loop1: fadegfx_loop1:
-   LD HL, 16384                 ; Apuntamos HL a la zona de atributos +    ld hl, 16384                 ; Apuntamos HL a la zona de atributos 
-   LD DE, 6144                  ; Iteraciones bucle+    ld de, 6144                  ; Iteraciones bucle
  
 fadegfx_loop2: fadegfx_loop2:
-   LD A, (HL)                   ; Cogemos el grupo de 8 pixeles+    ld a, (hl)                   ; Cogemos el grupo de 8 pixeles 
  
 +    ;-- Actuamos sobre el valor de los pixeles --
 +    cp 0                         ;
 +    jr z, fadegfx_save           ; Si ya es cero, no hacemos nada
  
-   ;-- Actuamos sobre el valor de los pixeles -- +    ex af, af                    Nos guardamos el dato
-   CP 0                         ; +
-   JR Z, fadegfx_save           ; Si ya es cero, no hacemos nada+
  
-   EX AFAF'                   Nos guardamos el dato en A'+    ld ac                      Pasamos el contador a A 
 +    cp 15                        ; Comparamos A con 15 
 +    jr nc, fadegfx_mayor15       ; Si es mayor, saltamos
  
-   LD AC                      Pasamos el contador a A +    ex afaf                    Recuperamos en A los pixeles 
-   CP 15                        ; Comparamos A con 15 +    rla                          ; Rotamos A a la izquierda 
-   JR NC, fadegfx_mayor15       ; Si es mayor, saltamos +    jr fadegfx_save              ; Y guardamos el dato
-    +
-   EX AF, AF'                   ; Recuperamos los pixeles desde A' +
-   RLA                          ; Rotamos A a la izquierda +
-   JR fadegfx_save              ; Y guardamos el dato+
  
 fadegfx_mayor15: fadegfx_mayor15:
-   EX AFAF'                   ; Recuperamos los pixeles desde A' +    ex afaf                    ; Recuperamos en A los pixeles 
-   SRL A                        ; Rotamos A a la derecha +    srl a                        ; Rotamos A a la derecha
  
-   ;-- Fin actuacion sobre el valor de los pixeles --+    ;-- Fin actuacion sobre el valor de los pixeles --
  
 fadegfx_save: fadegfx_save:
  
-   LD (HL),                   ; Almacenamos el atributo modificado +    ld (hl),                   ; Almacenamos el atributo modificado 
-   INC HL                       ; Avanzamos puntero de memoria+    inc hl                       ; Avanzamos puntero de memoria
  
-   ; Incrementamos el contador y comprobamos si hay que resetearlo +    ; Incrementamos el contador y comprobamos si hay que resetearlo 
-   INC C +    inc c 
-   LD AC +    ld ac 
-   CP 32 +    cp 32 
-   JR NZ, fadegfx_continue+    jr nz, fadegfx_continue
  
-   LD C, 0+    ld c, 0
  
 fadegfx_continue: fadegfx_continue:
  
-   DEC DE +    dec de 
-   LD AD +    ld ad 
-   OR E +    or e 
-   JP NZ, fadegfx_loop2      ; Hasta que DE == 0+    jp nz, fadegfx_loop2      ; Hasta que DE == 0
  
-   DJNZ fadegfx_loop1        ; Repeticion 9 veces+    djnz fadegfx_loop1        ; Repeticion 9 veces
  
-   POP HL +    pop hl 
-   POP DE +    pop de 
-   POP BC +    pop bc 
-   POP AF                       ; Restauramos registros +    pop af                       ; Restauramos registros 
-   RET+    ret
  
-END 50000+    INCLUDE "utils.asm" 
 + 
 +    END 50000
 </code> </code>
  
Línea 1306: Línea 1166:
  
 \\  \\ 
-{{ cursos:ensamblador:gfx1_fadeg.png |Captura durante el fade de pixeles}}+{{ cursos:ensamblador:gfx1_fadeg.png?640 |Captura durante el fade de pixeles}}
 \\  \\ 
  
- Podemos cambiar la rutina para que realice diferentes efectos sobre los píxeles modificando el núcleo de la misma, identificado con el comentario //Actuamos sobre el valor de los píxeles//+ Podemos cambiar la rutina para que realice diferentes efectos sobre los píxeles modificando el núcleo de la misma, identificado con el comentario //Actuamos sobre el valor de los píxeles//.
  
  A continuación podemos ver la rutina de degradación de atributos que vimos como un ejemplo en el capítulo dedicado a la pila. Este efecto aplicado sobre una pantalla gráfica puede utilizarse como "fundido a negro" de la misma. Podemos utilizar el esqueleto del programa anterior como base para llamar a esta rutina:  A continuación podemos ver la rutina de degradación de atributos que vimos como un ejemplo en el capítulo dedicado a la pila. Este efecto aplicado sobre una pantalla gráfica puede utilizarse como "fundido a negro" de la misma. Podemos utilizar el esqueleto del programa anterior como base para llamar a esta rutina:
Línea 1318: Línea 1178:
 ;----------------------------------------------------------------------- ;-----------------------------------------------------------------------
 FadeAttributes: FadeAttributes:
-   PUSH AF +    push af 
-   PUSH BC +    push bc 
-   PUSH DE +    push de 
-   PUSH HL                      ; Preservamos los registros +    push hl                      ; Preservamos los registros 
-    + 
-   LD B, 9                      ; Repetiremos el bucle 9 veces+    ld b, 9                      ; Repetiremos el bucle 9 veces
  
 fadescreen_loop1: fadescreen_loop1:
-   LD HL, 16384+6144            ; Apuntamos HL a la zona de atributos +    ld hl, 16384+6144            ; Apuntamos HL a la zona de atributos 
-   LD DE, 768                   ; Iteraciones bucle+    ld de, 768                   ; Iteraciones bucle 
 + 
 +    halt 
 +    halt                         ; Ralentizamos el efecto
  
-   HALT 
-   HALT                         ; Ralentizamos el efecto 
-    
 fadescreen_loop2: fadescreen_loop2:
-   LD A, (HL)                   ; Cogemos el atributo +    ld a, (hl)                   ; Cogemos el atributo 
-   AND 127                      ; Eliminamos el bit de flash +    and %01111111                ; Eliminamos el bit de flash 
-   LD CA+    ld ca
  
-   AND 7                        ; Extraemos la tinta (AND 00000111b) +    and %00000111                ; Extraemos la tinta (and 00000111b) 
-   JR Z, fadescreen_ink_zero    ; Si la tinta ya es cero, no hacemos nada+    jr z, fadescreen_ink_zero    ; Si la tinta ya es cero, no hacemos nada
  
-   DEC A                        ; Si no es cero, decrementamos su valor+    dec a                        ; Si no es cero, decrementamos su valor
  
 fadescreen_ink_zero: fadescreen_ink_zero:
-   
-   EX AF, AF'                   ; Nos hacemos una copia de la tinta en A' 
-   LD A, C                      ; Recuperamos el atributo 
-   SRA A 
-   SRA A                        ; Pasamos los bits de paper a 0-2 
-   SRA A                        ; con 3 instrucciones de desplazamiento >> 
  
-   AND 7                        Eliminamos el resto de bits +    ex af, af                    ; Nos hacemos una copia de la tinta en A' 
-   JR Z, fadescreen_paper_zero  Si ya es cero, no lo decrementamos+    ld a, c                      Recuperamos el atributo 
 +    sra a 
 +    sra a                        ; Pasamos los bits de paper a 0-2 
 +    sra a                        con 3 instrucciones de desplazamiento >>
  
-   DEC A                        ; Lo decrementamos+    and %00000111                ; Eliminamos el resto de bits 
 +    jr z, fadescreen_paper_zero  ; Si ya es cero, no lo decrementamos 
 + 
 +    dec a                        ; Lo decrementamos
  
 fadescreen_paper_zero: fadescreen_paper_zero:
-   SLA A +    sla a 
-   SLA A                        ; Volvemos a color paper en bits 3-5 +    sla a                        ; Volvemos a color paper en bits 3-5 
-   SLA A                        ; Con 3 instrucciones de desplazamiento <<+    sla a                        ; Con 3 instrucciones de desplazamiento <<
  
-   LD C                     ; Guardamos el papel decrementado en A +    ld c                     ; Guardamos el papel decrementado en A 
-   EX AFAF'                   ; Recuperamos A' +    ex afaf                    ; Recuperamos A' 
-   OR C                         ; A = A OR C   PAPEL OR TINTA+    or c                         ; A = A or c   PAPEL OR TINTA
  
-   LD (HL),                   ; Almacenamos el atributo modificado +    ld (hl),                   ; Almacenamos el atributo modificado 
-   INC HL                       ; Avanzamos puntero de memoria+    inc hl                       ; Avanzamos puntero de memoria
  
-   DEC DE +    dec de 
-   LD AD +    ld ad 
-   OR E +    or e 
-   JP NZ, fadescreen_loop2      ; Hasta que DE == 0+    jp nz, fadescreen_loop2      ; Hasta que DE == 0
  
-   DJNZ fadescreen_loop1        ; Repeticion 9 veces+    djnz fadescreen_loop1        ; Repeticion 9 veces
  
-   POP HL +    pop hl 
-   POP DE +    pop de 
-   POP BC +    pop bc 
-   POP AF                       ; Restauramos registros +    pop af                       ; Restauramos registros 
-   RET+    ret
 </code> </code>
  
 \\  \\ 
-{{ cursos:ensamblador:fade.png |Captura durante el fade de la pantalla}}+{{ cursos:ensamblador:fade.png?640 |Captura durante el fade de la pantalla}}
 \\  \\ 
  
  Rutinas más complejas pueden producir cortinillas y efectos mucho más vistosos. En la revista Microhobby se publicaron muchos de estos efectos de zoom, desaparición de pantalla o inversión, dentro de la sección //Trucos//.  Rutinas más complejas pueden producir cortinillas y efectos mucho más vistosos. En la revista Microhobby se publicaron muchos de estos efectos de zoom, desaparición de pantalla o inversión, dentro de la sección //Trucos//.
 +
 +Del mismo modo, el libro **//40 Best Machine code Routines for the ZX Spectrum//** ("Las 40 mejores rutinas en código máquina para el ZX Spectrum") de //John Hardman y Andrew Hewson// nos proporciona una serie de rutinas en ensamblador para realizar diferentes acciones con los píxeles y los atributos de la videoram, como por ejemplo:
 +
 +\\ 
 +   * Scrollear atributos a izquierda, derecha, arriba o abajo.
 +   * Scroll de pantalla de un carácter (8 pixels) a izquierda, derecha, arriba o abajo.
 +   * Scroll de pantalla de un pixel a izquierda, derecha, arriba o abajo.
 +   * Mezclar dos imágenes con ''OR'' o ''XOR''.
 +   * Inversión de la pantalla (píxeles a 0 se ponen a 1, y píxeles a 1 se ponen a 0).
 +   * Invertir carácter vertical y horizontalmente.
 +   * Rotar carácter 90º en sentido horario.
 +   * Alterar todos los atributos de la pantalla (los bits deseados).
 +   * Cambiar todos los atributos de la pantalla de un determinado valor por otro valor.
 +   * Rellenado de regiones cerradas (poniendo a 1 los píxeles dentro de esas regiones).
 +   * Impresión de figuras.
 +   * Copia de una zona de la pantalla en otra, ampliándola por una cantidad entera (por ejemplo, x2 o x3).
 +
  
 \\  \\ 
 ===== La Shadow VRAM de los modelos de 128K ===== ===== La Shadow VRAM de los modelos de 128K =====
  
- En el capítulo dedicado a la paginación de memoria en los modelos de 128KB se habló de la paginación de bloques de 16KB sobre el área entre $C000 y $FFFF. El bloque de 16KB que almacena la videoram (el bloque 5, o, como se le conoce técnicamente, **RAM5**) está normalmente mapeado sobre $4000.+ En el capítulo dedicado a la paginación de memoria en los modelos de 128KB se habló de la paginación de bloques de 16KB sobre el área entre $c000 y $ffff. El bloque de 16KB que almacena la videoram (el bloque 5, o, como se le conoce técnicamente, **RAM5**) está normalmente mapeado sobre $4000.
  
 \\  \\ 
Línea 1399: Línea 1276:
 \\  \\ 
  
- En los modelos de 128K, existe un segundo bloque de 16KB que podemos utilizar como VideoRAM (**Shadow VRAM**). El Z80 y la ULA nos permiten mapear **RAM7** sobre $C000-$FFFF, dejando la VideoRAM original sobre $4000. Más interesante todavía, la ULA puede visualizar el contenido de RAM7 en lugar del de RAM5 aunque no hayamos mapeado RAM7 en ningún sitio. Y recordemos que también podemos mapear la VRAM estándar (RAM5) sobre $C000, accediendo a ella a través de $C000 además de mediante $4000.+ En los modelos de 128K, existe un segundo bloque de 16KB que podemos utilizar como VideoRAM (**Shadow VRAM**). El Z80 y la ULA nos permiten mapear **RAM7** sobre $c000-$ffff, dejando la VideoRAM original sobre $4000. Más interesante todavía, la ULA puede visualizar el contenido de RAM7 en lugar del de RAM5 aunque no hayamos mapeado RAM7 en ningún sitio. Y recordemos que también podemos mapear la VRAM estándar (RAM5) sobre $c000, accediendo a ella a través de $c000 además de mediante $4000.
  
- El poder visualizar una VRAM aunque no esté mapeada y el poder mapear tanto RAM5 como RAM7 sobre $C000 nos permite organizar el código de nuestro programa para que siempre escriba sobre $C000, teniendo mapeada en $C000 la pantalla que actualmente no esté visible. + El poder visualizar una VRAM aunque no esté mapeada y el poder mapear tanto RAM5 como RAM7 sobre $c000 nos permite organizar el código de nuestro programa para que siempre escriba sobre $c000, teniendo mapeada en $c000 la pantalla que actualmente no esté visible
 + 
 + La utilidad principal de esta funcionalidad es la de poder generar un cuadro de imagen o animación en una "pantalla virtual" (la pantalla shadow) que no es visible, cambiando la visualización a esta pantalla una vez compuesta la imagen actual. De esta forma es posible trabajar con una pantalla completa sin que nos alcance el haz de electrones durante su dibujado, especialmente en juegos que realicen scrolles de todo el área de imagen.
  
- La utilidad principal de esta funcionalidad es la de poder generar un cuadro de imagen o animación en una "pantalla virtual" (la pantalla shadow) que no es visible, cambiando la visualización a esta pantalla una vez compuesta la imagen actual. De esta forma es posible trabajar con una pantalla completa sin que nos alcance el haz de electrones durante su dibujado, especialmente en juegos que realicen scrolles de todo el área de imagen.  
-  
  En el tiempo disponible tras un pulso VSYNC no hay tiempo material para actualizar los 6KB de una pantalla completa sin que el haz de electrones alcance a nuestro programa conforme manipula la memoria, por lo que esta técnica permitiría realizar ese tipo de acciones con el siguiente proceso:  En el tiempo disponible tras un pulso VSYNC no hay tiempo material para actualizar los 6KB de una pantalla completa sin que el haz de electrones alcance a nuestro programa conforme manipula la memoria, por lo que esta técnica permitiría realizar ese tipo de acciones con el siguiente proceso:
  
 \\  \\ 
-   * Mapeamos RAM7 sobre $C000.+   * Mapeamos RAM7 sobre $c000.
    * Visualizamos RAM5 (RAM7 no es visible).    * Visualizamos RAM5 (RAM7 no es visible).
-   * Trabajamos sobre $C000 (sobre RAM7). Los cambios en nuestra pantalla shadow no son visibles. +   * Trabajamos sobre $c000 (sobre RAM7). Los cambios en nuestra pantalla shadow no son visibles. 
-   * Esperamos una interrupción (mediante HALT o mediante coordinación con la ISR de la ULA).+   * Esperamos una interrupción (mediante halt o mediante coordinación con la ISR de la ULA).
    * Cambiamos la visualización a RAM7 (RAM5 deja de ser visible).    * Cambiamos la visualización a RAM7 (RAM5 deja de ser visible).
-   * Mapeamos ahora RAM5 sobre $C000+   * Mapeamos ahora RAM5 sobre $c000
-   * Trabajamos sobre $C000 (sobre RAM5). Los cambios en nuestra pantalla shadow no son visibles.+   * Trabajamos sobre $c000 (sobre RAM5). Los cambios en nuestra pantalla shadow no son visibles.
    * Repetimos el proceso.    * Repetimos el proceso.
 \\  \\ 
  
- Con este mecanismo siempre trabajamos sobre $C000 pero los cambios que realizamos sobre esta pantalla virtual no son perceptibles por el usuario. Cambiando la visualización de la VRAM a nuestra pantalla actual tras una interrupción hacemos los cambios visibles de forma inmediata, sin que el haz de electrones afecte a nuestro scroll o al dibujado de sprites. La tasa de fotogramas por segundo ya no sería de 50 (no podríamos generar 1 cuadro de imagen por interrupción) pero se evitaría un posible molesto efecto de parpadeo o cortinilla.+ Con este mecanismo siempre trabajamos sobre $c000 pero los cambios que realizamos sobre esta pantalla virtual no son perceptibles por el usuario. Cambiando la visualización de la VRAM a nuestra pantalla actual tras una interrupción hacemos los cambios visibles de forma inmediata, sin que el haz de electrones afecte a nuestro scroll o al dibujado de sprites. La tasa de fotogramas por segundo ya no sería de 50 (no podríamos generar 1 cuadro de imagen por interrupción) pero se evitaría un posible molesto efecto de parpadeo o cortinilla.
  
- La desventaja de este sistema es que utilizamos $C000-$FFFF como pantalla virtual con lo que perdemos 16KB efectivos de RAM así como la posibilidad de paginar sobre $C000. Nos quedan así 16KB de memoria (entre $8000 y $BFFF) para alojar el código de nuestro programa, los datos gráficos, textos, etc. Esto puede ser una enorme limitación según el tipo de juego o programa que estemos realizando. + La desventaja de este sistema es que utilizamos $c000-$ffff como pantalla virtual con lo que perdemos 16KB efectivos de RAM así como la posibilidad de paginar sobre $c000. Nos quedan así 16KB de memoria (entre $8000 y $bfff) para alojar el código de nuestro programa, los datos gráficos, textos, etc. Esto puede ser una enorme limitación según el tipo de juego o programa que estemos realizando.
  
- En realidad, si diseñamos adecuadamente nuestro programa, podemos aprovechar más de 16KB, puesto que sólo necesitamos mapear RAM5 ó RAM7 en $C000 durante la generación de la pantalla virtual. Esto obliga a que los gráficos, fuentes, sprites y mapeados del juego deban estar disponibles en $8000-$BFFF, pero una vez finalizada la generación de la pantalla podemos volver a mapear RAM0 sobre $C000, volviendo a la lógica del juego que podría estar ubicada en ese bloque, junto al resto de variables, imágenes o textos usados en los menúes, efectos sonoros, músicas, etc. + En realidad, si diseñamos adecuadamente nuestro programa, podemos aprovechar más de 16KB, puesto que sólo necesitamos mapear RAM5 ó RAM7 en $c000 durante la generación de la pantalla virtual. Esto obliga a que los gráficos, fuentes, sprites y mapeados del juego deban estar disponibles en $8000-$bfff, pero una vez finalizada la generación de la pantalla podemos volver a mapear RAM0 sobre $c000, volviendo a la lógica del juego que podría estar ubicada en ese bloque, junto al resto de variables, imágenes o textos usados en los menúes, efectos sonoros, músicas, etc.
  
- Como véis, se necesita tener muy controlada la ubicación de las diferentes rutinas y variables y diseñar el juego para que mapee la página adecuada en cada momento y salte a una rutina concreta sólo cuando la rutina a la que hace referencia un CALL esté contenida en la página mapeada.+ Como véis, se necesita tener muy controlada la ubicación de las diferentes rutinas y variables y diseñar el juego para que mapee la página adecuada en cada momento y salte a una rutina concreta sólo cuando la rutina a la que hace referencia un call esté contenida en la página mapeada.
  
- Se reseñó también, en el apartado //Particularidades del +2A/+3// la existencia de unos modos extendidos de paginación que permitirían ubicar la segunda VideoRAM (el bloque 7, o RAM7) sobre $4000, permitiendo el alternar entre la visualización de RAM5 o de RAM7 sin perder la memoria $C000-$FFFF como "Pantalla Virtual":+ Se reseñó también, en el apartado //Particularidades del +2A/+3// la existencia de unos modos extendidos de paginación que permitirían ubicar la segunda VideoRAM (el bloque 7, o RAM7) sobre $4000, permitiendo el alternar entre la visualización de RAM5 o de RAM7 sin perder la memoria $c000-$ffff como "Pantalla Virtual":
  
 \\  \\ 
Línea 1435: Línea 1312:
 \\  \\ 
  
- Como puede verse en la figura anterior, los modos Bit2 = 0, Bit3 = 1 (Bancos 4-5-6-3) y Bit2 = 1, Bit 3=1 (Bancos 4-7-6-3) del puerto $1FFD permiten paginar cualquiera de las 2 videorams (RAM5 o RAM7) sobre $4000.+ Como puede verse en la figura anterior, los modos Bit2 = 0, Bit1 = 1 (Bancos 4-5-6-3) y Bit2 = 1, Bit 1=1 (Bancos 4-7-6-3) del puerto $1ffd permiten paginar cualquiera de las 2 videorams (RAM5 o RAM7) sobre $4000.
  
- Pese a las posibilidades de "animación sin parpadeo" que proporcionan estas técnicas, la utilización de cualquiera de las dios tiene una desventaja clara además de la "pérdida" (durante el dibujado de la pantalla shadow) de los 16KB $C000-$FFFF, y es la incompatibilidad con modelos de 48K, requiriendo un modelo de 128Kb para paginar RAM7 o incluso de un +2A/+3 para el uso de la paginación extendida.+ Pese a las posibilidades de "animación sin parpadeo" que proporcionan estas técnicas, la utilización de cualquiera de las dos tiene una desventaja clara además de la "pérdida" (durante el dibujado de la pantalla shadow) de los 16KB $c000-$ffff, y es la incompatibilidad con modelos de 48K, requiriendo un modelo de 128Kb para paginar RAM7 o incluso de un +2A/+3 para el uso de la paginación extendida. Si a los 16KB de RAM5 le restamos los 7KB de pantalla nos quedan otros 9KB adicionales, pero con la particularidad de que ese bloque de memoria está "compartido" con la ULA por lo que la velocidad de lectura, escritura y ejecución efectiva de este bloque se puede ver reducida hasta en un 25%.
  
 \\  \\ 
Línea 1459: Línea 1336:
   * {{cursos:ensamblador:06_fade.asm|Sencillo fundido de atributos de pantalla}}   * {{cursos:ensamblador:06_fade.asm|Sencillo fundido de atributos de pantalla}}
   * {{cursos:ensamblador:06_fade.tap|Tap del ejemplo anterior}}   * {{cursos:ensamblador:06_fade.tap|Tap del ejemplo anterior}}
- +  * {{cursos:ensamblador:utils.asm|Librería de utilidades usada en algunos ejemplos (ASM)}}. 
 \\  \\ 
 ===== Enlaces ===== ===== Enlaces =====
Línea 1467: Línea 1345:
   * [[http://www.worldofspectrum.org/faq/reference/z80reference.htm|Z80 Reference de WOS]].   * [[http://www.worldofspectrum.org/faq/reference/z80reference.htm|Z80 Reference de WOS]].
   * [[http://www.speccy.org/trastero/cosas/Fichas/fichas.htm|Microfichas de CM de MicroHobby]].   * [[http://www.speccy.org/trastero/cosas/Fichas/fichas.htm|Microfichas de CM de MicroHobby]].
-  * [[http://www.arrakis.es/~ninsesabe/pasmo/|PASMO]]. 
  
 \\  \\ 
 +**[ [[.:indice|⬉]] | [[.:paginacion_128k|⬅]] | [[.:gfx2_direccionamiento|➡]] ]**
  
  • cursos/ensamblador/gfx1_vram.1288911603.txt.gz
  • Última modificación: 04-11-2010 23:00
  • por sromero