Programación en X68000 capítulo 3, Anatomía de un programa.
En este artículo voy a explicar la estructura que uso en mis programas en X68000.
Normalmente declaro varias variables y una serie de funciones antes de la función main que implemento después y que sirven para interactuar con el ordenador.
Si leíste los dos artículos anteriores podemos trabajar con dos cadenas de herramientas, la de Lydux y la de Jason Stevens basada en MARIKO que es la original.
Como los dos toolchains usan diferentes nombres de cabecera y Lydux no tiene un alias para las interrupciones, hago por tanto dos ramas de compilación con el pre procesador que busca si la constante __MARIKO_CC__ está declarada.
#ifdef __MARIKO_CC__ #include <doslib.h> #include <iocslib.h> #else #include <dos.h> #include <iocs.h> #define interrupt __attribute__ ((interrupt_handler)) #endif
Entonces dependiendo de si compilamos con uno y otro usamos unas cabeceras u otras y declaramos el alias ara la interrupción.
Tambien incluyo estas dos liberias:
#include <stdio.h> #include <signal.h>
La primera es la de siempre stdio.h que ya declara constantes como NULL y con la que podemos hacer las cosas estándar.
La segunda librería signal.h es para capturar el evento de presionar Ctrl + C que cierra el programa y nos devuelve al sistema operativo. De esta manera podemos liberar recursos y cerrar el programa limpiamente.
Es importante hacer esto porque podemos haber cambiado el modo de pantalla y si salimos sin más del programa, cuando volvemos a Human68k se queda con ese modo y tenemos que reiniciar.
Después declaro estas dos variables:
char last_mode; volatile int _timer = 0;
La variable last_mode es para capturar el modo de pantalla que había cuando se lanzó el programa.
Esto es para que si cambiamos de modo lo podamos re establecer.
La variable _timer es para establecer un temporizador que podamos usar para medir el tiempo.
Tiene que ser volatile para evitar que el compilador la elimine al optimizar el programa porque no entienda como la usamos.
Después declaro una serie de interrupciones. Para quien no lo sepa, una interrupción es una función que asociamos a un evento del sistema operativo y donde le decimos al ordenador que queremos que haga cuando ese evento sucede.
Estas interrupciones son:
void interrupt timer(); // for timing void interrupt vsync_disp(); // for vertical sync void interrupt hsyncst(); // for horizontal sync void interrupt crtcras(); // for raster sync
La interrupción timer se puede asociar a un temporizador al que le programamos la frecuencia con la que tiene que ser llamado y podemos alojar un incremento en la variable _timer.
Esta interrupción tendría este aspecto:
void interrupt timer()
{
++_timer;
}
La interrupción vsync_disp se puede asociar al evento de sincronización verical. Este es un evento que ocurre cada vez que el X68000 va a empezar a pintar la pantalla. Se puede usar para sincronizar el programa con el refresco de pantalla. Este evento ocurriría 60 veces por segundo.
La interrupción hsyncst es parecida a vsync_disp pero se dispara cada vez que el X68000 va a pintar una linea horizontal. Esto es que si hay unas 512 lineas y la pantalla se pinta 60 veces por segundo, este evento se llama unas 31000 veces por segundo.
Esto se puede usar para hacer efectos con el escaneo de pantalla como cambiar la paleta conforme se pinta la pantalla o cambiar los sprites de modo que parezca que hay mas.
El código en esta interrupción tiene que ser extremadamente rápido y optimizado o sino nunca terminará de ejecutarse ya que sera llamada otra vez y otra antes de que termine y el ordenador se bloqueará.
La interrupción crtcras es igual que hsyncst pero solo se dispara cuando el escaneo llega a cierta linea que nosotros le decimos.
Como esto no se ejecuta siempre sino solo en determinada linea tenemos tiempo de hacer mas cosas que con hsyncst. Podemos incluso re programar la siguiente interrupción si queremos que ocurra en varias lineas en el mismo fotograma.
Es importante que si estas interrupciones usan variables globales, estas sean declaradas volatile.
Esto es porque las interrupciones no son funciones que se llamen por nuestro programa sino por el sistema operativo y el compilador no entiende el contexto en que el que acceden a las variables y sus optimizaciones podrían generar un ensamblador que no las use y sencillamente no funcionarían.
Habrá un artículo mas adelante explicando en profundidad estas interrupciones. Es solo que es bueno saber que existen.
Después de las interrupciones declaro estas dos funciones:
void init(); void terminate();
La función init() es para inicializar cosas. Por ejemplo, es en esta función donde capturo el modo de pantalla con este comando:
last_mode = _iocs_crtmod(-1); //capture the video mode before the program
Después suelo desactivar el cursor de la línea de comando:
_iocs_b_curoff(); //disable the cursor
Límpio la pantalla, esto no es necesario si se cambia de resolucion:
_iocs_b_clr_al(); //clear the whole screen
Aquí también inicio la interrupción del temporizador:
_iocs_timerdst( timer, //Processing address (interrupt disabled at 0) 7, //Unit time (1 = 1.0, 2 = 2.5, 3 = 4.0, 4 = 12.5, 5 = 16.0, 6 = 25.0, 7 = 50.0, micro sec unit) 20 //Counter (when 0, treat as 256) );
El primer parametro es el nombre de la interrupción, el segundo la unidad de tiempo el cual esta en 50 microsegundos y el tercero la frecuencia, cada cuantas veces la unidad seleccionada, queremos disparar la interrupcion. En este caso cada 20 veces 50 micro segundos, es decir 1000 microsegundos, lo que es cada 1 mili segundo.
Se pueden hacer muchas mas cosas en esta función claro. No es obligatorio hacerlo así pero creo que es una buena práctica.
Y por ultimo asocio la otra función terminate() a la señal que sistema operativo manda al programa cuando pulsamos Ctrl + C de modo que se lanza cuando pasa eso.
La función terminate() pues hace lo opuesto. Esto es liberar los recursos.
En el ejemplo des asocio las interrupciones que haya podido configurar:
_iocs_vdispst(
(void *)NULL,
0,//0: vertical blanking interval 1: vertical display period
0
);
_iocs_hsyncst ((void *)NULL); //horizontal interrupt
_iocs_crtcras (
(void *)NULL,
0 //int_ luster
);
_iocs_timerdst(
(void *)NULL, //Processing address (interrupt disabled at 0)
0, //Unit time (1 = 1.0, 2 = 2.5, 3 = 4.0, 4 = 12.5, 5 = 16.0, 6 = 25.0, 7 = 50.0, micro sec unit)
0 //Counter (when 0, treat as 256)
);
Esto es importante porque sino se hace. Aunque el programa se haya cerrado, las interrupciones siguen ahí ejecutándose.
Vuelvo a activar el cursor de la línea de comandos:
_iocs_b_curon();
Vuelvo a re establecer el modo de pantalla:
_iocs_crtmod(last_mode);
y finalmente salimos del programa:
_dos_exit();
Por último voy a explicar la funcion main() que es el punto de entrada de nuestro programa.
Empezaría llamando la función init(), después tendríamos el cuerpo del programa que en este caso imprimimos el obligado Hello World!, y una indicación para pulsar una tecla.
init();
//body of our program
_dos_c_print("Hello world !\r\n");
_dos_c_print("Press a key.\r\n");
Después tenemos un bucle que se interrumpe al pulsar una tecla pero que imprime el numero del temporizador en ese momento de modo que podemos ver la cuenta del tiempo.
while(_dos_inpout(0xFF) == 0){
_dos_c_locate(0,2);
printf("counting %d \r\n", _timer);
}
En el X68000 podemos usar _dos_c_print o printf para imprimir cadenas. Pero _dos_c_print no soporta formatos. Es estrictamente como el comando puts() en C++.
Con _dos_c_locate(0,2); situamos el cursor en la columna 0 y la fila 2 para que lo que se imprima después se imprima a partir de ahí. De otra forma se imprimiría en la siguiente línea y en la siguiente y así.
Al pulsar una tecla nos salimos del bucle y llamamos terminate() y se acaba el programa.
El código completo de este programa quedaría así:
#ifdef __MARIKO_CC__
#include <doslib.h>
#include <iocslib.h>
#else
#include <dos.h>
#include <iocs.h>
#define interrupt __attribute__ ((interrupt_handler))
#endif
#include <stdio.h>
#include <signal.h>
char last_mode; // video mode before the program started
volatile int _timer = 0; // time
void interrupt timer(); // for timing
void interrupt vsync_disp(); // for vertical sync
void interrupt hsyncst(); // for horizontal sync
void interrupt crtcras(); // for raster sync
void init();
void terminate();
int main(void)
{
init();
//body of our program
_dos_c_print("Hello world !\r\n");
_dos_c_print("Press a key.\r\n");
while(_dos_inpout(0xFF) == 0){
_dos_c_locate(0,2);
printf("counting %d \r\n", _timer);
}
//end of our program
terminate();
return 0;
}
void init()
{
last_mode = _iocs_crtmod(-1); //capture the video mode before the program
_iocs_b_curoff(); //disable the cursor
_iocs_b_clr_al(); //clear the whole screen
/**
* Other initialization actions.
*/
_iocs_timerdst(
timer, //Processing address (interrupt disabled at 0)
7, //Unit time (1 = 1.0, 2 = 2.5, 3 = 4.0, 4 = 12.5, 5 = 16.0, 6 = 25.0, 7 = 50.0, micro sec unit)
20 //Counter (when 0, treat as 256)
);
signal(SIGINT, terminate); //for the Control + C
}
void terminate()
{
/* Un interrupt */
_iocs_vdispst(
(void *)NULL,
0,//0: vertical blanking interval 1: vertical display period
0
);
_iocs_hsyncst ((void *)NULL); //horizontal interrupt
_iocs_crtcras (
(void *)NULL,
0 //int_ luster
);
_iocs_timerdst(
(void *)NULL, //Processing address (interrupt disabled at 0)
0, //Unit time (1 = 1.0, 2 = 2.5, 3 = 4.0, 4 = 12.5, 5 = 16.0, 6 = 25.0, 7 = 50.0, micro sec unit)
0 //Counter (when 0, treat as 256)
);
//we activate the console cursor
_iocs_b_curon();
//we restore the video mode
_iocs_crtmod(last_mode);
//we exit the program
_dos_exit();
}
void interrupt timer()
{
++_timer;
}
void interrupt vsync_disp()
{
//whatever you do in this event
}
void interrupt hsyncst()
{
//whatever you do in this event
//it has to be real quick
}
void interrupt crtcras()
{
//whatever you do in this event
}
También puedes encontrarlo en mi Githup en https://github.com/FedericoTech/X68KTutorials/tree/main/Anatomy
Espero que sea de utilidad como toma de contacto a la programación en X68000.
Un saludo y hasta otra.

Comentarios