Extra, extra, Arcade Classics Magazine nº 2

Y volvemos a estar de enhorabuena, y una vez más gracias a Enrique Segura Alcalde, que nos vuelve a regalar una nueva entrega de Arcade Classics Magazine, la revista que edita además de Play Again.

En esta caso nos trae el número 2 de esta publicación, y como viene siendo costumbre en él, totalmente gratuito. Y es que Enrique está dispuesto a darnos entretenimiento tan solo a cambio de nuestro tiempo.

Pero Enrique no solo dedica su tiempo a sus proyectos, también saca tiempo para colaborar con otros proyectos como es el caso de Mentero, magazine digital en el que se encarga de la sección de videojuegos retro.

Aquí os dejo los enlaces a dos de sus artículos:

Y hablando de Dreamcast. Como ya anunciamos, Enrique ha publicado el libro Dreamcast, el sueño eterno. Si no lo tenéis todavía, ya estáis tardando. En serio, merece la pena.

Si quereis saber algo más sobre el libro, aquí os dejo una reña de TuberViejuner.

TuberViejuner

Y otra más de El Spectrumero Javi Ortiz.

Play Again 7, fiel a su cita

Ha tardado algo más de lo previsto, pero al fin llega el número 7 de Play Again.

Pese a que Enrique ya juega en otra liga, Dolmen Editorial acaba de publicar su libro Deamcast, el sueño eterno, esto no ha impedido que nos vuelva a regalar otro número de la revista que el mismo edita, y que nos hace llegar de manera gratuita.

Es una gran alegría que nuestro amigo Enrique haya hecho realidad su sueño con la publicación de este libro, así como que siga publicando números de Play Again para nuestro deleite.

Si queréis conocerle, Enrique va a estar este sábado 5 de octubre en Madrid Games Week, presentando su libro a las 17:00h.

Recuera que puedes descargarte todos los números de Play Again.

Play Again nº 7

Dreamcast, el sueño eterno. Nuevo libro de Enrique Segura Alcalde

Y la perseverancia tiene premio. Tanto va el cántaro a la fuente, que al final se llena.

Ese es el caso de Enrique, que tras la edición de Insert Coin, Play Again y Arcade Classics Magazine, ha cumplido un sueño, no solo suyo, pues este sueño es compartido con sus amigos, y ve como Dolmen Editorial publica su nuevo libro.

No nos queda más que desearle suerte en esta nueva aventura, y esperar a que llegue el libro para poder leerlo.

Play Again nº 6 – Especial de Verano 2019

El verano está a la vuelta de la esquina y, como viene siendo habitual, Enrique Segura Alcalde acude fiel a su cita, trayéndonos el número 6 de Play Again, el especial del verano de 2019.

Como otras veces, la descarga es gratuita aunque le podéis solicitar una copia física, que suelen estar muy bien ajustadas de precio.

En este especial de verano, entre otras cosas, Enrique nos trae un reportaje sobre 20 juegos imprescindibles de ZX Spectrum, 10 cartuchos de Game Boy para llevarte a la playa, y mucho más.

Desde aquí queremos dar las gracias a Enrique por su trabajo y generosidad.

Play Again nº 6

Ya puedes descargarte todos los números de Play Again.

Arcade Classics Magazine

No sabemos de donde saca tiempo Enrique Segura Alcalde, pero el caso es que lo saca; para goce y disfrute de todos nosotros.

A la edición de Play Again, añade ahora la edición de Arcade Classics Magazine, y como viene siendo habitual en él, es de libre distribución.

Nos cuenta Enrique que, «lo que en un principio iba a ser un especial de Play Again, ha terminado tomando entidad propia». También nos dice que, «aunque depende de la aceptación, la idea es lanzar más números, aunque sin una periodicidad concreta». ¿El lanzamiento de Arcade Classics Magazine significa el final de Play Again? «¡No! De ninguna manera», casi nos grita Enrique.

Aportaciones como las de Enrique, hacen que la escena retro cada día este más viva.

Desde aquí queremos mostrar nuestro máximo apoyo a Enrique en su nueva aventura, y darle las gracias por hacernos partícipes de ella.

Ya puedes descargar el número uno de Arcade Classics Magazine. Esperamos que os guste.

Arcade Classics Magazine nº 1

Entorno de desarrollo para ensamblador Z80

Una de las primeras dudas a la hora de afrontar el reto de programar, en cualquier lenguaje, es que entorno de desarrollo tengo que preparar.

A continuación voy detallar el entorno que uso para programar en ensamblador para Z80. Puede no ser el mejor, pero es con el que me siento más cómodo. Ya se sabe que para gustos, colores.

A la hora de configurar un entorno de desarrollo, debemos contar con al menos tres herramientas:

  1. Editor de texto.
  2. Ensamblador.
  3. Emulador para probar y depurar nuestros programas.

Editores de texto hay muchos y variados, también gratuitos, y muy potentes. Yo me inclino por los gratuitos, y tras haber trabajado con varios, últimamente trabajo con Visual Studio Code. Es gratuito y tiene extensiones para reconocer la sintaxis del ensamblador del Z80. Yo uso la extesión Z80 Assembly. También tiene una consola integrada que a mi me resulta muy útil.

Para ensamblar lo programas uso Pasmo, que está disponible para Windows, Linux y Max OS/X. Pasmo es un ensamblador cruzado para Z80, que compila para varias plataformas, entre ellas para ZX Spectrum. La compilación en Pasmo se hace por línea de comandos. Yo siempre ejecuto Pasmo con los mismos parámetros, cuando compilo para ZX Spectrum.

pasmo --name NombrePrograma --tapbas Archivo.asm Archivo.tap --public

El parámetro public es muy importante. Genera un fichero llamado con el mismo nombre, que contiene las direcciones de memoria de todas las etiquetas del programa.

En Windows es importante incluir la ruta de Pasmo en la variable de sistema Path, para poder invocarlo desde cualquier sitio. En Linux esto no es necesario y en Mac OS/X no lo he probado nunca.

Yo en concreto, que uso Windows, creo un archivo .bat con la llamada a Pasmo y lo invoco desde la ventana de comandos integrada en Visual Studio Code. Copio este archivo en cada proyecto, cambiando solo el nombre del programa y los nombres de los archivos.

Por último queda el emulador. Yo uso ZX Spin 0.7. Me resulta cómodo por que en una sola ventana puedo depurar, ver el estado de los registros, acceder a ver el valor de todas las posiciones de memoria, etc. Solo está disponible para Windows, pero en Linux funciona ejecutándolo con Wine. Además tiene un editor, que aunque no es muy estable, permite compilar y ejecutar programas, conservando el nombre de las etiquetas en el depurador. Suelo abrir ZX Spin 0.7 y luego unicamente vuelvo a cargar el fichero .tap cada vez que compilo y quiero ver los resultados.

Espero que os sirva de ayuda lo aquí expuesto.

Llega Retrópolis Valencia 2019

Retrópolis Valencia está a la vuelta de la esquina, y han publicado su programa, que puedes consultar en su web.

Aquí puedes ver un resumen:

  • 10:30 – De la Edad de Oro a Commandos, los años perdidos.
  • 11:45 – Visita guiada al Museo de informática
  • 13:00 – Retrogaming: Una misión posible en sistemas Arcade de nueva generación.
  • 16:30 – El proyecto MSXVR.
  • 18:00 – Concierto Games&Symphonies Pocket.

Como ya sabéis, Retrópolis Valencia 2019 se celebra el sábado 4 de mayo, en EST Ingeniería Informática de 10:00h a 19:00h.

La entrada es gratuita, pero requiere una entrada sostenible, que trata de que los asistentes lleven algún tipo de aparato electrónico o consumible, para ser reciclado. Además los asistentes pueden llevar kilos de alimentos no perecederos, a cambio de los cuales se le darán videojuegos o material de videojuegos.

Por otro lado, si tienes una tienda y quieres exponer tus productos en Retrópolis Valencia 2019, aún estás a tiempo.

Y recuerda, también sortean una PS Mini Classic.

¿Te lo vas a perder?

Viajes Retro Parla

Nos han llegado rumores de que Retro Parla quiere organizar un viaje para visitar, el recientemente anunciado, Arcade Vintage Museo del Videojuego de Ibi, que tiene previsto abrir sus puertas el próximo mes de junio.

Aunque por ahora solo es una idea, trabajan con el objetivo de organizar un viaje que incluiría ida y vuelta en autobús, y visita al museo.

El proyecto, que todavía está en una etapa de desarrollo muy temprana, nos parece muy interesante y esperamos que llegue a buen término. Nosotros vamos a ir reservando plaza, por si acaso.

El autobús tendría salida y llegada en Parla. En principio sería ida y vuelta en el día, para que salga lo más económico posible.

También nos comentan que dependiendo del número de interesados, se podría fletar más de un autobús.

Todas aquellas personas que estén interesadas pueden escribir un e-mail, y de esta forma ayudarles, para que puedan hacerse una idea del volumen de personas a tener en cuenta para organizar el viaje.

Esperamos que este proyecto se convierta pronto en realidad, y que sea el primero de muchos.

Posiciones de pantalla ZX Spectrum

La pantalla del ZX Spectrum tiene una resolución de 256*192 píxeles, o lo que es lo mismo 32 columnas y 192 scanlines. Con cada byte podemos configurar 8 píxeles, encendiendo los que están a 1 y apagando los que están a 0; 256 píxeles / 8 píxeles por byte = 32 columnas. El área gráfica ocupa 6.144 bytes ($1800) y comienza en la dirección de memoria 16.384 ($4000).

Una de las particularidades del ZX Spectrum es la forma en la que se distribuye el área gráfica. Cada carácter se compone de 64 píxeles, o lo que es lo mismo 8×8. A cada una de las líneas del carácter se la denomina scanline. Si pintamos en el primer scanline de un carácter y luego sumamos 32 a la posición de memoria, la lógica nos dice que nos situamos en el siguiente scanline del carácter, pero no es así ya que en realidad nos estamos situando en el primer scanline del carácter situado justo debajo del que estamos.

Si pintamos la posición $4000, primer byte de la pantalla, y luego queremos pintar en el primer scanline de la línea 8 de pantalla, la lógica dice que solo habría que sumar 8 veces 32 ($20) a la posición $4000, por lo que el primer scanline del primer carácter de la línea 8 estaría en la posición $4100. Pero esto no es así ya que la posción $4100 corresponde con el segundo scanline del primer carácter de la pantalla.

Este comportamiento es debido a que la pantalla del ZX Spectrum está dividida en tercios. Cada uno de los tercios tiene 8 líneas y cada una de las líneas 8 scanlines. El primer tercio va desde la posición de memoria $4000 hasta la $47FF, el segundo tercio desde la $4800 hasta la $4FFF y el tercer tercio desde la $5000 hasta la $57FF.

Aunque en principio esto resulte extraño, cuando se ve la composición que toman los bytes a la hora de conformar una posición de memoria, todo cobra sentido. La composición de los bytes es la siguiente:

010T TSSS LLLC CCCC

Donde 010 es siempre fijo, TT representa el tercio (de 0 a 2), SSS representa el scanline dentro del carácter (de 0 a 7), LLL representa la línea dentro del tercio (de 0 a 7) y CCCCC representa la columna (de 0 a 31).

Si nos fijamos en la línea, de 0 a 23, los bits 0, 1 y 3 siempre indican la línea dentro del tercio, y los bits 4 y 5 el tercio.

Línea 0:  0000 0000     Línea 0 del tercio 0
Línea 7: 0000 0111 Línea 7 del tercio 0
Línea 8: 0000 1000 Línea 0 del tercio 1
Línea 15: 0000 1111 Línea 7 del tercio 1
Línea 16: 0001 0000 Línea 0 del tercio 2
Línea 23: 0001 0111 Línea 7 del tercio 2

Para ver todo lo expuesto, vamos a realizar un sencillo ejemplo que rellena los bytes de la primera columna con un secillo patrón $AA (1001 1001).

org $8000 

ld hl, $4000 ; HL = dirección de inicio de la VideoRAM.
ld bc, $20 ; BC = valor a sumar para pasar al siguiente
; carácter.
ld a, $08 ; A = para dibujar el primer scanline del primer
; tercio.

loop:
ld (hl), $aa ; Carga en pantalla el patrón $AA.
add hl, bc ; Avanza HL a la siguiente línea.
dec a ; Decrementa A.
jr nz, loop ; Bucle hasta que A llegue a 0.

ret

Si ejecutamos el programa, dibuja el patrón $AA en el primer scanline de las 8 primeras líneas.

Para ver la distribución de la pantalla del ZX Spectrum, solo hay que ir cambiado la línea LD A, $08 e ir cargando múltiplos de $08; $08, $10, $18, $20, $28, $30, $38, $40, $48…

Hasta que no lleguemos a $48, no se verá como se pinta el primer scanline de las 8 líneas del segundo tercio.

A partir de aquí, vamos a ir viendo rutinas que os pueden ayudar a la hora de trabajar con la pantalla del ZX Spectrum. Tened en cuenta que estas rutinas no controlan si las coordenadas están fuera del área de la pantalla.

La rutina CharCoord2PointerHR, toma unas coordenadas X e Y y retorna la dirección de memoria correspondiente.

; ----------------------------------------------------------------
; CharCoord2PointerHR: Obtiene la dirección de memoria 
; correspondiente a las coordenadas de carácter especificadas. 
; 
; Entrada: B -> Coordenada Y (0 a 23).
;          C -> Coordenada X (0 a 31).
; 
; Salida: HL -> Dirección de memoria que corresponde a las
;               coordenadas. 010T TSSS LLLC CCCC. 
;
; Altera el valor de los registros AF y HL.
; ----------------------------------------------------------------- CharCoord2PointerHR:
ld a, b       ; A = línea. Bits 0, 1 y 2 línea. Bits 3 y 4 tercio.
and $18       ; Se queda con el tercio 0001 1000. 
              ; Scanline siempre a 0.
or $40         ; Agrega la parte fija 0100 0000.
ld h, a       ; H = A. Parte alta de la dirección calculada.

ld a, b       ; A = línea.
and $07       ; Se queda con la línea dentro del tercio.
rrca
rrca
rrca          ; Rota tres veces para pasar valor a bits 5, 6 y 7.
or c          ; Agrega la columna.
ld l, a       ; L = A. Parte baja de la dirección calculada.

ret

Para probar esta rutina podemos ir pasándole posiciones y luego cargando un patrón en la dirección devuelta y así ver donde dibuja.

org $8000

ld b, $10 ; Carga en B la línea.
ld c, $10 ; Carga en C la columna.
call CharCoord2PointerHR ; Calcula la posición de memoria.
ld (hl), $ff ; Activa todos los píxeles en la
; posición de memoria.

inc b ; Incrementa la línea.
inc c ; Incrementa la columna.
call CharCoord2PointerHR ; Calcula la posición de memoria.
ld (hl), $ff ; Activa todos los píxeles en la
; posición de memoria.

inc b ; Incrementa la línea.
dec c ; Decrementa la columna.
call CharCoord2PointerHR ; Calcula la posición de memoria.
ld (hl), $ff ; Activa todos los píxeles en la
; posición de memoria.

ret

La rutina PointerHR2CharCoord, toma una dirección de memoria y retorna las coordenadas de carácter correspondientes.

; ---------------------------------------------------------------- 
; PointerHR2CharCoord: Obtiene las coordenadas de carácter
; correspondientes a la dirección de memoria especificada.
;
; Entrada: HL -> Puntero a memoria. 010T TSSS LLLC CCCC.
;
; Salida: B -> Coordenada Y (0 a 23).
; C -> Coordenada X (0 a 31).
;
; Altera el valor de los registros AF y BC.
; ---------------------------------------------------------------- PointerHR2CharCoord:
ld a, h ; A = parte alta de la dirección de memoria. 010T TSSS.
and $18 ; Se queda con el tercio. Bits 4 y 5 de la
; coordenada Y. Scanline siempre a 0, son coordenadas
; de carácter.
ld b, a ; B = A.

ld a, l ; A = parte baja de la dirección de memoria. LLLC CCCC.
and $e0 ; Se queda con la línea dentro del tercio.
rrca
rrca
rrca ; Rota tres veces para pasar valor a bits 0, 1 y 3.
or b ; Agrega los bits 4 y 5.
ld b, a ; B = A. Coordenada Y calculada.

ld a, l ; A = parte baja de la dirección de memoria. LLLC CCCC.
and $1f ; Se queda con la columna.
ld c, a ; C = A. Coordenada X calculada.

ret

La rutina PointerHRNextLine, toma una dirección de memoria y retorna la dirección de memoria correspondiente a la línea siguiente.

; ----------------------------------------------------------------
; PointerHRNextLine: obtiene la dirección de memoria
; correspondiente a la siguiente línea de carácter.
;
; Entrada: HL -> Dirección actual. 010T TSSS LLLC CCCC.
;
; Salida: HL -> Dirección de la línea siguiente.
; 010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- PointerHRNextLine:
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
add a, $20 ; Añade una línea (LLLC CCCC + 0010 0000).
ld l, a ; L = A.
ret nc ; Si no hay acarreo, la línea sigue entre 0 y 7, sale.

; Hay acarreo, hay que cambiar el tercio de la pantalla.
ld a, h ; A = parte alta de la dirección. 010T TSSS.
add a, $08 ; Añade un tercio (010T TSSS + 0000 1000).
ld h, a ; H = A.

ret

La rutina PointerHRPreviousLine, toma una dirección de memoria y retorna la dirección de memoria correspondiente a la línea anterior.

; ----------------------------------------------------------------
; PointerHRPreviousLine: obtiene la dirección de memoria
; correspondiente a la línea anterior de carácter.
;
; Entrada: HL -> Dirección actual. 010T TSSS LLLC CCCC.
;
; Salida: HL -> Dirección de la línea anterior.
; 010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- PointerHRPreviousLine:
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
and $e0 ; Se queda con la parte de la línea (1110 0000).
jr z, PointerHRPreviousLine_continue ; Si línea es 0,
; cambio de tercio.

; No hay cambio de tercio.
ld a, l ; A = parte baja de la dirección, LLLC CCCC.
sub $20 ; Resta una línea (LLLC CCCC - 0010 0000).
ld l, a ; L = A. Dirección calculada.

ret

PointerHRPreviousLine_continue:
; Hay que cambiar el tercio.
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
or $e0 ; Pone la línea a 7 (LLLC CCCC or 1110 0000).
ld l, a ; L = A. Parte baja de la dirección calculada.

ld a, h ; A = parte alta de la dirección. 010T TSSS.
sub $08 ; Resta uno al tercio (010T TSSS - 0000 1000).
ld h, a ; H = A. Parte baja de la dirección calculada.

ret

La rutina PointerHRNextScanLine, toma una dirección de memoria y devuelve la del siguiente scanline.

; ----------------------------------------------------------------
; PointerHRNextScanLine: obtiene la dirección de memoria
; correspondiente al siguiente scanline.
;
; Entrada: HL -> dirección actual. 010T TSSS LLLC CCCC.
;
; Salida: HL -> dirección del siguiente scanline.
; 010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- PointerHRNextScanLine:
ld a, h ; A = parte alta de la dirección. 010T TSSS.
and $07 ; Se queda con el scanline.
cp $07 ; Comprueba si el scanline es 7.
jr z, PointerHRNextScanLine_continue ; Es 7, cambio de línea.

; El scanline no es 7.
inc h ; Aumenta en 1 el scanline y sale.

ret

PointerHRNextScanLine_continue:
; Hay que cambiar la línea.
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
add a, $20 ; Añade una línea (LLLC CCCC + 0010 0000).
ld l, a ; L = A.
ld a, h ; A = parte alta de la dirección. 010T TSSS.
jr nc, PointerHRNextScanLine_end ; Si no hay acarreo, salta
; para terminar el cálculo.

; Hay acarreo, hay que cambiar el tercio.
add a, $08 ; Añade uno al tercio (010T TSSS + 0000 1000).

PointerHRNextScanLine_end:
and $f8 ; Se queda con la parte fija y el tercio.
; Deja el scanline a 0.
ld h, a ; H = A. Dirección calculada.

ret

Para probar la rutina, vamos a utilizar un pequeño programa para ver como dibuja. Al contrario de lo que vimos en el primer ejemplo, ahora si se pinta seguido, un scanline seguido del que va justo detrás.

org $8000

ld hl, $4000 ; HL = dirección de inicio de la VideoRAM.
ld b, $08 ; B = para dibujar 8 scanlines.

loop:
ld (hl), $aa ; Carga en pantalla el patrón $AA.
call PointerHRNextScanLine ; Avanza al siguiente scanline.
djnz loop ; Bucle hasta que B llegue a 0.

ret

Si ejecutamos el programa, dibuja el patrón $AA en el primer carácter. El primer ejemplo lo hacía en el primer scanline de las 8 primeras líneas.

Al igual que hicimos en el primer ejemplo, podemor ir cargando múltiplos de $08 en B para ver como va dibujando el programa; $08, $10, $18, $20, $28, $30, $38, $40, $48…

Por último, la rutina PointerHRPreviousScanLine, toma una dirección de memoria y retorna la dirección de memoria correspondiente al scanline anterior.

; ----------------------------------------------------------------
; PointerHRPreviousScanLine: obtiene la dirección de memoria
; correspondiente al scanline anterior.
;
; Entrada: HL -> dirección actual. 010T TSSS LLLC CCCC.
;
; Salida: HL -> Dirección del scanline anterior.
; 010T TSSS LLLC CCCC.
;
; Altera el valor de los registros AF y HL.
; ---------------------------------------------------------------- PointerHRPreviousScanLine:
ld a, h ; A = parte alta de la dirección. 010T TSSS.
and $07 ; Se queda con el scanline.
or a ; Comprueba si está a 0.
jr z, PointerHRPreviousScanLine_continue ; Si es 0, cambiar línea.

; No hay cambio de línea.
dec h ; Decrementa el scanline y sale.

ret

PointerHRPreviousScanLine_continue:
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
and $e0 ; Se queda con la parte de la línea.
or a ; Comprueba si está a cero.
jr z, PointerHRPreviousScanLine_change ; Si es 0, cambiar tercio.

; No hay que cambiar tercio.
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
sub $20 ; Resta un línea, (LLLC CCCC - 0010 0000).
ld l, a ; L = A.
jr PointerHRPreviousScanLine_end ; Salta a fin de cálculo.

PointerHRPreviousScanLine_change:
; Hay que cambiar de tercio.
ld a, l ; A = parte baja de la dirección. LLLC CCCC.
or $e0 ; Pone la línea en 7.
ld l, a ; L = A.

ld a, h ; A = parte alta de la dirección. 010T TSSS.
sub $08 ; Resta uno al tercio.
ld h, a ; H = A.

PointerHRPreviousScanLine_end:
ld a, h ; A = parte alta de la dirección. 010T TSSS.
and $F8 ; Se queda con la parte fija y el tercio.
or $07 ; Pone el scanline a 7.
ld h, a ; H = A.

ret

Y para poder probar la rutina, otro sencillo programa.

org $8000

ld hl, $57ff ; HL = dirección de fin de la VideoRAM.
ld b, $08 ; B = para dibujar 8 scanlines.

loop:
ld (hl), $aa ; Carga en pantalla el patrón $AA.
call PointerHRPreviousScanLine ; Retrocede al scanline anterior.
djnz loop ; Bucle hasta que B llegue a 0.

ret

Todo lo que he puesto aquí, lo he aprendido siguiendo el curso de ensamblador que se encuentra en speccy.org y leyendo el libro Código Máquina del ZX-Spectrum de Jesús Alonso Cano.

Las rutinas aquí expuestas son los ejercicios que he realizado para practicar lo aprendido.

Numeros BCD

Los números BCD se ven representados de manera natural, tal y como nosotros los veríamos; el 99 hexadecimal, representa al 99 decimal.

Para conseguir esto, un byte puede representar números del 0 al 99, dividiendo el byte en dos nibbles (4 bits). Cada nibble puede representar números del 0 a 9.

De esta manera, el 10 hexadecimal, que en decimal es 16, en BCD representa el 10 decimal. Dicho de otra manera, vemos los números hexadecimales como si de números decimales se tratara.

Para representar un número de 10 dígitos, se necesitan 5 bytes. Se puede usar un byte extra para indicar la posición de la coma, y así poder trabajar con decimales. Los números BCD se usan para operar con cifras de más de 16 bits.

A la hora de operar con números BCD, hay que tener muy presente la instrucción DAA (Decimal Adjust Accumulator). DAA realiza ajustes en los resultados de operaciones con números BCD, y funciona de la siguiente manera:

  • Comprueba los bits 0, 1, 2 y 3. Si contienen un dígito no BCD (mayor de 9) o se establece el flag H; suma o resta, dependiendo de la operación, 06h (0000 0110b) al byte.
  • Comprueba los bits 4, 5, 6 y 7. Si contienen un dígito no BCD (mayor de 9) o se establece el flag C; suma o resta, dependiendo de la operación, 60h (0110 0000b) al byte.

De esta manera, si tenemos $09 y sumamos $01, el resultado es $0A. Tras ejecutar DAA, el resultado es $10. Después de cada instrucción aritmética, incremento o decremento hay que ejecutar DAA.

ld a, $09     ; A = $09
inc a ; A = $0A
daa ; A = $10

dec a ; A = $0F
daa ; A = $09

add a, $03 ; A = $0C
daa ; A = $12

sub $03 ; A = $0F
daa ; A = $09

Por otro lado, la manera de obtener el código ASCII de cada nibble, es relativamente sencilla.

La rutina que se muestra a continuación, recibe un número BCD en el registro B y lo muestra en pantalla.

; -----------------------------------------------------
; Imprime en pantalla un número BCD.
; Entrada: B -> Número BCD a imprimir.
; Altera el valor del registro AF.
; -----------------------------------------------------
PrintBCD:
ld a, b ; Carga en A el número a imprimir
and $f0 ; Se queda con el nibble superior
rrca
rrca
rrca
rrca ; Lo rota cuatro veces para pasarlo al nibble inferior
add a, '0' ; Suma el ASCII del 0 para obtener el ASCII del número
rst $10 ; Muestra el número en pantalla (Spectrum)
;call $bb5a ; Muestra el número en pantalla (Amstrad CPC)

ld a, b ; Vuelve a cargar el número a imprimir en A
and $0f ; Se queda con el nibble inferior
add a, '0' ; Suma el ASCII del 0 para obtener el ASCII del número
rst $10 ; Muestra el número en pantalla (Spectrum)
;call $bb5a ; Muestra el número en pantalla (Amstrad CPC)

ret ; Sale de la rutina

RST $10, en ZX Spectrum, imprime en pantalla el carácter correspondiente al código Ascii cargado en el registro A.

CALL $bb5a, hace lo propio en Amstrad CPC.