Hello Atari ST

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.