Los paddles

Otro periférico curioso y ochentero son los paddles:

Paddles

Lo primero que llama la atención es que, usando un único conector para puerto de control, hay una pareja de paddles. Por tanto, como el C64 tiene dos puertos de control, en total es posible conectar cuatro paddles (dos parejas).

Lo segundo que llama la atención es que un paddle es básicamente una rueda y un disparador. La rueda es lo que técnicamente se llama un potenciómetro, es decir, una resistencia variable. Al mover la rueda cambia la resistencia y, consecuentemente, un voltaje.

Este voltaje variable llega a través del CIA1 al SID, que dispone de un conversor analógico digital, es decir, un dispositivo electrónico capaz de convertir una señal analógica (el voltaje variable) en una señal digital (un número almacenado en un registro).

Como hay dos paddles, hacen falta dos registros en los que almacenar el resultado de la conversión analógico – digital, que son:

  • POTX = $d419.
  • POTY = $d41a.

Obsérvese que las direcciones de estos dos registros ($d419 y $d41a) son parte del rango de direcciones del SID ($d400 – $d41c). Por otro lado, el prefijo “POT” viene de “potenciómetro”.

Estos dos registros POTX y POTY sirven para leer tanto la pareja de paddles conectada al puerto de control #1 como la pareja de paddles conectada al puerto de control #2. Por tanto, hace falta algún mecanismo para conmutar entre una pareja y otra.

Ese mecanismo es el puerto A del CIA1, es decir, el registro CIAPRA = $dc00. Si en los bits 6 y 7 de CIAPRA escribimos %01, se leen los paddles del puerto de control #1. En cambio, si escribimos %10 en esos mismos bits, se leen los paddles del puerto de control #2.

Ahora bien, dado que el CIA1 y más en particular su registro CIAPRA = $dc00 intervienen en la lectura del teclado, cosa que el sistema operativo hace 60 veces por segundo mediante una rutina de interrupción, para que la selección de los paddles sea correcta y no interfiera con la lectura del teclado, hay que desactivar esa interrupción antes de seleccionar y leer los paddles, y volver a activarla después.

La desactivación de la interrupción se consigue escribiendo el valor %01111111 = 127 sobre el registro de control de interrupciones del CIA1 (CIAICR = $dc0d). Y la reactivación se consigue escribiendo el valor %10000001 = 129. Recordemos el uso del CIA1 como fuente de temporización.

Por último, quedan los disparadores. Para leerlos, también se usa el CIA1, concretamente:

  • CIAPRA = $dc00, bit 2 = 0 => disparo del paddle 1.
  • CIAPRA = $dc00, bit 3 = 0 => disparo del paddle 2.
  • CIAPRB = $dc01, bit 2 = 0 => disparo del paddle 3.
  • CIAPRB = $dc01, bit 3 = 0 => disparo del paddle 4.

En resumen, y como digo, otro periférico bien curioso. Sólo recuerdo haberlo utilizado para un juego de coches llamado “Lemans”. En este juego se utilizaba como volante para dirigir los coches:

lemans_07

Funcionamiento del lápiz óptico

No sé si alguna vez os habréis planteado cómo funciona un lápiz óptico, uno de esos dispositivos que se usaban en los 80 para pintar en pantalla.

Lápiz óptico

Es un periférico que siempre me ha llamado mucho la atención, porque nunca había tenido muy claro cómo funcionaba. Y no me refiero tanto desde el punto de vista electrónico (básicamente es un fototransistor), sino más bien desde el punto de vista de cómo el C64 podía determinar sobre qué posición X, Y de la pantalla estaba apoyado.

Recordemos que lo que había en los 80 eran televisiones de tubo de rayos catódicos (CRT), no pantallas táctiles como la que tenemos ahora…

La cosa parece magia, pero es más sencilla de lo que parece:

Ya sabemos lo que es el raster: el rayo con el que el VIC va actualizando la pantalla. Y ya sabemos, también, que la línea por la que va el raster se puede conocer con los registros SCROLY = $d011 (bit 7) y RASTER = $d012 (bits 0 a 7).

Pero es que el VIC no sólo conoce la línea que va actualizando (Y). También conoce la columna (X). Por tanto, ya tenemos todas las piezas del puzle:

  • El usuario conecta el lápiz al puerto de control #1.
  • El usuario pone el lápiz en una posición X, Y de la pantalla.
  • Cuando el raster o el rayo catódico pasa por la posición del lápiz (cosa que hace 60 veces / segundo) activa su fototransistor, que genera una señal al puerto de control #1.
  • La señal llega al VIC, que guarda en sus registros LPENX = $d013 y LPENY = $d014 la posición X, Y por la que iba el raster en el momento de recibir la señal.
  • La aplicación que usa el lápiz, lee de LPENX y LPENY las coordenadas X, Y del lápiz, y actúa en consecuencia, por ejemplo, pintando un pixel en esa posición.

¿¿No es curioso y astuto??

Otra curiosidad es que LPENX y LPENY son bytes. Por tanto, pueden tomar los valores 0 a 255. Sin embargo, sabemos que la resolución del VIC en modo bitmap estándar es de 200 x 320 pixels. Es decir, con un byte tenemos suficiente para la coordenada Y (200 filas), pero no tenemos suficiente para la coordenada X (320 columnas).

Por ello, la resolución con que se determina X es de dos pixels, y el valor que se almacena en LPENX (de 0 a 160) debe multiplicarse por dos para obtener la coordenada X del lápiz.

Bancos direccionados por el VIC

Todo lo que hemos visto en la entrada anterior tiene que ver con los mapas de memoria direccionados por el microprocesador 6510. Como hemos visto, se pueden direccionar bancos de ROM o sustituirlos por la RAM que está “debajo”.

Por si esto fuera poco, el VIC también admite diferentes configuraciones de memoria. Para empezar, el VIC es capaz de direccionar 16K y, como el C64 direcciona 64K, eso significa que admite cuatro configuraciones básicas o cuatro bancos.

Pero es que, además, para cada uno de los cuatro bancos posibles, es posible recolocar los juegos de caracteres personalizados, los bitmaps, la RAM de pantalla, los sprites, etc. No ocurre lo mismo con la RAM de color, que siempre tiene que estar en el rango $d800 – $dbe7.

Bancos del VIC:

La forma de seleccionar el banco de 16K direccionado por el VIC es actuando sobre los bits 0 y 1 del registro CI2PRA = $dd00 del CIA2:

  • %00: $c000 – $ffff
  • %01: $8000 – $bfff
  • %10: $4000 – $7fff
  • %11: $0000 – $3fff (valor por defecto)

Bancos VIC

Juegos de caracteres personalizados:

La forma de ubicar los juegos de caracteres personalizados, como ya vimos en su momento, es actuando sobre los bits 1, 2 y 3 del registro VMCSB = $d018. Esto da lugar a 2^3 = 8 posibles valores o ubicaciones.

Estas ocho posibles ubicaciones, lógicamente, son relativas al comienzo del banco de 16K que esté direccionando el VIC:

Juegos caracteres

Bitmaps:

La forma de ubicar los bitmaps, como también vimos en su momento, es actuando sobre el bit 3 del registro VMCSB = $d018. Esto da lugar a dos posibles valores o ubicaciones.

Estas dos posibles ubicaciones, nuevamente, son relativas al comienzo del banco de 16K que esté direccionando el VIC:

Bitmaps

RAM de pantalla y punteros de sprites:

La forma de ubicar la RAM de pantalla es actuando sobre los bits 4, 5, 6 y 7 del registro VMCSB = $d018. Esto da lugar a 2^4 = 16 posibles valores o ubicaciones:

  • %0000: $0000 – $03ff
  • %0001: $0400 – $07ff (valor por defecto)
  • %0010: $0800 – $0bff
  • %1111: $3c00 – $3fff

Estas dieciséis posibles ubicaciones, una vez más, son relativas al comienzo del banco de 16K que esté direccionando el VIC:

RAM pantalla

La RAM de pantalla, en realidad, no ocupa 1K = 1.024 bytes, sino que sólo ocupa 1.000 bytes (25 filas x 40 columnas). Por tanto, sobran 24 bytes. Entre esos 24 bytes están los ocho punteros a los sprites. Más concretamente, los punteros a los sprites son los últimos ocho bytes de los 1.024 configurados en VMCSB.

Definición de los sprites:

No sólo es posible reubicar los punteros de los sprites dentro de los 16K direccionados por el VIC, cambiando la ubicación de la RAM de pantalla.

Por supuesto, también es posible reubicar la propia definición gráfica de los sprites (64 bytes por sprite). Esto es posible, precisamente, porque esa definición no ocupa una posición fija, sino que ocupa una posición variable (siempre dentro de los 16K direccionados por el VIC) que viene indicada o especificada por los punteros de los sprites.

En definitiva, entre las posibilidades de configuración de la memoria del 6510 y las del VIC, la flexibilidad es casi absoluta, como hemos visto.

Otros mapas de memoria avanzados

En una de las primeras entradas del blog describimos el mapa de memoria estándar del C64. Este mapa de memoria es así:

Memoria

Es decir, consta de:

  • La página 0, que contiene muchas variables o posiciones que se usan desde el intérprete de BASIC y desde el Kernal.
  • La página 1, que se usa para la pila de llamadas a subrutinas.
  • 40K de RAM, que incluyen la RAM de pantalla en su ubicación estándar ($0400-$07e7) y 38K de RAM para programas en BASIC ($0801-$9fff).
  • El intérprete de BASIC en ROM.
  • 4K de RAM donde, de forma muy habitual, se han ubicado los programas en código máquina, al menos, los programas pequeños.
  • Los chips especiales para vídeo, sonido y entrada / salida. También la RAM de color.
  • El Kernal en ROM.

Este es el mapa estándar, es decir, el que ofrece el C64 recién arrancado. Ahora bien, una de las características clave del C64 es que soporta diferentes mapas de memoria en función de las necesidades de la aplicación. De hecho, el microprocesador 6510 es esencialmente un 6502, con el mismo juego de instrucciones, pero con capacidad para gestionar diferentes mapas de memoria.

De esta gestión se encargan los registros:

  • D6510 = $0000.
  • R6510 = $0001.

El primero es un DDR o registro de dirección de datos. Es decir, sus bits sirven para configurar si los bits de un IOP o puerto de entrada / salida asociado, en este caso el puerto R6510 = $0001, son de entrada o salida. Su valor por defecto es 47 = %00101111, lo que significa que todos los bits del puerto R6510 son de salida (1), salvo el 4, que es de entrada (0); los bits 6 y 7 no se usan.

Por su lado, el registro R6510 = $0001 es el puerto de entrada / salida propiamente dicho. En este caso, en función de los valores 0 o 1 que se configuren en sus bits, se consigue que el microprocesador 6510 direccione unos u otros bancos de memoria, en particular, bancos de ROM (lo normal) o RAM (opcional).

En particular, los bits del puerto R6510 = $0001 son:

Bit Nombre bit Si vale 0 Si vale 1
0 LORAM Las direcciones $a000 – $bfff direccionan RAM Las direcciones $a000 – $bfff direccionan la ROM con el intérprete de BASIC
1 HIRAM Las direcciones $e000 – $ffff direccionan RAM Las direcciones $e000 – $ffff direccionan la ROM con el Kernal
2 CHAREN Las direcciones $d000 – $dfff direccionan la ROM con el mapa de caracteres Las direcciones $d000 – $dfff direccionan los chips especiales y la RAM de color

El registro R6510 tiene más bits (3, 4 y 5), pero tienen que ver con el datasette, y no con los mapas de memoria. Los bits 6 y 7 no se utilizan.

Extensiones de RAM:

Total, configurando un 0 en el bit 0 de R6510 podemos eliminar el intérprete de BASIC y ganar 8K de RAM adicionales. Igualmente, configurando un 0 en el bit 1 de R6510 podemos eliminar el Kernal y ganar otros 8K de RAM adicionales.

Podemos eliminar sólo el BASIC o BASIC y Kernal. Lo que no podemos hacer es mantener el BASIC y quitar el Kernal, puesto que el BASIC se apoya en éste.

De este modo, si tenemos un programa en ensamblador / código máquina que es muy grande, podemos adaptar el mapa de memoria a nuestras necesidades:

Memoria - sin BASIC

Memoria - sin BASIC ni Kernal

Y si aun así sigue sin caber en RAM, tendremos que organizar el programa en varios “trozos” (por ejemplo, pantallas 1 – 10, pantallas 11 – 20, etc.) de modo que en cada momento tengamos cargada en RAM sólo la parte que estamos usando y, mediante cargas sucesivas, ir cambiando de parte.

Todos estos mapas de memoria, y otras posibilidades que hay si tenemos en cuenta los cartuchos de expansión, se describen en las páginas 260 y siguientes del “Programmer’s Reference Guide”.

Modelo de “RAM bajo ROM”:

Al modelo de memoria del C64 se le llama de “RAM bajo ROM”. Esto no sólo significa que haya bloques de RAM que están ocultos detrás de otros bloques de ROM y que, desactivando la ROM, se pueda usar la RAM.

Es que, además, y de forma sorprendente, se pueden usar los bloques de ROM y RAM a la vez. Veamos cómo:

RAM bajo ROM.PNG

Supongamos que tenemos los bloques de ROM activos (bits 0 y 1 de R6510 con valor 1). En esta situación, si leemos la posición $a000, puesto que la ROM está activa, estamos leyendo la ROM:

Lectura ROM

Pero si escribimos en la posición $a000, puesto que la ROM está activa, y la ROM es de sólo lectura, no tendría sentido escribir en la ROM. De hecho, es físicamente imposible escribir en la ROM. Por ello, lo que ocurre en realidad es que estamos escribiendo en la RAM que está “por debajo”:

Escritura RAM.PNG

De este modo, es perfectamente posible hacer cosas como:

  • Copiar el intérprete de BASIC de ROM a RAM.
  • Modificar el intérprete de BASIC ya copiado en RAM.
  • Desactivar la ROM de BASIC, activando la RAM.
  • Y usar el intérprete modificado en RAM.

Y los mismo con las rutinas del Kernal.

Por ejemplo, podemos modificar el “prompt” de BASIC de “READY.” a “C:/>” de este modo: (versión en ensamblador)

  • Copiamos el intérprete de BASIC de ROM a RAM:

Copia BASIC

  • Cambiamos el “prompt” en RAM (posiciones 41848 a 41853):

Cambia prompt.PNG

  • Desactivamos la ROM de BASIC, dando paso a la RAM:

Desactiva BASIC

A partir de ese momento, el “prompt” deja de ser “READY.” y pasa a ser “C:/>”. Esto es así porque se está ejecutando la versión modificada de RAM. Igualmente, podríamos modificar comandos de BASIC o añadir otros nuevos.

Mapa de caracteres en ROM:

Sin actuar sobre el registro R6510, el mapa de caracteres en ROM no está accesible. Es decir, sí está accesible para el VIC para pintar los caracteres en pantalla, pero no está accesible para el 6510 o, lo que es lo mismo, no está accesible para los programas que se ejecutan en él.

Esto es fácil de comprobar si tenemos en cuenta que el primer carácter del juego de caracteres es la @:

Arroba

Si el mapa de caracteres en ROM fuera direccionable, al leer la primera posición (la $d000 = 53.248) deberíamos leer el valor %00111100 = 60, ya que la fila superior de la @ tiene activados los bits 2 – 5. Sin embargo, leemos 0:

Mapa de caracteres no direccionable.PNG

Esto es así porque, en realidad, tras la dirección $d000 lo que estamos leyendo es el registro SP0X del VIC, es decir, la coordenada X del sprite 0.

De hecho, podemos cambiar el valor de SP0X = $d000, lo que viene a confirmar nuevamente que no estamos direccionando ROM, porque la ROM no se puede modificar:

SP0X.PNG

Total, el mapa de caracteres no está inicialmente disponible para el 6510 (ojo, sí para el VIC). Sin embargo, configurando un 0 en el bit 2 de R6510 podemos dejar de direccionar los chips especiales (VIC, SID y CIAs) y pasar a direccionar la ROM con el mapa de caracteres.

Esto puede ser útil, por ejemplo, para copiar ese mapa de caracteres de ROM a RAM, y luego adaptar los caracteres que sean de interés, a modo de juego de caracteres personalizados.

Sin embargo, este activar el mapa de caracteres no es inocuo, porque va acompañado de desactivar la entrada / salida. Por tanto, si se hace, hay que hacerlo con cuidado. Previamente habrá que desactivar las posibles interrupciones generadas por la entrada / salida (ver registro CIAICR) porque, caso de no desactivarlas, si se produjera una interrupción de este tipo durante el proceso, ésta no podría ser identificada ni tratada correctamente.

Total, casi merece más la pena diseñar un juego de caracteres personalizados completo, pero tomando como base de partida los caracteres estándar (como hace el editor de caracteres de CBM prg Studio). Te evitas andar copiando los caracteres estándar de ROM a RAM y, sobre todo, te evitas problemas con las interrupciones.

En todo caso, han pasado 30 años y no dejan de sorprenderme las capacidades del C64. Y eso que no hemos hablado de cartuchos de expansión…


Programa de ejemplo: Prog61

Enlaces de descarga del código

Algunos lectores me están pidiendo que habilite enlaces para facilitar la descarga del código ensamblador, tanto de los proyectos del volumen I como de los proyectos del volumen II.

Ningún problema, ahí van:

  • Enlace para descarga del código ensamblador de los proyectos del volumen I, incluyendo las librerías: volumen I
  • Enlace para descarga del ensamblador de los proyectos del volumen II (Asteroids), incluyendo las librerías mejoradas: volumen II

Espero que con esta medida resulte más fácil descargar, revisar y probar el código.

¡¡Feliz 2020!!