This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

CNROM bank switching with cc65

CNROM bank switching with cc65
by on (#159640)
I'm trying to get CHR bank switching to work with cc65, but nothing happens when I write "1" to 0x8000. Can anyone spot the issue?

Linker config:
Code:
SYMBOLS {

   __STACKSIZE__: type = weak, value = $0500; # 5 pages stack

   NES_MAPPER: type = weak, value = 3;          # mapper number
   NES_PRG_BANKS: type = weak, value= 2;          # number of 16K PRG banks, change to 2 for NROM256
   NES_CHR_BANKS: type = weak, value = 2;          # number of 8K CHR banks
   NES_MIRRORING: type = weak, value = 1;          # 0 horizontal, 1 vertical, 8 four screen
}

MEMORY {

    ZP:       start = $0000, size = $0100, type = rw, define = yes;
    HEADER:      start = $0000, size = $0010, file = %O ,fill = yes;
    PRG:       start = $8000, size = $7fc0, file = %O ,fill = yes, define = yes;
    DMC:       start = $ffc0, size = $003a, file = %O, fill = yes;
    VECTORS:      start = $fffa, size = $0006, file = %O, fill = yes;
    CHR:       start = $0000, size = $2000, file = %O, fill = yes;
    CHR2:       start = $0000, size = $2000, file = %O, fill = yes;
    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;
    SAMPLES:  load = DMC,            type = rw;
    CHARS:    load = CHR,            type = rw;
    CHARS2:   load = CHR2,           type = rw;
    BSS:      load = RAM,            type = bss, define = yes;
    HEAP:     load = RAM,            type = bss, optional = yes;
    ZEROPAGE: load = ZP,             type = zp;
}

FEATURES {

    CONDES: segment = INIT,
       type = constructor,
       label = __CONSTRUCTOR_TABLE__,
       count = __CONSTRUCTOR_COUNT__;
    CONDES: segment = RODATA,
       type = destructor,
       label = __DESTRUCTOR_TABLE__,
       count = __DESTRUCTOR_COUNT__;
    CONDES: type = interruptor,
       segment = RODATA,
       label = __INTERRUPTOR_TABLE__,
       count = __INTERRUPTOR_COUNT__;
}


The end of my crt0.s:
Code:
.segment "CHARS"
        .incbin        "tiles.chr"
.segment "CHARS2"
        .incbin        "tiles2.chr"


Switching code:
Code:
#define BANK ((char *) 0x8000)

static void screena() {

//      ppu_off();
        *BANK = 0;
//      ppu_on_all();
}


static void screenb() {

//      ppu_off();
        *BANK = 1;
//      ppu_on_all();
}


The CHR files exist and are correct. At runtime it only displays from the first bank though. Shutting down the PPU for switching didn't make a difference.
Re: CNROM bank switching with cc65
by on (#159641)
I may be misunderstanding the bus conflict page. I understood it as "the write will corrupt that address, and afterwards any read from there will return the new value", which is fine, because that's init code that's not run after startup.
Re: CNROM bank switching with cc65
by on (#159642)
You must write the bank index to a location in ROM that contains the same value you're writing. Games normally have a table for this (e.g. Table: $00, $01, $02, etc.) and use an index register to offset the write address (i.e. TAX, STA Table, X). I'm not sure how that would be done in C.
Re: CNROM bank switching with cc65
by on (#159643)
Yeah, that's the issue, I don't speak 6502 asm. I did consider an u8 var and writing to its address, but in cc65 a write to a pointer is indirect, which breaks many things.
Re: CNROM bank switching with cc65
by on (#159644)
Ha, this works. Thanks!

Code:
static void screena() {

        static const u8 dummy = 0;
        (u8) dummy = 0;
}
Re: CNROM bank switching with cc65
by on (#159645)
Cool, it works when you know what bank you have to switch, but what about when the bank number is a variable? Can you do the same with an array and use the bank number as an index?
Re: CNROM bank switching with cc65
by on (#159646)
Maybe? Can't try now, about to head out. If not, and you really need an array, just make an array of function pointers ;)
Re: CNROM bank switching with cc65
by on (#159649)
FCEUX 2.2.2 had a bus conflict hack that just returned 0 for all bus conflicts instead of AND behaviour, which may have added to the confusion if you were using it for testing. This will be fixed in the next version.

Rather than trying to find some magic combo to generate working code from C, I generally recommend to do all hardware register control directly in assembly. Just make a simple wrapper function, like this:
Code:
; code in an assembly file
.export bankswitch_
bankswitch_:
   tax
   sta bank_table, X
   rts
bank_table:
   .byte $00, $01, $02, $03

// C header
void __fastcall__ bankswitch(unsigned char bank);

// C usage
banskwitch(3);
bankswitch(b); // can use a variable
Re: CNROM bank switching with cc65
by on (#159652)
I can confirm rainwarrior's solution should work, as I used pretty much the exact same code when Family Picross was on CNROM for a short amount of time.