cursos:ensamblador:avanzadas1

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:avanzadas1 [13-01-2024 11:11] – [Valor mínimo para ORG] sromerocursos:ensamblador:avanzadas1 [21-01-2024 10:31] (actual) – [Obtener el valor de PC (Contador de Programa)] sromero
Línea 54: Línea 54:
 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 ''ORG 32768''? ¿Por qué se ha recomendado un valor mínimo de ''ORG 33500''?
  
-Con ''ORG 32768'' hay un problema, y es la pila (stack) del Z80. La pila crece habia abajo (decrece), y con ''ORG 32768'', **BASIC al hacer el CLEAR situará la pila POR DEBAJO de 32768**, con lo cual **la pila estará en zona de contended memory**.+Con ''ORG 32768'' hay un problema, y es la pila (stack) del Z80. La pila crece habia abajo (decrece), y con ''ORG 32768'', **BASIC al hacer el CLEAR situará la pila Por deBAJO de 32768**, con lo cual **la pila estará en zona de contended memory**.
  
-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 'estas 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 ''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 'estas son operaciones que usaremos mucho en nuestros programas.
  
 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 64: Línea 64:
     ORG 33500     ORG 33500
  
-    CALL ROM_CLS+    call ROM_CLS
  
-    LD HL, 0 +    ld hl, 0 
-    ADD HLSP +    add hlsp 
-    LD BH +    ld bh 
-    LD CL +    ld cl 
-    CALL ROM_STACK_BC +    call ROM_STACK_BC 
-    CALL ROM_PRINT_FP+    call ROM_PRINT_FP
  
-    RET+    ret
  
-ROM_CLS       EQU  $0DAF +ROM_CLS       EQU  $0daf 
-ROM_STACK_BC  EQU  $2D2B +ROM_STACK_BC  EQU  $2d2b 
-ROM_PRINT_FP  EQU  $2DE3+ROM_PRINT_FP  EQU  $2de3
  
     END 33500     END 33500
Línea 101: Línea 101:
 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'es caerían dentro de la contended memory, algo que tampoco es tan grave, y que probablemente ni siquiera tuvieron en cuenta muchos juegos comerciales de la época del Spectrum que usaban alegremente esta zona de memoria. 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'es caerían dentro de la contended memory, algo que tampoco es tan grave, y que probablemente ni siquiera tuvieron en cuenta muchos juegos comerciales de la época del Spectrum que usaban alegremente esta zona de memoria.
  
-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, las cuales realizan muchos CALLs anidados y no realizan RETs hasta que sus llamadas a ellas mismas terminan (pero que, si están bien programadas, acaban vaciando la pila igualmente).+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, las cuales realizan muchos calls anidados y no realizan RETs hasta que sus llamadas a ellas mismas terminan (pero que, si están bien programadas, acaban vaciando la pila igualmente).
  
 Así pues, nuestra recomendación de valor de inicio **ORG** mínimo para cualquier programa sería de __**cualquiera de esos 4 valores**__, ya sea **35000** si nos gusta ver un número redondo en el ORG, **34000** si queremos arañar un KB extra, o **33500** si estamos seguros de que tenemos pila suficiente con 708 bytes fuera de la contended memory. Así pues, nuestra recomendación de valor de inicio **ORG** mínimo para cualquier programa sería de __**cualquiera de esos 4 valores**__, ya sea **35000** si nos gusta ver un número redondo en el ORG, **34000** si queremos arañar un KB extra, o **33500** si estamos seguros de que tenemos pila suficiente con 708 bytes fuera de la contended memory.
  
 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 "desperdiciados" que tenemos entre 25000 y 32768, pero que están en contended memory. 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 "desperdiciados" que tenemos entre 25000 y 32768, pero que están en contended memory.
- 
- 
  
 \\  \\ 
Línea 137: Línea 135:
 \\  \\ 
  
-Para poner uno de los bloques disponibles en la "ventana" entre 49152 ($C000) y 65535 ($FFFF), tendremos que perder de vista el bloque que tenemos "mapeado" actualmente.+Para poner uno de los bloques disponibles en la "ventana" entre 49152 ($c000) y 65535 ($ffff), tendremos que perder de vista el bloque que tenemos "mapeado" actualmente.
  
 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 "perder de vista", de modo que el código ejecutable del programa tiene que estar en la memoria entre 25000 y 49152.+  * 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 "perder de vista", de modo que el código ejecutable del programa tiene que estar en la memoria entre 25000 y 49152.
  
   * 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 ''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.
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 (''pila_original DEFW 0'') y restaurar el valor de SP antes de hacer un ''RET'' de salida del menú.
  
 Lo siguiente que hacemos es llamar a todas las rutinas de inicialización de uso único que tenga el programa, alojadas en este "BIN" cargado en 26000 y que sólo llamaremos una vez o sólo llamaremos durante el menú. Lo siguiente que hacemos es llamar a todas las rutinas de inicialización de uso único que tenga el programa, alojadas en este "BIN" cargado en 26000 y que sólo llamaremos una vez o sólo llamaremos durante el menú.
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 BINel del juego, se define en 33500 y contiene todo el código del juego, unos 32KB de código entero para el juego, del que hemos rescatado hasta 7KB al sacarlos al menú.+El BIN generado por el primer ASM se utiliza para generar el código que se cargará en 26000:
  
 <code z80> <code z80>
 ;;; Fichero menu.asm => genera menu.bin ;;; Fichero menu.asm => genera menu.bin
-ORG 26000+    ORG 26000
  
-    ; Ponemos la pila colgando del programa principal, +    ; Ponemos la pila colgando del programa principal, y fuera 
-    ; y fuera de la contended memory. +    ; de la contended memory. Si necesitaramos volver a BASIC 
-    LD SP, 33500+    ; habria que hacer una copia de SP en una variable para 
 +    ; recuperarla antes del RET final del programa. 
 +    ld sp, 33500
  
-    CALL detectar_modelo_128K     ; rutina en este ASM / bloque +    call Detectar_Modelo_128K     ; rutina en este ASM / bloque 
-    CALL inicializar_algo         ; rutina en este ASM / bloque+    call Inicializar_Algo         ; rutina en este ASM / bloque
  
-menu:+Menu:
     ; codigo del menu aqui.     ; codigo del menu aqui.
     ; ... en algun punto..     ; ... en algun punto..
-    CALL iniciar_partida +    call Iniciar_Partida 
-    JP menu+    jp Menu
  
 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 +    END 26000 
-</code z80>+</code> 
 + 
 +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 "reducido" hasta 7KB al sacarlos al menú que está alojado a partir de 26000:
  
 <code z80> <code z80>
Línea 210: Línea 212:
     ORG 33500     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 
-    RET+    ret
  
     END 33500     END 33500
-</code z80>+</code>
  
 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 264: Línea 266:
 El segundo mecanismo de ahorro de tiempo de carga si usamos gráficos con máscaras, es "autogenerar" las máscaras a partir de los gráficos del sprite, algo que se puede realizar vía código si son convexos. El segundo mecanismo de ahorro de tiempo de carga si usamos gráficos con máscaras, es "autogenerar" las máscaras a partir de los gráficos del sprite, algo que se puede realizar vía código si son convexos.
  
 +\\ 
 +==== 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 "Buffer de Impresión". Es un buffer temporal usado por la impresora del Spectrum cuando está imprimiendo, pero que no se utiliza en caso contrario.
 +
 +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 ''PUSH'' son 2 bytes), pero que puede causar problemas en los modelos de 128K, por lo que no es recomendable cambiar la pila a esta ubicación.
 +
 +Aún así, podemos utilizar esa zona como "pila" o incluso como "zona de variables" de nuestro programa y así ahorrar hasta 256 bytes de RAM, teniendo en cuenta 2 cosas:
 +
 +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 355: Línea 375:
  
 <code z80> <code z80>
-    LD BC, (23670) +    ld bc, (23670) 
-    RET+    ret
 </code> </code>
  
-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 ''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.
  
 \\  \\ 
Línea 371: Línea 391:
 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.+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 ''FRAMES'' con un **INC (IY+$40)** por lo que si modificamos IY en nuestros programas y se produce una interrupción, modificaremos una zona de la memoria que no es la esperada. Si necesitamos usar IY, es mejor desactivar las interrupciones antes de hacerlo, y volver a activarlas después.+Como ya comentamos, la rutina de la ROM **$0038**, llamada en im1, utiliza el registro IY para actualizar el tercer byte de la variable ''FRAMES'' con un **INC (IY+$40)** por lo que si modificamos IY en nuestros programas y se produce una interrupción, modificaremos una zona de la memoria que no es la esperada. Si necesitamos usar IY, es mejor desactivar las interrupciones antes de hacerlo, y volver a activarlas después.
  
 \\  \\ 
 ==== Uso del registro I ==== ==== Uso del registro I ====
  
-Tampoco se debe cargar el registro "I" con valores entre 40h o 7Fh (aunque no se vaya a usar IM2). Sólo se deben redirigir las interrupciones IM2 entre 8000h y BFFFh. Más adelante hablaremos de lo que son los modos de interrupción.+Tampoco se debe cargar el registro "I" con valores entre 40h o 7Fh (aunque no se vaya a usar im2). Sólo se deben redirigir las interrupciones im2 entre 8000h y BFFFh. Más adelante hablaremos de lo que son los modos de interrupción.
  
  
Línea 403: 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, etc. Al acabar la intro, volvemos al banco original. 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, etc. Al acabar la intro, volvemos al banco original.
  
-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 412: Línea 432:
  
 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.
 +
 +
 +\\ 
 +\\ 
 +===== Código Automodificable =====
 +
 +Ya vimos en un capítulo anterior un ejemplo de **código automodificable**. Consiste en sobreescribir en memoria un opcode o un dato en función de una condición, para que cuando lleguemos a ese punto del programa, se ejecute el opcode modificado.
 +
 +En el ejemplo que ya vimos, utilizamos código automodificable para sobreescribir un instrucción de carga "futura" que nos permitirá recuperar el valor del registro BC más adelante, por ejemplo porque vamos a modificarlo con un bucle, y no tenemos la pila disponible:
 +
 +<code z80>
 +    ld (save_bc+1), bc    ; Escribimos BC en la parte NN NN del
 +                          ; opcode "ld bc, NN NN" en memoria
 +
 +    ... hacer cosas con BC, perdiendo su valor ...
 +
 +save_bc:
 +    ld bc, $0000          ; En el LD anterior cambiamos $000
 +                          ; por el valor de BC, así que cuando
 +                          ; el z80 llegue aquí no es ya ld bc, 0
 +                          ; sino ld bc, valor_que_tenia_BC
 +                          ; así que recuperaremos BC aquí.
 +</code>
 +
 +Cuando se ejecuta la instrucción ''ld (save_bc+1), bc'', estamos sobreescribiendo nuestro propio programa, cambiando el opcode que estaba ensamblado en memoria (''ld bc, $0000'', es decir "**$01 $00 $00**") por **$01 XX XX**, siendo **XX XX** el nuevo valor que queremos introducir en el opcode.
 +
 +Cuando se ejecuta el ''ld (save_bc+1), bc'', estamos escribiendo el valor de BC en ese momento encima de "XX XX" de forma que cuando la ejecución llegue a ese punto, el comando ''ld bc, XX'' se ejecutará y recuperará en BC el valor que tenía BC cuando se almacenó en esa posición de memoria.
 +
 +La escritura en memoria (''ld (nn), bc'' = 20 ciclos de reloj) y su posterior asignación (10 ciclos) cuesta un total de 30 ciclos de reloj, mientras que si hubiéramos usado PUSH/POP costaría sólo 21 ciclos en total (11 el ''PUSH'' y 10 el ''POP''). Sin embargo, si estamos en una rutina de impresión de Sprites donde usamos la pila para recuperar datos del Sprite, por ejemplo, es una forma de recuperar valores de registros en una situación donde no podemos hacer ''PUSH'' y ''POP'' para ello.
 +
 +Y no sólo podemos modificar datos, también podríamos modificar opcodes, cambiando ''JR Z''s por ''JR NZ''s, por ejemplo, para cambiar la condicionalidad de los saltos de alguna forma.
 +
 +\\ 
 +===== Obtener el valor de PC (Contador de Programa) =====
 +
 +Utilizando ''CALL'' y la pila del Z80, podemos obtener en un registro el valor del Contador de Programa o ''PC'':
 +
 +<code z80>
 +    call NextInstr       ; CALL mete PC en la pila
 +NextInstr:
 +    pop hl               ; HL contiene ahora PC
 +</code>
 +
 +Al ejecutarse ''call NextInstr'', el Z80 mete en la pila la dirección de retorno para el ''CALL'', en este caso, justo la dirección en la que está el contador de programa. Una vez hecho eso, no hacemos un ''RET'' para volver sino que hacemos ''POP HL'' para extraer PC de la pila sin volver al punto de llamada. El flujo de ejecución continuaría tras el ''pop hl'', con el valor de PC introducido en HL.
 +
 +¿Para qué podría valer algo así? Nuestro compañero **<nowiki>Xor_A [Fer]</nowiki>** nos proporciona un ejemplo con el cual podemos utilizar el valor de PC para imprimir cadenas sin tener que cargar en HL cada vez la posición de la cadena, intercalando el código y los datos:
 +
 +<code z80>
 +Imprimir_Mensajes:
 +    call MyPrintString
 +    DB "cadena1", $ff
 +    call MyPrintString
 +    DB "cadena2", $ff
 +    call MyPrintString
 +    DB "cadena3", $ff
 +    (...)
 +
 +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
 +</code>
 +
 +La llamada a ''MyPrintString'' mete en la pila la dirección de PC, que es la dirección del primer carácter de la cadena.
 +
 +La rutina extrae ese valor de la pila y lo usa como "puntero a la cadena" en HL. Incrementa HL tras imprimir cada carácter, y al acabar de imprimir la cadena, mete en la pila el valor de HL para que el ''RET'' vuelva al flujo principal del programa, pero justo después de los datos de la cadena, es decir, al siguiente ''call MyPrintString''.
 +
 +Esto nos evitaría lo siguiente, que suponen 3 bytes por cada instrucción ''ld hl, NNNN'':
 +
 +<code z80>
 +    ld hl, cadena1
 +    call MyPrintString
 +    ld hl, cadena2
 +    call MyPrintString
 +    ld hl, cadena3
 +    call MyPrintString
 +</code>
  
 \\  \\ 
-**[ [[.:indice|⬉]] | [[.:compresion_rle|⬅]] | [[.:avanzadas2|➡]] ]**+**[ [[.:indice|⬉]] | [[.:compresion_rle|⬅]] | [[.:avanzadas2|➡]] ]**
  • cursos/ensamblador/avanzadas1.1705144295.txt.gz
  • Última modificación: 13-01-2024 11:11
  • por sromero