MMC3的IRQ中断

原文作者:Allan Blomquist

iNES Mapper #4 (MMC3)提供了一种方便的方法,来对PPU进行帧间改变,以实现NES游戏中的分屏卷动等“特殊效果”。与其使用Sprite-0 Hits去择机写入PPU硬件,不如使用IRQ。这可以使CPU在一帧内就做完工作,而不是浪费时间去轮询Sprite-0 Hits标志。 MMC3有4个寄存器,是MMC3为IRQ触发而设的:

$C000 - IRQ计数器 - Mapper用它来倒计时以触发IRQ。每一条扫描线,该值减一;当它到0时,IRQ就触发。

注意: 背景和精灵必须启用,在$2001(D3D4),这样倒数计数才会进行。

$C001 - IRQ计数锁存器 - 写入你想要的值,也就是用来倒数的值;而不是直接往IRQ计数寄存器里写值。

$E000 - IRQ禁用/确认器 - 往这里写任意值,都能禁用IRQ的触发,但也能将IRQ计数器锁存器中的值复制到实际的IRQ计数器。一旦确认了一次IRQ,就会保证发生中断。

$E001 - IRQ启动器 - 往这里写入任意值,都能启用IRQ的触发。

因此,大多数的商业游戏都是这样使用这些寄存器:

当你想设置的IRQ时:(放在你的NMI/vblank程序中)

  1. $E000以确认任何当前待定的中断
  2. $C000$C001写入你要等待的扫描线数
  3. 再次写$E000以锁定倒数值
  4. $E001以启用IRQ计数器

一旦你的IRQ发生了:(在你的IRQ程序中)

  1. $E000以确认IRQ并禁用IRQ计数器

你在游戏使用MMC3的IRQ之前,有几件事情你必须设置好。首先,你要设置好ROM结尾中断向量表里的IRQ向量,指向IRQ发生时你想被调用的代码。IRQ向量的位置在$FFFE,在NMI向量和RESET向量的右边。当一个IRQ中断触发时,CPU会跳到的16位地址$FFFE上,并且CPU标志被自动存下来,在IRQ程序返回时自动恢复。但是CPU的寄存器不会被保存,所以你必须确保在IRQ程序结束时,它们会被恢复原样(开始时应该入栈AXY寄存器,结束时出栈YXA,注意顺序)。

第二件你必须做的事情,使用MMC3的IRQ,就要阻止其他的在NES上产生的IRQ中断。NES有一个“帧计数器”,默认会在每帧结束时产生IRQ中断,非常像NMI。如果不禁用这个,你的IRQ代码次数,会比你要的更多,从而搞砸你的游戏。禁用帧计数器的IRQ中断,只需要在你的程序开始时,把$40写入到$4017

最后,为了能够使用IRQ,你一定要告诉NES的CPU,你有实际确认它们,而不是忽略了它们,因为它在默认情况下(这也是为什么你不必担心在简单的游戏里,帧计数器的IRQ的发生 - CPU实际上忽略了它们)会启用的IRQ。你必须用“CLI”指令清除CPU中的中断禁用标志。在某些时刻,如果你想重新禁用它们,要用“SEI”。

下面是一个在nbasic中使用MMC3 IRQ中断的最小例子。 它使用一个IRQ中断来改变屏幕上一半的背景颜色,而不需要使用Sprite-0-Hits来确定你的位置。

注意: MMC3 IRQ的时机安排是大多数模拟器没有真正做到的事情(例如,一个倒数值X,可能会在扫描线X-1或X+1上,实际触发一个IRQ中断,这取决于你使用的模拟器)。如果你使用IRQ,请在各种模拟器多测试几遍你的游戏。

;//nesasm文件头
asm
    .inesprg 1 ;//1个PRG段
    .ineschr 0 ;//0个CHR段
    .inesmir 0 ;//镜像类型0
    .inesmap 4 ;//内存使用mapper 4 (MMC3)
    .org $C000
    .bank 0
endasm

start:

    gosub vwait
    set $2000 $80           ;// 打开NMI
    set $2001 $08           ;// 打开BG使MMC3能够计数

    set $4017 $40           ;// 禁用帧计数器IRQ
asm
    cli                     ;// 启用IRQ处理过程
endasm

gameloop:
    goto gameloop

;// NMI程序
nmi:
    set $2006 $3F
    set $2006 $00           ;// 设置背景色为红色
    set $2007 $06

    set $E000 $01
    set $C000 $78           ;// 设置MMC3让IRQ中断发生在
    set $C001 $78           ;// 120条扫描线之后
    set $E000 $01
    set $E001 $01

    set $2006 $00
    set $2006 $00

    set $2001 $08           ;// 打开BG使MMC3能够计数

    resume

;// IRQ程序
irq:

asm
    pha
    txa
    pha                     ;// 保存寄存器A,X和Y。你必须这么做,因为
    tya                     ;// IRQ中断了你的主线程代码。改变你的
    pha                     ;// 寄存器值,对主线程来说,这是可是
                            ;// 不太好的...
endasm                      

    set $2001 $00           ;// 强制关闭背景,迫使愚蠢的模拟器意识到我在改变背景色

    set $2006 $3F
    set $2006 $00           ;// 设置背景色为绿色
    set $2007 $0A

    set $E000 $01           ;// 确认IRQ并禁止进一步产生IRQ

    set $2006 $00
    set $2006 $00

asm
    pla
    tay                     ;// 取回你的寄存器值
    pla
    tax
    pla
endasm

    resume


;//等待屏幕刷新
vwait:
    asm
        lda $2002
        bpl vwait   ;//等待回溯开始
    vwait_1:
        lda $2002
        bmi vwait_1 ;//等待回溯结束
    endasm
    return

;//文件脚部
asm
    ;//跳转表指向NMI,Reset和IRQ开始位置
    .bank 1
    .org $fffa
    .dw nmi, start, irq
endasm