Learn Multi platform 68000 Assembly
Programming... By Magic!
Platform Specific Lessons
MinTile is a
multiplatform 'engine' which allows us to define our game code in
a common way, and let the platform specific code handle the
platform specific work!... it was used to write 'ChibiFighter' on
the Z80.
For more details on Mintile, see the Mintile series here...
|
 |
 |
The blitter isn't really efficient working at
such small sizes, so the speed isn't very good, but this makes a
nice example of using the blitter with bit shifted data.
|
Tile Drawing Routines
Our example shows an onscreen tilemap and two software 'sprites'.
Mintile supports scrolling and X-Flip (but not Y-flip)
The 'sprites' are actually miniature tile maps, this is to reduce
the amount of platform specific code.
These are all created via the DoStrip platform specific routine,
which we'll look at here.
This example writes to the screen in software. |
 |
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects.
It calculates a VRAM destination in A6, from an X,Y position in
D1,D4
Co-ordinates are in 'Logical Units' (Pairs of pixels) - this is to
allow for single byte co-ordinates and maintain compatibility with
the 8 bit systems
Each line of the screen is 40 bytes, but there are 4 bitplanes.
This routine calculates the bytepos in A6, but preserves the full
xpos in d1
|
|
We need a 1 byte 'mask' for the 4 bitplanes of the 8 lines of our
tile.
We'll bitshift this using the destination Xpos as a mask for our
tile and the screen. |
 |
DoStrip will draw one horizontal strip of tiles.
The job is defined by the following registers:
A2=Source Tilemap
A5=Tile Bitmap Patterns
A6=Vram destination
D7=Width of strip
The tilemap will either be the TileCache (for the background) or a
mini tilemap (for sprites)
If using the tile cache we zero the tiles after they are drawn.
We calculate the address of the source pattern data by multiplying
the tile number by 64.
Actually the tiles are only 32 bit bytes, but they are 'padded' to 1
word wide, so we can do the bitshifts.
|
|
We need to set up the blitter, First we need to work out the
bitshift, to allow per pixel movement.
We do this with the bottom two bits of D1, however these need to be
shifted to the top 4 bits of BLTCON0/BLTCON1
We use the formula "$5A = !AC+A!C" to do the blitting operation A is
the one tile mask, B is the source pattern C is the current screen
data, D is the final screen destination.
we need to define the "Mods" that shift the sources after each line,
to move to the next line.
|
 |
We need to horizontally flip the bytes in the tiles,
and we'll do this via a 256 byte Lookup Table.
For each of the 256 possible bytes, We calculate the opposite
position of the 8 pixels of a bitplane |
 |
The blitter can't do the Xflip job for us, so we'll generate
Xflipped patterns in advance.
TestChibikoRev will store the flipped tile data
|
 |
We use our LUT to xflip the source pattern data. |
 |
DoStripRev has essentially the same function, however
we use the X-flipped patterns, and move through the tilemap from
right to left.
|
|
MinTile is a
multiplatform 'engine' which allows us to define our game code in
a common way, and let the platform specific code handle the
platform specific work!... it was used to write 'ChibiFighter' on
the Z80.
For more details on Mintile, see the Mintile series here...
|
 |
Tile Drawing Routines
Our example shows an onscreen tilemap and two software 'sprites'.
Mintile supports scrolling and X-Flip (but not Y-flip)
The 'sprites' are actually miniature tile maps, this is to reduce
the amount of platform specific code.
These are all created via the DoStrip platform specific routine,
which we'll look at here.
This example writes to the screen in software. |
 |
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects.
It calculates a VRAM destination in A6, from an X,Y position in
D1,D4
Co-ordinates are in 'Logical Units' (Pairs of pixels) - this is to
allow for single byte co-ordinates and maintain compatibility with
the 8 bit systems
Each line of the screen is 40 bytes, but there are 4 bitplanes,
these are grouped in clusters of 4 words, with one word per bitplane
in the format: $0000111122223333
Because of this, we need a 'byte' offset to calculate the position
within the word, depending on if our tile is odd or even
|
|
DoStrip will draw one horizontal strip of tiles.
The job is defined by the following registers:
A2=Source Tilemap
A5=Tile Bitmap Patterns
A6=Vram destination
D7=Width of strip
The tilemap will either be the TileCache (for the background) or a
mini tilemap (for sprites)
If using the tile cache we zero the tiles after they are drawn.
We calculate the address of the source pattern data by multiplying
the tile number by 32
There are 4 bitplanes for each tile, spaced 2 bytes apart.
After each line we add 160 to move down a line. we repeat 8 times
We need to move across a tile, but due to the memory layout,
depending on if we're on an even tile, or an odd one we'll need to
shift one byte, or 7
After a tile we repeat for all the tiles on the line (the count is
in D7)
|
|
We need to horizontally flip the bytes in the tiles,
and we'll do this via a 256 byte Lookup Table.
For each of the 256 possible bytes, We calculate the opposite
position of the 8 pixels of a bitplane |
 |
DoStripRev has essentially the same function, however
we flip the pixels via the LUT
We load A1 with the address of the lookup table, we then read in a
byte into D0, and use the source byte as an offset in the lookup
table. Reading from this resulting address gets the X-flipped
equivalent byte, which we write to the screen.
We write 4 times per line and for all 8 lines
|
|
MinTile is a
multiplatform 'engine' which allows us to define our game code in
a common way, and let the platform specific code handle the
platform specific work!... it was used to write 'ChibiFighter' on
the Z80.
For more details on Mintile, see the Mintile series here...
|
 |
 |
The FIX layer is really only designed for the
GUI, but it works like a normal 8x8 pixel tile tilemap, so is an
easy way to get graphics to the screen.
|
Tile Drawing Routines
Our example shows an onscreen tilemap and two software 'sprites'.
Mintile supports scrolling and X-Flip (but not Y-flip)
The 'sprites' are actually miniature tile maps, this is to reduce
the amount of platform specific code.
These are all created via the DoStrip platform specific routine,
which we'll look at here.
This example writes to the screen using the Fix layer. |
 |
We have a 'GetScreenPos' function , which calculates the VRAM
destination for the sprite objects.
It calculates a VRAM destination in A6, from an X,Y position in
D1,D4
Co-ordinates are in 'Logical Units' (Pairs of pixels) - this is to
allow for single byte co-ordinates and maintain compatibility with
the 8 bit systems
The Fixlayer goes down the screen, then across, so we multiply the
Xpos by 32, and add the Ypos to the base address of the Fix layer
($7000)
|
|
DoStrip will draw one horizontal strip of tiles.
The job is defined by the following registers:
A2=Source Tilemap
A5=Tile Bitmap Patterns
A6=Vram destination
D7=Width of strip
The tilemap will either be the TileCache (for the background) or a
mini tilemap (for sprites)
If using the tile cache we zero the tiles after they are drawn.
We add A5 to the tile number - by default this is $1800+768 - to
point to the first FIX tile for the background,palette 1
To move across the screen, we add 32 to the destination address (as
each column has 32 tiles)
we repeat until the line is done.
|
|
DoStripRev will draw the X-flipped character, but the FIX layer
can't perform the Xflip operation, so we have an alternative set of
Xflipped patterns we use for the job.
|
 |
DoStripRev will use the Xflipped patterns, and walk through the
source A2 tilemap in reverse
|
 |
We need to define the address of the FIX layer data at the
appropriate memory address in NeoGeo.xml |
 |
MinTile is a
multiplatform 'engine' which allows us to define our game code in
a common way, and let the platform specific code handle the
platform specific work!... it was used to write 'ChibiFighter' on
the Z80.
For more details on Mintile, see the Mintile series here...
|
 |
 |
The NeoGeo had no tilemap, but it has 'chainable' hardware
sprites, that can be combined to form a tilemap.
That's what we'll do this time! The sprites are
16x16, but we'll scale them to 50% to simulate an 8x8 tilemap.
|
Tile Drawing Routines
Our example shows an onscreen tilemap and two software 'sprites'.
Mintile supports scrolling and X-Flip (but not Y-flip)
The 'sprites' are actually miniature tile maps, this is to reduce
the amount of platform specific code.
These are all created via the DoStrip platform specific routine,
which we'll look at here.
The sprites are scaled to 50% for the background, so we need to
double the size of our tile patterns to get them the right size
(which is why they appear small in this example) |
 |
Before we can use the 'Tilemap' We have to build it out of
sprites!
There are a variety of addresses which define each of the sprite.
|
Address
of |
|
Bit |
|
|
Function |
Sprite
1 |
|
F |
E |
D |
C |
B |
A |
9 |
8 |
|
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
Purpose |
Sample
Value
|
TileAddr 1,2..32 |
$0040,$0042
�$007E |
|
N |
N |
N |
N |
N |
N |
N |
N |
|
N |
N |
N |
N |
N |
N |
N |
N |
N=Tile Number L |
$2000 |
TilePal 1,2..32 |
$0041,$0043
...$007F |
|
P |
P |
P |
P |
P |
P |
P |
P |
|
N |
N |
N |
N |
A |
A |
V |
H |
P=Pallete,
N=tile Number H, A=Animate (4/8) V=Vflip H=Hflip |
$0100 |
Shrink |
$8001 |
|
- |
- |
- |
- |
H |
H |
H |
H |
|
V |
V |
V |
V |
V |
V |
V |
V |
H=H shrink
(F=off), V=V shrink (FF=off) |
$0FFF |
Ypos |
$8201 |
|
Y |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
|
Y |
C |
T |
T |
T |
T |
T |
T |
Y=Y pos (from
bottom, so 288+208-Y) C=Chain another sprite on right,
T=Tile count vertically |
$E002 |
Xpos |
$8401 |
|
X |
X |
X |
X |
X |
X |
X |
X |
|
X |
- |
- |
- |
- |
- |
- |
- |
X=X pos (from
left) |
$0800 |
|
|
The first sprite is the 'Anchor' to which all the others acre
connected, We need to set all the sprite parameters for the object.
The Sprite is 32 tiles tall, with a scale of $77F (50% - 8x8 pixels)
We build all the tiles in the column, all of them point to tile
$2200 by default (a blank tile), and palette 1
Our tilemap is 32x24, each sprite draws a full column, so we've used
32 of the available sprites! |
 |
DoStrip will draw one horizontal strip of tiles. it's
intended for the background tiles.
The job is defined by the following registers:
A2=Source Tilemap
A5=Tile Bitmap Patterns
A6=Vram destination
D7=Width of strip
The tilemap will either be the TileCache (for the background) or a
mini tilemap (for sprites)
If using the tile cache we zero the tiles after they are drawn.
We set the matching sprite tile for our fake tile map to make it
visible in the desired position.
|
|
DoStripSprite will draw one horizontal strip of tiles
for the movable characters
The job is defined by the following registers:
A2=Source Tilemap
A5=Tile Bitmap Patterns
D1,D4= X,Y pos
D7=Width of strip
We use HspriteNum to track the next free sprite number.
We position the hardware sprite, and set the tile number (Vram
Address $0000+), Palette ($0001+), Scale ($8000+), Xpos ($8200+) and
Ypos ($8400+)
Due to the way MinTile works, it's not really possible to chain
tiles for the vertical height, so we use more hardware sprites for
each column.
|
 |
DoStripRev functions essentially the same, however it
sets the 'H' bit of $0001+ to Xflip the patterns, and moves through
the tilemap from right to left.
|
 |
We need to define the address of the Sprite pattern data at the
appropriate memory address in NeoGeo.xml |
 |
The Interrupt Handler - Vblanks
We're going to use VBlank to control the speed of our music.
Vblank and many other interrupts are generated by the MFT. The
interrupts can be enabled with $E88007/9, however they are
also 'masked' with $E88013/5 (a bit 1 in both enables an
interrupt!)
Interrupt 6 - Gpip4 is the Vblank (referred to as V-Disp) is
controlled by bit 7 of $E88009 and $E88015 - This causes an
execution of vector $46 at address $000118
We need to load the address of our interrupt handler to
$000118
We don't need to, but actually we can change this address!...
The default vector base for the interrupts is vector $40 at
address $000100 - this can be changed with the top 4 bits of
$e88017
|
 |
We need to protect all our register, as ChibiTracks will use
them.
We return from our interrupt handler with RTE
|
|
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 also need to have a ChibiTracks music file to play. |
|
We need to allocate up to 256 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
If we want to change the speed, we can load a new value into
SongSpeed at any time.
|
|
All that's left is to execute the PLAY routine to update the
playing music. |
 |
The Interrupt Handler - Vblanks
The Vblank (Vsync) interrupt will cause an execution of the
IRQ0 vector at $00000064
First we need to put the address of our interrupt handler at
this address.
|
 |
Next we need to set the interrupt level in the SR register.
This is controlled by bits 8-10.
We need to set a value of 0, to allow our interrupt to occur,
this will also enable the other interrupts. |
 |
Our interrupt handler needs to check if our game is running,
we do this with bit 7 of $10FD80 - A value of 1 means our game
is running, otherwise we run the system interrupt handler at
$10FD80
Next we need to do some system tasks, we clear the interrupt
with bit 2 of $3C000C.
We kick the watchdog at $3C000C - this tells the system we
haven't crashed (otherwise it auto reboots)
We run to system routines to do various jobs, $C0044A handles
IO, $C004CE shows any system messages.
We can now worry about updating our music!
We need to protect all our register, as ChibiTracks will use
them.
We return from our interrupt handler with RTE
|
|
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 also need to have a ChibiTracks music file to play. |
|
We need to allocate up to 256 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
If we want to change the speed, we can load a new value into
SongSpeed at any time.
|
|
All that's left is to execute the PLAY routine to update the
playing music. |
 |
 |
We're going
to 'Play nice' with the OS for a change!
The OS will do most of the VBlank work for us, we'll just tag
onto it's job list, and update our music once a frame.
|
The Interrupt Handler - Vblanks
The system executes a series of interrupt handlers during
Vblank, These are defined in a list, with the lists
pointer at address $000456. The length of the list is at
address $000454 (8 by default)
We want to register the address of interrupt handler in this
list, so we need to look for a zero entry ($00000000) if we
can find one we put our interrupt handler in it's slot.
|
 |
ChibiTracks uses all our registers, but the OS is protecting
them for us.
We return from our interrupt handler with RTS, the other
defined Vblanks interrupt handlers will (or did) execute.
|
|
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 also need to have a ChibiTracks music file to play. |
|
We need to allocate up to 256 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
If we want to change the speed, we can load a new value into
SongSpeed at any time.
|
|
All that's left is to execute the PLAY routine to update the
playing music. |
 |
 |
We're going
to 'Play nice' with the OS for a change!
The OS will do most of the VBlank work for us, we'll just tag
onto it's job list, and update our music once a frame.
|
The Interrupt Handler - Vblanks
We need to get the operating system to define the interrupt,
we load a pointer to the OS into A6 from address $4
We need to define a structure with the information about the
interrupt, we load it into A1
We select the interrupt type with D0, we want interrupt 5
(VERTB - vblank)
we now execute function 'Exec - AddIntServer' at address -168
|
 |
Here is the structure that defines the interrupt in A1. The
first few bytes should be left unchanged.
LN_NAME points to a zero
terminated string giving the interrupt a name - it can be
anything really.
IS_DATA can point to a data
structure for the interrupts parameters, it's address is
loaded into A1. This will be handy if you're using multiple
instances of the same interrupt handler, but we don't really
need it.
IS_CODE points to the actual
subroutine which will be executed. |
 |
ChibiTracks uses all our registers, the calling routine will
deal with D0 and D1 for us though, so we don't Push and Pop
those.
A0 must retain the value $DFF000 for Vblank interrupts - We're
backing up all Address registers so that is fine!
We've added a little test of IS_DATA, by adding 1 to A1
(MyData)
To tell the OS to pass control to other interrupts we set the
Zero flag and return with RTS
|
 |
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 also need to have a ChibiTracks music file to play. |
|
We need to allocate up to 256 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
If we want to change the speed, we can load a new value into
SongSpeed at any time.
|
|
All that's left is to execute the PLAY routine to update the
playing music. |
 |
| |
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
|