[SOLVED]Trying to display all tiles of CHR on screen

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

Haruka
Posts: 53
Joined: Fri Mar 23, 2018 8:58 pm

[SOLVED]Trying to display all tiles of CHR on screen

Post by Haruka »

Hello everyone. I'm trying to display all tiles of CHR on the screen that an NROM can hold.

Here is the CHR I've made:
0000.png
Here is the code I've written:

Code: Select all

#define PPUCTRL	(*(unsigned char*)0x2000)
#define PPUMASK	(*(unsigned char*)0x2001)
#define PPUSTAT	(*(unsigned char*)0x2002)
#define PPUADDR	(*(unsigned char*)0x2006)
#define PPUDATA	(*(unsigned char*)0x2007)


static const unsigned char bg_palette[16] = {0x0F, 0x00, 0x10, 0x20,	0x01, 0x11, 0x21, 0x31,		0x05, 0x15, 0x25, 0x35,		0x0A, 0x1A, 0x2A, 0x3A};
static const unsigned char spr_palette[16] = {0x0F, 0x13, 0x23, 0x33,	0x01, 0x71, 0x27, 0x37,		0x05, 0x1C, 0x2C, 0x3C,		0x0A, 0x19, 0x29, 0x39};


void isr_NMI(void);


static void waitForVblank(void){
	while((PPUSTAT & 0x80) != 0);
}
 
static void drawScreen_C(void){
	unsigned char i = 0, j = 0;
	unsigned char idx = 0;
	for(i=0;i<16;++i){
		unsigned short addr = (unsigned short)(0x2000 + i * 32);
		PPUCTRL = (unsigned char)0x80;
		PPUADDR = (unsigned char)((addr >> 8) & 0xFF);
		PPUADDR = (unsigned char)((addr >> 0) & 0xFF);
		for(j=0;j<16;++j){
			PPUDATA = idx++;
		}
	}
	idx = 0;
	for(i=16;i<30;++i){
		unsigned short addr = (unsigned short)(0x2000 + i * 32);
		PPUCTRL = (unsigned char)0x90;
		PPUADDR = (unsigned char)((addr >> 8) & 0xFF);
		PPUADDR = (unsigned char)((addr >> 0) & 0xFF);
		for(j=0;j<16;++j){
			PPUDATA = idx++;
		}
	}
}

void isr_NMI(){
	//drawScreen_C();
}

void main(void){
	unsigned char i;

	// enable vblank
	PPUCTRL = (unsigned char)0x80;

	// change palette during vblank
	waitForVblank();

	// set palette
	PPUADDR = (unsigned char)0x3F;
	PPUADDR = (unsigned char)0x00;
	for (i = 0; i < 16; ++i){
		PPUDATA = *(bg_palette + i);
	}
	for (i = 0; i < 16; ++i){
		PPUDATA = *(spr_palette + i);
	}

	drawScreen_C();
	
	// turn on rendering
	PPUMASK = (unsigned char)0x0E;

	while(1){
		
	}
}
What I expected:
0001.png
0001.png (29.85 KiB) Viewed 5210 times
What I finally got:
0002.png
0002.png (31.27 KiB) Viewed 5210 times
Could anybody give me any hint and tell me how to achieve my goal? Thank you!
Last edited by Haruka on Thu Nov 08, 2018 6:00 pm, edited 1 time in total.
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: Trying to display all tiles of CHR on screen

Post by Banshaku »

You cannot show the content of both pattern table at the same time: one is for Background and one is for sprites.

You can only show the content of one pattern table for the BG, since 1 pattern table has 256 values and those index refer to the currently selected pattern table. This means your program is working properly, since your first write the BG data, it shows the content of the first pattern table, then you switched pattern table, now the BG is updated to show the content of the newly selected pattern table, then you writing index 0-256, which show the content of the currently selected pattern table. So both will show the content of the selected pattern table, which is the expected behavior.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Trying to display all tiles of CHR on screen

Post by rainwarrior »

You could do a sprite 0 split halfway through the screen to change the tiles below a given scanline, but the easier way might be just to hook up bit 4 of $2000 to a controller button so you can flip between the 2 pages easily.

Edit: Just to clarify, that bit does not affect what value you write to the nametable backgrounds. I notice you flip it when you write the second half. The big only affects what is displayed at time of rendering, it doesn't affect the stored nametable data at all.
Haruka
Posts: 53
Joined: Fri Mar 23, 2018 8:58 pm

Re: Trying to display all tiles of CHR on screen

Post by Haruka »

rainwarrior wrote:You could do a sprite 0 split halfway through the screen to change the tiles below a given scanline
Well, I believe that is indeed something advanced technique. I'll continue reading the tutorials and trying my best to see if I can make it.
rainwarrior wrote:but the easier way might be just to hook up bit 4 of $2000 to a controller button so you can flip between the 2 pages easily.
Actually displaying the 2 pages of pattern table is not my final goal.
I'm recently planning to make an interesting cartridge that contains a powerful MCU on board, which generates images in realtime, and converts the image data to "pattern data format of NES PPU" and finally flush the data chunk to CHR RAM every NMI. The MCU does nearly all the work (almost instantly compared to NES itself due to its strong power and fast speed), the NES only needs to display the 2 pages of pattern table on the screen with a well organized nametable.
Now you see, I encounterd the problem.
That's the whole story.
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: Trying to display all tiles of CHR on screen

Post by Banshaku »

With a memory controller like an mmc3, what you would do is to change the content of the chr bank at a specific location and it would allow to continue to show the content of another bank. With the scanline counter and proper timed code, it wouldn't be an issue. This is how you would display a pictures that exceed the amount of 256 tiles per pattern table.
Haruka
Posts: 53
Joined: Fri Mar 23, 2018 8:58 pm

Re: Trying to display all tiles of CHR on screen

Post by Haruka »

MMC3 is a good idea. But currently I don't know how to manipulate MMC3. Maybe I could try searching some sample code.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Trying to display all tiles of CHR on screen

Post by rainwarrior »

If you're planning on running all the code in a separate CPU you could just make the NES NOP for half the frame and then write to $2000 to switch the tiles at that point, no need for even an IRQ or waiting on a sprite.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Trying to display all tiles of CHR on screen

Post by tokumaru »

rainwarrior wrote:If you're planning on running all the code in a separate CPU you could just make the NES NOP for half the frame and then write to $2000 to switch the tiles at that point, no need for even an IRQ or waiting on a sprite.
But you still have to sync with the end of vblank in order to do this, unless the NMI handler executes in a constant number of cycles. The sprite 0 hit flag and the sprite overflow flag can be used to detect the end of vblank, since that's when those flags get cleared.

Here's a question: if you're generating the patterns externally, why are you restricting yourself to using only 512 tiles? If you use 16KB of CHR-RAM, instead of 8KB, you'll have enough tiles to fill the entire screen with unique graphics. The only problem is that the NES can only address 8KB by itself, you'd need a mapper (something as simple/cheap as CNROM will do) to switch the rest of the CHR-RAM.
Haruka
Posts: 53
Joined: Fri Mar 23, 2018 8:58 pm

Re: Trying to display all tiles of CHR on screen

Post by Haruka »

tokumaru wrote: Here's a question: if you're generating the patterns externally, why are you restricting yourself to using only 512 tiles?
Because 8KB of CHR RAM is enough for my requirement.
tokumaru wrote: If you use 16KB of CHR-RAM, instead of 8KB, you'll have enough tiles to fill the entire screen with unique graphics. The only problem is that the NES can only address 8KB by itself, you'd need a mapper (something as simple/cheap as CNROM will do) to switch the rest of the CHR-RAM.
Yes, exactly. however my CNROM cartrigde does not work at all. Once powered on, the CHR ROM or CHR RAM immediately got hot. I believe there must be short-circuit somewhere when I replaced the mask rom chips to flash/sram from my donor cart.
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Re: Trying to display all tiles of CHR on screen

Post by Bregalad »

With a memory controller like an mmc3, what you would do is to change the content of the chr bank at a specific location and it would allow to continue to show the content of another bank. With the scanline counter and proper timed code, it wouldn't be an issue. This is how you would display a pictures that exceed the amount of 256 tiles per pattern table.
To be honnest, if your goal is to have 512 tiles for background instead of the usual 256, and that you're going to do that with a mapper, I'd rather suggest using MMC2 or MMC4 than MMC3. Those mappers were developped exactly for this purpose.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Trying to display all tiles of CHR on screen

Post by Dwedit »

There was a video of someone who used a separate processor to display arbitrary images using the NES PPU, but still had noise in his final image.

https://youtu.be/ar9WRwCiSr0
https://www.youtube.com/watch?v=hTlNVUmBA28
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Trying to display all tiles of CHR on screen

Post by rainwarrior »

tom7's noise isn't an intractable problem though, rasteri had done the same thing a lot more cleanly prior:
viewtopic.php?f=22&t=16807

(There is another form of noise for the dithering, but that was intentionally added. You can verify that it's a stable picture wherever the screen is not moving.)
Bregalad wrote:To be honnest, if your goal is to have 512 tiles for background instead of the usual 256, and that you're going to do that with a mapper, I'd rather suggest using MMC2 or MMC4 than MMC3. Those mappers were developped exactly for this purpose.
I don't think there's a need to have any mapper at all for this intended purpose, plain NROM is good enough for this if 8k of CHR is sufficient.

Just wait for vblank, set page 0, use a countdown loop to wait for half the screen to pass, then set page 1. (Can probably even do this in C without much problem, i.e. inside that while(1) loop.)
Haruka
Posts: 53
Joined: Fri Mar 23, 2018 8:58 pm

Re: Trying to display all tiles of CHR on screen

Post by Haruka »

Bregalad wrote:To be honnest, if your goal is to have 512 tiles for background instead of the usual 256, and that you're going to do that with a mapper, I'd rather suggest using MMC2 or MMC4 than MMC3. Those mappers were developped exactly for this purpose.
MMC3 is much more common than MMC2/MMC4. Easier to find and cheaper to buy.
Haruka
Posts: 53
Joined: Fri Mar 23, 2018 8:58 pm

Re: Trying to display all tiles of CHR on screen

Post by Haruka »

Using sprite 0 hit check perfectly solved my problem!
0000.png
0000.png (18.5 KiB) Viewed 4997 times
0001.png
0001.png (18.84 KiB) Viewed 4997 times
Here is the code:

Code: Select all

#define PPUCTRL	(*(unsigned char*)0x2000)
#define PPUMASK	(*(unsigned char*)0x2001)
#define PPUSTAT	(*(unsigned char*)0x2002)
#define OAMADDR	(*(unsigned char*)0x2003)
#define OAMDATA	(*(unsigned char*)0x2004)
#define PPUADDR	(*(unsigned char*)0x2006)
#define PPUDATA	(*(unsigned char*)0x2007)


static const unsigned char bg_palette[16] = {0x0F, 0x00, 0x10, 0x20,	0x01, 0x11, 0x21, 0x31,		0x05, 0x15, 0x25, 0x35,		0x0A, 0x1A, 0x2A, 0x3A};
static const unsigned char spr_palette[16] = {0x0F, 0x13, 0x23, 0x33,	0x01, 0x71, 0x27, 0x37,		0x05, 0x1C, 0x2C, 0x3C,		0x0A, 0x19, 0x29, 0x39};


void isr_NMI(void);


static void waitForVblank(void){
	while((PPUSTAT & 0x80) != 0);
}

static void fillPalette(void){
	unsigned char i = 0;
	// change palette during vblank
	waitForVblank();
	// set palette
	PPUADDR = (unsigned char)0x3F;
	PPUADDR = (unsigned char)0x00;
	for (i = 0; i < 16; ++i){
		PPUDATA = *(bg_palette + i);
	}
	for (i = 0; i < 16; ++i){
		PPUDATA = *(spr_palette + i);
	}
}

static void drawScreen_C(void){
	unsigned char x = 0, y = 0;
	unsigned char idx = 0;
	unsigned short t = 0;
	idx = 0x40;
	for(y=6;y<15;++y){
		unsigned short addr = (unsigned short)(0x2000 + y * 32 + 6);
		PPUADDR = (unsigned char)((addr >> 8) & 0xFF);
		PPUADDR = (unsigned char)((addr >> 0) & 0xFF);
		for(x=6;x<26;++x){
			PPUDATA = idx++;
		}
		waitForVblank();
	}
	idx = 0x40;
	for(y=15;y<24;++y){
		unsigned short addr = (unsigned short)(0x2000 + y * 32 + 6);
		PPUADDR = (unsigned char)((addr >> 8) & 0xFF);
		PPUADDR = (unsigned char)((addr >> 0) & 0xFF);
		for(x=6;x<26;++x){
			PPUDATA = idx++;
		}
		waitForVblank();
	}
	// fill the left-most column of the screen with non-transparent color for sprite 0 hit check
	for(y=0;y<30;++y){
		unsigned short addr = (unsigned short)(0x2000 + y * 32);
		PPUADDR = (unsigned char)((addr >> 8) & 0xFF);
		PPUADDR = (unsigned char)((addr >> 0) & 0xFF);
		PPUDATA = 1;
	}
}

static void addSprite0(void){
	waitForVblank();
	OAMADDR = 0x00;
	OAMDATA = 119;
	OAMDATA = 1;
	OAMDATA = 0;
	OAMDATA = 0;
}

void isr_NMI(){
	//drawScreen_C();
}

void main(void){
	// enable vblank
	PPUCTRL = (unsigned char)0x80;

	fillPalette();

	drawScreen_C();
	
	addSprite0();
	
	// turn on rendering
	PPUMASK = (unsigned char)0x1E;

	while(1){
		// change pattern table page when sprite 0 hit happened, and back when vblank finished
		if((PPUSTAT & 0x40) != 0){
			PPUCTRL = (unsigned char)0x90;
		}else{
			PPUCTRL = (unsigned char)0x80;
		}
	}
}
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: [SOLVED]Trying to display all tiles of CHR on screen

Post by Dwedit »

Note that sprite 0 hits require....a sprite 0 hit. At least one nontransparent pixel from the sprite needs to overlap at least one nontransparent pixel from the background tile.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Post Reply