Tiles and palettes
The basic units that make all graphics on the Mega Drive are tiles (the tiny images making up larger ones) and palettes (the colors used by the images).
I suggest to get an emulator that can show you the video memory contents (like BlastEm or Regen), since you won't see anything on screen yet but you can check if the graphics got loaded into memory.
- What's a tile?
- What's a palette?
- Loading tiles into video RAM
- Loading palettes into color RAM
- Background color
What's a tile?
Every graphic you see on screen (sprites, foreground, background, etc.) is made out of tiles. A tile is an 8×8 image that can have up to 15 colors (and transparent pixels). Every possible color has a number associated with it (see palettes below).
A tile with the letter "A" in it may look like this:

Larger graphics are achieved by putting multiple tiles together:

In memory, tiles are stored as 32 bytes, where every 4 byte (a longword) represents a row, and every four bits is a pixel (which conveniently happens to be 1 hexadecimal digit per pixel). The letter "A" tile shown above looks like this in memory:
LetterA:
dc.l $00111000 ; 0 = background
dc.l $01101100 ; 1 = darker shade
dc.l $22000220 ; 2 = lighter shade
dc.l $22000220
dc.l $11111110
dc.l $11000110
dc.l $11000110
dc.l $00000000
What's a palette?
The limited amount of memory means you have to pick a set of colors to use. This is called a palette. The Mega Drive can show up to four palettes on screen, and each palette consists of 16 colors (however, the first color is always transparent, unless used as the background color). Tiles may use any of the four palettes.
An example generic palette:

In memory, palettes are 32 bytes each, where every color is two bytes.
Colors are in BGR format
(like RGB but blue comes first). Each of the blue, green and red
components are one hexadecimal digit, though only even numbers are
used (i.e. 02468ACE
). So for example, $E00
is
blue and $048
is brown.
The example palette looks like this (remember first color is always transparent):
Palette:
dc.w $000 ; Transparent
dc.w $6AE,$26A,$026 ; Skin shades
dc.w $E00,$A00,$600 ; Blue shades
dc.w $00E,$00A,$006 ; Red shades
dc.w $0EE,$0AA ; Yellow shades
dc.w $EEE,$AAA,$666,$000 ; Gray shades
Loading tiles into video RAM
To use on screen, tiles are loaded into "video RAM" (VRAM). This memory is 64KB large, having room for up to 2048 unique tiles (however, some of this space will be eaten up by tables, so assume more or less 75% of that amount). Of course you don't need to keep every tile your game ever uses here, only those you need to use right now.
We're going to deal with the VDP ports (the control port and the data port). Let's get some labels for their addresses:
VdpCtrl: equ $C00004 ; VDP control port
VdpData: equ $C00000 ; VDP data port
To copy a tile in memory, first you need to know its location in video memory. Since every tile is 32 bytes, it basically goes like this (note that tile numbers start at 0, not 1):
tile number × 32
Although this being an old CPU, since we're multiplying by a power of two (25 in this case) it's better to use bit shift:
tile number << 5
Now we need to write the tiles to video
memory (we use the SetVramAddrReg
macro from that page).
Note that since 32 bytes is 8 longwords, we directly write all longwords
each iteration of the loop and make our life easier (as well as making
the loop faster).
; d0 = first tile number
; d1 = number of tiles
; a0 = pointer to tiles
LoadTiles:
; Tell VDP where to write
lsl.w #5, d0
SetVramAddrReg d0
; Copy them to VRAM
; 1 iteration = 1 tile
subq.w #1, d1
lea (VdpData), a1
@Loop:
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
dbf d1, @Loop
rts
Loading palettes into color RAM
Palettes get their own unique memory called "color RAM" (CRAM). The procedure to load them is similar however. The palette address is computed in a similar way (since they're also 32 bytes):
palette number << 5
So to copy the palettes we do the same thing as when loading tiles, except this time we use the
SetCramAddrReg
macro since the palettes are in CRAM:
; in d0 = palette number
; in a0 = pointer to palette
LoadPalette:
; Tell VDP where to write
lsl.w #5, d0
SetCramAddrReg d0
; Copy the whole palette
lea (VdpData), a1
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
move.l (a0)+, (a1)
rts
Iwis says
You don't have to write entire palettes, you can write individual color entries too (remember 1 color = 2 bytes). Doing this is left as an exercise to the reader, but the above should give you enough information to figure out how it'd work.
Background color
The background color is the color that's shown on screen where nothing else is drawn (where there are no sprites and neither of the tilemaps is opaque).
The background color can be picked from any of the 64 colors. This is the only time where a "transparent" color can be used (i.e. the first color of a palette). You'll usually want to spend it as another color for the stage background, so you'll probably want to assign it to a stage-specific palette.
The background color is determined by one of the VDP registers
(especifically VDP register
$87xx
). In case you don't already have it, here's a
constant to go with it:
VDPREG_BGCOL: equ $8700
The background color needs the full color number (which includes its palette as well), which as you can guess it's based on its address in color RAM. Thankfully, it's easy to figure out:
(palette number × 16) + color number
Changing the background color is as simply as writing to that register:
and.w #$FF, d0 ; Remove excess bits
or.w #VDPREG_BGCOL, d0 ; Include the register number
move.w d0, (VdpCtrl) ; Write the register
Even easier if the color is fixed, which is a common situation (e.g. this sets the background color to the color 0 of palette 3):
move.w #VDPREG_BGCOL|$30, (VdpCtrl)