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.


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

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:

    ; 2. Output colour data
    ld hl,PaletteData
    ld b,(PaletteDataEnd-PaletteData)
    ld c,$be

We are not setting up the other 14 colours in the background palette, or the sprite palette, because we are not using them.

Read more about the palette


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

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.

    ; 2. Output tile data
    ld hl,FontData              ; Location of tile data
    ld bc,FontDataEnd-FontData  ; Counter for number of bytes to write
        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.

Read more about tiles


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:

    ; 2. Output tilemap data
    ld hl,Message
    ld bc,MessageEnd-Message  ; Counter for number of bytes to write
        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

Read more about the tilemap

Now the graphics are all set up - all the necessary elements are configured in VRAM and CRAM.

< Initialisation | Lesson 1 | Palette >