Raster

El registro “raster” ya se presentó en la entrada dedicada a la animación de sprites.

El “raster” se puede entender como un rayo que va recorriendo la pantalla de izquierda a derecha y de arriba abajo, actualizando la información. En el fondo así funcionaban las televisiones de tubo de rayos catódicos (CRTs) de los 80, mediante un rayo que iba actualizando la imagen. Lo que ocurre en este caso es que ese rayo está controlado por el VIC.

Pues bien, existe un registro en la memoria del C64 que permite trabajar con este rayo. En realidad, es un registro ($d012) y parte de otro (bit 7 de $d011). Más concretamente, estos registros permiten hacer dos cosas:

  • Leer por qué línea de la pantalla va el rayo. Esta información se obtiene leyendo la posición $d012 (bits 0…7) y el bit 7 de $d011 (bit 8). Con 9 bits tenemos 512 líneas.
  • Escribir en el registro. En este caso, lógicamente, no se consigue modificar la secuencia normal de actualización del rayo, que es fija, sino que lo que se consigue es que cuando el rayo alcance la línea escrita, se genere una interrupción.

Las interrupciones del VIC se describirán más en detalle en la entrada siguiente, ya que no sólo el “raster” puede generar interrupciones. Las colisiones de sprites también puede generarlas.

De momento, baste decir que cualquiera de los dos enfoques anteriores (detectar mediante lectura que el “raster” ha alcanzado una línea X o generar y atender una interrupción cuando alcanza una línea X) sirve para sincronizar la ejecución del programa (por ejemplo, las animaciones) y el refresco de la pantalla.

Lo más habitual suele ser actualizar los gráficos cuando el “raster” está fuera de la zona visible de la pantalla (a partir de la línea 250), porque de este modo se consigue una actualización más suave, sin parpadeo.

El “raster” actualiza la pantalla 60 veces por segundo (60 Hz) de modo que, aunque sólo se actualicen los gráficos cuando el “raster” pasa por la línea 255, ya se actualizan con mucha frecuencia. Tanta que el ojo no lo percibe.


Programa de ejemplo: el programa de la entrada dedicada a la animación utiliza el «raster» en modo lectura.

10 comentarios en «Raster»

  1. Hola,
    Traté de crear esta rutina de ejemplo que a través de Raster Interrupt divide la pantalla en dos mitades, una blanca y otra negra.
    La rutina es ampliamente comentada.
    Me gustaría tu análisis técnico y tu opinión.
    En la Rutina he añadido la desactivación del escaneo del teclado para evitar el molesto efecto de «parpadeo» de la pantalla.
    ¿Lo implementé bien?
    De lo contrario, ¿qué me aconsejaría para evitar el «parpadeo»?
    Gracias por adelantado.
    Un saludo
    Attilio Capuozzo
    Fundador de Grupo de Facebook
    «RetroProgramming Italia – RP Italia»

    * = $c000

    IRQLO = $0314
    IRQHI = $0315
    RASREG = $D012
    IENREG = $D01A
    SCREEN = $D021
    INTREG = $D019

    setup
    sei
    lda #init
    sta $0315 ; Cargo el vector CINV con la dirección de mi rutina
    cli
    lda #1
    sta $dc0d ; Desactivo el escaneo del teclado (apago temporizador A de CIA # 1)
    sta IENREG ; Habilito la interrupción de ráster
    lda #0
    sta RASREG ; Cargo RASREG con «scan line» #0
    lda $d011
    and #127
    sta $d011 ; Restablecer el bit 8 del Raster Register
    rts
    init
    lda INTREG ;Examino el registro de interrupciones
    and #1
    beq normalExit ; Si no es una interrupción de ráster,salte a la rutina IRQ normal
    sta INTREG; Restablecer el «latch» de la interrupción de ráster
    lda RASREG ; Si RASREG 0, lo cargo con «scan line» #0
    bne rasterCompare0
    lda #145 ; Siguiente interrupción de ráster en #145
    sta RASREG
    sta SCREEN ; Cambio el color de fondo a 1 (blanco)
    jmp exit
    RasterCompare0
    lda #0
    sta RASREG
    sta SCREEN ; Cambio el color de fondo a 0 (negro)
    exit
    jmp $ea81 ; Hago el «Pull» de la pila de registros Y, X y A
    normalExit
    jmp $ea31; Normal Rutina de IRQ

    Me gusta

    1. Hola, Attilio.

      Gracias por tu mensaje.

      La rutina me parece bien en general. No obstante, ahí van algunos comentarios:

      1) Te faltan los operadores < y > en lda #<init y lda #>init. Esto es un efecto del copiar y pegar. Ten en cuenta que este blog está basado en web y, en HTML, < y > se usan para delimitar las etiquetas (tags). Los has copiado, pero si no usas las secuencias de escape correctas (< y >) el navegador se los comerá.

      2) Fijas el valor de $0315, pero no el de $0314. Entiendo que esto es un descuido al copiar y pegar.

      3) Defines contantes como IRQLO, IRQHI y otras, pero luego no las usas. Te sugiero usarlas, el código queda más limpio y fácil de mantener.

      4) En general, todo lo que es la preparación o configuración de las interrupciones (setup) lo haría entre las instrucciones sei y cli, para evitar que se produzcan interrupciones con la configuración a medias.

      5) Para terminar la rutina haces jmp $ea81. A mí me gusta más terminar recuperando los registros de la pila y haciendo rti. El efecto es el mismo, pero me parece más claro.

      6) El tema del parpadeo cuando no desactivas el teclado tengo que pensarlo más a fondo. Consultaré mis fuentes a ver si doy con ello.

      Un saludo, HVSW.

      Le gusta a 1 persona

      1. Hola de nuevo.

        Es probable que el parpadeo que estás observando tenga que ver con la falta de sincronización entre el VIC y el 6510, lo que llaman las «bad lines» (líneas malas).

        El VIC tarda 63 ciclos de reloj en pintar una línea raster. En algunos casos (las llamadas «bad lines»), antes de pintar la línea raster el VIC necesita leer los caracteres a pintar. Y esto lo hace «robando» ciclos a la CPU.

        Por tanto, en una línea raster normal la CPU tiene 63 ciclos de reloj para hacer cosas, mientras que en una «bad line» tiene menos ciclos.

        Para evitar el parpadeo, hay que conseguir que CPU y VIC estén perfectamente sincronizados, y conseguir esto no es fácil.

        Dejo un enlace de interés sobre el VIC en general y sobre las «bad lines» en particular:

        https://csdb.dk/release/download.php?id=54309

        Un saludo, HVSW.

        Le gusta a 1 persona

  2. Los símbolos > y < faltan en la rutina del comentario anterior, que desafortunadamente no se copiaron de CBM Prg Studio (no sé por qué).
    Espero que la rutina siga siendo comprensible.
    Me disculpo.
    Un saludo
    Attilio Capuozzo
    Fundador de Grupo de Facebook
    “RetroProgramming Italia – RP Italia”

    Le gusta a 1 persona

    1. Hola de nuevo, Attilio.

      Puedes echarle un vistazo al programa SPLIT SCREEN del libro «Commodore 64 exposed»:

      https://archive.org/details/commodore-64-exposed/page/n157/mode/2up

      El programa está parte en BASIC y parte en ensamblador (que se carga a partir de la sección DATA de BASIC). La parte BASIC es la preparación de la rutina de interrupción, y la parte en ensamblador es la rutina de interrupción propiamente dicha (que empieza en $c000).

      Observa varias cosas:

      1) El programa también desactiva las interrupciones del CIA 1 (1020 POKE 56333,127), pero luego vuelve a activarlas (1090 POKE 56333,129). Esto equivale a hacer «sei» y «cli» en ensamblador para preparar la rutina de interrupción. En BASIC no hay «sei» ni «cli».

      2) La rutina de interrupción, que está en ensamblador, es muy parecida a la que tú planteas: a) verifica si es una interrupción raster, b) reconoce la interrupción, c) despacha a un sitio u otro en función del número de línea ráster, d) reprograma la interrupción para la línea 145 o 30, según el caso.

      3) La rutina de interrupción juega con el bitmap y caracteres personalizados pero, por lo demás, tiene la misma estructura que la tuya. Tú en vez de usar bitmap y caracteres personalizados cambias el color de fondo.

      En definitiva, es un programa muy parecido a tu rutina. Yo no lo he probado, pero quizás tenga los mismos problemas de parpadeo. Puedes comprobarlo tú mismo.

      Un saludo, HVSW.

      Le gusta a 1 persona

  3. Primero que nada gracias por la nueva respuesta.
    Luego me gustaría agregar que he estudiado el programa «Split Screen» que indicaste en el libro «Commodore 64 Exposed» y he notado que también menciona el problema del «parpadeo» y sugiere resolverlo agregando la línea 1045 en BASIC:
    POKE 56334, PEEK (56334) Y 254
    que se utiliza para detener el temporizador A de CIA 1 y, por lo tanto, para deshabilitar la exploración del teclado.
    Por lo tanto, desactiva el temporizador A y no lo vuelve a activar desactivando el teclado.
    Una instrucción muy similar a la que usé pero que actuó en el Registro 56333 ($DC0D) que en BASIC es equivalente a:
    POKE 56333.1.
    Gracias
    Saludos
    Attilio Capuozzo
    Fundador de Grupo Facebook
    «RetroProgramming Italia – RP Italia»

    Me gusta

    1. Efectivamente, el libro «Commodore 64 Exposed» pone en su página 147:

      «You may notice that the border between the 2 modes jumps around at times. This is because the raster interrupt is an IRQ interrupt and is therefore queued up after previous interrupts. This problem can be remedied by adding the following line which turns off keyboard interrupts. 1045 POKE 56334, PEEK(56334) AND 254.»

      El registro 56334 ($dc0e en hexadecimal) es el registro CIACRA que, como vimos en la entrada dedicada a la temporización con el CIA1, vale para arrancar y parar el temporizador A del CIA1.

      Otra idea que me parece interesante es que la rutina de interrupción de SPLIT SCREEN no verifica líneas concretas del raster, es decir, no comprueba si $d012 vale 30 o 145 para actuar en consecuencia.

      En vez de eso, hace lda $d012 + bmi int2. La instrucción bmi (branch on minus) salta si el flag N del registro de estado está activo, es decir, si el valor cargado en el acumulador (que viene del raster $d012 en este caso) es negativo o, lo que es equivalente, igual o mayor que 128.

      De este modo, por encima de la línea 128 salta a un sitio y por debajo de ella salta a otro. Creo que esto es ventajoso frente a comprobar líneas concretas ya que, como VIC y CPU corren en paralelo, no descartaría que cuando la CPU pueda procesar la interrupción el VIC ya pueda tener el raster en otra línea.

      Si lo que digo es cierto, comprobar líneas concretas puede hacer que la rutina de interrupción no procese algunas interrupciones del raster por no coincidir las líneas con exactitud, lo que haría que el efecto de parpadeo fuera mayor.

      Un saludo, HVSW.

      Le gusta a 1 persona

      1. Hola HVSW,

        Reescribí la rutina de interrupción de ráster.

        ¡Pude evitar el problema del parpadeo sin apagar el teclado!

        El problema con el parpadeo se debe al hecho de que cada cierto tiempo se produce la interrupción del temporizador A antes de la interrupción de ráster.

        Luego, la interrupción del temporizador A se procesará primero y luego nuestra rutina de administración de interrupciones de ráster (la interrupción de ráster se pondrá en cola).

        El efecto del parpadeo es aún más notorio cuando el usuario presiona teclas en el teclado mientras toca nuestro Routine en Ensamblador.

        Por lo tanto, es necesaria la desactivación del temporizador A Interrupt.

        Sin embargo, incluso si desactivamos el temporizador A, la fuente seguirá enviando una solicitud de interrupción que no será ejecutada por el microprocesador 6510.

        Entonces en nuestra Rutina de administración de interrupciones de ráster en Ensamblador, inmediatamente después de las instrucciones que queremos que se ejecuten cuando ocurra cada interrupción de ráster, debemos verificar el bit 0 del Registro de control de interrupciones CIA 1 (CIAICR) mapeado a la dirección 56333 ($dc0d).

        Si el bit 0 está en 1, significa que el temporizador A ha generado una solicitud de interrupción y, por lo tanto, nuestra rutina saltará (JMP) a la rutina IRQ normal que comienza en $ea31.

        De lo contrario, si el bit 0 está en 0, haremos “pull” de la pila de los 3 registros de datos Y, X y A y luego saldremos de nuestra rutina a través de RTI.

        ¡El juego está terminado!

        La parte del código SETUP que antes formaba parte del Routine en Ensamblador ahora se ha insertado en el BASIC Loader.

        En la Rutina BÁSICA, luego de haber cargado la Rutina en Ensamblador en el Cassette Buffer (Dirección 828), deshabilito la Interrupción del Temporizador A, cambio el Vector de la Rutina IRQ, configuro la primera Interrupción Raster a RasterLine 1 y finalmente habilito la Interrupción Raster en el Registro 53274 ( $ d01a), el registro de habilitación de interrupciones.

        Al comienzo de la Rutina de administración de interrupciones de ráster en Ensamblador, ya no necesitaré verificar si mi Rutina realmente ha sido recuperada de la fuente de comparación de ráster, ya que he deshabilitado la fuente de la solicitud de interrupción del temporizador A en el cargador BASIC (como Lo mencioné antes).

        A continuación encontrará las fuentes del BASIC Loader y el Routine en Ensamblador del Raster Interrupt.

        Los comentarios los he escrito en inglés para que todos puedan entenderlos.

        Saludos

        Attilio Capuozzo

        Fundador de Grupo Facebook “RetroProgramming Italia – RP Italia”

        BASIC Loader:

        10 REM GENERATED ML LOADER

        20 SA = 828

        30 FOR N = 0 TO 48

        40 READ A% : POKE SA+N,A%: NEXT N

        50 DATA 169,1,141,25,208,173,18,208

        60 DATA 16,13,162,1,160,7,142,18

        70 DATA 208,140,33,208,24,144,10,162

        80 DATA 145,160,2,142,18,208,140,33

        90 DATA 208,173,13,220,41,1,208,6

        100 DATA 104,168,104,170,104,64,76,49

        110 DATA 234

        120 poke 56333,127: rem Disable Timer A Interrupt (Keyboard Scan)

        130 poke 788,60:poke 789,3: rem Change IRQ Interrupt Vector

        140 poke 53265,peek(53265)and127: rem Clear MSB Raster Register ($d011)

        150 poke53266,1: rem First Raster Interrupt at RasterLine 1

        160 poke53274,129: rem Enable Raster Interrupt

        RASTER INTERRUPT ROUTINE (Start Address $033c/828 Cassette Buffer)

        * = $033c

        RASREG = $D012

        SCREEN = $D021

        INTFLAGREG = $D019

        init

        lda #1

        sta INTFLAGREG ; Reset raster interrupt latch

        lda RASREG

        bpl rasterCompareTop ; If MSB Raster Register=0 then the current RasterLine is <= 127 (Top of Screen)

        ldx #1 ; Load X Register for next Raster Interrupt (Raster Compare=1)

        ldy #7 ; Load Y Register with Color Code 7=Yellow (Screen Color Register $d021)

        stx RASREG

        sty SCREEN

        clc

        bcc checkTimerAInt

        rasterCompareTop

        ldx #145 ; Next Raster Interrupt at RasterLine 145

        ldy #2 ; Screen Color 2=Red

        stx RASREG

        sty SCREEN

        checkTimerAInt ; Check if a Timer A Interrupt has occurred in the meantime

        lda $dc0d

        and #1

        bne normalIRQRoutine ; Perform Normal IRQ Interrupt Routine if Timer A Interrupt has occurred

        exit ; Pull from Stack of Data Registers Y, X and A + ReTurn from Interrupt

        pla

        tay

        pla

        tax

        pla

        rti

        normalIRQRoutine

        jmp $ea31

        Me gusta

  4. Hola Attilio.

    En tu comentario pones de manifiesto muchas cosas interesantes:

    1) Que el parpadeo se produce por interferencia de la IRQ del temporizador del CIA1 (la que escanea el teclado 60 veces / segundo) con tu interrupción ráster.

    2) Que al hacer uso del teclado se nota que el parpadeo es mayor. Efectivamente, esto lo pude comprobar el otro día al probar tu programa.

    3) Que es posible darle prioridad a tu rutina de gestión de la interrupción ráster sobre la IRQ del temporizador A del CIA1.

    4) Para lo anterior, desactivas las interrupciones del temporizador A del CIA1 y, tras ejecutar tu rutina de interrupción raster, verificas si el temporizador A del CIA1 ha llegado a cero (registro $dc0d), en cuyo caso ejecutas las IRQ normal con jmp $ea31.

    Pues me parece muy bien. Es una forma muy inteligente de darle prioridad a tu interrupción raster sobre la interrupción del teclado, evitando así el parpadeo. Y, a la vez, no dejar de atender el teclado, el cual puede ser necesario en muchas situaciones.

    ¿Podría ocurrir ahora que el «parpadeo» (entre comillas) se sintiera en la lectura del teclado? Es decir, ¿podría ocurrir ahora que nos dejáramos alguna tecla pulsada sin leer?

    Puedes tener en cuenta, también, que no es necesario recurrir al temporizador A del CIA1 para leer el teclado. El teclado también se puede leer de forma directa (ver https://programacion-retro-c64.blog/2019/06/14/cia1-lectura-del-teclado/), si bien suele resultar muy cómodo apoyarse en los servicios del «sistema operativo».

    Enhorabuena, muy buena aportación.

    Un saludo, HVSW.

    Me gusta

    1. Hola de nuevo, Attilio.

      Siguiendo con este hilo, en el segundo libro de Derek Morris hay un apartado dedicado a las interrupciones en general y a las interrupciones raster en particular. Quisiera comentarlo.

      Como es sabido, el proceso de interrupciones IRQ completo es así (ver https://programacion-retro-c64.blog/2018/12/10/interrupciones/):

      – Se produce una interrupción.
      – Se guardan el contador de programa y el registro de estado en la pila.
      – Se ejecuta el código apuntado por $fffe – $ffff (ROM), que tienen los valores $48 y $ff.
      – Se ejecuta el código en $ff48, que hace todo lo que sigue.
      – Se guardan el acumulador, el registro X y el registro Y en la pila.
      – Se analiza si la interrupción IRQ es hardware o software mediante el flag B – break.
      – En el caso software salta a la dirección apuntada por $0316 – $0317 (RAM).
      – En el caso hardware salta a la dirección apuntada por $0314 – $0315 (RAM).

      Por tanto, si modificamos el contenido de las posiciones $0314 – $0315 (RAM) y apuntamos a una rutina propia, podemos conseguir que se ejecute nuestra rutina de interrupción personalizada. Esta modificación hay que hacerla con cuidado para evitar que se atiendan interrupciones durante el cambio (instrucciones «sei» y «cli»).

      Y lo normal es que esa rutina de interrupción personalizada haga algo así:

      – Un procesamiento particular, el que se quiera.
      – Y termine con a «jmp $ea31».

      $31 y $ea son el contenido normal de $0314 – $0315. Por tanto, lo que estamos haciendo es encadenar nuestra rutina de interrupción personalizada con la rutina de interrupción normal del sistema, que se encarga de activar el cursor, leer el teclado, actualizar el reloj, etc. Y termina recuperando los registros desde la pila y haciendo «rti».

      Todo esto es conocido. Lo que interesa resaltar ahora es que en todo ese proceso hay mucho código del sistema operativo del C64, tanto antes de llamar a la rutina de interrupción personalizada como después, al llamar a «jmp $ea31». Esto hace que las rutinas de interrupción personalizadas sean lentas.

      Una manera de acelerar el proceso es así (y esto es lo que comenta Derek Morris en su libro):

      – Desactivando el Kernal. Y como el BASIC se apoya en el Kernal habrá que desactivarlo también.
      – Al desactivar el Kernal, su ROM se sustituye por RAM. Por tanto, ya podemos poner en $fffe – $ffff lo que queramos. Podemos apuntar a nuestra rutina de interrupción personalizada directamente, sin pasar por $ff48.
      – En nuestra rutina de interrupción personalizada, tendremos que empezar guardando los registros en la pila, luego hacer lo que nos interese (ej. cambiar el modo gráfico de la pantalla o multiplexar sprites), y terminar recuperando los registros y haciendo «rti».

      La forma de desactivar el Kernal y el BASIC está comentada aquí:

      Otros mapas de memoria avanzados

      Un saludo, HVSW.

      Me gusta

Deja un comentario