Hi everyone, let’s jump right into it. I was pretty busy this last week but was able to begin continue playing around in the NES development space, can’t wait to get into the material, but I’ll I feel like I’m got a little bit more exploring to do. So let’s being where most programmers begin when playing around with a new technology stack to familiar themselves with program execution, “Hello World”!
Text Support
Text support is something I’ve overlooked from the beginning of my computing experiences. When I was younger, I was mildly obsessed with understanding exactly how functions like printf worked. I would take time looking through source code and disassembling binaries to try to track down exactly how this was happening. Well, we’re not working with libc support here! Now the llvm mos sdk does have some semblance of std c/c++ support, but we won’t be exploring it right now primarily because we know: all screen output is PPU based, and there is no default symbol sprite map.
So, about that sprite sheet, how do we get it. I expect there are a number of them already available online, but I also had an interested in understand how to generate them.
Creating the sprite map
Create or look for existing text tile map without restrictive license.
https://www.nesdev.org/wiki/Tools has quite a few tools available for working with sprite maps, but unfortunately in my case, not that many of them have ports for Mac based system (that I am aware). The most portable that I found was, pilbmp2nes.py. We won’t get into the sprite encoding right now, but for the sake of this exercise, just know that the PPU renders sprites in 8×8 or 8×16 and the tile conversion script needs colors to be encoded as index.
I’m not plugging any tools here (I wish it supported NES sprites more directly of course), but I’d purchased Aesprite a long time, so I used it to scale image to 128×64, change color mode to indexed, and export:
Incorporating Sprite Map
To incorporate our sprite map, first we must translate whatever graphic format that we’re using into the NES. This is where pilbmp2nes comes in (remember the file needs to be in index color mode).
python3 pilbmp2nes.py -i ascii.png -o ascii.chr
Once we’ve successfully converted the format, it’s trivial to include this tilemap in our project. All we need to do is include the binary data within an assembly file in the appropriate section.
chars.s
.section .chr_rom,"a",@progbits
.incbin "ascii.chr"
Hello World!
Here’s our simple program.
#include <neslib.h>
const unsigned char palBackground[16]={ 0x0f,0x16,0x26,0x36,0x0f,0x18,0x28,0x38,0x0f,0x19,0x29,0x39,0x0f,0x1c,0x2c,0x3c };
const char hello[] = "Hello World!";
int pos = 0;
int main() {
pal_spr(palSprites);//set palette for sprites
ppu_on_spr();
while(1) {
ppu_wait_nmi();
pos++;
oam_set(0);
for(int i=0;i<sizeof(hello);++i) {
//set a sprite for current ball
oam_spr(pos+(i*8),pos+i/2,hello[i],1);
}
}
return 0;
}
Setup
Our setup is pretty simple here, the PPU loads the color palette for sprites and enables the PPU for sprite rendering
pal_spr(palSprites);//set palette for sprites
ppu_on_spr();
The Loop
For our main game loop, we just loop for infinite. The first call without our loop is pretty important. This call causes the program to wait until PPU issues a vblank non-maskable interrupt to indicate that PPU memory registers will not be accessed during the next 20 scan line cycles. Immediately following this line is where all of the visual manipulation needs to take place.
In our case, all we’re doing is resetting the sprite table index to perform our write operations (oam_spr will increment index, so we need to be sure to reset or we will end up with unintended results).
Finally we assign render locations for our sprites using the direct ascii value as the sprite table index. If you look above you should notice that Aesprite didn’t do any annotations for us to base our selection on, but the layout of the ascii text ended up mapping ascii values to index values directly.
while(1) {
ppu_wait_nmi();
pos++;
oam_set(0);
for(int i=0;i<sizeof(hello);++i) {
//set a sprite for current ball
oam_spr(pos+(i*8),pos+i/2,hello[i],1);
}
}
The Result
We now build our program with
llvm_exec mos-nes-nrom-clang hello_world.c chars.s -lneslib -o hello_world.nes -O3 -flto
and observe our running program
The first thing you might noticed is that our text is not completely rendered! How can this be? Many who have played the nintendo classic are well familiar with sprite flicker during games. I didn’t realize when I first started looking at this a few years ago that this was due to an inherent limitation in the hardware, the PPU can only render pixels from a maximum of 8 sprites on any lines! So until recently, I thought that the PPU was responsible for the sharing render time for sprites on the same line. As you can see from our example, this is simply not the case, but we won’t spend any more time on this problem at this time.
Leave a Reply