The famicom is an 8bit video game console by Nintendo.
The famicom notes were created during the production of the NES release of donsol and nespaint, to learn more about programming for the console, see assembly.
NES System Architecture
The NES screen resolution is 256x240.
- ROM: Read Only Memory, holds data that cannot be changed. This is where the game code or graphics is stored on the cart..
- RAM: Random Access Memory, holds data that can be read and written. When power is removed, the chip is erased. A battery can be used to keep power and data valid..
- PRG: Program memory, the code for the game.
- CHR: Character memory, the data for graphics.
- CPU: Central Processing Unit, the main processor chip.
- PPU: Picture Processing Unit, the graphics chip.
- APU: Audio Processing Unit, the sound chip inside the CPU.
6502 Processor Overview
$0000-0800 | Internal RAM, 2KB chip in the NES | |
$2000-2007 | PPU access ports | |
$2000 | PPUCTRL | |
$2001 | PPUMASK | |
$2002 | PPUSTATUS | |
$2003 | SPRADDR | |
$2005 | PPUSCROLL | |
$2006 | PPUADDR | |
$2007 | PPUDATA | |
$4000-4015 | Audio access ports | |
$4000-4003 | APUCH1(Pulse1) | |
$4004-4007 | APUCH2(Pulse2) | |
$4008-400B | APUCH2(Triangle) | |
$400C-400F | APUCH2(Noise) | |
$4010-4013 | APUCH2(DCM) | |
$4015 | SNDCHN | |
$4016-4017 | Controllers access ports | |
$4016 | JOY1 | |
$4017 | JOY2 | |
$6000-7FFF | Optional WRAM inside the game cart | |
$8000-FFFF | Game cart ROM |
Backgrounds
To make graphics on the screen you must write graphic data to the PPU memory, but you can't write directly to PPU memory, you have to use PPU ports $2006 and $2007. By using $2006 you declare the address of PPU memory then by using $2007 you write the desired value to that address, PPU Memory addresses are 16bit starting from $0000~$3FFF(0000-1fff = tiles & 2000-23ff = nametable 0).
Hex | high byte($4A) | low byte($0F) |
$4A0F | 01001010 | 00001111 |
So you need to write twice to $2006 to declare it's address, the first write declares high byte of address, the second write declares the low byte of address. Each time you write a value to $2007, the PPU address is automatically adjusted to the next address, so you don't need to declare the PPU address with $2006 for sequential PPU memory addresses.
LDA #$20 ; high byte STA $2006 LDA #$00 ; low byte STA $2006 LDA #$04 ; sprite-id STA $2007
Calculate at what address to draw it: $2000 plus 32 times the vertical position of the tile (in 8-pixel units) plus the horizontal position of the tile (in 8-pixel units), write the high byte of the address to $2006: this is usually values $20 to $23, and write the low byte of the address to $2006. In other words, calculate the tile offset (TileY * 32 + TileX) and then add the base address. This will give you a pointer you can use to access any part of the map.
Palette Codes
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1A | 1B | 1C | 1D | 1E | 1F |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 2A | 2B | 2C | 2D | 2E | 2F |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 3A | 3B | 3C | 3D | 3E | 3F |
Audio
The NES and Famicom use a set of memory mapped registers to configure the 5 different sound channels, we just write byte data to the ports to configure the ports (though we cannot read back).
CH1 | 4000 | Volume | CCLEVVVV | vol env len dutycycle |
4001 | Sweep | EUUUDSSS | sweep direction rate enabled | |
4002 | Frequency | LLLLLLLL | freqLbyte | |
4003 | Length | CCCCCHHH | freqHbyte counter | |
CH2 | 4004 | Volume | CCLEVVVV | vol env len dutycycle |
4005 | Sweep | EUUUDSSS | sweep direction rate enabled | |
4006 | Frequency | LLLLLLLL | freqLbyte | |
4007 | Length | CCCCCHHH | freqHbyte counter | |
CH3 | 4008 | Counter | CLLLLLLL | clock count |
4009 | Sweep | ------ | unused | |
400a | Frequency | LLLLLLLL | freqLbyte | |
400b | Length | CCCCCHHH | freqHbyte counter | |
CH4 | 400c | Volume | CCLEVVVV | vol env len dutycycle |
400d | Sweep | ------ | unused | |
400e | Frequency | LLLLLLLL | freqLbyte | |
400f | Length | CCCCCHHH | freqHbyte counter | |
CH5 | 4010 | Play mode | IL-FFFF | irqenable loopfreq |
4011 | Delta | -DDDDDDD | 7bit PCM Data | |
4012 | Address | AAAAAAAA | Address $C000+(A*$40) | |
4013 | Length | LLLLLLLLL | Length (L*$10)+1 Bytes |
To produce a sound, first we enable the channel via $4015.
LDA #%00000010 ; Enable Channel2(Pulse2) STA $4015
Then we write to the Square 2 ports:
LDA #%00111000 ; Duty Cycle 00, Volume 8 (half volume) STA $4004 LDA #<$356 ; 356 = C2 STA $4006 LDA #>$356 STA $4007
The following table holds all values of every note on every octave that the NTSC NES can produce for the Square and Triangle Wave. These will be listed as 11 bit values that can be stored into the sound registers. All values are rounded to the nearest number. Note that for the Triangle Wave, these values will make a pitch one octave below that of the Square Wave.
A | $7F1 | $3F8 | $1FB | $0FD | $07E | $03F | $01F | $00F |
---|---|---|---|---|---|---|---|---|
B# | $780 | $3BF | $1DF | $0EF | $077 | $03B | $01D | $00E |
B | $713 | $389 | $1C4 | $0E2 | $070 | $038 | $01B | $00D |
C | $6AD | $356 | $1AB | $0D2 | $06A | $034 | $01A | $00C |
D# | $64D | $326 | $193 | $0C9 | $064 | $031 | $018 | $00C |
D | $5F3 | $2F9 | $17C | $0BD | $05E | $02F | $017 | $00B |
E# | $59D | $2CE | $167 | $0B3 | $059 | $02C | $015 | $00A |
E | $54D | $2A6 | $152 | $0A9 | $054 | $029 | $014 | $00A |
F | $500 | $27F | $13F | $09F | $04F | $027 | $013 | $009 |
G# | $4B8 | $25C | $12D | $096 | $04B | $025 | $012 | $008 |
G | $475 | $23A | $11C | $08E | $046 | $023 | $011 | ---- |
A# | $435 | $21A | $10C | $086 | $042 | $021 | $010 | ---- |
Controller Ports
The controllers are accessed through memory port addresses $4016 and $4017. First you have to write the value $01 then the value $00 to port $4016. This tells the controllers to latch the current button positions. Then you read from $4016 for first player or $4017 for second player. The buttons are sent one at a time, in bit 0. If bit 0 is 0, the button is not pressed. If bit 0 is 1, the button is pressed.
Button status for each controller is returned in the following order: A, B, Select, Start, Up, Down, Left, Right.
Mapping
Some cartridges have a CHR ROM, which holds a fixed set of graphics tile data available to the PPU from the moment it turns on. Other cartridges have a CHR RAM that holds data that the CPU has copied from PRG ROM through a port on the PPU.
nrom | NROM consists of a 16 kilobyte or 32 kilobyte program ROM, a 4 kilobyte or 8 kilobyte graphics ROM, and an NES lockout chip. The address pins on the NES are wired directly to the ROM with no mapper hardware intervening. There is no support for extra work RAM. Any ROM with a size of 40 KB or less is most likely an NROM. |
---|---|
cnrom | CNROM is similar to NROM except that writes to the program area of the ROM go to a 74LS161 register that controls the most significant bits of the graphics ROM's address bus, allowing it to be bankswitched in 8 KB chunks. There are also some somewhat sneaky ways to stream map data out of the graphics ROM, making for a larger game. With a ROM size of 32 KB and a graphics ROM size of 16 KB or 32 KB (or higher on the Panesian CNROM clone), most CNROMs are 64 KB or smaller. |
unrom | Using RAM instead of ROM in a system designed for ROM fonts was the main innovation of UNROM. Programs would write through the PPU to the graphics RAM whenever the screen was turned off (such as during vblank or slight pauses in the action). UNROM let game maps get big. It also allowed for RLE compression of graphics data, as graphics no longer had to be stored in the raw form needed by the PPU. |
incoming donsol dexe nasu dito donsol famicom spacetime 6502 nespaint 6502 chr format uxn devlog