How do I set up MMC3?
Moderator: Moderators
- BioMechanical Dude
- Formerly AlienX
- Posts: 137
- Joined: Fri Apr 18, 2014 7:41 am
- Location: Bulgaria
How do I set up MMC3?
So far I've been writing programs with no mapper (UNROM). I wanted to try out something with MMC3, but it turns out that simply setting up the mapper is more confusing than I thought.
Now, I do realize there's already a thread like this, but there they simply focused on fixing the problem that the person, who tried to use the mapper had, so a lot of things remain unexplained to me.
What I ask is for someone to write a step-by-step guide, that details what to put into the header, how many PRG and CHR banks are correct to use, how does the number of banks affect where to write your initial code and any special code that might be necessary to set things up. If it's simple enough, you can also add bank-switching.
I ask this, not only to get a clear idea of how to do things, but for any user, who is just starting out with this mapper and would like to know how to work with it.
Thanks!
Now, I do realize there's already a thread like this, but there they simply focused on fixing the problem that the person, who tried to use the mapper had, so a lot of things remain unexplained to me.
What I ask is for someone to write a step-by-step guide, that details what to put into the header, how many PRG and CHR banks are correct to use, how does the number of banks affect where to write your initial code and any special code that might be necessary to set things up. If it's simple enough, you can also add bank-switching.
I ask this, not only to get a clear idea of how to do things, but for any user, who is just starting out with this mapper and would like to know how to work with it.
Thanks!
Greetings! I'm That Bio Mechanical Dude and I like creating various stuff like movies, games and of course chiptunes!
You can check out my YouTube Channel.
You can also follow me on Twitter.
You can check out my YouTube Channel.
You can also follow me on Twitter.
Re: How do I set up MMC3?
For one thing, "UNROM" isn't "no mapper". It's a mapper that switches PRG ROM A17-A14 based on the contents of a 74HC161, OR'd with CPU A14 to provide a fixed bank at $C000.
Asking "how many PRG and CHR banks are correct to use" is is like asking "how long is a piece of string?". I can provide the minimum and maximum sizes, but I'm not going to make an exhaustive list of 16-byte headers for every possible combination of PRG ROM size, CHR ROM size, CHR RAM size, WRAM size, and mirroring behavior. Cartesian products get really big really fast.
MMC3 PRG ROM can be 16 KiB to 512 KiB in powers of two.
MMC3 WRAM is either unpopulated, 8 KiB, or 8 KiB with battery.
MMC3 CHR ROM can be unpopulated, 8 KiB to 256 KiB, or 8 KiB to 64 KiB if CHR RAM is also used, in powers of two.
MMC3 CHR RAM is either unpopulated, 8 KiB, or 32 KiB. At least one of CHR ROM or CHR RAM must be populated. A battery is not available.
MMC3 nametable mirroring can be either normal (switchable H/V) or based on the bit 7 of the CHR ROM bank (which allows oddball things like 1-screen, diagonal, or L-shaped mirroring).
TQROM rule: If a board has both CHR ROM and CHR RAM, then CHR ROM is limited to 64 KiB, and the more flexible mirroring is not available. A board containing both CHR RAM and WRAM doesn't exist officially. If it did, it would probably be as expensive as Koei's SOROM boards, as it'd need four memories.
The iNES mapper for MMC3 depends on the chosen options.
In all cases, startup code must be in the very last 8 KiB bank of PRG ROM, which is fixed to $E000-$FFFF.
Asking "how many PRG and CHR banks are correct to use" is is like asking "how long is a piece of string?". I can provide the minimum and maximum sizes, but I'm not going to make an exhaustive list of 16-byte headers for every possible combination of PRG ROM size, CHR ROM size, CHR RAM size, WRAM size, and mirroring behavior. Cartesian products get really big really fast.
MMC3 PRG ROM can be 16 KiB to 512 KiB in powers of two.
MMC3 WRAM is either unpopulated, 8 KiB, or 8 KiB with battery.
MMC3 CHR ROM can be unpopulated, 8 KiB to 256 KiB, or 8 KiB to 64 KiB if CHR RAM is also used, in powers of two.
MMC3 CHR RAM is either unpopulated, 8 KiB, or 32 KiB. At least one of CHR ROM or CHR RAM must be populated. A battery is not available.
MMC3 nametable mirroring can be either normal (switchable H/V) or based on the bit 7 of the CHR ROM bank (which allows oddball things like 1-screen, diagonal, or L-shaped mirroring).
TQROM rule: If a board has both CHR ROM and CHR RAM, then CHR ROM is limited to 64 KiB, and the more flexible mirroring is not available. A board containing both CHR RAM and WRAM doesn't exist officially. If it did, it would probably be as expensive as Koei's SOROM boards, as it'd need four memories.
The iNES mapper for MMC3 depends on the chosen options.
- CHR ROM and CHR RAM: mapper 119 (byte 6: $7x, byte 7: $7x)
- Oddball mirroring: mapper 118 (byte 6: $6x, byte 7: $7x)
- Neither: mapper 4 (byte 6: 4x, byte 7: $0x)
In all cases, startup code must be in the very last 8 KiB bank of PRG ROM, which is fixed to $E000-$FFFF.
Re: How do I set up MMC3?
Your total PRG ROM size has to be less than or equal to 512KB. Valid values in the header for PRG banks are 1, 2, 4, 8, 16, 32. The mapper supports 512KB max for PRG, the ROM header specifies how many 16 KB banks there will be. 512/16=32. It should be a power of 2 because physical ROMs are.
Your total CHR ROM size has to be less than or equal to 256KB. Valid values in the header for PRG banks are 1, 2, 4, 8, 16, 32. The mapper supports 256KB max for PRG, the ROM header specifies how many 8KB banks there will be. 256/8=32. It should be a power of 2 because physical ROMs are.
Edit: 0 is valid too if you want CHR RAM.
Your reset label (and vectors) need to be in the last 8KB of your PRG data. MMC3 divides your PRG data into 8KB segments, but only the last 8KB segment (bank) is guaranteed a position on power on so reset code needs to be there. The last 8KB also needs to be mapped to $E000-$FFFF.
Disable IRQs by writing any value to $E000 after reset. (You can reenable them later when you learn about them.) To set vertical mirroring, write #$00 to $A000. To set horizontal mirroring, write #$01 to $A000. You can change this at any time, but definitely set it up to something known at the start.
Writing a value to $8000 affects how the CHR and PRG banks are arranged, as well as setting which bank a write to $8001 will change. The highest 2 bits written control how the CHR and PRG banks are arranged, the lowest 3 bits control which bank a write to $8001 will change.
For now, let's assume the highest bits are all zero. Writing #$06 to $8000 will say, "I want a write to $8001 to change which bank is at $8000-$9FFF now." Writing a 0 to $8001 will then map the first 8KB of your PRG data to $8000-$9FFF. Write a #$07 to $8000 will say, "I want a write to $8001 to change which bank is at $A000-$BFFF now." Write a 1 to $8001 will then map the second 8KB of your PRG data to $A000-$BFFF.
$8000 and $8001 are a bit like the $2006 and $2007 pair, except they can be written at any time and you write the even one once per value instead of twice. First you set what you want to change with the even register, then write the value to the odd register.
So say you're changing from a 32 KB NROM game with 8KB CHR and horizontal mirroring to MMC3.
Hopefully with the above, the chart on the wiki will make more sense. The way it describes CHR banks is pretty technical. But it's basically:
When writing a 1KB CHR bank (2, 3, 4, or 5 was last written to $8000), the value written to $8001 directly corresponds to 1KB in CHR. 0 is first 1KB of CHR, 1 is second 1KB of CHR, 2 is third KB of CHR etc.
When writing a 2KB CHR bank, (0 or 1 was last written to $8000) write the 1KB segment you want to start on. (But... one is subtracted from odd values. So you can only start at 0, 2, 4 etc.)
The value written to $8001 for PRG chooses which 8KB segment, the value written to $8001 for CHR chooses which 1KB segment, basically.
As for how else to setup, it would be helpful to know what assembler you used to build your previous ROMs.
Edit2: As far how does the number of banks affect where to write your code. Hmm...For CHR, so long as you only add banks after the banks that are there, nothing in your code really needs to change to add CHR banks.
PRG gets a little trickier because the very last PRG bank is the fixed bank (where your reset code and vectors need to be.) If you add PRG banks after the last one, you need to move that stuff too. If you add PRG banks before the first one, your writes to $8001 all need to be offset.
If your 8KB banks in order are A, B, C, and D. 0 refers to A, 1, refers to B, 2 refers to C, 3 refers to D. If you add E before A, 0 now refers to E... so any writes you had that wanted to swap in A will now swap in E.
So ideally you add banks before your last two banks (which are fixed at specific places), but after all the other banks.
If you find any of this confusing, by all means ask! This is a general answer, but feel free to ask for a specific one.
Edit3: Swapped mirroring whoops! Changed less than to less than or equal to while I'm here...
Your total CHR ROM size has to be less than or equal to 256KB. Valid values in the header for PRG banks are 1, 2, 4, 8, 16, 32. The mapper supports 256KB max for PRG, the ROM header specifies how many 8KB banks there will be. 256/8=32. It should be a power of 2 because physical ROMs are.
Edit: 0 is valid too if you want CHR RAM.
Your reset label (and vectors) need to be in the last 8KB of your PRG data. MMC3 divides your PRG data into 8KB segments, but only the last 8KB segment (bank) is guaranteed a position on power on so reset code needs to be there. The last 8KB also needs to be mapped to $E000-$FFFF.
Disable IRQs by writing any value to $E000 after reset. (You can reenable them later when you learn about them.) To set vertical mirroring, write #$00 to $A000. To set horizontal mirroring, write #$01 to $A000. You can change this at any time, but definitely set it up to something known at the start.
Writing a value to $8000 affects how the CHR and PRG banks are arranged, as well as setting which bank a write to $8001 will change. The highest 2 bits written control how the CHR and PRG banks are arranged, the lowest 3 bits control which bank a write to $8001 will change.
For now, let's assume the highest bits are all zero. Writing #$06 to $8000 will say, "I want a write to $8001 to change which bank is at $8000-$9FFF now." Writing a 0 to $8001 will then map the first 8KB of your PRG data to $8000-$9FFF. Write a #$07 to $8000 will say, "I want a write to $8001 to change which bank is at $A000-$BFFF now." Write a 1 to $8001 will then map the second 8KB of your PRG data to $A000-$BFFF.
$8000 and $8001 are a bit like the $2006 and $2007 pair, except they can be written at any time and you write the even one once per value instead of twice. First you set what you want to change with the even register, then write the value to the odd register.
So say you're changing from a 32 KB NROM game with 8KB CHR and horizontal mirroring to MMC3.
Code: Select all
.org $E000
reset:
sta $E000;Disable IRQs.
lda #$01
sta $A000;Horizontal mirroring
ldx #$00;Not specifically MMC3, but good to do early
stx $2000; Disable NMI
stx $2001; Disable Rendering
lda #$06;We want to change $8000-$9FFF to what it was when we were 32KB NROM
sta $8000;We'll now change $8000-$9FFF when we write to $8001
lda #0
sta $8001;Now the first 8KB segment of PRG is at $8000-$9FFF just like it was in the 32KB NROM version of the game.
;Now we need to make sure $A000-$BFFF is in the right place
lda #$07
sta $8000;We'll now change $A000-$BFFF when we write to $8001
lda #1
sta $8001;Now the second 8KB segment of PRG is at $A000-$BFFF just like it was in the 32KB NROM version of the game.
;$E000-$FFFF was already in the last 8KB of PRG, and that's guaranteed for MMC3.
;$C000-$DFFF is also fine. The high bits being clear when we last wrote to $8000 guarantee this.
;Done with PRG!
lda #0; Next write to $8001 will now change the 2KB at $0000-$07FF
sta $8000
lda #0
sta $8001;We want the first 2KB of our CHR there, so 0.
lda #1;Similar drill. Next write to $8001 will now change the 2KB at $0800-$0FFF
sta $8000
;This is the part that gets a bit confusing! We're selecting the start of a 1KB segment our CHR starts
;Odd numbers essentially become the even number below them.
;Since 0 is the first 1KB, and 1 is the second 1KB, and both were already mapped by changing a 2KB segment
;2 is the proper value for the next 2KB segment.
lda #2
sta $8001
lda #2
sta $8000;Next write to $8001 will now change 1KB at $1000-$13FF
lda #4;3 was the second part of the 2KB segment from the write above. So 4...
sta $8001
lda #3
sta $8000;Next write to $8001 will now change 1KB at $1400-$17FF
lda #5;Since we're writing 1KB segments now, odd numbers are valid.
sta $8001
lda #4
sta $8000;Next write to $8001 will now change 1KB at $1800-$1BFF
lda #6
sta $8001
lda #5
sta $8000;Next write to $8001 will now change 1KB at $1C00-$1FFF
lda #7
sta $8001
When writing a 1KB CHR bank (2, 3, 4, or 5 was last written to $8000), the value written to $8001 directly corresponds to 1KB in CHR. 0 is first 1KB of CHR, 1 is second 1KB of CHR, 2 is third KB of CHR etc.
When writing a 2KB CHR bank, (0 or 1 was last written to $8000) write the 1KB segment you want to start on. (But... one is subtracted from odd values. So you can only start at 0, 2, 4 etc.)
The value written to $8001 for PRG chooses which 8KB segment, the value written to $8001 for CHR chooses which 1KB segment, basically.
As for how else to setup, it would be helpful to know what assembler you used to build your previous ROMs.
Edit2: As far how does the number of banks affect where to write your code. Hmm...For CHR, so long as you only add banks after the banks that are there, nothing in your code really needs to change to add CHR banks.
PRG gets a little trickier because the very last PRG bank is the fixed bank (where your reset code and vectors need to be.) If you add PRG banks after the last one, you need to move that stuff too. If you add PRG banks before the first one, your writes to $8001 all need to be offset.
If your 8KB banks in order are A, B, C, and D. 0 refers to A, 1, refers to B, 2 refers to C, 3 refers to D. If you add E before A, 0 now refers to E... so any writes you had that wanted to swap in A will now swap in E.
So ideally you add banks before your last two banks (which are fixed at specific places), but after all the other banks.
If you find any of this confusing, by all means ask! This is a general answer, but feel free to ask for a specific one.
Edit3: Swapped mirroring whoops! Changed less than to less than or equal to while I'm here...
Last edited by Kasumi on Mon Dec 04, 2017 3:48 am, edited 3 times in total.
Re: How do I set up MMC3?
Didn't know it could have 32kb of CHR-RAM. Well obviously it can, but I don't think it's Nintendo-standard to do this.tepples wrote: MMC3 CHR RAM is either unpopulated, 8 KiB, or 32 KiB. At least one of CHR ROM or CHR RAM must be populated. A battery is not available.
Same for battery, definitely not used on CHR-RAMs by Nintendo-standard carts, but technically nothing prevents one to do exactly that.
Re: How do I set up MMC3?
One unlicensed game published by Retrotainment Games in 2016 uses (a clone of) MMC3 with 32 KiB of CHR RAM. I posted an explanation of how it lays out CHR RAM.Bregalad wrote:Didn't know it could have 32kb of CHR-RAM. Well obviously it can, but I don't think it's Nintendo-standard to do this.tepples wrote:MMC3 CHR RAM is either unpopulated, 8 KiB, or 32 KiB.
- BioMechanical Dude
- Formerly AlienX
- Posts: 137
- Joined: Fri Apr 18, 2014 7:41 am
- Location: Bulgaria
Re: How do I set up MMC3?
Wow, thanks for your replies! Kasumi's guide was especially detailed and it cleared up a lot of stuff for me. I still have a few questions, though.
I am using ASM6 to compile my programs, and this made me wonder... how do you define the different banks that aren't mapped to any particular address? Given that there are gonna be a lot of PRG banks that simply won't fit into the address range, how do I declare them? NESASM3 has a .bank command, but ASM6 doesn't. What do I do here?
Also, when bankswitching a certain area, where code is being run from, will the CPU continue reading from the address it was on, when the bankswitch happen? For example, let's say I'm running some code from a bank in $8000-$9FFF, I bankswitch that area and the instruction that causes the switch happens at $817F. After the switch, will the CPU continue reading from $8180 or will it somehow reset to $8000?
Thanks!
I am using ASM6 to compile my programs, and this made me wonder... how do you define the different banks that aren't mapped to any particular address? Given that there are gonna be a lot of PRG banks that simply won't fit into the address range, how do I declare them? NESASM3 has a .bank command, but ASM6 doesn't. What do I do here?
Also, when bankswitching a certain area, where code is being run from, will the CPU continue reading from the address it was on, when the bankswitch happen? For example, let's say I'm running some code from a bank in $8000-$9FFF, I bankswitch that area and the instruction that causes the switch happens at $817F. After the switch, will the CPU continue reading from $8180 or will it somehow reset to $8000?
Thanks!
Greetings! I'm That Bio Mechanical Dude and I like creating various stuff like movies, games and of course chiptunes!
You can check out my YouTube Channel.
You can also follow me on Twitter.
You can check out my YouTube Channel.
You can also follow me on Twitter.
Re: How do I set up MMC3?
I guess the simplest answer to your address question is there are two types of address.
Even with NROM, the first byte after your header could go into $8000 in NES Memory, or it could go into $C000 in NES memory.
The NES doesn't really care where things are in your rom, only where they'll be mapped in NES' own memory.
What .org does is specify where things should be mapped in NES memory for that particular block of code. (I'll explain why I said should instead of will later.)
gets assembled as $4C (jmp) $00 $80
gets assembled as $4C (jmp) $00 $A0
Without the .org, jmp main couldn't work. There'd be no way for the assembler to know WHERE to put the jump. (Well, it could use some arbitrary starting location to assemble bytes at.)
Now, say you have the second example (.org $A000) in your first bank (bank 0).
And you did this:
You've just put code designed to be at $A000 at $8000. So that jmp main would jmp to $A000, but that's not where you just bank swapped in the infinite loop bank. So your program would probably crash. If you did this instead:
You would jmp to where you put the main loop in memory. But then immediately after, it would still jmp to $A000 and crash. ("main" is replaced with $A000 when assembled)
This is why I said "where things should be mapped" rather than "will be mapped." You can totally put blocks of code in memory where they're not designed to be, but you shouldn't.
I don't use much asm6, but as far as I can tell, you use .base and .pad
For example:
.org is usually equivalent to .pad in asm6. The above demonstrates a few things. That it's possible to have two $A000-$BFFF banks in a row. That it's possible to have two $8000-$9FFF banks in a row. That it's possible to define two 8KB banks together as one 16KB bank so long as their addresses would have been contiguous. ("banks 2 and 3") A lot of people treat MMC3 as if it has 16KB banking and just swap both 8KB banks every time.
Don't do this, but what follows is a detailed example:
Say this is bank zero.
And this is bank one
If the code was running at $8000 in bank zero:
Mapped at $800A is jmp $8000. But at $8007 is sta $8001 that changes what's mapped at $800A. (Really, what's mapped in the whole region from $8000-$9FFF)
So it continues from $800A in bank 1 rather than bank 0. In other words, rendering will get disabled, rather than looping by jumping back to $8000.
I've attached a test ROM that demonstrates this. (Note that it doesn't do a lot of initialization it probably should, but it's good enough to show the concept in the current version of FCEUX.)
Put a breakpoint for execution at $8000, reset or power on, then step through it. You can see the next instruction change. I'd recommend never swapping the bank you're currently in, though. (At least not with MMC3) A good practice is having your main loop in $E000-$FFF9. You can swap in a data bank into $8000-$9FFF from there. You can swap in a bank with subroutines to read that data in $A000-$BFFF. You jsr to the subroutine, when it returns you can swap both banks again from the safety of the fixed bank.
Even if some of this goes over your head, so long as you understand the recommendation, how to "make banks" in asm6, and the "two types of address" you're fine to continue. The bank switching the bank you're in question is more theoretical than practical. At least with MMC3. AxROM swaps all of $8000-$FFFF at once. Even then, I'd probably opt for code in RAM that does the swap and jmps to the right location in the new bank.
Even with NROM, the first byte after your header could go into $8000 in NES Memory, or it could go into $C000 in NES memory.
The NES doesn't really care where things are in your rom, only where they'll be mapped in NES' own memory.
What .org does is specify where things should be mapped in NES memory for that particular block of code. (I'll explain why I said should instead of will later.)
Code: Select all
.org $8000
main:;endless loop
jmp main
gets assembled as $4C (jmp) $00 $80
Code: Select all
.org $A000
main:;endless loop
jmp main
Without the .org, jmp main couldn't work. There'd be no way for the assembler to know WHERE to put the jump. (Well, it could use some arbitrary starting location to assemble bytes at.)
Now, say you have the second example (.org $A000) in your first bank (bank 0).
And you did this:
Code: Select all
lda #$06;
sta $8000;We'll now change $8000-$9FFF when we write to $8001
lda #0
sta $8001;Now the first 8KB segment of PRG is at $8000-$9FFF
jmp main
Code: Select all
lda #$06;
sta $8000;We'll now change $8000-$9FFF when we write to $8001
lda #0
sta $8001;Now the first 8KB segment of PRG is at $8000-$9FFF
jmp $8000
This is why I said "where things should be mapped" rather than "will be mapped." You can totally put blocks of code in memory where they're not designed to be, but you shouldn't.
I don't use much asm6, but as far as I can tell, you use .base and .pad
For example:
Code: Select all
;bank 0
.base $8000;Assemble the next byte to be mapped at $8000
nop;Now the next byte will be mapped at $8001
.pad $A000;Fills from $8001 to $9FFF. (Will assemble the next byte at $A000)
;bank 1
.base $A000
;code here
.pad $C000
;banks 2 and 3
.base $8000
;code here
.pad $C000
;bank 4
.base $A000
;code here
.pad $C000
;bank 5
.base $A000
;code here
.pad $C000
;bank 6
.base $8000
;code here
.pad $A000
;bank 7
.base $8000
;code here
.pad $A000
The simplest answer is the bank switch is basically completely invisible to the CPU. So yes, it will totally still try to run the instruction at the address following the current instruction. The bank switch might change what's there, of course. (It wouldn't specifically be one byte greater, though. Different instructions are different sizes.)will the CPU continue reading from the address it was on, when the bankswitch happen?
Don't do this, but what follows is a detailed example:
Say this is bank zero.
Code: Select all
.org $8000
lda #$06;
sta $8000;We'll now change $8000-$9FFF when we write to $8001
lda #1
sta $8001;Now the second 8KB segment of PRG is at $8000-$9FFF
jmp $8000
Code: Select all
.org $8000
nop;a byte for lda
nop;a byte for #$06
nop;a byte for sta
nop;a byte for $00
nop;a byte for $80
nop;a byte for lda
nop;a byte for #1
nop;a byte for sta
nop;a byte for $01
nop;a byte for $80
;We are now at the same address
;as jmp $8000 in bank zero
lda #$00;disable rendering
sta $2000
sta $2001
Mapped at $800A is jmp $8000. But at $8007 is sta $8001 that changes what's mapped at $800A. (Really, what's mapped in the whole region from $8000-$9FFF)
So it continues from $800A in bank 1 rather than bank 0. In other words, rendering will get disabled, rather than looping by jumping back to $8000.
I've attached a test ROM that demonstrates this. (Note that it doesn't do a lot of initialization it probably should, but it's good enough to show the concept in the current version of FCEUX.)
Put a breakpoint for execution at $8000, reset or power on, then step through it. You can see the next instruction change. I'd recommend never swapping the bank you're currently in, though. (At least not with MMC3) A good practice is having your main loop in $E000-$FFF9. You can swap in a data bank into $8000-$9FFF from there. You can swap in a bank with subroutines to read that data in $A000-$BFFF. You jsr to the subroutine, when it returns you can swap both banks again from the safety of the fixed bank.
Even if some of this goes over your head, so long as you understand the recommendation, how to "make banks" in asm6, and the "two types of address" you're fine to continue. The bank switching the bank you're in question is more theoretical than practical. At least with MMC3. AxROM swaps all of $8000-$FFFF at once. Even then, I'd probably opt for code in RAM that does the swap and jmps to the right location in the new bank.
- BioMechanical Dude
- Formerly AlienX
- Posts: 137
- Joined: Fri Apr 18, 2014 7:41 am
- Location: Bulgaria
Re: How do I set up MMC3?
Thank you very much! That really cleared up a lot. I will now try to compile a program, using MMC3. I'll ask, if I encounter any issues.
Greetings! I'm That Bio Mechanical Dude and I like creating various stuff like movies, games and of course chiptunes!
You can check out my YouTube Channel.
You can also follow me on Twitter.
You can check out my YouTube Channel.
You can also follow me on Twitter.
Re: How do I set up MMC3?
I do not want to write about MMC3 in the topic about MMC1.
https://forums.nesdev.com/viewtopic.php ... 0&start=15
That is why I continue my previous questions about MMC3 here:
I am trying to initialize the MMC3 code, but I think I got lost in everything.
I try to write an MMC3 code with 256KB PRG and CHR RAM:
Reset Code moved to E000:
At the beginning, switching the bank $ 8000- $ 9FFF to the new one (0E) in which the graphics data are located (8kb chr).
https://forums.nesdev.com/viewtopic.php ... 0&start=15
That is why I continue my previous questions about MMC3 here:
I am trying to initialize the MMC3 code, but I think I got lost in everything.
I try to write an MMC3 code with 256KB PRG and CHR RAM:
Code: Select all
.inesprg 16 ;256kb prg
.ineschr 0 ;chr ram
.inesmap 4
.inesmir 0
Code: Select all
RESET:
sta $e000
sei
cld
lda #$40
sta $4017
ldx #$FF
TXS
ldx #$07
mmc3initloop:
stx $8000
lda mmc3tbl,x
sta $8001
dex
bpl mmc3initloop
sta $A000
stx $2000
stx $2001
VBlankWait1:
BIT $2002
BPL VBlankWait1
VBlankWait2:
BIT $2002
BPL VBlankWait2
TXA
ClearMemory:
STA $0000, x
STA $0100, x
STA $0200, x
STA $0300, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
INX
BNE ClearMemory
JSR CopyCHR ;copy chr data to CHR ROM
;loadscreen, loadsprite etc
Forever:
JSR VBWait
JMP Forever
mmc3tbl:
.db $0,$2,$4,$5,$6,$7,$0,$1
Code: Select all
CopyCHR: ;in e000
LDA %00000110 ;6: Select 8 KB PRG ROM bank at $8000-$9FFF (or $C000-$DFFF)
STA $8000
LDA #$0E ;switch to bank 0E
STA $8001
;copychr data to cart chrram code
LDA %00000110
STA $8000
LDA #$00 ;back to bank 00
STA $8001
RTS
Re: How do I set up MMC3?
Remark: that code is writing #$FF to $2000 and $2001, which is not what you want it to do - you should change those to STA and insert an "LDA #$00" above the "sta $A000" just to make it absolutely clear what you're writing (even though it's already getting that value from the beginning of mmc3tbl, it's unclear and confusing and you will forget about that little detail at some point).sdm wrote:Code: Select all
ldx #$07 mmc3initloop: stx $8000 lda mmc3tbl,x sta $8001 dex bpl mmc3initloop sta $A000 stx $2000 stx $2001 ... mmc3tbl: .db $0,$2,$4,$5,$6,$7,$0,$1
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
P.S. If you don't get this note, let me know and I'll write you another.
Re: How do I set up MMC3?
ok, everything seems to work, the problem was a small trifle - before LDA% 00000110, there was no "#" character (of course, nesasm3 did not display an error and I missed the problem).
I changed the reset code. Works, but is it correct?
Test code, 16 banks prg (256kb), chr ram, chr data in bank 2 (switched bank 8000), sound at START BUTTON in bank 17 (switched bank 8000),
a/b also switch mirroring (h/v).
I only want to switch bank 0 (address 8000-9FFF). Banks A000, C000 and E000 will not be changed. It seems that everything works, but not tested on hardware.
I changed the reset code. Works, but is it correct?
Code: Select all
RESET:
sta $E000;Disable IRQs.
lda #$01
sta $A000;Horizontal mirroring
sei
cld
lda #$40
sta $4017
ldx #$FF
TXS
inx
stx $2000
stx $2001
lda #$06
sta $8000
lda #0
sta $8001
lda #$07
sta $8000
lda #1
sta $8001
VBlankWait1:
BIT $2002
BPL VBlankWait1
VBlankWait2:
BIT $2002
BPL VBlankWait2
TXA
ClearMemory:
STA $0000, x
STA $0100, x
STA $0200, x
STA $0300, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
INX
BNE ClearMemory
JSR CopyCHR ;copy chr data to cart (
(...)
Code: Select all
CopyCHR:
LDA #%00000110 ;6 / 8000-9fff
STA $8000
LDA #2 ;switch 0 to bank 2
STA $8001
LDA #$00
STA $2006
LDA #$00
STA $2006
LDX #$20 ;32x 256 bytes / 8kb
LDA #low(TileSet)
STA <NTloadLO
LDA #high(TileSet)
STA <NTloadHI
LDY #$00
CopyCHRLoop:
LDA [NTloadLO], y
STA $2007
INY
BNE CopyCHRLoop
INC <NTloadHI
DEX
BNE CopyCHRLoop
LDA #%00000110
sta $8000
LDA #0 ;back to bank 0
STA $8001
RTS
Code: Select all
.bank 2 ;mmc3 bank 2
.org $8000
TileSet:
.incbin "demo.spr"
.incbin "demo.chr"
a/b also switch mirroring (h/v).
I only want to switch bank 0 (address 8000-9FFF). Banks A000, C000 and E000 will not be changed. It seems that everything works, but not tested on hardware.
- Attachments
-
- demo_mmc3_d2.nes
- (256.02 KiB) Downloaded 378 times
Last edited by sdm on Tue Sep 18, 2018 5:45 am, edited 2 times in total.
Re: How do I set up MMC3?
A good practice when doing this kind of optimisation is to leave the instruction here, but comment it. That way you won't waste the 2 bytes of an useless "LDA #$00" instruction where you know A is already always $00, but the intent of loading #$00 into A is still here, and if you ever modify the code you can uncomment it to make it activate again.Quietust wrote: Remark: [...] you should change those to STA and insert an "LDA #$00" above the "sta $A000" just to make it absolutely clear what you're writing (even though it's already getting that value from the beginning of mmc3tbl, it's unclear and confusing and you will forget about that little detail at some point).
Re: How do I set up MMC3?
In the previous reset code I inserted the MMC3 init code, which I was thinking is some standard init code for this mapper. Maybe I did something wrong, I do not know.
viewtopic.php?f=10&t=12030&start=30#p225789
But I see, you do not need any special mmc3 initialization? Because the code I did works. As I wrote above, I only care about switching the 8000-9FFF bank without using advanced MMC3 functions (IRQ etc).
viewtopic.php?f=10&t=12030&start=30#p225789
But I see, you do not need any special mmc3 initialization? Because the code I did works. As I wrote above, I only care about switching the 8000-9FFF bank without using advanced MMC3 functions (IRQ etc).
Re: How do I set up MMC3?
If you don't initialize CHR windows 0-5, you'll display wrong CHR banks. This is true even if you use CHR RAM because the MMC3 can bankswitch CHR RAM.
Re: How do I set up MMC3?
Ok, but if I do not initiate mmc3 CHR banks at the beginning, but I will use chr banks in the standard order for NROM, then the code will work and mmc3 will read everything in the standard order? ($ 0000 4kb spr $ 1000 4kb bgr, 8kb all for one bank).
I did the test code 256KB PRG + 16KB CHR. It works, but I did not test it on the console and card with mmc3.
I did the test code 256KB PRG + 16KB CHR. It works, but I did not test it on the console and card with mmc3.
Code: Select all
.bank 32
.org $0000
TileSet0:
.incbin "demo.spr0" ;lda 0 sta 8000, lda 0 sta 8001 = spr bank 0
.incbin "demo.spr1" ;lda 0 sta 8000, lda 3 sta 8001 = spr bank 1
.incbin "demo.chr0" ;lda 2 sta 8000, lda 4 sta 8001 = bgr bank 0
.incbin "demo.chr1" ;lda 2 sta 8000, lda 5 sta 8001 = bgr bank 1
.incbin "demo.chr2" ;lda 2 sta 8000, lda 6 sta 8001 = bgr bank 2
.incbin "demo.chr3" ;lda 2 sta 8000, lda 7 sta 8001 = bgr bank 3
.bank 33
.org $0000
TileSet1:
.incbin "demo.spr2" ;lda 0 sta 8000, lda 8 sta 8001 = spr bank 2
.incbin "demo.spr3" ;lda 0 sta 8000, lda 10 sta 8001 = spr bank 3
.incbin "demo.chr4" ;lda 2 sta 8000, lda 12 sta 8001 = bgr bank 4
.incbin "demo.chr5" ;lda 2 sta 8000, lda 13 sta 8001 = bgr bank 5
.incbin "demo.chr6" ;lda 2 sta 8000, lda 14 sta 8001 = bgr bank 6
.incbin "demo.chr7" ;lda 2 sta 8000, lda 15 sta 8001 = bgr bank 7
;etc
- Attachments
-
- test_mmc3_e_chrom.nes
- (272.02 KiB) Downloaded 374 times
Last edited by sdm on Fri Sep 21, 2018 3:02 am, edited 6 times in total.