High Resolution | Low Resolution | |||||||
RegNum | 768x512 | 512x512 | 512x256 | 256x256 | 512x512 | 512x256 | 256x256 | Register Purpose |
E80000 | $89 | $5B | $5B | $2B | $4B | $4B | $25 | R00 Horizontal total |
E80002 | $0E | $09 | $09 | $04 | $03 | $03 | $01 | R01 Horizontal synchronization end position timing |
E80004 | $1C | $11 | $11 | $06 | $04 | $05 | $00 | R02 Horizontal display start position |
E80006 | $7C | $51 | $51 | $26 | $45 | $45 | $20 | R03 Horizontal display end position |
E80008 | $237 | $237 | $237 | $237 | $103 | $103 | $103 | R04 Vertical total |
E8000A | $05 | $05 | $05 | $05 | $02 | $02 | $02 | R05 Vertical synchronization end position timing |
E8000C | $28 | $28 | $28 | $28 | $10 | $10 | $10 | R06 Vertical display start position |
E8000E | $228 | $228 | $228 | $228 | $100 | $100 | $100 | R07 Vertical display end position |
E80010 | $1B | $1B | $1B | $1B | $44 | $44 | $24 | R08 External synchronization horizontal adjust: Horizontal position tuning |
E80028 | $406 |
$405 |
$05 | $01 | $00 | R20 Memory mode/Display mode control | ||
E82400 | $04 |
$04 | $00 | $00 | $00 | R0 (Screen mode initialization) - Detail | ||
E82500 | $2E4 | $2E4 | $2E4 | $2E4 | $2E4 | $2E4 | $2E4 | R1 (Priority control) - Priority (Sprites foreground) |
E82600 | $DF |
$DF | $C1 | $C1 | $C1 | R2 (Special priority/screen display) - Screen On / Sprites On | ||
EB0808 | $200 | $200 | $200 | $200 | $200 | $200 | $200 | BG Control (Sprites Visible, slow writing) |
EB080A | $FF | $FF | $FF | $FF | $FF | $25 | Sprite H Total | |
EB080C | $15 | $15 | $0A | $09 | $09 | $04 | Sprite H Disp | |
EB080E | $28 | $28 | $28 | $10 | $10 | $10 | Sprite V Disp | |
EB0810 | $15 | $11 | $10 | $05 | $01 | $00 | Sprite Res %---FVVHH |
We're going to define a
'screenInit' routine which will turn on the graphics screen - we can choose the resolution we want by defining a symbol, such as Res256x256 These Tutorials are using a 16 color mode (as it's most common on retro systems) We need to set up the CRT parameters which will define how the screen image is formed - we also need to define the registers which will configure the screen layout. Note: we're not doing anything with the sprite registers at this time - at the time of writing, hardware sprites are not used in these tutorials, though theyy may be added later! if you want more info on the registers, please see the GamesX website, or the (Japanese) X68000 technical data book |
![]() |
The X68000 is capable of multiple color depths
and multiple layers of paralax, In these tutorials we're only going to use one 16 color layer - the reason for this is that we're going to do the same on a wide variety of 68000 systems (and even the same tasks on z80 and 6502 systems) and the vast majority of these systems support 16 color, but few support 256 colors. |
![]() |
The X68000 screen is memory
maps to the hardware, but it's slightly odd compared to other
systems... Each screen pixel uses two bytes of memory WHATEVER the screen mode! - so whether you're using 4bpp or 8bpp, you're still going to use |
|
||||||||||
We're going to use the A6
register to write data to the screen - we'll use a
'GetScreenPos' command to convert an X,Y pos (in D1,D2) We're using Graphics Screen 0 - which starts at $C00000... Each X pixel takes 2 bytes - so we do a ROL to shift X left 1 bit Each Y line is 1024 bytes, so we do 8+2 ROL's to shift Y left 10 bits... we can't do ROL #10, the command doesn't support it |
![]() |
||||||||||
We have a GetNextLine command to move down a line as well... | ![]() |
![]() |
This Vsync routine was used in Grime 68000 to slow down the game, but it's not perfect, on a 100mhz x68 it's much faster than a 1st gen machine... but this is the best the author has managed to do so far! |
You'll need to define Cursor_X and Cursor_Y in ram somewhere... also not there's some simple LOCATE and CLS commands not shown here, download the sources.z7 if you want to see them! | ![]() |
Lets take a look at how bitmap data is stored on the Atari
ST... The screen is 16 color - which means we need 4 bits for each color... These colors are stored in 'Bitplanes' - this means each byte defines one of the color bits for 8 pixels. On the Atari ST pixel data is stored in words... 16 pixels worth of data for a single bitpalne are stored in a Word... the next word will be the next bitplane for the same 16 pixels... and so on for all 3 bitplanes... After all 4 bitplanes for the first 16 pixels, the next 16 pixels will start... The screen is 320 pixels wide, so each line is 160 bytes across... and Line 1 is 160 bytes after Line 0 in the memory. |
![]() |
Address | Mode | Bits | Purpose | Details |
FF8200 | RW | -------- HHHHHHHH | Video Base H | Need 32256 bytes per screen |
FF8202 | EW | -------- MMMMMMMM | Video Base M | Can�t specify L byte |
FF8240 | RW | -----RRR -GGG-BBB | Palette Color 0 | |
FF8242 | RW | -----RRR -GGG-BBB | Palette Color 1 | |
FF8244 | RW | -----RRR -GGG-BBB | Palette Color 2 | |
FF8246 | RW | -----RRR -GGG-BBB | Palette Color 3 | |
FF8248 | RW | -----RRR -GGG-BBB | Palette Color 4 | |
FF824A | RW | -----RRR -GGG-BBB | Palette Color 5 | |
FF824C | RW | -----RRR -GGG-BBB | Palette Color 6 | |
FF824E | RW | -----RRR -GGG-BBB | Palette Color 7 | |
FF8250 | RW | -----RRR -GGG-BBB | Palette Color 8 | |
FF8252 | RW | -----RRR -GGG-BBB | Palette Color 9 | |
FF8254 | RW | -----RRR -GGG-BBB | Palette Color 10 | |
FF8256 | RW | -----RRR -GGG-BBB | Palette Color 11 | |
FF8258 | RW | -----RRR -GGG-BBB | Palette Color 12 | |
FF825A | RW | -----RRR -GGG-BBB | Palette Color 13 | |
FF825C | RW | -----RRR -GGG-BBB | Palette Color 14 | |
FF825E | RW | -----RRR -GGG-BBB | Palette Color 15 | |
FF8260 | RW | ------SS | Screen Mode | 00=320x200
@4bpp 01=640x200 @2bpp 11=640x400 @1bpp |
We may need to wait for VBlank to delay our games and make
them run at even speed... We can use function $25 of Trap 14 (Xbios) to wait for the vblank. |
![]() |
Alternatively we can use the Vblank counter at address $462 - this is a counter that's updated by the firmware every time vblank occurs. | ![]() |
Ideally, we'd rather go direct to the hardware and detect VBLANK rather than using the firmware, but the author of these tutorials is too stupid to figure it out!... if you know how, please give him a clue - and tell him how it works! | ![]() |
![]() |
We
could
use Atari ST native format, but there are advantages to
storing the data byte aligned, and converting it to word
alignment. The reason we're doing this is it allow us to write to any byte aligned X position - whereas if we used native Atari ST screen foramt we'd have to be word aligned. |
Most Console systems will have
2 types of graphics layer... a 'Tile Map' which is a grid of
predefined 'tiles'... these are usually 8x8 in size... on a 16
bit system, usually multiple layers of tilemaps exist to
define parallax... On top of this we would add sprites to make our player character and other such things... But the NeoGeo HAS NO TILEMAP! |
![]() |
So how does the NeoGeo work
with no Tilemap? Well... sprites on the NeoGeo are 16 pixels wide, and can be up to 512 pixels tall - but they can be combined!... and because the NeoGeo is capable of a whopping 380 tiles, we can combine 20 of them together to 'simulate' a tilemap!... this is how background graphics are drawn on the Neogeo! On top of this are our 'normal' sprites - enemies, player characters and such... There is one final layer, the 'Fix Layer'... this is made up of 16 color 8x8 tiles, in a simple grid... it's designed to do onscreen text and the like... but I used it in GrimeZ80 to do all the game graphics... so if your needs are simple, and you want 8x8 block graphics, it can be used for the job... but don't worry, we'll learn about sprites later |
![]() |
![]() |
Grime
68000 used the FIX map for all it's graphics... but we
should have probably used Sprites... Sprites are 16x16 - but the NeoGeo has Hardware scaling, so we could scale them down to 8x8 - We'll learn all about Sprites in a later lesson! |
The Neogeo screen has a resolution of 320x224, and each tile
is 8x8 - giving an effective screen size of 40x28... The actual tilemap is 40x32... the top and bottom two lines are not show (Shown in red on the chart to the right) However, because of the CRT layout - it is likely that the left and rightmost 1 column will not be visible (Shown in orange to the right)... this gives a visible screen of 38x28 The Fix Layer is positioned in VRAM at &7000 (each position contains 1 word / 2 bytes)- each Tile is defined by 16 bits in the following format:
Where P is the Palette number, and T is the Tile number The fixmap appears at $7000 in Vram... tile data for the Fixmap are in ROM Each address contains a WORD... Write a word to $3C0000 to select VRAM Address Write a word to $3C0002 to Send data to VRAM Tiles in memory are ordered in COLUMNS... so in memory (X,Y) co-ordinate (0,1) comes after (0,0) ($7000)... and (1,0) comes 32 words ($7020) after (0,0) For example - to set tile (0,2) to tile 256 in palette 1 (note this is in the "May be offscreen" area, but should appear on an emulator) Move.W #$7002,d1 ;Address - Tile 2 Move.W #$1100,d0 ;PTTT - Palette and tile Move.w d1,$3C0000 ;set address in vdp Move.w d0,$3C0002 ;set tile data in vdp |
![]()
|
||||||||||||||||||||||||||||||||||||||||||||
Fix Layer tiles are in an odd format! Each tile is 8x8 at 4bpp ... so 32 bytes per tile... The two nibbles of each byte represent 2 pixels , but they are BACKWARDS... so the 1st (High) nibble is the 2nd pixel color, and the 2nd nibble (Low) is the 1st pixel color The bytes are stored in Columns, then rows... and the columns are out of order too! .. Visible pixels ABCDEFGH are stored in Ram in order FEHGBADC The 8 bytes of each column are in normal top->bottom format... so at least that:s something! |
![]() |
||||||||||||||||||||||||||||||||||||||||||||
My AkuSprite editor (used in these tutorials) has support to export images in the correct "FIX" format for the NeoGeo | ![]() |
![]() |
The
FIX and SPRITE formats are different on the NeoGeo which is
VERY ANNOYING! You can use AkuSprite Editor to create valid files - and remember, it's open source, so if it doesn't do the job well enough, you can use the source to make something better! |
If we want to create a 'proper' rom file, we'll need to calculate the correct Hashes, but it's quicker to skip it, and the game will run, so it's OK for our testing | ![]() |
We're going to put our
'Chibiko' character on the screen, the sprite is 48x48, so
it's made up of 6x6 FIX tiles... We'll set the correct positions of the fix tilemap to the parts of the bitmap. |
![]() |
We're going to use a function
called FillAreaWithTiles... this will take a start XY
position, a Width and Height, and a start Tilenumber... it
will fill the entire area with consecutive tiles
Left->Right, Top->bottom Note... the NeoGeo works the opposite way: Columns first, then Rows We're allocating tiles 0-255 for our font, so we're using tile 256 for our Chibiko Bitmap |
![]() |
When we call our
FillAreaWithTiles function, we'll need to convert the X,Y
co-ordinate into a memory The first 2048 tiles ($800) are used by the firmware - we're also going to use Palette 1 - so we add $1800 to the tile number to get the data we write into VRAM We then need to calculate the offset in the FIX map for the tile we want to change... the fixmap is organized in Column/Row order (Going down then Across)... We do this by adding X*32+Y to the start address of the fixmap ($7000) We shift the Xpos left 5 times with ROL.L #5 - multiplying X by 32... Because the top 2 lines of the NeoGeo screen are not viewable, we add 2 to the Ypos To set the Tile, we write the address we want to change to $3C0000.... and the TileNumber/Palette to $3C0002 We repeat the procedure for each of the tiles we want to draw to the screen. |
![]() |
The Screen is memory mapped
from $20000-$28000, it is also possible to have a second page
at $28000-30000, but this will render the OS unusable, as it's
fixed variables are in that memory Lines are in a linear format, with each line 128 bytes below the last... two concecutive bytes make up 4 pixels in 8 color, or 8 pixels in 4 color mode. There are two possible screen modes, configured by bit 3 of port $18063 setting a 0 give 4 colors at 512x256 with Black,Red,Green and White setting a 1 give 8 colors at 256x256 with Black, R, G B, C, M, Y and White There is no palette - the colors are fixed... it is not possible to have 'brightness levels' - colors are on or off. My AkuSprite Editor can export 4 or 8 color bitmaps, however it does not support Flashing mode.
|
4 Color mode:
8 Color mode: (F is flashing)
|
Flashing only works in 8 color mode -it's a HARDWARE flash,
and does not use th interrupts or firmware. A flashing bit of 1 will toggle flashing ON or OFF... flashing always starts as OFF at the start of a line When a Flashing bit 1 is set - all subsequent pixels will flash between their normal defined color and the color of the pixel with the flashing bit set. |
![]() F= Flashing bit of 1
|
![]() |
Many
Emulators don't bother to emulate FLASHing... QLAY2 doesn't
do it, and Q-Emulator doesn't seem to do it right except in
full screen mode... The latest versions of Zesarux DO seem
to do it well though... That said, Falshing mode is pretty dumb anyway, so you're not missing anything important if your emulator doesn't support it! |
When we want to change screen mode, we just set bit of
$18063 according to the mode we want... a 1 will set 8 color mode... a 0 will ser 4 color mode! |
![]() |
The
Sinclair
QL's screen is pretty limited for a 16 bit machine, but
remember... it's an 68008 - which has an 8-bit data bus, so
technically it's and 8 bit machine... this means it's much
slower than an Amiga or similar... and you'll have to
optimize yout code more to keep the games fast. The reduced graphics were probably and unavoidable sacrifice considering the price-point and early release of the machine. |
![]() |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ||
$18021 Read | B | M | R | X | F | T | I | G | B=Baud
state,
M=Microdrive inactive, R=Rtc
state,
X=eXternal
Interrupt,
F=Frame
vsync,
T=Transmit
interrupt,
I=IPC
Interface interrupt G=Gap
interrupt
(microdrive) |
$18021 Write | R | F | M | X | F | T | I | G | R=tRansmit mask, F=interFace mask, M=gap Mask,X=reset eXternal Interrupt,F=reset Frame vsync, T=reset Transmit interrupt, I=reset IPC Interface interrupt G=reset Gap interrupt |
![]() |
Setting
Reg 16 to 01 defines a tilemap of 64x32 - we could define
one that's 32x32 or 64x64, but as our visible screen is
40x28 so 64x32 seems a good choice! If you change it, or the memory address of Scroll A, then you're going to have to change the rest of the code in todays example! |
When we want to write data to
the VDP, we just write the data value to address $C00000
(vdp_data).... but first we need to select the VDP destination
address using $C00004 (vdp_ctrl) We'll need to select addresses from $0000-$C000 to define our tiles and each tile takes 32 bytes. Unfortunately, because the VDP address select is designed to be backwards compatible with the SMS, the bytes we have to send to select the port are a little odd!... Effectively the top two bits of the address are moved to the bottom two, and the top two bits are set to %01... you can see some how various sample VDP addresses convert to address select commands in the table to the right. |
|
|
Selecting VDP
memory
is really confusing... but don't worry, we're going to
create a little function that will do all the work for us! The reason it's so confusing is the Genesis was designed to be backwards compatible with the Master system... that's why the genesis uses a Z80 for it's sound chip - it can be used as the main CPU for SMS support! |
![]() |
![]() |
AkuSprite
editor
supports all the systems covered by these tutorials... and
It'll be extended to cover any systems added in the future. It's only a basic program, but you can load bitmaps in via the clipboard so you can use a more advanced eitor like Krita to do the hard work |
Of
course this is just a simple example... but you can use it
to create something better... Take a look at Grime
68000 if you want to see a more advanced example! We've only looked at Tiles so far, but don't worry, we'll look at sprites later! |
![]() |
We can use Vblank as a way of slowing down our game...
Vblank occurs when the screen restarts drawing, so happens 60
times a second (50 on PAL systems) We can detect Vblank using bit 3 of data read from the Control port, we just read in a byte and test it, if the bit is 1 we're in VBlank. We can wait until Vblank starts, and ends to ensure our game isn't running faster than 50fps. |
![]() |
![]() |
Lesson
P6 - Bitmap Functions on the Amiga The Amiga is essentially a 'bitmap' based system, we allocate an area of memory to be our screen data, and we can write data into that area to get our pixels on the screen, However there are some complexities to getting things working, so lets learn how to make it behave! |
![]() |
![]() BitmapTest.asm |
![]() |
The Amiga works in
'Bitplanes'... this is where each byte of data for the screen
contains a single bit of 8 pixels - called a bitplane...
4 of these bitplanes combined will allow us 16 colors... The Amiga 500 supports up to 6 bitplanes, We can optionally configure these bitplanes into 2 layers in parallax... The Amiga can use up to 32 colors in a single layer (5 bitplanes) ... or have two 3 bitplane layers with 8 colors each (odd numbered bitplanes will be layer 1, even numbers will be layer 2) In these tutorials we'll be defining one 320x200 layer of 16 colors (4 bitplanes)... this is because it will give good 'compatibility' with the Atari ST, and the other systems we're covering in these tutorials. |
8
pixels we want to set:![]() Bitplanes compared to nibbles ![]() |
Word 1 | Word 2 | ||||||||||||||||||||||||||||||||||||
F | E | D | C | B | A | 9 | 8 |
|
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
F | E | D | C | B | A | 9 | 8 |
|
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Command | Details | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | n | n | n | n | n | n | n | n | 0 | D | D | D | D | D | D | D | D | D | D | D | D | D | D | D | D | Change setting | n= address to Change ($DFFnnn) D=new data for address | ||||
V | V | V | V | V | V | V | V | H | H | H | H | H | H | H | 1 | v | v | v | v | v | v | v | v | h | h | h | h | h | h | h | 1 | wait for pos | V=Vops H=Hpos v=Vpos Compare enable h=hpos compare enable |
We need our screen and the 'Copperlist' for the
co-processor to be contained within the 'Chip Ram' To ensure this we need to define a 'ChipRAM' section - the Assembler / Linker will create an executable file, and the operating system will ensure this area of the program is in the correct We're also going to use the command 'CNOP 0,4'... this zero pads the area to a 32 bit boundary - needed for the screen data Now we have some memory, we can start setting up the screen! |
![]() |
![]() |
'Chip
Ram'
in the Amiga is the basic ram of the machine (not add on
upgrades)... we need it to do tasks like Graphics and Sound
FX... Our main code probably isn't running in Chip ram, so we can't 'tag' our screen buffer in there - we have to declare proper section, so the Assembler will build the correct executable and the OS will give us our memory |
The first stage of setting up
our screen is calling the os, and opening the graphics library
... we'll use this to turn on the screen, but we'll do all the other screen config by directly manipulating the hardware registers! You can see the different libraries the Amiga has here ... and the full contents of the Graphics library here |
![]() ![]() |
We're going do the basics of
defining the screen size, bitplanes and position. we need to use the chip registers to do this (see here for the full list) We're going to set up our 320x200 16 color (4 bitplane) layer, Next we'll set up the screen position Finally we want to start the DMA control to handle the screen... we're now going to set up our copperlist that will define the screen memory and colors! |
![]() ![]() |
We need to define our
copperlist, the Coprocessor will use it to draw each frame of
our screen. As mentioned before, the copperlist commands are made up of two words per command, The first word (2 bytes) is a register number... for example $00e2 is $DFF0E2 The second word (2 bytes) is the new value for the command. First we'll define the memory location of the 4 bitplanes that make up our screen - they'll be 8000 bytes apart (40x200) Then we'll define our starting color palette... we'll also remember the memory position of the palette definitions, as we can use it later to change our colors. The last commands is a command to wait forever ($fffffffe) ... the list will automatically restart when the next screen redraw starts. |
![]() |
When we've defined our copperlist, we load the address of the copperlist into the pointer of the Chip Ram - our screen is finally set up! |
![]() |
![]() |
The example here is just creating a single plane, you could have more colors, or more planes with a more advanced set up, but that's beyond the scope of these tutorials - but feel free to play around if you want! |
Our screen is split into
4 bitplanes, and as the screen is 320 pixels wide (40 bytes)
and the screen is 200 lines tall, then each bitplane will be
8000 pixels apart. Of course, if we want to set the color of a pixel, we'll have to set all these bitplanes, and we'll see that in our code |
|
||||||||||
We can see this mimicked in our
bitmap copying code... The byte data of the bitmap are in bitplane format (exported by my AkuSprite Editor) 4 consecutive bytes bytes are copied to each bitplane. We use the GetScreenPos to calculate the correct memory position from a X,Y position (where X is in Bytes, and Y is in lines) We also use GetNextLine to move down a line at the end of each line of our bitmap |
![]() |
||||||||||
The bitmap will be shown to the screen at the position we specified! | ![]() |
Want to
convert a bitmap for use on the Amiga? The Free & open
source AkuSprite Editor can do it for you! It's included in the sources.7z, so go get it if you want! |
![]() |
Setting up the screen was hard,
but Calculating a screen position is easy.. all we need
to do is multiply our Ypos by 40 (the width of the
screen) and add X , and the base of the screen. Moving down a line is also easy, we just add 40 to the current position! |
![]() |
![]() |
Of
course, calling a routine to move down a line is a pretty
lousy to do things if you're just doing an Amiga game, you
should just put that ADDA straight in the loop! The reason we do the call is we're supporting lots of machines with the common drawing code... THEN it makes sense! |
If we want to slow our game down, a good way is to wait for
Vblank (The start of screen redraw) this will cap our game to 60/50 hz, and keep our game speed under control We can test for Vblank on the Amiga by testing $DFF004 (VposR) |
![]() |
![]() |
Lesson
P7 - Joystick Reading on the X68000 The X68000 doesn't have it's 'own' joystick... it uses a 9 pin port, that quite strangely supports 2 button MSX joypads, or 8 button Genesis / Megadrive joypads (6 fires + Mode/Start) This gives us a joypad layout that rivals the NeoGeo - and more than we're ever likely to need... it's not to hard to get the data out the joypad either, lets learn how to do it! |
![]() |
![]() JoyTest.asm
|
![]() |
On the Z80 We used to read in the buttons and fires into a
single byte... on the 68000 systems we're going to keep the
same format for ULDR Fire1+2 and start... but we'll also
extend into a second byte for any extra fire buttons we
have... for each bit, a 1 means the button is up (unpressed) a 0 means the button is down (pressed) We'll load player 1's joystick data into D0... and player 2's into D1 |
|
||||||||||||||||||||||||||||||||||||
Our test program is very simple, all it does is read in the two controllers, and show the value of the registers to the screen. | ![]() |
![]() |
The data we're producing here is not just consistent across 68000 systems, but also the 6502 and Z80 systems in these tutorials... this was used as part of Grime 68000 to make converting the game easier. |
We're going to use 3 ports to access the two players
buttons... $E9A001 reads player 1, $E9A003 reads player 2 $E9A005 selects which buttons we're reading in |
|
||||||||||||||||
Depending on the bits we write to $E9A005, the data we get back from the two Joystick ports will either return the basic joystick buttons, or the extra buttons of a genesis joypad. |
|
We're going to define a
function called "Player_ReadControlsDual" This will read both joysticks, we load the address of the joystick we want to read into A0... then we call JoystickProcessOne The JoystickProcessOne function will process all the buttons from the port in A0... and return the buttons in D0 |
![]() |
To select the basic controls,
we write $0 to $E9A005... now we just
need to read in from (A0)... we need to shift the bits around
a bit to get them in the same format as the other systems... Now we do the same with the extra buttons, we write %00110000 to $E9A005... and then we bit shift all the extra buttons in the correct positions... finally we OR all the unused bits to 1, to make the returned data consistent. |
![]() |
![]() |
Lesson
P8 - Joystick Reading on the Atari ST It's time for the Atari ST!... we're going to read in from the Atari's 1 button Joystick... Unfortunately, to my knowledge, there's no way of getting direct access to the Joystick... so begrudgingly I'm going to have to use the firmware to do the job! |
![]() |
![]() JoyTest.asm
|
|
![]() |
On the Z80 We used to read in the buttons and fires into a
single byte... on the 68000 systems we're going to keep the
same format for ULDR Fire1+2 and start... but we'll also
extend into a second byte for any extra fire buttons we
have... for each bit, a 1 means the button is up (unpressed) a 0 means the button is down (pressed) We'll load player 1's joystick data into D0... and player 2's into D1 |
|
||||||||||||||||||||||||||||||||||||
Our test program is very simple, all it does is read in the two controllers, and show the value of the registers to the screen. | ![]() |
We're going to define a few bytes of data for our use in
this example... two longs to back up the settings of the Bios
(we won't actually need them, but you might if you want to
undo things)... We also need two bytes... one for each joystick's data during the read operation |
![]() |
Our
principle is simple, get the bios to run our program code...
and use that code to copy the joystick data into a buffer...
When we want it, we'll sort the data into the format we need. |
![]() |
We're going to tell the BIOS that we want to use the
Joystick... we'll need Trap #13 to send a command to the
bios... We want to use Command #3 to send a command via Bconout... we push #13 as a word onto the stack before the trap command... BUT... the BconOut also needs a device number and a character to send... We want device #4 - which is the keyboard (which the joystick is connected to)... we push this onto the stack too... We want to send #$14 (#20) to the keyboard - this command tells the keyboard to send events (Every joystick movement is automatically returned.) When we've done all this, and the Trap #13 finishes, we need to fix the stack, so we add 6 to the stack, to remove the 3 words we pushed |
![]() |
Now we're going to use the XBIOS... which uses Trap #14...
we want to use command #34 'KbdVBase' - so we push #34
onto the stack as a word... |
![]() |
KbdVbase returns a vector table in D0 - this is a table of
calls for various purposes - we want to change entry 6...each
is a long so 6x4=24 We'll back up the address of the vector table, Next We're going to back up the current Keyboard vector (though in our code we won't ever undo it)... Finally we'll copy the address of our new joystick code (JoystickHandler) to the 24'th entry in the table... This means our code can get the Joystick data. |
![]() |
![]() |
Now we've
made the Bios do what we want, our Joystick handler will be
called automatically, and will grab the data for us... That's all we need the Bios for... now we're in control again! |
Because we patched it into the
Vector table, Our Joystick handler will be called by the
firmware, when it does, A0 will point to the data we
want... We're going the data from both joysticks into our 2 byte 'Joystick data' cache... we'll process that when the game actually wants to know what keys are pressed |
![]() |
When it comes to processing the
joysticks, we'll transfer the byte from the buffer for each
joystick into D0, then we'll run 'ReadControlsProcessOne'...
this will shift the bits from the Atari ST Format (F---RLDU)
to our common format (---FRLDU) We do this by shifting the bit we want to move into eXtend, then shifting things around, and putting it back in the position we want... We do this twice, moving Player 2's data into D1 |
![]() |
![]() |
Lesson
P9 - Joystick Reading on the
NeoGeo The Joystick on the NeoGeo makes things easy for us! There are two ports, one contains Select/Start buttons, and the other contains the joypad settings. We can just read in from these to get the directions selected on the Joypad. |
![]() |
![]() |
|
![]() |
On the Z80 We used to read in the buttons and fires into a
single byte... on the 68000 systems we're going to keep the
same format for ULDR Fire1+2 and start... but we'll also
extend into a second byte for any extra fire buttons we
have... for each bit, a 1 means the button is up (unpressed) a 0 means the button is down (pressed) We'll load player 1's joystick data into D0... and player 2's into D1 |
|
||||||||||||||||||||||||||||||||||||
Our test program is very simple, all it does is read in the two controllers, and show the value of the registers to the screen. | ![]() |
Bits | ||||||||||
Address | Purpose | Method | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
$10FDAC | Sel/Start | Bios | P4-Select | P4-Start | P3-Select | P3-Start | P2-Select | P2-Start | P1-Select | P1-Start |
$380000 | Sel/Start | Direct | AES/MVS | WriteProtect | CardInserted 2 | CardInserted 1 | P2-Select | P2-Start | P1-Select | P1-Start |
$10FD96 | Player 1 | Bios | D | C | B | A | Right | Left | Down | Up |
$300000 | Player 1 | Direct | D | C | B | A | Right | Left | Down | Up |
$10FD9C | Player 2 | Bios | D | C | B | A | Right | Left | Down | Up |
$340000 | Player 2 | Direct | D | C | B | A | Right | Left | Down | Up |
![]() |
The NeoGeo
AES also has coinslots, these can be read in from $320001
Bits 0,1,3,4 are coins 1-4... bit 3 is the 'service button' You'll need these if you're trying to crete an arcade game! |
As it's fairly easy, The example code today will support reading from either the BIOS variables, or the direct ports, to select the Bios we just need to define NeoJoy_UseBios, and the code will use the bios ports instead. | ![]() |
First we're going to handle Joystick 1 - we'll load it's
data into D4... We're also going to need Joystick 1's Start button, all the start buttons are in the same port, so we'll load them into D3 |
![]() |
As both joysticks use the same layout, We'll use a common
function 'Player_ReadControlsOne' to shift the bits around
into the order we need... we'll run it first for joystick 1,
and store the result in D0... We'll take a look at what the function does in a moment. |
![]() |
We're going to do the same now for Player 2. We don't need to read the Start buttons in again, as the second players start still remains in the byte in D3 we read before. |
![]() |
We using the 'Player_ReadControlsOne' function to convert
the data for both players... When this function starts, D4 will contain the joystick fires, First we need to shift button D out into D2 - we'll need it later, but we need to put the Start button into Bit 7 We'll shift the Start button into D4, we'll also 'skip' the select button - The AES arcade machines do not have it anyway. Now we want to move that D button into our register - so we shift it 8 bits to the left, and OR it into the correct position in D4... The last thing to do is ensure any unused bits are set to 1 - if we're using the BIOS we need to flip all the bits, but if we're not then we only flip the unused ones. |
![]() |
We're only
using two joysticks in these tutorials, but if that's not
enough you can theoretically have another have another 2 These are read in via the bios from $1OFDA2, $10FDA8... Whether the emulators will actually support them in another matter! |
![]() |
![]() |
Lesson
P10 - Cursor reading on the Sinclair QL The Sinclair QL supports up to 2 joysticks via the CTL ports (CTL1 and CTL2)... but these actually connect to the keyboard keys! Joystick 1 maps to the Cursor keys, and space for fire... We're going to read the cursors, and use them as the same joystick buttons on our other systems. |
![]() |
![]() JoyTest.asm |
![]() |
Reading in from the keboard has
to be done with Trap 1 - command 9 We have to send a sequence of command bytes - with byte 6 as the row number - the trap will return a byte in D1 - with a bit high when the button is down. An example of the command is shown to the right - this example will read in row 1 Joystick 1 (CTL1) uses Up, Down, Left, Right and Space Joystick 2 (CTL2) uses F4, F2, F1, F3 and F5 |
lea keycommand,a3 move.b #$11,d0 Trap #1 keycommand: dc.b $09 ;0 - Command dc.b $01 ;1 - parameter bytes dc.l 0 ;2345 - send option (%00=low nibble) dc.b 1 ;6 - Parameter: Row dc.b 2 ;7 - length of reply (%10=8 bits) |
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | |
7 | Shift | Ctrl | Alt | X | V | / | N | , |
6 | 8 | 2 | 6 | Q | E | O | T | U |
5 | 9 | W | I | Tab | R | - | Y | |
4 | L | 3 | H | 1 | A | P | D | J |
3 | I | Caps | K | S | F | = | G | ; |
2 | | | Z | . | C | B | pound | M | ~ |
1 | Enter | Left / J1-L | Up /
J1-U |
Esc | Right / J1-R | \ | Space / J1-F | Down / J1-D |
0 | F4/ J2-U | F1 / J2-L | 5 | F2 / J2-D | F3 / J2-R | F5 / J2-F | 4 | 7 |
![]() |
We're
only
going to read in one players controls in this example... we
could read in two players, but we would have the risk of
Keyclash.. When 3 buttons are pressed, that all make up corners of a square , then a 4'th will 'fire' incorrectly... EG: if buttons 1,3 and 2 are pressed then Q will fire even though it wasn't pressed... ALL keyboards do this to some extent due to the way they share lines. |
We're going to use a fixed read command, as we only need row 1 for our use, We'll use this with our Trap command later, | ![]() |
our 'Player_ReadControlsDual' function will read in the
input, We're going to use Trap #1 to read in from the keyboard, we need to pass the command we just defined to the trap in A3 The function will return the bits of the line in D1 We're going to use the bits in D1 to build up the result in D0 |
![]() |
We're going to move each of the
controls in one by one, We'll use D2 as a temporary copy of the the register, and shift the requred key bit into D0 We'll use Escape as a start button and Space,Enter and \ as Fire 1-3, shifting them into D0 Finally we'll shift in all the bits of UDLR into D0 |
![]() |
Finally, we need to blank out D1 - as we're not reading a
second player, We also need to flip the bits of D0 to make them 1 when a button is not pressed, and 0 when it is. |
![]() |
All
of
our controls are being read from a single line... If you
wanted to use a key like TAB for start, you'd need to read
in from another line of the keymap with another TRAP
command... Of course there's no reason you can't do this, but it would take more code, and you'd have to start thinking about keyclash |
![]() |