Learn Multi platform 6502 Assembly Programming... For Monsters!
Platform Specific Lessons
Compared to other
systems, the 'Hardware Sprites' of the atari are pretty limited,
and may not be very useful as they are just 8 pixels wide and one
color That said, they may be useful in some cases, you could combine them together to make a 32x32 player sprite - or use them for parallax effects or something! |
![]() |
Atari 800 / 5200
Sprites
The Atari's hardware sprites are very weird!
Basically each sprite is 8 pixels wide and just 2 colors
(1+transparent)... there are 4 'normal' ones that are 8 pixels wide....
and 4 missile sprites that are just 2(!) pixels wide... but we can
position them together to give us 5 sprites.
Despite being 8 pixels wide... each sprite is up to 128 pixels tall
(or 256 in hires mode) - the entire height of the screen!... if you can't
guess this is because the systems is changing the data each rasterline.
The data used to draw the sprite is taken from a single pointer at $D407... if this
pointer is set to $18 then all the sprites will use the $1800-$1FFF range
- the exact address differs depending on whether the Resolution bit of
$D400 is set to 0 or 1... in Res1 Sprites will be at $1800+$400 - $1C00
... or in Res0 $1800+$200 = $1A00
On an Atari 800 where the GTIA is at $D000 this would give the following
addresses for the sprite settings GTIA is at $C000 on the 5200)
Player | Res0 Data | Res1Data | Width | Color | Xpos |
0 | $1A00+ypos | $1C00+ypos | $D008 | $D012 | $D000 |
1 | $1A80+ypos | $1D00+ypos | $D009 | $D013 | $D001 |
2 | $1B00+ypos | $1E00+ypos | $D00A | $D014 | $D002 |
3 | $1B80+ypos | $1F00+ypos | $D00B | $D015 | $D003 |
4 (Missiles) | $1980 | $1B00 | $D00C | $D019* / $D012-$D015 |
$D004-$D007 |
To make use of sprites, we need to
set the addresses shown above for the player sprite attributes, we
also need to turn sprites on! The example code to the right should do the job! note you needto set symbol GTIA to $D000 on the Atari 800, or $C000 on the Atari 5200 |
lda
#%00111110 sta $D400 ;DMA control (SDMCTL) lda #$18 ;Sprites will be at $1800+$300 (or +$180 in low res mode) sta $D407 ;Store player sprite base lda #%00000011 sta GTIA+$001D ;Graphics Control (GRACTL) lda #%00010001 ;Priority: sprite 5 to use color 3 sta GTIA+$1B ;and put sprites in front of background |
Atari 800 / 5200
Sprite Registers
Group | Name | Description | Address A80 | Address A52 | Bits | Notes |
GTIA | HPOSP0 | horizontal position of player 0 | $D000 | $C000 | ||
GTIA | HPOSP1 | horizontal position of player 1 | $D001 | $C001 | ||
GTIA | HPOSP2 | horizontal position of player 2 | $D002 | $C002 | ||
GTIA | HPOSP3 | horizontal position of player 3 | $D003 | $C003 | ||
GTIA | HPOSM0 | horizontal position of missile 0 (Player 4) | $D004 | $C004 | ||
GTIA | HPOSM1 | horizontal position of missile 1 (Player 4) | $D005 | $C005 | ||
GTIA | HPOSM2 | horizontal position of missile 2 (Player 4) | $D006 | $C006 | ||
GTIA | HOPSM3 | horizontal position of missile 3 (Player 4) | $D007 | $C007 | ||
GTIA | SIZEP0 | player 0 size | $D008 | $C008 | ------WW | Width of sprite (0-3) |
GTIA | SIZEP1 | player 1 size | $D009 | $C009 | ------WW | Width of sprite (0-3) |
GTIA | SIZEP2 | player 2 size | $D00A | $C00A | ------WW | Width of sprite (0-3) |
GTIA | SIZEP3 | player 3 size | $D00B | $C00B | ------WW | Width of sprite (0-3) |
GTIA | SIZEM | missile size | $D00C | $C00C | wwWWwwWW | Width of sprite (Need to set all 4 parts) |
GTIA | GRAFP0 | player 0 graphics | $D00D | $C00D | (Used by DMA) | |
GTIA | GRAFP1 | player 1 graphics | $D00E | $C00E | (Used by DMA) | |
GTIA | GRAFP2 | player 2 graphics | $D00F | $C00F | (Used by DMA) | |
GTIA | GRAFP3 | player 3 graphics | $D010 | $C010 | (Used by DMA) | |
GTIA | GRAFM | missile graphics | $D011 | $C011 | (Used by DMA) | |
GTIA | COLPM0 | color/brightness, player/missile 0 | $D012 | $C012 | ||
GTIA | COLPM1 | color/brightness, player/missile 1 | $D013 | $C013 | ||
GTIA | COLPM2 | color/brightness, player/missile 2 | $D014 | $C014 | ||
GTIA | COLPM3 | color/brightness, player/missile 3 | $D015 | $C015 | ||
GTIA | COLPF3 | color/brightness of setcolor 3 / Player 5 (missile) | $D019 | $C019 | ||
GTIA | PRIOR | p/m priority and GTIA mode | $D01B | $C01B | GGmMpppp | G=gtia mode (0=normal) C=multiColor M=Missile (player 5) pppp=priority setting (1=sprites in front 4=behind) |
GTIA | GRACTL | graphics control | $D01D | $C01D | ------L45 | Latch Trigger / Enable 4 player / enable 5 (missiles) |
ANTIC | DMACTL | Direct Memory access control (DMA) | $D400 | $C400 | ||
ANTIC | PMBASE | player/missile address / 256 | $D407 | $C407 |
Coding a Player sprite
(sprite 0-3)
Coding the Missile sprite (sprite 4)
![]() |
The weird hardware of the
Atari probably goes back to the early days of pong!... the 4 player
sprites would be the characters - and the 4 two pixel missiles
would be the balls or bullets for those players. Unfortunately it's not really very impressive by even the standards of the 80s! |
Hardware Sprites
Unlike other systems, Lynx hardware sprites are not an extra layer! the
'Suzy' graphics chip draws the sprite into the Vram area of the 6502's
addressable range
This may leave you wondering why not just do our sprites in software with
the 6502... but the Suzy chip is VERY fast... it's a 16mhz 16 bit chip...
and can even do dynamic scaling of sprites!
Sprites for the Suzy chip have to be held in RAM, and need a 'Sprite
control block' to define the drawing of a sprite... this pointer is passed
to the Suzy chip to get it to draw a sprite
My Akusprite Editor can Export Literal and RLE bitmaps...
but lets take a look at the theory
Sprites can be 'Literal' (plain bmp) or 'RLE compressed' (defined
by bit 7 of byte two of the SCB - SCBCTL1).... the colordepth is
defined in SPRCTL0 (See later) Each line of a sprite starts with a byte - this an offset to the next line... effectively the number of bytes in the line +1 .... effectively the pointer to the next line. 1 or 0 in this position have special meanings!... 0 means the end of the sprite... 1 means the end of the 'quardrent'... note this is optional! Akusprite does not use it! Quadrent rendering is where the sprite is drawn in 4 sections from the middle... with a 1 byte marking each 1/4 of the sprite... (followed by another 'offset to next line' byte) the first quadrent is DownRight (default)... the second quadrent is UpRight the third quadrent is UpLeft)... the fourth quadrent is DownLeft Apparently there is a bug in the hardware - the last bit of each line must be 0! - we should always have a 0 at the end of our sprites to counter it - color 0 is transparent anyway! You can see a Literal Sprite to the right... the Literal bitmap data is in green, and the header bytes are in cyan |
Literal
Sprite Example (BMP) LynxSprite: db $8, $11, $11, $11, $11, $11, $10,0 db $8, $10, $0, $0, $0, $0, $10,0 db $8, $10, $04, $44, $44, $0, $10,0 db $8, $10, $04, $3, $04, $0, $10,0 db $8, $10, $04, $3, $04, $0, $10,0 db $8, $10, $04, $44, $44, $0, $10,0 db $8, $10, $0, $0, $0, $0, $10,0 db $8, $11, $11, $11, $11, $11, $10,0 db 0 |
RLE Sprite
Data is a bit more tricky.... The first byte in a line is again an offset to the next line as before The next BIT will be a 'block definition'... defining what the following data is... 1 marks that the next data will be LITERAL 0 marks that the next data will be RLE The next 4 bits will be the number of pixels to draw-1... so 0 means 1 pixel, and 15 means 16 pixels... we will call this N If the block is RLE the next 1/2/3/4 bits (depending on bitdepth) will be used for the color to fill the next N pixels If the block LITERAL the next N *(1/2/3/4) bits (depending on bitdepth) will be used for the color of the next N pixels the next bit will be the next 'block definition'... this pattern repeats until the line is done. |
4bpp
RLE Sprite Example db $8 (offset to next line) db %01111000,%00000000 (RLE block...16 pixels... Color 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) db %00001001,%10000000 (RLE block...2 pixles... Color 3,3) db %10010000,%10010001,%10000000 (Literal block...3 pixels... Color 1,2,3) (next line starts here) |
Don't worry
about all the work of creating bitmap data, just use AkuSprite
Editor (or similar) to export valid bitmaps for the Lynx... It's worth knowing the theory, but it's unlikely you'd really want to do things yourself. |
![]() |
The Source code
![]() |
Usually
hardware sprites are always onscreen even if the screen/tilemap is
cleared... but the Lynx is different. We'll need to draw all the sprites again for the next frame - which is a pain, but unlike other systems, there is no limit to the number of sprites we have onscreen! |
The Sprite table allows for up to 64 sprites... each one has 4 words of data - making 256 words in total... it's held in VRAM between $7F00 and $7FFF
Vram From | Vram To | Purpose |
$0000 | $03FF | Min Tilemap (Tiles 0-63) |
$0400 | $0FFF | Possible Tilemap (Tiles 64-255) |
$1000 | $7FFF | Tiles 256-2048 |
$7F00 | $7FFF | SATB sprite table |
$8000 | $FFFF | PC-Engine only has 64k, so this is unused |
Word | F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Notes | |
1 | - | - | - | - | - | - | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y=Ypos (64 is first visible line) | |
2 | - | - | - | - | - | - | X | X | X | X | X | X | X | X | X | X | X=Xpos (32 is first visible line) | |
3 | - | - | - | - | - | A | A | A | A | A | A | A | A | A | A | A | A=Address (Top 10 bits $trueaddress>>5 ) | |
4 | YF | - | YS | YS | XF | - | - | XS | F | - | - | - | P | P | P | P | YF=Yflip XF=Xflip YS=Ysize XS=Xsize
F=Foreground (infront of tilemap) P=Palette |
Sprite Definitions
The basic sprite size is 16x16, though larger sprites can be created by tilling them, for up to 32x64.... only neighboring sprites can be tilled.First
16 bytes (Bitplane 1) |
1110000000000111 1000000100000001 1000000100000001 0000000100000000 0000000100000000 0000000100000000 0000000100000000 0000000111111100 0011111110000000 0000000010000000 0000000010000000 0000000010000000 0000000010000000 1000000010000001 1000000010000001 1110000000000111 |
Second
16 bytes (Bitplane 2) |
2220000000000222 2000000200000002 2000000200000002 0000000200000000 0000000200000000 0000000200000000 0000000200000000 0000000222222200 0022222220000000 0000000020000000 0000000020000000 0000000020000000 0000000020000000 2000000020000002 2000000020000002 2220000000000222 |
Third
16 bytes (Bitplane 3) |
3330000000000333 3000000300000003 3000000300000003 0000000300000000 0000000300000000 0000000300000000 0000000300000000 0000000333333300 0033333330000000 0000000030000000 0000000030000000 0000000030000000 0000000030000000 3000000030000003 3000000030000003 3330000000000333 |
Fourth
16 bytes (Bitplane 4) |
4440000000000444 4000000400000004 4000000400000004 0000000400000000 0000000400000000 0000000400000000 0000000400000000 0000000444444400 0044444440000000 0000000040000000 0000000040000000 0000000040000000 0000000040000000 4000000040000004 4000000040000004 4440000000000444 |
Reg | Name | Meaning |
$00 | MAWR | Memory Address Write |
$01 | MARR | Memory Address Read |
$02 | VRR/VWR | Vram Data Write / Vram Data Read |
$13 | SATB | VRAM-SATB Block Transfer Source |
Transfering Sprite Data Transfering Data to VRAM is the same as with tiles, we specify a source in ram, destination in VRAM, and use DefineTiles to transfer the data to the VRAM See this tutorial for details of how DefineTiles works |
![]() |
We're going to create a function called 'SetHardwareSprite' to do
the heavy lifting for us... We'll use zero page entries to define the settings for our sprite A is the hardware sprite number z_IXH/L will define the X position z_IYH/L will define the Y position z_H will set the size & flipping options z_L will select the layer and palette z_DE is the top 11 bits of the address of the sprite data in VRAM (Bits XXXXXXXX XXX-----) - we get them by bitshifting the address with >>5 |
![]() |
Setting a hardware sprite We're going to need to use the memory mapped hardware graphics ports at $0100/2/3 - and the equivalent ST0/1/2 commands when we're writing fixed values First the function needs to select the VRAM address of the sprite settings - to do this we multiply the sprite number by 4, and select the address (Starting at $7Fxx) by writing to Register $00 (with ST0) |
![]() |
We need to select Register $02 (with ST0) as we now want to write
bytes to the $7Fxx range we selected before... We write our Ypos, Xpos, Sprite address and Attributes to vram - as the address autoincs, we don't need to do anything else! |
![]() |
OK, the data is in VRAM, but that's not enough to change the
visible sprites, we need to initiate a copy of the VRAM to the STAB
sprite table... We do this by writing the VRAM address of our sprites ($7F00) to reg $13 |
![]() |
The sprites will now be visible on screen! | ![]() |
![]() |
In this example we've only looked at a 16x16
sprite, but the PC engine can combine tiles to make sprites up to
32x64, we just need to set more patterns and set the bits to make
the sprite bigger. |
Sprites
Sprites
on the nes are defined by 256 bytes of OAM memory- 4 bytes per sprite
The byte is selected by setting the OAM-address with memory location $2003
- effectively with 4x the sprite number... then by writing the 4
bytes to $2004 (the OAM address autoincs)
Byte | Purpose | Bits | Meaning |
1 | Ypos | YYYYYYYY |
|
2 | Tilenum | TTTTTTTT |
|
3 | Attribs | VHB---PP | Vflip Hflip Background priority Palette |
4 | Xpos | XXXXXXXX |
PPU Graphics ports
To directly alter the sprites, we'll use use ports $2003 and $2004...
$2003 selects the OAM address... then we write the 4 bytes to $2004...
this can only be done during VBLANK - so it's better to use a buffer, and
transfer it to VRAM during Vblank.
Port | Name | Bits | Details | Notes |
$2003 | OAMADDR | Sprite address | (0-255) | |
$2004 | OAMDATA | Sprite data (to write to addr, autoincs) |
Pattern Definitions for sprites and tiles
The NES has 2 pattern tables, they are selected with PPU Register $2000 Bit 4 & 3Byte Data | Byte Data | ||
First 8 bytes | 00111100 01111111 01100011 01100011 01111111 01100011 01100011 00000000 |
Second
8 bytes |
00222200 02222222 02200022 02200022 02222222 02200022 02200022 00000000 |
If you want to create NES sprites, my AkuSprite editor can export
valid format data. It's included in the Sources.7z - and is free and open source. |
![]() |
We can set up
sprites in two ways, directly by writing to the OAM, or by making
a buffer, and using the DMA to copy it... The DMA way is better... but first we'll look at the 'simpler' direct way. |
![]() |
Simple Sprite Example
Sprite data in VRAM is the same as tile data, so we can just use
our DefineTiles function to transfer it. We covered DefineTiles in a previous lesson... |
![]() |
||||||||||||||||||||||||||||||
We're going to create a function called 'SetHardwareSprite' to do
the job of talking to the hardware. A is the hardware sprite number z_IXL will define the X position z_IYL will define the Y position z_L will select the flipping option and palette z_E is the tile number First we need to wait for VBLANK - we use the WaitFrame function we wrote before for this... Then we multiply A by 4 - as there are 4 bytes per sprite... we write A into $2003 - selecting the address in the OAM for the sprite we'll change... We write all 4 bytes to $2004 - it automatically increments the destination address in the OAM, so these 4 writes set the sprite data... Writing to the OAM messes up the scroll position, so we have to reset it with our ResetScroll function |
![]() |
||||||||||||||||||||||||||||||
We're going to show a 16x16 sprite... but sprites on the NES are
8x8 - so we're going to need 4 of them to show our image The top corner of the sprite will be at position ($40,$40) - and we're using Palette 0 for the sprite... We're going to use zero page entries z_IXL,z_IYL and z_L to define settings for our 'SetHardwareSprite' function - but we'll use X and Y as temporary registers too - as we build up the 4 sprites. |
![]() |
||||||||||||||||||||||||||||||
We're going to use Hardware sprite 0 for the first part - so we
set A to 0... and we loaded our tile patterns into Tile 192... so we
set z_e to 192 We now call our function to set the sprite. |
![]() |
||||||||||||||||||||||||||||||
We need to do the same for the other 3 hardware sprites... We use the X,Y registers we set up before, and add 8 to offset them for the new locations, we also INC z_E to alter the tile number, we set A each time for the hardware sprite numbers
|
![]() |
The Code above
works... but waiting for the VBLANK all the time is not a
realistic way of doing things... instead we can allocate 256 bytes
of ram, write changes to the OAM there, then send them all in one
go using a DMA! Lets learn how! |
![]() |
Using a buffer, and copying during Vblank
Address | Purpose | Bits | Detail |
4014h | SPR-RAM
DMA
Register (W) |
HHHHHHHH |
High
byte of ram address to copy to OAM, EG: $02 copies $0200-$02FF |
Sprite Definitions - Overview
Sprites use as special bank of 512 bytes of 'OAM' memory for their
definitions... they also use standard VRAM for the pattern data.
In theory the Pattern data can be relocated... but in practice it's best
to just assume it's at $4000 (address in 16 bit words)
Sprites can be various sizes - a 'default size' is set for all sprites...
and certain selected sprites can be double size...
this is, however a bit tricky... lets say you have the default size as
8x8... and one double size 16,16 sprite
If we point this sprite 'double size' 16x16 sprite to pattern 'Tile
0', the 4 8x8 chunks will be made up of tile numbers:
1 | 2 |
16 | 17 |
Lets look at this example of a 16x16
sprite in AkuSprite Editor... Akusprite editor is designed for 8x8
sprites, but we can export a 16x16 one in the following way If we want to export this quickly, so we can use it as a single doublesize sprite, one option is to tick the 'FixedSize' tickbox, and set the size to 128,16 This will export the sprite correctly - of course there will be a lot of unused space in the exported file... so we would want to combine all our 16x16 together into a single image |
![]() |
![]() |
16x16 tiles are
rather a pain... so even though our sprite today is 16x16 - we'll
actually make it up out of 8x8 tiles anyway! |
Sprite Definitions - Ports Used
Address | Name | Purpose | Bits | Details |
$2101 | OBSEL | OAM size (Sprite) | SSSNNBBB | S=size N=Bame addr B=Base addr |
$2102 | OAMADDL/L | OAM address | LLLLLLLL | a=oam address L |
$2102 | OAMADDL/H | OAM address | R000000H | R= priority Rotation / H=oam address MSB |
$2104 | OAMDATA | OAM data | ???????? | ????????
Data to write to OAM ram |
$212C | TM | Main screen designation | ---S4321 | S=sprites 4-1=enable Bgx |
$2138 | OAMDATAREAD | Read data from OAM | ???????? | ???????? Data read from OAM ram |
Address | Byte 1 | Byte 2 | Meaning | SprNum |
$0000 | XXXXXXXX | YYYYYYYY | X=Xpos (bits 0-7) Y=Ypos | 0 |
$0001 | YXPPPCCCT | TTTTTTTT | Y=yflip X=xflip P=priority compared to BG (C=palette +128) | 0 |
$0002 | XXXXXXXX | YYYYYYYY | X=Xpos (bits 0-7) Y=Ypos | 1 |
$0003 | YXPPPCCCT | TTTTTTTT | Y=yflip X=xflip P=priority compared to BG (C=palette +128) | 1 |
� | � | � | � | � |
� | � | � | � | � |
$00FE | XXXXXXXX | YYYYYYYY | X=Xpos (bits 0-7) Y=Ypos | 127 |
$00FF | YXPPPCCCT | TTTTTTTT | Y=yflip
X=xflip P=priority compared to BG (C=palette +128) T= Tile Pattern
number |
127 |
$0100 | SXSXSXSX | (no 2nd byte) | S=doubleSize sprite X=Xpos (bit 8) | 0-3 |
$0101 | SXSXSXSX | (no 2nd byte) | S=doubleSize sprite X=Xpos (bit 8) | 4-6 |
� | � | � | � | |
$011F | SXSXSXSX | (no 2nd byte) | S=doubleSize sprite X=Xpos (bit 8) | 124-127 |
Programming a Sprite Example!
Setting a hardware sprite
![]() |
The SNES is capable
of a large number of 16 color sprites, but it's a little difficult
in some ways, the 16x16 tiles are harder to use than it feels they
should be, and the layout of the OAM makes setting the sprites a
little tricky. Still, the fact we can use 128 hardware sprites onscreen easily is certainly a bonus! |
Technical Details of Hardware Sprites
The Sprite pointers for the bitmap data, are a single byte... multiplying
the sprite pointer by 64 will give the address of the sprite *within the
16k bank of Vram* (so must be in the range $0000-$3FFF)
$1000-$2000 and $9000-$A000 are seen by the VIC as character ROM, so
sprites cannot be in this area!
Sprites are 21 vertical lines and 63 bytes each...
In 1bpp (2 color) mode this makes sprites 24x21...
In 2bpp (4 color) mode they are 12x21...
In both modes, Color 0 is Transparent
In 2bpp mode color 1,2 are read from $D025/6... and color 3 is the sprite
color.
![]() |
We're going to map the screen to the
$4000-$8000 range - out the way of our program (which starts at
$0800) and the rottern character rom (which appears at $1000-$2000
and $9000-$A000) This means we've changed our Font routines, and GetScrPos routine for any bitmap sprite functions as well! |
Address | Purpose | Bits | Meaning |
$07F8-$07FF | Sprite pointers (default - will change if screen moved) | SSSSSSSS | s*64=memory address |
$D000 | Sprite #0 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D001 | Sprite #0 Y-coordinate | YYYYYYYY | |
$D002 | Sprite #1 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D003 | Sprite #1 Y-coordinate | YYYYYYYY | |
$D004 | Sprite #2 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D005 | Sprite #2 Y-coordinate | YYYYYYYY | |
$D006 | Sprite #3 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D007 | Sprite #3 Y-coordinate | YYYYYYYY | |
$D008 | Sprite #4 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D009 | Sprite #4 Y-coordinate | YYYYYYYY | |
$D00A | Sprite #5 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D00B | Sprite #5 Y-coordinate | YYYYYYYY | |
$D00C | Sprite #6 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D00D | Sprite #6 Y-coordinate | YYYYYYYY | |
$D00E | Sprite #7 X-coordinate | XXXXXXXX | (only bits #0-#7). |
$D00F | Sprite #7 Y-coordinate | YYYYYYYY | |
$D010 | Sprite #0-#7 X-coordinates | 76543210 | (bit #8) |
$D015 | Sprite enable register | 76543210 | 1=on |
$D017 | Sprite double height register | 76543210 | |
$D01B | Sprite priority register | 76543210 | |
$D01C | Sprite multicolor mode register | 76543210 | 0=2 color
1=4color |
$D01D | Sprite double width register | 76543210 | |
$D01E | Sprite-sprite collision register | 76543210 | |
$D01F | Sprite-background collision reg | 76543210 | |
$D025 | Sprite extra color #1 | ----CCCC | |
$D026 | Sprite extra color #2 | ----CCCC | |
$D027 | Sprite #0 color | ----CCCC | |
$D028 | Sprite #1 color | ----CCCC | |
$D029 | Sprite #2 color | ----CCCC | |
$D02A | Sprite #3 color | ----CCCC | |
$D02B | Sprite #4 color | ----CCCC | |
$D02C | Sprite #5 color | ----CCCC | |
$D02D | Sprite #6 color | ----CCCC | |
$D02E | Sprite #7 color | ----CCCC |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | A | B | C | D | E | F |
Coding to support Hardware Sprites
Using our function for a test
![]() |
The C64 is capable of 8
sprites at one time, but if we're very clever, and switch the sprite
data while the screen is drawing, we can up this to 8 sprites per
line? Lets say there's 8 sprites on the top line of the screen... if we move those sprites to the bottom of the screen when the middle is being drawn - we'll double our sprites - we'd have to move them back before the start of the screen is drawn again! It's complex - and beyond the scope of this tutorial, but it's what the best C64 games do to make the most of the hardware! |
The CRTC Registers
The CRTC 6845 handles the display, it will size and position, and define the memory used by the screen...
In theory there are 17 registers, but in practice ones such as the Lightpen and Cursor registers may not be any use to us... the most interesting registers are marked in Yellow
Reg
|
Abbrev | Name | Range | Bits | Mode 0 | Mode 1 | Mode 2 | Mode 3 | Mode 4 | Mode 5 | Mode 6 | Mode 7 | Details |
0 | HTOT | Horizontal Total | 0-255 | DDDDDDDD | 127
($7F) |
127
($7F) |
127
($7F) |
127
($7F) |
63
($3F) |
63
($3F) |
63
($3F) |
63
($3F) |
Physical width of screen |
1 | HDISP | Horizontal Displayed | 0-255 | DDDDDDDD | 80 ($50) | 80 ($50) | 80 ($50) | 80 ($50) | 80 ($50) | 40 ($28) | 40 ($28) | 40 ($28) | Logical width in Chars |
2 | HSYNC | Horizontal Sync Position | 0-255 | DDDDDDDD | 98 ($62) | 98 ($62) | 98 ($62) | 98 ($62) | 49 ($31) | 49 ($31) | 49 ($31) | 51 ($33) | Logical Xpos |
3 | V/HWID
|
Horiz. and Vert. Sync Widths | 0-15,0-15 | VVVVHHHH | 40 ($28) | 40 ($28) | 40 ($28) | 40 ($28) | 36 ($24) | 36 ($24) | 36 ($24) | 36 ($24) |
Hsync / Vsync area size |
4 | VTOT | Vertical Total | 0-127 | -DDDDDDD | 38 ($26) | 38 ($26) | 38 ($26) | 30 ($1E) | 38 ($26) | 38 ($26) | 30 ($1E) | 30 ($1E) | Physical height of screen |
5 | VADJ | Vertical Total Adjust | 0-31 | ---DDDDD | 0 ($0) | 0 ($0) | 0 ($0) | 0 ($0) | 0 ($0) | 0 ($0) | 0 ($0) | 0 ($0) | Scanline Offset |
6 | VDISP | Vertical Displayed | 0-127 | -DDDDDDD | 32 ($20) | 32 ($20) | 32 ($20) | 25 ($19) | 32 ($20) | 32 ($20) | 25 ($19) | 25 ($19) | Logical Height in Chars |
7 | VSYNC | Vertical Sync position | 0-127 | -DDDDDDD | 34 ($22) | 34 ($22) | 34 ($22) | 27 ($1B) | 34 ($22) | 34 ($22) | 27 ($1B) | 27 ($1B) | Logical Ypos of screen |
8 | Interlace and Skew | 0-3 | ------DD | 1 ($1) | 1 ($1) | 1 ($1) | 1 ($1) | 1 ($1) | 1 ($1) | 1 ($1) | 2 ($2) |
0/2=off 1/3=on | |
9 | MR | Maximum Raster Address | 0-31 | ---DDDDD | 7 ($7) | 7 ($7) | 7 ($7) | 9 ($9) | 7 ($7) | 7 ($7) | 9 ($9) | 18 ($12) | Scanlines per Char row |
10 | Cursor Start Raster | 0-127 | -DDDDDDD | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Unneeded | |
11 | Cursor End Raster | 0-31 | ---DDDDD | 8 ($8) | 8 ($8) | 8 ($8) | 9 ($9) | 8 ($8) | 8 ($8) | 9 ($9) | 19 ($13) | Unneeded | |
12 | DISPH | Display Start Address | 0-63 | --HHHHHH | 8 ($8) | 8 ($8) | 8 ($8) | 8 ($8) | 8 ($8) | 8 ($8) | 8 ($8) | 8 ($8) | Screen Address H |
13 | DISPL | Display Start Address | 0-255 | LLLLLLLL | 48 ($30) | 48 ($30) | 48 ($30) | 48 ($30) | 48 ($30) | 48 ($30) | 48 ($30) | 48 ($30) | Screen Address L |
14 | CURH | Cursor Address H | 0-63 | --HHHHHH | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | unused |
15 | CURL | Cursor Address L | 0-255 | LLLLLLLL | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | unused |
16 | LPH | Light Pen Address | 0-63 | --HHHHHH | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Read Only |
17 | LPL | Light Pen Address | 0-255 | LLLLLLLL | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Read Only |
The Various registers will define the width, height and starting
position of the screen. Each screen mode will have a set of recommended registers settings (shown in the chart above), but we can resize the screen in some cases to make a smaller screen - and we may wish to do this to save memory if we don't need a 'full size' screen. By altering Reg12 we can effect hardware page flipping with Double Buffering - by allocating one memory area for a visible buffer, and a second for the drawing area, we can make sure the viewer won't see the screen redraw. In addition, we can alter Reg12+13 to effect a Horizontal or Vertical Hardware scroll - but this will expose other areas of Ram, which weren't used by the screen before. |
![]() |
Beware! While
BeebEM recrates some of the registers - it seems it does not
'accurately' emulate a real monitor... some of the settings which
may work fine on the emulator, may not work correctly on a real
BBC (or may risk damage to the display in the worst situations.) |
![]() |
Using and Testing the registers!
![]() |
This program
just allows you to test what each register does - and find
suitable settings for the screen size and position you need... Once you've done that, you'll need to reprogram your sprite, font and other drawing routines to work with the new screen position and orientation. |
Character Map
The PET has a character map of 128 characters... when the top bit (bit 7) is 1, these characters will be inverted - we'll need to use a combination of both for the block graphics.
The early PET had a single upper case character map... later systems had a second character map with lower case... The block characters used in this lesson exist in both.
We can switch between the two by writing to register $E84C...
Write #12 to $E84C
|
Write #14 to $E84C
|
VRAM Addresses
The character blocks
Showing our 'bitmap'
Here is the routine we'll use to show a bitmap. It's pretty simple, it copies lines of bytes from the source to screen ram, moving down a line after each line. |
![]() |
When we want to show a bitmap, we first calculate the destination
address in Vram with GetVDPScreenPos, We then specify the source data into zero page pair z_hl, and specify our bitmap size before using ShowBitmap. |
![]() |
Here is the result! | ![]() |
If you want to create a 'bitmap' for the PET, you can do it with
my Akusprite
editor. It will take a bitmap, and covert it to bytes that represent the correct characters - it's what was used for this tutorial!. |
![]() |
![]() |
The PET doesn't have any graphics modes that
can do bitmaps, and unlike its successor the VIC-20, it also
cannot have 'redefined' character codes. We'll have to make do with these characters in the charset for any 'game graphics'... but that's kind of the charm of these old systems! |
The PET Keyboard
The keys matrix layout is shown below:
Simulating a joystick
The PET didn't come
with a joystick port, however it is possible make a converter and
connect one via the parallel port. However, for simplicity, we'll stick to the built in keyboard for our game-controller needs! |
![]() |
PET Sound!
The PET is capable of up to 4 octaves... one of 3 possible pairs can be
selected with $E84A.
Address $E84B will turn the sound on or off... we
write #16 to turn it on, #0 to turn it off.
Address $E84A can be used to select the Octave with
value 15/51/85
Address $E848 can be used to select the note, a
value of 64-255 should be passed.
It is not possible to set the volume, or play multiple tones at the same
time.
Note Freq | octave=15 | octave=51 | octave=85 | |||
Octive 0 | Octive 1 | Octive 1 | Octive 2 | Octive 2 | Octive 3 | |
B | 251 | 125 | 251 | 125 | 251 | 125 |
C | 238 | 118 | 238 | 118 | 238 | 118 |
C# | 224 | 110 | 224 | 110 | 224 | 110 |
D | 210 | 104 | 210 | 104 | 210 | 104 |
D# | 199 | 99 | 199 | 99 | 199 | 99 |
E | 188 | 93 | 188 | 93 | 188 | 93 |
F | 177 | 88 | 177 | 88 | 177 | 88 |
F# | 168 | 83 | 168 | 83 | 168 | 83 |
G | 158 | 78 | 158 | 78 | 158 | 78 |
G# | 149 | 74 | 149 | 74 | 149 | 74 |
A | 140 | 69 | 140 | 69 | 140 | 69 |
A# | 133 | 65 | 133 | 65 | 133 | 65 |
(Sound info taken from the PetFaq)
Sfx with Chibisound!
These tutorials use a sound 'driver' called ChibiSound, Chibisound uses a single byte parameter in the Accumulator, and provides 64 different pitches, in low or high volume with either tones or noise. Sending a value of 0 mutes sound, all other values make a tone... Chibisound is intended to make it simple to have SFX in multiplatform games and was used in Grime Z80 and Grime 6502 |
|
We turn the sound on or off with $E84B. We select our tone with $E848... Chibisound only uses one octive, setting $E848=15 |
![]() |
![]() |
Now we've got graphics, sound and input
routines we can port Yquest and Grime to the PET! We'll have to use graphics characters for our game graphics! |