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.
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 gustaMe gusta
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.
Me gustaLe gusta a 1 persona
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.
Me gustaLe gusta a 1 persona
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”
Me gustaLe gusta a 1 persona
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.
Me gustaLe gusta a 1 persona
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 gustaMe gusta
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.
Me gustaLe gusta a 1 persona
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 gustaMe gusta
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 gustaMe gusta
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í:
https://programacion-retro-c64.blog/2020/01/13/otros-mapas-de-memoria-avanzados/
Un saludo, HVSW.
Me gustaMe gusta