Espamática
ZX SpectrumEnsamblador Z80Retro

Ensamblador ZX Spectrum – Utilidades

Esta es una recopilación de distintas rutinas que pueden ser útiles para tratar determinados aspectos del ZX Spectrum.

La entrada irá recibiendo actualizaciones.

Tabla de contenidos

Modelo de ZX Spectrum

En más de una ocasión necesitaremos saber en que modelo de ZX Spectrum se va a ejecutar nuestro programa, 16K, 48K o 128K, y en éste último caso si está en modo 48K o 128K.

La manera de obtener el modelo de ZX Spectrum que voy a mostrar se basa en lo expuesto por Sergio thEpOpE en el grupo de Curso Basic ZX de Telegram.

Según el valor que encontremos en la posición de memoria 0x4B (75), podemos saber si se trata de un modelo 128K o por el contrario si es un modelo de 16/48K. Si al leer el valor de esta posición de memoria obtenemos 0x6E (110), el programa se está ejecutando en un modelo de 128K, de lo contrario lo hace en un modelo 16/48K.

Si se está ejecutando en un modelo de ZX Spectrum de 16/48K, es interesante saber en cuál de ellos es, información que encontramos en las posiciones 0x5CB4 (23.732) y 0x5CB5 (23.733). En estas direcciones encontraremos la cantidad total de memoria: 0x7FFF (32.767) en modelos de 16K o 0xFFFF (65.535) en modelos de 48K. Como vemos, el segundo byte (el menos significativo) tiene el mismo valor, cambiando solo el más significativo, que se encuentra en la posición de memoria 0x5CB5 (23.733). Si leemos el valor de esta posición y obtenemos 0x7F (127) estamos ejecutando en un modelo de 16K, si obtenemos 0xFF (255) lo estamos haciendo en uno de 48K.

En los modelos de 128K el valor es el mismo que en los modelos de 48K.

Si se está ejecutando en un modelo de ZX Spectrum de 128K, es posible que se esté ejecutando en modo 48K o 128K, lo cual también podemos saber. Primero obtenemos el valor de la posición de memoria 0x5C3B (23.611), hacemos la división entre 16 y el resultado lo dividimos entre 2. Si el resultado de esta operación es igual a hacer la división entera del valor de la posición de memoria entre 16 y al resultado hacerle la división entera entre 2, se estaría ejecutando en modo 48K, de lo contrario en modo 128K.

Pseudocódigo
SI INT(PEEK(23611)/16)/2 = INT(INT(PEEK(32611)/16/2)
    modo 48K
SI NO
    modo 128K
FIN SI

O lo que es lo mismo, si el resultado de la división entera entre el valor de la posición de memoria 0x5C3B (23.611) y 16 es par está en modo 48K, de lo contrario está en modo 128K.

En ensamblador para ZX Spectrum se nos permite, con rotaciones hacia la derecha, hacer divisiones entre potencias de 2 (16 = 2^4), por lo que tendríamos que hacer cuatro rotaciones para realizar la división entre 16 y luego comprobar si es resultado es par, el bit 0 está a cero.

ASM
ld   a, $cc                   ; A = 1100 1100
; Usamos desplazamientos para realizar la división entre 16
sra  a                        ; A = A/2 =  0110 0110
sra  a                        ; A = A/4 =  0011 0011
sra  a                        ; A = A/8 =  0001 1001
sra  a                        ; A = A/16 = 0000 1100
; Comprueba si A es par
bit  $00, a
jr   z, EsPar                 ; Si el bit 0 es 0, es par, salta
EsImpar:
; Código de EsImpar
EsPar:
; Código de EsPar

Si os fijáis, hemos desplazado los bits 4 a 7 hasta los bits 0 a 3 y los 4 a 7 se han puesto a cero, comprobando finalmente si el bit 0, anteriormente bit 4, es 0 en el caso de ser par o 1 en el caso de ser impar. Nos podemos ahorrar desplazamientos y comprobar directamente el bit 4.

En base a lo que hemos visto, la rutina para averiguar en qué modelo de ZX Spectrum estamos ejecutando un programa podría ser la siguiente:

ASM
; ZXModel
;
; Obtiene el modelo de ZX
;
; Entrada: nada
;
; Salida:  BC = modelo de ZX
;              0 = 16K
;              1 = 48K
;              2 = 128K
;              3 = 128K en modo 48K
; Altera el valor de los registros BC y AF.
ZXModel:
ld   bc, $00			; BC se utiliza para devolver el valor a BASIC

ld   a, ($4b)           ; A = dirección modelo
cp   $6e
jr   z, is128           ; $6E, 128, salta

is16or48:
ld   a, ($5cb5)         ; A = cantidad memoria, parte alta
rla                     ; Rota a la derecha para comprobar si es $BF o $FF
ret  nc                 ; NC = 16K, sale ($BF)
inc  c                  ; C+=1
ret                     ; 48K, sale

is128:
ld   c, $02             ; C = 128K
ld   a, ($5c3b)         ; A = dirección de modo
; Sí INT(PEEK 23611/16)/2 = INT(INT(PEEK 23611/16)/2) = 48K.
; Dividir ente 16 es como desplazar 4 veces hacia de derecha,
; el bit 4 pasa al bit 0. 
; Saber si es divisible entre dos es como saber si es par, el bit 0 = 0
; Todo se resume en evaluar el bit 4, si es 0 es modo 48K, si es 1 es modo 128K.
bit  $04, a             ; ¿Bit 4?
ret  nz                 ; !0, modo 128K, sale
inc  c                  ; C+=1
ret                     ; Modo 48K

Para probar esta rutina, deberéis compilarla y cargarla, por ejemplo, en la dirección de memoria 0x7D00 (32.000), para que corra en cualquier modelo o clon de ZX Spectrum. Una vez cargada la rutina, si tecleáis PRINT USR 32000 saldrá impreso en pantalla el valor correspondiente al modelo de ZX Spectrum en el que se está ejecutando.

El valor devuelto por un programa ensamblador al BASIC del ZX Spectrum se hace a través del registro BC.

PAL o NTSC

En ocasiones, nos puede interesar saber si el ZX Spectrum en el que se está ejecutando nuestro programa es sistema PAL o NTSC.

¿Qué diferencia hay?

En sistema PAL se producen 50 interrupciones por segundo, mientras que en sistema NTSC el número de interrupciones por segundo asciende a 60.

¿Cómo nos afecta?

Nos puede llegar a afectar y mucho. Si nuestro programa se apoya en las interrupciones para temporizar, por ejemplo ejecutando la rutina de movimiento del enemigo cada cinco interrupciones, en un sistema PAL el enemigo se moverá cinco veces por segundo, mientras que en NTSC se moverá seis veces por segundo, resultando el movimiento del enemigo más rápido en NTSC que en PAL, pudiendo influir en la jugabilidad.

¿Cómo averiguamos el sistema?

En este caso me he apoyado en el emulador Es.pectrum de Habisoft, que podéis descargar desde aquí.

Este maravilloso emulador emula una vasta variedad de modelos de ZX Spectrum y sus clones, por lo que es ideal para probar el programa que he implementado para detectar si es PAL o NTSC, y además es gratuito.

El programa es sencillo, activa la interrupciones del ZX Spectrum en modo 2 y la primera vez que se ejecuta la rutina de interrupciones pone swCount a uno para indicar que se vaya incrementando la variable counter en el bucle principal. La segunda vez que se ejecuta la rutina de interrupciones pone swCount a dos para indicar que se debe salir el programa. El valor de counter se carga en BC para que se pueda obtener desde Basic.

En resumen, el programa cuenta las veces que se ejecuta el bucle principal entre dos interrupciones. En base a los resultados arrojados por la pruebas, podríamos decir que NTSC arroja valores inferiores a 100, mientras que PAL los arroja superiores.

Todo esto hay que tratarlo con mucho cuidado, si observáis los resultados podéis ver valores que están al límite.

ASM
; Programa para averiguar si es NTSC o PAL
; Compilar: pasmo --name PAL_NTSC --tapbas PAL_NTSC.asm PAL_NTSC.tap
; Una vez cargado, ejecutar PRINT USR 32000 para ver el resultado
; No compatible con modelo de 16K

; Resultados de PRINT USR 32000 usando emulador Es.pectrum
;
; https://habisoft.com/espectrum/
;
; Sinclair:
;       48/48+                  216
;       48 NTSC                  64
;       128                     230
;       48 ar                   ¿?
;       48 se                   215
; Timex:
;       TS 2068                  64
;       TC 2068                 216
;       TC 2048                 216
;       Komputer 2086           216
; Investrónica:
;       48+ es                  215
;       128+ es                 230
;       Inves+                  230
; Amstrad:
;       +2                      230
;       +2 es                   230
;       +2 fr                   230
;       +2 ar                   ¿?
;       +2A 4.0                 230
;       +2A 4.1                 230
;       +2A 4.0 es              230
;       +2A 4.1 es              230
;       +2A ar                  ¿?
;       +3 4.0                  230
;       +3 4.1                  230
;       +3 4.0 es               230
;       +3 4.1 es               230
; Pentagon:
;       Pentagon 128            241
;       Pentagon 512            241
;       Pentagon 1024 SL        227
; ZS Rechearch:
;       Leningrad               184
;       Scorpion ZS-256         151
;       Scorpion ZS-256 Turbo+  215
; Microdigital:
;       Tk90x pt-BR              73
;       Tk90x es-AR             233
;       Tk95  pt-BR              72
;       Tk95  es-AR             233
; ATM:
;       ATM-Turbo               215
;       ATM-Turbo 2+            176
; Otros:
;       Orel BK-08               ¿?
;       Dubna 48K                15
;       HC-91                   230
;       BK-001                  105
;       Foton-Ik03               ¿?
;       Alf TV Game (Elf)        ¿?
;       Vesta IK-30             184
; Mods:
;       +3e                     230
;       +3e es                  230
;       +2e                     230
;       +2e es                  230
;       Pentagon es             241
; Virtuales:
;       Spec256 48K             216
;       Spect256 128K           230
org  $8000

; Entrada: prepara las interrupciones
main:
ld   hl, isr            ; HL = dirección rutina interrupciones
ld   ($feff), hl        ; Guarda en $feff la dirección
ld   a, $fe             ; de la rutina isr
ld   i, a               ; I = $fe
im   2                  ; Pasa a modo 2 de interrupciones

; Bucle, espera a que swCount sea 1 para empezar a contar
; Cuando swCount es 2, sale del bucle y fin de programa
loop:
ld   a, (swCount)       ; A = indicador para saber si contar
or   a                  ; ¿A = 0?
jr   z, loop            ; Z = sí, salta
cp   $02                ; ¿A = 2?
jr   z, exit            ; Z = sí, salta

ld   hl, counter        ; HL = dirección contador
inc  (hl)               ; Contador+=1

jr   loop               ; Bucle

; Salida del programa
; Pone las interrupciones en modo 1
; Pone el contador en BC para que pueda ser recuperado
; desde BASIC
exit:
im   1                  ; Pasa a modo 1 de interrupciones
ld   b, $00
ld   a, (counter)
ld   c, a               ; BC = contador, para devolver el resultado
ret                     ; Vuelta al BASIC

; Rutina de interrupciones
; En cada interrupción incrementa swCount
isr:
push hl                 ; Preserva HL

ld   hl, swCount
inc  (hl)               ; Incrementa el indicador para saber si contar

pop  hl                 ; Recupera HL

ei                      ; Habilita interrupciones
ret                     ; Sale

; Variables
swCount:                ; Indicador para saber si contar
db $00
counter:                ; Contador
db $00

Descargas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies

Descubre más desde Espamática

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo