MMC3 Test Cartridge CA65

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
Atomfusion
Posts: 7
Joined: Sat Apr 23, 2022 5:48 pm

MMC3 Test Cartridge CA65

Post by Atomfusion »

I have now corrected my errors and am now sharing an MMC3 starter cartridge on Github.
Complete Build Environment CA65 Basic MMC3 Cartridge With Test Code
1. PRG Rom Bank Switching
2. IRQ Scan Line Trigger
3. CHR Sprite Changes 2x2k and 4x1k
This is just an entry level MMC3 assembly code/setup to show you how to setup MMC3
But this can lead to create depth of field (Parallax) and dual area's

https://github.com/Atomfusion1/NES_MMC3_CA65_Demo_Cart





I have been spending a week trying to program an NES cartridge,
I find it far harder than it should be, One everyone tells you to go learn from Nerdy Nights but then tell you not to program in nesasm but ca65 deals with memory segments differently. Many people post Asm code without saying what assemble to use and on top don't even put in the .nes file to see if its worth your time etc.
This Tutorial File I'm working on Working Sound and effects, Weapon, Sprites, Background, Scrolling (no collision or many other things)
Most tutorials seem to fall into two types, One they are two simple and there is not enough of anything there to be useful/good starting point, or two so far advanced that its useless to a beginner. (rant over)

This Tutorial File I'm working on Working Sound and effects, Weapon, Sprites, Background, Scrolling (no collision or many other things) Feel free to test it out, its not optimal but my goal is to make it readable / easy to follow in order to use everything to make your own game level / start out, This can at least give you a quick feeling of how far down the rabbit hole you want to go.

I am looking for a little help with CA65 CFG file and mappers MMC1 and MMC4
I have spent about 8 Hours messing with nes.cfg in order to get 4 selectable CHR locations

This Cfg file will let you build NROM(0), CNROM(3), MMC1(1), MMC3(4) but only up to 32 Prog Rom and 32 Chr rom I would like to get a 128x128 MMC1 and a full layout of MMC.

What I would like:
1. Simple NROM(0), CNROM(3), MMC1(1), MMC3(4) cfg files .
2. if its not much trouble for you, a snippet of how to change banks / prog pages .

Code: Select all

SYMBOLS {
    __STACKSIZE__: type = weak, value = $0300; # 3 pages stack
}
# Full Config for MMC3 
MEMORY {
	# First 28 bytes of the zero page are used by NES library
    ZP: start = $10, size = $FD, type = rw, define = yes;

    # INES Cartridge Header
    HEADER: start = $0, size = $10, file = %O ,fill = yes;

    # 2 16K ROM Banks
    # - startup
    # - code
    # - rodata
    # - data (load)
	# NROM256
	PRG: start = $8000, size = $7ffa, file = %O ,fill = yes, define = yes;

	# Hardware Vectors at end of the ROM
	# NROM256
	VECTORS: start = $fffa, size = $6, file = %O, fill = yes;

    # 4 8K CHR Bank
    CHR: start = $0000, size = $8000, file = %O, fill = yes;

    # standard 2K SRAM (-zeropage)
    # $0100 famitone, palette, cpu stack
	# $0200 oam buffer
	# $0300..$800 ca65 stack

    #RAM: start = $0300, size = $0500, define = yes;

	# Use this definition instead if you going to use extra 8K RAM
	RAM: start = $6000, size = $2000, define = yes;
}

SEGMENTS {
    HEADER:   load = HEADER,         type = ro;
    STARTUP:  load = PRG,            type = ro,  define = yes;
    LOWCODE:  load = PRG,            type = ro,                optional = yes;
    INIT:     load = PRG,            type = ro,  define = yes, optional = yes;
    CODE:     load = PRG,            type = ro,  define = yes;
    RODATA:   load = PRG,            type = ro,  define = yes;
    DATA:     load = PRG, run = RAM, type = rw,  define = yes;
    VECTORS:  load = VECTORS,        type = rw;
	CHARS:    load = CHR,            type = rw;
    BSS:      load = RAM,            type = bss, define = yes;
    HEAP:     load = RAM,            type = bss, optional = yes;
    ZEROPAGE: load = ZP,             type = zp;
}

FEATURES {
    CONDES: type    = constructor,
            label   = __CONSTRUCTOR_TABLE__,
            count   = __CONSTRUCTOR_COUNT__,
            segment = ONCE;
    CONDES: type    = destructor,
            label   = __DESTRUCTOR_TABLE__,
            count   = __DESTRUCTOR_COUNT__,
            segment = RODATA;
    CONDES: type    = interruptor,
            label   = __INTERRUPTOR_TABLE__,
            count   = __INTERRUPTOR_COUNT__,
            segment = RODATA,
            import  = __CALLIRQ__;
}
Last edited by Atomfusion on Tue Apr 26, 2022 5:56 pm, edited 1 time in total.
Atomfusion
Posts: 7
Joined: Sat Apr 23, 2022 5:48 pm

Re: MMC3 Test Cartridge

Post by Atomfusion »

Ok well I managed another 8 hours to get MMC3 working, But now I am having an IRQ problem in the code,
The second you enable the IRQ it Triggers non stop, it will not turn off even after STA $E000
The debugger says the Interrupt is always Set It seems to be going into the IRQ loop 5-6 times per scanline even when directly disabled



Thanks for any incite


Code: Select all

.segment "HEADER"

NES_MAPPER = 4                        ; 0 = NROM
; I have the Code in the HelloNES.asm Blocked off if you enable it you can switch between the 8k CHR chunks
NES_MIRROR = 1                         ; 0 = horizontal mirroring, 1 = vertical mirroring
NES_SRAM   = 0                         ; 1 = battery backed SRAM at $6000-7FFF

.byte 'N', 'E', 'S', $1A                ; ID
.byte $04                               ; 16k PRG ROM count (NOT 8K)
.byte $0F                               ; 16 x 8k CHR chunk count
.byte NES_MIRROR | (NES_SRAM << 1) | ((NES_MAPPER & $f) << 4)
.byte (NES_MAPPER & %11110000)
.byte $0, $0, $0, $0, $0, $0, $0, $0    ; padding

Code: Select all

SYMBOLS {
    __STACKSIZE__: type = weak, value = $0300; # 3 pages stack
}
# Full Config for MMC3 
MEMORY {
	# First 28 bytes of the zero page are used by NES library
    ZP: start = $10, size = $FD, type = rw, define = yes;

    # INES Cartridge Header
    HEADER: start = $0, size = $10, file = %O ,fill = yes;

	# 1 x 8K PRG RAM
	PRGRAM: start = $6000, size = $2000, define = yes;

    # 8 X 8K PRG ROM
	PRGROM1:    start = $8000, size = $2000, file = %O ,fill = yes, define = yes;
    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;
    PRGROM2:    start = $A000, size = $2000, file = %O ,fill = yes, define = yes;
    PRGROM3:    start = $C000, size = $2000, file = %O ,fill = yes, define = yes;
    PRGROM4:    start = $E000, size = $1FFA, file = %O ,fill = yes, define = yes;
	# Hardware Vectors at end of the ROM
	# NROM256
	VECTORS: start = $FFFA, size = $6, file = %O, fill = yes;

    # standard 2K SRAM (-zeropage)
    # $0100 famitone, palette, cpu stack
	# $0200 oam buffer
	# $0300..$800 ca65 stack

    #RAM: start = $0300, size = $0500, define = yes;
    # ! 16 Banks of 8K CHR ROM
    CHR: start = $0000, size = $20000, file = %O, fill = yes;
}

SEGMENTS {
    HEADER:   load = HEADER,            type = ro;
    STARTUP:  load = PRGRAM,           type = ro,      define = yes;
    LOWCODE:  load = PRGROM2,           type = ro,                      optional = yes;
    INIT:     load = PRGROM3,           type = ro,      define = yes,   optional = yes;
    CODE:     load = PRGROM4,           type = ro,      define = yes;
    RODATA:   load = PRGROM3,            type = ro,     define = yes;
    DATA:     load = PRGROM4, run = PRGRAM, type = rw,  define = yes;
    VECTORS:  load = VECTORS,           type = rw;
    # PRG ROM
    BANK0:    load = PRG0,           type = ro, define = yes;
	BANK1:    load = PRG1,           type = ro, define = yes;
    BANK2:    load = PRG2,           type = ro, define = yes;
	BANK3:    load = PRG3,           type = ro, define = yes;


    # Swapable 128 kb stay out of the first two 16k as they are above 
    CHARS:    load = CHR,            type = rw;


    BSS:      load = PRGRAM,            type = bss,     define = yes;
    HEAP:     load = PRGRAM,            type = bss,     optional = yes;
    ZEROPAGE: load = ZP,                type = zp;
}

FEATURES {
    CONDES: type    = constructor,
            label   = __CONSTRUCTOR_TABLE__,
            count   = __CONSTRUCTOR_COUNT__,
            segment = ONCE;
    CONDES: type    = destructor,
            label   = __DESTRUCTOR_TABLE__,
            count   = __DESTRUCTOR_COUNT__,
            segment = RODATA;
    CONDES: type    = interruptor,
            label   = __INTERRUPTOR_TABLE__,
            count   = __INTERRUPTOR_COUNT__,
            segment = RODATA,
            import  = __CALLIRQ__;
}
Last edited by Atomfusion on Tue Apr 26, 2022 5:49 pm, edited 1 time in total.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: MMC3 Test Cartridge

Post by unregistered »

Atomfusion wrote: Sat Apr 23, 2022 6:28 pm I have been spending a week trying to program an NES cartridge,
I find it far harder than it should be, One everyone tells you to go learn from Nerdy Nights but then tell you not to program in nesasm but ca65 deals with memory segments differently. Many people post Asm code without saying what assemble to use
It’s surely understandable since you just started a week ago. Programming a asm NES game requires one to learn many things. He/she must pick an assembler and learn lots about it. The programmer must know enough, about chosen assembler, in order to translate whatever code he/she is observing into the chosen assembler’s syntax.

This takes a while.
Atomfusion wrote: Sat Apr 23, 2022 6:28 pmI am looking for a little help with CA65 CFG file and mappers MMC1 and MMC4
I have spent about 8 Hours messing with nes.cfg in order to get 4 selectable CHR locations
My Trial code can be found here https://github.com/Atomfusion1/NES-MMC3-Template-

This Cfg file will let you build NROM(0), CNROM(3), MMC1(1), MMC3(4) but only up to 32 Prog Rom and 32 Chr rom I would like to get a 128x128 MMC1 and a full layout of MMC.

What I would like:
1. Simple NROM(0), CNROM(3), MMC1(1), MMC3(4) cfg files .
2. if its not much trouble for you, a snippet of how to change banks / prog pages .
This is kind of confusing, to me… you say you’ve spent a week trying to make a (1) NES game; but, you are inquiring about 4 different mappers. 4 mappers == 4 NES games.

So, it seems you are still in the planning stages… makes sense if you are still trying to choose a mapper.

A wealth of NES info is at https://wiki.nesdev.com/.


(I’ll return to add the wiki’s mapper page.)
EDIT: https://www.nesdev.org/wiki/Mapper

note: Scroll that page down until you reach the grid of links to NES mappers in that wiki.

Each mapper page contains exact info on how to set it up and switch banks. The MMC1 page is super interesting bc you will discover that switching MMC1 banks takes 5 ROM writes, among other simple things.

Other mappers have been setup differently and bank switching with them requires far fewer bytes/cycles.


So, pick a mapper and try to learn all you can; when something doesn’t make sense, please ask here where many experienced NES programmers will be able to respond.

Often, people are encouraged to begin with an NROM game since that’s easiest to setup. Mappers are just one of the many topics programmers should learn about while creating a NES game. :)
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: MMC3 Test Cartridge

Post by unregistered »

Ooooh, personally, I recommend asm6 for a first assembler. That’s what I started using. It’s simple and an asm6-type assembler is still enough for me at least.
Atomfusion
Posts: 7
Joined: Sat Apr 23, 2022 5:48 pm

Re: MMC3 Test Cartridge

Post by Atomfusion »

NesDev.org is a great resource but horrible learning site, lack of full codes and lots of jargon that only works after you already know what you are looking for. Its like searching the web without google search, or a 2 hour YouTube with what you need is only 30s long kind of easy to skip.

Another 8 hours and a new Tech demo but this time I have simplified it to just the bare minimum to test MMC3 PRG ROM switching and IRQ..

This rom works with NMI and the IRQ until you add a simple JMP command in the NMI code. Then the emulator breaks, it acts as though the IRQ does not return to the proper spot in the code. (with debug) it just keeps going to BRK area's
This rom is drawing a blue background with Sprite on top to about 128 then changing the background to Red for the rest of the rom.

1. It breaks if you use a JMP even to the same fixed PRG ROM ($E000) with the same code the NMI is using , but also the switchable rom area's

I can only assume its still an Initialization issue, or the nes.cfg file still.
Issue Fixed See 1st Post for Best Version

My problem right now is as far as I can tell this code should just work, so I'm not sure of the next step to debug its problems.

Thanks Again
Last edited by Atomfusion on Tue Apr 26, 2022 5:48 pm, edited 1 time in total.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: MMC3 Test Cartridge

Post by tokumaru »

Just a wild guess about what could be throwing the IRQ off: the scanline counter on the MMC3 works by monitoring the PPU address lines changing as PPU memory is accessed during rendering, but the mapper can't tell the difference between the address lines changing due to rendering and due to the CPU using registers $2006 and $2007. So the fact that you're using $2006/7 to change the palette *after* having started the scanline counter could be what's throwing it off. Try doing oll the VRAM manipulation *before* setting up the IRQ and see if that solves the problem.
Atomfusion
Posts: 7
Joined: Sat Apr 23, 2022 5:48 pm

MMC3 Test Cartridge

Post by Atomfusion »

Yah I looked at that some by putting E001 at the end, I can reproduce the problem with just this code and mesen event viewer.
No PPU writes
Event viewer shows Mapper register writes all over the screen After enable JMP enable
and invalid opcode then crashes back into reset.

The most straight forward discussion/approach that I have found is here http://bobrost.com/nes/files/mmc3irqs.txt
IRQ vector at end points to IRQ in code E0B8
LDX #$40, STX $4017 ; disables APU Frame Frame IRQ
CLI to start interrupts

Code: Select all

 LDA #$02
    STA $4014       ; set the high byte (02) of the RAM address, start the transfer

    
    ;;;;; Breaks IRQ 
    JMP COLOR1 ; This Breaks the IRQ but can be used if IRQ is set to 0; Can Switch banks in Reset.s to colors 
    ; Enabling sprites and background for left-most 8 pixels


    LDA #$01            ;; disable IRQ Cycle
    STA $E000
    LDA #$80            ;; Set Value for Trigger 
    STA $C000
    STA $C001           ;; Resets Counter for new Line count 
    LDA #$01            ; Enable IRQ 
    STA $E001

    RTI ; Interrupt Return.. RTS for normal Returns 

Code: Select all

    IRQ:
        LDA #$01    ;; disable IRQ
        STA $E000
    RTI
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: MMC3 Test Cartridge

Post by tokumaru »

I just realized you're trying to JMP to COLOR1 rather than JSR to it. This will most definitely crash the program, since COLOR1 ends with an RTS instruction.

You should still set up the scanline counter after you're done using $2006/7 though. Putting only the $E001 write in the end isn't enough, because the counter keeps going regardless of whether IRQs are enabled.
Atomfusion
Posts: 7
Joined: Sat Apr 23, 2022 5:48 pm

Re: MMC3 Test Cartridge

Post by Atomfusion »

Well that's the mistake a Noob makes.. that the compiler thinks is ok

JMP does not return by RTS but JSR does ... This might explain some of my troubles with how I have written the code.

Thank You, I think that observation will fix most of my code and 24 Hours worth of time !
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: MMC3 Test Cartridge

Post by tokumaru »

Atomfusion wrote: Tue Apr 26, 2022 5:42 amWell that's the mistake a Noob makes.. that the compiler thinks is ok
Well, assemblers are different from compilers in the sense that they don't really see any structure in your code, just a stream of instructions and data... In assembly, subroutines, repetition blocks, conditional blocks and so on are human constructs, not bound by the stricter rules of high level languages.
User avatar
Ben Boldt
Posts: 1149
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: MMC3 Test Cartridge

Post by Ben Boldt »

tokumaru wrote: Tue Apr 26, 2022 8:07 am
Atomfusion wrote: Tue Apr 26, 2022 5:42 amWell that's the mistake a Noob makes.. that the compiler thinks is ok
Well, assemblers are different from compilers in the sense that they don't really see any structure in your code, just a stream of instructions and data... In assembly, subroutines, repetition blocks, conditional blocks and so on are human constructs, not bound by the stricter rules of high level languages.
Believe me, you don't have to be a noob to make that mistake. ;)

It is also possible to intentionally manipulate the stack in other ways where there is not always a 1:1 relationship between JSR and RTS.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: MMC3 Test Cartridge

Post by tokumaru »

Ben Boldt wrote: Wed May 11, 2022 10:44 amIt is also possible to intentionally manipulate the stack in other ways where there is not always a 1:1 relationship between JSR and RTS.
It sure is! The most common "abuse" of the stack in this sense is to manually push a return address (minus 1) to the stack and RTS to it. This is a common way to implement jump tables. I use it in my NMI handler to jump from one VRAM update subroutine to the next quickly: my VRAM update buffer contains data interleaved with the addresses of the subroutines that use that data, so the RTS at the end of each subroutine immediately calls the next one.

You can also manually remove a return address from the top of the stack and save it elsewhere, so that later you can put it back on the stack and RTS to it. This can be used to implement threads, since different pieces of logic can effectively pause their execution and have it resumed later.

I've also heard of people putting constant arguments for a subroutine right after the JSR instruction, so the subroutine would get the return address, use it to create a pointer to access the arguments, then increment the address by the size of the arguments, and put it back on the stack, causing the RTS to return to the instruction immediately after the arguments.
Post Reply