Doing Interrupts on MSX

Post Reply
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Doing Interrupts on MSX

Post by Timmy »

I have been researching the last couple of days and it seems that all the information about interrupts is distributed everywhere and therefore I'm writing this.

So, how to initialise for interrupts:

Code: Select all

void __FASTCALL__ im_init_msx()
{
        #asm
        di
        ld        hl,_msx_wyz_play
        ld        (0xfd9f+1),hl
        ld        a,195
        ld        (0xfd9f),a
        ei
        #endasm
}
0xfd9f is the system variable HTIMI, you can find it in some of the definitions but obviously I can't access them on my level. There is another hook that can be used but that's not a vertical retrace interrupt.

This interrupt is a hook of 5 bytes and sometimes there's assembly code in there. If you are only using a ROM and not disks then you can easily overwrite it.

This interrupt routine is a vertical retrace interrupt, where only register A needs to be preserved.
So for example, if I want to play with the wyzplayer, I use this: (note: a more specific version further on)

Code: Select all

void __FASTCALL__ msx_wyz_play()
{
        #asm
        push af
        call ay_wyz_play
        pop af
        #endasm
}
Finally, a feature of the vertical retrace interrupt on the MSX, is that the MSX is an international machine. Therefore it can run on 50Hz or 60Hz. I modify a routine i found (on msx.org) to determine what speed it is running on:

Code: Select all

int __FASTCALL__ msx_get_vreq()
{
    #asm
    ld hl, 50
    ld a,(0x2d)
    cp 1
    jr nc,tMSX2
tMSX1:
    ld a,(0x2b)
    bit 7,a
    ret nz
    ld l, 60
    ret
tMSX2:
    ld a,(0xffe8)
    bit 1,a
    ret nz
    ld l, 60
    ret
    #endasm
}
(All the numbers inside are system or ROM variables, except for 50 and 60). Returns 50 or 60 depending on the frequency.

I also wrote a new interrupt routine to connect the result together, in which I connect these things together (but it's very ugly). What I do here is to skip calling the play routine every 6th interrupt if it's 60Hz, in this case we can make 50Hz music for the Spectrum as well as MSX.

Code: Select all

void __FASTCALL__ msx_wyz_play()
{
        #asm
        push af
        ld a, (_wyz_freq60)
        and a
        jr z, m50hz
        ld a, (_wyz_60counter)
        and a
        jr nz, m60continue
        ld a,5
        ld (_wyz_60counter), a
        jr m50end
m60continue:
        dec a
        ld (_wyz_60counter), a
m50hz:
        call ay_wyz_play
m50end:
        pop af
        #endasm
}

where I initialised them with:
   wyz_freq60 = (msx_get_vreq() == 60);
   wyz_60counter = 5;
I have a code where I incorporated all these into the player code, but since that's the previous version, I post these separate functions here first, and perhaps I can incorporate these function into that code later.

I'd also suggest to not move the code of 60Hz and playing into the library because those functions are probably better off in the application level. The im_init_msx() is probably a candidate to move to the library, but also not necessary because this function doesn't do anything with the old hook yet.

Note: these functions are suitable for ROM mode only with no disk usage. For other case I understand that it would need a lot more work like storing and calling the old hook as well as doing an "interslot call". I have no idea how that would work but I was told it would be very complicated and very memory intensive (more than the im2 on the Spectrum).
User avatar
dom
Well known member
Posts: 2072
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

Thanks Timmy.

The interrupt setup is remarkably similar to the code here: https://github.com/z88dk/z88dk/blob/mas ... 1_init.asm - the im1_init() call is consistent across machines that allow the interrupt to be hooked in this way. Likewise there's an nmi_init() to hook into the non-maskable interrupt.

In z88dk we setup a manager so that multiple hooks can be registered - I think this is probably fairly important on VDP machines so that various display tricks can be performed.

Good tips on register preservation: the im1 manager we install actually preserves af/hl so on the MSX there's actually no need to save registers - I left them in the example to simplify it a little.

I have to admit that I've just been doing testing in ROM mode - though I think the interrupt code would still work from basic - being in ROM mode I think we can assume that no previous hooks have been registered and that we can restore by poking a ret.
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

dom wrote:The interrupt setup is remarkably similar to the code here: https://github.com/z88dk/z88dk/blob/mas ... 1_init.asm - the im1_init() call is consistent across machines that allow the interrupt to be hooked in this way. Likewise there's an nmi_init() to hook into the non-maskable interrupt.

In z88dk we setup a manager so that multiple hooks can be registered - I think this is probably fairly important on VDP machines so that various display tricks can be performed.
I know, I've based my code on that one. Note that the code that currently in master, is the H.KEYI hook. That interrupt routine is called every single time an interrupt is triggered. That means that not only vertical retrace interrupts will call it (but also line interrupts, and other hardware amongst others). For music and timing purposes, the H.TIMI is better. So it's a little more complicated but I didn't want to write everything in the first post. :)
Good tips on register preservation: the im1 manager we install actually preserves af/hl so on the MSX there's actually no need to save registers - I left them in the example to simplify it a little.
Note that for the HTIMI hook needs register A to be stored. I haven't found out what registers need to be store for HKEYI.
I have to admit that I've just been doing testing in ROM mode - though I think the interrupt code would still work from basic - being in ROM mode I think we can assume that no previous hooks have been registered and that we can restore by poking a ret.
I was told that disk based programs sometimes install a hook there to stop the disk drive from spinning after a period of time. So ROM mode programs with no disk accesses are fine.
User avatar
dom
Well known member
Posts: 2072
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

Timmy wrote:I know, I've based my code on that one. Note that the code that currently in master, is the H.KEYI hook. That interrupt routine is called every single time an interrupt is triggered. That means that not only vertical retrace interrupts will call it (but also line interrupts, and other hardware amongst others). For music and timing purposes, the H.TIMI is better. So it's a little more complicated but I didn't want to write everything in the first post. :)
Argh, I've gone number blind. That's a much better place to hook in you're right - I'll change the master code to hook there instead. I should have just read down 4 lines in the disassembly!
Note that for the HTIMI hook needs register A to be stored. I haven't found out what registers need to be store for HKEYI.
I don't think anything needs to be saved:

Code: Select all

                    ...save all registers
                    call    $fd9a                           ;[0c4a] cd 9a fd
                    in      a,($99)                         ;[0c4d] db 99
                    and     a                               ;[0c4f] a7
                    jp      p,$0d02                         ;[0c50] f2 02 0d
                    call    $fd9f                           ;[0c53] cd 9f fd
                    ei                                      ;[0c56] fb
                    ld      ($f3e7),a                       ;[0c57] 32 e7 f3
I was told that disk based programs sometimes install a hook there to stop the disk drive from spinning after a period of time. So ROM mode programs with no disk accesses are fine.
Is that transient programs or firmware/non-transient programs?
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

dom wrote:Is that transient programs or firmware/non-transient programs?
I have no idea what you mean by this question, and to be honest I don't think it matters. You either save the original hook somewhere in RAM and jump to it, or you don't. I'm also out of time and energy to go and ask around, for now. Looking up on many documents, writing and testing the code, and writing this thread already took a couple of days and I need a break for now on this subject. :)

Note: It's not uncommon that people are confused about the two hooks. Not that many documentation is available, and even the original wyzplayer code used HKEYI.

Also, good to know that HKEYI don't need register preserving.
User avatar
dom
Well known member
Posts: 2072
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

Timmy wrote:Looking up on many documents, writing and testing the code, and writing this thread already took a couple of days and I need a break for now on this subject. :)
No worries, it's really appreciated and has given me a nudge in the right direction. Looking at the address with a disk rom installed I can see there's an interslot call registered here, presumably this is the drive idle call. That's answered my question - it's set by firmware.

So, I'm going to copy whatever is there and install it as the first ISR so it will still be called. This should mean that disk access will continue to work.
stefano
Well known member
Posts: 2137
Joined: Mon Jul 16, 2007 7:39 pm

Post by stefano »

>Note: It's not uncommon that people are confused about the two hooks. Not that >many documentation is available, and even the original wyzplayer code used HKEYI.

That's why I decided to disassemble it:
https://github.com/z88dk/techdocs/blob/ ... xbasic.asm

I think Dom is already using it as a reference, line 3153 (routine labeled _KEYINT) sounds quite self documenting, once we know it is connected to the interrupt.
Post Reply