nmi
For the NES to be able to populate our nametables with the proper values, aside to the OAM DMA, we need at least to be able to handle the NMI interrupt. NMI stands for Non-Maskable Interrupt, an interrupt the CPU always has to handle, no matter what.
For an NMI to be flagged, we need two flags to be true: NMI_occured and NMI_output
Start of VBLANK | NMI_occurred | true |
End of VBLANK (after) | NMI_occurred | false |
Read of PPUSTATUS | NMI_occurred | (NMI_occured to bit 7 of PPUSTATUS) then false |
Write to PPUCTRL | NMI_output | = bit 7 of PPUCTRL |
When the CPU handles the NMI, it is supposed to push the Program Counter (PC) onto the stack, then push the Status Register (P) onto the stack. [PC-high-byte -> PC-low-byte -> P]
Then the PC will then be set to memory[0xfffb] << 8 | memory[0xfffa]. So, fetching the new high-byte for PC from 0xfffb and the new low-byte for PC from 0xfffa.
vblank
The VBlank (vertical blanking – the time where the PPU isn’t occupied with drawing pixels), appears from scanlines 241, ppuCyle 1 up until scanline 261, ppuCycle 1. Each scanline consists of 340 ppuCycles.
We acknowledge the VBlank by setting bit 7 of PPUSTATUS to 1 or 0.
void stepPPU() {
// stepPPU
ppuCycles++;
if (ppuCycles > 340) {
ppuCycles -= 341;
ppuScanline++;
}
if (0 <= ppuScanline && ppuScanline <= 239) { // drawing
}
else if (ppuScanline == 241 && ppuCycles == 1) { // VBlank
PPU_STATUS.setVBlank();
NMI_occured = true;
drawNameTables();
handleWindowEvents();
}
else if (ppuScanline == 261 && ppuCycles == 1) { // VBlank off / pre-render line
PPU_STATUS.clearVBlank();
NMI_occured = false;
ppuScanline = 0;
}
}
nametables
With the NMI and VBlank working, we are now able to let the code populate our nametables correctly.
The 4 nametables are part of the VRAM, are of 256 byte size each and are located at 0x2000, 0x2400, 0x2800 and 0x2c00. In some games, nametables are mirrored to other nametables, but we will get to that later on.
Each byte in the nametables represents a tile number, corresponding to the Pattern Tables we finished earlier. Which of the nametables (0 or 1) the tile number is a part of, is dictated by bit 4 of PPUCTRL.
With this information, and a little modification of our drawing routine from the Pattern Tables, we can already display all 4 nametables, and see the demo of “Donkey Kong”.
for (int r = 0; r < 1024; r++) {
for (int col = 0; col < 256; col++) {
uint16_t tile_nr = VRAM[0x2000 + (r / 8 * 32) + (col / 8)];
uint16_t tile_attr = VRAM[0];
uint16_t adr = PPU_CTRL.background_pattern_table_adr_value + (tile_nr * 0x10) + (r % 8);
uint8_t pixel = ((VRAM[adr] >> (7 - (col % 8))) & 1) + (((VRAM[adr + 8] >> (7 - (col % 8))) & 1) * 2);
framebuffer[(r * 256 * 3) + (col * 3)] = COLORS[pixel];
framebuffer[(r * 256 * 3) + (col * 3) + 1] = COLORS[pixel];
framebuffer[(r * 256 * 3) + (col * 3) + 2] = COLORS[pixel];
}
}
SDL_UpdateTexture(texture_nt, NULL, framebuffer, 256 * sizeof(unsigned char) * 3);
SDL_RenderCopy(renderer_nt, texture_nt, NULL, NULL);
SDL_RenderPresent(renderer_nt);
Note: For simplicity's sake, the 4 nametables are drawn below each other, and not in the 2x2 pattern, but we will change this later on. The following gif is cropped to the first / top nametable.
Comments