CIA1: Reloj TOD

El C64 dispone de un reloj llamado TOD – Time of Day. No se trata de un reloj como el de los ordenadores modernos, que si se apaga y enciende el ordenador sigue en hora gracias a una batería. Es más bien un contador que, partiendo de cero, se va incrementando. Si se pone en hora, a partir de ese momento marcará la hora del día con horas, minutos, segundos y décimas de segundo.

El reloj TOD consta de cuatro registros:

REGISTRO DIRECCIÓN FUNCIÓN
TODTEN $dc08 Time of day. Décimas de segundo.
TODSEC $dc09 Time of day. Segundos.
TODMIN $dc0a Time of day. Minutos.
TODHRS $dc0b Time of day. Horas.

Si se leen los registros se leerán respectivamente las horas, los minutos, los segundos y las décimas de segundo. Si se escriben los registros el reloj se pondrá en hora. En particular, al escribir en el registro TODTEN se arranca el reloj.

Tanto al leer como al escribir los datos podría ocurrir el siguiente problema. Supongamos que son las 11:59. Se leen las horas (11h) y, al leer los minutos ya son las 12:00. Por tanto, se lee 00 para los minutos, es decir, las 11:00, cuando se empezó a las 11:59 y se terminó a las 12:00. Para evitar estos problemas, el reloj tiene un sistema de bloqueo (“latching”), de modo que al leer las horas el valor a leer se bloquea hasta que se lean las décimas de segundo, aunque el reloj se sigue actualizando internamente. Esto también ocurre con las escrituras.

Los registros del TOD están codificados en BCD, lo que significa que cada registro (8 bits) tiene codificado un dígito decimal (del 0 al 9) en cada nibble. Esto es suficiente para codificar las horas (de 00 a 12), los minutos (de 00 a 59), los segundos (de 00 a 59), y las décimas (de 0 a 9), y facilita la presentación de los datos al usuario.

Es más, el bit 7 de TODHRS, que en principio no se utiliza porque la hora más alta posible no lo necesita (12=%0001-0010), se utiliza para marcar si la hora es AM (bit 7 = 0) o PM (bit 7 = 1).

Además de lo anterior, el registro TOD tiene una alarma, de modo que es posible configurar una hora que, una vez alcanzada, se genera una interrupción. Esto se consigue grabando una hora en los registros TOD a la vez que el bit 7 del registro CIACRB está a 1. De este modo, la operación de grabación (“sta”) no cambia la hora del reloj, sino que configura la alarma.

El C64 tiene un reloj alternativo en las posiciones $a0-$a1-$a2 (Software Jiffy Clock), pero este reloj se actualiza por software (mediante interrupciones) y, por tanto, no es tan preciso como el TOD, que está actualizado por hardware (mediante registros específicos).

Dos usos interesantes del TOD son generar números pseudo aleatorios y, simplemente, saber el tiempo que ha transcurrido desde un determinado momento.

Reloj TOD


Programa de ejemplo: Prog58

CIA1: Temporización

Una forma sencilla de conseguir un “cronómetro” es mediante un contador. Un contador es un registro que, partiendo de cero, va incrementando su valor a intervalos regulares. De este modo, cuando termine el intervalo de tiempo a medir, se para el contador y se analiza su valor. Y dado que el período de tiempo entre incrementos será conocido, esto permite calcular fácilmente el tiempo transcurrido: tiempo = valor del contador x período entre incrementos.

Otra variante de lo anterior es la “cuenta atrás”. El mecanismo es muy parecido. En este caso, el contador se carga con un valor inicial y se va decrementando a intervalos regulares. Cuando el contador llega a 0, se activa algún “flag” o se genera una interrupción.

Esta forma de esperar un tiempo es mucho más interesante que el típico bucle de espera implementado por programa:

Retardo

En primer lugar, el tiempo de espera de un bucle es difícil de conocer y controlar con precisión. Habría que analizar el número de ciclos de reloj que consume todo el bucle (o cada iteración), y multiplicarlo por el tiempo que dura cada ciclo de reloj (el inverso de la frecuencia de reloj). Pero es que, además, mientras el 6510 ejecuta el bucle de espera no puede ejecutar otra cosa; son ciclos de reloj desperdiciados durante la espera.

Sin embargo, si la espera o cuenta atrás la controla otro registro (un registro del CIA1), el 6510 puede seguir ejecutando otras tareas útiles y, cuando termine el tiempo establecido, se ejecutará una rutina de interrupción. Por tanto, es un enfoque mucho más interesante.

De hecho, este es el mecanismo que utiliza el sistema operativo del C64. Un registro genera interrupciones 60 veces por segundo, y esas interrupciones se utilizan para escanear el teclado y realizar otras tareas.

Por claridad, repetimos aquí los registros del CIA1 que tienen que ver con temporización:

REGISTRO DIRECCIÓN FUNCIÓN
TIMALO $dc04 Contador A. Parte menos significativa (low).
TIMAHI $dc05 Contador A. Parte más significativa (high).
TIMBLO $dc06 Contador B. Parte menos significativa (low).
TIMBHI $dc07 Contador B. Parte más significativa (high).
CIAICR $dc0d Registro de control de interrupciones.

Bit 0: 1=contador A llega a 0.

Bit 1: 1=contador B llega a 1.

Bit 2: 1=alarma TOD.

Bit 7: 1=cualquier flag activo.

CIACRA $dc0e Registro de control del contador A.

Bit 0: 1=arrancar; 0=parar.

Bit 3: 1=one-shot; 0=free-running.

Bit 5: 1=reloj externo; 0=reloj interno.

CIACRB $dc0f Registro de control del contador B.

Bit 0: 1=arrancar; 0=parar.

Bit 3: 1=one-shot; 0=free-running.

Bit 6,5: 01=reloj externo; 00=reloj interno; 10=contador A pasa por 0.

Bit 7: 1=alarma TOD.

Contadores A y B:

En primer lugar, tenemos los contadores A y B, ambos de 16 bits (TIMAHI-TIMALO y TIMBHI-TIMBLO). Sirven para controlar intervalos de tiempo como hemos descrito, funcionando en modo “cuenta atrás”.

Se cargan con el valor inicial deseado mediante la instrucción “sta”, y se van decrementando una vez arrancados. Se pueden leer con la instrucción “lda”, en cuyo caso devuelven por qué valor van.

Registros de control de los contadores A y B:

En segundo lugar, tenemos los registros de control asociados a esos contadores (CIACRA y CIACRB), que sirven para arrancar y parar los contadores, seleccionar los diferentes modos de funcionamiento, seleccionar la señal de tiempo que produce los decrementos, etc.

Para arrancar un contador hay que poner a 1 el bit 0 de CIACRA/CIACRB. Para parar un contador hay que poner a 0 ese mismo bit.

Respecto a los modos de funcionamiento, tenemos el modo “one shot” (bit 3 = 1) y el modo “free running” (bit 3 = 0). Con el primero el contador sólo cuenta una vez y se para al llegar a 0. Con el segundo, el contador cuenta una y otra vez, hasta que se cambie su configuración.

Por último, respecto a la señal de reloj que produce los decrementos, ésta puede ser el reloj interno del C64 (bit 5 = 0) o una señal de reloj externa (bit 5 = 1) que se aplique al pin CNT del puerto de usuario.

El reloj interno del C64 tiene una frecuencia de 0,985 MHz en el modelo PAL (Europa) y de 1,023 MHz en el modelo NTSC (América). Es decir, los ciclos de reloj son de 1,02 y 0,97 microsegundos, respectivamente.

En el caso del contador B, y sólo en este caso, es posible configurarlo para que se decremente cada vez que el contador A pase por 0. Es decir, es posible concatenar los dos contadores A y B como si fueran un único contador de 32 bits. De este modo se consigue contabilizar intervalos de tiempo muy largos, algo más de una hora si la fuente de reloj utilizada es la interna (2^32 intervalos de 1,02 microsegundos = 1,21 horas).

Esta configuración se consigue con bit 6 = 1 y bit 5 = 0 del CIACRB.

Registro de control de las interrupciones:

En tercer lugar, tenemos el registro de control de interrupciones (CIAICR), que es donde están los “flags” que señalizan que los contadores A o B han llegado a 0, y donde se configuran las interrupciones deseadas.

Cuando el contador A llega a 0 se activa el bit 0 de CIAICR, y cuando el contador B llega a 0 se activa el bit 1. Para detectar estos flags habrá que ejecutar la instrucción “lda CIAICR”, en cuyo caso todos los flags se vuelven a poner a 0 automáticamente.

El bit 7 de CIAICR se activa siempre que cualquiera de los “flags” (los otros bits de CIAICR) esté activado.

Para configurar las interrupciones deseadas, habrá que ejecutar la instrucción “sta CIAICR”, donde, en función del valor del acumulador:

  • Si el bit 7 vale 1: los bits 0…6 que estén a 1 activarán las interrupciones cuando los flags correspondientes de CIAICR pasen a estar activados.
  • Si el bit 7 vale 0: los bits 0…6 que estén a 1 desactivarán las interrupciones de los flags correspondientes de CIAICR.

En ambos casos (bit 7 = 0 o 1) los restantes bits a 0 a cargar en CIAICR ni activan ni desactivan interrupciones. Simplemente, no modifican la configuración de las interrupciones.

Contador segundos-1


Programa de ejemplo: Prog57

CIA1: Lectura de los joysticks

El C64 admite conectar dos joysticks, uno al puerto de control 1 y otro al puerto de control 2:

Joysticks

El joystick conectado al puerto de control 1 se puede leer con el puerto CIAPRB del CIA1, y el joystick conectado al puerto de control 2 se puede leer con el puerto CIAPRA del CIA1. Sí, aunque no lo parezca, es correcto: joystick1 => CIAPRB y joystick2 => CIAPRA.

En realidad, el uso del joystick ya lo vimos en la entrada dedicada a las colisiones de sprites y, más en particular, en su programa de ejemplo (programa 37). Entonces no entramos a explicar los detalles y simplemente presentamos un código como éste:

LibJoy

Ahora ya sabemos que CIAPRA y CIAPRB son registros del CIA1. Y en realidad sería más adecuado llamar a las rutinas «leeJoystick1» (CIAPRB) y «leeJoystick2» (CIAPRA), ya que los usuarios identifican los joysticks en función del puerto de control al que están conectados (1 o 2), y no en función del puerto de datos con los que se leen.

Un joystick es un dispositivo electrónico que consta de cinco interruptores:

  • Arriba.
  • Abajo.
  • Izquierda.
  • Derecha.
  • Disparo.

Cuando el usuario empuja el joystick hacia arriba, ese interruptor se cierra, lo que hace que el voltaje de determinado pin del puerto de control se ponga a 0 voltios. Y lo mismo con el resto de interruptores.

Por supuesto, es posible cerrar varios interruptores a la vez. Por ejemplo, el disparo se puede combinar con cualquiera de las direcciones. Incluso hay direcciones que se puede combinar, como arriba y derecha/izquierda, o abajo y derecha/izquierda. Por último, hay combinaciones de interruptores que no tienen sentido, como arriba y abajo o derecha e izquierda a la vez.

Al final, cada pin del puerto de control se lee mediante un bit del puerto de datos correspondiente. Si se trata del puerto de control 1, se lee con el puerto CIAPRB; y si se trata del puerto de control 2 se lee con el puerto CIAPRA.

Tanto los puertos de control (las conexiones físicas) como los puertos de datos (los registros CIAPRA y CIAPRB) tienen ocho bits, pero como un joystick sólo tiene cinco interruptores, sólo se utilizan los cinco bits menos significativos:

  • %00011111 = nada pulsado.
  • %00001111 = disparo.
  • %00010111 = derecha.
  • %00011011 = izquierda.
  • %00011101 = abajo.
  • %00011110 = arriba.

Por último, en la entrada anterior ya se comentó que el puerto CIAPRB se utiliza para leer el teclado. Esto significa que puede haber confusiones entre teclado y joystick1. Cuando un programa lee CIAPRB, no tiene manera fácil de determinar si lo que lee ahí proviene del teclado o del joystick1, aunque hay algunos trucos como desactivar temporalmente el teclado mientras se lee el joystick1.

Y este es el motivo por el que muchos juegos del C64 utilizaban el joystick2. El joystick2 se lee con el puerto CIAPRA y, al no usarse este puerto para leer el teclado, se evitaba la confusión.


Programa de ejemplo: Prog56

CIA1: Lectura del teclado

El teclado se puede leer con la rutina SCNKEY del Kernal. No obstante, también se puede leer de forma directa utilizando el CIA1. Esto es lo que vamos a ver en esta entrada.

Las teclas del teclado están conectadas a una matriz de conexiones de modo que, cuando se pulsa una tecla (o varias a la vez), se “activan” determinados bits del puerto B del CIA1. Esos bits sirven para indicar la tecla o teclas que se han pulsado.

Matriz teclado

Si se compara la matriz anterior con un teclado del C64 se verá que faltan dos de las 66 teclas:

  • La tecla SHIFT LOCK.
  • La tecla RESTORE.

La tecla SHIFT LOCK, en realidad, no necesita ser leída de forma independiente. Siempre que esté pulsada se leerá como que está pulsada la tecla LEFT SHIFT.

Y la tecla RESTORE está conectada a la patilla “NMI” del 6510. Es decir, cuando se pulsa genera interrupciones no enmascarables (tipo NMI).

La forma de leer el teclado implica hacer un uso un poco singular del CIA1. De una forma global, el CIA1 se está utilizando para leer el teclado (digamos que como puerto de entrada), pero la forma de conseguirlo es configurando el puerto A como puerto de salida (CIDDRA = $ff) y el puerto B como puerto de entrada (CIDDRB = $00). Esta es la configuración por defecto del CIA1.

Para saber si una determinada tecla está pulsada, por ejemplo, la M, hay que escribir un 0 en la fila correspondiente (bit 4 del puerto A) y un 1 en el resto de filas (resto de bits del puerto A). De este modo, digamos que “centramos la atención” en esa fila. Posteriormente, leemos el puerto B. Esta lectura nos dirá qué tecla o teclas están pulsadas (de la fila en cuestión, es decir, N, O, K, M, 0, J, I o 9). En particular, si el bit 4 del puerto B está a 0, entonces la tecla M está pulsada.

Tecla pulsada

Lógicamente, si al programador no le interesa una tecla específica, por ejemplo, la M, sino que le interesa cualquier tecla que se haya podido pulsar, el programa tendrá que recorrer todas las filas de la matriz anterior y, para cada fila, determinar la tecla o teclas que se hayan pulsado.

Esta información (la tecla pulsada) se puede entregar al programa usuario tan pronto como se detecte la primera tecla pulsada, y volver a empezar el barrido, o hacer barridos completos de toda la matriz y entregar todas las teclas pulsadas mediante un buffer.


Programa de ejemplo: Prog55

Registros del CIA1

Como ya hemos comentado anteriormente, el C64 tiene dos CIAs, el CIA1 y el CIA2. Ambos sirven para que el microprocesador 6510 se comunique con dispositivos periféricos conectados al C64. Además, también tienen funciones de temporización.

En particular, el CIA1 conecta al microprocesador 6510 con el teclado y joysticks o paddles conectados a los puertos de control. Todos ellos son periféricos de entrada.

CIA1

Para conseguir esto el CIA1 dispone de los siguientes registros:

REGISTRO DIRECCIÓN FUNCIÓN
CIAPRA $dc00 Puerto de datos A.
CIAPRB $dc01 Puerto de datos B.
CIDDRA $dc02 Registro de dirección de datos A:

 

Bit 0…7 = 0: entrada.

Bit 0…7 = 1: salida.

CIDDRB $dc03 Registro de dirección de datos B:

 

Bit 0…7 = 0: entrada.

Bit 0…7 = 1: salida.

TIMALO $dc04 Contador A. Parte menos significativa (low).
TIMAHI $dc05 Contador A. Parte más significativa (high).
TIMBLO $dc06 Contador B. Parte menos significativa (low).
TIMBHI $dc07 Contador B. Parte más significativa (high).
TODTEN $dc08 Time of day. Décimas de segundo.
TODSEC $dc09 Time of day. Segundos.
TODMIN $dc0a Time of day. Minutos.
TODHRS $dc0b Time of day. Horas.
CIASDR $dc0c Envío y recepción de datos en modo serie por el puerto de usuario.
CIAICR $dc0d Registro de control de interrupciones.
CIACRA $dc0e Registro de control del contador A.
CIACRB $dc0f Registro de control del contador B.

Los “puertos de datos” (CIAPRA y CIAPRB) son los registros o posiciones de memoria a través de los que el microprocesador recibe o envía los datos. No se debe confundir este significado de la palabra puerto (como posición de memoria) con el significado de puerto como conexión o interfaz física.

Los “registros de dirección de datos” (CIDDRA y CIDDRB) sirven para indicar si los puertos correspondientes van a funcionar como puerto de entrada o puerto de salida. Además, esta configuración funciona bit a bit, es decir:

  • Si el bit X de CIDDRA vale 0, entonces el bit X de CIAPRA funcionará de entrada.
  • Si el bit X de CIDDRA vale 1, entonces el bit X de CIAPRA funcionará de salida.
  • Si el bit Y de CIDDRB vale 0, entonces el bit Y de CIAPRB funcionará de entrada.
  • Si el bit Y de CIDDRB vale 1, entonces el bit Y de CIAPRB funcionará de salida.

Los registros dedicados a la temporización se verán en detalle más adelante. De momento, baste decir que básicamente son contadores que permiten medir intervalos de tiempo. Como hay dos contadores (A y B), se puede usar uno, el otro, o ambos concatenados. Además, el CIA1 puede generar interrupciones IRQ cuando los contadores llegan a un determinado valor, o de forma periódica, como las interrupciones que se generan 60 veces por segundo para leer el teclado.

Conexión de un C64 a Internet

Otra novedad bastante impresionante es la posibilidad de conectar un C64 a una intranet con protocolo Ethernet y, a través de ella, a Internet.

El VIC-20 y el C64 ya disponían de módems. Se trataba de dispositivos que, usando la red telefónica convencional y diferentes modulaciones, permitían conectar estos equipos a BBS’s y otros servicios en línea similares.

Modem

Sin embargo, Internet se popularizó en España en torno al año 1995 y, a esas alturas, Commodore International ya había desaparecido (desapareció en 1994). Es decir, que Internet, o al menos la popularización de Internet, es posterior al C64, que es de los años 80 y primeros 90.

Sin embargo, hoy en día se han desarrollado dispositivos que, bien usando el puerto de usuario, bien usando el puerto de expansión, permiten conectar un C64 a una intranet basada en Ethernet. Y puesto que muchas intranets están conectadas a Internet a través de un router, esto permite conectar un C64 a Internet.

Aunque no los he probado, al menos tengo referencias de estos dos dispositivos:

Internet

Lógicamente, un C64 no puede consumir los contenidos típicos de Internet: páginas web, fotos, vídeos, ficheros de música, aplicaciones en línea, etc. El hardware y el software son tan limitados que no lo permiten. Pero lo que sí puede consumir es contenidos adaptados al C64.

Y esto es, precisamente, lo que ofrecen páginas como CommodoreServer.com. En esta página se ofrecen servicios de subida y descarga de imágenes D64, tanto a espacios públicos como privados, juegos multiusuario en línea, chat en línea con otros usuarios, etc. Para ello, la página definió el protocolo CSIP (Commodore Server Internet Protocol), un protocolo de la familia TCP/IP orientado a la prestación de servicios y contenidos para el C64.

Algunos de los servicios de CommodoreServer.com no sólo pueden usarse desde un C64 físico con una conexión a Internet. Muchos de ellos pueden usarse también desde un PC con el emulador VICE. Y además también hay servicios complementarios en formato web convencional.

Imágenes T64 y D64; dispositivos SD2IEC

Aunque en la entrada anterior hemos dicho que no estamos especialmente interesados en los tipos de periférico del C64, ni en su uso práctico, lo cierto es que en los últimos años sí ha habido algunos desarrollos que, por su utilidad, merece la pena comentar.

En primer lugar, están las imágenes T64 y D64. Una imagen es un fichero de PC o similar que contiene una copia bit a bit de un dispositivo, por ejemplo, de un disco duro.

En el caso de las imágenes T64 estamos hablando de ficheros de PC que contienen una copia bit a bit de una cinta del C64 (la “T” viene de tape). Y en el caso de las imágenes D64 estamos hablando de ficheros de PC que contienen una copia bit a bit de un diskette (la “D” viene de diskette).

Tanto una cinta como un diskette del C64 podían contener varios ficheros o programas (normalmente en formato PRG), no solamente uno. Por tanto, hoy en día tenemos la comodidad de poder manejar en un fichero de PC todo el contenido de una cinta o de un diskette.

Se pueden descargar ficheros T64 y D64, y también ficheros PRG sueltos, de muchos sitios de Internet dedicados al C64, como por ejemplo Gamebase64 (http://gamebase64.com/) o The C64 Scene Database (https://csdb.dk/). Lógicamente, si lo hacemos debemos ser respetuosos con los derechos de autor, si bien en muchos casos se trata de software olvidado y/o desarrollado por empresas que ya no existen. Es más, gracias a sitios como estos se han podido conservar.

Los ficheros T64 y D64, es decir, el contenido de las cintas y discos que representan, se pueden cargar en VICE con la opción “Autostart disk/tape image” del menú “File”. Es más, con esta opción se puede explorar el contenido de una imagen antes de usarla:

Imagenes y VICE

Por último, están los dispositivos SD2IEC. Estos dispositivos, como indica su nombre, hacen de puente (de ahí el “2” o “to”) entre sistemas de almacenamiento modernos, por ejemplo, tarjetas SD, y conexiones antiguas, por ejemplo, la conexión IEC o puerto serie del C64, que es la que se utilizaba para la disquetera.

De este modo, es posible guardar en almacenamiento moderno, de mucha más capacidad y velocidad, muchos diskettes del C64. Parece mentira, pero en una tarjeta SD actual (¿128 GB?) pueden caber muchos miles de diskettes de 170 KB.

SD2IEC

Entrada/salida

Por entrada/salida, E/S, o I/O en inglés, se entiende el intercambio de información entre el ordenador, el C64 en nuestro caso, y el mundo exterior. Cuando la información entra en el ordenador se habla de “entrada”, y cuando sale se habla de “salida”.

La entrada/salida normalmente requiere la conexión al ordenador y el uso de dispositivos periféricos. En el caso del C64 los periféricos más habituales son:

  • TV o monitor tipo CRT (tubo de rayos catódicos).
  • Datasette.
  • Unidad de diskette.
  • Cartuchos.
  • Joysticks.
  • Paddles.
  • Impresoras.
  • Modems.
  • Etc.

Periféricos

No obstante, en ocasiones también se utiliza el C64 para conectarlo a circuitos electrónicos desarrollados a media, por ejemplo, sensores o sistemas de control. Recuerdo que, cursando la EGB hacia la mitad de los 80, en mi colegio nos llevaron de visita a una fábrica conservera cuyo sistema de producción estaba controlado por … ¡¡un VIC-20!! Increíble, pero totalmente cierto. Doy fe.

La conexión del C64 a los dispositivos periféricos es mediante los “puertos”. Esta palabra resulta un poco ambigua porque, como veremos más adelante, también se utiliza para designar las posiciones de memoria que utiliza el microprocesador (6510) para leer/escribir de los dispositivos. Por ello casi prefiero hablar de “interfaces” o “conexiones”.

Las interfaces o conexiones del C64 son:

  • Puertos de control.
  • Puerto de expansión.
  • Puerto de usuario.
  • Puerto del datasette.
  • Puerto de vídeo.
  • Puerto de RF.
  • Puerto serie.

Interfaces

Cuando la comunicación entre el C64 y el periférico es bit a bit se habla de entrada/salida en serie, y cuando tiene lugar mediante varios bits a la vez, normalmente de ocho en ocho, se habla de entrada/salida en paralelo. El puerto serie permite entrada/salida en serie; el puerto de usuario permite entrada/salida en paralelo.

El propio teclado se puede considerar un periférico de entrada, aunque su conexión con el C64 es interna, no externa, y, por tanto, no se puede ver salvo que se abra el equipo.

Teclado

La comunicación del microprocesador con los periféricos o dispositivos electrónicos conectados a las interfaces no es directa, sino que en muchos casos es a través de unos chips especiales llamados 6526 CIA – Complex Interface Adapter. El C64 tiene dos CIAs, llamados CIA1 y CIA2.

Resumiendo mucho, lo que hacen estos chips es servir de interfaz entre el microprocesador y los periféricos o dispositivos. Gracias a ellos, el microprocesador ve y trabaja con unas posiciones de memoria llamadas “puertos” (obsérvese la ambigüedad con la otra acepción de puerto). Estos puertos se puedan manejar con instrucciones “lda” y “sta” igual que cualquier otra posición de memoria. Además, estos chips tienen capacidades de temporización muy útiles, es decir, permiten definir relojes, cronómetros, y similares.

Dado que en este blog estamos especialmente interesados en la programación en ensamblador, y no tanto en describir los tipos de periféricos y su uso práctico, más adelante nos centraremos en cuáles son los registros del CIA1 y del CIA2, y cómo hacer algunos programas de E/S y temporización.

Ficheros SID

El SID fue un chip muy avanzado para su época. Ya hemos comentado sus características: tres voces independientes, varias formas de onda, filtros, etc. Esto hizo que la música del C64, típicamente la de sus juegos, fuera superior a la de sus competidores. Y no sólo eso, también fue superior a la de algunos equipos que vinieron después, como los primeros PCs, que apenas tenían un altavoz interno con capacidad para emitir algunos pitidos.

Todo esto hizo que hubiera auténticos forofos de la música del C64. Y algunos llegan hasta hoy, ya que hay varias iniciativas en Internet para recopilar y conservar música del C64. Algunos, incluso, hacen “remixes” de piezas SID (ver http://remix.kwed.org/ o https://www.remix64.com/). Para mi gusto esto último ya es demasiado… 🙂

Una de las iniciativas más destacadas es High Voltage SID Collection, que es una página web y base de datos de ficheros SID. Esta página permite descargar la colección completa (más de 50.000 títulos), buscar piezas en función de sus metadatos (ej. título, autor, año de publicación, etc.) y descargarlas en formato SID, descargar reproductores SID, visitar páginas relacionadas con el C64, etc.

Hardball

Un fichero SID es un fichero que, curiosamente, no sólo contiene la información musical de la pieza que hemos visto en entradas anteriores (ej. octavas, notas, duraciones, volúmenes, etc.) Además, contiene código máquina del C64 para inicializar el SID y reproducir la pieza.

Por tanto, hay dos formas principales de reproducir un fichero SID:

  • Cargándolo en un reproductor multimedia, por ejemplo, de PC o MAC, con capacidad de reproducir ficheros SID. Algunos reproductores tienen esta capacidad de forma nativa, y otros requieren algún tipo de plug-in. Hay muchos ejemplos de reproductores bajo la sección “SID PLAYERS” de HVSC.
  • Incluyéndolo en algún programa en ensamblador, por ejemplo, con la directiva “incbin” de CBM prg Studio. Habrá que incluir el fichero en la dirección indicada (en el caso de arriba $7000), inicializar el SID con la dirección indicada (en el caso de arriba con «jsr $7c30»), y reproducir la pieza también con la dirección indicada (en el caso de arriba con «jsr $7c33»).

A continuación, un ejemplo de reproducción con TinySID:

Commando.PNG

Respecto a la segunda opción, la de incluir un fichero SID en un programa en ensamblador, hay que tener en cuenta que, en función del diseño del programa, la ubicación en memoria del fichero SID podría no resultar adecuada. Para tales situaciones, hay programas que permiten modificar el fichero SID para moverlo a otra posición de memoria. Es el caso de Sidreloc.

Utilizando ficheros SID descargados resulta realmente fácil añadir música y efectos a nuestros programas en ensamblador. Esto puede verse en el programa de ejemplo.

Y a partir de ahora, entrada/salida…


Programa de ejemplo: Prog54

Duración y volumen

En la entrada anterior hemos visto cómo guardar en una tabla la información de la melodía, es decir, la secuencia de notas con sus octavas. Si en vez de tener una melodía simple, tuviéramos una melodía con dos o tres voces, sería suficiente con que la tabla fuera de doble o triple entrada.

Además de lo anterior, hemos visto cómo leer la tabla, convertir la información de octavas y notas a frecuencias (consultando una tabla de frecuencias para la octava 7 y dividiendo la frecuencia obtenida por dos N veces), y cómo programar el SID con las frecuencias.

El esquema anterior (guardar la información musical en una tabla) se puede extender también a la duración de las notas y su volumen. De esto nos ocupamos en esta entrada.

La duración de las notas es el tiempo que transcurre desde que empieza la fase “attack” hasta que termina la fase “release”:

ADSR

La fase “attack” empieza cuando se activa el bit 0 del registro VCREG1/2/3. La fase “release” empieza cuando se desactiva ese mismo bit 0, y termina tras la duración de esa fase (recuérdese que el parámetro “release” es un tiempo).

Por tanto, podemos controlar la duración de la nota controlando el tiempo desde que activamos el bit 0 del registro VCREG1/2/3 hasta que lo desactivamos. Esto se puede hacer con una rutina de retardo. Y la duración de cada nota se puede registrar en una nueva entrada de la tabla ya descrita anteriormente.

Sin ser experto en música, supongo que musicalmente no será necesario que las tres voces estén sincronizadas. Es decir, supongo que es perfectamente posible que una voz y otra cambien de nota en momentos diferentes. Sin embargo, desde el punto de vista de la programación, esto complicaría mucho el programa, y más en un ordenador como el C64 que sólo tiene un hilo de ejecución (interrupciones aparte).

Por todo ello, supondremos que las tres voces del C64 van sincronizadas, es decir, empiezan a la vez, y cambian de una nota a la siguiente en el mismo instante. O, lo que es lo mismo, las duraciones de las sucesivas notas tienen que ser las mismas para las tres voces.

En definitiva, será suficiente con añadir una cuarta columna a nuestra tabla de triple entrada, que ahora pasará a ser de cuádruple entrada:

Tabla melodía triple.PNG

La duración de las notas no se suele expresar en términos absolutos (por ejemplo, «una negra equivale a un segundo»). Más bien, se suele expresar como una proporción de un compás. De este modo, si el compás es de 4/4, las duraciones son:

  • Redonda = 4/4
  • Blanca = 2/4
  • Negra = 1/4
  • Corchea = 1/8
  • Semicorchea = 1/16
  • Fusa = 1/32
  • Semifusa = 1/64

Duración notas

Desde el punto de vista de la programación, dado que vamos a usar una rutina de retardo para controlar la duración (básicamente un bucle), lo más práctico es que la rutina haga más o menos iteraciones en función del tipo de nota. Y puesto que nos interesa no complicar mucho más la tabla anterior, lo más práctico sería algo así:

  • Redonda = 16 iteraciones.
  • Blanca = 8 iteraciones.
  • Negra = 4 iteraciones.
  • Corchea = 2 iteraciones.
  • Semicorchea = 1 iteración.
  • Fusa = prescindir.
  • Semifusa = prescindir.

De este modo, dedicando el nibble alto de la cuarta entrada a la duración es suficiente, y podemos dedicar el nibble bajo al volumen. Por tanto, tenemos que prescindir de fusas y semifusas en aras de mantener la tabla lo más sencilla posible.

Y con el volumen ya hemos dejado caer lo que va a pasar. El volumen es común para las tres voces (bits 0, 1, 2 y 3 del registro SIGVOL). Y sólo puede tomar los valores 0 – 15. Por tanto, perfectamente podemos almacenarlo en el nibble que deja libre la duración (nibble bajo).

En conclusión, si complicamos el programa de ejemplo de la entrada anterior, de modo que, además de las octavas y notas para las tres voces, también estén en la tabla (que pasa a tener cuatro entradas) la duración y el volumen, ya tenemos todo resuelto. Además de leer y configurar las frecuencias de las diferentes voces, tendremos que leer y configurar el volumen, y mantener las voces activas durante el tiempo indicado, cambiando entonces a la siguiente nota.

Un último apunte de interés es que, en cuanto las melodías tengan unas pocas notas, la tabla superará las 256 posiciones de memoria. Por tanto, es probable que el modo de direccionamiento indexado se nos quede corto, y tengamos que usar el modo de direccionamiento indirecto – indexado.


Programa de ejemplo: Prog53