12. Practical Loops
« 11. More Assembly: Branching and Loops 13. Background Graphics »
Table of Contents:
Now that you know how to create loops for various purposes, it's time to put that knowledge to use cleaning up our existing code. Using loops will make your assembly code cleaner, more readable, and easier to extend in the future.
To make full use of loops, we will pair the looping opcodes we learned last
chapter with a new addressing mode. Back in
Chapter 5, when we first talked
about opcodes, two addressing modes were introduced: absolute mode
(e.g., LDA $8001
) and immediate mode (e.g.,
LDA #$a0
). Now, we will learn a third addressing mode:
indexed mode.
Indexed Mode
Indexed mode combines a fixed, absolute memory address with the variable contents of an index register (hence the name "index register"). To use indexed addressing mode, write a memory address, a comma, and then a register name.
LDA $8000,X
The example code above will fetch the contents of memory address ($8000
- the value of the X register). If the current value of the X register is
$05
, then the commandLDA $8000,X
will fetch the contents of memory address$8005
.
Using indexed mode allows us to perform actions across a range of memory addresses
with ease. As a simple example, here is a code snippet that will set the
256 bytes of memory from $3000
to $30FF
to $00
.
LDA #$00
TAX
clear_zeropage:
STA $3000,X
INX
BNE clear_zeropage
To review, line 1 above sets the accumulator to zero (#$00
),
and then line 2 copies that zero to the X register. Line 4 stores the zero
from the accumulator to memory address ($3000
plus X register), which
will be $3000
the first time through the loop. Line 5 increments the X
register, and then line 6 checks the status of the zero flag in the
processor status register. If the last operation was not equal to zero,
we return to the label at line 3. When we increment the X register from
zero to one, the result of the last operation is one, so the zero
flag will not be set and the loop will repeat again. The next time
through the loop, when we reach line 4, the zero from the accumulator
will be stored again at memory address ($3000
plus X register), which
will now be memory address $3001
. The loop will repeat until
the X register is already $ff
and the increment at line 5
changes the X register to $00
.
Loading Palettes and Sprites
Now that you understand indexed mode, let's use it to simplify our existing code for loading palettes and sprites. In our existing code from Chapter 10, palette and sprite loading is tedious, repetitive, and error-prone. This is in large part because the code tightly mixes data and logic. By using loops and indexed addressing, we can separate the palette and sprite data from the code that sends that data to the PPU, making it easier to update the data without inadvertently breaking things.
Our code from Chapter 10 to load palette data looks like this:
21 ; write a palette
22 LDX PPUSTATUS
23 LDX #$3f
24 STX PPUADDR
25 LDX #$00
26 STX PPUADDR
27 LDA #$29
28 STA PPUDATA
29 LDA #$19
30 STA PPUDATA
31 LDA #$09
32 STA PPUDATA
33 LDA #$0f
34 STA PPUDATA
Let's separate out the palette values and store them somewhere else.
The palette values here are read-only data, so we will store them
in the RODATA
segment and not in the current CODE
segment. It will look something like this:
60 .segment "RODATA"
61 palettes:
62 .byte $29, $19, $09, $0f
We set a label (palettes
) to easily identify the start of our
palette data, and then we use the .byte
directive to tell the
assembler "what follows is a series of plain data bytes, do not try to
interpret them as opcodes".
Next, we will need to adjust our palette-writing code to loop over the
data in RODATA
. We'll keep lines 21-26 above that set the
PPU address to $3f00
, but starting at line 27, we'll make
use of a loop:
27 load_palettes:
28 LDA palettes,X
29 STA PPUDATA
30 INX
31 CPX #$04
32 BNE load_palettes
Instead of hard-coding each palette value, we load it as "the address of
the palettes
label plus the value of the X register".
By incrementing the X register each time through the loop (INX
),
we can sequentially access all of the palette values.
Note that to end the loop, we are comparing against #$04
.
This ensures that we will run this loop for four, and only four, values.
If we set the comparison operand to something larger, we could end up reading
memory beyond what we intended as palette storage, which can have
unpredictable effects.
Now that our palettes are loading in a cleaner fashion, let's turn
our attention to sprite data. Just like with palettes, we can store
our sprite data in RODATA
and read it with a loop.
The current sprite loading code looks like this:
36 ; write sprite data
37 LDA #$70
38 STA $0200
39 LDA #$05
40 STA $0201
41 LDA #$00
42 STA $0202
43 LDA #$80
44 STA $0203
Following the same process as with the sprites, our new sprite loading code will look like this:
36 ; write sprite data
37 LDX #$00
38 load_sprites:
39 LDA sprites,X
40 STA $0200,X
41 INX
42 CPX #$04
43 BNE load_sprites
This code is subtly different from the palette loading code. Note that on line
40, instead of writing to a fixed address (PPUDATA
), we use indexed
mode to increment the address to write to as well as the address to read from.
One more step: we still need to move our sprite data into RODATA
. Here
is our sprite data, in a much more readable, one-line-per-sprite format:
63 sprites:
64 .byte $70, $05, $00, $80
Homework
Now that you have seen how to use loops and branching to make assembly code
more readable and maintainable, it's time to try them out for yourself.
Extend the existing code to load four full palettes (with colors of your
choosing) and to draw at least four sprites to the screen. You'll need
to modify the palette and sprite data in RODATA
as well as
change the loop counters in the palette-loading and sprite-loading loops.
Don't forget to re-assemble your source files and link them into a new
.nes file (see the end of
Chapter 8 for a refresher).
All code from this chapter can be downloaded in a zip file.
« 11. More Assembly: Branching and Loops 13. Background Graphics »