FC开发小记之Mapper

在一个阳光明媚的早晨,小明正开心的为他的游戏角色添加新动作,想想马上就可以完成这个角色还有点小激动了。然而没过多久小明一声惨叫引来了众同事,原来是小明编译游戏遇到了问题:


热心的围观同事立即讨论得出一个结论“游戏程序容量存储空间不足”。小明一脸无语的想:这不是明摆着的事嘛!然而手上却没停下来立即查找资料。没多久小明就找到了与当前容量问题相关的FC Mapper一些信息:

1、FC Mapper是FC游戏厂商为了突破FC机能限制设计的一种扩展方案(默认FC的PRG容量只有16K,CHR容量只有8K)。FC游戏厂商根据自身需求设计Mapper方案(PRG容量扩容、CHR图案扩容、添加特殊音频芯片等)。在实体卡带都有一个Mapper控制芯片。


图示:004号的任天堂的MMC3方案_PRG、CHR容量加大


图示:005号的任天堂的MMC5方案_PRG、CHR容量加大、音频处理

2、FC的Mapper方案现已多达256种,以下是Mapper方案列表:

图示:Mapper方案列表
3、Mapper控制芯片是通过提供指定端口来实现特殊功能的(如切换代码块、切换CHR图案等)
切换代码块原理(以MMC3为例)

图示:代码段交换示意图

首先通过$8000端口赋值指定下标,标识FC主机该地址范围内的程序数据将被切换。其次通过$8001端口赋值指定下标,将卡带该地址范围内的程序数据填充替换到$8000端口指定的FC主机地址里。

切换CHR原理(以MMC3为例)

图示:CHR图案交换示意图

首先通过$8000端口赋值指定下标,标识FC主机的PPU图案表该地址范围内的图案数据将被切换。其次通过$8001端口赋值指定下标,将卡带CHR该地址范围内的图案数据填充替换到$8000指定的FC主机的PPU图案地址里。
注: 当$8000端口指定的值为0或1时,这时$8001端口则只能指定图案表A、B…的第1位、第3位下标(如图案表A的0,2下标,图案表B的4、6下标).

小明根据自己的需求决定使用004号“任天堂MMC3”的Mapper方案,该方案可以将PRG容量由16K扩大到512K,CHR容量从8K扩大到256K。下面是他的开发步骤:
1,配置文件:确认Mapper方案、PRG容量、CHR容量
SYMBOLS {
__STACKSIZE__ = $0500; # 5 pages stack
//设置MAPPER代号
NES_MAPPER = 4;
//设置PRG容量,1页16K(当前为32*16=512K)
NES_PRG_BANKS = 32;
//设置CHR容量,1页8K(当前1*8=256K)
NES_CHR_BANKS = 32;
//设置镜象(0:横向,1:竖向)
NES_MIRRORING = 0;
}

2,配置文件:确认PRG内存片段
指定程序片段存储归属内存地址
MEMORY {
PRG0: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG1: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG2: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG3: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
PRG4: start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
… …
PRG5: start = $a000, size = $2000, file = %O ,fill = yes, define = yes;
PRG6: start = $c000, size = $2000, file = %O ,fill = yes, define = yes;
PRG7: start = $e000, size = $1fca, file = %O ,fill = yes, define = yes;
}
指定程序片段名
SEGMENTS {
CODE0: load = PRG0, type = ro, define = yes;
CODE1: load = PRG1, type = ro, define = yes;
CODE2: load = PRG2, type = ro, define = yes;
CODE3: load = PRG3, type = ro, define = yes;
CODE4: load = PRG4, type = ro, define = yes;
CODE5: load = PRG5, type = ro, define = yes;
CODE6: load = PRG6, type = ro, define = yes;
… …
STARTUP: load = PRG7, type = ro, define = yes;
}

3,程序代码段
#program dataseg(“片段名”)
#program rodataseg(“片段名”)
#program codeseg(“片段名”)

4,切换程序代码段示例
*((unsigned char*)0x8000) = 6;
*((unsigned char*)0x8001) = 0;
//将$8000-9FFF地址里的程序代码切换为CODE0代码段
*((unsigned char*)0x8000) = 7;
*((unsigned char*)0x8001) = 1;
//将$A000-BFFF地址里的程序代码切换为CODE1代码段

5,切换CHR示例
*((unsigned char*)0x8000) = 0;
*((unsigned char*)0x8001) = 0;
//将PPU的$0000 – $07FF地址里的图案数据切换为“图案表A下标为0和1的”2K图案数据。
*((unsigned char*)0x8000) = 1;
*((unsigned char*)0x8001) = 2;
//将PPU的$0800 – $0FFF地址里的图案数据为“图案表A下标为2和3的”2K图案数据。
*((unsigned char*)0x8000) = 2;
*((unsigned char*)0x8001) = 4;
//将PPU的$1000 – $13FF地址里的图案数据为“图案表B下标为4的”1K图案数据。

经过上面的调整,再也不用为代码量发愁,图形画面也变丰富了,问题也得到了圆满解决。热衷分享的小明还把他找到资料链接分享了出来:
http://wiki.nesdev.com/w/index.php/Mapper
http://nesdev.com/NESDoc.pdf(第38页)