Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previaÚltima revisiónAmbos lados, revisión siguiente | ||
cursos:ensamblador:avanzadas1 [05-01-2024 14:29] – [Dirección inicial de carga del código máquina] sromero | cursos:ensamblador:avanzadas1 [21-01-2024 10:26] – [Obtener el valor de PC (Contador de Programa)] sromero | ||
---|---|---|---|
Línea 14: | Línea 14: | ||
En todos los programas que hemos visto, hemos recomendado ubicar nuestros programas en lugares como **35000**, es decir, utilizar el área desde 35000 hasta 65535 (si no usamos los UDGs o Gráficos Definidos por el Usuario) o hasta 65338 si los usamos (porque los últimos 168 bytes se suelen reservar para los UDGs en BASIC). | En todos los programas que hemos visto, hemos recomendado ubicar nuestros programas en lugares como **35000**, es decir, utilizar el área desde 35000 hasta 65535 (si no usamos los UDGs o Gráficos Definidos por el Usuario) o hasta 65338 si los usamos (porque los últimos 168 bytes se suelen reservar para los UDGs en BASIC). | ||
- | Eso nos proporciona (con un ORG 35000) hasta unos 30KB de RAM en un Spectrum de 48KB para nuestro programa incluyendo código, textos, gráficos y sonidos. Podremos alojar datos y código, y leer y escribir en la memoria, en todo el espacio entre 35000 y 65535, es decir, en 30535 bytes (29.8KB, es decir, casi 30KB). | + | Eso nos proporciona (con un '' |
- | Si bajamos ORG hasta 30000, ganamos unos 5KB extras para nuestro programa. Bajándolo hasta 28000, ganamos 7KB. Y en 26000 tenemos aproximadante 9 valiosísimos Kilobytes adicionales. | + | Si bajamos |
- | **__Sin embargo, nuestra recomendación es utilizar | + | **__Sin embargo, nuestra recomendación es utilizar |
Reducir la dirección de origen por debajo de 32768 tiene dos problemas que vamos a ver a continuación. | Reducir la dirección de origen por debajo de 32768 tiene dos problemas que vamos a ver a continuación. | ||
Línea 26: | Línea 26: | ||
**1.- BASIC y la pila del Z80 están por debajo de ORG** | **1.- BASIC y la pila del Z80 están por debajo de ORG** | ||
- | Por un lado, por debajo de nuestro programa están las variables del sistema, está BASIC, está el programa BASIC que estamos ejecutando y después del programa BASIC (decreciendo desde RAMTOP) tenemos la pila (stack) del Z80. | + | Por un lado, por debajo de nuestro programa están las variables del sistema, está BASIC, está el programa BASIC que estamos ejecutando y después del programa BASIC (decreciendo desde '' |
- | Es decir, que cuando usamos un **ORG dirección**, debajo de esa dirección debe caber tanto la pila como el programa cargador que vamos a usar para lanzar nuestro código máquina. | + | Es decir, que cuando usamos un '' |
Hagamos un ejercicio de cálculo y supongamos que no queremos volver a BASIC al acabar de ejecutarse nuestro programa (algo que era normal en juegos y programas, donde simplemente reseteábamos el ordenador)... ¿hasta qué valor podríamos bajar el ORG y cargar los datos en él, sin afectar al cargador BASIC? | Hagamos un ejercicio de cálculo y supongamos que no queremos volver a BASIC al acabar de ejecutarse nuestro programa (algo que era normal en juegos y programas, donde simplemente reseteábamos el ordenador)... ¿hasta qué valor podríamos bajar el ORG y cargar los datos en él, sin afectar al cargador BASIC? | ||
- | Como dirección mínima para un ORG, para que haya espacio para un cargador BASIC mínimo (un **CLEAR**, un **LOAD "" | + | Como dirección mínima para un ORG, para que haya espacio para un cargador BASIC mínimo (un '' |
En el Spectrum un programa Basic comienza normalmente a partir de 23755. Por poco código BASIC que introduzcamos en el cargador, prácticamente llegaremos hasta la dirección 24000. Por eso muchos cargadores Basic tenían una línea 0 en la que mediante un REM ya metían el cargador en código máquina (que además permitía cargar sin las cabeceras, ahorrando ese espacio). | En el Spectrum un programa Basic comienza normalmente a partir de 23755. Por poco código BASIC que introduzcamos en el cargador, prácticamente llegaremos hasta la dirección 24000. Por eso muchos cargadores Basic tenían una línea 0 en la que mediante un REM ya metían el cargador en código máquina (que además permitía cargar sin las cabeceras, ahorrando ese espacio). | ||
Línea 52: | Línea 52: | ||
El código que está en la contended memory tiene que ser leído por el procesador en el ciclo de " | El código que está en la contended memory tiene que ser leído por el procesador en el ciclo de " | ||
- | Entonces ¿por qué no ubicar nuestro programa en **ORG 32768**? ¿Por qué se ha recomendado un valor mínimo de **ORG 33500**? | + | Entonces ¿por qué no ubicar nuestro programa en '' |
- | Con "**ORG 32768**" | + | Con '' |
- | Al igual que pasaba con el código de nuestro programa, en este caso cualquier operación PUSH, POP, CALL (que tiene que hacer PUSH de la dirección de retorno a la pila) y RET (que tiene que leerla para volver) podrá ser interrumpida y retrasada por la ULA. Y CALL, RET, PUSH y POP son operaciones que usaremos mucho en nuestros programas. | + | Al igual que pasaba con el código de nuestro programa, en este caso cualquier operación |
Para que la pila quede fuera de la contended memory, dado que BASIC la situa por debajo de ORG, hay que subir un poco ORG, para darle espacio dentro de estos primeros bytes del bloque 32K-48K. Si establecemos ORG en diferentes valores, y con el siguiente programa miramos el valor de SP al inicio de nuestro programa después de cargarlo, veremos lo siguiente: | Para que la pila quede fuera de la contended memory, dado que BASIC la situa por debajo de ORG, hay que subir un poco ORG, para darle espacio dentro de estos primeros bytes del bloque 32K-48K. Si establecemos ORG en diferentes valores, y con el siguiente programa miramos el valor de SP al inicio de nuestro programa después de cargarlo, veremos lo siguiente: | ||
Línea 62: | Línea 62: | ||
<code z80> | <code z80> | ||
; Programa utilizado para ver el valor de SP con diferentes ORG | ; Programa utilizado para ver el valor de SP con diferentes ORG | ||
- | ORG 33500 | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ROM_CLS | + | ROM_CLS |
- | ROM_STACK_BC | + | ROM_STACK_BC |
- | ROM_PRINT_FP | + | ROM_PRINT_FP |
- | END 33500 | + | |
</ | </ | ||
Línea 93: | Línea 93: | ||
\\ | \\ | ||
- | "ORG 35000" | + | '' |
- | "ORG 34000" | + | '' |
- | Con "ORG 33500" | + | Con '' |
Cabe destacar que **no estamos hablando de que sólo tengamos 708 bytes de pila, y que si hacemos PUSHes adicionales se vaya a colgar el programa**. No, simplemente si hacemos muchas apilaciones de datos, sin sacarlos de la pila, los nuevos valores después de algo más de 350 PUSH' | Cabe destacar que **no estamos hablando de que sólo tengamos 708 bytes de pila, y que si hacemos PUSHes adicionales se vaya a colgar el programa**. No, simplemente si hacemos muchas apilaciones de datos, sin sacarlos de la pila, los nuevos valores después de algo más de 350 PUSH' | ||
- | Y recordemos que la pila crece hacia abajo con PUSH y CALL pero decrece y tiende a volver a las direcciones más altas con cada POP y RET, por lo que lo normal es que se mueva en la parte superior donde la hemos situado, salvo que usemos funciones con recursividad, | + | Y recordemos que la pila crece hacia abajo con '' |
Así pues, nuestra recomendación de valor de inicio **ORG** mínimo para cualquier programa sería de __**cualquiera de esos 4 valores**__, | Así pues, nuestra recomendación de valor de inicio **ORG** mínimo para cualquier programa sería de __**cualquiera de esos 4 valores**__, | ||
Va a ser complicado que lleguemos a ocupar los 30-31KB de memoria que tenemos por delante, salvo que estemos realizando un juego de calidad comercial o con muchos datos. En ese caso, en este mismo capítulo veremos ideas para, entre otras cosas, darle un uso a esos 7KB de RAM " | Va a ser complicado que lleguemos a ocupar los 30-31KB de memoria que tenemos por delante, salvo que estemos realizando un juego de calidad comercial o con muchos datos. En ese caso, en este mismo capítulo veremos ideas para, entre otras cosas, darle un uso a esos 7KB de RAM " | ||
- | |||
- | |||
\\ | \\ | ||
Línea 137: | Línea 135: | ||
\\ | \\ | ||
- | Para poner uno de los bloques disponibles en la " | + | Para poner uno de los bloques disponibles en la " |
Eso quiere decir que tenemos que organizar y diseñar nuestro código alrededor de esto ya que: | Eso quiere decir que tenemos que organizar y diseñar nuestro código alrededor de esto ya que: | ||
- | * El código de nuestro programa deberá estar en la ventana que no se mapea (entre 32768 -$8000- y 49152 -$BFFF-). Si vamos a cambiar de banco, la ejecución del programa no puede estar en el banco que vamos a " | + | * El código de nuestro programa deberá estar en la ventana que no se mapea (entre 32768 -$8000- y 49152 -$bfff-). Si vamos a cambiar de banco, la ejecución del programa no puede estar en el banco que vamos a " |
- | * La pila tampoco puede estar situada en el bloque que paginamos, porque la perderíamos y el próximo POP/RET tendría resultados desastrosos. Por suerte, en nuestro caso estamos ubicándola por debajo del ORG así que no debemos de tener problemas. | + | * La pila tampoco puede estar situada en el bloque que paginamos, porque la perderíamos y el próximo |
* Las rutinas de ISR y las rutinas que reproduzcan sonidos deberían también estar en la parte de la memoria no paginable, a menos que precisamente queramos aprovechar uno de los bancos de memoria del Spectrum para alojar la música y el reproductor. En ese caso en la rutina que atiende a la interrupción lo que haríamos sería cambiar de banco, llamar al reproductor para que " | * Las rutinas de ISR y las rutinas que reproduzcan sonidos deberían también estar en la parte de la memoria no paginable, a menos que precisamente queramos aprovechar uno de los bancos de memoria del Spectrum para alojar la música y el reproductor. En ese caso en la rutina que atiende a la interrupción lo que haríamos sería cambiar de banco, llamar al reproductor para que " | ||
Línea 149: | Línea 147: | ||
* Si no tenemos problemas de memoria y queremos hacer una versión 48K del mismo juego 128K, podemos hacer que la única diferencia entre ambos sea la carga de la música y el reproductor en un banco 128K. El " | * Si no tenemos problemas de memoria y queremos hacer una versión 48K del mismo juego 128K, podemos hacer que la única diferencia entre ambos sea la carga de la música y el reproductor en un banco 128K. El " | ||
- | * Durante la carga del juego podríamos ir cambiando de banco para cargar diferentes bloques de datos desde cinta en los diferentes bancos. Esto lo podemos hacer por ejemplo cargando primero en RAM una pequeña rutina de cambio de bancos llamable desde BASIC (con USR) para ir cambiando de banco y hacer los correspondientes | + | * Durante la carga del juego podríamos ir cambiando de banco para cargar diferentes bloques de datos desde cinta en los diferentes bancos. Esto lo podemos hacer por ejemplo cargando primero en RAM una pequeña rutina de cambio de bancos llamable desde BASIC (con '' |
* Organizando bien los datos del juego, el mismo ejecutable podría ser versión 48K y 128K. Simplemente, | * Organizando bien los datos del juego, el mismo ejecutable podría ser versión 48K y 128K. Simplemente, | ||
Línea 173: | Línea 171: | ||
Para hacer esto, lo idea sería tener 2 ORG en nuestro programa en sjasmplus, o dos ficheros ASM en pasmo, para generar 2 ficheros BIN de código máquina separados. En el primero, tendríamos el punto de ejecución del inicio de nuestro programa. | Para hacer esto, lo idea sería tener 2 ORG en nuestro programa en sjasmplus, o dos ficheros ASM en pasmo, para generar 2 ficheros BIN de código máquina separados. En el primero, tendríamos el punto de ejecución del inicio de nuestro programa. | ||
- | Lo primero que hacemos es cambiar la pila para sacarla de la contended memory, asignándola a la misma dirección donde empieza el código del juego en sí (33500). Debido a este cambio, ya no podremos volver al BASIC si finalizamos el menú con un RET. Lo normal es que no se vuelva más al BASIC, pero si quisíeramos hacerlo, podemos guardarnos el valor de SP en una variable en memoria (**pila_original DEFW 0**) y restaurar el valor de SP antes de hacer un RET de salida del menú. | + | Lo primero que hacemos es cambiar la pila para sacarla de la contended memory, asignándola a la misma dirección donde empieza el código del juego en sí (33500). Debido a este cambio, ya no podremos volver al BASIC si finalizamos el menú con un ret. Lo normal es que no se vuelva más al BASIC, pero si quisíeramos hacerlo, podemos guardarnos el valor de SP en una variable en memoria ('' |
Lo siguiente que hacemos es llamar a todas las rutinas de inicialización de uso único que tenga el programa, alojadas en este " | Lo siguiente que hacemos es llamar a todas las rutinas de inicialización de uso único que tenga el programa, alojadas en este " | ||
Línea 179: | Línea 177: | ||
Finalmente ejecutamos todo el código del menú hasta el momento en que el jugador comience la partida que deberemos saltar al punto de entrada del juego. | Finalmente ejecutamos todo el código del menú hasta el momento en que el jugador comience la partida que deberemos saltar al punto de entrada del juego. | ||
- | El segundo | + | El BIN generado por el primer ASM se utiliza para generar |
<code z80> | <code z80> | ||
;;; Fichero menu.asm => genera menu.bin | ;;; Fichero menu.asm => genera menu.bin | ||
- | ORG 26000 | + | |
- | ; Ponemos la pila colgando del programa principal, | + | ; Ponemos la pila colgando del programa principal, |
- | ; y fuera de la contended memory. | + | ; de la contended memory. |
- | | + | |
+ | ; recuperarla antes del RET final del programa. | ||
+ | ld sp, 33500 | ||
- | | + | |
- | | + | |
- | menu: | + | Menu: |
; codigo del menu aqui. | ; codigo del menu aqui. | ||
; ... en algun punto.. | ; ... en algun punto.. | ||
- | | + | |
- | | + | |
graficos_menu DEFB ..... | graficos_menu DEFB ..... | ||
Línea 203: | Línea 203: | ||
; rutinas que queremos alojar en este bloque de datos | ; rutinas que queremos alojar en este bloque de datos | ||
- | END 26000 | + | |
- | </ | + | </ |
+ | |||
+ | El segundo BIN, el del juego, se define en 33500 y contiene todo el código del mismo, unos 32KB de código entero para nuestro programa, del que hemos " | ||
<code z80> | <code z80> | ||
;;; Fichero juego.asm => genera juego.bin | ;;; Fichero juego.asm => genera juego.bin | ||
- | ORG 33500 | + | |
- | iniciar_partida: | + | Iniciar_Partida: |
; codigo del juego aqui | ; codigo del juego aqui | ||
- | ; para volver al menu, un RET | + | ; para volver al menu, un ret |
- | | + | |
- | </ | + | END 33500 |
+ | </ | ||
Al tener 2 BINs, nos tendremos que hacer un cargador BASIC personalizado que cargue cada bloque de cinta en su dirección correcta, y crear una cinta que contenga: | Al tener 2 BINs, nos tendremos que hacer un cargador BASIC personalizado que cargue cada bloque de cinta en su dirección correcta, y crear una cinta que contenga: | ||
Línea 225: | Línea 228: | ||
</ | </ | ||
- | Otra opción, en lugar de colocar el menú a partir de 26000, sería comprimir los niveles del mapeado de nuestro juego en ZX0, e incluirlos con un INCBIN en un fichero ASM con origen 26000 para cargarlos en esa zona. | + | Otra opción, en lugar de colocar el menú a partir de 26000, sería comprimir los niveles del mapeado de nuestro juego en ZX0, e incluirlos con un '' |
Después, en el código principal del juego antes de empezar cada pantalla, podemos " | Después, en el código principal del juego antes de empezar cada pantalla, podemos " | ||
Línea 263: | Línea 266: | ||
El segundo mecanismo de ahorro de tiempo de carga si usamos gráficos con máscaras, es " | El segundo mecanismo de ahorro de tiempo de carga si usamos gráficos con máscaras, es " | ||
+ | \\ | ||
+ | ==== Utilizar el Buffer de Impresión para la pila o alojar variables ==== | ||
+ | |||
+ | Existe una zona de memoria en el Spectrum de 48K que BASIC utiliza como " | ||
+ | |||
+ | Este área son 256 bytes, desde 23296 (5B00h) a 23551 (5BFFh). | ||
+ | |||
+ | Hay gente que pone la pila en la posición de memoria del buffer de impresión, que puede ser suficiente si no vamos a hacer más de 128 anidaciones (cada '' | ||
+ | |||
+ | Aún así, podemos utilizar esa zona como " | ||
+ | |||
+ | 1.- Está en la Contended Memory, aunque no es un problema grande para lo que es el acceso puntual a variables. | ||
+ | |||
+ | 2.- Los modelos de 128 tienen ahí rutinas de paginación de las ROM de sintaxis 128 y también partes del intérpretes de 48. Si pretendemos volver al BASIC después de ejecutar nuestro programa, o usar rutinas de la ROM en un 128K, no podemos tocar ese área. | ||
+ | |||
+ | 3.- Tampoco podremos usarlo en modelos 128K si tenemos activas las interrupciones en im1 (Modo de Interrupciones 1). Si usamos nuestra propia rutina ISR en im2 y desde ella no llamamos a la im1 (para que actualice FRAMES u otras variables del sistema), no debería haber problemas en utilizar ese área si vemos que nuestro programa crece y necesitamos arañar unos bytes para las variables del mismo. | ||
+ | |||
+ | \\ | ||
\\ | \\ | ||
Línea 275: | Línea 296: | ||
Una pantalla de carga de casi 7KB, comprimida, se puede quedar en menos de 3KB (a veces incluso 1.5 - 2 KB). Es una reducción de como mínimo el 50% del tiempo de carga, si no de un 66% en algunas ocasiones. | Una pantalla de carga de casi 7KB, comprimida, se puede quedar en menos de 3KB (a veces incluso 1.5 - 2 KB). Es una reducción de como mínimo el 50% del tiempo de carga, si no de un 66% en algunas ocasiones. | ||
- | Para eso, guardamos la pantalla comprimida, y en lugar de cargar la pantalla con **LOAD "" | + | Para eso, guardamos la pantalla comprimida, y en lugar de cargar la pantalla con '' |
Una vez descomprimida la pantalla, podemos sobreescribir todo el buffer de datos comprimidos con nuestro código del programa. | Una vez descomprimida la pantalla, podemos sobreescribir todo el buffer de datos comprimidos con nuestro código del programa. | ||
Línea 296: | Línea 317: | ||
<code z80> | <code z80> | ||
;;; Bloque codigo menu.asm | ;;; Bloque codigo menu.asm | ||
- | ORG 33500 | + | |
; código del menu aqui | ; código del menu aqui | ||
Línea 304: | Línea 325: | ||
</ | </ | ||
- | El BIN generado al ensamblar este programa lo podremos cargar con nuestro cargador (asumiendo que no ocupe más de 5KB) entre 27000 y 32000, y desempaquetarlo en 40000, para que todo el código y datos del menú desempaquetados acaben en la posición de memoria esperada (donde estarían con un LOAD "" | + | El BIN generado al ensamblar este programa lo podremos cargar con nuestro cargador (asumiendo que no ocupe más de 5KB) entre 27000 y 32000, y desempaquetarlo en 40000, para que todo el código y datos del menú desempaquetados acaben en la posición de memoria esperada (donde estarían con un '' |
Así, nuestro programa " | Así, nuestro programa " | ||
Línea 330: | Línea 351: | ||
==== Dirección inicial de carga del código máquina ==== | ==== Dirección inicial de carga del código máquina ==== | ||
- | Como acabamos de ver, si nuestro programa principal está realizado en BASIC y lo que vamos a hacer es llamar a rutinas en código máquina desde él, estas rutinas deberían ser cargadas mucho más allá de la dirección 26000, ya que tiene que caber no sólo un simple cargador BASIC, sino todo nuestro programa. En este caso, lo normal sería cargar las rutinas en la parte alta de la memoria (por ejemplo, según cómo de extenso sea el programa en BASIC, en **50000**, con su **CLEAR 49999** previo), ya que la mayor ocupación de memoria en este caso será seguro por parte del programa BASIC y no de las rutinas en código máquina (para la cual hemos dejado unos 15KB empezando en 50000 y acabando donde empiezan los UDG, en 65368). | + | Como acabamos de ver, si nuestro programa principal está realizado en BASIC y lo que vamos a hacer es llamar a rutinas en código máquina desde él, estas rutinas deberían ser cargadas mucho más allá de la dirección 26000, ya que tiene que caber no sólo un simple cargador BASIC, sino todo nuestro programa. En este caso, lo normal sería cargar las rutinas en la parte alta de la memoria (por ejemplo, según cómo de extenso sea el programa en BASIC, en **50000**, con su '' |
\\ | \\ | ||
Línea 347: | Línea 368: | ||
**RANDOMIZE USR dirección** | **RANDOMIZE USR dirección** | ||
- | En esta opción, se ejecuta el **USR dirección**, lo que produce un salto a la rutina en dicha dirección. Cuando esta acaba, el valor del registro BC se devuelve a BASIC y se asigna como parámetro a RANDOMIZE. | + | En esta opción, se ejecuta el '' |
Esto produce que el valor devuelto por USR se use como semilla para números aleatorios. Es una forma de ejecutar código máquina sin sacar nada por pantalla y con una disrupción mínima para BASIC (sólo cambiamos la semilla de los números aleatorios). Es la forma normal de llamar a código máquina desde cargadores BASIC cuando no pretendemos continuar la ejecución en BASIC ni volver a él. | Esto produce que el valor devuelto por USR se use como semilla para números aleatorios. Es una forma de ejecutar código máquina sin sacar nada por pantalla y con una disrupción mínima para BASIC (sólo cambiamos la semilla de los números aleatorios). Es la forma normal de llamar a código máquina desde cargadores BASIC cuando no pretendemos continuar la ejecución en BASIC ni volver a él. | ||
- | Pero si estamos haciendo un programa en BASIC con llamadas a rutinas código máquina, y la parte BASIC usa llamadas a **RND** para obtener números aleatorios, no querremos que se estropee el valor de la semilla de números aleatorios (porque será establecida con el valor que dejemos en BC) en cada llamada. En ese caso no debemos utilizar RANDOMIZE USR, o, si lo hacemos, nos puede interesar finalizar nuestras rutinas en código máquina con: | + | Pero si estamos haciendo un programa en BASIC con llamadas a rutinas código máquina, y la parte BASIC usa llamadas a '' |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
</ | </ | ||
- | El valor de **23760** (**$5C76**) es el actual valor de **SEED**, la variable del sistema que contiene la semilla por defecto que tuvieramos antes de la llamada, con lo si lo asignamos a BC antes de retornar, el comando RANDOMIZE volvería a establecer dicho valor y así lo preservaríamos al volver a BASIC. | + | El valor de **23760** (**$5c76**) es el actual valor de '' |
\\ | \\ | ||
**LET V=USR dirección** | **LET V=USR dirección** | ||
- | Finalmente tenemos LET USR. Si no queremos que aparezca nada por pantalla (PRINT USR) ni tampoco alterar el valor de la semilla de números aleatorios, ni tampoco preservarla al final de cada rutina (RANDOMIZE USR), podemos optar por usar LET con una variable desechable para poder llamar a USR y desechar el valor devuelvo por la rutina en BC. Este valor se asignaría a la variable que hayamos escogido en BASIC (y que se recomienda que sea de una sóla letra ya que es más rápido en el BASIC del Spectrum) y simplemente podemos ignorar su valor. | + | Finalmente tenemos |
\\ | \\ | ||
Línea 369: | Línea 390: | ||
Otra restricción importante sobre nuestro código máquina si lo vamos a llamar desde BASIC es que no debemos de utilizar los registros del Z80 llamados **IX** e **IY** en funciones que vayamos a llamar desde BASIC. | Otra restricción importante sobre nuestro código máquina si lo vamos a llamar desde BASIC es que no debemos de utilizar los registros del Z80 llamados **IX** e **IY** en funciones que vayamos a llamar desde BASIC. | ||
+ | |||
+ | De hecho, no deberíamos utilizar **IY** ni siquiera desde ensamblador puro si estamos usando el modo de interrupción im1. | ||
+ | |||
+ | Como ya comentamos, la rutina de la ROM **$0038**, llamada en im1, utiliza el registro IY para actualizar el tercer byte de la variable '' | ||
\\ | \\ | ||
==== Uso del registro I ==== | ==== Uso del registro I ==== | ||
- | Tampoco se debe cargar el registro " | + | Tampoco se debe cargar el registro " |
Línea 382: | Línea 407: | ||
\\ | \\ | ||
- | 1.- En el cargador del juego, una vez cargado todo el código del mismo (lo que sería la versión completa 48K), con las optimizaciones de carga que se deseen (compresión, | + | 1.- En el cargador del juego, una vez cargado todo el código del mismo (lo que sería la versión completa 48K), con las optimizaciones de carga que se deseen (compresión, |
- | 2.- En el inicio de nuestro juego, utilizamos la rutina de detección del modelo 128K mediante comprobación de la paginación, | + | 2.- En el inicio de nuestro juego, utilizamos la rutina de detección del modelo 128K mediante comprobación de la paginación, |
3.- Si el modelo es 48K, simplemente lanzamos el juego. | 3.- Si el modelo es 48K, simplemente lanzamos el juego. | ||
Línea 398: | Línea 423: | ||
1.- Antes de mostrar el menú del juego 48K, si estamos en modo 128K (consultando la variable que estableció la rutina detectora de modelo) podemos utilizar los gráficos extra cargados en otro banco para mostrar la introducción (que sólo se verá al arrancar el juego). Conmutamos al banco y pintamos las pantallas, textos, animaciones, | 1.- Antes de mostrar el menú del juego 48K, si estamos en modo 128K (consultando la variable que estableció la rutina detectora de modelo) podemos utilizar los gráficos extra cargados en otro banco para mostrar la introducción (que sólo se verá al arrancar el juego). Conmutamos al banco y pintamos las pantallas, textos, animaciones, | ||
- | 2.- En la rutina ISR IM2 del juego, si es un modo 128K, conmutamos al banco de la música y llamamos al player. A este player le pediremos lo que sea necesario (parar, arrancar música, cambiar de melodía, etc) según lo que indique alguna variable de control que tengamos en memoria para que el IM2 sepa qué tiene que hacer con la música en cada momento. Esta variable la controlaremos desde el juego en sí como veremos en el paso 5. | + | 2.- En la rutina ISR im2 del juego, si es un modo 128K, conmutamos al banco de la música y llamamos al player. A este player le pediremos lo que sea necesario (parar, arrancar música, cambiar de melodía, etc) según lo que indique alguna variable de control que tengamos en memoria para que el im2 sepa qué tiene que hacer con la música en cada momento. Esta variable la controlaremos desde el juego en sí como veremos en el paso 5. |
3.- Una vez se ha llamado a la rutina del player y hemos vuelto de ella (todavía en la ISR), si estamos en modo 128K ya se puede volver a conmutar al banco de memoria original, para que la ISR siga ejecutándose como en los modelos de 48K. | 3.- Una vez se ha llamado a la rutina del player y hemos vuelto de ella (todavía en la ISR), si estamos en modo 128K ya se puede volver a conmutar al banco de memoria original, para que la ISR siga ejecutándose como en los modelos de 48K. | ||
Línea 408: | Línea 433: | ||
Con esto el mismo juego 48K podrá tener música en modo 128K con unos pequeños añadidos muy sencillos, y ser compatible con ambos modelos. | Con esto el mismo juego 48K podrá tener música en modo 128K con unos pequeños añadidos muy sencillos, y ser compatible con ambos modelos. | ||
+ | |||
+ | \\ | ||
+ | ===== Obtener el valor de PC (Contador de Programa) ===== | ||
+ | |||
+ | Utilizando '' | ||
+ | |||
+ | <code z80> | ||
+ | call NextInstr | ||
+ | NextInstr: | ||
+ | pop hl ; HL contiene ahora PC | ||
+ | </ | ||
+ | |||
+ | Al ejecutarse '' | ||
+ | |||
+ | ¿Para qué podría valer algo así? Nuestro compañero **< | ||
+ | |||
+ | <code z80> | ||
+ | Imprimir_Mensajes: | ||
+ | call MyPrintString | ||
+ | DB " | ||
+ | call MyPrintString | ||
+ | DB " | ||
+ | call MyPrintString | ||
+ | DB " | ||
+ | (...) | ||
+ | |||
+ | MyPrintString: | ||
+ | pop hl ; obtenemos la direccion de la cadena (PC) | ||
+ | mpr_loop: | ||
+ | ld a, (hl) | ||
+ | cp $ff | ||
+ | Jr z, mprexit | ||
+ | rst $10 | ||
+ | inc hl | ||
+ | jr mpr_loop | ||
+ | mpr_exit: | ||
+ | push hl ; introducimos direccion de retorno | ||
+ | ret ; PC continuará después de la cadena impresa | ||
+ | </ | ||
+ | |||
+ | La llamada a '' | ||
+ | |||
+ | La rutina extrae ese valor de la pila y lo usa como " | ||
+ | |||
+ | Esto nos evitaría lo siguiente, que suponen 3 bytes por cada instrucción '' | ||
+ | |||
+ | <code z80> | ||
+ | ld hl, cadena1 | ||
+ | call MyPrintString | ||
+ | ld hl, cadena2 | ||
+ | call MyPrintString | ||
+ | ld hl, cadena3 | ||
+ | call MyPrintString | ||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | **[ [[.: |