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 | ||
cursos:ensamblador:esqueleto_programa [08-01-2024 04:43] – [Librería reutilizable para los ejemplos] sromero | cursos:ensamblador:esqueleto_programa [19-01-2024 11:32] (actual) – sromero | ||
---|---|---|---|
Línea 8: | Línea 8: | ||
Para ello, necesitamos saber lo siguiente: | Para ello, necesitamos saber lo siguiente: | ||
- | - Tener el esqueleto de un programa mínimo, vacío, que no haga nada, al cual podamos añadirle instrucciones para hacer nuestras pruebas. Podremos utilizar un esqueleto de programa similar para cada prueba que queramos realizar. | + | - Tener el esqueleto de un programa mínimo, que no haga nada, al cual podamos añadirle instrucciones para hacer nuestras pruebas. Podremos utilizar un esqueleto de programa similar para cada prueba que queramos realizar. |
- Saber cómo ensamblar ese programa para obtener un fichero TAP que podamos probar en un emulador. | - Saber cómo ensamblar ese programa para obtener un fichero TAP que podamos probar en un emulador. | ||
- Tener alguna forma de imprimir por pantalla para tener un feedback visual en las pruebas que hagamos. | - Tener alguna forma de imprimir por pantalla para tener un feedback visual en las pruebas que hagamos. | ||
- | Vamos a empezar por el " | + | Vamos a empezar por el " |
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | | + | |
+ | | ||
; código que queremos probar aqui | ; código que queremos probar aqui | ||
- | | + | |
- | END 35000 | + | |
+ | | ||
</ | </ | ||
- | A continuación, | + | A continuación, |
- | Al ejecutarlo, como lo único que tenemos es un "RET", se devolverá el control al BASIC y todo lo que podremos ver en pantalla es un "**0 OK, 40:1**" (o similar) en la parte inferior. | + | Nótese que las directivas '' |
+ | |||
+ | Al ejecutarlo, como lo único que tenemos es un '' | ||
\\ | \\ | ||
Línea 31: | Línea 35: | ||
\\ | \\ | ||
- | Ya está, tenemos así todo lo necesario para hacer cualquier prueba en ensamblador que queramos, introduciendo el código que deseemos entre el ORG y el RET. El RET devolverá el control al BASIC una vez haya terminado la ejecución de nuestro programa. | + | Ya está, tenemos así todo lo necesario para hacer cualquier prueba en ensamblador que queramos, introduciendo el código que deseemos entre el '' |
Por otra parte, en ocasiones, en nuestras pruebas, nos vendría bien tener una forma de tener información visual sobre la ejecución del código. | Por otra parte, en ocasiones, en nuestras pruebas, nos vendría bien tener una forma de tener información visual sobre la ejecución del código. | ||
- | Por ejemplo, supongamos que estamos en el capítulo sobre los flags y el comando | + | Por ejemplo, supongamos que estamos en el capítulo sobre los flags y el comando |
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
; código que queremos probar, como | ; código que queremos probar, como | ||
- | ; por ejemplo, pruebas con CP y JR: | + | ; por ejemplo, pruebas con CP y jr: |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
es_igual: | es_igual: | ||
- | ; A = B | + | ; A == B |
- | | + | |
es_diferente: | es_diferente: | ||
Línea 55: | Línea 60: | ||
fin: | fin: | ||
- | | + | |
- | END 35000 | + | |
+ | | ||
</ | </ | ||
\\ | \\ | ||
- | Utilizando un emulador que tenga un " | + | Utilizando un emulador que tenga un "**debugger**" o "**depurador**" de Z80 podríamos ejecutar el código paso a paso para ver si se produce el salto o no, pero es posible que en estos momentos iniciales de aprendizaje nos resulte complicado hacer esto. |
- | Lo que podemos hacer, de una manera muy sencilla, es utilizar referencias visuales, como por ejemplo **llamar a las rutinas de la ROM que permiten imprimir en pantalla**, como **RST 16** (o **RST $10**). | + | Lo que podemos hacer, de una manera muy sencilla, es utilizar referencias visuales, como por ejemplo **llamar a las rutinas de la ROM que permiten imprimir en pantalla**, como '' |
- | Veámos cómo funciona | + | Veámos cómo funciona |
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
+ | |||
+ | call $0daf ; CLS (borrar pantalla) | ||
- | | + | |
+ | rst 16 ; Imprimir A en la posicion del cursor | ||
- | | + | |
- | RST 16 ; Imprimir A en la posicion del cursor | + | |
- | | + | END 35000 |
- | END 35000 | + | |
</ | </ | ||
En el listado hay 3 líneas nuevas añadidas a nuestro " | En el listado hay 3 líneas nuevas añadidas a nuestro " | ||
- | La primera (**CALL | + | La primera ('' |
- | Las otras 2 (**LD A, ' | + | Las otras 2 ('' |
El resultado de ejecutar el programa de arriba sería: | El resultado de ejecutar el programa de arriba sería: | ||
Línea 90: | Línea 97: | ||
\\ | \\ | ||
- | Podemos imprimir no sólo este carácter, sino todos cuantos deseemos, con sucesivas llamadas a RST 16: | + | Podemos imprimir no sólo este carácter, sino todos cuantos deseemos, con sucesivas llamadas a '' |
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | END 35000 | + | |
+ | | ||
</ | </ | ||
Línea 116: | Línea 124: | ||
\\ | \\ | ||
- | Algo muy importante al respecto de RST 16 es que por defecto, imprime en el "CANAL 1", que son las 2 líneas de la parte inferior de la pantalla del Spectrum (donde aparecen los mensajes). Si queremos imprimir en la parte superior de la pantalla, tenemos que " | + | Algo muy importante al respecto de '' |
- | Si queremos imprimir texto, pero no borrar la pantalla, al quitar el **CALL | + | Si queremos imprimir texto, pero no borrar la pantalla, al quitar el '' |
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | En cualquier caso, lo habitual es borrar la pantalla con CLS por lo que se abrirá el canal 2 automáticamente al hacerlo y no necesitaremos hacerlo con la llamada anterior. | + | En cualquier caso, lo habitual es borrar la pantalla con '' |
- | Por otra parte, con RST 16 podremos también saltar el cursor a la siguiente línea con el carácter 13: | + | Por otra parte, con '' |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
</ | </ | ||
\\ | \\ | ||
\\ | \\ | ||
- | **Aprovechando | + | **Aprovechando |
Con esta nueva funcionalidad ya podemos poner algo de feedback visual en nuestras pruebas. Volvamos a nuestro ejemplo de comparación, | Con esta nueva funcionalidad ya podemos poner algo de feedback visual en nuestras pruebas. Volvamos a nuestro ejemplo de comparación, | ||
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
es_igual: | es_igual: | ||
; A = B | ; A = B | ||
- | | + | |
- | | + | |
- | | + | |
es_diferente: | es_diferente: | ||
; A != B | ; A != B | ||
- | | + | |
- | | + | |
fin: | fin: | ||
- | | + | |
- | END 35000 | + | |
+ | | ||
</ | </ | ||
Línea 173: | Línea 182: | ||
==== Usar constantes para las rutinas de la ROM ==== | ==== Usar constantes para las rutinas de la ROM ==== | ||
- | Si, como a mí, no te gusta ver en el código " | + | Si por cuestiones de legibilidad |
Este código es menos legible: | Este código es menos legible: | ||
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | CALL $0DAF ; Rutina CLS de la ROM | + | |
- | | + | |
- | RST 16 | + | |
- | | + | |
- | END 35000 | + | rst 16 |
+ | |||
+ | ret | ||
+ | |||
+ | | ||
</ | </ | ||
Línea 191: | Línea 202: | ||
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | | + | |
+ | | ||
+ | |||
+ | ld a, " | ||
+ | rst 16 | ||
- | | + | |
- | RST 16 | + | |
- | RET | + | ROM_CLS EQU $0daf |
- | END 35000 | + | |
- | ROM_CLS EQU $0DAF | + | END 35000 |
</ | </ | ||
- | Recomendamos encarecidamente definir | + | Recomendamos encarecidamente definir |
- | De esa forma, cuando revisites tu código más adelante sabrás exáctamente qué hace ese CALL sin la necesidad de haber dejado un comentario. | + | De esa forma, cuando revisites tu código más adelante sabrás exáctamente qué hace ese call sin la necesidad de haber dejado un comentario. |
\\ | \\ | ||
Línea 212: | Línea 225: | ||
Otra opción muy sencilla para tener feedback visual podría ser, en lugar de imprimir caracteres por pantalla, cambiar el color del borde utilizando por ejemplo la rutina de la ROM. | Otra opción muy sencilla para tener feedback visual podría ser, en lugar de imprimir caracteres por pantalla, cambiar el color del borde utilizando por ejemplo la rutina de la ROM. | ||
- | Se puede cambiar el borde cargando en el registro A un valor del 0 al 7 y llamando a la dirección | + | Se puede cambiar el borde cargando en el registro A un valor del 0 al 7 y llamando a la dirección |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
</ | </ | ||
Línea 243: | Línea 256: | ||
En otras ocasiones nos interesará saber qué valor tiene un registro o una posición de memoria, así que más que imprimir un carácter, nos puede interesar saber el valor numérico en sí. Para esto podemos aprovechar dos rutinas de la ROM que nos permitirán imprimir valores desde 0 hasta 65535. | En otras ocasiones nos interesará saber qué valor tiene un registro o una posición de memoria, así que más que imprimir un carácter, nos puede interesar saber el valor numérico en sí. Para esto podemos aprovechar dos rutinas de la ROM que nos permitirán imprimir valores desde 0 hasta 65535. | ||
- | Estas rutinas reciben el valor a imprimir en BC y se usan de la siguiente forma: | + | Estas rutinas reciben el valor a imprimir en el registro |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | La primera rutina (**$2D2B**) sirve para meter el valor de BC en la "pila de la calculadora" | + | La primera rutina (**$2d2b**) sirve para meter el valor de BC en la "pila de la calculadora" |
Así pues, podemos poner cualquier valor en BC, hacer esas 2 llamadas a la ROM, y lo veremos aparecer en pantalla en la posición del cursor: | Así pues, podemos poner cualquier valor en BC, hacer esas 2 llamadas a la ROM, y lo veremos aparecer en pantalla en la posición del cursor: | ||
<code z80> | <code z80> | ||
- | ORG 40000 | + | |
- | CALL ROM_CLS | + | |
- | | + | |
- | CALL ROM_STACK_BC | + | |
- | CALL ROM_PRINT_FP | + | |
- | | + | |
- | | + | |
+ | call ROM_PRINT_FP | ||
- | | + | |
- | ; No existe "LD BC, HL", asi que hacemos | + | |
- | | + | |
- | LD C, L ; por lo que => BC = HL | + | |
- | CALL ROM_STACK_BC | + | |
- | CALL ROM_PRINT_FP | + | |
- | | + | |
- | | + | ; No existe "ld bc, hl", asi que hacemos |
+ | | ||
+ | ld c, l ; por lo que => BC = HL | ||
+ | call ROM_STACK_BC | ||
+ | call ROM_PRINT_FP | ||
- | | + | |
- | CALL ROM_STACK_BC | + | rst 16 ; Retorno |
- | CALL ROM_PRINT_FP | + | |
- | | + | |
- | RST 16 ; Retorno | + | call ROM_STACK_BC |
+ | call ROM_PRINT_FP | ||
- | | + | |
- | CALL ROM_STACK_BC | + | rst 16 ; Retorno |
- | CALL ROM_PRINT_FP | + | |
- | | + | |
- | RST 16 ; Retorno | + | call ROM_STACK_BC |
+ | call ROM_PRINT_FP | ||
- | | + | |
- | | + | |
- | LD C, A | + | |
- | CALL ROM_STACK_BC | + | |
- | CALL ROM_PRINT_FP | + | |
- | | + | |
+ | ld b, 0 | ||
+ | ld c, a ; Imprimir el valor de A (B=0) | ||
+ | call ROM_STACK_BC | ||
+ | call ROM_PRINT_FP | ||
+ | |||
+ | ret | ||
variable | variable | ||
- | RAMTOP | + | RAMTOP |
- | ROM_CLS | + | ROM_CLS |
- | ROM_STACK_BC | + | ROM_STACK_BC |
- | ROM_PRINT_FP | + | ROM_PRINT_FP |
- | END 40000 | + | |
</ | </ | ||
Línea 314: | Línea 328: | ||
\\ | \\ | ||
- | Nótese cómo gracias a estas 2 rutinas podemos ver tanto el valor de cualquier registro de 16 bits (copiando su valor a BC), como de cualquier dirección de memoria (ya sea una variable de nuestro programa o el contenido de cualquier otra celdilla, como RAMTOP), o de un registro de 8 bits (haciendo 0 la parte alta de BC). | + | Nótese cómo gracias a estas 2 rutinas podemos ver tanto el valor de cualquier registro de 16 bits (copiando su valor a BC), como de cualquier dirección de memoria (ya sea una variable de nuestro programa o el contenido de cualquier otra celdilla, como '' |
- | En el ejemplo podemos ver que RAMTOP vale 39999, justo el valor que hace el CLEAR cuando lanzamos nuestro programa con ORG 40000. | + | En el ejemplo podemos ver que '' |
- | Ahora tenemos la capacidad de realizar cuantas pruebas queramos mediante nuestro " | + | Ahora tenemos la capacidad de realizar cuantas pruebas queramos mediante nuestro " |
- | + | ||
- | Otra cosa interesante que puede verse en el listado anterior es que en el juego de instrucciones del procesador Z80 no existe una instrucción **LD BC, HL**. Necesitamos poner en BC el valor a imprimir por pantalla, así que podemos hacerlo copiando la parte alta de HL en la parte alta de BC (**LD B, H**) y después la parte baja de HL en la parte baja de BC (**LD C, L**), que resulta en la copia exacta del contenido de HL en BC. Como veremos, en el microprocesador Z80 no podemos utilizar todas las instrucciones con todos los operandos, aunque hay pequeños trucos para saltarse estas limitaciones, | + | |
+ | Otra cosa interesante que puede verse en el listado anterior es que en el juego de instrucciones del procesador Z80 no existe una instrucción '' | ||
\\ | \\ | ||
Línea 330: | Línea 343: | ||
Veamos cómo podríamos crear una pequeña " | Veamos cómo podríamos crear una pequeña " | ||
- | Una " | + | Una " |
Las librerías permiten reutilizar código entre proyectos, y hacerlos más legibles, además de que cualquier mejora en el código de una librería permite que esa mejora llegue a todos los programa que estamos realizando al actualizar la librería en ellos. | Las librerías permiten reutilizar código entre proyectos, y hacerlos más legibles, además de que cualquier mejora en el código de una librería permite que esa mejora llegue a todos los programa que estamos realizando al actualizar la librería en ellos. | ||
- | Creemos un fichero | + | Creemos un fichero |
<code z80> | <code z80> | ||
+ | ;=== Libreria utils.asm: Funciones utiles varias para pruebas de === | ||
+ | ;=== lenguaje ensamblador Z80 en Spectrum === | ||
+ | |||
; | ; | ||
; PrintNum: Imprime en la pantalla un valor en decimal usando la ROM. | ; PrintNum: Imprime en la pantalla un valor en decimal usando la ROM. | ||
- | ; Entrada: BC = valor a imprimir por pantalla | + | ; |
+ | ; ENTRADA: BC = valor a imprimir por pantalla | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintNum: | PrintNum: | ||
- | | + | |
- | | + | push bc |
- | | + | push de |
+ | push hl | ||
+ | call $2d2b | ||
+ | | ||
+ | | ||
+ | pop de | ||
+ | pop bc | ||
+ | pop af | ||
+ | ret | ||
; | ; | ||
Línea 355: | Línea 381: | ||
; PrintNum4digits: | ; PrintNum4digits: | ||
; | ; | ||
- | CLS | + | CLS |
- | CLS_COLOR | + | CLS_COLOR |
- | BORDER | + | BORDER |
- | PAUSE | + | PAUSE |
- | MULT_HLDE | + | MULT_HLDE |
- | PIXEL_ADDR2 | + | PIXEL_ADDR2 |
- | PrintNum4digits EQU $1A1B ; Imprime valor de BC (0-9999) | + | PrintNum4digits EQU $1a1b |
; | ; | ||
- | ; PrintChar: Hacer desde nuestro programa "CALL PrintChar", | + | ; PrintChar: Hacer desde nuestro programa "call PrintChar", |
- | ; equivalente a hacer un "CALL $0010", | + | ; equivalente a hacer un "call $0010", |
- | ; Es un simple envoltorio de abreviatura | + | ; Es un simple envoltorio de abreviatura |
- | ; Entrada: A = caracter a imprimir por pantalla | + | ; |
+ | ; ENTRADA: A = caracter a imprimir por pantalla | ||
; | ; | ||
- | PrintChar | + | PrintChar: |
- | ; Podriamos llamar a $15E6 en su lugar | + | push af |
- | ; que es donde llama internamente $0010. | + | rst 16 |
+ | pop af | ||
+ | ret | ||
; | ; | ||
- | ; PrintSpace y PrintCR: | + | ; PrintSpace y PrintCR: para abreviar codigo, imprimen SPACE o ENTER. |
+ | ; | ||
+ | ; ENTRADA, SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintSpace: | PrintSpace: | ||
- | | + | |
- | | + | ld a, ' ' |
- | | + | |
+ | | ||
+ | ret | ||
PrintCR: | PrintCR: | ||
- | | + | |
- | | + | ld a, 13 |
- | | + | |
+ | | ||
+ | ret | ||
</ | </ | ||
Línea 390: | Línea 425: | ||
<code z80> | <code z80> | ||
- | ORG 33500 | + | |
- | | + | |
+ | | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
variable | variable | ||
- | ;; Incluimos nuestra libreria aqui | + | |
- | INCLUDE " | + | INCLUDE " |
- | END 33500 | + | |
</ | </ | ||
- | Es evidente que el programa se ha simplificado mucho, a costa de añadir saltos y retornos a rutinas externas, algo que no es un problema cuando estamos realizando pruebas y programas de aprendizaje. No tener gran cantidad de código en pantalla para tareas que son repetitivas (como imprimircadenas) nos facilitará ver el código que realmente nos interese, y evitará errores al programar ya que las llamadas a la funciones de la librería simplifican todo el proceso de desarrollo. | + | Es evidente que el programa se ha simplificado mucho, a costa de añadir saltos y retornos a rutinas externas, algo que no es un problema cuando estamos realizando pruebas y programas de aprendizaje. No tener gran cantidad de código en pantalla para tareas que son repetitivas (como imprimir cadenas) nos facilitará ver el código que realmente nos interese, y evitará errores al programar ya que las llamadas a la funciones de la librería simplifican todo el proceso de desarrollo. |
Con esta pequeña librería tal y como la acabamos de crear, disponemos de las siguientes funciones: | Con esta pequeña librería tal y como la acabamos de crear, disponemos de las siguientes funciones: | ||
Línea 422: | Línea 458: | ||
^ Rutina ^ Utilidad ^ | ^ Rutina ^ Utilidad ^ | ||
| **BORDER** | Permite cambiar el color del borde al valor indicado en A. | | | **BORDER** | Permite cambiar el color del borde al valor indicado en A. | | ||
- | | **CLS** | Limpia la pantalla. Para ello utiliza el valor del atributo que haya alojado en la posición de memoria | + | | **CLS** | Limpia la pantalla. Para ello utiliza el valor del atributo que haya alojado en la posición de memoria |
- | | **CLS_COLOR** | EQU (constante) que apunta a la dirección de la variable del sistema que permite establecer el color deseado al borrar la pantalla, en formato (COLOR_PAPEL*8)+COLOR_TINTA, | + | | **CLS_COLOR** | EQU (constante) que apunta a la dirección de la variable del sistema que permite establecer el color deseado al borrar la pantalla, en formato (COLOR_PAPEL*8)+COLOR_TINTA, |
- | | **PrintChar** | Imprime en pantalla, en la posicion actual del cursor, el caracter contenido en A. | | + | | **PrintChar** | Imprime en pantalla, en la posicion actual del cursor, el caracter contenido en A. Es el equivalente a '' |
- | | **PrintCR** | Realiza un salto de linea (imprime el caracter 13 o _CR con RST 16). | | + | | **PrintCR** | Realiza un salto de linea (imprime el caracter 13 o _CR con '' |
- | | **PrintSpace** | Imprime en pantalla, en la posicion actual del cursor, un espacio (con RST 16). | | + | | **PrintSpace** | Imprime en pantalla, en la posicion actual del cursor, un espacio (con '' |
| **PrintNum** | Imprime en pantalla, en la posicion actual del cursor, el valor decimal del contenido del registro BC, es decir, valores entre 0 y 65535. Utiliza para ello la pila de la calculadora BASIC, lo cual no es especialmente óptimo. | | | **PrintNum** | Imprime en pantalla, en la posicion actual del cursor, el valor decimal del contenido del registro BC, es decir, valores entre 0 y 65535. Utiliza para ello la pila de la calculadora BASIC, lo cual no es especialmente óptimo. | | ||
- | | **PrintNum4digits** | Imprime en pantalla, en la posicion actual del cursor, el valor decimal del contenido del registro BC, siempre que este valga entre 0 y 9999. Es la rutina que usa el sistema para imprimir el número de línea de BASIC (OUT_NUM_1), | + | | **PrintNum4digits** | Imprime en pantalla, en la posicion actual del cursor, el valor decimal del contenido del registro BC, siempre que este valga entre 0 y 9999. Es la rutina que usa el sistema para imprimir el número de línea de BASIC ('' |
| **PAUSE** | Pausar la ejecución del programa durante N segundos. El valor de N lo metemos en el registro BC multiplicado por 50, de forma que con BC = 500, por ejemplo, pausaremos durante 10 segundos. | | | **PAUSE** | Pausar la ejecución del programa durante N segundos. El valor de N lo metemos en el registro BC multiplicado por 50, de forma que con BC = 500, por ejemplo, pausaremos durante 10 segundos. | | ||
| **MULT_HLDE** | Calcula HL=HL*DE siempre que el resultado de la multiplicación no exceda de 65535. Incluso pese a esta limitación, | | **MULT_HLDE** | Calcula HL=HL*DE siempre que el resultado de la multiplicación no exceda de 65535. Incluso pese a esta limitación, | ||
Línea 441: | Línea 477: | ||
| **PrintHex** | Imprime en pantalla el valor del registro A en hexadecimal, | | **PrintHex** | Imprime en pantalla el valor del registro A en hexadecimal, | ||
| **PrintHex16** | Imprime en pantalla el valor del registro BC en hexadecimal, | | **PrintHex16** | Imprime en pantalla el valor del registro BC en hexadecimal, | ||
- | | **PrintString** | Imprime una cadena definida en memoria con (DEFB/DB) acabada en $FF como último byte (es el indicador de fin de cadena, para ellos se ha definido también una constante _EOS o -End Of String-). Como veremos en el ejemplo, la cadena soporta utilizar códigos de color, posicionamiento en coordenadas (y,x), flash, etc. | | + | | **PrintString** | Imprime una cadena definida en memoria con (por ejemplo con '' |
- | | **PrintNum2digits** | Imprime en la pantalla el valor de A, pero sólo los últimos 2 dígitos (0-99). Es ideal para imprimir valores como números de pantalla, vidas o tiempos, y además no usa la pila de la calculadora BASIC por lo que es mucho más rápida (además sólo imprimir 2 dígitos y del registro de 8 bits A). Para realizar la conversión de valor número a 2 ASCIIs a imprimir, utiliza una rutina auxiliar y después llama a RST 16 para imprimirlos. | | + | | **PrintNum2digits** | Imprime en la pantalla el valor de A, pero sólo los últimos 2 dígitos (0-99). Es ideal para imprimir valores como números de pantalla, vidas o tiempos, y además no usa la pila de la calculadora BASIC por lo que es mucho más rápida (además sólo imprimir 2 dígitos y del registro de 8 bits A). Para realizar la conversión de valor número a 2 ASCIIs a imprimir, utiliza una rutina auxiliar y después llama a rst 16 para imprimirlos. | |
| **Byte2ASCII_Dec2Digits** | Convierte el valor del registro H en una cadena de texto ASCII de max. 2 caracteres (0-99) decimales, y los devuelve en DE. | | | **Byte2ASCII_Dec2Digits** | Convierte el valor del registro H en una cadena de texto ASCII de max. 2 caracteres (0-99) decimales, y los devuelve en DE. | | ||
| **CursorAt** | Mueve el cursor a las coordenadas de pantalla X,Y especificadas en DE (D=X, E=Y). Es importante que X esté entre 0 y 31 e Y entre 0 y 21 que son los límites del canal 2 (parte superior de la pantalla). | | | **CursorAt** | Mueve el cursor a las coordenadas de pantalla X,Y especificadas en DE (D=X, E=Y). Es importante que X esté entre 0 y 31 e Y entre 0 y 21 que son los límites del canal 2 (parte superior de la pantalla). | | ||
| **Wait_For_Key** | Se queda esperando en un bucle hasta que se detecte una tecla pulsada, y devuelve su código ASCII en " | | **Wait_For_Key** | Se queda esperando en un bucle hasta que se detecte una tecla pulsada, y devuelve su código ASCII en " | ||
| **Wait_For_No_Key** | Se queda esperando en un bucle hasta que no haya ninguna tecla pulsada. A veces se utiliza antes de leer el teclado para asegurarse de que no nos saltamos una pausa porque el usuario tenía alguna tecla pulsada cuando la realizamos. | | | **Wait_For_No_Key** | Se queda esperando en un bucle hasta que no haya ninguna tecla pulsada. A veces se utiliza antes de leer el teclado para asegurarse de que no nos saltamos una pausa porque el usuario tenía alguna tecla pulsada cuando la realizamos. | | ||
- | | **PrintFlags** | Imprime en la posición actual del cursor el contenido del registro de FLAGS en binario. Recordemos que los Flags son: "S Z - H - P/V N C". Función utilísima para estudiar el comportamiento de las instrucciones y detectar en ocasiones problemas en saltos condicionales y comparaciones. | | + | | **PrintFlags** | Imprime en la posición actual del cursor el contenido del registro de Flags en binario. Recordemos que los Flags son: "S Z - H - P/V N C". Función utilísima para estudiar el comportamiento de las instrucciones y detectar en ocasiones problemas en saltos condicionales y comparaciones. | |
- | | **PrintFlag** | Imprime en la posición actual del cursor el contenido (0/1) del FLAG especificado en A. Se deben utilizar las constantes | + | | **PrintFlag** | Imprime en la posición actual del cursor el contenido (0/1) del Flag especificado en el registro |
- | | **PIXEL_ADDR2** | Punto interno de la rutina de la ROM PIXEL_ADDRESS. Esta rutina recibe en BC las coordenadas de un punto (B=Y en 0-192, C=X en 0-255) y devuelve en HL la dirección de memoria de la celdilla de la VideoRam que contiene en pixel, y en A el número de pixel donde está. | | + | | **PIXEL_ADDR2** | Punto interno de la rutina de la ROM '' |
| **_INK**, **_PAPER**, **_AT**, etc | Constantes (también colores como **_RED**, **_BLUE**, etc) para utilizar como códigos de control en las cadenas. | | | **_INK**, **_PAPER**, **_AT**, etc | Constantes (también colores como **_RED**, **_BLUE**, etc) para utilizar como códigos de control en las cadenas. | | ||
- | | **_EOS** | Constante para indicar fin de cadena ($FF). | | + | | **_EOS** | Constante para indicar fin de cadena ($ff). | |
| **_CR** | Constante para indicar fin de línea. | | | **_CR** | Constante para indicar fin de línea. | | ||
Línea 461: | Línea 497: | ||
; | ; | ||
; CursorAt: Mueve el cursor a la posicion indicada por DE (XY) | ; CursorAt: Mueve el cursor a la posicion indicada por DE (XY) | ||
+ | ; ENTRADA: DE = XY (D=X, E=Y) | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
CursorAt: | CursorAt: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
; PrintString: | ; PrintString: | ||
- | ; valor $FF usando | + | ; valor $ff usando |
- | ; Entrada: | + | ; |
- | ; El valor de DE no se preserva (se incrementa) | + | ; ENTRADA: DE = Dirección de la cadena a imprimir. |
+ | ; SALIDA: | ||
+ | ; MODIFICA: | ||
; | ; | ||
PrintString: | PrintString: | ||
- | | + | |
- | CP $FF ; chequeamos si es $FF (fin de cadena) | + | print_string_loop: |
- | | + | ld a, (de) ; Leemos el caracter apuntado por DE |
- | | + | CP _EOS ; chequeamos si es $ff (fin de cadena) |
- | | + | |
- | | + | |
+ | | ||
+ | | ||
+ | end_print_string: | ||
+ | pop af | ||
+ | ret | ||
; | ; | ||
- | ; PrintBin: Imprime en la pantalla un valor en binario usando | + | ; PrintBin: Imprime en la pantalla un valor en binario usando |
- | ; | + | ; Añade el prefijo ' |
- | ; Entrada: A = valor a imprimir por pantalla en binario | + | ; |
+ | ; ENTRADA: A = valor a imprimir por pantalla en binario | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintBin: | PrintBin: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
printbin_loop: | printbin_loop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
printbin_es_uno: | printbin_es_uno: | ||
- | | + | |
- | | + | |
- | | + | ; usar de nuevo el BIT 7 en el bucle |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
; PrintHex: Imprime en la pantalla un numero de 1 byte en hexadecimal. | ; PrintHex: Imprime en la pantalla un numero de 1 byte en hexadecimal. | ||
- | ; | + | ; Para ello convierte el valor numérico en una cadena llamando |
- | ; a Byte2ASCII_Hex y luego llama a RST 16 para imprimir cada | + | ; Byte2ASCII_Hex y luego llama a rst 16 para imprimir cada caracter por |
- | ; caracter por separado. Imprime un $ delante y ESPACIO detrás. | + | ; separado. Imprime un $ delante y ESPACIO detrás. |
- | ; Entrada: A = valor a imprimir por pantalla en hexadecimal | + | ; |
+ | ; ENTRADA: A = valor a imprimir por pantalla en hexadecimal | ||
+ | ; | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintHex: | PrintHex: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
+ | ld a, h ; Recuperamos A | ||
- | | + | |
+ | push de | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | rst 16 |
+ | | ||
+ | ld a, (hl) | ||
+ | rst 16 ; Imprimimos segundo valor HEX | ||
- | | + | |
- | | + | |
- | + | | |
- | | + | |
- | LD A, (HL) | + | |
- | RST 16 ; Imprimimos segundo valor HEX | + | |
- | + | ||
- | POP DE | + | |
- | POP AF | + | |
- | POP HL | + | |
- | + | ||
- | | + | |
; | ; | ||
; PrintHex16: Imprime en la pantalla un numero de 2 bytes en hexadecimal. | ; PrintHex16: Imprime en la pantalla un numero de 2 bytes en hexadecimal. | ||
- | ; Entrada: BC = valor a imprimir por pantalla en hexadecimal | + | ; |
+ | ; ENTRADA: BC = valor a imprimir por pantalla en hexadecimal | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintHex16: | PrintHex16: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
Línea 591: | Línea 640: | ||
; Rutina adaptada de Num2Hex en http:// | ; Rutina adaptada de Num2Hex en http:// | ||
; | ; | ||
- | ; IN: H = Numero a convertir | + | ; ENTRADA: H = Numero a convertir |
- | ; OUT: [Byte2ASCII_output] = Espacio de 2 bytes con los ASCIIs | + | ; SALIDA: [Byte2ASCII_output] = Espacio de 2 bytes con los ASCIIs |
+ | ; MODIFICA: | ||
; | ; | ||
Byte2ASCII_Hex: | Byte2ASCII_Hex: | ||
- | + | push af | |
- | | + | |
- | | + | |
- | | + | |
- | LD DE, Byte2ASCII_output | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
B2AHex_Num1: | B2AHex_Num1: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
B2AHex_Num2: | B2AHex_Num2: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
B2AHex_Exit: | B2AHex_Exit: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
Byte2ASCII_output DB 0, 0 | Byte2ASCII_output DB 0, 0 | ||
Línea 632: | Línea 681: | ||
; | ; | ||
; PrintNum2digits: | ; PrintNum2digits: | ||
- | ; base 10, pero solo los 2 últimos digitos (0-99). | + | ; base 10, pero solo los 2 últimos digitos (0-99). Para ello convierte |
- | ; | + | ; el valor numerico en una cadena llamando a Byte2ASCII_2Dig y luego |
- | ; | + | ; llama a rst 16 para imprimir cada caracter por separado. |
- | ; | + | |
; | ; | ||
- | ; Entrada: A = valor a " | + | ; ENTRADA: A = valor a " |
+ | ; SALIDA: | ||
+ | ; MODIFICA: NADA | ||
; | ; | ||
PrintNum2digits: | PrintNum2digits: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
Línea 656: | Línea 706: | ||
; cadena de texto de max. 2 caracteres (0-99) decimales. | ; cadena de texto de max. 2 caracteres (0-99) decimales. | ||
; | ; | ||
- | ; IN: | + | ; ENTRADA: A = Numero a convertir |
- | ; OUT: DE = 2 bytes con los ASCIIs | + | ; SALIDA: |
+ | ; MODIFICA: A, FLAGS | ||
; | ; | ||
; Basado en rutina dtoa2d de: | ; Basado en rutina dtoa2d de: | ||
Línea 663: | Línea 714: | ||
; | ; | ||
Byte2ASCII_Dec2Digits: | Byte2ASCII_Dec2Digits: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
dtoa2dloop: | dtoa2dloop: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
; PrintFlags: Imprime en la pantalla en binario el registro F (flags). | ; PrintFlags: Imprime en la pantalla en binario el registro F (flags). | ||
+ | ; | ||
+ | ; ENTRADA: | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintFlags: | PrintFlags: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
; PrintFlag: Imprime en la pantalla en binario el flag indicado. | ; PrintFlag: Imprime en la pantalla en binario el flag indicado. | ||
- | ; IN: | + | ; |
+ | ; ENTRADA: F = registro de Flags | ||
+ | ; A = FLAG a imprimir, con constantes como " | ||
+ | ; SALIDA, MODIFICA: NADA | ||
; | ; | ||
PrintFlag: | PrintFlag: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | ; y B contiene el antiguo valor A |
- | | + | |
loopPrintFlag: | loopPrintFlag: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
; Wait_For_Key: | ; Wait_For_Key: | ||
; Devuelve el ASCII de la tecla pulsada en A. | ; Devuelve el ASCII de la tecla pulsada en A. | ||
+ | ; | ||
+ | ; ENTRADA: NADA | ||
+ | ; SALIDA: ASCII de la tecla pulsada sacado de LAST_K | ||
; | ; | ||
Wait_For_Key: | Wait_For_Key: | ||
- | | + | |
- | Wait_for_key_loop: | + | push af |
- | | + | wait_for_key_loop: |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
+ | ld a, ($5c08) | ||
+ | | ||
; | ; | ||
; Wait_For_No_Key: | ; Wait_For_No_Key: | ||
; poder poner como condicion que el usuario suelte la tecla). | ; poder poner como condicion que el usuario suelte la tecla). | ||
+ | ; | ||
+ | ; ENTRADA, SALIDA, MODIFICA: NADA | ||
; | ; | ||
Wait_For_No_Key: | Wait_For_No_Key: | ||
- | | + | |
- | | + | wait_for_no_key_loop: |
- | | + | xor a |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
+ | | ||
+ | | ||
; | ; | ||
; Constantes definidas para usar con cadenas o RST | ; Constantes definidas para usar con cadenas o RST | ||
; | ; | ||
- | _EOS EQU $FF | + | _EOS EQU $ff |
_CR EQU 13 | _CR EQU 13 | ||
_INK EQU 16 | _INK EQU 16 | ||
Línea 765: | Línea 832: | ||
_YELLOW | _YELLOW | ||
_WHITE | _WHITE | ||
+ | |||
+ | _FLAG_S | ||
+ | _FLAG_Z | ||
+ | _FLAG_H | ||
+ | _FLAG_PV | ||
+ | _FLAG_N | ||
+ | _FLAG_C | ||
</ | </ | ||
+ | |||
+ | Viendo el código de la librería, podemos ver varias cosas interesantes: | ||
+ | |||
+ | * Al inicio de cada rutina, se debe documentar qué hace, qué parámetros de entrada y salida tiene y si modifica algún registro en su desarrollo. Esta información nos será muy útil después e incluso lo habitual es tenerla además fuera en una " | ||
+ | |||
+ | * Las funciones preservan los valores de los registros que utilizan internamente usando PUSH al principio de las mismas y POP al final. Si estuvieramos haciendo una librería para desarrollar juegos, en ocasiones nos puede interesar no preservar todos los registros en algunas de las rutinas que más críticas sean en velocidad si las funcione que las llaman tienen esto en cuenta. | ||
A continuación vamos a ver un ejemplo de uso de algunas funciones de la librería (usaremos estas y otras de las funciones que incluye a lo largo del resto de capítulos del curso): | A continuación vamos a ver un ejemplo de uso de algunas funciones de la librería (usaremos estas y otras de las funciones que incluye a lo largo del resto de capítulos del curso): | ||
Línea 771: | Línea 851: | ||
<code z80> | <code z80> | ||
; Prueba de la libreria " | ; Prueba de la libreria " | ||
- | ORG 33500 | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Esperar pulsacion de tecla antes de salir e imprimirla | ; Esperar pulsacion de tecla antes de salir e imprimirla | ||
- | | + | |
- | | + | |
- | | + | |
- | cadena1 DEFB 'FLAGS (F) y ZF: ', $FF | + | cadena1 DEFB 'FLAGS (F) y ZF: ', $ff |
cadena2 DEFB 'Esto es una cadena con salto', | cadena2 DEFB 'Esto es una cadena con salto', | ||
Línea 850: | Línea 930: | ||
DEFB _FLASH, 1, 'FLASH 1', _FLASH, 0, ' FLASH 0', _EOS | DEFB _FLASH, 1, 'FLASH 1', _FLASH, 0, ' FLASH 0', _EOS | ||
- | ; Incluimos nuestra " | + | |
- | INCLUDE " | + | INCLUDE " |
- | END 33500 | + | |
</ | </ | ||
Línea 864: | Línea 944: | ||
El programa borra la pantalla y espera 3 segundos. Tras eso realiza las diferentes impresiones y espera la pulsación de una tecla, imprimiendo su ASCII abajo a la derecha, pegado al asterisco. | El programa borra la pantalla y espera 3 segundos. Tras eso realiza las diferentes impresiones y espera la pulsación de una tecla, imprimiendo su ASCII abajo a la derecha, pegado al asterisco. | ||
- | Para imprimir el primer valor numérico hemos usado **PrintNum** con el objetivo de imprimir el valor " | + | Para imprimir el primer valor numérico hemos usado '' |
- | Nótese que al ensamblar el programa, PASMO nos dará unos " | + | Nótese |
< | < | ||
Línea 879: | Línea 959: | ||
</ | </ | ||
- | No debemos preocuparnos por estos warnings. Pasmo simplemente nos está advirtiendo de que hemos definido unas etiquetas EQU en el código (como por ejemplo _MAGENTA) que después no hemos usado en ninguna otra parte del programa. Sólo son mensajes informativos para que podamos hacer limpieza de referencias y variables que no se usan en el programa que estamos ensamblando. En nuestro caso, esas variables deben de estar en la librería aunque no las estemos usando en este programa concreto por lo que podemos ignorar los mensajes. | + | No debemos preocuparnos por estos warnings. Pasmo simplemente nos está advirtiendo de que hemos definido unas etiquetas |
- | Otro apunte importante sobre nuestro programa de ejemplo es que normalmente, | + | Otro apunte importante sobre nuestro programa de ejemplo es que normalmente, |
Queremos destacar de nuevo que el objetivo de esta librería de funciones es recoger funciones básicas para el desarrollo del curso. En el momento en que nos planteemos desarrollar un juego o una aplicación de calidad profesional, | Queremos destacar de nuevo que el objetivo de esta librería de funciones es recoger funciones básicas para el desarrollo del curso. En el momento en que nos planteemos desarrollar un juego o una aplicación de calidad profesional, | ||
- | Por otra parte, no es normal hacer una única librería (un único fichero | + | Por otra parte, no es normal hacer una única librería (un único fichero |
- | Cuando hacemos un **INCLUDE** de un fichero asm en nuestro programa, el ensamblador " | + | Cuando hacemos un '' |
Por eso, esta librería **utils.asm** que contiene múltiples funciones variadas, la utilizaremos para nuestras pruebas, ya que realizando pruebas no nos importará que librería aumente el tamaño final en los menos de 300 bytes que ocupa el código de la librería, pero lo normal es no utilizarla en las versiones finales de nuestros programas o juegos. Además, la librería dista mucho de ser óptima, ya que utiliza rutinas de la ROM para realizar determinadas tareas, y más adelante desarrollaremos nuestras propias rutinas mucho más eficientes. | Por eso, esta librería **utils.asm** que contiene múltiples funciones variadas, la utilizaremos para nuestras pruebas, ya que realizando pruebas no nos importará que librería aumente el tamaño final en los menos de 300 bytes que ocupa el código de la librería, pero lo normal es no utilizarla en las versiones finales de nuestros programas o juegos. Además, la librería dista mucho de ser óptima, ya que utiliza rutinas de la ROM para realizar determinadas tareas, y más adelante desarrollaremos nuestras propias rutinas mucho más eficientes. |