module ZXLib::Sys::Macros

ZXLib::Sys Macros

Some of the macros require:

require 'zxlib/math'
# ...
  macro_import MathInt
  macro_import ::ZXLib::Math

Public Instance Methods

chan_exists(name = nil, output: de, input: nil, chan_name: 'U', buffer: 23296) click to toggle source

Looks for a ZX Spectrum CHAN entry determined by output, input and a chan_name.

  • output

    output routine address or a 16bit register holding that address except hl

  • input

    input routine address or a 16bit register holding that address

  • strm_no

    stream number (4 - 15) or 8bit register name

  • chan_name

    a channel name immediate value or a 8bit register

  • buffer

    address of buffer (5 bytes) to use, by default it's a printer buffer at 23296

Optionally give namespace label a name.

ZF=1 if found, hl points to the record address that matches ZF=0 if not found, hl points to the memory address immediately after the last record

Modifies: af, hl, bc, de

# File lib/zxlib/sys.rb, line 759
def chan_exists(name = nil, output: de, input: nil, chan_name: 'U', buffer: 23296)
    chan_name = chan_name.ord if String === chan_name
    raise ArgumentError, "output or input must not be hl register pair" if output == hl or input == hl
    isolate name, use: [:rom, :vars] do |eoc|
        input  = rom.error_j if input.nil?
        output = rom.error_j if output.nil?
                ld   a, chan_name unless chan_name == a
                ld   hl, buffer
                push hl
        oh, ol = if register?(output)
                output.split
        else
                ld   de, output
                [d, e]
        end
                ld   [hl], ol
                inc  hl
                ld   [hl], oh
                inc  hl
        ih, il = if register?(input)
                input.split
        else
                ld   de, input
                [d, e]
        end
                ld   [hl], il
                inc  hl
                ld   [hl], ih
                inc  hl
                ld   [hl], a
                pop  de            # de record address
                exx
                push hl
                ld   hl, [vars.prog]
                dec  hl
                exx                # hl' stop search address
                ld   bc, 5         # record length
                ld   hl, [vars.chan]    # CHAN
                find_record h, l
                jr   NZ, cleanup
                sbc  hl, bc        # beginning of the record found
        cleanup exx
                pop  hl
                exx
    end
end
char_ptr_from_code(chars, code=a, tt:de) click to toggle source

Calculates the address of the first byte of a character. The calculated address will be available in the hl register.

  • chars

    the address of a code=0 character as a hl register, address, label or a label pointer e.g.: [vars.chars].

  • code

    an 8-bit register or a code number (addresses and pointers works to).

  • tt

    a 16-bit register except hl.

T-States: 46, +(0|4|7|13) depending on code, +(0|10|16) depending on chars.

Modifies: af, tt, hl

# File lib/zxlib/sys.rb, line 652
def char_ptr_from_code(chars, code=a, tt:de)
  raise ArgumentError unless ((register?(chars) and chars == hl) or address?(chars)) and
                             register?(tt) and !tt.bit8? and tt != hl
  th, tl = tt.split
  isolate do
                ld   a, code unless code == a
                ld   hl, chars unless chars == hl
                3.times { rlca }
                ld   tl, a
                anda 0b00000111
                ld   th, a
                xor  tl
                ld   tl, a
                add  hl, th|tl
  end
end
create_chan_and_open(name = nil, output:, input: nil, strm_no: 4, chan_name: 'U') click to toggle source

Creates a ZX Spectrum CHAN entry and opens it as a stream #N.

  • output

    a routine address or a 16bit register holding that address, except hl.

  • input

    a routine address or a 16bit register holding that address, except hl.

  • strm_no

    a stream number (4 - 15) or an 8bit register name; nil if none of the streams should be attached.

  • chan_name

    a channel name (immediate value). if nil or 0 no channel name is being written.

Optionally give the returned namespace label a name.

Modifies: af, hl, bc, de.

# File lib/zxlib/sys.rb, line 679
def create_chan_and_open(name = nil, output:, input: nil, strm_no: 4, chan_name: 'U')
    chan_name = String === chan_name ? chan_name.ord : chan_name.to_i
    raise ArgumentError, "output or input must not be the hl register" if output == hl or input == hl
    isolate name, use: [:rom, :vars] do
        input  = rom.error_j if input.nil?
        output = rom.error_j if output.nil?
        if register?(strm_no) or pointer?(strm_no)
            unless strm_no == a
                ld   a, strm_no   # stream to open
            end
                add  a, a         # calculate the strm vector offset in hl
                # ld   hl, vars.strms.user # hl points to STRMS #0
                # adda_to h, l
                add  vars.strms.user
                ld   l, a
                ld   h, vars.strms>>8
                push hl
        end
                push input if register?(input)
                push output if register?(output)
                ld   hl, [vars.prog]  # a new channel starts below prog
                dec  hl          #
                ld   bc, 0x0005  # make space
                call rom.make_room
                inc  hl          # hl points to 1st byte of new channel data
        if register?(output)
                pop de
        else
                ld   de, output
        end
                ld   [hl], e
                inc  hl
                ld   [hl], d
        if register?(input)
                pop de
        else
                ld   de, input
        end
                                 # save address of 2nd byte of new channel data
                push hl unless strm_no.nil?
                inc  hl
                ld   [hl], e
                inc  hl
                ld   [hl], d
                inc  hl
        unless chan_name.zero?   # channel name
                ld   [hl], chan_name
        end
        unless strm_no.nil?      # open #stream_no to channel
                pop  hl          # get address of 2nd byte of output routine
                ld   de, [vars.chans] # calculate the offset to the channel data
                anda a           # and store it in de
                sbc  hl, de
                ex   de, hl      # de = our channel address - chans + 1
            if register?(strm_no) or pointer?(strm_no)
                pop  hl
            else
                ld   hl, vars.strms.user[strm_no]
            end
                ld   [hl], e      # lsb of 2nd byte of new channel data
                inc  hl
                ld   [hl], d      # msb of 2nd byte of new channel data
        end
    end
end
cursor_key_pressed?(t:b, io:self.io) click to toggle source

Test for cursor keys being pressed.

Options:

  • t

    A temporary 8-bit register.

  • io

    A label containing ula sub-label addressing ULA I/O bus.

Modifies: af, t.

Output:

  • ZF=0

    if any of the cursor keys is being pressed.

  • a

    bits b0..b3=1 if a cursor key is being pressed.

     b3  b2  b1  b0
    [] [] [] []
    
# File lib/zxlib/sys.rb, line 630
def cursor_key_pressed?(t:b, io:self.io)
    isolate do
            key_pressed? 0xf7, 0x10, io:io # key [5]
            ld   t, a
            key_pressed? 0xef, 0x1c, io:io # keys [6] [7] [8] . .
            rrca
            ora  t                         # keys [5] [6] [7] [8] .
            rrca                           # keys [←] [↓] [↑] [→]
    end
end
find_def_fn_args(argnum=b, subroutine:true, not_found:nil, cf_on_direct:false, &not_found_blk) click to toggle source

Gets a DEF FN argument value address.

Requires: macro_import MathInt.

  • argnum

    1-based argument index (0 is 256), may be a register or a number.

  • subroutine

    if true will use ret instruction.

  • not_found

    if subroutine is false and not_found is defined, the routine will jump to this address when argument was not found, otherwise success is signalled with ZF=1.

  • not_found_blk

    if subroutine is false and not_found is nil, the routine produced by the block will be run. Make sure the routine doesn't fall through though.

  • cf_on_direct

    if true and DEFADD is not defined CF will be set.

When subroutine is true or not_found is nil, the success is signalled with ZF:

  • ZF=1 if found,

  • ZF=0 if not found.

hl points to the argument value when found.

Modifies: af, hl and optionally b unless argnum == 1.

# File lib/zxlib/sys.rb, line 1031
def find_def_fn_args(argnum=b, subroutine:true, not_found:nil, cf_on_direct:false, &not_found_blk)
    isolate use: :vars do |eoc|
                    ld    b, argnum unless argnum == b or argnum == 1
                    ld    hl, [vars.defadd]
                    ld    a, h
                    ora   l
                    scf if cf_on_direct
                    jr    exit_on_zf
        if argnum != 1
        loop0       ld    a, 5              # skip argument value
                    adda_to  h, l
        end
        seek_next   ld    a, [hl]
                    inc   hl
                    cp    0x0E              # variable argument marker
        if argnum == 1
            if subroutine
                    ret   Z
            else
                    jr    Z, eoc
            end
        else
                    jr    Z, found_arg
        end
                    cp    ?).ord            # arguments terminator
        exit_on_zf  jr    NZ, seek_next
        if subroutine
                    inc   a                 # ZF=0, not found
                    ret
        elsif not_found
                    jp    not_found
        elsif block_given?
                    ns(&not_found_blk)
        else
                    inc   a                 # ZF=0, not found
                    jr    eoc if argnum != 1
        end
        if argnum != 1
        found_arg   djnz  loop0
                    ret if subroutine
        end
    end
end
find_record(th=h, tl=l) click to toggle source

Search for a record that matches a large block of memory.

  • +th|tl'+

    address of the last byte to search + 1 (preserved)

  • hl

    target

  • de

    source (preserved)

  • bc

    record length (preserved)

ZF=1 if found, hl points immediately after the record that matches, CF=0 ZF=0 if not found, hl points to the memory address immediately after the last record

Modifies: af, hl and stack

# File lib/zxlib/sys.rb, line 817
def find_record(th=h, tl=l)
    isolate do |eoc|
        seekloop1   push  bc
                    push  de
        seekloop0   ld    a, [de]
                    inc   de
                    cpi
                    jr    NZ, adjust
                    ld    a, b
                    ora   c
                    jr    NZ, seekloop0
                    pop   de
                    pop   bc           # de - preserved, bc - preserved, hl to next record after found
                    jr    eoc          # ZF=1 found
        adjust      add   hl, bc       # next record
                    pop   de
                    pop   bc
                    ld    a, h
                    exx
                    cp    th
                    exx
                    jr    C,  seekloop1  # h < h'
                    jr    NZ, eoc    # h > h'
                    ld    a, l
                    exx
                    cp    tl
                    exx
                    jr    C,  seekloop1  # l < l'
                    ora   1              # ZF = 0
    end
end
key_pressed?(line_mask=0, key_mask=0x1f, io:self.io) click to toggle source

Test for a key or keys being pressed.

  • line_mask

    Keyboard half-line mask, may be an 8 bit register. The default is 0 which means all available lines.

  • key_mask

    Key mask (b0..b4), may be an 8 bit register. The default is 0b11111 which means all keys in a half-line.

Options:

  • io

    A label containing ula sub-label addressing ULA I/O bus.

    line  key bits                     line  key bits
          b4,  b3,  b2,  b1,      b0         b4,  b3,  b2,   b1,      b0
    0xf7 [5], [4], [3], [2],     [1]   0xef [6], [7], [8],  [9],     [0]
    0xfb [T], [R], [E], [W],     [Q]   0xdf [Y], [U], [I],  [O],     [P]
    0xfd [G], [F], [D], [S],     [A]   0xbf [H], [J], [K],  [L], [ENTER]
    0xfe [V], [C], [X], [Z], [SHIFT]   0x7f [B], [N], [M], [SS], [SPACE]

Modifies: af.

Output:

  • ZF=0

    (NZ) if any of the specified keys is being pressed.

  • a

    bits b0..b4=1 if a key is being pressed at any of the specified half-line.

# File lib/zxlib/sys.rb, line 603
def key_pressed?(line_mask=0, key_mask=0x1f, io:self.io)
    isolate do
      if line_mask == 0
            xor  a
      else
            ld   a, line_mask unless line_mask == a
      end
            inp  a, (io.ula)
            cpl
            anda key_mask
    end
end
mmu128_select_bank(bank:nil, screen:nil, disable_intr:true, enable_intr:true, mmu_port_in_bc:false, sys128:self.sys128) click to toggle source

Selects an upper memory bank (0-7) and/or a screen memory page (0-1) to be displayed.

Options:

  • bank

    Selects a memory bank available at 0xC000-0xFFFF as an integer or any of the 8-bit registers except a, or indirect memory address via a 16-bit register. In this instance you should pass true to the screen: option if bit-3 of the bank should select a screen to be displayed.

  • screen

    0 - Display screen from bank 5. 1 - Display screen from bank 7. nil to preserve screen bit-3 from the bank register.

  • disable_intr

    A boolean flag indicating that the routine should disable interrupts. Provide false only if you have already disabled the interrupts.

  • enable_intr

    A boolean flag indicating that the routine should enable interrupts. Provide false if you need to perform more uninterrupted actions.

  • mmu_port_in_bc

    A boolean flag indicating if bc registers already contain the mmu_port I/O address. If not the bc will be loaded with sys128.mmu_port value.

  • sys128

    A label with mmu_port sub-label addressing the 128k MMU I/O port (0x7FFD) and mmu_value addressing memory where the 128k ROM is storing the last value output to MMU I/O port (0x5B5C).

    0xFFFF                                       screen: 0         screen: 1
    +--------+--------+--------+--------+--------+--------+--------+--------+
    | Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 |
    |        |        |(also at|        |        |(also at|        |        |
    |        |        | 0x8000)|        |        | 0x4000)|        | shadow |
    |        |        |        |        |        | screen |        | screen |
    +--------+--------+--------+--------+--------+--------+--------+--------+
    0xC000

Memory banks 1,3,5 and 7 are contended, which reduces the speed of memory access in these banks.

Modifies: af, bc, memory at sys128.mmu_value (0x5B5C).

# File lib/zxlib/sys.rb, line 1136
def mmu128_select_bank(bank:nil, screen:nil, disable_intr:true, enable_intr:true, mmu_port_in_bc:false, sys128:self.sys128)
    mask = 0b11111111
    merg = 0b00000000
    unless bank.nil?
        mask = mask & 0b11111000
        if register?(bank)
            merg = bank
        else
            merg = merg | (bank.to_i & 0b00000111)
        end
    end
    unless screen.nil?
        mask = mask & 0b11110111
        unless register?(bank)
            merg = merg | ((screen.to_i.zero? ? 0 : -1) & 0b00001000)
        end
    end
    isolate do
              ld   bc, sys128.mmu_port unless mmu_port_in_bc
              di   if disable_intr
              ld   a, [sys128.mmu_value] # previous value of port
              anda mask
              ora  merg unless merg == 0
              ld   [sys128.mmu_value],a
              ei   if enable_intr # directly after an EI, interrupts aren't accepted.
              out  (c), a
    end
end
mmu128_swap_screens(swap_bank:false, disable_intr:true, enable_intr:true, mmu_port_in_bc:false, sys128:self.sys128) click to toggle source

Swap displayed screens.

Options:

  • swap_bank

    A boolean flag indicating that the routine should additionally swap screen memory banks at 0xC000. For this to have a desired effect bank 5 or 7 should have been previously selected e.g. with Macros.mmu128_select_bank.

  • disable_intr

    A boolean flag indicating that the routine should disable interrupts. Provide false only if you have already disabled the interrupts.

  • enable_intr

    A boolean flag indicating that the routine should enable interrupts. Provide false if you need to perform more uninterrupted actions.

  • mmu_port_in_bc

    A boolean flag indicating if bc registers already contain the mmu_port I/O address. If not the bc will be loaded with sys128.mmu_port value.

  • sys128

    A label with mmu_port sub-label addressing the 128k MMU I/O port (0x7FFD) and mmu_value addressing memory where the 128k ROM is storing the last value output to MMU I/O port (0x5B5C).

Modifies: af, bc, memory at sys128.mmu_value (0x5B5C).

# File lib/zxlib/sys.rb, line 1181
def mmu128_swap_screens(swap_bank:false, disable_intr:true, enable_intr:true, mmu_port_in_bc:false, sys128:self.sys128)
    swap_bits = if swap_bank then 0b00001010 else 0b00001000 end
    isolate do
              ld   bc, sys128.mmu_port unless mmu_port_in_bc
              di   if disable_intr
              ld   a, [sys128.mmu_value] # previous value of port
              xor  swap_bits
              ld   [sys128.mmu_value],a
              ei   if enable_intr # directly after an EI, interrupts aren't accepted.
              out  (c), a
    end
end
move_basic_above_scld_screen_memory(check_ensure:false) click to toggle source

Moves Basic program and variables above the screen 1 (to 0x7B00).

  • check_ensure

    when true checks if a call to MAKE-ROOM is needed.

Modifies: af, bc, de, hl.

# File lib/zxlib/sys.rb, line 1200
def move_basic_above_scld_screen_memory(check_ensure:false)
    isolate use: [:memT2k, :vars, :rom] do |eoc|
            ld   hl, memT2k.rambot
            ld   de, [vars.prog]
            dec  de
            cp   a
            sbc  hl, de
            jr   C, eoc if check_ensure
            jr   Z, eoc if check_ensure
            ld16 bc, hl
            ex   de, hl
            call rom.make_room
    end
end
read_arg_string(adh=d, adl=e, lenh=b, lenl=c) click to toggle source

Reads a string address and its length from a ZX Basic's stringish FP-value.

hl

must point to the 1st byte of the FP-value.

adh

most significant byte address output register.

adl

least significant byte address output register.

lenh

most significant byte length output register.

lenl

least significant byte length output register.

hl will point to the last byte of the FP-value.

T-States: 52.

Modifies: hl, adh, adl, lenl, lenh

# File lib/zxlib/sys.rb, line 995
def read_arg_string(adh=d, adl=e, lenh=b, lenl=c)
    raise ArgumentError if [adh, adl, lenl].any? {|r| [h, l].include?(r) }
    isolate do
                    inc   hl
                    ld    adl, [hl]
                    inc   hl
                    ld    adh, [hl]
                    inc   hl
                    ld    lenl, [hl]
                    inc   hl
                    ld    lenh, [hl]
    end
end
read_integer32_value(t3=d, t2=e, t1=b, t0=c) click to toggle source

Reads a 32-bit integer from a ZX Basic's FP-value.

Requires: macro_import ::ZXLib::Math.

hl

must point to the 1st byte of the FP-value.

t3

most significant 8-bit output register.

t2

8-bit output register.

t1

8-bit output register.

t0

least significant 8-bit output register.

The result is being loaded into t3|t2|t1|t0. CF=1 signals that the FP-value is too big to fit into a 32-bit integer. ZF=1 signals that the FP-value is positive. In this instance accumulator a = 0. ZF=0 signals that the FP-value is negative. In this instance accumulator a = 0xFF. If the value is negative the integer WON'T BE a twos complement number.

hl will always point to the last byte of the FP-value.

Modifies: af, af', hl, bc, de.

# File lib/zxlib/sys.rb, line 960
def read_integer32_value(t3=d, t2=e, t1=b, t0=c)
    # legacy arguments handling (th, tl)
    if [t3, t2].all? {|t| [bc, de].include?(t)}
        t1, t0 = t2.split
        t3, t2 = t3.split
    end
    raise ArgumentError unless [t3,t2,t1,t0].uniq.size == 4 and
                               [t3,t2,t1,t0].all? {|t| [b,c,d,e].include?(t)}
    isolate do
                    ld    a, [hl]
                    inc   hl
                    ld    t3, [hl]
                    inc   hl
                    ld    t2, [hl]
                    inc   hl
                    ld    t1, [hl]
                    inc   hl
                    ld    t0, [hl]
                    fp_to_integer32 t3, t2, t1, t0, exp:a
    end
end
read_integer_value(th=d, tl=e, sgn=c, normal_negative:false, t:a) click to toggle source

Reads a signed integer from a ZX Basic's FP-value.

hl

must point to the 1st byte of the FP-value.

th

most significant byte output register.

tl

least significant byte output register.

sgn

sign byte register.

normal_negative

if the twos complement number should be normalized.

t

a temporary register, used only with normal_negative = true.

If t is the accumulator register the a' register is being used.

The result is being loaded into th|tl and sgn. ZF=0 (NZ) signals the FP-value is not an integer. hl will always point to the last byte of the FP-value.

T-States: 59, normal_negative=true: (t=a') 91, (t=r) 87.

Modifies: af, hl, th, tl and sgn, optionally: t or af'.

# File lib/zxlib/sys.rb, line 867
def read_integer_value(th=d, tl=e, sgn=c, normal_negative:false, t:a)
    raise ArgumentError if [h,l,ixh,ixl,iyh,iyl,a].include?(tl) or !register?(tl) or
                           [h,l,ixh,ixl,iyh,iyl,a].include?(th) or !register?(th) or
                           [h,l,ixh,ixl,iyh,iyl,a].include?(sgn) or !register?(sgn) or
                           (register?(t) and [h,l,th,tl,sgn,ixh,ixl,iyh,iyl].include?(t)) or !register?(t)
    isolate do |eoc|
        if normal_negative
            if t == a
                    ld    a, [hl]
                    ex    af, af
            else
                    ld    t, [hl]
            end
                    inc   hl
                    ld    sgn, [hl]
                    inc   hl
                    ld    a, [hl]
                    inc   hl
                    xor   sgn
                    sub   sgn
                    ld    tl, a
                    ld    a, [hl]
                    inc   hl
                    adc   a, sgn
                    xor   sgn
                    ld    th, a
            if t == a
                    ex    af, af
            else
                    ld    a, t
            end
                    ora   [hl]
        else
                    ld    a, [hl]
                    inc   hl
                    ld    sgn, [hl]
                    inc   hl
                    ld    tl, [hl]
                    inc   hl
                    ld    th, [hl]
                    inc   hl
                    ora   [hl]
        end
    end
end
read_positive_int_value(th=d, tl=e) click to toggle source

Reads a positive integer from a ZX Basic's FP-value.

hl

must point to the 1st byte of the FP-value.

th

most significant byte output register.

tl

least significant byte output register.

The result is being loaded into th|tl. ZF=0 (NZ) signals the FP-value is not a positive integer. hl will always point to the last byte of the FP-value.

T-States: 59.

Modifies: af, hl, th and tl.

# File lib/zxlib/sys.rb, line 926
def read_positive_int_value(th=d, tl=e)
    raise ArgumentError if [h,l,ixh,ixl,iyh,iyl,a].include?(tl) or [h,l,ixh,ixl,iyh,iyl,a].include?(th)
    isolate do
                    ld    a, [hl]
                    inc   hl
                    ora   [hl]
                    inc   hl
                    ld    tl, [hl]
                    inc   hl
                    ld    th, [hl]
                    inc   hl
                    ora   [hl]
    end
end
report_error(error) click to toggle source

Returns to ZX Basic with the error report.

  • error

    Error report signature as a number 0..9 or a letter A..R

# File lib/zxlib/sys.rb, line 507
def report_error(error)
    errno = [*'0'..'9', *'A'..'R'].index(error.to_s.upcase[0])
    raise ArgumentError unless errno
    isolate do
        err     rst  0x08
                db   errno - 1
    end
end
report_error_unless(condition, error) click to toggle source

Returns to ZX Basic with the error report if condition is NOT met.

  • condition

    NZ, Z, NC, C, PO, PE, P, M

  • error

    Error report signature as a number 0..9 or a letter A..R

# File lib/zxlib/sys.rb, line 492
def report_error_unless(condition, error)
    raise ArgumentError unless Condition === condition
    isolate do |eoc|
        if condition.jr_ok?
                jr   condition, eoc
        else
                jp   condition, eoc
        end
        err     report_error error
    end
end
restore_rom_interrupt_handler(enable_intr:true) click to toggle source

Restore interrupt handler ZX Spectrum ROM's standard IM1 mode.

  • enable_intr

    If true invoke ei instruction at the end.

T-states: 28/24

Modifies: a, i.

# File lib/zxlib/sys.rb, line 572
def restore_rom_interrupt_handler(enable_intr:true)
    isolate do
            im1
            ld  a, 0x3F
            ld  i, a
            ei if enable_intr
    end
end
return_with_fp(pop_ret_address:true, rom:self.rom, restore_iy:self.vars_iy, restore_hl_alt:rom.end_calc) click to toggle source

Creates a routine that returns to the calling ZX-Basic's USR function an FP value.

When returning from a user code an integer from the bc register is being used as a USR function return value via STACK-BC routine.

To return a floating point value use this routine instead of invoking ret.

The FP value to be returned should be held in a, e, d, c, b registers.

Options:

  • pop_ret_address

    Set to false if the STACK-BC return address was already fetched from the machine stack.

  • rom

    A namespace label containing the ROM routine addresses as sub-labels.

  • restore_iy

    A value to restore iy register to. Set to nil to not restore iy.

  • restore_hl_alt

    A value to restore hl' register to. Set to nil to not restore hl'.

# File lib/zxlib/sys.rb, line 1090
def return_with_fp(pop_ret_address:true, rom:self.rom, restore_iy:self.vars_iy, restore_hl_alt:rom.end_calc)
    isolate do
                    pop   hl if pop_ret_address
                    ld    iy, restore_iy if restore_iy
        if restore_hl_alt
                    exx
                    ld    hl, restore_hl_alt
                    exx
        end
                    call  rom.stk_store
                    rst   rom.fp_calc
                    db    0x38 # end-calc  make HL = STKEND-5
                    ret
    end
end
setup_custom_interrupt_handler(handler, enable_intr:true, vector_page:0x3B) click to toggle source

Creates a routine that sets up custom interrupt handler using ZX Spectrum ROM's unused space as a IM2 mode jump table.

  • handler

    One of the 16bit register pair or an address or a pointer to the address of the handler routine.

  • enable_intr

    If true invoke ei instruction at the end.

This routine uses a mode 2 interrupt. In this mode the address of the interrupt routine is formed in the following way: an 8-bit value found on the BUS (which in most ZX Spectrum models is 255) is being added to register i x 256, which form a vector table address. A word (two bytes) is being read from that address, providing the address where the call to the interrupt routine is made to.

This routine makes these assumptions about the machine:

  • The byte at the memory address 0 contains 243.

  • The 2 consecutive bytes at the memory address vector_page x 256 + 8-bit BUS value and the following one, both contain 255.

  • The RAM memory between 65524 and 65535 will not be used for other purposes.

NOTE

The assumptions are true for most ZX Spectrum models and clones including Pentagon machines and Timex TC2048. However they are not true for Timex TC2068 or TS2068.

Options:

  • vector_page

    A most significant byte of the interrupt vector table address loaded into i register. The default is 0x3B so the address of the routine is found at 0x3BFF in most ZX Spectrum models.

Modifies: a, i, hl if handler is not a register pair.

# File lib/zxlib/sys.rb, line 545
def setup_custom_interrupt_handler(handler, enable_intr:true, vector_page:0x3B)
    isolate do
                    ld  a, 0x18          # 18H is jr
                    ld  [0xFFFF], a
                    ld  a, 0xC3          # C3H is jp
                    ld  [0xFFF4], a
        set_handler label                # set handler part (may be used to only change the routine address)
        if [bc, de, hl, sp, ix, iy].include?(handler)
                    ld  [0xFFF5], handler
        else
                    ld  hl, handler
                    ld  [0xFFF5], hl
        end
                    ld  a, vector_page   # Supported by ZX Spectrum 128, +2, +2A, +3 and probably most clones.
                    ld  i, a             # load the accumulator with FF filled page in rom.
                    im2
                    ei if enable_intr
    end
end