![]() |
Lesson
H1 - Hello World on the CPC As always, the CPC is my first 'Go to' system for development, and we'll be using Winape again, for it's superb built in Assembler! We'll use the firmware to do the work of drawing text to the screen, and we'll create our own PrintString function! |
![]() |
See
SimpleHelloWorld folder
|
![]() |
![]() |
Load up WinApe , Select Show Assembler from the Assembler menu | ![]() |
We need to define a start for our program in memory... BASIC and
the firmware need the first chunk, so memory address &1200 is
a good starting point to start your program at &1200 add the command ORG &1200 |
![]() |
To Print a Character, we're going to use firmware call &BBFA... calling this will print the Accumulator to the screen. | ![]() |
By using PrintChar with character 13 then character 10, we can start a new line | ![]() |
We're going to use this Printchar routine in our Printstring
function This function will be common to every platform |
![]() |
To use this function, we just point HL to the message, and Call PrintString | ![]() |
Select Assemble from the Assemble Menu |
![]() |
In the emulator type Call &1200 and press enter | ![]() |
The message will be shown onscreen |
![]() |
PrintChar equ
&BB5A org &1200 ld hl,Message ;Address of string Call PrintString ;Show String to screen ret ;Finished Hello World PrintString: ld a,(hl) ;Print a '255' terminated string cp 255 ret z inc hl call PrintChar jr PrintString Message: db 'Hello World 323!',255 NewLine: ld a,13 ;Carriage return call PrintChar ld a,10 ;Line Feed jp PrintChar |
We also have Montior Tools we can add... These have been covered previously in the Multiplatform tutorials... |
![]() |
With the Monitor, we can dump the
contents of the Main Registers... We can also dump areas of memory. |
![]() |
If we can see the registers and memory, we can use this to debug
code we're working on, and work out what's happening... Of course, on Winape we actually have a better option in the built in debugger (Press F7!) |
![]() |
DISK: The easiest way to run our program is compiling with WinApe, but if you want to use a different Assembler or emulator, we need to create an image! Creating a disk image can be performed with CPCDiskXP.exe To add an executable to a disk image, we need to add an AmsDos Header... |
![]() |
As we'll need to run our program often, it's best
to do this from the command line (there's a batch file to do
this in my DevTools) Here's an example usage: ![]() We've specified the Run Address the Source file (Binary program we compiled) and a Destination Disk image |
|
When it comes to loading a program in basic from Disk or Tape
there are three stages 1. Allocate memory with the MEMORY command 2. Load the Binary program to the address in our ORG statement 3. Call the address of the ORG statement |
![]() |
Tape: If you want to build a tape I use 2cdt This is a command line tool that builds a tape image, here's an example usage: By default 2CDT adds a file to an image, the first line starts a New Image with the -n switch We specify the Name of the file as it appears on the tape with -R Next we specify the source file name and path Finally we specify the destination tape image name If nessasary we can specify the type of file, in this example we specified one file as a 'Pure Data Block' (rather than a turbo loading data block) Note: The go.bas has to be already in CPC format (not a text file)... create a basic file on the CPC, and export it from the disk image with CPCDiskXP The program can be loaded on the CPC in the same way as the Disk image |
![]() |
Cartridge: The Easiest way to create a cartridge is with NoCart , this tool converts a disk image into a CPC+ Cartridge. It's a command line tool, and the command is as follows: We specify a Source Disk image - this is the disk with the program we want to make into a cartridge A Destination cartridge to create Finally, a basic run command which will start the program |
![]() |
Advanced Cartridge: Nocart works by using an Amsdos firmware emulator to run the disk image... if we want to use the full capabilities of the CPC+, then we probably want a more advanced solution... BuildCPR Will convert a binary rom to a cartrige - but you'll have to know how to do everything yourself with no default firmware, so it's not something for beginners... BuildCPR is released as source, but there is a compiled version in \Utils\buildcpr in my DevTools kit I used a modified version of the OS source file PATCHDOS.asm from BuildCPR, which loads the first 32k of ROM from the cartridge into RAM, and then executes it - effectively the same as my Disk code above. (This is available in my DevTools - \Utils\buildcpr folder) This needs to be compiled with PASMO to a ROM file |
![]() |
Next we need to merge the ROM![]() We need to merge the roms with our program binary into a temp file Then we convert the temp file into a destination CPR Cartridge ... |
![]() |
Creating a 'Real' Cartridge allows you to
use ROM banking to gain access to large amounts of data
allowing you to have a machine with 64k ram and 512k rom - if
you convert disk images, you've effectively got a 464+ with a
disk system. 'Real' cartridges are better, but they're more work, especially if you're planning to release for non plus CPC's as well. |
We're going to compile our program with the VASM
assembler, and test with the FUSE
emulator... I tend to use Notepad++ for my editor - you can get a pre-configured package in my DevTools download When we start our program, the first command should be an ORG &8000 statement... this defines that our program will be loaded in memory address &8000 (32768) |
![]() |
If your using my Devtools and Notepad++, press F6 and select VASM ZXS If you're not using my Devtools then read on - it's going to be tricky! |
![]() |
We're compiling with Oldstyle
VASM... The build script I use is shown to the right. There are a few parameters you may want to change... The Source file is specified - this version is called by a batch file, so it's shown as %1 here, The Destination file - we write to Program.Bin here.... we're outputting to binary here (specified by -Fbin) Some Symbols are defined - you won't need these for the Hello World example, but my other files use them. ![]() Note: This is just a binary, we need to get it into a tape or Disk to run it. |
|
*** Creating a Tape Image *** First we're going to convert our file to a TAP... we're going to use BinTap to do this (note the version in my download package is a newer build) We Specify a source Binary, a destination TAP, a filename (for the file in the TAP) and the origin (though we can override the origin in our loader) |
![]() |
We want to add a loader to our tape, the loader is shown too
the right, it has two lines. The first line loads the next file to memory address 32768 (&8000 - the same address as our ORG statement) the next line is a 'Randomize' statement, but it's actually a trick to 'Call &8000' - the USR statement defines a user function... this will run our program We can just create the basic program in notepad, and convert it using MkTap (I didn't write it, but I can't find a link to the original) We're specifying: A Source file to read from (in text format) A destination TAP file A Spectrum filename for the file (inside the TAP) We're also specifying a line number for the program to start (10) |
![]() ![]() |
We've now got a program TAPe and a loader TAPe... we need to
combine them using the windows COPY command - specifying /B for
binary copy We specify the source files, separater by +... order is important We specify the destination file for the complete TAP |
![]() |
We can specify the tape file on the Fuse emulator command line to load it automatically | ![]() |
*** Creating a DSK (Spectrum +3) Image *** The Spectrum +3 uses the same Disk file format as the Amstrad CPC, but we need a different kind of header for the files, I've created a tool called PlusThreeHeader that can add the correct file header First we use PlusThreeHeader to convert our Sourcefile into a Destination file valid for the Speccy (We need to give it a .C extension) Next we use CPCDiskXP to add the Destination file to a Disk image. |
![]() |
When we want to start Fuse, we can
specify a Source Disk image that we want
to load, we also need to specify that we're using a +3
machine... we can even load a Memory
Snapshot at the same time, to get the machine ready to
load our program!![]() |
|
To actually start the program, once again we need a basic
loader.. Once it's typed in, we could save a memory Snapshot, and load it on the command line to allow us to start our program quickly |
![]() |
If we want our disk to start automatically with the Loader, we just need to save a file called "DISK" and specify the line number to start the program (10) | ![]() |
*** Creating a TRD image *** TRD (Betadisk) is supported by many SD card disk emulators like the DIVMMC ENJOY and is the only real option for disks on non +3 systems... unfortunately it's a major pain! You can download an empty TRD image here... You can get a detailed (translated) TRD manual here... an English manual is also here |
|
To add files to a disk image we use TrdTool TRDTool cannot overwrite files... first we try to delete the file we want to add from the disk image (in case it already exists on the disk) Then we add the file we want to add to the disk image The file MUST end in .C |
![]() |
When loading Fuse, we probably want to emulate 128k
Machine, and we need to enable Betadisk We need to specify our TRD Image we made You may also want to have a memory snapshot loaded up, with your program about to start! |
![]() |
When sending a command to TRDOS, we first have to start with "Randomize USR 15619 : REM :"... what
follows will be the actual command TRDOS
executes You'll probably want to save a memory snapshot to save you typing this lot again! You can start the TRDOS command line with Randomize USR 15616... * Note: Some very old versions of TRDOS did not use 15619/15616 - they used alternate numbers |
![]() |
If you save a file called 'boot' it will autostart where possible (48k and DIVMMC only?) | ![]() |
![]() |
You may want
to develop for the ZX spectrum or MSX, but you should
startwith the CPC and WINAPE! Winape has a built in Assembler and Debugger that are second to none! In the early days you will make a lot of mistakes, and having the compiler ,emulator and debugger in one place will save you a lot of time and problems!You can even develop MSX and SPECTRUM code with it - the MSX and Spectrum versions of ChibiAkumas are compiled in winape too!.... If youdont't use widows, Winape works OK with WINE! |
in the Multiplatform
series, we created some tools for debugging programs, we can use these with this hello world example by including a few extra files. |
![]() |
This gives us two tools, a Monitor,
which will dump the CPU registers to screen. and a MemDump, which will output a number of bytes of memory to screen |
![]() |
We can use these tools to see the state of the CPU and Memory,
and this allows us to test and debug our code. |
![]() |
![]() |
It's
worth bearing in mind that TRD is bigger than DSK... a
single TRD can take 720k of data, but a DSK is only 180k... During development of ChibiAkumas, the game was developed on TRD, before compression was applied to make the game run on DSK. |
We're going to compile our program with the VASM
assembler, and OpenMSX for
emulation First we need to create a header... the header is 10 bytes... the header starts at &4000 in MSX memory (but byte 0 in the Binary output of the assembler) The first two bytes are always AB... the next two bytes point to the first line of code in ram (so probably &400A - 10 bytes in) The next 8 bytes are all &00 - as they are unused for our purposes |
![]() |
We need to add a small header to our
binary file to make it valid for the operating system - it
contains a calculated checksum, so it's easier to use a tool to
make it for us, I've created a command line tool called MSXheader
which will do this for us![]() We specify a Source file to read A Destination file to save the result, and a Load address... this is the place in memory the MSX OS will load the file by default |
|
We don't even need to create a real
disk image, OpenMSX can use a folder as a disk... so putting a
file in the specified folder will allow the MSX to use it, but
if we want to create a disk image (which we will for
redistribution) we can use the free tool BFI...
We need to collect all the files for the disk into a folder,
then convert the folder to a disk image![]() We can specify a source folder with the files, A Disk Image to create, I found some machines had trouble booting from the image, so I added a new bootsector to replace the default boot sector with an MSX bootsector, which seemed to solve the problem |
|
We can start OpenMSX from the command
line with the finished disk image![]() We specify a source disk image, We also specify a machine type, I tend to use the MSX2+ Sony XDJ as a good testing machine for disk software |
|
We need to load our program into memory (the load address is
in the file header) We then need to define USR0 to point to the address we want to call, and execute the program with a USR0(0) call To make the disk boot automatically save this to the disk as AUTOEXEC.BAS |
![]() ![]() |
in the Multiplatform
series, we created some tools for debugging programs, we can use these with this hello world example by including a few extra files. |
![]() |
This gives us two tools, a Monitor,
which will dump the CPU registers to screen. and a MemDump, which will output a number of bytes of memory to screen |
![]() |
We can use these tools to see the state of the CPU and Memory, and this allows us to test and debug our code. | ![]() |
We're going to use VASM to assemble the program to a binary
file.![]() We're going to specify the Source Asm file, The Destination Binary, We're also going to define some symbols, these are only needed for the monitor tools Finally we'll output a listing file - this is optional but will help if we have any unexpected problems |
In the Multiplatform
series, we created some tools for debugging programs, we can use these with this hello world example by including a few extra files. |
![]() |
This gives us two tools, a Monitor,
which will dump the CPU registers to screen. and a MemDump, which will output a number of bytes of memory to screen |
![]() |
We can use these tools to see the state of the CPU and Memory, and this allows us to test and debug our code. | ![]() |
If you're using my program, we can call it with a script on the command line | ![]() |
The text in the script are sent as keypresses to the program
(including Enter keys)... In this example we do 3 kinds of command N = Create a new disk image S = Save a file to the image (Specifying Source file, and Destination name) W = Write the complete disk to our computer as a disk image If we save our program with the name AutoGo , the SAM will load it automatically for us! |
![]() |
We can start SimCoupe with our disk image by specifying the disk image on the command line | ![]() |
If
you don't want to use the modified DiskMan, you can use the same
keypresses on the regular version, Alternatively there are disk editors that use a User Interface - the trouble is, while these programs work, they aren't efficient for developing, where you may be running your program dozens of times when fixing a problem |
![]() |
We can see the contents of our disk with the DIR command. | ![]() |
If we've created an AutoGo file, then it will run
automatically... we can load to a different address in the same
way as the spectrum, We need to specify the load address as well. |
![]() |
If we want to call a program in memory, we use the USR command to call an address - we need to send the output to somewhere, so we use the RANDOMIZE command for this.... We can also use CALL &8000 | ![]() |
After our program has finished, we'll stop the processor so we
can see the result. |
![]() |
After the last byte of our program, we need to define the FileEnd
so we can calculate the length of the file. |
![]() |
If we have FileIO set up (Learn more here)
we can just use a folder on our hard drive to run the program
from... In this case we need to specify File: as the source drive, and our program name |
![]() |
If we we want to create a DSK image, for a real machine (or if
we haven't got FileIO set up) we can just convert a folder with
the program into a disk image with the free tool BFI...
We need to specify two things... The Source folder, and the Destination disk image |
![]() |
We can start our program with a RUN command... Alternatively, if we save our program with the name START, we can load it automatically by pressing F1 |
![]() ![]() |
If we want to save time, we can configure EP128emu
on the command line,![]() I specify a config file with the correct emulator settings, and also a memory Snapshot to save time typing the run command |
![]() |
Setting up
FileIO is a bit of a pain, but the load time on the emulator is
faster than from Disk Image, so it's worth it... The same goes for using snapshot save states, they can help reduce the load time - and saving a second or two every run really ads up when you're developing a game! |
We're going to use VASM to assemble the program to a binary
file.![]() We're going to specify the Source Asm file, The Destination Binary, We're also going to define some symbols, these are only needed for the monitor tools Finally we'll output a listing file - this is optional but will help if we have any unexpected problems |
In the Multiplatform
series, we created some tools for debugging programs, we can use these with this hello world example by including a few extra files. |
![]() |
This gives us two tools, a Monitor,
which will dump the CPU registers to screen. and a MemDump, which will output a number of bytes of memory to screen |
![]() |
We can use these tools to see the state of the CPU and Memory, and this allows us to test and debug our code. | ![]() |
In the Multiplatform
series, we created some tools for debugging programs, we can use these with this hello world example by including a few extra files. |
![]() |
This gives us two tools, a Monitor,
which will dump the CPU registers to screen. and a MemDump, which will output a number of bytes of memory to screen |
![]() |
We can use these tools to see the state of the CPU and Memory, and this allows us to test and debug our code. | ![]() |
|
![]() |
First, We need to compile our program into a
binary, we use VASM in these tutorials, but you can use another
assembler if you prefer. We run VASM on the command line ![]() We need to specify a source file, We need to specify a destination binary For my Monitor tools, we need to define some symbols finally, we can output a listing, which can be used for debugging |
|
OK, we've made our binary, but there's a problem! - we need to
add a header and footer to our file, and unfortunately they're
quite complex! The header needs the file size adding to it, that's not too bad, and we could make it with VASM, but the footer needs a checksum which we must calculate, To create the header, I use a sample header file, and attach it to the binary made in the last stage, then I patch in the size, and add the checksums. |
![]() |
We're going to use a tool I wrote called BinaryTools
- this tool can do various useful things for building ROM
images! First we make a second copy of the program, this is so we can use one as a source to calculate checksums, and the other as the destination for those checksums. Now we calculate the checksums, and append them to the end of the program binary Next we insert the new length of the binary into the template header. Now we append the Execute position (the ORG we used in our code) to the end of the file (with the high byte (&65) repeated), we also insert it into the header. Our last stage is to use the Copy command in binary mode (/b) to merge the header and the program binary into a usable tape file! Phew! we've made a usable TAP! |
![]() |
We can automatically start our TAP with Jynx, by specifying
the TAP on the command line! The tape will load itself without us typing a single command! |
![]() |
![]() |
The Firmware of the Lynx is pretty slow,
even when drawing text, it may be better to use our own font
and draw things ourselves (like in the main tutorials), but of
course that will use up memory! You'll have to decide what your priorities are, and write your code accordingly! |
![]() |
The Limited
screen space means these monitor tools aren't a lot of use - for
more practical purposes, it's probably better to print the one
register you're interested in on screen when you're debugging See the main tutorial series for details on how to do Locate commands to re-position the print cursor! |
Loading
a program on the TI is a pain, so you definitely want to use a
save state to save time loading the program. If you want to run your program on a real machine, you'll need a USB cable - unfortunately they're horridly overpriced - so it's probably best to just stick to the emulator |
![]() |
![]() |
The example
today will build for the Sega Mastersystem by default... but with
a few tweaks we can compile for the GameGear... To do this just define the symbol 'BuildSGG' (with 'BuildSGG equ 1') - this will compile the Gamegear version (with alternate palette, tile offset, screen size and rom footer) |
Beginning a program
Only
the International SMS checks the Checksum, and while the later
GameGears check the header for the ''TMR SEGA" string, all other
systems check nothing at all! If you run the FUSION emulator without a bios Rom, then you can test your game on SMS or GG just fine with no header at all! |
![]() |
Initializing the screen
We need to turn on the screen and set it up, to do this we're
going to write default values to the VDP control port We're going to define symbols for the VDP Control port (&BF) and VDP data port (&BE) To transfer the data to the VDP we'll use OTIR. OTIR repeatedly OUTs bytes from HL to port C... and the byte count will be in B The code to the right will set up a valid screen. |
![]() ![]() ![]() |
Ok, so the screen will be working, but we won't be able to see
anything - as all the colors will be black! We need to set up a valid palette!... we're going to use OTIR to send the palette data to the VDP data port, but first we need to select the destination address in VRAM (&C000 is the start of the palette)... we do this with a function called 'PrepareVram' Next we out the bytes of the palette to the Vdp Data port (&BE) Note: we're sending 32 bytes of data on the GameGear - but only 16 on the Master System... that's because the GameGear has a better palette range than the SMS! |
![]() ![]() |
The palette is the biggest difference between the SMS and GG...
both have 16 colors for the tiles (background), but the SMS uses one
byte per color (2 bit per channel), but the GameGear has 2 bytes per
color (4 bits per channel) We use a symbol (BuildSGG) if it's defined, we'll use the GameGear palette, otherwise we'll use the MasterSystem one. |
![]() |
We're going define tiles 0-96 as our font (0 will be space) We're importing a font file called 'Font96.FNT'... this uses 8x8 characters (SMS/GG tiles are also 8x8) The font file is black and white (1 bit per pixel)... but the SMS/GG uses 4 bitplanes (4 bits per pixel)... We write the same byte 4 times, setting each bitplane to the same value, efffectively setting the text to color 15 If we wanted to set the font color to 1, we could load a with zero after the first 'out (vdpData),a', effectively setting bitplanes 2,3,4 to zero. |
![]() ![]() |
We have a font, but we need to actually handle the 'cursor'
position. Each time we show a character, we need to work out the position to show it, but we then need to update the position so we know where the next one should be shown to the screen. For this purpose we'll use two bytes of Ram at (&C000/1) for an X and Y position... But because the gamegear screen is smaller than the master system, the top left visible tile is not (0,0), but is actually (6,3)... we need to define the correct start position depending on the system |
![]() |
Printing a character to the screen
Ok, we need to print a character to the screen, but it's a little
tricky. The 'Tilemap' starts at VRAM address &3800... each tile is 2 bytes, and the tilemap is 32 tiles wide... therefore our formula is: Tile VramAddress=&3800 + (Ypos * 64)+ (Xpos *2) We achieve this calculation via a series of bitshifts... once we've found the Vram address, and selected it with the 'PrepareVram' function, we just need to OUT 2 bytes to the VdpData port, |
![]() |
Our font starts with Space (Character 32), so we subtract 32 from
the Ascii character to get the tile number... the second byte we
write is always zero in this case (it's used for palette options
flipping tiles and a few other things) |
![]() |
Once we've shown the character, we need to update the cursor, we
increase 'NextCharX' and see if we're at the end of the screen The screen is 32 tiles wide on the SMS, and just 20 on the GG... If we're at the end of the screen, we call the NewLine function to move down a line |
![]() |
The newline command increases the Y position, and resets the X position to the left of the screen. | ![]() |
Using our PrintChar to print a string
We're going to write a simple string printing routine, it prints a
255 terminated string. All we do is read in bytes from HL until we get to a 255, at which point we return... we print all other characters with our PrintChar routine |
![]() |
To print the string, just load HL with the address of the string, and call the function | ![]() |
This will show 'Hello World' to the screen | ![]() |
Patching the CRC into the Rom footer
We wrote a footer at &7FF0 before, but we didn't put a valid
checksum, which will mean the game won't boot on some SMS To fix this, we need to calculate a 16 bit 'Checksum' and write it to bytes &7FFA/B - we achieve this by adding each byte from &0000-&7FEF (even if the rom is larger than 32k)... The Checksum is 16 bit, this means if the calculation goes over 65535 (&FFFF) it rolls back to 0 I've written a tool called 'BinaryTools' which can create a valid checksum, it's included in the Devtools pack... the command to create the CRC is shown below: ![]() |
As an added bonus we can include the 'Monitor Tools' we developed
in the multiplatform series. These allow us to show the registers or parts of the Ram for testing and debugging purposes |
![]() |
This gives us 2 commands... Monitor shows all the registers, MemDumpDirect will show C bytes from address HL to the screen |
![]() |
We can see the result onscreen. | ![]() |
![]() |
This example code is pretty long, but we've
got the screen set up correctly, and now we can see the registers
and Ram, so we can do more from here! the "PrepareVram" function can be used to do sprites and things as well, so you should have a great starting point to make something better. |
![]() ![]() |
Lesson
H9- Hello World on the Gameboy and Gameboy Color This time we're going to learn how to make Hello World on the Gameboy and GBC... essentially they're the same system, but remember, the Gameboy isn't a 'true' z80 - it's a gbz80! |
![]() |
See
SimpleHelloWorld folder
|
![]() |
![]() |
![]() |
As far as
todays code goes, the regular Game Boy and GBC are virtually the
same, so the code we're making today will compile on GB or GBC -
we just need to define a symbol BuildGBC for the Gameboy Color! The Batch files provided in the DevTools will do this automatically! |
Beginning a program
We need to start our rom cartridge, the first 64 bytes are used by
the Z80 RST's - the gameboy does not need these, not even RST7 -
which does not handle the interrupt handler on the gameboy. In fact, the Gameboy Interrupts are handled by addreses &0040, &0048, &0050, &0058, &0060 - we need to make sure these interrupts won't do anything, so we just put a RETI at each |
![]() |
Execution of our cartridge will start at address &0100, but we need to put the cartridge header at &0104 - so we only have room for a jump here | ![]() |
Next we need the rom header - this is very important for a real
gameboy - as if the header Checksum, or Nintendo Logo are not
correct the game won't boot. We'll learn how to calculate that checksum later. |
![]() |
Our actual code will start at the 'begin' label | ![]() |
We're
going to have to do quite a bit of set up to get the system
working, we need to define our font, and palette so we can see
anything on screen... The gameboy is a great little system, but if you're just starting out with Z80, then a system like the Amstrad CPC is easier to get started with thanks to it's firmware. |
![]() |
Initializing the screen
We're going to start by setting up the position of our screen...
we're going to reset the 'tilemap' - moving it to position (0,0) We're also going to define two defined memory addresses NextCharacterX and Y - these will remember the position of the next character to draw - these will be in Gameboy Ram at &C000/1 |
![]() ![]() |
Next we need to turn off the screen so we can set things up... We have to wait until the screen is in Vblank... to do this we check address &FF44 - which will return the screen line - when the screen line is 156, we're in Vblank.... To turn off the screen we reset bit 7 of &FF40 |
![]() |
Now our screen is off ,we need to define our font... We're going to put this in tiles 0-95 - our font has 96 characters (from 32-128... first character is space, containing all standard visible Ascii characters) Our bitmap font is a black and white 8x8 font (8 bytes per character)... we're going to load from the label 'BitmapFont' into address &8000 - the start of the patterns in video ram Now, we read in each byte - but we write it twice... the Gameboy uses 2 bits per pixel - meaning 2 bitplanes, so we 'double up' the font data, writing it twice... This effectively means the font is Color 3 in the palette... We repeat for all the bytes of the font. |
![]() ![]() |
The next stage is to set the palette... this depends on if we're
using a Gameboy Color, or classic. On the Black and white GB: on a black and white gameboy, we define the background with address &FF47... sprites are defined by &FF48/9... each uses 2 bits per color, 00=White and 11=black On the Color GB: On color systems we have 8 palettes (0-7)... we're defining a function called 'SetGBCPalettes'... we're using palette 0 for the background, and palette 7 for the font... each palette has 4 colors - 2 bytes per color, so we multiply the palette number by 8. |
![]() |
On the Color GB: The native format of the GBC palettes is 16 bit in the format -BBBBBGG GGGRRRRR We read in bytes from our palette definiton (the end of the definition is defined by a bytepair of 255,255) Next we write the palette number we want to change to &FF68 (from C), after that we write the Low byte (from E) to &FF69 We increment C, and write the second palette number we want to change to &FF68 (from C), and write the High byte (from D) to &FF69 we Inc C again, and repeat to do the same for the following palette entry. |
![]() |
Ok, our font and palette are ready, so now we can turn the screen
back on! We're ready to write our program! |
![]() |
Printing a character to the screen
When we want to print a character to the screen, we need to
calculate the position in the Tilemap we want to draw the character, Although the screen is 20 tiles wide, The tilemap is 32 tiles wide... it starts at memory address &9800 Our formula is: Tile Address= &9800 + (ypos*32) + xpos We calculate this by reading in our Y-Pos, multiplying it by 32 (via bitshifts) and adding the X-Pos... then we add the &9800 base... The tile address for the character is now in HL |
![]() |
Before we write to Vram, we need to wait... we'll use a function
called 'LCDWait' to do this... we'll take a look at this in a
moment! As our font starts at character 32, we subtact 32 from the accumulator, the write A to the address in HL to set the tile visible at the location we calculated. On the Black and white GB, we're done! |
![]() |
On the Color GB: We need to select the palette... we're going to use GBC palette 7... The palette entry for our tile is at the same address (in HL)... BUT we need to switch in the extra GBC ram... to do this we write a 1 to address &FF47... This turns the GBC ram on... we write our palette number 7 to HL... Finally we turn off the GBC ram, by writing a 0 |
![]() |
When we want to wait for Vram to be ready, we need to check the
'Mode' bits of &FF41... in bits 0-1 If the 'Mode' is 0 or 1 - we can Write, if it's 2 or 3 we need to wait, so we can check this by waiting for bit 1 to be zero. |
![]() |
When we want to start a newline, we just Zero NextCharX, and increase NextCharY... we need to do this when Xpos reaches 20 (the width of the screen) | ![]() |
Using our PrintChar to print a string
We can build up our PrintChar routine into a PrintString routine, Our string is 255 terminated. | ![]() |
We load HL with the string address, and Call PrintString | ![]() |
The Text will be shown to screen | ![]() ![]() |
![]() |
Compiling our
rom is easy, we just need to compile to a binary file... because
we've included the header in our source as-is... we can just load
it straight away in Visual boy Advance! If we're using the BGB emulator, we need to fix the CRC first, lets learn how! |
Patching the CRC into the Rom
To make our ROM work with the BGB emulator , we need to ensure the
checksum is OK, we can fix it with RGBFIX
-v will tell the software to fix the rom checksum -p 0 will pad the rom with zeros. |
![]() |
Monitor Tools
We can include the Monitor tools we wrote in the Multiplatform
series. These will help us develop our programs |
![]() |
If we want to see the state of the registers, we can just call the
Monitor function... We can also dump parts of the memory, just load HL with an address, and C with a bytecount, and call Montior_MemDumpDirect |
![]() |
The Results can be seen here. | ![]() |
![]() |
Lesson
H10- Hello World on the ZX81 The ZX81 is much earlier than the Speccy, and it's hardware is far more limited... We'll have to include some 'special code' in our example to make something the ZX81 can run! |
![]() |
ZX8_HelloWorld.asm
|
|
![]() |
Beginning a program
We're going to create a "P" file... this is the binary data which
would be contained by a tape... we'll load it into our emulator to
run our tests. The start of our program needs a 116 byte header - this contains a variety of variables and pointers required for the system to run. |
![]() |
The start of our program is a 'REM statement'... This is to
'Contain' our ASM program!... Our binary will all be part of one huge line of our program which will follow this. |
![]() |
Ending a program
At the end of our ASM program
we need a second basic line. This is an "RAND USR 16514" ... this executes address 16514 (&4082) - the first line of our ASM program! |
![]() |
We've created our program... but we need to do two other things...
Firstly we need to allocate some 'VRAM' for the screen display (with appropriate new lines)... we'll start it all as space characters. We also need to allocate an address for 'Basic VARS'... we'll allocate 128 bytes at the end of our program. |
![]() |
Hello World!
We use 'byte 255' terminated
strings in all our tutorials. We'll load the address of our string into HL, and show it to the screen with PrintString. We need two bytes for our 'Cursor' pos so we know where to put the next character... we use the 'COORDS' bytes in the OS vars for this purpose. |
![]() |
Our PrintString routine just relies on our 'PrintChar' function to
do all the work! |
![]() |
The ZX81 does NOT use ASCII... we'll convert our ascii to the
character set used by the ZX81. |
![]() |
First we check if we're drawing a space... if it is, we need to
use Char 0. We now see if we've been passed a number, these are in a different place in the ZX81, so we add 7 Next we see if we've been passed lower case - the ZX81 can't do this - we convert to uppercase ASCII by subtracting 32 Next we convert A-Z by subtracting 27. Any Character over 64 is going to be a problem!... we'll convert these to '.' Once we know our character we calculate the screen destination VRAM with 'GetScreenPos' and write the byte of our character. We move our Xpos, nad if we're at Char 32 we start a new line. |
![]() |
To calculate the VRAM destination we use the following formula Destination ram = VRAM+1+ XPOS + (YPOS*33) We skip the first byte of VRAM as this is a newline byte. We effect multiplication of Ypos*33 by adding the Ypos*1, then bitshifting YPOS to calculate YPOS*32 and adding again. |
![]() |
We also create a newline command, which will move us down a Yline and Zero the Xpos | ![]() |
Note: This PRINTCHAR function doesn't provide for
brackets, punctuation etc)ess super-fast! If you need support for more characters, you could upgrade it to cope, but memory is so limited on the ZX81, it seemed better to 'do without' in this case. |
![]() |
Building our program
Here's the command I use to build a valid TAPE.P from the ASM
source.![]() %BuildFile% ... This is where your sourcefile goes - for example HELLO.ASM (%BuildFile% is used by my batch file) -chklabels ... This tells VASM to check for labels that look like commands... for example if we forget to indent a RET command it will be mistaken for a label! -nocase ... This turns off case sensitivity -Dvasm=1 -DBuildZX8=1 ... These are some predefined symbols (like EQU statements)... you don't need these for this example, but others will! -Fbin ... This tells VASM to output a BIN file. -o "\RelZX8\tape.p" ... This is the destination file - we'll get our emulator to run it. -L \BldZX8\Listing.txt ... This is a debugging listing file, you don't need it, but it may help you. |
Running our program
We can get Zesarux to start our program automatically with the command shown. | ![]() |
Here is the result! |
![]() |
Monitor Tools
As an added bonus we can include the 'Monitor Tools' we developed
in the multiplatform series. These allow us to show the registers or parts of the Ram for testing and debugging purposes |
![]() |
This gives us 2 commands... Monitor shows all the registers, MemDumpDirect will show C bytes from address HL to the screen |
![]() |
We can see the result onscreen. | ![]() |
![]() |
The memdump
address header of "4000:" has been incorrectly shown as "40003". That's because the colon ":" is not properly drawn by the PrintChar function! |
![]() |
Lesson
H11 - Hello World on the CPC via Native Tools (MAXAM) Lets try something a bit different!... We've built 'Hello World' on the CPC before, but this time lets try with the CPC's own assembler, MAXAM!... lets write, assemble and run our 'Hello World' all from the CPC itself, without winape's assembler or cross compilation! |
![]() |
CPC_Maxam.txt
|
![]() |
![]() |
MAXAM isn't
included in the Devtools package, but you can get it from the
fantastic CPCWiki
website! Fortunately WINAPE's assembler is pretty much identical to MAXAM, so basic programs will assemble fine without changes! |
Option 1!... Maxam 1.14 ROM
Lets try out the MAXAM rom version! We need to change the Memory settings of the CPC, and plug the MAXAM rom into one of the slots. |
![]() |
When we reboot our emulator the ROM will initialize. We can now use the '|M' (Bar M) command to start maxam... You can also type |maxam if you really like typing! |
![]() |
We can write a program in Amstrad Basic but We'll type in the program in with the Text Editor this time! (We'll use basic next time!) Select T - Text Editor, then E - Edit text |
![]() |
Lets Type in our program... ...or use Winape AutoType if we're getting tired fingers ! It's not Cheating *** IT'S NOT! *** |
![]() |
We need to assemble our program, Press ESC to go back to the previous menu, then select A - Assemble text |
![]() |
We will get a warning about a possible space missing in our
PrintString label, but the program will compile fine! |
![]() |
We need to run our program, so this time select J-Jump
to code. Our program started with ORG &4000, so specify address 4000. Our program will run! |
![]() |
Option 2!... Maxam Disk
That was far too easy, so this time lets use the DISK version....
because we enjoy pain! Insert the disk image, and type Run"disc |
![]() |
If we select 1 we'll get all the functionality of the ROM version,
but lets save some memory and loading time with just the assembler. Select option 3 |
![]() |
we'll be asked how many bytes to reserve for our code, we'll
allocate 2000. we could now use |m as before (Though option 3 didn't load the text editor), but this time we'll do it all with basic! |
![]() |
We can just type our assembly code in basic. The first line before our ASM code should be the |assemble command, all our code should start with a REM statement ' (Apostrophe) The |assemble command will build our program, so we can just use RUN to build it! |
![]() |
Our program will assemble like before. We can use CALL &4000 to run it. |
![]() |
With
MAXAM 1.5 the devs got lazy and couldn't be bothered to create a
text editor (or something like that!)... fortunately their text
editor has build functions added, so we'll use that! We can still compile from basic too! Lets have a go! |
![]() |
Option 3!... Maxam 1.5+ Protext ROM
This time we'll need two roms MAXAM15 and PROTEXT |
![]() |
We can compile from basic, or use |m But this time let's write our program in protext with |p |
![]() |
Lets switch to program mode, This mode is plaintext only Type in PROG at the prompt and hit enter. |
![]() |
Type your program in then press ESC to
get back to the prompt Type in ASM to build our program |
![]() |
Once our program compiles we can use J
to execute it directly from protext. (J= Jump) Our program will run! |
![]() |
![]() |
We've only scratched the teensiest weeniest
little bit of the capabilities of MAXAM and PROTEXT, for the full
features, please see the manuals! Or, you know, just use VASM in windows, and make your life easier, it's up to you! |