To start programming as quickly as possible, we’ll walk through a simple C program that displays text on the NES screen.
Most programming tutorials include a simple “Hello, World!” example, which prints a single line of text to the console. Usually this only takes a few lines of code, but on the NES, it’s a little more complicated.
View Code
#include "neslib.h" void main(void) { // set palette colors pal_col(0,0x02); // set screen to dark blue // enable PPU rendering (turn on screen) ppu_on_all(); // infinite loop while (1) ; }
First, we include the NESLib header file. NESLib is a C library that gives us NES programming functions:
#include "neslib.h"
We also need to include a pattern table, which contains the bitmap
graphics used by the game. The IDE includes a generic pattern table with
letters and numbers. We’ll include it with a special #link
command:
//#link "chr_generic.s"
When our game starts up, it runs the main() function. This, in turn, calls other functions which initialize the system and start our game.
void main(void) {
...
}
The first thing our main function will do is set the color of the entire screen. This is done with the pal_col() function:
pal_col(0,0x02); // set screen to dark blue
We set it to hexadecimal 0x02
, which translates into dark blue.
NES graphics are made out of 8x8 tiles. Each tile can have three different colors, chosen from a palette of 64 colors. Let’s set the palette colors now:
pal_col(1,0x14); // pink
pal_col(2,0x20); // grey
pal_col(3,0x30); // white
These tiles are arranged in a 32 column by 30 row grid called a nametable. The nametable chooses characters from the pattern table that go into a scrollable background layer.
To display our message, we need to put the bytes that comprise the text
string HELLO, WORLD!
into the nametable.
Writing to video memory requires two steps. First, we set the address to be written with the vram_adr() function.
vram_adr(NTADR_A(2,2));
NTADR_A@ is a C \term{macro} which calculates the \term{address} of a given column and row in a nametable. In our example, we've placed our @HELLO, WORLD!
on column two, line two.
Next, we call the vram_write() function, passing our HELLO, WORLD!
string along with the number of bytes to write into video memory — 13
characters, in this case.
vram_write("HELLO, WORLD!", 13);
When our program starts, the screen is turned off. To display our text, we have to turn on the Picture Processing Unit (or PPU):
ppu_on_all();
We don’t have anything else to do in this demo, so we enter an infinite loop:
while (1);
This keeps the CPU from exiting the main() function, which isn’t allowed by our NES library — games are supposed to run forever, or at least until the console is reset.
The CPU will remain busy inside the infinite loop, but the PPU will continue to run, outputting frames of video to the emulated CRT. You should see the following message on the emulator screen:
View Code
#include "neslib.h" //#link "chr_generic.s" void main(void) { // set palette colors pal_col(0,0x02); // set screen to dark blue pal_col(1,0x14); // fuchsia pal_col(2,0x20); // grey pal_col(3,0x30); // white // write text to name table vram_adr(NTADR_A(2,2)); // set address vram_write("HELLO, WORLD!", 13); // write bytes to video RAM // enable PPU rendering (turn on screen) ppu_on_all(); // infinite loop while (1) ; }
Tweaking the Code¶
Now let’s make a few changes to the example. Suppose we want the
Hello, World!
message to show up for just 10 seconds and then
disappear.
To do this, we’ll have to count video frames in a loop. We’ll declare a
local variable called x
to hold the loop counter value.
Our C compiler requires us to declare all local variables at the
beginning of a function, so we start by declaring x
:
void main(void) {
int x; // <-- add this line
We’ll use a for
loop to count from 0 to 499. Each loop iteration, we
call ppu_wait_frame(). This function keeps the CPU busy until the
next frame starts, which happens 50 times a second (more on this in
Chapter [chap:scrolling]{reference-type=“ref”
reference=“chap:scrolling”}).
for (x=0; x<500; x++) { // <-- add these lines
ppu_wait_frame(); // <-- after
} // <-- ppu_on_all()
After the loop ends, we call ppu_off() to turn the screen off:
ppu_off(); // <-- add this line
We’ll review this C example in more detail in Chapter [chap:helloc]{reference-type=“ref” reference=“chap:helloc”}. In the meantime, let’s move onto reviewing some essential prerequisites for NES programming, starting with binary numbers.
To revert your changes back to the original code, select **File** from
the menu, choose **Revert to Original\...** and click **OK** when
prompted.
If you're already familiar with binary and hexadecimal numbers, logical
and shift operations, and unsigned vs. signed arithmetic, you can safely
skip to the next chapter.
Atari 2600 Stuff¶
View Code
.include "vcs-ca65.h" .segment "VECTORS" VecNMI: .word Reset VecReset: .word Reset VecBRK: .word Reset .code Reset: CLEAN_START nop jmp Reset
The INCLUDES option¶
View Code
---delete nop ---after CLEAN_START lda #$30 sta COLUBK
An Example ASM Program¶
First we’re going to initialize the cart.
10a = 2
11print('my 1st line')
12print(f'my {a}nd line')
View Code
include "cartheader.dasm" Start sei ; turn off interrupts ldy #5 sty $d020 ; reset border color Wait1 jmp Wait1 ; endless loop
Why do we highlight??
.. code-block:: ca65
Foo: lda #1
rts
View Code
include "cartheader.dasm" ; variables Temp equ $03 ; program start Start sei ; turn off interrupts ldy #0 sty $d020 ; reset border color Loop lda Message,y ; load message byte beq EOM ; 0 = end of string clc adc #$c0 ; + 192 sta $400+41,y ; store to screen iny bne Loop ; next character EOM Wait1 lda $d011 bmi Wait1 ; wait for line < 256 Wait2 lda $d012 ; get current scanline Wait3 cmp $d012 beq Wait3 ; wait for scanline to change lsr lsr clc adc Temp sta $d020 ; set border color lda $d011 ; get status bits bpl Wait2 ; repeat until line >= 256 sty $d020 ; reset border color dec Temp ; scroll colors jmp Wait1 ; endless loop Message ; PETSCII - http://sta.c64.org/cbm64pet.html byte "HELLO`WORLDa" byte 0
Cool, eh??