MMC3的IRQ中断
周三 11 一月 2023原文作者: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
(D3
、D4
),这样倒数计数才会进行。
$C001
- IRQ计数锁存器 - 写入你想要的值,也就是用来倒数的值;而不是直接往IRQ计数寄存器
里写值。
$E000
- IRQ禁用/确认器 - 往这里写任意值,都能禁用IRQ的触发,但也能将IRQ计数器锁存器
中的值复制到实际的IRQ计数器
。一旦确认了一次IRQ,就会保证发生中断。
$E001
- IRQ启动器 - 往这里写入任意值,都能启用IRQ的触发。
因此,大多数的商业游戏都是这样使用这些寄存器:
当你想设置的IRQ时:(放在你的NMI/vblank程序中)
- 写
$E000
以确认任何当前待定的中断 - 向
$C000
和$C001
写入你要等待的扫描线数 - 再次写
$E000
以锁定倒数值 - 写
$E001
以启用IRQ计数器
一旦你的IRQ发生了:(在你的IRQ程序中)
- 写
$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