This is the first part of the Hello World Project.
As I’ve been an Atari ST owner since the late 80’s, and still code on my ST occasionally, it was an easy choice to begin with this platform. I could write most of this code without looking through documentation, with a few exceptions, like the OS function code number.
Before I begin to explain the code, I should tell you that this code is not very system friendly. You will not be able to exit the program, all you can do is reboot. After I wrote this code, I have started looking at the Amiga, and realized that it is quite handy to be more system friendly and exit the program in a more kind way. It will save you many reboots of the hardware/emulator while coding. It is very likely that I will return to this code later and fix those issues.
It is quite straight forward to get something draw on the screen on the ST. Just set the address to your allocated screen memory and move some data there. But to access the hardware registers that change the screen address and palette registers, we have to run our code in supervisor mode. This is special mode in the m68k processors where you can execute some privileged instructions and access protected memory areas. I will not go any deeper into the working of the m68k processor family, but if you are interested in programming the Atari, Amiga or any other machine containing an m68k processor, I recommend that you download the Programmers Reference Manual.
One way to run our code in supervisor mode is to call the XBIOS function Supexec, which calls a function in supervisor mode. Since this code is not system friendly, I decided to just stop and loop forever once the function has been completed.
The pea super_run instruction pushes the address to the function super_run to the stack, and the trap #14 instruction calls the XBIOS that then calls our function before it completes.
pea super_run ; Push function address to stack move.w #$26,-(sp) ; XBIOS call to Supexec. trap #14 ; Software interript #14 -> XBIOS addq.w #6,sp ; Fix stack pointer (not really needed in this case.) .halt bra.s .halt
The first thing to do in our function is to fix the screen memory address and set the screen base registers to point to our allocated memory. At the very end of the source code, there is a SECTION BSS entry, which contains a label screen_mem followed by a ds.b statement. The ds.b declares that we want to allocate space. The ST screen memory always consist of 32000 bytes (unless you are really advanced and do some hardware tricks to achieve overscan, but that is another story).
On an ST, the screen address must be aligned on 256 bytes. Therefore we allocate 256 extra bytes, add 255 to the original address and then clears the lowest byte. If you don’t understand why, grab a pen and a piece of paper and do some calculations yourself.
To set the screen base address, there are two byte sized registers you must write to. As the memory address space of the ST is only 24 bits, and the lowest byte in the address must be 0, it is only the two middle bytes in the 32 bit address that are relevant. The higher of these should be written to $ff8201 and the lower to $ff8203. The last to instructions in the code block below takes care of that. Remember, the m68k is a big endian system.
super_run: move.l #screen_mem,d0 ; Move address to screen mem to d0 add.l #$ff,d0 ; Add 255 d0 address clr.b d0 ; Clear lowest byte in address move.l d0,a6 ; Store screen pointer in a6 lsr.w #8,d0 ; Shift low 16 bits right 8 bits. move.l d0,$ffff8200.w
Lets move on to the colors. The ST palette is stored at the address $ff8240 and contains 16 16-bit wide (a word in the m68k lingo) registers with color data. Here is a little loop that sets all palette entries to black.
At address $ff8260 lies the screen mode register. By setting this to 0 we set the resolution to low res, which is 320 by 200 pixels.
; Set palette movea.w #$8240,a1 ; Pointer to palette entries. moveq #0,d0 ; Color is black. moveq #15,d1 ; loop 16 times. (dbra loops until dn == -1 ) .clear_palette move.w d0,(a1)+ dbra.w d1,.clear_palette move.w d0,$ffff8260.w
Start copying data to the screen memory. In lo wres mode, the screen consists of four bitplanes, interleaved with 16 bit words for each plane. If you never used bitplanes, they can be a bit confusing at first, but since many of the old school platforms in one way or the other uses bitplanes, it can be a good idea to read up on them. I will not try to explain how they work just now.
The graphics i made is one bitplane (uses only one color and thus only need one bit per pixel), so we skip three bitplanes when copying data.
lea gfx,a0 ; Load graphics data pointer to a0. move.l a6,a1 ; Copy screen address to a1 moveq #5,d1 ; loop 6 times (dbra loops until dn == -1) .draw_loop move.w (a0)+,0(a1) move.w (a0)+,8(a1) move.w (a0)+,16(a1) lea 160(a1),a1 ; Skip to next row, (160 bytes per row) dbra.w d1,.draw_loop
The screen base is set, our graphics is copied to the screen and the only thing that remains is to set palette entry 1 to our color of choice. Of course, if we had copied the graphics data to another bitplane than the first we would have to change another palette entry.
On plain ST the palette data consists three bits of data for each RGB channel in the lowest three nibbles. $700 would be red, $070 blue and $007 green.
move.w #$777,d0 move.w d0,$ffff8242.w rts ; Return from subroutine
Define the graphics in a data section. The data contains of binary data, where one bit corresponds to one pixel. 48 pixels wide and 6 pixels high.
SECTION DATA gfx: dc.w %0100010000000101,%0000000100010000,%0000000100001010 dc.w %0100010011100101,%0011000100010011,%0001100100111010 dc.w %0100010100010101,%0100100100010100,%1010010101001010 dc.w %0111110111110101,%0100100101010100,%1010000101001010 dc.w %0100010100000101,%0100100101010100,%1010000101001000 dc.w %0100010011110101,%0011000010100011,%0010000100111010
And finally we declare some space for the screen memory that we referenced earlier. The difference between the DATA section and BSS section is that everything in the DATA section will be store as data in the binary file, whilst the BSS section will be allocated by the system at load time. There is a flag in the executable file header that tell the OS whether the memory in the BSS section should be set to zero before the program starts. This is the default behaviour, but if the fast-load flag is set, the memory in the BSS section will be undefined when the program starts.
SECTION BSS ; Reserve screen memory screen_mem: ds.b 32256 END
Any questions or complaints? Leave a comment!
Next up, Amiga OCS. In a future not so far away.