# Previo Tras los múltiples mensajes de protesta, manifestaciones, y dos intentos de asesinato, hemos decidido remodelar esta sección del curso para facilitar aún más el aprendizaje de nuestros alumnos. Rogamos disculpen las molestias ocasionadas, y los daños mentales y psicológicos que puedan ocasionar el presente curso en la mente de nuestros lectores. Para aquellos que no estén acostumbrados a un ejercicio mental prolongado recomendamos leer una línea cada 30 minutos, y ver el programa de Ana Rosa para limpiar la mente antes de cada capítulo. Esta edición está homologada por la Real academia de la Lengua, habiendo sido revisada personalmente por Jaime Tejedor "Fernado Fernan" Gómez. Gracias por su colaboración. # Subrutinas No hay nada más liante que empezar a escribir un programa e ir añadiendo líneas, ya que llega un punto en el que uno ya no sabe ni por cual parte está escribiendo, ni a donde conduce todo. Para ello lo más adecuado es usar subrutinas, es decir, dividir el programa en secciones claramente diferenciadas y que podamos reaprovechar en otros programas. Para empezar vamos a coger la rutina que ya conocemos para cambiar el borde la pantalla: ; Nuestro tercer programa ORG 32768 ld a, 1 ; borde azul, para actualización inmediata out (254), a RET Hasta aquí todo bien, es muy corta... pero es un latazo tener que escribir el mismo código para cada vez que actualicemos el color del borde. Por ello vamos a dividir el programa en la rutina principal, que llamará a la subrutina, y la subrutina en si: ; ------------------------------------------- ; Programa: Nuestra primera subrutina ; ------------------------------------------- ORG 32768 ; ------------------------------------------- ; Rutina principal ; ------------------------------------------- ld a, 1 ; borde azul, para actualización inmediata call borde RET ; ------------------------------------------- ; SUBRUTINA PARA CAMBIAR EL BORDE DE PANTALLA ; ENTRADAS: a, es el color del borde ; SALIDAS: ninguna ; ------------------------------------------- borde out (254), a ret ¡Que no cunda el pánico! vamos a explicar lo que hemos hecho. En primer lugar hemos puesto unos recuadros de texto para diferenciar bien cada parte del código, explicando en su interior que demonios es lo que hay a continuación, no son impresindibles... pero quedan muy bonitos y ayudan bastante a diferenciar cada parte del programa. Antes de la rutina principal hemos separado el ORG, ya que se trata de una orden para que el ensamblador sepa a partir de qué punto de la memoria empieza el programa. En la rutina principal tenemos una cosa nueva que no hemos visto antes: "call borde". Suena francamente mal, y su función es llamar a una subrutina llamada "borde". Antes de CALL hemos cargado "a" con el valor 1 (el color del borde) y para terminar hemos realizado un RET que finaliza el programa. Cuando realizamos la llamada a la subrutina no alteramos nada en la memoria, por eso la subrutina verá que "a" tiene un valor que puede usar. Si nos fijamos en el programa tras CALL hemos usado un nombre y no una dirección, a eso se le llama "etiqueta" en ensamblador. Las etiquetas las usamos nosotros por comodidad, y el ensamblador se encarga de traducirlo por nosotros a una dirección de memoria. La subrutina la hemos emplazado después del programa principal. ¿Cómo la definimos? simplemente tecleando un nombre de 5 letras de extensión delante de una instrucción: borde out (254), a En esta línea hemos hecho dos cosas: definir una etiqueta y ejecutar una instrucción. De esta forma se crean las subrutinas, con una etiqueta que sirva como referencia para su llamada, y el código de la misma terminando con un ret para RETornar. Fácil, ¿no? Podemos usar cuantas subrutinas queramos, siempre por debajo de la rutina principal, y como podeis ver es conveniente que antes de cada subrutina indiquemos para lo que sirve, además de las entradas que usa y las salidas que proporciona (si las hubiera). # Registros y bits ¡NOOOOO! ¡TEORIA NOOOOO! HARAKIRIIIIIIII! Calma, es algo muy simple y escueto. Hemos visto que existe un registro "a" para usarlo a modo de papel, pero nuestro ordenador es más listo y tiene varios "papeles": a b c d e h l Los hemos separado aposta por pares, ¿el motivo? que algunas instrucciones en ensamblador las usan de dos en dos. Por ejemplo para usarlas como direcciones de memoria. La hoja más importante es "a", es la hoja principal de trabajo y con la que más vamos a trabajar. Todos los registros, por separado, pueden tener un valor entre 0 y 255 en valor decimal, o un valor entre: 00000000 y 11111111 en código binario. Nosotros entendemos el decimal, y la CPU del ordenador sólo entiende el binario. El programa ensamblador se encarga de "traducir" nuestro lenguaje al suyo. No obstante, entender el binario puede resultar útil en muchas ocasiones para realizar ciertas triquiñuelas. # Nuestra segunda subrutina Para nuestra segunda subrutina vamos a realizar algo más complicado, nos vamos a crear un CLS sin usar ninguna rutina de la ROM: ; ------------------------------------------- ; SUBRUTINA PARA HACER UN CLS SIN USAR LA ROM ; ENTRADAS: a, es el color de tinta/papel ; SALIDAS: pone el mapa de pantalla a 0 ; Ver tabla de color al final del programa ; ------------------------------------------- sucls ld hl, 22528 ld de 22529 ld bc, 767 ld (hl),a ldir ld hl, 16384 ld de, 16385 ld bc, 6143 ld (hl),l ldir ret Antes de ver 10 veces seguidas la película de JFK, para planear perfectamente el asesinato del autor de este curso, esperamos que se nos de una oportunidad (si, tengo doble personalidad, Gollum no es el único... ssssssss). Si teclamos la rutina dada, tal cual, no hacemos nada. Esto es una subrutina, y hace falta una rutina principal que la llame (o al menos usar un ORG antes para usarla directamente como rutina). La forma de llamar a esta subrutina es muy simple: call sucls Es decir, el programa más básico que incluya la subrutina sería: ; ------------------------------------------- ; Programa: Nuestra segunda subrutina ; ------------------------------------------- ORG 32768 ; ------------------------------------------- ; Rutina principal ; ------------------------------------------- call sucls RET ; ------------------------------------------- ; SUBRUTINA PARA HACER UN CLS SIN USAR LA ROM ; ENTRADAS: a, es el color de tinta/papel ; SALIDAS: pone el mapa de pantalla a 0 ; Ver tabla de color al final del programa ; ------------------------------------------- sucls ld hl, 22528 ld de 22529 ld bc, 767 ld (hl),a ldir ld hl, 16384 ld de, 16385 ld bc, 6143 ld (hl),l ldir ret Ahora pasamos a la subrutina en si. Lo que más llama la atención es que usamos muchos registros nuevos, antes sólo usábamos "a" y además por parejas (a eso se le llama de 16 bits). Para entenderlo todo empezaremos por la instrucción que menos conocemos, LDIR. LDIR realiza la acción siguiente: coge el valor de la posición de memoria apuntada por HL y la copia a la dirección de memoria apuntada por DE, tras lo cual incrementa HL y DE (es decir pasa a la siguiente posición). Esta operación se repite tantas veces como se indique en BC (en nuestro caso 767 veces). En pocas palabras, se trata de una instrucción que copia de una parte de la memoria a otra. Pensad en ella como si fuera otra subrutina (de hecho podemos llamar desde una subrutina a otras subrutinas). En nuestro ejemplo hay dos partes de la memoria a borrar: los pixels y los atributos (colores). Los atributos es lo primero que borramos, y esta parte empieza en la posición de memoria 22528, con una longitud de 768 bytes: ld hl, 22528 ld de 22529 ld bc, 767 ld (hl),a ldir Aprovechando la instrucción LDIR hemos copiado con la instrucción: ld (hl),a el valor de "a" a la posición de memoria que apunta HL: ld hl, 22528 es decir, 22528 que es el primer byte de la zona de atributos en pantalla. Con ello, si hacemos que LDIR copie continuamente el valor del byte anterior al siguiente (es decir, que copie consecutivamente "a" de una posición a la siguiente) llenaremos todo el espacio de atributos. No tenemos que realizar esta acción 768 veces, que es lo que ocupan los atributos, sino 767 veces, ya que el primer byte de la zona de atributos ya lo hemos puesto con el valor "a" nosotros. La segunda parte borra la zona de pixels a 0: ld hl, 16384 ld de, 16385 ld bc, 6143 ld (hl),l ldir La zona de pixels empieza en 16384 y tiene una extensión de 6144 bytes. En esta ocasión hemos usado una argucia para evitar tener que usar "a"... como queremos poner todo a 0, y como casualmente 16384 en binario es 01000000 00000000... y hemos hecho: ld hl, 16384 para indicar la posición de memoria donde va a comenzar LDIR... pues reaprovechamos "L". Si no quisiéramos usar esa argucia, podríamos usar: ld hl, 16384 ld de, 16385 ld bc, 6143 ld (hl),b ldir Pero previamente tendríamos que poner en "b" el valor 0 si queremos borrar todos los pixels (o 255 si quisiéramos tenerlos todos activados). Ese es el funcionamiento de la subrutina que hemos preparado, pero aún cuando no sepamos como funciona la podemos usar. Basta preparar los registros que necesita la subrutina a nuestro antojo y realizar el CALL correspondiente. En este caso si realizamos el call y ponemos en "a" antes el color que queremos para el borrado de la pantalla, no tenemos porqué saber como funciona la subrutina. # Pasamos a la acción Bien, ya estamos en disposición de usar cualquier subrutina, aún cuando no sepamos su funcionamiento, ya que nos basta con saber que parámetros (registros de entrada) necesita. ¿No nos cree? pase y vea: ; ------------------------------------------- ; Prueba de subrutinas usando sprites ; ; Radastan ; ------------------------------------------- ORG 32768 ; ------------------------------------------- ; Rutina principal ; ------------------------------------------- ld a, 15 ; papel azul y tinta en blanco call sucls ld a, 1 ; borde azul, para actualización inmediata call borde ld d, 7 ; posición vertical del sprite ld e, 7 ; posición horizontal del sprite ld hl, cubo ; sprite a usar call print ret ; ------------------------------------------- ; SUBRUTINA PARA HACER UN CLS SIN USAR LA ROM ; ENTRADAS: a, es el color de tinta/papel ; SALIDAS: pone el mapa de pantalla a 0 ; Ver tabla de color al final del programa ; ------------------------------------------- sucls ld hl, 22528 ld de 22529 ld bc, 767 ld (hl),a ldir ld hl, 16384 ld de, 16385 ld bc, 6143 ld (hl),l ldir ret ; ------------------------------------------- ; SUBRUTINA PARA CAMBIAR EL BORDE DE PANTALLA ; ENTRADAS: a, es el color del borde ; SALIDAS: ninguna ; ------------------------------------------- borde out (254), a ret ; ------------------------------------------- ; RUTINA DE IMPRESION DE UN SPRITE 16x16 PIXELS ; CON ATRIBUTOS EN CUALQUIER POSICION DE CARACTER ; ENTRADAS: ; D será la posición del cursor vertical en caracteres ; E será la posición del cursor horizontal en caracteres ; HL es la posición de memoria donde tenemos el sprite ; SALIDAS: se escribe en el mapa de pantalla ; ADVERTENCIAS: no comprueba límites de pantalla ; ------------------------------------------- print push de ; salvamos los valores vertical y horizontal push de ; salvamos los valores vertical y horizontal push de ; salvamos los valores vertical y horizontal call cdrw ; calculamos dirección de pantalla ld b, 8 call draw pop de ; recuperamos el valor horizontal inc d ; incrementamos una línea call cdrw ld b, 8 call draw ; Ahora imprimimos los atributos pop de ; recuperamos el valor horizontal call cdrw call catr call colr pop de ; recuperamos el valor horizontal inc d ; incrementamos una línea call cdrw call catr call colr ret draw ld a,(hl) ; hl indica la posición del sprite en memoria ld (de),a ; de indica la posición de pantalla inc hl inc e ld a,(hl) ; esta parte imprime el segundo byte ld (de),a inc hl dec e inc d djnz draw ; decrementa B y si es cero deja de saltar a draw ret colr ld a,(hl) ld (de),a inc e inc hl ld a,(hl) ld (de),a dec e inc hl ret cdrw ld a, d ; recuperamos el valor vertical and 7 ; nos quedamos con la posición en el tercio rrca rrca rrca ; rotamos para dejar su valor en múltiplos de 32 (linea) and 224 ; borramos el resto de bits por si las moscas or e ; sumamos el valor horizontal ld e, a ; e preparado ld a, d and 24 ; modificamos según el tercio de pantalla or 64 ; nos posicionamos a partir de 16384 (16384=64+0 en dos bytes) ld d, a ; d preparado ret catr ld a,d rra rra rra ; multiplicamos por 32 and 3 ; nos quedamos con los tres bits bajos or 88 ; apuntamos al comienzo del mapa de atributos ld d,a ; ya tenemos d listo, e no hay que cambiarlo ret ; ------------------------------------------- ; sprite de un cubo ; los cuatro últimos bytes son el color ; se pueden usar sprites creados con SevenuP ; ------------------------------------------- cubo defb 255,255,128,1,128,1,128,1,128,1,128,1,128,1,128,1 defb 128,1,128,1,128,1,128,1,128,1,128,1,128,1,255,255 defb 12,13,14,15 ; - TINTA - ; ; NEGRO 0 00000000 ; AZUL 1 00000001 ; ROJO 2 00000010 ; MAGENTA 3 00000011 ; VERDE 4 00000100 ; AZULADO 5 00000101 ; AMARILLO 6 00000110 ; BLANCO 7 00000111 ; ; - PAPEL - ; ; NEGRO 0 00000000 ; AZUL 8 00001000 ; ROJO 16 00010000 ; MAGENTA 24 00011000 ; VERDE 32 00100000 ; AZULADO 40 00101000 ; AMARILLO 48 00110000 ; BLANCO 56 00111000 ; ------------------------------------------- ; FIN DEL PROGRAMA ; ------------------------------------------- ¿PERO TE HAS VUELTO LOCO? Para nada, si observa el programa verá que la rutina principal la entiende a la perfección, aunque no entienda nada de lo que cada subrutina está haciendo. Esa es la gracia del lenguaje ensamblador, se pueden usar subrutinas hechas por otros. No intente comprender lo que hace la subrutina de impresión de sprites, sólo lea los comentarios de la cabecera, como verá es muy simple: ; D será la posición del cursor vertical en caracteres ; E será la posición del cursor horizontal en caracteres ; HL es la posición de memoria donde tenemos el sprite En este ejemplo hemos dado otra vuelta de tuerca, y el sprite también lo ponemos en el código del programa mediante "defb". Esta instrucción sirve para introducir valores de byte, tantos como queramos, y realmente tampoco es una instrucción de ensamblador, es otra instrucción usada por el programa ensamblador para facilitarnos la vida. Si usamos el programa SevenuP y creamos un sprite de 16x16 pixels podemos obtener el código necesario para cambiar el sprite "cubo" por el que queramos. No obstante no es el momento de comprender esta parte, eso lo dejamos para más adelante. Lo dicho, no intente comprender la subrutina de sprites, simplemente úsela.