Entrada / salida a disco con dio.h

En la documentación de cc65 (https://cc65.github.io/doc/dio.html) se describe dio.h como una librería de entrada/salida de bajo nivel (bajo nivel de abstracción) o de entrada/salida de sectores.

Recordemos que los discos, tanto los actuales de PC como los antiguos del C64, se organizan en platos, pistas y sectores. Los sectores son la unidad mínima de información que se puede leer o escribir.

Cuando uno maneja un API o librería de alto nivel, la programación para acceder a disco la hace en términos de “ficheros” (ej. abre el fichero, cierra el fichero, escribe X en el fichero, etc.). Cuando uno maneja un API o librería de bajo nivel, la programación la hace en términos de sectores, lo cual es bastante más duro, porque de algún modo el programador tiene que saber qué sectores corresponden a qué ficheros, si es que quiere programar algo con sentido. Otra opción es limitarse a hacer programas de gestión de discos, del tipo copiadores de disco, reparadores de disco, etc.

Pues bien, en teoría, con la librería dio.h deberíamos ser capaces de hacer entrada/salida a disco de bajo nivel desde un C64. Sin embargo, me da la sensación de que, al menos la distribución de cc65 que yo tengo, no implementa dio para el C64.

Inspección de dio.h:

Si le echamos un vistazo a dio.h vemos que hay funciones para:

  • Obtener el tamaño (en bytes) de los sectores de un disco.
  • Obtener el número de sectores de un disco.
  • Abrir un disco y obtener un manejador (“handle”).
  • Cerrar un disco.
  • Leer desde un disco unos cuantos sectores.
  • Escribir a disco unos cuantos sectores.
  • Escribir a disco unos cuantos sectores y verificarlos.
  • Etc.

Aquí ponemos un extracto:

Como vemos, efectivamente se trata de un API de “bajo nivel”, puesto que la abstracción más compleja que se maneja es el “sector”. En ningún momento el API maneja el concepto de “fichero”.

En todo caso, serían funciones muy útiles para un C64.

Imágenes T64 y D64:

En este blog ya hemos analizado las imágenes T64 y D64 con anterioridad. Se trata de ficheros, típicamente de PC, que son una copia bit a bit de un dispositivo. En el caso T64 se trata de la copia de una cinta (“T” por tape); en el caso D64 se trata de la copia de un disco (“D” por disk).

En VICE es posible crear y conectar imágenes T64 y D64. Por ejemplo, con la opción File > Attach disk image > Drive 8 podemos:

  • Crear una nueva imagen D64 con “Create image”.
  • O seleccionar una imagen D64, es decir, un fichero del PC como “DISCO.D64”, y conectarlo a VICE / al C64 como si fuera la unidad 8.

El contenido del disco, es decir, de la imagen D64, se puede ver en la sección “Image Contents”. En este caso el disco llamado “DIO” tiene 664 bloques o sectores, de los que uno contiene un programa PRG llamado “MIPROGRAMA” y otros 663 están disponibles.

De hecho, el programa PRG lo he grabado en la imagen D64 tecleando un programa sencillo en BASIC (10 PRINT “HOLA”; 20 GOTO 10) y luego usando SAVE “MIPROGRAMA”, 8. Todo ello desde VICE, claro.

Sea como fuere, lo que tenemos es un disco de 664 bloques o sectores a 256 bytes el sector, es decir, de casi 170 KB. Bueno, más que un disco es una imagen D64, pero a todos nuestros efectos es lo mismo.

Pues bien, usando dio.h debería ser posible contar los sectores del disco, consultar el tamaño del sector (256 bytes), leer sectores, escribir sectores, etc. Vamos a intentarlo.

Programa de ejemplo con dio.h:

Veamos el siguiente programa en C:

Es bien sencillo:

  • Importa dio.h.
  • Y la función main(), define un “manejador” e intenta abrir el disco 8.

Pues bien, si compilamos con cc65 vemos que nos da el error:

Es decir, el programa se compila bien, porque el tipo dhandle_t y el prototipo de la función dio_open() están en el header file dio.h. Es decir, es un programa correcto. Sin embargo, el fichero objeto que se genera (“dio.o”) no se consigue enlazar con alguna librería que implemente dio_open().

En definitiva, la distribución de cc65, al menos la que tengo yo, no trae soporte para dio en el C64. Otra forma de confirmar esto mismo es revisando el fichero “readme.txt” del directorio cc65\samples:

En este fichero se afirma que el programa de ejemplo “diodemo.c”, que se apoya en conio y dio, sólo está disponible para Atari y Apple II.

Una pena, snif, snif…

Manejo del ratón con mouse.h

Lo del ratón merece algo de explicación previa:

Tipos de ratón en el C64:

Los ratones son dispositivos apuntadores que se suelen usar en ordenadores con interfaz de usuario gráfica, por ejemplo, un PC con Windows o un MAC. Sin embargo, la interfaz de usuario normal del C64 era en modo texto (pantalla de 40×25), aunque, por supuesto, también estaba GEOS.

A pesar de eso, el C64 y otros ordenadores Commodore sí soportaron el uso de ratón, aunque no era tan habitual encontrarlos como el joystick. De hecho, en el C64 había dos tipos de ratones:

  • Ratones en modo joystick o digital: En este modo, la lectura del ratón arrojaba una dirección análoga a la de un joystick (norte, sur, este, oeste, noroeste, noreste, suroeste o sureste), así como la pulsación de un botón. Esencialmente, era lo mismo que usar un joystick, pero manejando un ratón.
  • Ratones en modo proporcional o analógico: En este modo, la lectura del ratón arroja una posición (X, Y) que, además, puede cambiar más rápido o más despacio en función de la velocidad con que se mueva el ratón. Adicionalmente, permite la lectura de dos o más botones.

Un ejemplo del primer tipo de ratón era el Commodore 1350, y del segundo el Commodore 1351:

Inspección de mouse.h:

La librería mouse.h está en cc65\include, como todos los header files de cc65. Si le echamos un vistazo vemos cosas parecidas a las que vimos en joystick.h:

  • Constantes y funciones para manejar drivers de ratones.
  • Constantes para identificar el botón izquierdo y derecho.
  • Tipos de datos (structs de C) para manejar la posición del ratón, así como el estado de los botones.
  • Funciones para mover el ratón y leer su posición.
  • Etc.

Por ejemplo, aquí tenemos un extracto:

De especial interés son las estructuras “mouse_pos” y “mouse_info”, porque nos van a permitir leer la posición y los botones del ratón. Recordemos que los structs de C sólo son tipos de datos; luego hay que declarar variables de esos tipos.

Vamos a hacer un programa de ejemplo muy parecido al de joystick.h. Primero instalaremos el driver para el ratón, y luego leeremos y pintaremos su posición.

Instalación del driver:

El programa principal, es decir, la función main(), es como sigue:

Este programa:

  • Borra la pantalla con la función clrscr().
  • Instala el driver con la función mouse_install().
  • En caso de error en la instalación del driver, llama a error_driver() y termina.
  • Entra en un bucle infinito que lee el ratón con mouse_pos() y pinta su posición con pinta_mouse().

Lectura del ratón:

La lectura del ratón se hace con la función mouse_pos(). Esta función es equivalente a joy_read() en el caso del joystick.

La principal diferencia entre ambas funciones es que leer un joystick se resuelve con un byte (de hecho, con 5 bits, uno por cada dirección y otro para el disparo), mientras que leer un ratón requiere una estructura de datos más compleja (una coordenada X, una coordenada Y, y el estado de los botones).

Por este motivo, la función joy_read() simplemente devuelve un char (un byte), mientras que la función mouse_pos() no puede devolver un valor, que tendría que ser de un tipo de dato simple en C (ej. un char), sino que recibe la dirección de (un puntero a) una estructura de tipo “mouse_pos”. Con este puntero, la función puede acceder al contenido de la variable de tipo “mouse_pos” y cambiar su contenido. Esto es lo que en C y otros lenguajes se llama “pasar variables por referencia”, en oposición a “pasarlas por valor”.

Bueno, al final en la variable “pos”, que es de tipo “mouse_pos”, lo que tenemos es la lectura del ratón. “pos” es una estructura con dos campos:

  • pos.x: La coordenada X del ratón.
  • pos.y: La coordenada Y del ratón.

La forma de acceder a los campos de una estructura en C (struct) es mediante el punto (.).

Por último, hay que pintar la posición del ratón. Esto se hace en la función pinta_mouse():

Lo que hace esta función es:

  • Coloca el cursor en la posición (0, 0) con la función gotoxy() de conio.h.
  • Pinta la coordenada X del ratón con printf().
  • Pinta la coordenada Y del ratón con printf().

No hay que confundir el parámetro “pos”, que es local a la función pinta_mouse(), con la variable “pos”, que es de la función main(). El parámetro “pos” es un puntero a la variable “pos”. Por eso al llamar a pinta_mouse() se usa &pos, siendo & el operador que obtiene la dirección de la variable “pos”.

Siendo “pos” un puntero a la variable “pos” (jaja, parece un trabalenguas), la forma de acceder a sus campos x e y es mediante el operador -> . Este operador de C es muy gráfico, porque parece una flecha, recordándonos que el parámetro “pos” es un puntero.

Quizás podíamos haber llamado “pos” a la variable y “ppos” al parámetro, significando la primera “p” que es un puntero a “pos”.

Ratón y VICE:

Como sabemos, en VICE es posible simular el joystick con el teclado. Pues bien, también es posible simular el ratón del C64 con el ratón del PC en que se ejecuta VICE.

Para ello, hay que ir a la opción Settings > Control port settings… y poner en el puerto de control 1 un ratón 1351:

A partir de ese momento, se le puede indicar a VICE que capture los eventos del ratón del PC como si fueran del ratón del C64, lo cual se hace con Alt + Q o Settings > Mouse settings… y Grab mouse events:

El problema de hacerlo así es que veréis que el ratón del PC deja de responder, es decir, deja de moverse el puntero del ratón del PC. Esto es así porque esos movimientos ya los está capturando VICE. Pero tampoco se aprecia nada en VICE porque el programa que corre en el C64 (de momento ninguno, el intérprete de BASIC) tampoco está preparado para ello.

Por tanto, lo que hay que hacer es compilar el programa en C (esto damos por conocido cómo se hace), cargar el programa arrastrando el fichero PRG hasta VICE, y luego configurar VICE con un mouse 1351 en el puerto de control 1 y empezar a capturar eventos del ratón con Alt + Q. Veréis algo así:

De hecho, si movéis el ratón del PC veréis que las coordenadas X e Y van cambiando. Éstas oscilan desde (0, 0) hasta (319, 199). ¿De dónde salen estos valores? Pues muy fácil: 40 caracteres de ancho * 8 pixels = 320 pixels; 25 caracteres de alto * 8 pixels = 200. Y como empezamos en el (0, 0), que es la esquina superior izquierda, la esquina inferior derecha es la (319, 199). ¡¡Todo encaja!!

Ah, y una cosa importante: para salir del modo captura de eventos del ratón hay que volver a pulsar Alt + Q. Por tanto, Alt + Q sirve tanto para entrar como para salir del modo captura de eventos.

El lector interesado puede completar este ejemplo definiendo un sprite con la forma de un puntero de ratón, y moviéndolo por la pantalla del C64 en función de la lectura (X, Y) del ratón.

Chulo, ¿no?


Código de ejemplo: mouse

Manejo del joystick con joystick.h

Joystick, es decir, el fichero joystick.h y la implementación correspondiente, es la librería de cc65 para el manejo de joysticks. Recordemos que al C64 se le pueden conectar uno o dos joysticks.

El fichero joystick.h, al igual que todos los header files de cc65, está en cc65\include. Si le echamos un vistazo vemos que define:

  • Constantes y funciones para manejar drivers de joysticks.
  • Constantes para identificar el joystick 1 y el joystick 2.
  • Macros para identificar si un joystick se está moviendo hacia arriba, abajo, izquierda o derecha, o pulsando un botón de disparo. Una macro del preprocesador de C es parecida a una constante, es decir, una instrucción #define, pero con parámetros.
  • Etc.

Aquí vemos un extracto del fichero joystick.h:

Como en el caso de conio.h, podemos hacer de programa de ejemplo en C para ver si nos detecta los joysticks en VICE, que en el fondo se simulan mediante teclas del PC donde se ejecuta VICE:

Instalación del driver:

En informática un “driver” suele referirse a un software que permite que un ordenador se comunique con un dispositivo. Pues bien, cc65 maneja el concepto de “driver” para tener acceso a los joysticks.

Esto me resulta un tanto confuso porque, como ya vimos hace tiempo en este blog, el joystick 1 viene a equivaler al registro CIAPRB = $dc01 y el joystick 2 viene a equivaler al registro CIAPRA = $dc00. Vamos, que no veo mucha necesidad de que ningún software o driver intermedie entre esos registros y la aplicación. Bastaría con leer esas posiciones de memoria y actuar en consecuencia. Esto con cc65 debería ser fácil, porque cc65 permite definir variables y vincularlas a determinadas posiciones de memoria (ej. $dc00 o $dc01).

Quizás el motivo radique en que cc65 no sólo está pensando para el C64, sino también para otros ordenadores basados en el 6502, que podrían ser más complejos en cuanto al manejo del joystick. En este contexto, tener una interfaz de programación unificada para manejar los joysticks podría ser de utilidad.

Sea como fuere, para conseguir leer los joysticks en VICE, que se simulan con el teclado del PC donde corre VICE, he tenido que instalar un driver:

Como se puede ver, el programa en C anterior:

  • Borra la pantalla con la función clrscr().
  • Instala el driver con la función joy_install().
  • En caso de error en la instalación del driver, llama a la función error_driver(), que básicamente saca un mensaje por pantalla.
  • Entra un bucle infinito que lee el joystick 2 con la función joy_read() y pinta un mensaje significativo con la función pinta_joy().

Lectura del joystick:

Estrictamente hablando, la lectura del joystick se hace con la función joy_read(). Ahora bien, tras leer el joystick, que en este caso es el JOY_2, hay que actuar en consecuencia, en función de que el joystick apunte arriba, abajo, a la izquierda, a la derecha, o a una combinación de estas direcciones. Además, tenemos el disparo.

Este “actuar en consecuencia” en este ejemplo consiste simplemente en mostrar un mensaje por pantalla. En un ejemplo más realista se trataría de mover un sprite, por ejemplo.

Pues bien, esto se hace con la función pinta_joy():

La función pinta_joy() hace uso de las macros JOY_UP(), JOY_DOWN(), JOY_LEFT(), JOY_RIGHT() y JOY_BTN_1(). Las macros son instrucciones del preprocesador de C que se definen con #define. Por tanto, son muy parecidas a las constantes, pero tienen algún parámetro, en este caso el byte (o char) que se ha leído previamente del joystick.

Al final, lo que hace el preprocesador antes de compilar es sustituir la macro por su definición. Es decir, JOY_UP(joy) se sustituiría por (joy & JOY_UP_MASK), donde & es la operación AND y JOY_UP_MASK es una máscara (una constante) que representa el movimiento hacia arriba. De este modo, si ese AND es distinto de cero, o “cierto” en C, eso significa que el joystick efectivamente se está empujando hacia arriba.

Al final, en función de la dirección en que se empuje el joystick, lo que hace pinta_joy() es pintar con Conio “Arriba”, “Abajo”, “Izquierda”, “Derecha” o “Disparo” en la esquina superior izquierda de la pantalla (posición (0, 0)). A las cadenas les ponemos algunos espacios extra al final porque, al ser “Izquierda” la cadena más larga (9 caracteres), conviene borrar los posibles caracteres sobrantes si después de mover a la izquierda movemos en otra dirección cuya cadena representativa tiene menos caracteres.

Joystick y VICE:

A estas alturas del blog, con varios libros sobre C64 a nuestras espaldas, yo creo que todos sabemos que VICE simula los joysticks mediante el teclado del PC donde se ejecuta, y que hay que ir al menú Settings > Joystick settings > Joystick settings… para configurar los joysticks:

Sobre el joystick 2, que es el que usa nuestro programa, seleccionamos “Keyset A” (o B, el que más rabia nos dé) y luego “Configure Keyset A”. Y en la pantalla emergente, configuramos las teclas del PC que queremos asociar con cada movimiento del joystick 2, por ejemplo, los cursores y Control:

A partir de aquí, compilamos el programa en C con cl65.exe (damos por sabido cómo se hace), arrastramos el fichero PRG hasta VICE, y observamos algo así, que va cambiando según movemos el joystick 2 / pinchamos las teclas:

Total, ¡¡ya sabemos usar los joysticks desde C!! Fácil, ¿no?


Código de ejemplo: joystick