Одна из ключевых функций мапперов это переключить программных банков памяти и банков памяти с графикой CHR. Если брать для сравнение маппер MMC1 то он переключает графику постранично, по факту переключая полностью. MMC3 имеет 5 банков CHR памяти и позволяет их переключать по отдельности не переключая полностью pattern table.
MMCC3 маппит память следующим образом 2кб, 2кб, 1кб, 1кб, 1кб, 1кб. В документации nesdev есть следующая таблица.
7 bit 0
---- ----
CPMx xRRR
||| |||
||| +++- Specify which bank register to update on next write to Bank Data register
||| 000: R0: Select 2 KB CHR bank at PPU $0000-$07FF (or $1000-$17FF)
||| 001: R1: Select 2 KB CHR bank at PPU $0800-$0FFF (or $1800-$1FFF)
||| 010: R2: Select 1 KB CHR bank at PPU $1000-$13FF (or $0000-$03FF)
||| 011: R3: Select 1 KB CHR bank at PPU $1400-$17FF (or $0400-$07FF)
||| 100: R4: Select 1 KB CHR bank at PPU $1800-$1BFF (or $0800-$0BFF)
||| 101: R5: Select 1 KB CHR bank at PPU $1C00-$1FFF (or $0C00-$0FFF)
||| 110: R6: Select 8 KB PRG ROM bank at $8000-$9FFF (or $C000-$DFFF)
||| 111: R7: Select 8 KB PRG ROM bank at $A000-$BFFF
||+------- Nothing on the MMC3, see MMC6
|+-------- PRG ROM bank mode (0: $8000-$9FFF swappable,
| $C000-$DFFF fixed to second-last bank;
| 1: $C000-$DFFF swappable,
| $8000-$9FFF fixed to second-last bank)
+--------- CHR A12 inversion (0: two 2 KB banks at $0000-$0FFF,
four 1 KB banks at $1000-$1FFF;
1: two 2 KB banks at $1000-$1FFF,
four 1 KB banks at $0000-$0FFF)
Записью в порт $8000 (и вообще в любой другой четный адрес $8002, $8004 и т.д. до адреса регистра $9FFE) мы записываем "флаг" составленный по таблице выше, и указываем на банк памяти который стоит переключить. Далее в нечетные регистры маппера $8001 мы записываем "номер" страницы. Слово номер немного не подходит по смыслу но упрощает понимание механизма.
Прежде чем мы перейдем непосредственно к переключению графики, мы должны снова отредактировать конфигурацию линкера, разбить CHR секцию памяти на 5 частей и выделить для каждой части по 16 страниц графики. Я выбрал конфигурацию 128kb/128kb программной и графической памяти, из этого следует что разделив 128/(2+2+1+1+1+1)=16 проще 128/8=16 страниц памяти. Отдельно для каждой страницы 2*16 + 2*16 + 1*16 + 1*16 + 1*16 + 1*16 = 128. Ну все проверили все правильно. Смотрим на конфиг который получился для линкера
Конфигурация полностью
MEMORY {
HEADER: start=$00, size=$10, fill=yes, fillval=$00;
ZEROPAGE: start=$10, size=$ff;
STACK: start=$0100, size=$0100;
OAMBUFFER: start=$0200, size=$0100;
RAM: start=$0300, size=$0500;
ROM_0: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D0;
ROM_1: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D1;
ROM_2: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D2;
ROM_3: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D3;
ROM_4: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D4;
ROM_5: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_6: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_7: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_8: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_9: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_10: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_11: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_12: start = $8000, size = $2000, type = ro, file = %O, fill=yes, fillval = $D5;
ROM_H: start = $A000, size = $4000, type = ro, file = %O, fill=yes, fillval = $CC;
PRG_FIXED: start = $E000, size = $2000, type = ro, file = %O, fill = yes, fillval = $FF;
CHR: start=$1000, size=$20000;
CHR_2KB_1: start=$0000, size=$32768;
CHR_2KB_2: start=$0800, size=$32768;
CHR_1KB_1: start=$1000, size=$16384;
CHR_1KB_2: start=$1400, size=$16384;
CHR_1KB_3: start=$1800, size=$16384;
CHR_1KB_4: start=$1C00, size=$16384;
}
SEGMENTS {
HEADER: load=HEADER, type=ro, align=$10;
ZEROPAGE: load=ZEROPAGE, type=zp;
STACK: load=STACK, type=bss, optional=yes;
OAM: load=OAMBUFFER, type=bss, optional=yes;
BSS: load=RAM, type=bss, optional=yes;
DMC: load=ROM_H, type=ro, align=64, optional=yes;
CODE_1: load=ROM_0, type=ro, align=$0100;
CODE_2: load=ROM_1, type=ro, align=$0100;
CODE_3: load=ROM_2, type=ro, align=$0100;
CODE_4: load=ROM_3, type=ro, align=$0100;
CODE_5: load=ROM_4, type=ro, align=$0100;
CODE_6: load=ROM_5, type=ro, align=$0100;
CODE_7: load=ROM_6, type=ro, align=$0100;
CODE_8: load=ROM_7, type=ro, align=$0100;
CODE_9: load=ROM_8, type=ro, align=$0100;
CODE_10: load=ROM_9, type=ro, align=$0100;
CODE_11: load=ROM_10, type=ro, align=$0100;
CODE_12: load=ROM_11, type=ro, align=$0100;
CODE_13: load=ROM_12, type=ro, align=$0100;
CODE: load=PRG_FIXED, type=ro, align=$0100;
RODATA: load=ROM_12, type=ro, align=$0100;
VECTORS: load=PRG_FIXED, type=ro, start=$FFFA;
CHR_2KB_1_1: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_2: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_3: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_4: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_5: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_6: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_7: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_8: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_9: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_10: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_11: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_12: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_13: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_14: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_15: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_1_16: load=CHR_2KB_1, type=ro, align=16, optional=yes;
CHR_2KB_2_1: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_2: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_3: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_4: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_5: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_6: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_7: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_8: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_9: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_10: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_11: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_12: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_13: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_14: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_15: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_2KB_2_16: load=CHR_2KB_2, type=ro, align=16, optional=yes;
CHR_1KB_1_1: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_2: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_3: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_4: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_5: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_6: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_7: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_8: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_9: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_10: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_11: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_12: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_13: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_14: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_15: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_1_16: load=CHR_1KB_1, type=ro, align=16, optional=yes;
CHR_1KB_2_1: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_2: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_3: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_4: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_5: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_6: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_7: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_8: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_9: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_10: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_11: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_12: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_13: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_14: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_15: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_2_16: load=CHR_1KB_2, type=ro, align=16, optional=yes;
CHR_1KB_3_1: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_2: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_3: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_4: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_5: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_6: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_7: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_8: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_9: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_10: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_11: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_12: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_13: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_14: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_15: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_3_16: load=CHR_1KB_3, type=ro, align=16, optional=yes;
CHR_1KB_4_1: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_2: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_3: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_4: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_5: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_6: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_7: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_8: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_9: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_10: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_11: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_12: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_13: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_14: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_15: load=CHR_1KB_4, type=ro, align=16, optional=yes;
CHR_1KB_4_16: load=CHR_1KB_4, type=ro, align=16, optional=yes;
}
Немного может ввести в заблуждение того что адреса банков разные.
CHR_2KB_1: start=$0000, size=$32768;
CHR_2KB_2: start=$0800, size=$32768;
CHR_1KB_1: start=$1000, size=$16384;
CHR_1KB_2: start=$1400, size=$16384;
CHR_1KB_3: start=$1800, size=$16384;
CHR_1KB_4: start=$1C00, size=$16384;
Размеры банков вычисляются путем умножение количество страниц на размер банка, к примеру для 2кб банка это будет 16*2048 = 32768 и соответственно 16*1024 = 16384.
Далее я сделал по 16 chr файлов для каждого банка и сложил их в отдельной директории, в отдельный файл asm определил все секции и подключил их. Теперь есть большая база графики.
Для большей визуализации банков приведу пример chr из игры super mario bros если бы он был разделен на банки.
![](https://habrastorage.org/getpro/habr/upload_files/1dd/5ae/fa0/1dd5aefa038cc0225bc488dad1a66ce4.jpg)
Видите каждый банк вы можете просто заменить своей страницей и составить новый графический файл.
Теперь, когда мы разобрались в конфигурации, банках и страницах. Нам необходимо понять еще очень важную вещь как расположены страницы в памяти, в памяти они по факту склеиваются в один файл. По этому мы должны при переключение указать начало графики в байтах к примеру $20 (32) и тут если выбран банк 2кб то будут взята графика в размере 2кб от данного адреса то есть с $20-$22 (32-34 байта). Есть еще одна особенность мы должны указывать начальный адрес страницы полностью.
Довольно длинное вступление и подготовка, и так, для того что бы переключить chr мы просто должны сделать следующее:
.proc startChr
LDA #$00 ; 1 2kb банка на первую страницу
STA $8000
LDX #$00 ; оно как бы по умолчанию но я просто привел пример в статье
STX $8001
LDA #$01 ; 2 2kb банка
STA $8000
LDX #$02 ; указание на вторую страницу 00 + 2кб = 02
STX $8001
LDA #$02 ; 3 страниц 1kb памяти
STA $8000
LDX #$40 ; указывает на первую страницу 1кб 0 + 32 + 32 = 64 в хекс $40
STX $8001
RTS
.endproc
В качестве заключения как обычно, ссылки на предыдущие статьи об MMC3: