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

Entrada / salida por consola con conio.h

Conio es la librería de cc65 para hacer entrada / salida por consola, es decir, por pantalla. Si analizamos su fichero de cabecera tiene funciones para:

  • Borrar la pantalla.
  • Colocar el cursor en determinada posición (x, y).
  • Consultar la posición del cursor.
  • Escribir un carácter o cadena en la posición actual del cursor o en la posición (x, y).
  • Imprimir valores o variables conforme a un formato.
  • Leer caracteres del teclado.
  • Leer el carácter y el color de la posición actual del cursor o de la posición (x, y).
  • Activar o desactivar el parpadeo del cursor.
  • Activar o desactivar la escritura en modo invertido.
  • Modificar el color para el texto a escribir.
  • Modificar el color del borde y/o del fondo de la pantalla.
  • Etc.

Como con casi cualquier librería, el estudio exhaustivo de Conio y todas sus funciones está fuera del alcance de esta entrada. Para un estudio a fondo lo mejor es mirarse el fichero cc65\include\conio.h.

No obstante, como las funciones más habituales en el C64 suelen ser borrar la pantalla, modificar el color del fondo, del borde y del texto, y escribir determinadas cadenas en la posición actual del cursor o en la posición (x, y), sí vamos a hacer un programa de prueba para tomarle el pulso a estas funciones.

Observemos el siguiente programa en C basado en la librería conio.h:

Es bastante autoexplicativo:

  • Incluye las funciones de conio.h.
  • Incluye las funciones y constante de c64.h, por ejemplo, los colores como COLOR_BLACK y COLOR_GREEN.
  • Define la función main() que, basándose en las funciones de conio, borra la pantalla, cambia los colores del fondo, del borde y del texto, y, por último, escribe una frase en la posición actual del cursor.

El siguiente paso sería compilar con cl65.exe pero, como ya hemos visto varios ejemplos en las entradas anteriores, salvo que se dé algún caso con alguna circunstancia especial, a partir de ahora obviaremos cómo hacerlo.

Por último, si arrastramos el programa compilado hasta el icono de VICE veremos esto:

¿Se puede hacer más fácil?

Algunos diréis, bueno, en BASIC es igual de fácil. Y lo es, pero recordemos que BASIC es un lenguaje interpretado, mientras que C es compilado. Es decir, lo que hemos generado y ejecutado en última instancia es código máquina, que es mucho más rápido que BASIC.

Y, en el fondo, eso es lo que buscamos con cc65, lo mejor de ambos mundos. La facilidad de programar como en BASIC (pero en C) y la rapidez del código máquina. ¿Quién da más?


C´odigo de ejemplo: conio

Header files específicos para máquinas de Commodore: cbm.h y c64.h

Bueno, pero todo este rollo de los prototipos y los header files, que en el fondo es de C, ¿qué tiene que ver con nuestro querido C64 o el compilador cc65? Pues tiene que ver en que cc65 incorpora, además de muchos otros header files que son relativos a las librerías estándar de C, varios header files con prototipos de funciones y con constantes que son de interés en las máquinas Commodore, ya sea el C64 u otras.

cc65 también incluye header files de librerías para hacer entrada / salida por consola (conio.h), manejo de joystick (joystick.h), manejo de ratón (mouse.h), entrada / salida a disco (dio.h), gráficos (tgi.h), manejo de GEOS (geos.h), etc. Sin embargo, estos header otros files, aunque sin duda son muy útiles en un C64, no son objeto de esta entrada.

Por ello nos centramos ahora en cbm.h y c64.h:

Header file cbm.h:

El header file cbm.h es para todo tipo de máquinas Commodore. Y como todos los header files están en la ruta cc65\include.

Si analizamos su contenido vemos que tiene bastantes constantes, que son las instrucciones #define del preprocesador, y prototipos de funciones.

No es el objeto de esta entrada analizar todo el cbm.h en detalle, pero sí seleccionar algún ejemplo que sea fácil de entender y al que se le vea utilidad. Por ejemplo, se definen constantes para caracteres PETSCII que pueden ser de interés:

De este modo, el programador de C, en vez de tener que saber que el carácter PETSCII para “home”, que es el carácter que vale para llevar el cursor a la esquina superior izquierda de la pantalla, tiene el valor 19, llega con que en sus programas use el literal CH_HOME:

En ese listado echo en falta algunos caracteres clásicos de PETSCII, como 18 – RVS ON para activar el modo invertido, los caracteres que al imprimirse cambian el color de las letras, el 147 – CLR para borrar la pantalla, etc., pero estos siempre los puede añadir el programador como constantes propias.

En definitiva, muy útil…

Header file c64.h:

El header file c64.h también incluye definiciones muy útiles propias ya del C64. Por ejemplo, constantes para los colores, para el manejo del joystick, etc.

Pero lo más importante de todo, con diferencia, es que define estructuras de datos (structs en C) que, al estar vinculadas a las posiciones de memoria del VIC ($d000), SID ($d400) y CIAs ($dc00 y $dd00), permiten al programador acceder directamente desde C a esas posiciones de memoria y, por tanto, leer y/o alterar los registros del VIC, el SIC o los CIAs.

Dicho más claramente: si programamos en C para el C64 con cc65 y usamos esos structs del header file c64.h, podemos, directamente desde C, crear y manejar sprites, reproducir música, usar temporizadores, modificar el color del vídeo, etc.

¡¡Me quito el sombrero!!


Código de ejemplo: home

Header files y librerías en C

C es un lenguaje de programación procedimental, lo que quiere decir que una de las abstracciones principales que se usan a la hora de programar en C son los “procedimientos”, también llamados funciones o métodos, según el lenguaje. Yo creo que el término más común en el caso de C es el de “función”.

Una función viene a ser el equivalente en C de una rutina jsr – rts en ensamblador del 6502 / 6510. La principal diferencia es que las llamadas a funciones C admiten parámetros de entrada y salida, mientras que las llamadas a rutinas no admiten parámetros per sé, sino que hay que “simularlos” mediante el uso de posiciones de memoria o usando los registros del microprocesador.

Para poder usar una función de C, es decir, para poder llamarla, la función tiene que estar declarada previamente en el programa. Por ejemplo, si tenemos un programa así:

la compilación del programa fallará, ya sea con cc65 o con cualquier otro compilador de C, ya que la función factorial() primero se llama y luego se define (esto es incorrecto):

Sin embargo, si el programa lo redefinimos así:

ahora la compilación funcionará correctamente:

Es más, cuando decimos que la función llamada tiene que estar “declarada” previamente, no quiere decir que tenga que estar implementada con todo su código, sino que es suficiente con que el programador aporte el “prototipo”, que en el fondo es el nombre de la función y los parámetros de entrada y salida. Con esta información el compilador ya puede compilar, comprobando si la llamada a la función factorial() que se encuentra es sintácticamente correcta o no.

Aquí vemos el programa anterior retocado con un prototipo:

Pues bien, cuando un programa es grande y complejo, y cuando utiliza librerías estándar de C o suministradas por terceros, es habitual recoger los prototipos de las funciones que guardan relación entre sí, por ejemplo, por estar relacionadas con la entrada / salida o cualquier otra cuestión, en lo que se llama un “header file” (fichero de cabecera).

Un fichero de cabecera de C es un fichero con extensión *.h que incluye los prototipos de las funciones, y otras cosas como constantes, que se quieren compartir entre varios programas o ficheros. El fichero de cabecera se referencia luego desde los ficheros que van a usar esas funciones, utilizando para ello la instrucción #include <fichero-cabecera.h> del preprocesador de C. El preprocesador es un paso previo a la compilación.

Aquí vemos el programa retocado con un header file (aunque este programa es tan sencillo que no tiene mucho sentido hacerlo así, pero sirve de ejemplo):

En la siguiente entrada veremos los header files que aporta cc65 específicos para máquinas Commodore y el C64.


Código de ejemplo: factorial

Análisis del programa “hello” generado por cc65

Como hemos dicho, el programa generado por cc65 está en formato PRG. Por tanto, los dos primeros octetos del fichero indican la dirección de carga del programa en memoria del C64. Si abrimos el fichero con un editor hexadecimal para Windows como HxD:

vemos que esa dirección de carga es $0801, es decir, justo la posición inicial de la RAM de BASIC. Esto confirma que, como sabemos, cc65 genera un programa PRG con una pequeña parte de BASIC, una especie de cargador.

De hecho, si con HxD analizamos no sólo los dos primeros octetos, sino los 14 primeros octetos, vemos que son así:

Es decir:

  • $0801 es la dirección de carga del programa, es decir, el comienzo de la RAM de BASIC.
  • $080b es la dirección de la segunda instrucción de BASIC, la que sigue a la primera instrucción. Luego veremos en qué consiste.
  • $0213 es el número de línea de la primera instrucción BASIC, es decir, 531 en decimal.
  • $9E es el token de la primera instrucción BASIC. Se trata de la instrucción SYS, que sirve para llamar desde BASIC a código máquina.
  • $32303631 es la representación en PETSCII de 2061, que es la dirección de memoria a la que llama la instrucción SYS.
  • $000000 son tres octetos a cero. Obsérvese que el primero de ellos está en la dirección $080b, es decir, donde iría esa hipotética segunda instrucción de BASIC. Esto significa que el programa BASIC termina aquí; sólo tiene la instrucción SYS y ninguna más.

Esto confirma lo que vimos con LIST en la entrada anterior, que el cargador BASIC es así:

531 SYS2061

(o 531 sys2061 si la pantalla del C64 está en minúsculas)

El valor 2061 en hexadecimal es $080d. Por tanto, el programa en código máquina que se llama desde BASIC empieza en la dirección 2061 = $080d, justo después del propio cargador BASIC, que ocupa desde $0801 hasta $080c.

Todo esto lo podemos comprobar si arrastramos el fichero “hello” hasta el emulador VICE:

Y si entramos en el monitor de VICE con ALT + M y vemos el contenido de la memoria a partir de la dirección $0801 vemos el contenido esperado (el mismo que ya vimos en HxD):

Y si desensamblamos a partir de la dirección $080d vemos el comienzo del programa en código máquina que se está ejecutando:

Este programa en código máquina no es fácil de interpretar sin mayor investigación ya que, en primer lugar, no está completo (el pantallazo anterior sólo es el comienzo) y, en segundo lugar, la parte que vemos ahí no corresponde directamente con lo que conocemos / esperamos, es decir, con la impresión de “Hello world!”, sino que cc65 incluye trozos de código correspondientes al entorno de ejecución de cc65 (runtime) y a librerías de C.

Sin embargo, si volvemos a pulsar “m” dentro del monitor de VICE pronto veremos contenido que nos resulta familiar (la cadena “Hello world!”):

Habría que seguir investigando un poco más para conocer la estructura del programa en código máquina, qué parte corresponde al runtime de cc65, qué parte corresponde a librerías de C, y qué parte corresponde a nuestro programa en C ya compilado.

Una buena forma de empezar puede ser analizando el programa en ensamblador “hello.s”, que resultó de compilar el programa en C “hello.c”:

Inspeccionado este código en ensamblador parece que:

  • Hay un primer parámetro llamado S0001 que apunta al valor $25530d00, es decir, “%S\n” en PETSCII. Este es el formato de impresión que se le pasa a la función printf(). Este parámetro se mete en una pila mediante una llamada a una rutina “pushax”. La pila no es la pila del C64 (página 1 de la RAM), sino la pila del runtime de cc65, es decir, la estructura de datos que usa cc65 para implementar las llamadas a funciones y el paso de parámetros entre ellas.
  • Hay un segundo parámetro que es el texto “text” (“Hello world!”). Este texto, o más bien su dirección, también se mete en la pila mediante una segunda llamada a “pushax”.
  • Mediante el registro Y se pasa un valor 4, que seguramente es el número de bytes (4 bytes = 2 direcciones o punteros) que se han metido en la pila. De este modo la función printf() sabe cuántos parámetros debe procesar, dos en este caso, el puntero al formato “%S\n” y el puntero al texto “Hello world!”.
  • Se llama a la rutina “_printf”, que tiene toda la pinta de ser la versión compilada para el 6502 de la clásica función de C printf().
  • Carga un valor cero en el registro X y en el acumulador, que seguramente sea el retorno correcto (EXIT_SUCESS).
  • Termina con “rts”.

Pues bien, este código de alguna manera u otra tiene que estar dentro del fichero PRG. Y, efectivamente, está un poco antes de la cadena “Hello world!”, en el rango de direcciones de $0840 a $085a:

De este código máquina se puede deducir que la rutina “pushax” empieza en la dirección $0f06 y la rutina “_printf” en la dirección $0eca, al menos en este programa compilado (en otro programa las direcciones podrían ser diferentes). De este modo, podríamos seguir investigando sobre el runtime y las librerías de cc65.

En definitiva, es posible analizar el programa en código máquina que genera cc65, e incluso localizar dentro de él estructuras de datos (ej. cadena “Hello world!”), nuestro programa en C ya compilado, piezas del runtime de cc65 (ej. pila de llamadas) y librerías de C (ej. printf()).

Sin embargo, una compresión completa del programa generado por cc65 requiere un análisis más profundo, ya que cc65 incluye su entorno de ejecución (runtime) y las librerías de C utilizadas por el programa. Se podría abordar, pero llevaría su tiempo, y tampoco sé si tiene mucho interés más allá de las ideas generales ya expuestas.

Hello World en C para el C64

La página de documentación de cc65 (https://cc65.github.io/doc/) tiene cuatro apartados:

  • Un primer apartado con documentación sobre los programas, es decir, sobre el macro ensamblador (ca65.exe), el compilador (cc65.exe), el enlazador (ld65.exe), el compilador – enlazador (cl65.exe), etc.
  • Un segundo apartado con documentación sobre el uso de cc65. Este segundo apartado incluye el clásico ejemplo “Hello World”, trucos para conseguir programas más eficientes, depuración, etc.
  • Un tercer apartado sobre el entorno de ejecución de cc65 (cc65 runtime), la implementación en cc65 de las librerías típicas de C (ANSI / ISO C), y otras librerías propias de cc65 como “dio” para acceso a disco, “tgi” para gráficos, “geos” para el sistema operativo GEOS, “conio” para entrada / salida, “joystick” para manejo de joystick, “mouse” para manejo de ratón, etc.
  • Un cuarto apartado con información específica de las plataformas de destino de cc65, es decir, Apple II, Atari, VIC20, C16, C64, C128, etc.

En esta entrada vamos a revisar el clásico ejemplo “Hello World” para el C64, lo que nos servirá como introducción a todos los apartados anteriores. Este ejemplo se describe en la página https://cc65.github.io/doc/intro.html.

Ficheros fuente:

Los ficheros fuente están en la carpeta cc65\samples\tutorial:

Los ficheros fuente son:

  • “hello.c”: Este fichero está en C.
  • “text.s”: Este fichero está en ensamblador del 6502.

El fichero “Makefile” no es parte del código fuente, es decir, no es parte del programa propiamente dicho. Permite automatizar la compilación y construcción del programa con la herramienta “make” típica de los sistemas Unix / Linux.

El fichero “hello.c” es así:

Es decir, este programa en C referencia una variable llamada “text”, de tipo array de char, y declarada en el fichero en ensamblador “text.s”. También implementa la función main(), que es el programa principal. Este programa principal básicamente llama a la función printf() pasando la variable “text”, que la imprime por consola / pantalla, y luego termina devolviendo el código EXIT_SUCCESS, que indica que el programa ha terminado correctamente.

Por otro lado, el fichero “text.s” es así:

Es decir, es un programa en ensamblador que declara la variable “text” y le da el valor ASCII “Hello world!”.

No es necesario que la variable “text” se declare en un fichero aparte, ni mucho menos en un programa en ensamblador. El ejemplo lo hace así para ejemplificar la combinación de programas en C y programas en ensamblador, lo cual está permitido en cc65.

Proceso de compilación y construcción:

El proceso para construir el programa es así:

  • Con cc65.exe, hay que compilar el fichero “hello.c”. Esto da lugar al fichero en ensamblador “hello.s”.
  • Con ca65.exe, hay que ensamblar los ficheros “hello.s” y “text.s”. Esto da lugar a los ficheros objeto “hello.o” y “text.o”.
  • Con ld65.exe, hay que enlazar los ficheros “hello.o”, “text.o” y “c64.lib”. Esto da lugar al programa ejecutable “hello”.

Para poder ejecutar el compilador (cc65.exe), el ensamblador (ca65.exe) y el enlazador (ld65.exe), o bien usamos el directorio bin como directorio de trabajo, o bien lo incluimos en la variable de entorno PATH, que es la lista de rutas donde Windows busca los ejecutables.

En nuestro caso usaremos bin como directorio de trabajo. Por ello, conviene tener abiertas dos ventanas, una con el directorio cc65\bin y otra con el directorio cc65\samples\tutorial para ir viendo los resultados.

De este modo, si compilamos el fichero fuente C “hello.c”:

efectivamente se genera el fichero ensamblador “hello.s”:

Y si ensamblamos los ficheros “hello.s” y “text.s”:

efectivamente se generan los ficheros objeto “hello.o” y “text.o”.

Por último, si enlazamos los ficheros “hello.o”, “text.o” y “c64.lib”:

efectivamente se genera el ejecutable “hello”:

Programa ejecutable:

El fichero “hello” tiene formato PRG, es decir, formato de programa ejecutable para el C64. Por tanto, si arrastramos el fichero “hello” hasta el emulador VICE vemos esto:

Es decir, se carga un programa en BASIC, se ejecuta RUN, la pantalla se pone en minúsculas, y aparece el mensaje “Hello world!”, que es lo que cabía esperar a la vista de cómo es el programa en C que estamos analizando.

El programa en BASIC se puede analizar con LIST:

Es decir, se trata del programa BASIC:

531 sys2061

es decir, es un programa muy simple que básicamente llama al código máquina almacenado a partir de la dirección 2061 = $080d.

Es importante recalcar que, aunque aparentemente se cargue y ejecute un programa en BASIC, en realidad, el grueso del programa generado por cc65 está en código máquina a partir de la dirección $080d. Es decir, el programa en BASIC es un mero instrumento de cc65 para ejecutar el código máquina del 6502 que resulta de compilar el programa en C.

Una alternativa más directa:

Hay una forma más directa de generar el mismo resultado. Consiste en ejecutar el programa cl65.exe, que compila y enlaza todo en uno:

De este modo se genera el ejecutable “hello” y, además, no se generan los subproductos “hello.s”, “hello.o” y “text.o” (probablemente se generan igualmente, pero luego se borran):

El motivo por el que el ejecutable “hello” generado ahora ocupa 2.669 bytes, mientras que el generado antes ocupaba algo más (2.673 bytes), radica en que ahora con cl65.exe hemos usando la opción –O = Optimize code. Esta opción también puede usarse con el compilador cc65.exe.

Bueno, pues ya sabemos usar cc65 para programar en C y compilar a código máquina del C64.

cc65, un compilador de C para el C64

Hace ya meses que hablamos de 8bitworshop.com, una página web en la que se puede programar online, tanto en ensamblador como en C, para el C64 y otros ordenadores de 8 bits. Ya entonces me entró el gusanito de probar la programación en C para el C64. La programación en C para C64 de 8bitworkshop.com está basada en cc65.

cc65 es un compilador de C para C64 y otros ordenadores basados en el micro 6502. No es el único compilador de C para el C64, pero cc65 tiene la ventaja de ser “cross platform”, es decir, que tú programas y compilas en una máquina moderna, por ejemplo, un PC con Windows o Linux, y luego ejecutas el programa en un C64 o un emulador. De este modo te beneficias de las ventajas y la comodidad de usar un ordenador moderno, a la vez que programas para tu máquina favorita.

La página principal de cc65 es https://cc65.github.io/. En esta página se define cc65 así:

Es decir, cc65 es un completo paquete de desarrollo cruzado para sistemas 6502 que incluye un potente macro ensamblador, un compilador de C, un enlazador y varias otras herramientas. cc65 tiene un “entorno de ejecución para C” y soporta muchas máquinas basadas en el 6502, incluyendo el C64, otras máquinas de Commodore (VIC20, C16, C128, …), y muchas otras que no vienen al caso.

Al final de la página aparecen los siguientes enlaces:

siendo los más interesantes estos dos:

Más adelante revisaremos una selección de la documentación. De momento, llegue con pinchar “Windows Snapshot” y descargar un ZIP de unos 11 MB con cc65:

Como muchas otras herramientas del C64 para Windows, cc65 no requiere especial instalación. Llega con extraer el contenido del ZIP a una carpeta que se puede llamar “cc65” o como más se desee.

Que yo sepa, no hay una interfaz de usuario gráfica para cc65. Se utiliza mediante línea de comandos, es decir, mediante una ventana MS-DOS en el caso de Windows:

Como se puede ver, cc65 consta de varias carpetas: asminc, bin, cfg, html, include, lib, samples y target. En “bin” están los ejecutables del ensamblador, el compilador, el enlazador, etc.; en “html” está la documentación en formato HTML; en “samples” hay algunos ejemplos de programación en C; etc.

El fichero “variables.bat” no es parte de cc65. Es un fichero *.bat que puede resultar útil para definir variables de entorno de Windows ya que, como digo, cc65 se ejecuta desde línea de comandos:

En la siguiente entrada revisaremos un ejemplo de programa en C hecho con cc65 para el C64.

Assembly in one step

En este blog encontraréis montones de referencias útiles sobre C64. Al comienzo del blog tenéis una selección muy completa, pero no son las únicas, ya que en sucesivos “posts” he ido incluyendo más y más referencias:

Y como no paro de toparme con nuevas referencias de interés, aquí va una nueva que me ha parecido interesante:

“Assembly in one step” es un breve tutorial en inglés sobre programación del 6502. No es una mera revisión de sus instrucciones, sino que incluye:

  • Una descripción de la arquitectura del 6502.
  • Los modos de direccionamiento.
  • El juego de instrucciones.
  • Algunos programas de ejemplo.

Todo ello lo hemos revisado en este blog, y seguramente más a fondo, pero siempre está bien conocer fuentes nuevas y darle un repaso a los temas importantes.