`

Learn Multi platform 6502 Assembly Programming... For Monsters!

Platform Specific Lessons


Platform Specific Series - Now we know the basics, lets look at the details of the platforms we're covering!
    Lesson P1 - Bitmap Functions on the BBC

Lesson P2 - Bitmap Functions on the Atari 800 / 5200

Lesson P3 - Bitmap Functions on the Apple II

Lesson P4 - Bitmap Functions on the Atari Lynx

Lesson P5 - Bitmap Functions on the PC Engine (TurboGrafx-16)

Lesson P6 - Bitmap Functions on the NES / Famicom

Lesson P7 - Bitmap Functions on the SNES / Super Famicom

Lesson P8 - Bitmap Functions on the VIC-20

Lesson P9 - Bitmap Functions on the C64

Lesson P10 - Joystick Reading on the BBC

Lesson P11 - Joystick Reading on the Atari 800 / 5200

Lesson P12 - Joystick Reading on the Apple II

Lesson P13 - Joystick Reading on the Atari Lynx

Lesson P14 - Joystick Reading on the PC Engine (TurboGrafx-16)

Lesson P15 - Joystick Reading on the NES / Famicom and SNES

Lesson P16 - Joystick Reading on the VIC-20

Lesson P17 - Palette definitions on the BBC

Lesson P18 - Palette definitions on the Atari 800 / 5200

Lesson P19 - Palette definitions on the Atari Lynx

Lesson P20 - Palette definitions on the PC Engine (TurboGrafx-16)

Lesson P21 - Palette Definitions on the NES

Lesson P22 - Palette Definitions on the SNES / Super Famicom

Lesson P22 (z80) - Sound with the SN76489 on the BBC Micro

Lesson P23 - Sound on the Atari 800 / 5200

Lesson P23 (Z80) - Sound with the 'Beeper' on the Apple II

Lesson P24 - Sound on the Atari Lynx

Lesson P25 - Sound on the PC Engine (TurboGrafx-16)

Lesson P26 - Sound on the NES / Famicom

Lesson P27 - Sound on the SNES / Super Famicom: the SPC700

Lesson P28 - Sound on the SNES / Super Famicom: Writing ChibiSound

Lesson P29 - Sound on the on the VIC-20

Lesson P30 - Sound on the C64

Lesson P31 - Hardware Sprites on the Atari 800 / 5200

Lesson P32 - Hardware sprites on the Atari Lynx

Lesson P33 - Hardware Sprites on the PC Engine (TurboGrafx-16)

Lesson P34 - Hardware Sprites on the NES / Famicom

Lesson P35 - Hardware Sprites on the SNES / Super Famicom

Lesson P36 - Hardware Sprites on the C64

Lesson P37 - Screen settings with the CRTC on the BBC Micro!

Lesson P38 - Character Block Graphics on the PET

Lesson P39 - Key reading on the PET

Lesson P40 - Sound on the PET

Lesson P41 - Multiple layers on the SNES

Lesson P42 - Color maths on the Super Nintendo

Lesson P43 - Splitscreen scrolling and Sprite 0 Hit on the NES!

Lesson P44 - The NES Zapper!




Lesson P70 - Interrupt Driven Music on the Atari Lynx (ChibiTracks)
Lets upgrade our previous example with sound via ChibiTracks, we'll use the VBlank interrupt handlers to play some music


LNX_MinTile_WithSound.asm




The Interrupt Handler - Using Vblank!

We're going to define an interrupt handler for the IRQ interrupt,

We need to put this at address $FFFE-FFFF in memory, however by default this area is ROM!

However we can page blocks of the rom out, and replace them with RAM via port $FFF9
From    
To       
Use
$0000 $00FF Zero Page
$0100 $01FF Stack
$0200 $FBFF Program RAM
$FC00 $FCFF Suzy
$DF00 $FDFF Mikey
$FE00 $FFF7 ROM
$FFFA $FFFB NMI Address
$FFFC $FFFD Reset Address
$FFFE $FFFF IRQ Address
Here we set bit 3 to one, This replaces the Vector area with RAM ($FFFA-$FFFF)

We can now load the address of our interrupt handler
Our Interrupt handler is ready, but we need to tell the hardware to cause interrupts to occur!

$FD09 controls interrupt generation, bit 7 will cause the Vblank to occur.

We can now use CLI to allow the 6502 to accept interrupts
Our interrupt handler just needs to update our music.

ChibiTracks uses the Zeropage entries z_hl,z_bc,z_de and z_ix,as well as X,Y and A

Our interrupt handler finishes with RTI - Interrupt handlers automatically back up and restore the flags (P)

ChibiTracks
To use ChibiTracks, we need to include the modules of the music player,

We also need the platform specific "Chibisound Pro" sound driver.

We need to have a ChiBiTracks music file to play.
We have two options 'Allow Speed change' allows us to change the speed of our song. This is useful to keep the song playing the same speed on 50hz and 60hz screens. NOTE: Whether this will work effectively depends on if the speed was pre-multiplied on export (faster playback, but stops this function working)

AllowRelocation allows the binary to be loaded at any memory address, not just the one 'compiled' into the binary at export time - while disabling this will save a little speed/memory it's recommended you keep this enabled!
We need to allocate up to 128 bytes of memory for ChibiSoundPro, and ChibiTracks player.

We define pointers to the ChibiTracks variables within this ram.
Before we play anything we need to init the ChibiSoundPro Driver.

To start our song, we set SongBase to point to the music file we want to play, and call StartSong - we do this any time we want the music to change

Here we've slowed down the song a little, it was designed to play on 50 hz systems. If our Lynx is configured with a 60hz screen we will want to slow the music down a little
All that's left is to execute the PLAY routine to update the playing music.




Lesson P71 - Interrupt Driven Music on the Atari 800/5200 (ChibiTracks)
Lets upgrade our previous example with sound via ChibiTracks, we'll use the VBlank interrupt handlers to play some music


A52_MinTile_WithSound.asm




The Interrupt Handler - Using Vblank!

VBLANK causes an NMI interrupt, which executes the vector at address $FFFA, however this is in ROM, which we cannot change!

There is an address we can put the address of our interrupt handler, and the ROM will pass control to us.

On the Atar5200 the ROM handler will pass control promptly to the subroutine at vector $0202


On the Atar800 the ROM handler will pass control promptly to the subroutine at vector $0222, Unlike the Atari 5022, The Atari 800 pushes the A, X and Y register before passing control, so we must pop these at the end of our interrupt handler.
Atari 5200:
Function Address Type Details
VDSLST $0200 NMI Immediate IRQ Vector
VPRCED $0202 IRQ Immediate VBI Vector
VINTER $0204 IRQ VBI Vector Chained from Bios
VBREAK $0206 BRK DLI vector
VKEYBD $0208 IRQ Keyboard IRQ vector
VSERIN $020A IRQ Keypad continuation vector
VSEROR $020C IRQ Break IRQ vector
VSEROC $020E IRQ BRK interrupt vector

Atari 800
Function Address Type Details
VVBLKI $0222 NMI stage 1 VBI (pushes A X Y)
VVBLKD $0224 NMI return−from−interrupt routine
CDTMA1 $0226 NMI time−out 1 (used by SIO)
CDTMA2 $0228 NMI time−out 2 (not used by OS)
We now load the address of the interrupt handler, We load the address to $0202-3 on the Atari 5200 or $0222-3 on the Atari 800

as the Atari 800 pushes some registers for us, it's different for that system.
Our Interrupt handler is ready, but we need to tell the hardware to cause interrupts to occur!

The GTIA controls NMI generation via address $D40E, We can enable V-Blank by setting bit 6 to 1
Our interrupt handler just needs to update our music.

ChibiTracks uses the Zeropage entries z_hl,z_bc,z_de and z_ix,as well as X,Y and A

Our interrupt handler finishes with RTI - Interrupt handlers automatically back up and restore the flags (P)

ChibiTracks
To use ChibiTracks, we need to include the modules of the music player,

We also need the platform specific "Chibisound Pro" sound driver.

We need to have a ChiBiTracks music file to play.
We have two options 'Allow Speed change' allows us to change the speed of our song. This is useful to keep the song playing the same speed on 50hz and 60hz screens. NOTE: Whether this will work effectively depends on if the speed was pre-multiplied on export (faster playback, but stops this function working)

AllowRelocation allows the binary to be loaded at any memory address, not just the one 'compiled' into the binary at export time - while disabling this will save a little speed/memory it's recommended you keep this enabled!

SMSTranspose is offered on systems which have trouble with very low tones (including the Ataris), this shifts all the octaves up by one.
We need to allocate up to 128 bytes of memory for ChibiSoundPro, and ChibiTracks player.

We define pointers to the ChibiTracks variables within this ram.
Before we play anything we need to init the ChibiSoundPro Driver.

To start our song, we set SongBase to point to the music file we want to play, and call StartSong - we do this any time we want the music to change

Here we've slowed down the song a little, it was designed to play on 50 hz systems. If our Lynx is configured with a 60hz screen we will want to slow the music down a little
All that's left is to execute the PLAY routine to update the playing music.



Lesson P72 - Interrupt Driven Music on the C64 (ChibiTracks)
Lets upgrade our previous example with sound via ChibiTracks, we'll use the VBlank interrupt handlers to play some music


C64_MinTile_WithSound.asm



The Interrupt Handler - Using Vblank!

Our Vblank will causes an IRQ interrupt, which executes the vector at address $FFFE-F, however this is in ROM, which we cannot change!

There is an address we can put the address of our interrupt handler, and the ROM will pass control to us.

On the C64 the IRQ interrupt is redirected to vector at $0314

The interrupt handler even pushes some registers for us! A, X and Y are pushed onto the stack before our routine is called.
From To Function 
Registers
Pushed
$0314 
$0315 
IRQ A X Y
$0316 $0317 BRK A X Y
$0318 $0319 NMI
We now load the address of the interrupt handler, We load the address to $0314-5

We're going to cause a line interrupt at line 0 to detect Vblank.
We need to set $DC0D to the line we want the interrupt to occur (0)

We need to turn on the line interrupt too!
We do this with bit 0 of $D01A
Our interrupt handler just needs to update our music.

ChibiTracks uses the Zeropage entries z_hl,z_bc,z_de and z_ix,as well as X,Y and A


A, X and Y are pushed onto the stack before our routine is called, so we don't need to PUSH these, but we do need to PULL them!

Our interrupt handler finishes with RTI - Interrupt handlers automatically back up and restore the flags (P)

ChibiTracks
To use ChibiTracks, we need to include the modules of the music player,

We also need the platform specific "Chibisound Pro" sound driver.

We need to have a ChiBiTracks music file to play.
We have two options 'Allow Speed change' allows us to change the speed of our song. This is useful to keep the song playing the same speed on 50hz and 60hz screens. NOTE: Whether this will work effectively depends on if the speed was pre-multiplied on export (faster playback, but stops this function working)

AllowRelocation allows the binary to be loaded at any memory address, not just the one 'compiled' into the binary at export time - while disabling this will save a little speed/memory it's recommended you keep this enabled!

SMSTranspose is offered on systems which have trouble with very low tones (including the Ataris), this shifts all the octaves up by one.
We need to allocate up to 128 bytes of memory for ChibiSoundPro, and ChibiTracks player.

We define pointers to the ChibiTracks variables within this ram.
Before we play anything we need to init the ChibiSoundPro Driver.

To start our song, we set SongBase to point to the music file we want to play, and call StartSong - we do this any time we want the music to change

Here we've slowed down the song a little, it was designed to play on 50 hz systems. If our Lynx is configured with a 60hz screen we will want to slow the music down a little
All that's left is to execute the PLAY routine to update the playing music.





Lesson P73 - Interrupt Driven Music on the VIC20 (ChibiTracks)
Lets upgrade our previous example with sound via ChibiTracks, we'll use the Timer interrupt handlers to play some music


VIC_MinTile_WithSound.asm



The Interrupt Handler - Using timers.
The VIC 20 does not have a Vblanks interrupt, however we can use Timer1 to create a timed 50/60 hz interrupt which will allow us to control the speed of our music

We can use VIA1 or VIA2 to do this, but the kind of interrupt that occurs is different depending on which we use.

Timer 1 on VIA 1 causes an NMI interrupt
Timer 1 on VIA 2 causes an IRQ interrupt

We can't directly change the interrupt vector, as the high memory area is ROM, however the rom firmware passes control to the vector addresses at $0314-$0319.

The ROM for the IRQ handler backs up A,X and Y. the NMI one does not.
From To Function 
Registers Pushed
$0314 
$0315 
IRQ A X Y
$0316 $0317 BRK A X Y
$0318 $0319 NMI
If we want to use the NMI and VIA1, we need to turn on the interrupt with port $911E.

We also need to turn on the timer with $911B

Bit 7 defines if we're turning ON or OFF the interrupt.

We load our time speed into $9114-5 (lower is faster)

We then load the address of our interrupt handler to $0318-9


If we want to use the IRQ and VIA2, we need to turn on the interrupt with port $912E.

We also need to turn on the timer with $912B

Bit 7 defines if we're turning ON or OFF the interrupt.

We load our time speed into $9124-5 (lower is faster)

We then load the address of our interrupt handler to $0314-5
If we used the NMI/VIA1, then we need to push A,X and Y

We now need to clear any interrupts pending, if we don't, the interrupt will fire again, and our music will play super fast.

To do this we write 255 to $911D for VIA1/NMI or $912D for VIA2/IRQ



We now need to update the music!

ChibiTracks uses the Zeropage entries z_hl,z_bc,z_de and z_ix,as well as X,Y and A
ChibiTracks
To use ChibiTracks, we need to include the modules of the music player,

We also need the platform specific "Chibisound Pro" sound driver.

We need to have a ChiBiTracks music file to play.
We have two options 'Allow Speed change' allows us to change the speed of our song. This is useful to keep the song playing the same speed on 50hz and 60hz screens. NOTE: Whether this will work effectively depends on if the speed was pre-multiplied on export (faster playback, but stops this function working)

On the VIC it would be easier just to change the timer frequency, but our song here wasn't saved with pre-multiplication, so we still need it enabled.

AllowRelocation allows the binary to be loaded at any memory address, not just the one 'compiled' into the binary at export time - while disabling this will save a little speed/memory it's recommended you keep this enabled!
We need to allocate up to 128 bytes of memory for ChibiSoundPro, and ChibiTracks player.

We define pointers to the ChibiTracks variables within this ram.
Before we play anything we need to init the ChibiSoundPro Driver.

To start our song, we set SongBase to point to the music file we want to play, and call StartSong - we do this any time we want the music to change
All that's left is to execute the PLAY routine to update the playing music.





Lesson P74 - Vblank timed Music on the Apple II (ChibiTracks)
Lets upgrade our previous example with sound via ChibiTracks, we'll use the Vblank time playing some music


AP2_MinTile_WithSound.asm




There doesn't seem to be a way to use a Vblank interrupt on the Apple 2, but we can detect Vblanks, and play our music when it occurs.

So long as we check for vblank regularly, it will work ok, though won't be as efficient as an interrupt

Detecting Vblank
To check for Vblank, we need to test bit 7 of $C019.

If it's 0 then we're in Vblank (plus), otherwise we are not (minus)
Annoyingly it's the opposite on the IIc!

To ensure we catch the vblank, we'll test once per line draw in our graphics routine
We now need to update the music!

ChibiTracks uses the Zeropage entries z_hl,z_bc,z_de and z_ix,as well as X,Y and A


To ensure we don't update the music too often (unlikely unless silent!), we check to see if the Vblank has not finished, and wait until it has

ChibiTracks
To use ChibiTracks, we need to include the modules of the music player,

We also need the platform specific "Chibisound Pro" sound driver.

We need to have a ChiBiTracks music file to play.
'Allow Speed change' allows us to change the speed of our song. This is useful to keep the song playing the same speed on 50hz and 60hz screens. NOTE: Whether this will work effectively depends on if the speed was pre-multiplied on export (faster playback, but stops this function working)

On the VIC it would be easier just to change the timer frequency, but our song here wasn't saved with pre-multiplication, so we still need it enabled.

AllowRelocation allows the binary to be loaded at any memory address, not just the one 'compiled' into the binary at export time - while disabling this will save a little speed/memory it's recommended you keep this enabled!

FastSilent is specific to the Apple II, as the CPU is busy when playing sound, usually we pause when the sound is silent, but if we're controlling the playback speed in another way (like our vblank check) this just wastes cpu time. FastSilent will save this time, and allow us to draw more!

SMSTranspose shifts the notes up 1 octave, this helps on systems where the low notes can't be well represented
We need to allocate up to 128 bytes of memory for ChibiSoundPro, and ChibiTracks player.

We define pointers to the ChibiTracks variables within this ram.
Before we play anything we need to init the ChibiSoundPro Driver.

To start our song, we set SongBase to point to the music file we want to play, and call StartSong - we do this any time we want the music to change
All that's left is to execute the PLAY routine to update the playing music.




Lesson P75 - Sound and Joypad on the Atari 2600
Lets Take a look at the Atari 2600 hardware.
We'll create a combined example which used the Digital Joypad and sound hardware.


A26_SoundJoy.asm


We'll dump the contents of the sound registers to the screen using playfield registers PF0, PF1 and PF2..

This will allow us to see the bits in the registers, as vertical lines!

Sound registers

The Atari 2600 has 2 sound channels - though both are mono. Audio control selects one of 16 different waveforms for the sound.

Category Address Name Bits Details
TIA_Write $0015 AUDC0 ----TTTT audio control 0 T=Tonetype (8=noise 4=pure tone)
TIA_Write $0016 AUDC1 ----TTTT audio control 1 T=Tonetype (8=noise 4=pure tone)
TIA_Write $0017 AUDF0 ---FFFFF audio frequency 0 (0=Highest 31=lowest)
TIA_Write $0018 AUDF1 ---FFFFF audio frequency 1 (0=Highest 31=lowest)
TIA_Write $0019 AUDV0 ----VVVV audio volume 0 (15=Loudest)
TIA_Write $001A AUDV1 ----VVVV audio volume 1 (15=Loudest)

Joystick Registers

The Digital joypad directions can be read from the bits in port $2080, 1=not pressed 0=pressed
The Fire buttons can be read from the top bit of $003C/$003D, again 1=not pressed 0=pressed

Category Address Name Bits Details
TIA_Read $0038 INPT0 1....... read pot port (Analog dial)
TIA_Read $0039 INPT1 1....... read pot port (Analog dial)
TIA_Read $003A INPT2 1....... read pot port (Analog dial)
TIA_Read $003B INPT3 1....... read pot port (Analog dial)
TIA_Read $003C INPT4 1....... read input (Fire 1 0=pressed)
TIA_Read $003D INPT5 1....... read input (Fire 2 0=pressed)
PIA_RW $0280 SWCHA 11111111 Port A: I/O (read or write) Controller %RLDUrldu
PIA_RW $0281 SWACNT 11111111 Port A DDR, 0= input, 1=output
PIA_RW $0282 SWCHB 11111111 Port B; console switches (read only) %dD-C-SR
PIA_RW $0283 SWBCNT 11111111 Port B DDR (hardwired as input)

We can read in the digital direction buttons of the first digital controller from address $0280.

The direction buttons for controller 0 are in the top 4 bits... Controller 1 is in the bottom 4.

These are in the format %RLDUrldu... 1=Not pressed 0=Pressed

We test each direction and update variables in the zero page.
The top bit of $003C contains the state of the first fire button.
1=Not pressed 0=Pressed.

If Fire is pressed, we change the volume to 15, making it loud!
If we're using sound channel 0 we use sound regs $15,$17 and $17
$0015 is the control port, the value here defines the tone type.
$0017 is the frequency, 0 is the highest pitch.
$0019 is the volume, 15 is the loudest.

We can also use sound regs $16,$18 and $1A for sound channel 2
So we can visualize the register contents, we show the values to the screen with PFO/PF1 and PF2





Lesson P76 - Joystick and Sprite on the Atari 2600
Lets take a look at the sprite hardware.

Once again we'll use the joystick, this time to move a sprite around the screen!

A26_Sprite.asm



Like the playfield, The player 'Sprite' isn't a self generating object, it's a single byte register, which we will need to change each line to generate an image.
To make this clear, we'll fill it with a single pixel line for the 'out of area' pixels. Our intended sprite is a box shape and moves with the joypad.

Even positioning the sprite is hard... The X position is defined by a sync to the raster beam pos. The Y position is simply decided buy when we start sending our sprite data to the Sprite register  GRP0 ($001B)

The atari 'Player sprite' is 8 pixels wide, and the entire height of the screen.

By changing the data in the sprite register GRP0 ($001B) at the correct 'Y line' we can make the sprite move up and down with the joystick
The sprite can only be 8 pixels wide, but we can 'scale it' to 2x or 3x width

We can even have the sprite repeat 2 or 3 times!

The Number and size are controlled by register $0004 (NUMSIZ0)

     


Sprite Registers

Category 
Address 
Name Bits Details
TIA_Write $0004 NUSIZ0 --MMPPP number-size player-missile 0
TIA_Write $0005 NUSIZ1 --MMPPP number-size player-missile 1
TIA_Write $0006 COLUP0 CCCCLLLL C=Color L=Luminance for Player & Missile 0
TIA_Write $0007 COLUP1 CCCCLLLL C=Color L=Luminance for Player & Missile 1
TIA_Write $0010 RESP0 <strobe> reset player 0
TIA_Write $0011 RESP1 <strobe> reset player 1
TIA_Write $0012 RESM0 <strobe> reset missile 0
TIA_Write $0013 RESM1 <strobe> reset missile 1
TIA_Write $0014 RESBL <strobe> reset ball
TIA_Write $001B GRP0 BBBBBBBB graphics player 0
TIA_Write $001C GRP1 BBBBBBBB graphics player 1
TIA_Write $001D ENAM0 ......1. graphics (enable) missile 0
TIA_Write $001E ENAM1 ......1. graphics (enable) missile 1
TIA_Write $001F ENABL ......1. graphics (enable) ball
TIA_Write $0020 HMP0 SSSS----
horizontal motion player 0
TIA_Write $0021 HMP1 SSSS---- horizontal motion player 1
TIA_Write $0022 HMM0 SSSS---- horizontal motion missile 0
TIA_Write $0023 HMM1 SSSS---- horizontal motion missile 1
TIA_Write $0024 HMBL SSSS---- horizontal motion ball
TIA_Write $0025 VDELP0 .......1 vertical delay player 0
TIA_Write $0026 VDELP1 .......1 vertical delay player 1
TIA_Write $0027 VDELBL .......1 vertical delay ball
TIA_Write $0028 RESMP0 ......1. reset missile 0 to player 0
TIA_Write $0029 RESMP1 ......1. reset missile 1 to player 1
TIA_Write $002A HMOVE <strobe> apply horizontal motion
TIA_Write $002B HMCLR <strobe> clear horizontal motion registers
TIA_Write $002C CXCLR <strobe> clear collision latches


At the start of our code we set the default XY Pos of our sprite, we use two bytes in the zero page for this.


We then set the size of our sprite using register $04 (NUMSIZ0)
At the start of our frame loop, we have to set our timing up for Vblank.

'DoGameTasks' will handle joystick reading, and the X position of our sprite.
We load the old XY pos into the X,Y registers - we'll use these if we need to reset the sprite position due to it being out of bounds.

We load in the directions from $0280
The direction buttons for controller 0 are in the top 4 bits... Controller 1 is in the bottom 4.

These are in the format %RLDUrldu... 1=Not pressed 0=Pressed

We test each direction and update variables in the zero page.

We now check the updated XYpos with the limits of the screen, if we've gone over the X or Y boundary, we reset the value.

Going below 0 will result in a negative, which will also be reset by this code.

We're going to set the color based on the fire button!

The top bit of $003C contains the state of the first fire button.
1=Not pressed 0=Pressed.

Register $0006 (COLUP0) defines the color of player 0
We use the raster beam position to set the general Xposition, but that isn't very accurate, we use HMP0 (Reg $0020 - Player Horizontal Motion) to tweak the X pos.

The Tweak is Left_7 to Right_8

We lookup the correct tweak via a lookup table. we apply it with reg $002A (HMOVE), but we don't actually apply it until we've set the approximate Xpos.


We now count down the Xpos to approximate the beam position.

Once we reach zero, we set the Xpos to the current beam position with reg $0010 (RESP0) this resets the player xpos to the current beam position.


It's now time to apply the tweak using $002A (HMOVE). We need to do this after a WSYNC.
Our sprite bitmap is 16 lines tall.

it could be more or less, depending on how we transfer the data to the sprite register.
During the drawing routine, Y is the current Y line of the screen.

Once we reach the Ypos, we start counting the line of our sprite with X.


We load the accumulator with %00001000, This is the value used for the sprite in the 'out of area' pixels... really it should probably be #0, but this makes it clearer that the sprite is drawn for the full height of the screen.


During the sprite area (>Xpos <Xpos+16), we load a line from our sprite bitmap via 'MySprite,X'


Whatever the position, we update the sprite pixel register $001B (GRP0) ... this defines the current bitmap for the player sprite.




Lesson P77 - MaxTile software tilemap on the BBC
Lets take a look at the advanced 'Maxtile' tilemap on the BBC, it supports Xflip,Yflip, Fastfill and more!

BBC_V1_MaxTile_Normal.asm


MaxTile Definitions

Maxtile is optimized to work as efficiently as possible with the VRAM layout.

the BBC version is designed to work with a 256x192 screen, as this gives the best compatibility with other systems.

We send a sequence of values to the CRTC chip to set up the screen layout.

Our Screenbase will be at $5000


We use some spare memory for the 3 draw caches (each 256 bytes)

we also use 256 bytes for the 'Xflip lookup table'


The screen is 256x192, which is 128x96 in logical units.

The VRAM base of the screen is &5000.

Here is the Tile pattern data

The BBC Tile Drawing routines use 4 color 8x8 tiles, So each tile uses 16 bytes.

'Fill tiles' are a special case, they fill a tile with 2 bytes (saving time and memory)
The BBC Mode we're using has 4 pixels in each byte, with 2 bits defining each pixel color.

We'll calculate an Xflip LUT

It would be too slow to shift these in realtime, so we 'precaclulate' the flipped equivalent of each of the 256 possible source bytes.

This saves memory compared to storing alternative patterns.

Yflip does not require a lookup table, we instead move UP VRAM instead of DOWN as we draw the pattern!
When we want to draw a sprite object to the screen, we need to calculate the VRAM destination.

MaxTile uses X,Y co-ordinates in 'Logical Units' (Pairs of pixels) - this is passed in BC, and the Vram destination is returned in HL


The BBC screen layout is convenient for 8x8 tiles provided we draw aligned to an 8 pixel Y boundary.

8 consecutive bytes (Say from $5000-$5007) go down the screen, the 9th byte 'jumps back up 8 pixels to the next column.

Therefore to move across 4 pixels we add 8 to our VRAM destination, and to move down an 8 pixels we add 512 (64*8)

Tile Drawing!

The calling routine that exexutes DrawTile will set X and Y to zero, so we can rely on this being the case at the start of the routine... however we need to ensure that X and Y still contain zero by the end of our routine

The DrawTile Routine is called by the shared code, This shared code will backup and restore the stack pointer and load the first byte of the 16 bit tile number into A

The low bit is shifted out (the update bit)... we need to reset it to 0 anyway!

The following zero page entries  are loaded:
z_BCs = Tilemap
z_DEs = Tile Bitmap Pattern data
z_HL = VRAM Destination

To optimize things, the tile drawing works as a 'binary tree', deciding the kind of tile drawing routine to use

The Platform specific draw routine DrawTile starts by ckecking Bit 1 (now Bit 0)
This is the 'Program' flag - If this is 0, then this is the simplest unflipped tile, otherwise we switch to the advanced routine.

If we're drawing a basic tile, we shift a 0 back into A, and write it back, that clears the Update flag, as we will draw the tile now.
We're going to draw a basic unflipped tile

We need to load the second byte of the tilenumber, and add it to z_DE (the pattern data)

This gives us the source address - which we load into z_HL
We now use a huge 'unwrapped loop to move each line of our source pattern to the screen.

provided we're drawing to a vertically align

As all the 16 bytes of our 8x8 tile are consecutive bytes we just use Y as an offset for our source pattern (z_HLs) and screen destination (z_HL)


Note: the XY bits of the pattern number are unused in this case (They define flip mode if the Program bit was set)... These could be used for a 'Tile Tint' (for 4 color patterns on 16 color systems) or a bank number (for bankswitching huge tile sets)
If the Program bit was 1, then either we need to flip, or run some 'custom code'

Next we check the XY bits, if both are 0 this is not a flip (Transparent, Filled, Double etc)

If either bit (or both) is 1, then this is some kind of flip, so we calculate the pattern source address, and move it into the z_HLs
If the Y flip bit is 0 we must be X flipping!

We load each source pattern byte from z_HSs, then Xflip it via the Lookup table (in z_BC)

We then write these to the screen in the same way as the unflipped data.


We need to do the right hand half of the tile first, then the left, so we alter the HLs source pattern address, and execute DrawTileXflipPart twice to achieve this

If the Y flip bit was set we now check if the X flip bit is also set.

If it's not we're just Yflipping!

To flip vertically we load the source pattern data normally, but we write it to the screen from the bottom of the tile upwards.
if the X bit was set as well as Y we need to XY flip.

We do this with a combination of both 'tricks'

We use the LUT to X flip, and draw to the screen Bottom to Top to Y flip!

MaxTile Custom Draw Types

Bits 1,2,3... XYP=%001 defines a custom program

When a custom program is being used Bits 4,5 define the program type (rather than part of the low tile number)

%00=Filled Tile
%01=Double height tile
%10=unused
%11=Transparent tile/empty tile

The remaining two bits 6,7 act as the High part of the tile number %------NN nnnnnnnn
A double height tile uses only 4 lines of a pattern, so we multiply the pattern number by 8

We then draw each line of the pattern twice to the screen.
The fill tile is the simplest!

We use 2 bytes from the pattern data, and fill each line with one of the two

We use z_E for one line, then z_D for one line - this allows us to make a nice 'checkerboard' patterned fill, or alternate lines in different colors.

As so little data is read, This routine is the fastest, so should be used for as much as possible of our tilemap!
The final type is the Transparent tile.

if the Tilenumber=255 then this tile is completely transparent, and no data will be drawn
Our transparency is a crude '0 byte' transparency.

Basically, any byte equal to 0 is not drawn to the screen, others are drawn normally.

We use the same pattern data as usual, but as we read in each byte, we only draw it to the screen if it isn't zero..










Lesson P78 - MaxTile software tilemap on the Apple II
Lets take a look at the advanced 'Maxtile' tilemap on the Apple II , it supports Xflip,Yflip, Fastfill and more!

AP2_V1_MaxTile_Normal.asm


MaxTile Definitions

Maxtile Caches draws to the screen in 3 thirds to reduce flicker.
We use some spare memory for the 3 draw caches (each 256 bytes)

we also use 256 bytes for the 'Xflip lookup table'


The screen is 256x192, which is 128x96 in logical units, we add 4 to the screen base to center this screen.

The VRAM base of the screen is &5000.

Here is the Tile pattern data

The Apple II  Tile Drawing routines use 2 color 8x8 tiles (Actually 7x8 + 1 color bit!!!), So each tile uses 8 bytes.

'Fill tiles' are a special case, they fill a tile with 2 bytes (saving time and memory)
The Apple II  Mode we're using has 7 pixels in each byte, with the first bit as a color bit.

We'll calculate an Xflip LUT

It would be too slow to shift these in realtime, so we 'precaclulate' the flipped equivalent of each of the 256 possible source bytes.

This saves memory compared to storing alternative patterns.

Yflip does not require a lookup table, we instead move UP VRAM instead of DOWN as we draw the pattern!
When we want to draw a sprite object to the screen, we need to calculate the VRAM destination.

MaxTile uses X,Y co-ordinates in 'Logical Units' (Pairs of pixels) - this is passed in z_BC, and the Vram destination is returned in z_HL

The Apple II screen memory is a bit of a pain, as the screen is plit into thirds!

The screen layout is split in 3 parts according to Y line
%AABBBCCC - AA*$0028  BBB*$0080  CCC*$0400

We calculate the appropriate Y line with bit shifts and branches.

We then add the Xpos in bytes, +4 to center the screen.


The initial calculation is a pain, however moving down a tile is simple, we just add 4 to the top byte (z_H) of the Vram destination (z_HL



Tile Drawing!

The calling routine that exexutes DrawTile will set X and Y to zero, so we can rely on this being the case at the start of the routine... however we need to ensure that X and Y still contain zero by the end of our routine

The DrawTile Routine is called by the shared code, This shared code will backup and restore the stack pointer and load the first byte of the 16 bit tile number into A

The low bit is shifted out (the update bit)... we need to reset it to 0 anyway!

The following zero page entries  are loaded:
z_BCs = Tilemap
z_DEs = Tile Bitmap Pattern data
z_HL = VRAM Destination

To optimize things, the tile drawing works as a 'binary tree', deciding the kind of tile drawing routine to use

The Platform specific draw routine DrawTile starts by ckecking Bit 1 (now Bit 0)
This is the 'Program' flag - If this is 0, then this is the simplest unflipped tile, otherwise we switch to the advanced routine.

If we're drawing a basic tile, we shift a 0 back into A, and write it back, that clears the Update flag, as we will draw the tile now.

We load X with 4, this speeds up our 'Downline commands'
We're going to draw a basic unflipped tile

We need to load the second byte of the tilenumber, and add it to z_DE (the pattern data)

This gives us the source address - which we load into z_HL
We now use a huge 'unwrapped loop to move each line of our source pattern to the screen.

provided we're drawing to a vertically align

As all the 8 bytes of our 8x8 tile are consecutive bytes we just use Y as an offset for our source pattern (z_HLs) and write to destination (z_HL),

as we know X=4., we use (z_HL-4,x) as our destination

after each line we add 4 (via X) to z_H t move to the next vertical line.


Note: the XY bits of the pattern number are unused in this case (They define flip mode if the Program bit was set)... These could be used for a 'Tile Tint' (for 4 color patterns on 16 color systems) or a bank number (for bankswitching huge tile sets)
If the Program bit was 1, then either we need to flip, or run some 'custom code'

Next we check the XY bits, if both are 0 this is not a flip (Transparent, Filled, Double etc)

If either bit (or both) is 1, then this is some kind of flip, so we calculate the pattern source address, and move it into the z_HLs
If the Y flip bit is 0 we must be X flipping!

We load each source pattern byte from z_HSs, then Xflip it via the Lookup table (in z_BC)

We then write these to the screen in the same way as the unflipped data.

To save coding length, we don't use an unwrapped loop this time.
If the Y flip bit was set we now check if the X flip bit is also set.

If it's not we're just Yflipping!

To flip vertically we load the source pattern data normally, but we write it to the screen from the bottom of the tile upwards.
if the X bit was set as well as Y we need to XY flip.

We do this with a combination of both 'tricks'

We use the LUT to X flip, and draw to the screen Bottom to Top to Y flip!

MaxTile Custom Draw Types

Bits 1,2,3... XYP=%001 defines a custom program

When a custom program is being used Bits 4,5 define the program type (rather than part of the low tile number)

%00=Filled Tile
%01=Double height tile
%10=unused
%11=Transparent tile/empty tile

The remaining two bits 6,7 act as the High part of the tile number %------NN nnnnnnnn
A double height tile uses only 4 lines of a pattern, so we multiply the pattern number by 4

We then draw each line of the pattern twice to the screen.
The fill tile is the simplest!

We use 2 bytes from the pattern data, and fill each line with one of the two

We use z_E for one line, then z_D for one line - this allows us to make a nice 'checkerboard' patterned fill, or alternate lines in different colors.

As so little data is read, This routine is the fastest, so should be used for as much as possible of our tilemap!
The final type is the Transparent tile.

if the Tilenumber=255 then this tile is completely transparent, and no data will be drawn
Our transparency is a crude '0 byte' transparency.

Basically, any byte equal to 0 is not drawn to the screen, others are drawn normally.

We use the same pattern data as usual, but as we read in each byte, we only draw it to the screen if it isn't zero..










Lesson P79 - MaxTile software tilemap on the Lynx
Lets take a look at the advanced 'Maxtile' tilemap on the Atari Lynx , it supports Xflip,Yflip, Fastfill and more!

LNX_V1_MaxTile_Normal.asm


MaxTile Definitions

Due to the LYNX screen, the display is 26x17 tiles... unlike the other systems, the tiles are only 6x6 (not 8x8)... this gives a screen resolution of 156x102 pixels
Maxtile Caches draws to the screen in 3 thirds to reduce flicker.
We use some spare memory for the 3 draw caches (each 256 bytes)

we also use 256 bytes for the 'Xflip lookup table'


The VRAM base of the screen is &C000.

Here is the Tile pattern data

Lynx tile patterns are 6x6 pixels , with each byte containing 2 pixels... this means each tile is a rather odd 18 bytes!


'Fill tiles' are a special case, they fill a tile with 2 bytes (saving time and memory)
We'll calculate an Xflip LUT

We swap the pixels in each byte, storing the flipped equivalent in the LUT

This saves memory compared to storing alternative patterns.

Yflip does not require a lookup table, we instead move UP VRAM instead of DOWN as we draw the pattern!
When we want to draw a sprite object to the screen, we need to calculate the VRAM destination.

MaxTile uses X,Y co-ordinates in 'Logical Units' (Pairs of pixels) - this is passed in z_BC, and the Vram destination is returned in z_HL

The Lynx screen base is at address $C000.

On the Lynx the Vram calculation is $C000 + YposInLines * 80 + Xpos in bytes


Tile Drawing!

The calling routine that exexutes DrawTile will set X and Y to zero, so we can rely on this being the case at the start of the routine... however we need to ensure that X and Y still contain zero by the end of our routine

The DrawTile Routine is called by the shared code, This shared code will backup and restore the stack pointer and load the first byte of the 16 bit tile number into A

The low bit is shifted out (the update bit)... we need to reset it to 0 anyway!

The following zero page entries  are loaded:
z_BCs = Tilemap
z_DEs = Tile Bitmap Pattern data
z_HL = VRAM Destination

To optimize things, the tile drawing works as a 'binary tree', deciding the kind of tile drawing routine to use

The Platform specific draw routine DrawTile starts by ckecking Bit 1 (now Bit 0)
This is the 'Program' flag - If this is 0, then this is the simplest unflipped tile, otherwise we switch to the advanced routine.

If we're drawing a basic tile, we shift a 0 back into A, and write it back, that clears the Update flag, as we will draw the tile now.

We load X with 4, this speeds up our 'Downline commands'
We're going to draw a basic unflipped tile

We need to load the second byte of the tilenumber.

Max tile was designed to multiply the tilenumber by 16, by using the top 12 bits of the two byte tile value, but we need to multiply by 18, so we bitshift the value into the *2 position, and add the *2 and *16 values to get *18


We add this to z_DE (the pattern data)

This gives us the source address - which we load into z_HL
We need to write 3 bytes for each line,

after each line we add 80 to the VRAM destination to move down to the next line,

We repeat for each 6 lines of our tile.
If the Program bit was 1, then either we need to flip, or run some 'custom code'

Next we check the XY bits, if both are 0 this is not a flip (Transparent, Filled, Double etc)

If either bit (or both) is 1, then this is some kind of flip, so we calculate the pattern source address, and move it into the z_HLs
If the Y flip bit is 0 we must be X flipping!

We load each source pattern byte from z_HSs, then Xflip it via the Lookup table (in z_BC)

We then write these to the screen in the same way as the unflipped data.

To save coding length, we don't use an unwrapped loop this time.
If the Y flip bit was set we now check if the X flip bit is also set.

If it's not we're just Yflipping!

To flip vertically we load the source pattern data normally, but we write it to the screen from the bottom of the tile upwards - we do this by adding $190 (80*5) to move to the last line of the source pattern.
if the X bit was set as well as Y we need to XY flip.

We do this with a combination of both 'tricks'

We use the LUT to X flip, and draw to the screen Bottom to Top to Y flip!

MaxTile Custom Draw Types

Bits 1,2,3... XYP=%001 defines a custom program

When a custom program is being used Bits 4,5 define the program type (rather than part of the low tile number)

%00=Filled Tile
%01=Double height tile
%10=unused
%11=Transparent tile/empty tile

The remaining two bits 6,7 act as the High part of the tile number %------NN nnnnnnnn
A double height tile uses only 4 lines of a pattern, so we multiply the pattern number by 9 (half our 18 byte tile)

We then draw each line of the pattern twice to the screen.
The fill tile is the simplest!

We use 2 bytes from the pattern data, and fill each line with one of the two

We use z_E for one line, then z_D for one line - this allows us to make a nice 'checkerboard' patterned fill, or alternate lines in different colors.

As so little data is read, This routine is the fastest, so should be used for as much as possible of our tilemap!
The final type is the Transparent tile.

if the Tilenumber=255 then this tile is completely transparent, and no data will be drawn
Our transparency is a crude '0 byte' transparency.

Basically, any byte equal to 0 is not drawn to the screen, others are drawn normally.

We use the same pattern data as usual, but as we read in each byte, we only draw it to the screen if it isn't zero..