gravatar

home

writing an nes game, part 4 2014-10-19 20:00

Writing an NES game - graphics

Now that we can handle input, it's time to learn how to draw things. All drawing on the NES is handled by the PPU. The PPU does sprite-based drawing, meaning that all drawing is two dimensional, and happens in 8x8 blocks. Colors are determined by a 16-color palette, of which a given sprite can only use four colors (transparency being one of the colors). There is a background layer, containing 32x30 tiles, and a set of up to 64 sprites, which can each be either in front of or behind the background.

The PPU has its own memory space and mostly just runs on its own, handling the redrawing automatically every frame. In order to tell it what to do, we have to load various bits of data into its memory. As mentioned in an earlier post, the PPU is only capable of doing one thing at a time, so this drawing must happen at the beginning of the NMI interrupt, when we are guaranteed to be in VBlank.

There are four steps involved in drawing a sprite. First, we need the sprite itself. The pixel data for sprites is stored in the pattern table, and is typically (although not always) stored in CHR-ROM. It contains 16 bytes per sprite, where each pixel contains two bits of data, so each sprite can contain at most four colors (including transparent). Since CHR-ROM banks are 8KB each, this provides enough room for two sets of 256 tiles - typically one set is used for the background and the other set is used for foreground sprites, although this is not required. The patterns are laid out in memory as two chunks of 8 bytes each, where the first 8 bytes correspond to the high bit for the pixel, and the second 8 bytes correspond to the low bit for the pixel (each byte representing a row of pixels in the sprite). To help with generating this sprite data, I have written a script called pnm2chr, which can convert images in the .PBM/.PGM/.PPM formats (which most image editors can produce) into CHR-ROM data, so that you can edit sprites in an image editor instead of a hex editor.

writing an nes game, part 3 2014-10-15 15:30

Writing an NES game - input handling

Now that we have a general idea of how a program for the NES is structured, it'd be useful to get a bit further into the specific capabilities of the NES. One of the pretty basic (and pretty important) ones is reading input from the controllers.

The basic structure of an NES controller is a shift register. To read the controller inputs, you send a signal to the latch pin, which stores the state of the input buttons in the register, and then you send a sequence of signals to the clock pin to put the state of each button on the output in sequentially (A, B, Select, Start, Up, Down, Left, Right). As it turns out, the code you have to write to read from the controller maps pretty exactly to these operations. This is what it looks like:

read_controller1:
  ; memory address $4016 corresponds to the shift register inside the
  ; controller plugged into the first controller port. writing to it sets the
  ; state of the latch pin, and so we set the latch pin high and then low in
  ; order to store the controller state in the shift register.
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016

  ; reading from $4016 reads the output value of the data pin and also sends a
  ; signal to the clock pin in order to put the next bit of data on the output
  ; for the next read. the value that is read has the state of the button in
  ; the low bit, and the upper bits contain various other pieces of
  ; information (such as whether a controller is plugged in at all, etc), so
  ; if we only care about the state of the button we have to mask out
  ; everything else.
read_a:
  LDA $4016
  AND #%00000001
  BEQ read_b
  ; code for if the a button is pressed
read_b:
  LDA $4016
  AND #%00000001
  BEQ read_select
  ; code for if the b button is pressed
read_select:
  LDA $4016
  AND #%00000001
  BEQ read_start
  ; code for if the select button is pressed
read_start:
  LDA $4016
  AND #%00000001
  BEQ read_up
  ; code for if the start button is pressed
read_up:
  LDA $4016
  AND #%00000001
  BEQ read_down
  ; code for if the up button is pressed
read_down:
  LDA $4016
  AND #%00000001
  BEQ read_left
  ; code for if the down button is pressed
read_left:
  LDA $4016
  AND #%00000001
  BEQ read_right
  ; code for if the left button is pressed
read_right:
  LDA $4016
  AND #%00000001
  BEQ end_read_controller1
  ; code for if the right button is pressed

end_read_controller1:
  RTS

writing an nes game, part 2 2014-10-14 15:30

Writing an NES game - code layout

So before we get into the code itself, there are a couple more concepts that will be important to understand, in order to understand why the code is laid out the way it is.

First, we need to understand how the PPU handles drawing. The NES was designed to work on CRT screens, which work by drawing a horizontal line at a time from the top of the screen to the bottom, at which point it starts again at the top. This is done by a device called an electron gun, which fires electrons at a screen of pixels. The key point here is that drawing is done sequentially, one pixel at a time, and the mechanism requires some amount of time to move from one line to the next, and to move from the bottom of the screen back to the top. The period of time when the electron gun is moving from the end of one line to the beginning of the next is called the "HBlank" time, and the period of time when the electron gun is moving from the bottom of the screen back to the top is called the "VBlank" time. Except during HBlank and VBlank, the PPU is busy controlling what actually needs to be drawn, and manipulating it at all can cause all kinds of weird graphical glitches, so we need to make sure we only communicate with the PPU during HBlank or VBlank.

writing an nes game - inside the cartridge 2014-10-14 12:00

As an aside, this is what the inside of an NES cartridge looks like:

cartridge image

Note specifically the two physical ROM banks, one labeled PRG and the other labeled CHR (and basically nothing else on the circuit board).

writing an nes game, part 1 2014-10-13 16:00

Writing an NES game

For the past week or so at Hacker School, I've been learning how to write games for the NES. This was intended to just be a brief debugging detour for a different project I was working on, but as these things tend to go, it turned into an entire project on its own. You can see the end result at https://github.com/doy/nes-snake, but I wanted to go over the code to give an overview about what programming for the NES is like.

The NES itself has three processors - a 6502 CPU, a custom graphics chip called the PPU (picture processing unit), and a custom sound chip called the APU (audio processing unit, which I didn't use for this project). Game data is stored in ROM banks, typically with code and sprite data separate - code is stored in PRG-ROM (which consists of some number of 16KB banks) and sprite data is stored in CHR-ROM (which consists of some number of 8KB banks). The only difference between these two (other than the size) is that PRG-ROM is mapped to the address space of the CPU and CHR-ROM is mapped to the address space of the PPU (the different chips also have different address spaces). My game is simple enough to only require a single PRG-ROM bank and a single CHR-ROM bank.

hacker school, day 1 2014-09-02 19:55

Hacker School, Day 1

So I'm finally starting Hacker School. Today was partly an orientation day, and I spent a while meeting people and stuff, but eventually settled down to work.

For my first project, I decided to work on an IRC bouncer in Rust. I've wanted a decent IRC bouncer for a while - I'm currently using ZNC, which is the only really usable one I've found, but it doesn't really handle disconnection well. I'd like to be able to just close my laptop and go, and actually get all of the messages I missed when I open it back up. The problem is that if you don't explicitly disconnect the IRC client, the bouncer has no way of knowing when you stopped receiving messages, so messages in that timeout window tend to just get dropped, which makes it quite difficult to keep up with conversations.

The solution I'm going to try is to split the bouncer into two parts, a client and a server. The server still runs as usual, but you run a bouncer client locally, and that is what you connect to with your IRC client. The bouncer client then talks to the bouncer server using a different protocol which allows you to sync unread messages reliably.

keyboard mappings 2013-11-02 20:54

So I was at the Pittsburgh Perl Workshop, and John Anderson gave a talk about his personal configuration setup. It motivated me to spend quite a bit of time going over my own configuration, but in particular it reminded me that I had been wanting to adjust my keyboard for a while now. My pinkies have been getting tired more quickly lately, and I'm fairly sure this is in large part because of how often I have to use the Shift and Control keys. I do all of my work on laptops, so it would be pretty inconvenient to get an external keyboard, so I decided to actually put some effort into looking at ways to modify my existing keyboard to be easier to type on.

Control

One of the first things I did was read up ways to avoid finger stress. As it turns out, this is especially common in the Emacs community (since so many of their keyboard shortcuts rely on weird modifier key combinations), and there's even a project dedicated to making Emacs more ergonomic. One of the things that they do mention is that contrary to popular wisdom, mapping Caps Lock to Control really isn't a very good solution. They recommend swapping Control and Alt instead, since Control is used far more often, and you can press the Alt key with your thumb, which is a much stronger finger.