Punteros de sprites

En la entrada anterior hemos visto que un sprite se define con 64 bytes. La forma de obtener esos bytes es agrupando de 8 en 8, de izquierda a derecha y de arriba abajo, los bits/pixels de la matriz de 504 pixels, más un byte extra para alinear a 64.

¿Y ahora qué hacemos con esos 64 bytes? ¿Dónde los ubicamos en la memoria? ¿Cómo le indicamos al VIC dónde encontrarlos? La respuesta a estas preguntas es mediante los “punteros de sprites”.

Los “punteros de sprites” son 8 posiciones de memoria, una por sprite, que le indican al VIC dónde encontrar la definición de los sprites (sus 64 bytes). Pero, si una posición de memoria en el C64 se especifica mediante 16 bits (o 2 bytes), ¿cómo es posible usar sólo un byte para indicar dónde empieza la definición de un sprite?

La respuesta es que tiene truco. El VIC es capaz de direccionar 16 KB de memoria (no 64 KB, que es lo que puede direccionar el 6510). Y si dividimos 16 KB (16.384 bytes) en bloques de 64 bytes, salen 256 bloques. Y cada uno de esos 256 bloques se puede identificar con un solo byte, porque con un byte se pueden codificar 256 valores.

Por tanto:

  • La definición de los sprites la tenemos que ubicar en el banco de 16 KB direccionados por el VIC, que en el mapa de memoria estándar del C64 es el banco que va desde la posición $0000 hasta la $3fff (el banco más bajo de los 4 bancos de 16 KB).
  • La definición de los sprites no la podemos ubicar en cualquier posición dentro de esos 16 KB. Cada sprite tiene que empezar en una posición que sea múltiplo de 64.
  • El puntero al sprite sale de tomar la primera posición de la definición y dividirla por 64. Esta división tiene que dar un resultado entre 0 y 255 y, por tanto, puede almacenarse en un byte (el puntero al sprite).

Estos conceptos se describen gráficamente en la siguiente imagen:

Punteros sprites

Por tanto, si los 64 bytes de nuestra pulga los colocamos en $3f80, que es múltiplo de 64, su puntero tendría que valer 254.

Y nos queda una última cuestión: ¿dónde se almacena el puntero? Es decir, ¿dónde se almacena ese valor 254? La respuesta es que los punteros de los sprites se almacenan en los últimos 8 bytes (hay 8 sprites) del 1 KB correspondiente a la RAM de pantalla. Es decir, en el mapa de memoria estándar, en el que la RAM de pantalla ocupa las páginas 4, 5, 6 y 7, los punteros a los sprites se almacenan en las posiciones $07f8 – $07ff.

Por tanto, si queremos que el primer sprite (sprite 0) sea la pulga, en la posición $07f8 deberemos almacenar el valor 254, y en el bloque $3f80 – $3fbf deberemos almacenar los 64 bytes que definen la pulga.

Definición de sprites

Cada sprite se define mediante una matriz de 24 x 21 puntos o pixels, 24 en sentido horizontal y 21 en sentido vertical. Es decir, cada sprite es una matriz de 504 pixels, y cada pixel se codifica con un bit. El bit será 1 si el pixel está activado, y será 0 si el pixel está desactivado. Y así es como se define el aspecto gráfico del sprite.

A continuación, se muestra un ejemplo de diseño de un sprite, concretamente una pulga. En el ejemplo se han omitido los 0’s para facilitar la identificación de la figura:

1 1
1 1
1 1
1 1
1 1 1 1
1 1 1 1
1 1 1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1
1 1
1 1
1 1
1 1 1 1 1 1 1 1 1 1

Lógicamente, la definición del sprite tendrá que acabar en memoria bajo la forma de bytes de información. Esto se consigue de la siguiente manera: los bits o pixels se agrupan 8 en 8 de izquierda a derecha y de arriba abajo. Y cada grupo de 8 bits/pixels da lugar a un byte.

Por tanto, 504 bits equivalen a 63 bytes. Otra forma de obtener el mismo resultado es multiplicando los 3 bytes por fila por las 21 filas: 63 bytes.

En el ejemplo de la pulga, los 63 bytes que definen el sprite son los siguientes:

BYTE $00,$00,$00

BYTE $00,$00,$00

BYTE $00,$00,$00

BYTE $01,$00,$80

BYTE $00,$81,$00

BYTE $00,$42,$00

BYTE $00,$42,$00

BYTE $00,$5A,$00

BYTE $00,$3C,$00

BYTE $00,$7E,$00

BYTE $00,$5A,$00

BYTE $06,$7E,$60

BYTE $05,$7E,$A0

BYTE $04,$FF,$20

BYTE $02,$7E,$40

BYTE $02,$3C,$40

BYTE $02,$18,$40

BYTE $01,$00,$80

BYTE $01,$00,$80

BYTE $01,$00,$80

BYTE $1F,$00,$F8

En resumen, un sprite se codifica o define con 63 bytes. Pero 63 bytes es un número un poco extraño, porque no es potencia de dos, y ya sabemos que en informática casi siempre se utilizan potencias de dos. La potencia de dos más cercana a 63 es 64, porque 64 es 2^6.

De hecho, a la hora de almacenar en memoria los 63 bytes de un sprite no se puede hacer en una posición arbitraria, hay que hacerlo siempre empezando en una posición de memoria que sea múltiplo de 64. De otro modo, el VIC no sería capaz de localizar y tratar correctamente la información.

En definitiva, un sprite ocupa 63 bytes, pero en la práctica podemos actuar como si ocupara 64 bytes. Al último byte se le suele llamar “padding” o relleno, y a veces se utiliza para guardar la información del color.

Hay aplicaciones que permiten la definición y tratamiento de sprites, como es el caso del “Editor de Sprites” de CBM prg Studio. Merece la pena darse una vuelta por alguna de estas herramientas, porque facilitan mucho el trabajo con sprites. Por ejemplo, “Editor de Sprites” permite hacer cosas como:

  • Diseñar y visualizar sprites.
  • Obtener los bytes equivalentes en diferentes formatos (ficheros binarios, directivas “byte” del ensamblador, etc.).
  • Importar sprites desde diferentes formatos.
  • Definir el color (caso monocolor) o colores (caso multicolor).
  • Rotar sprites.
  • Voltear sprites.
  • Invertir sprites.
  • Expandir sprites.
  • Animar sprites.
  • Etc.

Sprite Editor

Sprites

Una de las capacidades más características del VIC son los “sprites”. Los sprites son objetos gráficos que se pueden mover por la pantalla.

El VIC soporta hasta 8 sprites a la vez, aunque con técnicas avanzadas como la “multiplexación de sprites” se pueden conseguir más sprites.

Con los sprites se puede hacer todo lo que sigue:

  • Definirlos.
  • Activarlos y desactivarlos.
  • Fijar su color. Pueden ser monocolor o multicolor.
  • Expandirlos.
  • Posicionarlos en pantalla y moverlos.
  • Animarlos.
  • Fijar sus prioridades.
  • Detectar colisiones.

Y todo esto es lo que vamos a ver en las entradas que siguen, empezando por la definición.

Color del fondo y del borde de la pantalla

Igual que es posible modificar el color de los caracteres en pantalla, también es posible modificar el color del borde y del fondo de la pantalla. Esto se hace con las posiciones:

  • Borde de pantalla: $d020.
  • Fondo de pantalla: $d021.

Por tanto, para cambiar el color del borde, llega con hacer un “sta”/”stx”/”sty” con el color deseado (ver tabla de colores de la entrada anterior) en la posición $d020. Y análogamente con el color de fondo y la posición $d021.

Mainframe


Programa de ejemplo: Prog30

RAM de color

En una entrada anterior hemos visto cómo pintar caracteres en pantalla, bien con las instrucciones “lda”/”sta”, bien con la rutina del Kernal “chrout”.

Pues bien, no sólo se pueden pintar caracteres. También se puede cambiar su color. Y para esto existe la RAM de color, que es una zona de 1000 posiciones RAM que determinan, una por una, el color de las 1000 posiciones correspondientes de la RAM de pantalla.

La RAM de color va desde la posición $d800 hasta la posición $dbe7. Por tanto, para fijar el color del carácter en la posición $0400, se debe fijar el color en $d800, y así sucesivamente.

Para fijar el color hay que almacenar en la posición elegida un byte con cualquiera de los 16 colores soportados por el C64 (en realidad sólo se utiliza el nibble menos significativo):

$00 – Negro $01 – Blanco $02 – Rojo $03 – Cyan (turquesa)
$04 – Morado $05 – Verde $06 – Azul $07 – Amarillo
$08 – Naranja $09 – Marrón $0a – Rojo claro $0b – Gris oscuro
$0c – Gris medio $0d – Verde claro $0e – Azul claro $0f – Gris claro

 
Colores


Programa de ejemplo: Prog29

Revisión de la situación

Hasta el momento hemos tratado los siguientes aspectos:

  • Introducción y objetivos del blog. Referencias.
  • El equipo de trabajo. CBM prg Studio y VICE.
  • Sistemas de numeración decimal, binario y hexadecimal.
  • El hardware del C64. La memoria y los registros del microprocesador.
  • Los modos de direccionamiento.
  • Las instrucciones del código máquina/ensamblador del 6510.
  • Subrutinas, macros e interrupciones.
  • Rutinas del Kernal.

En definitiva, nos hemos centrado en cómo es el código máquina/lenguaje ensamblador del C64 o, lo que es lo mismo, del microprocesador 6510.

A partir de ahora la temática cambia porque, una vez conocida la herramienta básica, se trata de aplicarla para hacer cosas: gráficos, sonido, entrada/salida, etc. Y estas capacidades las aportan los otros circuitos integrados del C64: el VIC (gráficos), el SID (sonido) y las CIAs (entrada/salida).

Como ya se comentó en su momento, a efectos del microprocesador, el resto de circuitos integrados no son más que posiciones de memoria. Por tanto, se manejan con instrucciones de lectura y escritura de datos (“lda”, “sta” y similares). Pero para poder usarlos, hay que conocer sus registros, qué hace cada uno de ellos, y en qué direcciones están.

Y esta será la temática principal a partir de ahora…

Códigos de pantalla vs caracteres PETSCII

A la hora de imprimir caracteres en pantalla tenemos dos opciones:

  • Hacerlo en una posición conocida, es decir, en cualquiera de las 1000 posiciones de la RAM de pantalla ($0400 – $07e7).
  • Hacerlo donde esté el cursor actualmente.

Para la primera opción usaremos directamente las instrucciones “lda” y “sta”, o sus variantes para los registros X e Y. Y para la segunda opción usaremos la rutina “chrout” del Kernal.

Pues bien, según la técnica que estemos usando debemos usar códigos de pantalla (“screen codes”) o la tabla de caracteres PETSCII. Como se podrá ver son tablas diferentes, porque en el primer caso el código correspondiente a la “A” es 1 y en el segundo caso es 65.

Códigos de pantalla

Cuando usamos “lda” y “sta” para poner un carácter en la pantalla (posiciones $0400 – $07e7) lo que estamos haciendo es exactamente lo mismo que cuando hacemos un “sta” con cualquier otra posición de memoria, es decir, simplemente almacenar ahí un byte.

Para los que sepan BASIC estamos hablando de hacer lo mismo que un “POKE 1024, 1”.

Lo que ocurre es que, si la posición utilizada pertenece a la RAM de pantalla, el VIC interpreta eso como que tiene que pintar determinado carácter en esa posición. Y el VIC pinta un carácter u otro en función de la tabla de códigos de pantalla.

Básicamente lo que hace el VIC es que lee el byte ahí almacenado (ej. 1), consulta su mapa de definición de caracteres, que está en ROM, y pinta el carácter (matriz de pixels) que corresponde a ese valor (ej. “A”).

El mapa de caracteres del C64 tiene 2 subconjuntos (a matizar en breve):

  • Las letras mayúsculas, los números y otros caracteres gráficos. Códigos 0 hasta 127.
  • Las versiones invertidas del punto anterior. Códigos 128 hasta 255.
Screen codes 1 Screen codes 2

Lo anterior (letras mayúsculas) es la opción por defecto al arrancar, pero se pueda cambiar pulsando SHIFT y la tecla Commodore (SHIFT y la tecla Windows en VICE). Eso sí, o las letras son todas mayúsculas, o todas minúsculas, pero no se pueden mezclar. Por eso es suficiente con un byte para codificar 512 caracteres (mayúsculas, mayúsculas invertidas, minúsculas y minúsculas invertidas).

Screen codes 1 Screen codes 2

Caracteres PETSCII

En este caso la historia es distinta porque vamos a través de la rutina “chrout”. A esta rutina, por diseño, le deben llegar caracteres y no códigos de pantalla.

El equivalente en BASIC sería hacer un “PRINT ‘A’”. Lo que pasa es que en BASIC hay números y hay cadenas de caracteres (variables numéricas y alfanuméricas), pero en ensamblador todo vienen a ser bytes.

El C64 no sigue el estándar ASCII (caracteres de 7 bits), pero tiene un estándar equivalente llamado PETSCII. Además, la tabla PETSCII es compatible con ASCII en lo fundamental, es decir, usa los mismos valores que ASCII para identificar letras, números y algunos caracteres de control. A esto el C64 añade sus muy característicos caracteres gráficos.

PETSCII

No conozco los detalles internos de la rutina “chrout” (se podrían consultar desensamblando el código a partir de la posición $f1ca), pero me puedo imaginar que lo que hace es determinar la posición de pantalla en función de la posición del cursor, obtener el código de pantalla que corresponde al carácter PETSCII recibido como parámetro, almacenarlo en esa posición de memoria, y mover el cursor.

CBM prg Studio y text

En CBM prg Studio, y en cualquier ensamblador, es posible definir el contenido de determinadas posiciones o zonas de memoria con directivas como “text” (para textos), “byte” (para bytes), “word” (para palabras, es decir, dos bytes), etc.

En particular, “text” vale para definir textos que luego van a ser leídos e impresos en pantalla mediante un programa en ensamblador. Por ejemplo:

texto1 text “En un lugar de La Mancha…”
texto2 text ‘En otro lugar de La Mancha…’

Pues bien, si lo que desea el programador es que CBM prg Studio almacene ahí códigos de pantalla, entonces debe usar comillas simples. Por el contrario, si lo que quiere el programador es que CBM prg Studio almacene caracteres PETSCII, entonces debe usar comillas dobles.


Programa de ejemplo: Prog28

Rutinas del Kernal

El sistema operativo del C64 básicamente consta de dos piezas:

  • El intérprete de BASIC.
  • El Kernal.

El intérprete de BASIC es un programa en ROM que interpreta, es decir, traduce y ejecuta, los programas en BASIC. En este blog estamos más interesados en el ensamblador y el código máquina del C64, básicamente porque el BASIC es fácilmente accesible para casi todo el mundo. El BASIC nos interesa tangencialmente, principalmente porque desde BASIC se puede ejecutar (comando SYS) e interactuar con el código máquina.

El Kernal (hoy en día diríamos “Kernel”) es un conjunto de rutinas en código máquina, también ubicadas en ROM, que dan servicios básicos al programador. El listado de estas rutinas se puede consultar en la dirección http://sta.c64.org/cbm64krnfunc.html.

Como se puede observar en el listado anterior, cada rutina aparece descrita con:

  • Nombre.
  • Descripción de la función.
  • Parámetros de entrada.
  • Parámetros de salida.
  • Registros modificados (ya se explicó en su momento que, salvo que se tomen medidas para evitarlo, todas las rutinas en general pueden modificar los registros).
  • Dirección.
  • Dirección real.

Hay dos direcciones, una “dirección sin más” y una “dirección real”, porque el Kernal utiliza una tabla de saltos o “jump table”.

Así, para llamar la rutina “chrout”, por ejemplo, los programadores normalmente harán “jsr $ffd2”, o “jsr chrout” con la constante chrout = $ffd2, que es la dirección de entrada a la “jump table”. Pero si se analiza el contenido de la posición $ffd2 y siguientes se verá que es así:

$FFD2 6C 26 03 JMP ($0326)

Es decir, se hace un salto (“jmp”) a la dirección apuntada por el vector o puntero $0326 – $0327 que, mediante los comandos BASIC PRINT PEEK(806) y PRINT PEEK(807), se puede deducir que apunta a $f1ca (recuérdese el orden «Little endian»), que es precisamente la “dirección real” que aparece en la ficha de “chrout”.

Chrout

En resumen, podemos llamar a la rutina de forma directa (dirección $f1ca) o a través de la “jump table” (dirección $ffd2). Al hacerlo de esta segunda manera tenemos la ventaja (en realidad teníamos) de que, si un día Commodore cambiaba la ubicación de las rutinas en futuros modelos, los programas seguirían funcionando.

Por lo demás, el Kernal tiene muchas rutinas. Se anima al lector a revisarlas y probarlas. Dos de las más utilizadas son:

  • CHROUT. Sirve para imprimir un carácter en la posición del cursor.
  • GETIN. Sirve para leer un carácter del teclado.

Getin


Programa de ejemplo: Prog18

Otras instrucciones

Según mis cuentas, las últimas instrucciones del 6510 que nos quedan por revisar son:

  • “bit”.
  • “nop”.

La instrucción “bit” es similar a la instrucción “and”, pero con algunas diferencias. “and” hace el AND bit a bit del acumulador y de una posición de memoria (o un valor en el modo inmediato), altera los flags del registro de estado consecuentemente, y almacena el resultado de ese AND en el acumulador. Pues bien, “bit” viene a hacer básicamente lo mismo (acumulador AND posición de memoria) pero sin almacenar el resultado del AND en el acumulador.

En el fondo “bit” viene a ser una manera de comprobar el contenido de una posición de memoria, o de ciertos bits de esa posición de memoria. Con “and” se podría conseguir el mismo efecto, pero si no interesa el resultado del AND, sino sólo ver cómo se alteran los flags, la forma más directa es usar “bit”.

De hecho, por ese motivo no se ha considerado “bit” como una instrucción para hacer operaciones lógicas (como “and”, “or” y “eor”), porque no da acceso al resultado del AND.

“bit” soporta menos modos de direccionamiento que “and”, ya que sólo soporta los modos absoluto y página cero. Modifica los flags S – sign, V – overflow, y Z – zero, si bien sólo Z – Zero se deriva del resultado del AND, ya que S – Sign y V – overflow se derivan directamente de los bits 7 y 6 de la posición de memoria referenciada. Sus detalles se pueden consultar en http://www.6502.org/tutorials/6502opcodes.html#BIT.

Por último, la instrucción “nop” es la más sencilla de todas, ya que no hace nada. Vale para rellenar zonas de memoria con algún valor conocido o para dejar inutilizado código máquina previamente existente.

La instrucción “nop” sólo utiliza el modo implícito y no modifica ningún flag. Sus detalles se pueden consultar en http://www.6502.org/tutorials/6502opcodes.html#NOP.


Programa de ejemplo: Prog27

Interrupciones

Una “interrupción” consiste en dejar de ejecutar el programa que está en curso para pasar a ejecutar una “rutina de interrupción”. Tras ejecutarse la rutina de interrupción continúa la ejecución del programa original allí donde se interrumpió.

Las rutinas de interrupción típicamente son rutinas del sistema (por ejemplo, para hacer que parpadee el cursor de BASIC, o para leer el teclado, etc.), pero como veremos más adelante el usuario puede modificarlas o incluso sustituirlas por otras propias.

Para que ocurra una interrupción, es necesario que se active una señal sobre uno de los dos “pines” (patillas) del microprocesador 6510 que tienen que ver con las interrupciones:

  • El pin IRQ.
  • El pin NMI.

Algunos chips del C64 (ej. CIAs) generan interrupciones de forma periódica, y otros (ej. VIC) cuando ocurre algún evento de interés para el programador.

Interrupciones IRQ por hardware

IRQ significa “interrupt request” (petición de interrupción). Cuando se activa este pin, si el flag I – interrupt del registro de estado está desactivado (instrucción “cli”), deja de ejecutarse el programa actual, se ponen a salvo los registros en la pila, y se ejecuta la rutina de interrupción.

Si, por el contrario, el flag I – interrupt estuviera activado (instrucción “sei”), la interrupción IRQ no sería atendida. Por ello a las interrupciones de tipo IRQ también se les llama “enmascarables”.

Interrupciones IRQ por software

Igual que es posible generar interrupciones IRQ por hardware, también es posible hacerlo por software. Para ello, hay que ejecutar la instrucción “brk”.

Cuando se ejecuta la instrucción “brk” ocurre básicamente lo mismo que cuando se activa el pin IRQ, con las diferencias de que se activa el flag B – break en el registro de estado, y que las interrupciones “brk” no son enmascarables mediante el flag I – interrupt.

La instrucción “brk” utiliza el modo de direccionamiento implícito, y sólo afecta al flag B – break. Sus detalles se pueden consultar en http://www.6502.org/tutorials/6502opcodes.html#BRK.

Rutina de interrupción IRQ

Más en detalle, al ejecutarse una interrupción IRQ hardware o software ocurre lo siguiente:

  • El contador de programa se guarda en la pila. Primero el MSB y luego el LSB.
  • El registro de estado se guarda en la pila.
  • Se ejecuta el programa apuntado por las posiciones $fffe y $ffff.

Las posiciones $fffe y $ffff conforman el “vector de interrupción”. Estas posiciones están en ROM, y contienen los valores $48 y $ff respectivamente. Es decir, apuntan a la dirección $ff48, que también está en ROM.

El programa que empieza en $ff48 se llama “rutina de interrupción”, y resumidamente hace esto:

  • Guarda el acumulador en la pila.
  • Guarda el registro X en la pila.
  • Guarda el registro Y en la pila.
  • 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 y $0317.
  • En el caso hardware salta a la dirección apuntada por $0314 y $0315.

Por defecto, las posiciones $0316 y $0317 apuntan a $fe66, y las posiciones $0314 y $0315 apuntan a $ea31. Pero las posiciones $03xx están en RAM y, por tanto, su contenido se puede modificar, modificando así el algoritmo de la rutina de interrupción o incluso sustituyéndolo por otro completamente nuevo.

Esta modificación hay que hacerla con cuidado, porque podrían tener lugar interrupciones en mitad de la modificación. Por ello, suele hacerse dentro de un trozo de código que empieza por “sei” (para inhabilitar temporalmente las interrupciones) y termina con “cli” (para volver a habilitarlas ya con la nueva rutina de interrupción).

De hecho, muchas veces en vez de llamar “vector de interrupción” a $fffe – $ffff, se suele llamar “vector de interrupción” a $0314 – $0315, y en vez de llamar “rutina de interrupción” al programa del sistema esbozado arriba (el que empieza en $ff48), se suele llama “rutina de interrupción” al programa de usuario apuntado por $0314 – $0315. Esto se hace así porque, en el fondo, lo que interesa al usuario son las posiciones $0314 – $0315 y el programa apuntado por este vector. Lo demás es código del sistema que no se puede modificar.

Instrucción “rti”

La rutina de interrupción, ya sea la rutina de interrupción del C64, o una propia del usuario, tiene que terminar con la instrucción “rti”.

Cuando se ejecuta esta instrucción ocurre lo siguiente:

  • Se recupera desde la pila el registro de estado.
  • Se recupera desde la pila el contador de programa. Primero el LSB y luego el MSB.
  • Continúa la ejecución en ese contador de programa, es decir, donde tuvo lugar la interrupción.

La instrucción “rti” usa el modo de direccionamiento implícito, y afecta a todos los flags porque recupera el registro de estado desde la pila. Sus detalles pueden consultarse en http://www.6502.org/tutorials/6502opcodes.html#RTI.

Adicionalmente, antes de la instrucción “rti” lo correcto sería recuperar los registros Y, X y acumulador desde la pila, dado que sólo así podrá continuar la ejecución en exactamente las mismas condiciones que cuando se produjo la interrupción.

Lo anterior es cierto si la “rutina de interrupción de usuario” es una rutina de interrupción completa, que sustituye a la del sistema. Sin embargo, en ocasiones se ven rutinas parciales que ejecutan un “trozo de código” adicional antes que la rutina habitual del C64, y que terminan con un “jmp $ea31” (es decir, con el resto de la rutina habitual del sistema). En este caso se puede confiar en que la recuperación de registros y el “rti” serán ejecutados por la rutina habitual del sistema.

Todo esto quedará más claro con los ejemplos…

Interrupciones NMI

NMI significa “non-maskable interrupts”, es decir, interrupciones no enmascarables. Son interrupciones hardware igual que las IRQ, pero con algunas diferencias:

  • Se activan con el pin NMI, no con el pin IRQ.
  • No son enmascarables, es decir, no les afecta el flag I – interrupt.
  • El vector de interrupción es $fffa – $fffb, no $fffe – $ffff.

Programas de ejemplo: Prog25 y Prog26