Atari 8-bit Programming Tips and Recommendations
The Atari 8-bit computers are outstanding in terms of flexibility. Clear interfaces in the operating system have enabled the creation of a multitude of compatible extensions and replacements. For you as the coder, this means that you have to stick to a number of basic rules to ensure that all Atari users can actually run your program. The things you have to keep in mind are that there are:
- emulators will always behave somehow differently than the real machine, TV or storage device
- multiple operating systems (XL-OS, QMEG-OS, HISIO-OS, ...)
- multiple CPUs (65C02, 65C816)
- multiple video systems (PAL, NTSC)
- multiple memory sizes (48k, 64k, 128k, 192k, 320k, 576k, 1088k, ...)
- multiple disk operating systems (Atari DOS 2.5, MyDOS, SpartaDOS, XDOS, ...)
- multiple storage devices (C:, D:, H:, ...)
- multiple storage media (real disk, SIO2PC, SIO2SD, SIDE, Maxflash, The!Cart, ....)
- multiple storage media access speeds (19.2kb/s, 57.6kb/s, 125kb/s)
- multiple storage media speeders and loaders (Turbo 1050, SIO2SD, ...)
The basic rules can be summarized as
- test on real hardware
- stick to the well-defined operating system addresses and vectors
- stick to legal opcodes
- check for the video system
- check for the memory size
- create self-contained executables that can be loaded from any DOS, device, medium or menu
- pack large files and avoid cold starts to reduce the loading times
- avoid using typical memory for speeder locations and interrupts during I/O
Of course there are exceptions to these basic rules. For example a game that needs to load additional levels that people create with a level editor from a disk cannot be a self-contained executable. A 256 bytes demo will use illegal opcodes and illegal OS entry points to stay in the size limit. But your aim should always be to try and stick to the basic rules, because otherwise you limit the number of people who will be able to see and use your program.
Experience shows that there are three major issues that lead to crashing programs and sad users:
- You develop with an emulator or non-standard operating system, where BASIC is disabled by default. You program uses the BASIC memory are RAM. You test and everything works fine.
But user is sad because all real Atari machines have BASIC enabled by default. So the user has to remember if and when to press OPTIONS when switching on the computer. Otherwise the program will crash either during loading or at the start.
SOLUTION: If you require BASIC to be disabled, then disable is via your code. Don't rely on settings in the emulator, modified OS'es or the user's imagination.
- You develop with an emulator and your program grows and grows. You use the RAM in the BASIC area and maybe even the extended memory. You test and the program loads instantly and works fine.
But user is sad because all real Atari machines load with 19.2kb/s. That means a 40k program will take about 30 seconds to load. With extended RAM, this can easily go up to several minutes.
SOLUTION: Pack your program with one of the great packers out there or use a built-in packing for the data in your program.
- You develop a great game in Europe and test on a PAL machine.
But user is sad because the colors will be different, music will play too fast and eventually the program will crash. All Atari machines in the US are NTSC machines. That means frame rate is 60 Hz instead of 50 Hz. As a result you have more frames per second and less CPU time per frame. The game will crash because a new frame starts already before the game logic in the vertical blank interrupt was finished.
SOLUTION: Check for the video system type of the machine. Adapt the replay speed and possible the logic, so it can also deal with the 60 Hz frame rate.
Creating a compatible program is very easy, if you know some do's and don'ts
For example this is a perfectly fine and 100% compatible program that will work on any 8-bit Atari in just 21 bytes.
ORG $2000 ;Save area
START LDA $D40B ;Get current screen line STA $D01A ;Set as background color to see the rainbow JMP START ;Loop RUN START ;Set RUNADR, so START is called after loading
The following section gives are more detailed view and examples on what these rules actually mean for your code. If you need support in keeping or understanding any of the rules decribed below or a tester, feel free to contact me at or on AtariAge.
Test on real hardware with real memory
Reason: In the emulator all RAM is filled with zeros by default and after a cold start. But the RAM under the BASIC area ($a000-$bfff), the OS area ($c000-cfff, $d800,$ffff) and the extended RAM banks ($4000-$7fff) is filled with random values when the real Atari is powered on. And the contents that are written in theses areas are also kept after a cold start.
This means that if your program relies on these area being fill with zeros, it will crash or display unexpected garbage. This can also happen for other memory areas. For example the area $2000-$4fff is not zero, if a program is loaded from a DOS menu. So test on real hardware and always fill the memory yourself with the values you require to be there.
Test on real hardware with real TV
Reason: The emulators like Altirra and Atari800Win offer color palettes that are not correct. A program developed on an emulator can show different colors on real Ataris.
For example the doors in "Project M 2.0" do not have the colors red, green, blue as in Altirra but brown, white-blue, blue-violet.
Test on real hardware with real I/O
Reason: All real Atari machines load with 19.2kb/s. That means a 40k program will take about 30 seconds to load. With extended RAM, this can easily go up to several minutes. In addition, there a different SIO routines, speeds and timings. So it may also be the case that a modified SIO works with 125kb/s, leaving no time for any VBI or DLI code you have added.
Pack your program with one of the great packers out there or use a built-iin packing for the data in your program. Avoid running code during loading. On modern devices the loading speed can also be so high, that people won't even see what you do wanted to show while loading anyway.
Loading and Memory Rules
The program should be delivered as executable file and not as complete ATR
Reason: If you create a bootable disk instead of one of multiple files, you make it impossible for the user to put multiple programs onto a hard disk or compilation like a disk magazine (like the ABBUC Mag) or a compilation (like the SillyPack).
This means less people will get your program. Also loading executables directly works much better and faster than loading from simulated ATRs for example on Maxflash, The!Cart, SIO2SD, SIDE and other devices.
The program should be loadable from a regular DOS menu like DOS 2.5 or MyDOS
Reason: On the Atari, people use many different DOS'es and storage devices with different properties. DOS 2.5 and MyDOS can be considered the "common denominator". If something works with them, it'll normally also work with the other DOS'es.
The program should use file names that start with an upper-case letter and are followed by upper-case letters and digits only
Reason: Digits as first character, underscores in file names etc. are not supported many standard DOS'es.
The program should load from DOS to $2000 or above
Reason: Depending on the number of attached drives and devices, the memory below this address is typlically used by DOS. If you load directly to the area below $2000, the program will overwrite these parts will not load properly.
The program should not overwrite system zero page ($00-$7f), page 1 ($100-$1ff), 4 ($400-$4ff), 5 ($500-$6ff), 6 ($600-$6ff)
Reason: These memory locations are used by software driven speeders and file loaders of various devices. If you overwrite them the speeder or loader will crash and the user will have to play around with configuration settings and in the works case will not be able to run your program at all.
Programmers often test with modified operating systems (with built-in speeders) or emulators (with speeder settings active). This gives the wrong impression, that loading times are no problem.
The program should not overwrite DOS ($700-$1fff) when it starts
Reason: The machine will crash when the user presses RESET.
If you overwrite DOS, you also have to set the "cold start flag" (COLST=$244) to a non-zero value. But keep in mind that booting DOS during a cold start takes a significant time on a real unexpanded machine. So prevent overwriting DOS whenever possible.
The program should switch off BASIC automatically, if it requires BASIC to be disabled
Reason: Don't force the user to remember when to press OPTION and when not.
Remember that the default in many modified OS'es and in emulators is "BASIC OFF". On a real machine the default is "BASIC ON". So if the area $A000-$BFFF is used already during loading, no data will be stored in the RAM and the program will typically crash upon start. Relief the user from controlling BASIC by adding the following short INI segment at the beginning of the program file. It uses the OPEN and CLOSE vectors from the official E: handler table instead of the CIOV to save some bytes.
LOADER LDA #$c0 ;Check if RAMTOP is already OK
CMP $6a ;This prevent flickering if BASIC is already off
STA $6a ;Set RAMTOP to end of BASIC
STA $2E4 ;Set RAMSIZ also
LDA $d301 ;Disable BASIC bit in PORTB for MMU
LDA $a000 ;Check if BASIC ROM area is now writeable
BEQ RAMNOK ;If not, perform error handling....
LDA #$01 ;Set BASICF for OS, so BASIC remains OFF after RESET
LDX #2 ;Close "E:" before re-openining it again
LDX #0 ;Open "E:" to ensure screen is not at $9C00
EDITOR LDA $e401,x ;This prevents garbage when loading up to $bc000
RAMNOK INC 712 ;Add your error handling here, there still is a ROM....
INI LOADER ;Make sure the loader is executed before the main program is loaded
The program must not change zero page addresses $02-03 and $08-$0d
Reason: These addresses are used by RESET routine. If you change them, the machine will hang on RESET.
Note that older G2F version have this bug. And real machines do not have "SHIFT-F5" like your emulator has.
The program should not overwrite menu or DOS areas ($400-$47f, $0700-$1fff)
Reason: These areas are used by speeders, menus and DOS. If your overwrite them you force the user to boot again and waste time.
The program should not set or change COLDST ($244) by default
Reason: If you force a cold start, you force the user to boot again and waste time.
An exception to this rule applies, if you cannot keep rule "the executable should not overwrite menu or DOS area". If you do overwrite the menu or DOS area ($400-$47f, $0700-$1fff), you must set COLDST ($244) to a non-zero value. Otherwise, the machine will hang on RESET. And real machines do not have "SHIFT-F5" like your emulator has to force a cold start.
The program should auto-detect the actual extended RAM of the machine
Reason: Different machines use different upgrades.
If you support just a single extension, many people will not be able to run your program.
The program should handle different TV systems (PAL/NTSC)
Reason: Machines in Europa typically use PAL, machines in the US typically use NTSC,
If your program only supports PAL or only NTSC, only a limited number of people will be able to use it. At least you should display an appropriate message in this case, so the user knows why it is not working. The best solution is of course to check the PAL ($d014) register and adapt the color palette and sound replay speed accordingly. For playing a PAL tune on and NTSC system, you can use a simple counter counting from 5..0 and call the sound replay only for the values 5..1, skipping the replay for the counter value 0.
LDA $D014 AND #$0F CMP #$01 BEQ IS_PAL CMP #$14 BEQ IS_NTSC
The program should change DMA and DL only during or immediately after the vertical blank
Reason: Changing the ANTIC registers or DMACTL ($d400) or DLISTL/H ($d402/3) while the TV beam is still displaying the screen causes interferences, flicking and rolling screen on real TV with real Ataris. If DLIs are enabled, crashes are very likely, too.
You will typically not see or detect such issues on an emulator. This is because it doesn't emulate this behaviour and because it always uses the exactly same timing.
The program should initialize POKEY when it starts
Reason: When the program is loaded from a SIO device, POKEY is partly still in I/O mode and will not play sounds as you expect.
Write the value "0" to AUDCTL ($d208) and "3" to SKCTL ($d20f) to initialize POKEY. If you use Raster Music Tracker player or CMC player, this is done by the player initializations.
LDA #0 STA $D208 LDA #3 STA $D20F
The program should limit the maximum total sound volume
Reason: Each of the 4 channels has a volume between 0 and 15. If the sum of all volumes exceeds 32, a real Atari will sound distorted.