Espamática
ZX SpectrumRetroZ80 Assembly

ZX Spectrum Assembly – Utilities

This is a compilation of different routines that can be useful to deal with certain aspects of the ZX Spectrum.

The entry will be updated in the future.

Translated with DeepL.

ZX Spectrum model

On more than one occasion we will need to know which model of ZX Spectrum our program is going to run on, 16K, 48K or 128K, and in the latter case if it is in 48K or 128K mode.

The way to get the ZX Spectrum model I’m going to show is based on what Sergio thEpOpE exposed in the Basic ZX Course group on Telegram.

Depending on the value that we find in the memory position 0x4B (75), we can know if it is a 128K model or on the contrary if it is a 16/48K model. If when reading the value of this memory position we get 0x6E (110), the program is running in a 128K model, otherwise it is running in a 16/48K model.

If you are running on a 16/48K ZX Spectrum model, it is interesting to know which one it is, information that can be found at locations 0x5CB4 (23,732) and 0x5CB5 (23,733). In these addresses we will find the total amount of memory: 0x7FFF (32,767) in 16K models or 0xFFFFFF (65,535) in 48K models. As we can see, the second byte (the least significant one) has the same value, changing only the most significant one, which is located at memory location 0x5CB5 (23,733).

If we read the value of this position and get 0x7F (127) we are executing in a 16K model, if we get 0xFF (255) we are executing in a 48K model. For 128K models the value is the same as for 48K models.

If it is running on a 128K ZX Spectrum model, it is possible that it is running in 48K or 128K mode, which we can also find out. First we get the value of the memory location 0x5C3B (23,611), we divide by 16 and the result is divided by 2. If the result of this operation is equal to the integer division of the value of the memory location by 16 and the result is divided by 2, it is running in 48K mode, otherwise in 128K mode.

Pseudocode
IF INT(PEEK(23611)/16)/2 = INT(INT(PEEK(32611)/16/2)
    48K model
ELSE
    128K model
END IF

In other words, if the result of the integer division between the value of memory location 0x5C3B (23,611) and 16 is even, it is in 48K mode, otherwise it is in 128K mode.

In ZX Spectrum assembly we are allowed, with right-hand rotations, to make divisions by powers of 2 (16 = 2^4), so we would have to make four rotations to make the division by 16 and then check if the result is even, bit 0 is zero.

ASM
ld   a, $cc                   ; A = 1100 1100
; We use displacements to perform the division by 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
; Check if A is even
bit  $00, a
jr   z, isEven                ; If bit 0 is 0, it is even, it skips
isOdd:
; isOdd code
isEven:
; isEven code

If you notice, we have shifted bits 4 to 7 to bits 0 to 3 and bits 4 to 7 have been set to zero, finally checking if bit 0, formerly bit 4, is 0 if it is even or 1 if it is odd. We can save displacements and check bit 4 directly.

Based on what we have seen, the routine to find out on which ZX Spectrum model we are running a program could be the following:

ASM
; ZXModel
;
; Gets the ZX model
;
; Entry:  nothing
;
; Output: BC = ZX model
;              0 = 16K
;              1 = 48K
;              2 = 128K
;              3 = 128K in 48K mode
; Alters the value of the BC and AF registers.
ZXModel:
ld   bc, $00			; BC is used to return the value to BASIC

ld   a, ($4b)           ; A = model address
cp   $6e
jr   z, is128           ; $6E, 128, jump

is16or48:
ld   a, ($5cb5)         ; A = amount of memory, upper part
rla                     ; Rotate to the right to check if it is $BF or $FF
ret  nc                 ; NC = 16K, jump ($BF)
inc  c                  ; C+=1
ret                     ; 48K, jump

is128:
ld   c, $02             ; C = 128K
ld   a, ($5c3b)         ; A = mode address
; IF INT(PEEK 23611/16)/2 = INT(INT(PEEK 23611/16)/2) = 48K.
; Dividing by 16 is like shifting 4 times to the right,
; bit 4 is set to bit 0
; Knowing if it is divisible by two is like knowing if it is even, bit 0 = 0
; It all boils down to evaluating bit 4, if it is 0 it is 48K mode, if it is 1 it is 128K mode
bit  $04, a             ; Bit 4?
ret  nz                 ; !0, 128K mode, exit
inc  c                  ; C+=1
ret                     ; 48K mode

To test this routine, you must compile it and load it, for example, at memory address 0x7D00 (32,000), so that it will run on any ZX Spectrum model or clone. Once the routine is loaded, if you type PRINT USR 32000, the value corresponding to the ZX Spectrum model on which it is running will be printed on the screen.

The value returned by an assembly program to the ZX Spectrum BASIC is done through the BC register.

PAL vs NTSC

Sometimes, we may be interested in knowing if the ZX Spectrum on which our program is running is PAL or NTSC system.

What is the difference?

In PAL system there are 50 interruptions per second, while in NTSC system the number of interruptions per second is 60.

How does it affect us?

It can affect us a lot. If our program relies on interrupts for timing, for example by executing the enemy movement routine every five interrupts, on a PAL system the enemy will move five times per second, while on NTSC it will move six times per second, resulting in faster enemy movement on NTSC than on PAL, which can affect gameplay.

How do we find out the system?

In this case I have relied on Habisoft’s Es.pectrum emulator, which you can download from here.

This wonderful emulator emulates a wide variety of ZX Spectrum models and their clones, so it’s ideal for testing the program I’ve implemented to detect if it’s PAL or NTSC, and it’s also free.

The program is simple, activate the ZX Spectrum interrupts in mode 2 and the first time the interrupt routine is executed it sets swCount to one to indicate that the counter variable in the main loop is being incremented. The second time the interrupt routine is executed it sets swCount to two to indicate that the program should exit. The value of counter is loaded into BC so that it can be obtained from Basic.

In short, the program counts the number of times the main loop is executed between two interrupts. Based on the test results, we could say that NTSC yields values below 100, while PAL yields values above 100.

All this has to be treated with great care, if you look at the results you can see values that are at the limit.

ASM
; Program to find out if it's NTSC or PAL
; Compile: pasmo --name PAL_NTSC --tapbas PAL_NTSC.asm PAL_NTSC.tap
; Once loaded, run PRINT USR 32000 to see the result
; Not compatible with 16K model

; Results of PRINT USR 32768 using emulator Es.pectrum
;
; https://habisoft.com/espectrum/
;
; Sinclair ZX Spectrum:
;       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

; Input: prepare for interruptions
main:
ld   hl, isr            ; HL = interrupt routine address
ld   ($feff), hl        ; Store in $feff the address
ld   a, $fe             ; of routine isr
ld   i, a               ; I = $fe
im   2                  ; Go to interrupt mode 2

; Loop, waits for swCount to be 1 to start counting
; When swCount is 2, exit loop and end of program
loop:
ld   a, (swCount)       ; A = indicator to know whether to count
or   a                  ; A = 0?
jr   z, loop            ; Z = yes, jump
cp   $02                ; A = 2?
jr   z, exit            ; Z = yes, jump

ld   hl, counter        ; HL = counter address
inc  (hl)               ; Counter+=1

jr   loop               ; Loop

; Exit from the programme
; Sets interrupts to mode 1
; Sets the counter to BC so that it can be retrieved
; from BASIC
exit:
im   1                  ; Go to interrupt mode 1
ld   b, $00
ld   a, (counter)
ld   c, a               ; BC = counter, to return the result
ret                     ; Back to BASIC

; Interruptions routine
; At each interrupt it increments swCount
isr:
push hl                 ; Preserves HL

ld   hl, swCount
inc  (hl)               ; Increases the indicator to know if counting

pop  hl                 ; Retrieves HL

ei                      ; Enables interruptions
ret                     ; Exit

; Vars
swCount:                ; Indicator to know whether to count
db $00
counter:                ; Counter
db $00

Downloads

2 comentarios en «ZX Spectrum Assembly – Utilities»

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.

Descubre más desde Espamática

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

Seguir leyendo