1

I was trying to learn how the Sega Master System works. The NMI occurs when the pause button on the console is pressed. So I wrote an NMI handler that makes a beep when the NMI occurs, then waits for the user to press A and B at the same time to return from NMI. But I've noticed that pressing the pause button again results in execution jumping back to the start of NMI (&0066) since I can hear the beep again. The NMI is the only code in my program that calls the necessary code to make the beep.

But what's really strange is that pressing A+B only once will return from NMI immediately, regardless of how many times I've triggered NMI by pressing the pause button. I would have expected to have to press the A+B buttons an equal number of times to the amount of times I "paused" the game, but that's not what happens. Instead execution returns immediately as if I only paused once (I can tell it returned immediately because in my main program the tile graphics are displaying the buttons being pressed and the response is immediate.) Not only that, the stack pointer isn't changing after returning either.

So here's my question: How come I'm getting multiple beeps which means multiple NMIs that are nested, yet pressing A+B to return acts as though I only had one NMI?

The NMI code. (At &0066 is a jump to this routine.)

nmiHandler:
    pushall       ;macro that pushes all registers
        ld a,&40
        call ChibiSound     
        
        call Delay16_Unsafe
        
        xor a
        call ChibiSound   ;these lines cause the beep, they're not really relevant.
        

WaitForUnpause:
        call Player_ReadControlsDual  ;get player 1 input into H.
        bit 5,h                       ;was B pressed?
        jr nz,WaitForUnpause          ;if not, keep waiting
            bit 4,h                   ;was A pressed?
            jr nz,WaitForUnpause      ;if not, keep waiting
    popall                            ;macro that pops all registers
    retn
4
  • 1
    What does reading the buttons do? Do two successive calls read the same state? If so, then you letting go of the buttons is much slower than unwinding all the nested calls.... Nov 12, 2021 at 1:21
  • Reading the buttons just puts the player 1 buttons into H, bit 5 represents the B button and bit 4 represents the A button. That's all Nov 12, 2021 at 2:11
  • So you return from the innermost NMI, read the buttons and they're still pressed, so you return from that NMI, read the buttons and they're still pressed ... Nov 12, 2021 at 3:36
  • If you want to unwind one NMI at a time, then you probably need to check that buttons A or B have been released before entering the loop WaitForUnpause.
    – OmarL
    Nov 12, 2021 at 15:11

1 Answer 1

4

But I've noticed that pressing the pause button again results in execution jumping back to the start of NMI (&0066)

Sure, as the PAUSE button does another MNI. After all, NMI can be stacked like any other interrupt. In fact, even more easy, as noone can stop them from occurring.

But what's really strange is that pressing A+B only once will return from NMI immediately, regardless of how many times I've triggered NMI by pressing the pause button.

Because it unravels all NMI faster than anyone can release the A+B.

Your code loops around reading the buttons and checking them, this is a few instructions, for sure not more than a few dozen machine cycles. Let's say it's 100 µs to go through that loop (it's far less in reality). So let's see what happens if PAUSE gets hit a few times:

  • All calls will stack in sequence, usually somewhere in the wait loop.

  • As soon as the wait loop detects A+B, it simply returns from the actual interrupt level, to the previous level.

  • Now it continuing the wait loop one level higher that got interrupted by NMI. It will take 100 µs (at maximum, depending where the NMI hit) until it detects that both buttons are still pressed, thus ending this loop as well, leaving that level.

  • This continues until either one of the buttons is released, or all levels are cleaned.

Cleaning the levels is rather fast, thus it will seem as if one press releases all levels at once - which in fact it does, as you never ever waited for a press to end before returning. Add the following code to do so and try again:

WaitForUnpause:             ; Wait for A and B pressed
        call Player_ReadControlsDual  ;get player 1 input into H.
        bit 5,h                       ;was B pressed?
        jr nz,WaitForUnpause          ;no -> keep waiting
        bit 4,h                       ;and A pressed?
        jr nz,WaitForUnpause          ;no -> keep waiting
                            ; A+B has been pressed, now
WaitForRelease:             ; Wait for A or B released
        call Player_ReadControlsDual  ;get player 1 input into H.
        bit 5,h                       ;was B released ?
        jr nz,LeaveNMI                ;yes -> end NMI
        bit 4,h                       ;or A released
        jr ze,WaitForRelease          ;no -> keep waiting
LeaveNMI:
        popall                        ;pop all registers
        retn                          ;return from NMI

(P.S.: this can be made a bit more compact, but that's a different story)

5
  • 1
    You can test this by requiring a sequence of presses, A then Not A then B for example. You should be able to catch most of the unwinds then. Nov 12, 2021 at 2:28
  • 1
    @DanSheppard Right, like that - it also should save the second call as well. Then again, this is meant to educate about the basic sequence issue, so a less sophisticated routine seems more appropriate, don't you think so?
    – Raffzahn
    Nov 12, 2021 at 2:32
  • I agree. I think interlocked a and b would be my test if I came up with this hypothesis to test it, because of debouncing but like you say this is about education and experimentation, not engineering. Nov 12, 2021 at 2:36
  • Interesting, I didn't think it would be that quick. Nov 14, 2021 at 2:04
  • @puppydrum64 Well, a 3.6 MHz Z80 does about 300,000 to 500,000 instructions per second (peak at 900,000 when doing single byte). So eve if you're able to press and release a button 20 times per second, the CPU still does several ten thousand instructions each. After all, speed is the very base to have moving pixels of a game fool you into seeing motion and changing scenes in real time :)
    – Raffzahn
    Nov 14, 2021 at 10:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.