Graphics on the Master System are built up from four things:
- Palette
- Tiles
- Tilemap
- Sprite table
To make the background, a combination of the palette, tiles and the tilemap are used. To make the sprites - i.e. the player, enemies, bullets, etc - we use the palette, tiles and the sprite table.
For Hello World, we are using just the background. All of the graphical element are stored in special RAM belonging to the VDP (the graphics chip). To get it there, the CPU communicates with the VDP using ports $bf and $be.
Palette
The palette defines which colours we can use. For Hello World, there are only two colours: black and white. Similarly to the VDP initialisation data, we want to store the necessary data in the ROM, and transfer it to the VDP.
.db $00,$3f ; Black, white
PaletteDataEnd:
We must tell the VDP we want to write to the palette (sometimes called CRAM, Colour RAM). We do this similarly to how we set the VRAM address before we wrote 16KB of zeroes, but this time the address is the palette index (we want to start at 0), and the high two bits must be set, which we achieve by ORing with $c000 (it is $4000 to choose the VRAM write address).
; Load palette
;==============================================================
; 1. Set VRAM write address to CRAM (palette) address 0 (for palette index 0)
; by outputting $c000 ORed with $0000
ld a,$00
out ($bf),a
ld a,$c0
out ($bf),a
Because we have a small amount of data (less than 256 bytes - actually, it's only two bytes) we can use the otir
instruction again, to output it to the VDP data port $be:
ld hl,PaletteData
ld b,(PaletteDataEnd-PaletteData)
ld c,$be
otir
We are not setting up the other 14 colours in the background palette, or the sprite palette, because we are not using them.
Tiles
Tiles are the building blocks of the graphics on the Master System. We must define all of the graphics in the form of 8x8 tiles; because we are drawing text, we will define one tile per character, to give us our font. The font data is stored in ROM; it's rather large, so I will not reproduce it all here.
.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
...
.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
FontDataEnd:
Tiles are loaded into VRAM at address $0000. We set the address as before:
; Load tiles (font)
;==============================================================
; 1. Set VRAM write address to tile index 0
; by outputting $4000 ORed with $0000
ld a,$00
out ($bf),a
ld a,$40
out ($bf),a
Because the tile data is rather large, we can't just use otir
to output it, because otir
can only count through register b
, which is 8 bits so the maximum is 256. Instead we must use a register pair, similarly to how we used one to count through 16KB when blanking VRAM. The difference is, this time we're reading the data from ROM instead of always outputting zeroes.
ld hl,FontData ; Location of tile data
ld bc,FontDataEnd-FontData ; Counter for number of bytes to write
WriteTilesLoop:
ld a,(hl) ; Get data byte
out ($be),a ; Output it
inc hl ; Add one to hl so it points to the next data byte
dec bc ; Decrement the counter and repeat until it's zero
ld a,b
or c
jp nz,WriteTilesLoop
This is the most complex bit so far, so let's go over it carefully. The first two lines are setting up two register pairs - bc
and hl
- with some numbers. hl
stores the location of the data, and bc
the length of the data (in bytes).
Then we read a byte of data into register a
. The brackets around (hl)
signify indirection - that means, we don't load a
with the value of hl
(that wouldn't make sense, since they are different numbers of bits anyway), instead we load a
with the value at the memory address contained in hl
. That means that we will get the tile data we want. Next we output it to the VDP data port, so effectively we have copied it from ROM to a register, and then to VRAM.
Next we increment hl
. That means we add one to it. That way, next time we read in, it will get us the next byte. There is no need to increment the VRAM address because the VDP does that automatically.
Finally we decrement the counter and loop if the result is not zero, as before. This means we will output exactly the right number of bytes of tile data.
This is similar to how otir
works underneath; the difference is, our version uses a 16-bit counter, and we specify the port number each time instead of using register c
to hold it.
Tilemap
Finally we output the tilemap. This tells the VDP which tile to put at which location on the screen. While the result is text, the Master System doesn't understand text (e.g. ASCII); we have to tell it the tile numbers. Additionally, it wants some extra information about how to display each tile. So the message is stored in the format the VDP understands, and we simply copy it from ROM to VRAM again. The destination VRAM address for the tilemap is $3800. We set the address again:
; Write text to name table
;==============================================================
; 1. Set VRAM write address to tilemap index 0
; by outputting $4000 ORed with $3800+0
ld a,$00
out ($bf),a
ld a,$38|$40
out ($bf),a
Then we copy the data from ROM to VRAM as before:
ld hl,Message
ld bc,MessageEnd-Message ; Counter for number of bytes to write
WriteTextLoop:
ld a,(hl) ; Get data byte
out ($be),a
inc hl ; Point to next letter
dec bc
ld a,b
or c
jp nz,WriteTextLoop
Now the graphics are all set up - all the necessary elements are configured in VRAM and CRAM.
< Initialisation | Lesson 1 | Palette >