User Tools

Site Tools


tutorial_sms

====== Differences ====== This shows you the differences between two versions of the page.

Link to this comparison view

tutorial_sms [2009/11/29 17:22]
xzakox
tutorial_sms [2013/11/10 18:36] (current)
zako [Explicación]
Line 1: Line 1:
 ====== Tutorial de programación de la Sega Master System ====== ====== Tutorial de programación de la Sega Master System ======
  
-No pretendo con esto hacer un tutorial paso a paso de programación de la SMS, sino partir de un programa de ejemplo e ir explicando sobre la marcha el programa y las características de la SMS.+No pretendo con esto hacer un tutorial paso a paso de programación de la SMS, sino partir de un programa de ejemplo e ir explicando sobre la marcha el programa y las características de la SMS, explicando además la programación del z80 que se va viendo en el ejemplo.
  
 Empezamos con el código fuente de nuestro pequeño programa de ejemplo: Empezamos con el código fuente de nuestro pequeño programa de ejemplo:
 {{:​tutorial-sms1.asm|}} {{:​tutorial-sms1.asm|}}
- 
 Que muestra por pantalla el logo de vieju.net, espera que pulsemos el boton 1 del pad y borra la pantalla. Que muestra por pantalla el logo de vieju.net, espera que pulsemos el boton 1 del pad y borra la pantalla.
 +
 +Para generar una rom binaria a partir de este código, con el wla-dx:
 +
 +<​code>​
 +wla-z80 -oi prueba.asm
 +wlalink prueba.link prueba.sms
 +</​code>​
 +
 +Necesitaremos un archivo prueba.link con el siguiente contenido:
 +<​code>​
 +[objects]
 +prueba.o
 +</​code>​
 +
 +Además necesitaremos los archivos de tiles, mapa y paleta: {{:​demo-includes.tar.gz|}}
  
 ===== Explicación ===== ===== Explicación =====
 Vamos diseccionando el programa parte por parte. Vamos diseccionando el programa parte por parte.
-Para empezar, comentar que en ensamblador los numeros decimales se escriben tal cual, los hexadecimales precedidos por $ ($12he) y los binarios precedidos por % (%10011011).+Para empezar, comentar que en ensamblador los numeros decimales se escriben tal cual, los hexadecimales precedidos por $ ($12fe) y los binarios precedidos por % (%10011011).
  
 <code asm> <code asm>
Line 30: Line 44:
 </​code>​ </​code>​
  
-Primero las directivas .MEMORYMAP y .ROMBANKMAP. Estas son directivas del ensamblador wla-dx que nos permiten definir cuestiones relacionadas con la rom que estamos creando. No es código z80, simplemente le dicen al ensamblador como crear el mapa de memoria y los bancos que vamos a usar en nuestro programa. En este caso, le decimos que solo tenemos un slot, de 0h 8000h (32K) y solo un banco de memoria, tambien solamente de 32K.+Primero las directivas .MEMORYMAP y .ROMBANKMAP. Estas son directivas del ensamblador wla-dx que nos permiten definir cuestiones relacionadas con la rom que estamos creando. No es código z80, simplemente le dicen al ensamblador como crear el mapa de memoria y los bancos que vamos a usar en nuestro programa. En este caso, le decimos que solo tenemos un slot, de $0 $8000 (32K) y solo un banco de memoria, tambien solamente de 32K.
 Luego el tag .sdsctag nos permite definir una versión un nombre, descripción y autor del programa. Sencillo. Luego el tag .sdsctag nos permite definir una versión un nombre, descripción y autor del programa. Sencillo.
  
Line 36: Line 50:
 ; Boot ; Boot
 .bank 0 slot 0 .bank 0 slot 0
-.org $0001+.org $0000
  
     di     di
Line 44: Line 58:
  
 Empezamos, usamos la directiva .bank del compilador para decirle que empezamos a definir el primer banco y el primer slot de memoria. Solo tenemos este, asi que solamente nos hará falta definirlo aqui. Empezamos, usamos la directiva .bank del compilador para decirle que empezamos a definir el primer banco y el primer slot de memoria. Solo tenemos este, asi que solamente nos hará falta definirlo aqui.
-Luego con .org le decimos que el programa comienza en 0000h, el primer banco de ROM.+Luego con .org le decimos que el programa comienza en $0000, el primer banco de ROM.
  
 Vamos con las primeras instrucciones de código; //di// deshabilita las interrupciones,​ no las usaremos en un ejemplo tan sencillo. Luego con //im 1//, le decimos al z80 que active el modo 1 de manejo de interrupciones,​ con él, una instrucción //rst $0038// será generada cuando se reciba una interrupción. Vamos con las primeras instrucciones de código; //di// deshabilita las interrupciones,​ no las usaremos en un ejemplo tan sencillo. Luego con //im 1//, le decimos al z80 que active el modo 1 de manejo de interrupciones,​ con él, una instrucción //rst $0038// será generada cuando se reciba una interrupción.
Line 73: Line 87:
  
 Ya empieza lo interesante. Para comenzar, apuntamos la pila a una de las últimas direcciones de la RAM interna. La SMS tiene 8KB ($1fff) de RAM entre las direcciones $c000-$dfff. Asi que apuntamos la pila a $dff0, dejando 16 bytes al final para otras cosas. //sp// es el registro de pila, que contiene la dirección actual del último elemento apuntado en la pila. Ya empieza lo interesante. Para comenzar, apuntamos la pila a una de las últimas direcciones de la RAM interna. La SMS tiene 8KB ($1fff) de RAM entre las direcciones $c000-$dfff. Asi que apuntamos la pila a $dff0, dejando 16 bytes al final para otras cosas. //sp// es el registro de pila, que contiene la dirección actual del último elemento apuntado en la pila.
-Como la pila es decreciente (cuando insertas un valor, SP decrece), no tendremos problemas en que se desborde (a no ser que metamos mas de 8192-bytes en ella!).+Como la pila es decreciente (cuando insertas un valor, SP decrece), no tendremos problemas en que se desborde (a no ser que metamos mas de 8192-16 bytes en ella!).
  
-Ahora vamos con algo más complicado. Inicializar el VDP, el chip gráfico de la SMS. Al final del programa tenemos una tabla con los valores de configuración del VDP entre dos etiquetas, vdp_data y vdp_data_end,​ de momento vamos a asumir que tenemos que usar estos datos para iniciarlo. Bien, entonces empezamos; cargamos el registro de 16 bit //hl//, con la dirección del primero de los datos a cargar. Cargamos en el registro //b// (8 bits) el numero ​de datos a cargar. ​¿Como ​se hace esto? pues diciéndole al ensamblador que reste el numero de bytes entre las etiquetas vdp_data_end y vdp_data. Sencillo, ¿no?. Y ahora cargamos en el registro //c// (tambien ​de 8 bits), la dirección del puerto de configuración del VDP, que si miramos la documentación,​ es el puerto $bf.+Ahora vamos con algo más complicado. Inicializar el VDP, el chip gráfico de la SMS. Al final del programa tenemos una tabla con los valores de configuración del VDP entre dos etiquetas, vdp_data y vdp_data_end,​ de momento vamos a asumir que tenemos que usar estos datos para iniciarlo. Bien, entonces empezamos; cargamos el registro de 16 bit //hl//, con la dirección del primero de los datos a cargar. Cargamos en el registro //b// (8 bits) el número ​de datos a cargar. ​¿Cómo ​se hace esto? pues diciéndole al ensamblador que reste el numero de bytes entre las etiquetas vdp_data_end y vdp_data. Sencillo, ¿no?. Y ahora cargamos en el registro //c// (también ​de 8 bits), la dirección del puerto de configuración del VDP, que si miramos la documentación,​ es el puerto $bf.
 Y ahora vamos con una compleja instrucción del z80, //otir//. //otir// es una instrucción de salida de bloque con incremento. ¿quéeee? Bien, en el z80 hay un conjunto de órdenes para usar los puertos de entrada/​salida,​ como //out ($de), a//, que envía el contenido del registro //a//, al puerto $de. Pues esta instrucción sirve para enviar un bloque de datos a un puerto de manera automática. ¿Cómo funciona? Pues espera que carguemos la dirección inicial de los datos en el registro //hl//, el número de datos a mandar en el registro //b//, y el puerto a usar en el registro //c//. Entonces, //otir// coge el primer dato apuntado por la dirección en //hl//, y lo envia al puerto que le decimos en //c//. Ahora decrementa //b//, nuestro contador de datos. Si //b// es igual a cero, la instrucción termina y la cpu pasa a la siguiente instrucción. En caso de que no fuera cero, pues incrementa la dirección en //hl//, con lo que apunta al siguiente dato a copiar, y sigue el proceso. Genial ¿no? Y ahora vamos con una compleja instrucción del z80, //otir//. //otir// es una instrucción de salida de bloque con incremento. ¿quéeee? Bien, en el z80 hay un conjunto de órdenes para usar los puertos de entrada/​salida,​ como //out ($de), a//, que envía el contenido del registro //a//, al puerto $de. Pues esta instrucción sirve para enviar un bloque de datos a un puerto de manera automática. ¿Cómo funciona? Pues espera que carguemos la dirección inicial de los datos en el registro //hl//, el número de datos a mandar en el registro //b//, y el puerto a usar en el registro //c//. Entonces, //otir// coge el primer dato apuntado por la dirección en //hl//, y lo envia al puerto que le decimos en //c//. Ahora decrementa //b//, nuestro contador de datos. Si //b// es igual a cero, la instrucción termina y la cpu pasa a la siguiente instrucción. En caso de que no fuera cero, pues incrementa la dirección en //hl//, con lo que apunta al siguiente dato a copiar, y sigue el proceso. Genial ¿no?
 Cuando termina esta instrucción ya hemos enviado todos los datos de configuración al VDP, vamos a por acción. Cuando termina esta instrucción ya hemos enviado todos los datos de configuración al VDP, vamos a por acción.
Line 89: Line 103:
 Entonces lo primero que hacemos es limpiarla Entonces lo primero que hacemos es limpiarla
 Para ello vamos a usar dos rutinas que están definidias más adelante, asi que tocará explicarlas también. Para ello vamos a usar dos rutinas que están definidias más adelante, asi que tocará explicarlas también.
-Por lo que se ve en el código, cargamos el registro //hl// con el valor $4000 y luego llamamos a dos rutinas con la instrucción //call//. Las rutinas se llaman ​asi, tan simple, //call etiqueta//.+Por lo que se ve en el código, cargamos el registro //hl// con el valor $4000 y luego llamamos a dos rutinas con la instrucción //call//. Las rutinas se invocan ​asi, tan simple, //call etiqueta//.
  
 Vamos entonces a explicar las subrutinas: Vamos entonces a explicar las subrutinas:
Line 104: Line 118:
 </​code>​ </​code>​
  
-Esta rutina es muy útil, como veremos. El VDPno está mapeado en la memoria principal, y como vimos antes, nos comunicamos con él usando los puertos de entrada y salida del z80. Como las instrucciones de E/S son limitadas, se puede hacer tedioso escribir ciertos bloques de código por ejemplo este caso. Quememos decirle al VDP que vamos a usar la dirección $4000 de la VRAM. Para ello, usando la instrucción normal de salida, out (puerto), a, tenemos que cargar en a, el valor $00 (el byte menos significativo),​ enviarlo con out al puerto $bf, cargar a con $40 (byte más significativo) y volver a enviarlo al puerto $bf, con lo que ahora el vdp ha recibido nuestra dirección de 16 bits. Bien, para ahorrarnos escribir todo eso cada vez que queramos pedir una dirección al vdp, (que en un juego será muy amenudo), pues creamos esta rutina. ¿Qué hace? pues lo mismo que describimos anteriormente,​ pero carga los valores en //a// sacándolos de los registros //l// y //h//, que juntos forman el registro //hl// de 16 bits. Entonces hace esto, primero mete el registro //af// en la pila (como vamos a usar //a// así no perdemos su contenido al llamar a esta rutina, y asi tampoco perdemos el registro de flags //f//). Luego cargamos en //a// el contenido de //l// (byte menos significativo de //hl//) lo mandamos al VDP, cargamos //a// con //h// (byte más significativo de //hl//), lo mandamos al VDP, y antes de volver con //ret//, sacamos af de la pila para restaurar ​sus valores antes de llamar a la rutina. Asi, como vimos antes, solo tenemos que meter el //hl// la dirección que queremos ​pedirpe ​al VDP, y llamar a esta rutina. Asi ya vemos también como se pasan parámetros en ensamblador,​ usando los registros :-)+Esta rutina es muy útil, como veremos. El VDP no está mapeado en la memoria principal, y como vimos antes, nos comunicamos con él usando los puertos de entrada y salida del z80. Al ser las instrucciones de E/S limitadas ​(además sólo podemos usar datos de 8 bits), se puede hacer tedioso escribir ciertos bloques de código, como por ejemplo ​en este caso. Quememos decirle al VDP que vamos a usar la dirección $4000 de la VRAM. Para ello, usando la instrucción normal de salida, ​//out (puerto), a//, tenemos que cargar en //a//, el valor $00 (el byte menos significativo),​ enviarlo con //out// al puerto $bf, cargar ​//a// con $40 (byte más significativo) y volver a enviarlo al puerto $bf, con lo que ahora el VDP ha recibido nuestra dirección de 16 bits. Bien, para ahorrarnos escribir todo eso cada vez que queramos pedir una dirección al VDP, (que en un juego será muy amenudo), pues creamos esta rutina. ¿Qué hace? pues lo mismo que describimos anteriormente,​ pero carga los valores en //a// sacándolos de los registros //l// y //h//, que juntos forman el registro //hl// de 16 bits. Entonces hace esto, primero mete el registro //af// en la pila (como vamos a usar //a// así no perdemos su contenido al llamar a esta rutina, y asi tampoco perdemos el registro de flags //f//). Luego cargamos en //a// el contenido de //l// (byte menos significativo de //hl//)lo mandamos al VDP, cargamos //a// con //h// (byte más significativo de //hl//), lo mandamos al VDP, y antes de volver con //ret//, sacamos af de la pila con //pop af// para restaurar ​los valores ​que tenian ​antes de llamar a la rutina. Asi, como vimos antes, solo tenemos que meter el //hl// la dirección que queremos ​pedirle ​al VDP, y llamar a esta rutina, y asi ya vemos también como se pasan parámetros en ensamblador,​ usando los registros :-)
  
-Ahora vamos con la siguiente ​rutina:+Ahora vamos con la siguiente:
 <code asm> <code asm>
 ; limpia la vram ; limpia la vram
tutorial_sms.1259511742.txt.gz · Last modified: 2009/11/29 17:22 by xzakox