Created
September 27, 2025 16:08
-
-
Save dillera/aa99701d7212e9c4a448259ccd04ccb4 to your computer and use it in GitHub Desktop.
llms.txt for Atari Coding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| LLM.txt | |
| AtariCodingLLM | |
| ██████╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗███████╗███████╗██████╗ ███████╗ ██████╗ ██╗ ██╗██╗██████╗ ███████╗ | |
| ██╔════╝██╔════╝██╔════╝ ██╔════╝ ██║ ██║██╔════╝██╔════╝██╔══██╗██╔════╝ ██╔════╝ ██║ ██║██║██╔══██╗██╔════╝ | |
| ██║ ██║ ███████╗ ███████╗ ██║ ██║███████╗█████╗ ██████╔╝███████╗ ██║ ███╗██║ ██║██║██║ ██║█████╗ | |
| ██║ ██║ ██╔═══██╗╚════██║ ██║ ██║╚════██║██╔══╝ ██╔══██╗╚════██║ ██║ ██║██║ ██║██║██║ ██║██╔══╝ | |
| ╚██████╗╚██████╗╚██████╔╝███████║ ╚██████╔╝███████║███████╗██║ ██║███████║ ╚██████╔╝╚██████╔╝██║██████╔╝███████╗ | |
| ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝╚═════╝ ╚══════╝ | |
| # cc65 Users Guide | |
| ## Overview | |
| [Overview](https://cc65.github.io/doc/cc65.html#s1) | |
| ## Usage | |
| [Usage](https://cc65.github.io/doc/cc65.html#s2) | |
| ## Command line option overview | |
| [Command line option overview](https://cc65.github.io/doc/cc65.html#ss2.1) | |
| ## Command line options in detail | |
| [Command line options in detail](https://cc65.github.io/doc/cc65.html#ss2.2) | |
| ## Input and output | |
| [Input and output](https://cc65.github.io/doc/cc65.html#s3) | |
| ## Differences to the ISO standard | |
| [Differences to the ISO standard](https://cc65.github.io/doc/cc65.html#s4) | |
| ## Extensions | |
| [Extensions](https://cc65.github.io/doc/cc65.html#s5) | |
| ## Predefined macros | |
| [Predefined macros](https://cc65.github.io/doc/cc65.html#s6) | |
| ## #pragmas | |
| [#pragmas](https://cc65.github.io/doc/cc65.html#s7) | |
| ## #pragma allow-eager-inline ([push,] on|off) | |
| [#pragma allow-eager-inline ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.1) | |
| ## #pragma bss-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma bss-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#ss7.2) | |
| ## #pragma charmap (<index>, <code>) | |
| [#pragma charmap (<index>, <code>)](https://cc65.github.io/doc/cc65.html#ss7.3) | |
| ## #pragma check-stack ([push,] on|off) | |
| [#pragma check-stack ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.4) | |
| ## #pragma code-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma code-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#ss7.5) | |
| ## #pragma codesize ([push,] <int>) | |
| [#pragma codesize ([push,] <int>)](https://cc65.github.io/doc/cc65.html#ss7.6) | |
| ## #pragma data-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma data-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#ss7.7) | |
| ## #pragma inline-stdfuncs ([push,] on|off) | |
| [#pragma inline-stdfuncs ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.8) | |
| ## #pragma local-strings ([push,] on|off) | |
| [#pragma local-strings ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.9) | |
| ## #pragma message (<message>) | |
| [#pragma message (<message>)](https://cc65.github.io/doc/cc65.html#ss7.10) | |
| ## #pragma optimize ([push,] on|off) | |
| [#pragma optimize ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.11) | |
| ## #pragma rodata-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma rodata-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#ss7.12) | |
| ## #pragma regvaraddr ([push,] on|off) | |
| [#pragma regvaraddr ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.13) | |
| ## #pragma register-vars ([push,] on|off) | |
| [#pragma register-vars ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.14) | |
| ## #pragma signed-chars ([push,] on|off) | |
| [#pragma signed-chars ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.15) | |
| ## #pragma static-locals ([push,] on|off) | |
| [#pragma static-locals ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.16) | |
| ## #pragma warn (name, [push,] on|off) | |
| [#pragma warn (name, [push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.17) | |
| ## #pragma wrapped-call (push, <name>, <identifier>) | |
| [#pragma wrapped-call (push, <name>, <identifier>)](https://cc65.github.io/doc/cc65.html#ss7.18) | |
| ## #pragma writable-strings ([push,] on|off) | |
| [#pragma writable-strings ([push,] on|off)](https://cc65.github.io/doc/cc65.html#ss7.19) | |
| ## #pragma zpsym (<name>) | |
| [#pragma zpsym (<name>)](https://cc65.github.io/doc/cc65.html#ss7.20) | |
| ## Register variables | |
| [Register variables](https://cc65.github.io/doc/cc65.html#s8) | |
| ## Inline assembler | |
| [Inline assembler](https://cc65.github.io/doc/cc65.html#s9) | |
| ## Implementation-defined behavior | |
| [Implementation-defined behavior](https://cc65.github.io/doc/cc65.html#s10) | |
| ## Copyright | |
| [Copyright](https://cc65.github.io/doc/cc65.html#s11) | |
| ## Overview | |
| [Overview](https://cc65.github.io/doc/cc65.html#toc1) | |
| ## library.html | |
| [library.html](https://cc65.github.io/doc/library.html) | |
| ## funcref.html | |
| [funcref.html](https://cc65.github.io/doc/funcref.html) | |
| ## coding.html | |
| [coding.html](https://cc65.github.io/doc/coding.html) | |
| ## Usage | |
| [Usage](https://cc65.github.io/doc/cc65.html#toc2) | |
| ## ca65.html | |
| [ca65.html](https://cc65.github.io/doc/ca65.html) | |
| ## Command line option overview | |
| [Command line option overview](https://cc65.github.io/doc/cc65.html#toc2.1) | |
| ## Command line options in detail | |
| [Command line options in detail](https://cc65.github.io/doc/cc65.html#toc2.2) | |
| ## #pragma bss-name | |
| [#pragma bss-name](https://cc65.github.io/doc/cc65.html#pragma-bss-name) | |
| ## #pragma check-stack | |
| [#pragma check-stack](https://cc65.github.io/doc/cc65.html#pragma-check-stack) | |
| ## #pragma code-name | |
| [#pragma code-name](https://cc65.github.io/doc/cc65.html#pragma-code-name) | |
| ## -t | |
| [-t](https://cc65.github.io/doc/cc65.html#option-t) | |
| ## #pragma data-name | |
| [#pragma data-name](https://cc65.github.io/doc/cc65.html#pragma-data-name) | |
| ## -d P | |
| [-d P](https://cc65.github.io/doc/cc65.html#option-dP) | |
| ## -d M | |
| [-d M](https://cc65.github.io/doc/cc65.html#option-dM) | |
| ## --create-dep | |
| [--create-dep](https://cc65.github.io/doc/cc65.html#option-create-dep) | |
| ## --create-full-dep | |
| [--create-full-dep](https://cc65.github.io/doc/cc65.html#option-create-full-dep) | |
| ## --inline-stdfuncs | |
| [--inline-stdfuncs](https://cc65.github.io/doc/cc65.html#option-inline-stdfuncs) | |
| ## #pragma allow-eager-inline | |
| [#pragma allow-eager-inline](https://cc65.github.io/doc/cc65.html#pragma-allow-eager-inline) | |
| ## -Os | |
| [-Os](https://cc65.github.io/doc/cc65.html#option-O) | |
| ## #pragma inline-stdfuncs | |
| [#pragma inline-stdfuncs](https://cc65.github.io/doc/cc65.html#pragma-inline-stdfuncs) | |
| ## -W | |
| [-W](https://cc65.github.io/doc/cc65.html#option-W) | |
| ## #pragma local-strings | |
| [#pragma local-strings](https://cc65.github.io/doc/cc65.html#pragma-local-strings) | |
| ## register variables | |
| [register variables](https://cc65.github.io/doc/cc65.html#register-vars) | |
| ## #pragma register-vars | |
| [#pragma register-vars](https://cc65.github.io/doc/cc65.html#pragma-register-vars) | |
| ## #pragma rodata-name | |
| [#pragma rodata-name](https://cc65.github.io/doc/cc65.html#pragma-rodata-name) | |
| ## #pragma signed-chars | |
| [#pragma signed-chars](https://cc65.github.io/doc/cc65.html#pragma-signed-chars) | |
| ## --cpu | |
| [--cpu](https://cc65.github.io/doc/cc65.html#option--cpu) | |
| ## #pragma writable-strings | |
| [#pragma writable-strings](https://cc65.github.io/doc/cc65.html#pragma-writable-strings) | |
| ## #pragma static-locals | |
| [#pragma static-locals](https://cc65.github.io/doc/cc65.html#pragma-static-locals) | |
| ## --register-vars | |
| [--register-vars](https://cc65.github.io/doc/cc65.html#option-register-vars) | |
| ## --warnings-as-errors | |
| [--warnings-as-errors](https://cc65.github.io/doc/cc65.html#option--warnings-as-errors) | |
| ## #pragma charmap() | |
| [#pragma charmap()](https://cc65.github.io/doc/cc65.html#pragma-charmap) | |
| ## --list-warnings | |
| [--list-warnings](https://cc65.github.io/doc/cc65.html#option-list-warnings) | |
| ## #pragma warn | |
| [#pragma warn](https://cc65.github.io/doc/cc65.html#pragma-warn) | |
| ## Input and output | |
| [Input and output](https://cc65.github.io/doc/cc65.html#toc3) | |
| ## Differences to the ISO standard | |
| [Differences to the ISO standard](https://cc65.github.io/doc/cc65.html#toc4) | |
| ## --standard | |
| [--standard](https://cc65.github.io/doc/cc65.html#option--standard) | |
| ## see below | |
| [see below](https://cc65.github.io/doc/cc65.html#extension-fastcall) | |
| ## Extensions | |
| [Extensions](https://cc65.github.io/doc/cc65.html#toc5) | |
| ## see there | |
| [see there](https://cc65.github.io/doc/cc65.html#inline-asm) | |
| ## GEOS library document | |
| [GEOS library document](https://cc65.github.io/doc/geos.html) | |
| ## Predefined macros | |
| [Predefined macros](https://cc65.github.io/doc/cc65.html#toc6) | |
| ## .CPU | |
| [.CPU](https://cc65.github.io/doc/ca65.html#.CPU) | |
| ## __CPU__ | |
| [__CPU__](https://cc65.github.io/doc/cc65.html#macro-CPU) | |
| ## --eagerly-inline-funcs | |
| [--eagerly-inline-funcs](https://cc65.github.io/doc/cc65.html#option-eagerly-inline-funcs) | |
| ## #pragmas | |
| [#pragmas](https://cc65.github.io/doc/cc65.html#toc7) | |
| ## #pragma allow-eager-inline ([push,] on|off) | |
| [#pragma allow-eager-inline ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.1) | |
| ## #pragma bss-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma bss-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#toc7.2) | |
| ## #pragma charmap (<index>, <code>) | |
| [#pragma charmap (<index>, <code>)](https://cc65.github.io/doc/cc65.html#toc7.3) | |
| ## #pragma check-stack ([push,] on|off) | |
| [#pragma check-stack ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.4) | |
| ## #pragma code-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma code-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#toc7.5) | |
| ## #pragma codesize ([push,] <int>) | |
| [#pragma codesize ([push,] <int>)](https://cc65.github.io/doc/cc65.html#toc7.6) | |
| ## --codesize | |
| [--codesize](https://cc65.github.io/doc/cc65.html#option-codesize) | |
| ## #pragma data-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma data-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#toc7.7) | |
| ## #pragma inline-stdfuncs ([push,] on|off) | |
| [#pragma inline-stdfuncs ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.8) | |
| ## #pragma local-strings ([push,] on|off) | |
| [#pragma local-strings ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.9) | |
| ## #pragma message (<message>) | |
| [#pragma message (<message>)](https://cc65.github.io/doc/cc65.html#toc7.10) | |
| ## #pragma optimize ([push,] on|off) | |
| [#pragma optimize ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.11) | |
| ## codesize pragma | |
| [codesize pragma](https://cc65.github.io/doc/cc65.html#pragma-codesize) | |
| ## #pragma rodata-name ([push, ]<name>[ ,<addrsize>]) | |
| [#pragma rodata-name ([push, ]<name>[ ,<addrsize>])](https://cc65.github.io/doc/cc65.html#toc7.12) | |
| ## #pragma regvaraddr ([push,] on|off) | |
| [#pragma regvaraddr ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.13) | |
| ## #pragma register-vars ([push,] on|off) | |
| [#pragma register-vars ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.14) | |
| ## #pragma signed-chars ([push,] on|off) | |
| [#pragma signed-chars ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.15) | |
| ## --signed-chars | |
| [--signed-chars](https://cc65.github.io/doc/cc65.html#option-signed-chars) | |
| ## #pragma static-locals ([push,] on|off) | |
| [#pragma static-locals ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.16) | |
| ## --static-locals | |
| [--static-locals](https://cc65.github.io/doc/cc65.html#option-static-locals) | |
| ## #pragma warn (name, [push,] on|off) | |
| [#pragma warn (name, [push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.17) | |
| ## #pragma wrapped-call (push, <name>, <identifier>) | |
| [#pragma wrapped-call (push, <name>, <identifier>)](https://cc65.github.io/doc/cc65.html#toc7.18) | |
| ## .bank | |
| [.bank](https://cc65.github.io/doc/ca65.html#.BANK) | |
| ## Other MEMORY area attributes | |
| [Other MEMORY area attributes](https://cc65.github.io/doc/ld65.html#MEMORY) | |
| ## #pragma writable-strings ([push,] on|off) | |
| [#pragma writable-strings ([push,] on|off)](https://cc65.github.io/doc/cc65.html#toc7.19) | |
| ## --writable-strings | |
| [--writable-strings](https://cc65.github.io/doc/cc65.html#option-writable-strings) | |
| ## #pragma zpsym (<name>) | |
| [#pragma zpsym (<name>)](https://cc65.github.io/doc/cc65.html#toc7.20) | |
| ## Register variables | |
| [Register variables](https://cc65.github.io/doc/cc65.html#toc8) | |
| ## Inline assembler | |
| [Inline assembler](https://cc65.github.io/doc/cc65.html#toc9) | |
| ## Implementation-defined behavior | |
| [Implementation-defined behavior](https://cc65.github.io/doc/cc65.html#toc10) | |
| ## Copyright | |
| [Copyright](https://cc65.github.io/doc/cc65.html#toc11) | |
| # Atari specific information for cc65 | |
| ## Overview | |
| [Overview](https://cc65.github.io/doc/atari.html#s1) | |
| ## Binary format | |
| [Binary format](https://cc65.github.io/doc/atari.html#s2) | |
| ## Memory layout | |
| [Memory layout](https://cc65.github.io/doc/atari.html#s3) | |
| ## atari target | |
| [atari target](https://cc65.github.io/doc/atari.html#ss3.1) | |
| ## atarixl target | |
| [atarixl target](https://cc65.github.io/doc/atari.html#ss3.2) | |
| ## Linker configurations | |
| [Linker configurations](https://cc65.github.io/doc/atari.html#s4) | |
| ## atari config files | |
| [atari config files](https://cc65.github.io/doc/atari.html#ss4.1) | |
| ## atarixl config files | |
| [atarixl config files](https://cc65.github.io/doc/atari.html#ss4.2) | |
| ## Platform specific header files | |
| [Platform specific header files](https://cc65.github.io/doc/atari.html#s5) | |
| ## Atari specific functions | |
| [Atari specific functions](https://cc65.github.io/doc/atari.html#ss5.1) | |
| ## Hardware access | |
| [Hardware access](https://cc65.github.io/doc/atari.html#ss5.2) | |
| ## Display lists | |
| [Display lists](https://cc65.github.io/doc/atari.html#ss5.3) | |
| ## Character mapping | |
| [Character mapping](https://cc65.github.io/doc/atari.html#ss5.4) | |
| ## Keyboard codes | |
| [Keyboard codes](https://cc65.github.io/doc/atari.html#ss5.5) | |
| ## Loadable drivers | |
| [Loadable drivers](https://cc65.github.io/doc/atari.html#s6) | |
| ## Graphics drivers | |
| [Graphics drivers](https://cc65.github.io/doc/atari.html#ss6.1) | |
| ## Extended memory drivers | |
| [Extended memory drivers](https://cc65.github.io/doc/atari.html#ss6.2) | |
| ## Joystick drivers | |
| [Joystick drivers](https://cc65.github.io/doc/atari.html#ss6.3) | |
| ## Mouse drivers | |
| [Mouse drivers](https://cc65.github.io/doc/atari.html#ss6.4) | |
| ## RS232 device drivers | |
| [RS232 device drivers](https://cc65.github.io/doc/atari.html#ss6.5) | |
| ## Limitations | |
| [Limitations](https://cc65.github.io/doc/atari.html#s7) | |
| ## Realtime clock | |
| [Realtime clock](https://cc65.github.io/doc/atari.html#ss7.1) | |
| ## atarixl target | |
| [atarixl target](https://cc65.github.io/doc/atari.html#ss7.2) | |
| ## DIO implementation | |
| [DIO implementation](https://cc65.github.io/doc/atari.html#s8) | |
| ## CONIO implementation | |
| [CONIO implementation](https://cc65.github.io/doc/atari.html#s9) | |
| ## Technical details | |
| [Technical details](https://cc65.github.io/doc/atari.html#s10) | |
| ## atari | |
| [atari](https://cc65.github.io/doc/atari.html#ss10.1) | |
| ## atarixl | |
| [atarixl](https://cc65.github.io/doc/atari.html#ss10.2) | |
| ## Other hints | |
| [Other hints](https://cc65.github.io/doc/atari.html#s11) | |
| ## Function keys | |
| [Function keys](https://cc65.github.io/doc/atari.html#ss11.1) | |
| ## Passing arguments to the program | |
| [Passing arguments to the program](https://cc65.github.io/doc/atari.html#ss11.2) | |
| ## Interrupts | |
| [Interrupts](https://cc65.github.io/doc/atari.html#ss11.3) | |
| ## Reserving a memory area inside a program | |
| [Reserving a memory area inside a program](https://cc65.github.io/doc/atari.html#ss11.4) | |
| ## Upgrading from an older cc65 version | |
| [Upgrading from an older cc65 version](https://cc65.github.io/doc/atari.html#ss11.5) | |
| ## Getting rid of the "system check" load chunk | |
| [Getting rid of the "system check" load chunk](https://cc65.github.io/doc/atari.html#ss11.6) | |
| ## License | |
| [License](https://cc65.github.io/doc/atari.html#s12) | |
| ## Overview | |
| [Overview](https://cc65.github.io/doc/atari.html#toc1) | |
| ## limitations | |
| [limitations](https://cc65.github.io/doc/atari.html#xllimitations) | |
| ## function reference | |
| [function reference](https://cc65.github.io/doc/funcref.html) | |
| ## Binary format | |
| [Binary format](https://cc65.github.io/doc/atari.html#toc2) | |
| ## Technical details | |
| [Technical details](https://cc65.github.io/doc/atari.html#techdetail) | |
| ## Reserving a memory area inside the program | |
| [Reserving a memory area inside the program](https://cc65.github.io/doc/atari.html#memhole) | |
| ## Memory layout | |
| [Memory layout](https://cc65.github.io/doc/atari.html#toc3) | |
| ## atari target | |
| [atari target](https://cc65.github.io/doc/atari.html#toc3.1) | |
| ## Final note | |
| [Final note](https://cc65.github.io/doc/atari.html#memhole_final_note) | |
| ## atarixl target | |
| [atarixl target](https://cc65.github.io/doc/atari.html#toc3.2) | |
| ## atarixl chargen location | |
| [atarixl chargen location](https://cc65.github.io/doc/atari.html#chargenloc) | |
| ## Linker configurations | |
| [Linker configurations](https://cc65.github.io/doc/atari.html#toc4) | |
| ## atari config files | |
| [atari config files](https://cc65.github.io/doc/atari.html#toc4.1) | |
| ## "system check" | |
| ["system check"](https://cc65.github.io/doc/atari.html#syschk) | |
| ## Getting rid of the "system check" load chunk | |
| [Getting rid of the "system check" load chunk](https://cc65.github.io/doc/atari.html#nosyschk) | |
| ## atarixl config files | |
| [atarixl config files](https://cc65.github.io/doc/atari.html#toc4.2) | |
| ## "system check" | |
| ["system check"](https://cc65.github.io/doc/atari.html#syschkxl) | |
| ## Platform specific header files | |
| [Platform specific header files](https://cc65.github.io/doc/atari.html#toc5) | |
| ## Atari specific functions | |
| [Atari specific functions](https://cc65.github.io/doc/atari.html#toc5.1) | |
| ## Hardware access | |
| [Hardware access](https://cc65.github.io/doc/atari.html#toc5.2) | |
| ## Display lists | |
| [Display lists](https://cc65.github.io/doc/atari.html#toc5.3) | |
| ## posix_memalign() | |
| [posix_memalign()](https://cc65.github.io/doc/funcref.html#posix_memalign) | |
| ## Character mapping | |
| [Character mapping](https://cc65.github.io/doc/atari.html#toc5.4) | |
| ## Keyboard codes | |
| [Keyboard codes](https://cc65.github.io/doc/atari.html#toc5.5) | |
| ## Loadable drivers | |
| [Loadable drivers](https://cc65.github.io/doc/atari.html#toc6) | |
| ## Graphics drivers | |
| [Graphics drivers](https://cc65.github.io/doc/atari.html#toc6.1) | |
| ## Selecting a good program load address | |
| [Selecting a good program load address](https://cc65.github.io/doc/atari.html#loadaddr) | |
| ## Extended memory drivers | |
| [Extended memory drivers](https://cc65.github.io/doc/atari.html#toc6.2) | |
| ## Joystick drivers | |
| [Joystick drivers](https://cc65.github.io/doc/atari.html#toc6.3) | |
| ## Mouse drivers | |
| [Mouse drivers](https://cc65.github.io/doc/atari.html#toc6.4) | |
| ## RS232 device drivers | |
| [RS232 device drivers](https://cc65.github.io/doc/atari.html#toc6.5) | |
| ## Limitations | |
| [Limitations](https://cc65.github.io/doc/atari.html#toc7) | |
| ## Realtime clock | |
| [Realtime clock](https://cc65.github.io/doc/atari.html#toc7.1) | |
| ## atarixl target | |
| [atarixl target](https://cc65.github.io/doc/atari.html#toc7.2) | |
| ## DIO implementation | |
| [DIO implementation](https://cc65.github.io/doc/atari.html#toc8) | |
| ## CONIO implementation | |
| [CONIO implementation](https://cc65.github.io/doc/atari.html#toc9) | |
| ## Technical details | |
| [Technical details](https://cc65.github.io/doc/atari.html#toc10) | |
| ## atari | |
| [atari](https://cc65.github.io/doc/atari.html#toc10.1) | |
| ## atarixl | |
| [atarixl](https://cc65.github.io/doc/atari.html#toc10.2) | |
| ## Other hints | |
| [Other hints](https://cc65.github.io/doc/atari.html#toc11) | |
| ## Function keys | |
| [Function keys](https://cc65.github.io/doc/atari.html#toc11.1) | |
| ## Passing arguments to the program | |
| [Passing arguments to the program](https://cc65.github.io/doc/atari.html#toc11.2) | |
| ## Interrupts | |
| [Interrupts](https://cc65.github.io/doc/atari.html#toc11.3) | |
| ## assembler manual | |
| [assembler manual](https://cc65.github.io/doc/ca65.html) | |
| ## Reserving a memory area inside a program | |
| [Reserving a memory area inside a program](https://cc65.github.io/doc/atari.html#toc11.4) | |
| ## Upgrading from an older cc65 version | |
| [Upgrading from an older cc65 version](https://cc65.github.io/doc/atari.html#toc11.5) | |
| ## Getting rid of the "system check" load chunk | |
| [Getting rid of the "system check" load chunk](https://cc65.github.io/doc/atari.html#toc11.6) | |
| ## License | |
| [License](https://cc65.github.io/doc/atari.html#toc12) | |
| █████╗ ████████╗ █████╗ ██████╗ ██╗ ███████╗██████╗ ███████╗ ██████╗██╗███████╗██╗ ██████╗ | |
| ██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██║ ██╔════╝██╔══██╗██╔════╝██╔════╝██║██╔════╝██║██╔════╝ | |
| ███████║ ██║ ███████║██████╔╝██║ ███████╗██████╔╝█████╗ ██║ ██║█████╗ ██║██║ | |
| ██╔══██║ ██║ ██╔══██║██╔══██╗██║ ╚════██║██╔═══╝ ██╔══╝ ██║ ██║██╔══╝ ██║██║ | |
| ██║ ██║ ██║ ██║ ██║██║ ██║██║ ███████║██║ ███████╗╚██████╗██║██║ ██║╚██████╗ | |
| ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚══════╝ ╚═════╝╚═╝╚═╝ ╚═╝ ╚═════╝ | |
| ██╗███╗ ██╗███████╗ ██████╗ ██████╗ ███╗ ███╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ | |
| ██║████╗ ██║██╔════╝██╔═══██╗██╔══██╗████╗ ████║██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝██╔═══██╗██╔══██╗ ██╔════╝██╔════╝██╔════╝ ██╔════╝ | |
| ██║██╔██╗ ██║█████╗ ██║ ██║██████╔╝██╔████╔██║███████║ ██║ ██║██║ ██║██╔██╗ ██║ █████╗ ██║ ██║██████╔╝ ██║ ██║ ███████╗ ███████╗ | |
| ██║██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║╚██╔╝██║██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ ██╔══╝ ██║ ██║██╔══██╗ ██║ ██║ ██╔═══██╗╚════██║ | |
| ██║██║ ╚████║██║ ╚██████╔╝██║ ██║██║ ╚═╝ ██║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ╚██████╔╝██║ ██║ ╚██████╗╚██████╗╚██████╔╝███████║ | |
| ╚═╝╚═╝ ╚═══╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ | |
| # Atari specific information for cc65 | |
| ## Shawn Jefferson and Christian Groessler | |
| --- | |
| *An overview over the Atari runtime system as it is implemented for the cc65 C | |
| compiler.* | |
| --- | |
| ## 1. [Overview](https://cc65.github.io/doc/atari.html#s1) | |
| ## 2. [Binary format](https://cc65.github.io/doc/atari.html#s2) | |
| ## 3. [Memory layout](https://cc65.github.io/doc/atari.html#s3) | |
| * 3.1 [`atari` target](https://cc65.github.io/doc/atari.html#ss3.1) | |
| * 3.2 [`atarixl` target](https://cc65.github.io/doc/atari.html#ss3.2) | |
| ## 4. [Linker configurations](https://cc65.github.io/doc/atari.html#s4) | |
| * 4.1 [`atari` config files](https://cc65.github.io/doc/atari.html#ss4.1) | |
| * 4.2 [`atarixl` config files](https://cc65.github.io/doc/atari.html#ss4.2) | |
| ## 5. [Platform specific header files](https://cc65.github.io/doc/atari.html#s5) | |
| * 5.1 [Atari specific functions](https://cc65.github.io/doc/atari.html#ss5.1) | |
| * 5.2 [Hardware access](https://cc65.github.io/doc/atari.html#ss5.2) | |
| * 5.3 [Display lists](https://cc65.github.io/doc/atari.html#ss5.3) | |
| * 5.4 [Character mapping](https://cc65.github.io/doc/atari.html#ss5.4) | |
| * 5.5 [Keyboard codes](https://cc65.github.io/doc/atari.html#ss5.5) | |
| ## 6. [Loadable drivers](https://cc65.github.io/doc/atari.html#s6) | |
| * 6.1 [Graphics drivers](https://cc65.github.io/doc/atari.html#ss6.1) | |
| * 6.2 [Extended memory drivers](https://cc65.github.io/doc/atari.html#ss6.2) | |
| * 6.3 [Joystick drivers](https://cc65.github.io/doc/atari.html#ss6.3) | |
| * 6.4 [Mouse drivers](https://cc65.github.io/doc/atari.html#ss6.4) | |
| * 6.5 [RS232 device drivers](https://cc65.github.io/doc/atari.html#ss6.5) | |
| ## 7. [Limitations](https://cc65.github.io/doc/atari.html#s7) | |
| * 7.1 [`Realtime clock`](https://cc65.github.io/doc/atari.html#ss7.1) | |
| * 7.2 [`atarixl target`](https://cc65.github.io/doc/atari.html#ss7.2) | |
| ## 8. [DIO implementation](https://cc65.github.io/doc/atari.html#s8) | |
| ## 9. [CONIO implementation](https://cc65.github.io/doc/atari.html#s9) | |
| ## 10. [Technical details](https://cc65.github.io/doc/atari.html#s10) | |
| * 10.1 [`atari`](https://cc65.github.io/doc/atari.html#ss10.1) | |
| * 10.2 [`atarixl`](https://cc65.github.io/doc/atari.html#ss10.2) | |
| ## 11. [Other hints](https://cc65.github.io/doc/atari.html#s11) | |
| * 11.1 [Function keys](https://cc65.github.io/doc/atari.html#ss11.1) | |
| * 11.2 [Passing arguments to the program](https://cc65.github.io/doc/atari.html#ss11.2) | |
| * 11.3 [Interrupts](https://cc65.github.io/doc/atari.html#ss11.3) | |
| * 11.4 [Reserving a memory area inside a program](https://cc65.github.io/doc/atari.html#ss11.4) | |
| * 11.5 [Upgrading from an older cc65 version](https://cc65.github.io/doc/atari.html#ss11.5) | |
| * 11.6 [Getting rid of the "system check" load chunk](https://cc65.github.io/doc/atari.html#ss11.6) | |
| ## 12. [License](https://cc65.github.io/doc/atari.html#s12) | |
| --- | |
| ## 1. [Overview](#toc1) | |
| This file contains an overview of the Atari runtime system as it comes | |
| with the cc65 C compiler. It describes the memory layout, Atari specific | |
| header files, available drivers, and any pitfalls specific to that | |
| platform. | |
| The Atari runtime support comes in two flavors: `atari` and `atarixl`. | |
| The `atari` target supports all Atari 8-bit computers, the `atarixl` only | |
| supports XL type or newer machines (excluding the 600XL). | |
| The `atarixl` runtime makes the whole 64K of memory available, with the | |
| exception of the I/O area at $D000 - $D7FF. Since the | |
| `atarixl` runtime has some | |
| [limitations](#xllimitations), it is | |
| recommended to use the `atari` target unless lack of memory dictates the | |
| use of the `atarixl` target. | |
| Please note that Atari specific functions are just mentioned here, they are | |
| described in detail in the separate | |
| [function reference](https://cc65.github.io/doc/funcref.html). Even functions marked as "platform dependent" may be available on | |
| more than one platform. Please see the function reference for more | |
| information. | |
| ## 2. [Binary format](#toc2) | |
| The Atari DOS executable file format supports more than one load block (*chunk*). | |
| The default binary output format generated by the linker for the | |
| Atari target is a machine language program with a standard executable | |
| header (FF FF <load chunk #1> ... <load chunk #n>). | |
| A load chunk has the format [<2 byte start address> <2 bytes end address> | |
| <chunk data>]. | |
| A run vector is added to the end of the | |
| file ($02E0 $02E1 <run vector>) and is calculated using | |
| the `start` label in crt0.s. (Technically the run vector is also a load chunk, | |
| but is not regarded as such here.) | |
| An `atari` program has two load chunks, an `atarixl` program has three load | |
| chunks. The load chunks are defined in the linker configuration files. For more | |
| detailed information about the load chunks see the chapter | |
| [Technical details](#techdetail). For the discussion here it's | |
| sufficient to know that the first load chunk(s) do preparation work and the | |
| main part of the program is in the last load chunk. | |
| The values determining the size of the main part of the program (the second load | |
| chunk for `atari`, the third load chunk for `atarixl`) are calculated in | |
| the crt0.s file from the \_\_STARTUP\_LOAD\_\_ and \_\_BSS\_LOAD\_\_ values. | |
| Be aware of that if you create a custom linker config file and start moving segments around (see section | |
| [Reserving a memory area inside the program](#memhole)). | |
| ## 3. [Memory layout](#toc3) | |
| ## 3.1 [`atari` target](#toc3.1) | |
| The default linker config file assumes that the BASIC ROM is disabled (or | |
| the BASIC cartridge unplugged). This gives a usable memory range of | |
| [$2000-$BC1F]. The library startup code examines the | |
| current memory configuration, which depends on the size of the | |
| installed memory and cartridges. It does so by using the value in | |
| the MEMTOP ($2E5) variable as highest memory address the program | |
| can use. The initial stack pointer, which is the upper bound of | |
| memory used by the program, is set to this value, minus an optionally | |
| defined \_\_RESERVED\_MEMORY\_\_ value. | |
| The default load address of $2000 can be changed by creating a custom | |
| linker config file or by using the "--start-addr" cl65 command line | |
| argument or the "--start-addr" or "-S" ld65 command line arguments. | |
| Please note that the first load chunk (which checks the available memory) | |
| will always be loaded at $2E00, regardless of the specified start | |
| address. This address can only be changed by a custom linker config file. | |
| Special locations: | |
| **Text screen** | |
| : The text screen depends on the installed memory size and cartridges | |
| and can be obtained from the SAVMSC variable ($58). | |
| **Stack** | |
| : The C runtime stack is located at MEMTOP and grows downwards, | |
| regardless of how your linker config file is setup. This | |
| accommodates the different memory configurations of the Atari | |
| machines, as well as having a cartridge installed. You can override | |
| this behaviour by writing your own crt0.s file and linking it to | |
| your program (see also | |
| [Final note](#memhole_final_note)). | |
| **Heap** | |
| : The C heap is located at the end of the program and grows towards the C | |
| runtime stack. | |
| ## 3.2 [`atarixl` target](#toc3.2) | |
| The startup code rearranges the memory as follows: | |
| 1. Screen memory and display list are moved below the program start address. | |
| 2. The ROM is disabled, making the memory in the areas [$C000-$CFFF] | |
| and [$D800-$FFF9] available. | |
| 3. Character generator data is copied from ROM to the CHARGEN location specified in the | |
| linker config file. This is (in the default `atarixl.cfg` file) at the same address as | |
| where it is in ROM ($E000, it can be changed, see | |
| [atarixl chargen location](#chargenloc)). With the character generator at $E000, there are two upper memory | |
| areas available, [$D800-$DFFF] and [$E400-$FFF9]. | |
| With the default load address of $2400 this gives a usable memory range of | |
| [$2400-$CFFF]. | |
| Please note that the first load chunk (which checks the system | |
| compatibility and available memory) will always be loaded at | |
| $2E00, regardless of the specified start address. This address | |
| can only be changed by a custom linker config file. | |
| Special locations: | |
| **Text screen** | |
| : The text screen depends on the selected load address ($2400 | |
| by default), and resides directly before that address, rounded to the next | |
| lower page boundary. | |
| The screen memory's start address can be obtained from the SAVMSC variable | |
| ($58). | |
| **Stack** | |
| : The C runtime stack is located at end of the MAIN memory area ($CFFF) | |
| and grows downwards. | |
| **Heap** | |
| : The C heap is located at the end of the program (end of BSS segment) and | |
| grows towards the C runtime stack. | |
| ## 4. [Linker configurations](#toc4) | |
| The ld65 linker comes with default config files for the Atari. There | |
| are two targets for the Atari, `atari` and `atarixl`. | |
| The default config file for `atari` is selected with | |
| `-t atari`, and the default config file for `atarixl` is selected with | |
| `-t atarixl`. | |
| The Atari package comes with additional secondary linker config files which | |
| can be used via `-t atari -C <configfile>` (for `atari` target) or | |
| `-t atarixl -C <configfile>` (for `atarixl` target). | |
| ## 4.1 [`atari` config files](#toc4.1) | |
| ### default config file (`atari.cfg`) | |
| The default configuration is tailored to C programs. It creates files | |
| which have a default load address of $2000. | |
| The files generated by this config file include the | |
| ["system check"](#syschk) load chunk. It can | |
| optionally be left out, see | |
| [Getting rid of the "system check" load chunk](#nosyschk). | |
| ### `atari-asm.cfg` | |
| This config file aims to give the assembler programmer maximum | |
| flexibility. All program segments (`CODE`, `DATA`, etc.) are | |
| optional. | |
| By default it creates regular DOS executable files, which have a default | |
| load address of $2E00. It's also possible to generate an image of | |
| just the program data without EXE header, load address, or (auto-)start address. | |
| To you so, you have to define the symbols `__AUTOSTART__` and `__EXEHDR__` | |
| when linking the program. Therefore, to generate a "plain" binary file, pass the | |
| options "`-D__AUTOSTART__=1 -D__EXEHDR__=1`" to the linker. | |
| It's also possible to create a non auto-starting program file, by defining | |
| only the `__AUTOSTART__` symbol. Such a program has to be run manually | |
| after being loaded by DOS (for example by using the "M" option of DOS 2.5). | |
| Defining only the `__EXEHDR__` symbol will create a (useless) file which | |
| doesn't conform to the DOS executable file format (like a "plain" binary file) | |
| but still has the "autostart" load chunk appended. | |
| The sections of the file which the defines refer to (`__AUTOSTART__` for | |
| the autostart trailer, `__EXEHDR__` for the EXE header and load address) | |
| is *left out*, keep this in mind. | |
| The values you assign to the two symbols `__AUTOSTART__` and `__EXEHDR__` | |
| don't matter. | |
| ### `atari-asm-xex.cfg` | |
| This config file allows writing multi segment binaries easily, without having to | |
| write the header explicitly on each segment. | |
| It is similar to the `atari-asm.cfg` above, but uses the ATARI (xex) file | |
| format support on LD65 instead of the standard binary output, so it does not | |
| have the `__AUTOSTART` nor the `__EXEHDR__` symbols. | |
| Note that each `MEMORY` area in the configuration file will have it's own | |
| segment in the output file with the correct headers, and you can specify and | |
| init address INITAD) for each memory area. | |
| ### `atari-cart.cfg` | |
| This config file can be used to create 8K or 16K cartridges. It's suited both | |
| for C and assembly language programs. | |
| By default, an 8K cartridge is generated. To create a 16K cartridge, pass the | |
| size of the cartridge to the linker, like "`-D__CARTSIZE__=0x4000`". | |
| The only valid values for `__CARTSIZE__` are 0x2000 and 0x4000. | |
| The option byte of the cartridge can be set with the `__CARTFLAGS__` | |
| value, passed to the linker. The default value is $01, which means | |
| that the cartridge doesn't prevent the booting of DOS. | |
| The option byte will be located at address $BFFD. For more information | |
| about its use, see e.g. "Mapping the Atari". | |
| ### `atari-cassette.cfg` | |
| This config file can be used to create cassette boot files. It's suited both | |
| for C and assembly language programs. | |
| The size of a cassette boot file is restricted to 32K. Larger programs | |
| would need to be split in more parts and the parts to be loaded manually. | |
| To write the generated file to a cassette, a utility (`w2cas.com`) to run | |
| on an Atari is provided in the `util` directory of `atari` target dir. | |
| ### `atari-xex.cfg` | |
| This config file shows how to write a binary using the ATARI (xex) file format | |
| support on LD65, this simplifies the memory areas and allows to add new memory | |
| areas easily without writing new headers and trailers. | |
| Note that the default C library includes the system-check chunk, so in this | |
| linker configuration we suppress the importing of the header and trailer for | |
| this chunk by defining the standard import symbols to a 0 value. For the | |
| initialization address of the system-check chunk, the INITAD is set directly in | |
| the configuration. | |
| ## 4.2 [`atarixl` config files](#toc4.2) | |
| ### default config file (`atarixl.cfg`) | |
| The default configuration is tailored to C programs. It creates files | |
| which have a default load address of $2400. | |
| The files generated by this config file include the | |
| ["system check"](#syschkxl) load chunk. It can | |
| optionally be left out, see | |
| [Getting rid of the "system check" load chunk](#nosyschk). | |
| ### `atarixl-largehimem.cfg` | |
| This is the same as the default config file, but it rearranges the | |
| high memory beneath the ROM into one large block. In order for this | |
| config file to work, the runtime library has to be recompiled with a | |
| special define. See the file `libsrc/atari/Makefile.inc` in the | |
| source distribution. | |
| The files generated by this config file include the | |
| ["system check"](#syschkxl) load chunk. It can | |
| optionally be left out, see | |
| [Getting rid of the "system check" load chunk](#nosyschk). | |
| ### `atarixl-xex.cfg` | |
| Similar to the `atari-xex.cfg` above, this config file shows how to write a | |
| binary using the ATARI (xex) file format support on LD65. | |
| In addition to the suppressing of the system-check headers and trailers, this | |
| also suppresses the shadow-ram-preparation headers and trailers, but does this | |
| by defining an "UNUSED" memory area that is not written to the output file. | |
| ## 5. [Platform specific header files](#toc5) | |
| Programs containing Atari specific code may use the `atari.h` | |
| header file. | |
| This also includes access to operating system locations (e.g. hardware shadow registers) by a structure called | |
| "`OS`". | |
| The names are the usual ones you can find in system reference manuals. Example: | |
| > ```` | |
| > ... | |
| > OS.savmsc = ScreenMemory; | |
| > OS.color4 = 14; // white frame | |
| > if (OS.stick0 != 15 || OS.ch != 255) // key or stick input? | |
| > ... | |
| > | |
| > ```` | |
| Please note that memory location 762/$2FA is called "`char_`" while the original name "`char`" conflicts with the C keyword. | |
| If you like to use the OS names and locations for the original Atari 800 operating system, please "`#define OSA`" before including the | |
| `atari.h` header file. | |
| If you like to target the floating point register model of revision 2 machines, put a "`#define OS_REV2`" before including `atari.h`. | |
| Access to the Basic programming language zero page variables is established by the structure "`BASIC`". | |
| ## 5.1 [Atari specific functions](#toc5.1) | |
| The functions and global variable listed below are special for the Atari. | |
| See the | |
| [function reference](https://cc65.github.io/doc/funcref.html) for declaration and usage. | |
| * get\_ostype | |
| * get\_tv | |
| * \_dos\_type | |
| * \_gtia\_mkcolor | |
| * \_getcolor | |
| * \_getdefdev | |
| * \_graphics | |
| * \_is\_cmdline\_dos | |
| * \_rest\_vecs | |
| * \_save\_vecs | |
| * \_scroll | |
| * \_setcolor | |
| * \_setcolor\_low | |
| * \_sound | |
| * waitvsync | |
| ## 5.2 [Hardware access](#toc5.2) | |
| The following pseudo variables declared in the `atari.h` header | |
| file do allow access to hardware located in the address space. Some | |
| variables are structures, accessing the struct fields will access the | |
| chip registers. | |
| **`GTIA_READ` and `GTIA_WRITE`** | |
| : The `GTIA_READ` structure allows read access to the GTIA. The | |
| `GTIA_WRITE` structure allows write access to the GTIA. | |
| See the `_gtia.h` header file located in the include directory | |
| for the declaration of the structure. | |
| **`POKEY_READ` and `POKEY_WRITE`** | |
| : The `POKEY_READ` structure allows read access to the POKEY. The | |
| `POKEY_WRITE` structure allows write access to the POKEY. | |
| See the `_pokey.h` header file located in the include directory | |
| for the declaration of the structure. | |
| **`ANTIC`** | |
| : The `ANTIC` structure allows read access to the ANTIC. | |
| See the `_antic.h` header file located in the include directory | |
| for the declaration of the structure. | |
| **`PIA`** | |
| : The `PIA` structure allows read access to the PIA 6520. | |
| See the `_pia.h` header file located in the include directory | |
| for the declaration of the structure. | |
| ## 5.3 [Display lists](#toc5.3) | |
| A major feature of the Atari graphics chip "ANTIC" is to | |
| process instructions for the display generation. | |
| cc65 supports constructing these display lists by offering defines | |
| for the instructions. In conjunction with the "void"-variable extension | |
| of cc65, display lists can be created quite comfortable: | |
| > ```` | |
| > ... | |
| > unsigned char ScreenMemory[100]; | |
| > | |
| > void DisplayList = | |
| > { | |
| > DL_BLK8, | |
| > DL_BLK8, | |
| > DL_BLK8, | |
| > DL_LMS(DL_CHR20x8x2), | |
| > ScreenMemory, | |
| > DL_CHR20x8x2, | |
| > DL_CHR20x8x2, | |
| > DL_CHR20x8x2, | |
| > DL_BLK4, | |
| > DL_CHR20x8x2, | |
| > DL_JVB, | |
| > &DisplayList | |
| > }; | |
| > ... | |
| > OS.sdlst = &DisplayList; | |
| > ... | |
| > | |
| > ```` | |
| Please inspect the `_antic.h` header file to determine the supported | |
| instruction names. Modifiers on instructions can be nested without need | |
| for an order: | |
| `DL_LMS(DL_HSCROL(DL_VSCROL(DL_DLI(DL_MAP80x4x2))))` | |
| Please mind that ANTIC has memory alignment requirements for "player | |
| missile graphics"-data, font data, display lists and screen memory. Creation | |
| of a special linker configuration with appropriate aligned segments and | |
| switching to that segment in the c-code is usually necessary. A more memory | |
| hungry solution consists in using the | |
| [posix\_memalign()](https://cc65.github.io/doc/funcref.html#posix_memalign) function in conjunction with copying your data to the | |
| allocated memory. | |
| ## 5.4 [Character mapping](#toc5.4) | |
| The Atari has two representations for characters: | |
| 1. ATASCII is character mapping which is similar to ASCII and used | |
| by the CIO system of the OS. This is the default mapping of cc65 when | |
| producing code for the atari target. | |
| 2. The internal/screen mapping represents the real value of the | |
| screen ram when showing a character. | |
| For direct memory access (simplicity and speed) enabling the internal | |
| mapping can be useful. This can be achieved by including the | |
| "`atari_screen_charmap.h`" header. | |
| A word of caution: Since the `0x00` character has to be mapped in an | |
| incompatible way to the C-standard, the usage of string functions in | |
| conjunction with internal character mapped strings delivers unexpected | |
| results regarding the string length. The end of strings are detected where | |
| you may not expect them (too early or (much) too late). Internal mapped | |
| strings typically support the "`mem...()`" functions. | |
| *For assembler sources the macro "`scrcode`" from the "`atari.mac`" | |
| package delivers the same feature.* | |
| You can switch back to the ATASCII mapping by including | |
| "`atari_atascii_charmap.h`". | |
| Example: | |
| > ```` | |
| > #include <atari_screen_charmap.h> | |
| > char* pcScreenMappingString = "Hello Atari!"; | |
| > | |
| > #include <atari_atascii_charmap.h> | |
| > char* pcAtasciiMappingString = "Hello Atari!"; | |
| > | |
| > ```` | |
| ## 5.5 [Keyboard codes](#toc5.5) | |
| For direct keyboard scanning in conjunction with e.g. the OS location "CH" (764/$2FC), | |
| all keyboard codes are available as defined values on C and assembler side. | |
| Example: | |
| > ```` | |
| > ... | |
| > while (!kbhit()); | |
| > switch (OS.ch) | |
| > { | |
| > case KEY_RETURN: | |
| > ... | |
| > case KEY_SPACE: | |
| > ... | |
| > case KEY_1: | |
| > ... | |
| > } | |
| > ... | |
| > | |
| > ```` | |
| You can find the C defines in the file "`atari.h`" or "`atari.inc`" for the assembler variant. | |
| ## 6. [Loadable drivers](#toc6) | |
| The names in the parentheses denote the symbols to be used for static linking of the drivers. | |
| ## 6.1 [Graphics drivers](#toc6.1) | |
| | | | | | | |
| | --- | --- | --- | --- | | |
| | `atari` | `atarixl` | screen resolution | display pages | | |
| | `atr3.tgi (atr3_tgi)` | `atrx3.tgi (atrx3_tgi)` | 40x24x4 (CIO mode 3, ANTIC mode 8) | 1 | | |
| | `atr4.tgi (atr4_tgi)` | `atrx4.tgi (atrx4_tgi)` | 80x48x2 (CIO mode 4, ANTIC mode 9) | 1 | | |
| | `atr5.tgi (atr5_tgi)` | `atrx5.tgi (atrx5_tgi)` | 80x48x4 (CIO mode 5, ANTIC mode A) | 1 | | |
| | `atr6.tgi (atr6_tgi)` | `atrx6.tgi (atrx6_tgi)` | 160x96x2 (CIO mode 6, ANTIC mode B) | 1 | | |
| | `atr7.tgi (atr7_tgi)` | `atrx7.tgi (atrx7_tgi)` | 160x96x4 (CIO mode 7, ANTIC mode D) | 1 | | |
| | `atr8.tgi (atr8_tgi)` | `atrx8.tgi (atrx8_tgi)` | 320x192x2 (CIO mode 8, ANTIC mode F) | 1 | | |
| | `atr8p2.tgi (atr8p2_tgi)` | `atrx8p2.tgi (atrx8p2_tgi)` | 320x192x2 (CIO mode 8, ANTIC mode F) | 2 | | |
| | `atr9.tgi (atr9_tgi)` | `atrx9.tgi (atrx9_tgi)` | 80x192x16b (CIO mode 9, ANTIC mode F, GTIA mode $40) | 1 | | |
| | `atr9p2.tgi (atr9p2_tgi)` | `atrx9p2.tgi (atrx9p2_tgi)` | 80x192x16b (CIO mode 9, ANTIC mode F, GTIA mode $40) | 2 | | |
| | `atr10.tgi (atr10_tgi)` | `atrx10.tgi (atrx10_tgi)` | 80x192x9 (CIO mode 10, ANTIC mode F, GTIA mode $80) | 1 | | |
| | `atr10p2.tgi (atr10p2_tgi)` | `atrx10p2.tgi (atrx10p2_tgi)` | 80x192x9 (CIO mode 10, ANTIC mode F, GTIA mode $80) | 2 | | |
| | `atr11.tgi (atr11_tgi)` | `atrx11.tgi (atrx11_tgi)` | 80x192x16h (CIO mode 11, ANTIC mode F, GTIA mode $C0) | 1 | | |
| | `atr14.tgi (atr14_tgi)` | `atrx14.tgi (atrx14_tgi)` | 160x192x2 (CIO mode 14, ANTIC mode C) | 1 | | |
| | `atr15.tgi (atr15_tgi)` | `atrx15.tgi (atrx15_tgi)` | 160x192x4 (CIO mode 15, ANTIC mode E) | 1 | | |
| | `atr15p2.tgi (atr15p2_tgi)` | `atrx15p2.tgi (atrx15p2_tgi)` | 160x192x4 (CIO mode 15, ANTIC mode E) | 2 | | |
| Many graphics modes require more memory than the text screen which is | |
| in effect when the program starts up. Therefore the programmer has to | |
| tell the program beforehand the memory requirements of the graphics | |
| modes the program intends to use. | |
| On the `atari` target his can be done by using the \_\_RESERVED\_MEMORY\_\_ | |
| linker config variable. The number specified there describes the number | |
| of bytes to subtract from the top of available memory as seen from the | |
| runtime library. This memory is then used by the screen buffer. | |
| On the `atarixl` target the screen memory resides below the program | |
| load address. In order to reserve memory for a graphics mode, one | |
| simply uses a higher program load address. There are restrictions on | |
| selectable load addresses, | |
| see | |
| [Selecting a good program load address](#loadaddr). | |
| The numbers for the different graphics modes presented below should | |
| only be seen as a rule of thumb. Since the screen buffer memory needs | |
| to start at specific boundaries, the numbers depend on the current top | |
| of available memory. | |
| The following numbers were determined by a BASIC program. | |
| | | | | |
| | --- | --- | | |
| | graphics mode | reserved memory | | |
| | 0 | 1 | | |
| | 1 | 1 | | |
| | 2 | 1 | | |
| | 3 | 1 | | |
| | 4 | 1 | | |
| | 5 | 182 | | |
| | 6 | 1182 | | |
| | 7 | 3198 | | |
| | 8 | 7120 | | |
| | 9 | 7146 | | |
| | 10 | 7146 | | |
| | 11 | 7146 | | |
| | 12 | 162 | | |
| | 13 | 1 | | |
| | 14 | 3278 | | |
| | 15 | 7120 | | |
| | 16 | 1 | | |
| | 17 | 1 | | |
| | 18 | 1 | | |
| | 19 | 1 | | |
| | 20 | 1 | | |
| | 21 | 184 | | |
| | 22 | 1192 | | |
| | 23 | 3208 | | |
| | 24 | 7146 | | |
| | 25 | 7146 | | |
| | 26 | 7146 | | |
| | 27 | 7146 | | |
| | 28 | 162 | | |
| | 29 | 1 | | |
| | 30 | 3304 | | |
| | 31 | 7146 | | |
| reserved memory required for different graphics modes | |
| The values of "1" are needed because the graphics command crashes if | |
| it doesn't have at least one byte available. This seems to be a bug of | |
| the Atari ROM code. | |
| Default drivers: `atr8.tgi (atr8_tgi)` and `atrx8.tgi (atrx8_tgi)`. | |
| ## 6.2 [Extended memory drivers](#toc6.2) | |
| Currently there is only one extended memory driver. It manages the second 64K of a 130XE. | |
| | | | | |
| | --- | --- | | |
| | `atari` | `atarixl` | | |
| | `atr130.emd (atr130_emd)` | `atrx130.emd (atrx130_emd)` | | |
| ## 6.3 [Joystick drivers](#toc6.3) | |
| Currently there are two joystick drivers available: | |
| | | | | | |
| | --- | --- | --- | | |
| | `atari` | `atarixl` | description | | |
| | `atrstd.joy (atrstd_joy)` | `atrxstd.joy (atrxstd_joy)` | Supports up to two/four standard joysticks connected to the joystick ports of the Atari. (Four on the pre-XL systems, two on XL or newer.) | | |
| | `atrmj8.joy (atrmj8_joy)` | `atrxmj8.joy (atrxmj8_joy)` | Supports up to eight standard joysticks connected to a MultiJoy adapter. | | |
| Default drivers: `atrstd.joy (atrstd_joy)` and `atrxstd.joy (atrxstd_joy)`. | |
| ## 6.4 [Mouse drivers](#toc6.4) | |
| Currently there are five mouse drivers available: | |
| | | | | | |
| | --- | --- | --- | | |
| | `atari` | `atarixl` | description | | |
| | `atrjoy.mou (atrjoy_mou)` | `atrxjoy.mou (atrxjoy_mou)` | Supports a mouse emulated by a standard joystick. | | |
| | `atrst.mou (atrst_mou)` | `atrxst.mou (atrxst_mou)` | Supports an Atari ST mouse. | | |
| | `atrami.mou (atrami_mou)` | `atrxami.mou (atrxami_mou)` | Supports an Amiga mouse. | | |
| | `atrtrk.mou (atrtrk_mou)` | `atrxtrk.mou (atrxtrk_mou)` | Supports an Atari trakball. | | |
| | `atrtt.mou (atrtt_mou)` | `atrxtt.mou (atrxtt_mou)` | Supports an Atari touch tablet. | | |
| All mouse devices connect to joystick port #0. | |
| Default drivers: `atrst.mou (atrst_mou)` and `atrxst.mou (atrxst_mou)`. | |
| ### Mouse callbacks | |
| There are two mouse callbacks available. | |
| The "text mode" callbacks (`mouse_txt_callbacks`) display the mouse cursor as a "diamond" character | |
| on the standard "GRAPHICS 0" text mode screen. The mouse cursor character can be changed by an | |
| assembly file defining the character by exporting the zeropage symbol `mouse_txt_char`. | |
| The default file looks like this: | |
| > ```` | |
| > .export mouse_txt_char : zp = 96 ; 'diamond' screen code | |
| > | |
| > ```` | |
| The "P/M" callbacks (`mouse_pm_callbacks`) use Player-Missile graphics for the mouse cursor. | |
| The cursor shape can be changed, too, by an assembly file. Here's the default shape definition: | |
| > ```` | |
| > .export mouse_pm_bits | |
| > .export mouse_pm_height : zeropage | |
| > .export mouse_pm_hotspot_x : zeropage | |
| > .export mouse_pm_hotspot_y : zeropage | |
| > .rodata | |
| > mouse_pm_bits: | |
| > .byte %11110000 | |
| > .byte %11000000 | |
| > .byte %10100000 | |
| > .byte %10010000 | |
| > .byte %10001000 | |
| > .byte %00000100 | |
| > .byte %00000010 | |
| > mouse_pm_height = * - mouse_pm_bits | |
| > ; hot spot is upper left corner | |
| > mouse_pm_hotspot_x = 0 | |
| > mouse_pm_hotspot_y = 0 | |
| > | |
| > ```` | |
| `mouse_pm_bits` defines the shape of the cursor, `mouse_pm_height` defines the number of | |
| bytes in `mouse_pm_bits`. `mouse_pm_hotspot_x` and `mouse_pm_hotspot_y` define the | |
| position in the shape where "the mouse points to". When using this callback page #6 ($600 | |
| - $6FF) is used for the P/M graphics data and no P/M graphics can otherwise be used | |
| by the program. The height of the shape (`mouse_pm_height`) | |
| must not exceed 32 lines since the callback routines cannot handle more than 32 lines. | |
| The default callbacks definition (`mouse_def_callbacks`) is an alias for the "P/M" callbacks. | |
| ## 6.5 [RS232 device drivers](#toc6.5) | |
| Currently there is one RS232 driver. It supports up to 9600 baud, requires hardware flow control | |
| (RTS/CTS) and uses the R: device (therefore an R: driver needs to be installed). It was tested | |
| with the 850 interface module. | |
| | | | | |
| | --- | --- | | |
| | `atari` | `atarixl` | | |
| | `atrrdev.ser (atrrdev_ser)` | `atrxrdev.ser (atrxrdev_ser)` | | |
| ## 7. [Limitations](#toc7) | |
| ## 7.1 [`Realtime clock`](#toc7.1) | |
| Access to the realtime clock is supported only when running on SpartaDOS-X. | |
| There needs to be a realtime clock driver installed. This is normally the case | |
| in the default installation (CONFIG.SYS) of SpartaDOS-X. | |
| A missing realtime clock driver in SpartaDOS-X is not supported, and the program | |
| may crash when calling the `clock_settime()` or `clock_gettime()` | |
| functions. | |
| The resolution of the realtime clock driver is 1 second. | |
| ## 7.2 [`atarixl target`](#toc7.2) | |
| * The display is cleared at program start and at program termination. This is a side | |
| effect of relocating the display memory below the program start address. | |
| * Not all possible CIO and SIO functions are handled by the runtime stub code which banks | |
| the ROM in and out. All functions used by the runtime library are handled, though. | |
| * The `_sys()` function is not supported. | |
| * It is not compatible with DOSes or other programs using the memory below the ROM. | |
| ## 8. [DIO implementation](#toc8) | |
| The Atari supports disk drives with either 128 or 256 byte sectors. | |
| The first three sectors of any disk are always 128 bytes long though. This is | |
| because the system can only boot from 128 bytes sectors. | |
| Therefore the DIO read and write functions transfer only 128 bytes | |
| for sectors 1 to 3, regardless of the type of diskette. | |
| ## 9. [CONIO implementation](#toc9) | |
| The console I/O is speed optimized therefore support for XEP80 hardware | |
| or f80.com software is missing. Of course you may use stdio.h functions. | |
| `cprintf` targets a 40 character line. On a 20-column display this has | |
| the unexpected effect of a blank line after your text. On such displays you can either | |
| use for example `gotoxy(20,0)` to target the "next" line, or you can switch to `write()` | |
| function which does not have this side effect. | |
| ## 10. [Technical details](#toc10) | |
| ## 10.1 [`atari`](#toc10.1) | |
| ### Load chunks | |
| An `atari` program contains two load chunks. | |
| 1. "system check" | |
| This load chunk is always loaded at address $2E00, and checks if the system has | |
| enough memory to run the program. It also checks if the program start address is not | |
| below MEMLO. If any of the checks return false, the loading of the program is aborted. | |
| The contents of this chunk come from the SYSCHKCHNK memory area of the linker config file. | |
| 2. main program | |
| This load chunk is loaded at the selected program start address (default $2000) and | |
| contains all of the code and data of the program. | |
| The contents of this chunk come from the MAIN memory area of the linker config file. | |
| ## 10.2 [`atarixl`](#toc10.2) | |
| ### General operation | |
| The `atarixl` target banks out the ROM while the program is running in | |
| order to make more memory available to the program. | |
| The screen memory is by default located at the top of available memory, | |
| $BFFF if BASIC is not enabled, $9FFF if BASIC is enabled. | |
| Therefore, in order to create a largest possible continuous memory area, | |
| the screen memory is moved below the program load address. This gives | |
| a memory area from <program load addr> to $CFFF. | |
| The startup code installs wrappers for interrupt handlers and ROM routines. | |
| When an interrupt or call to a ROM routine happens, the wrappers enable the | |
| ROM, call the handler or routine, and disable the ROM again. | |
| The "wrapping" of the ROM routines is done by changing the ROM entry | |
| point symbols in `atari.inc` to point to the wrapper functions. | |
| For ROM functions which require input or output buffers, the wrappers | |
| copy the data as required to buffers in low memory. | |
| ### Load chunks | |
| An `atarixl` program contains three load chunks. | |
| 1. "system check" | |
| This load chunk is always loaded at address $2E00, and checks if the system is | |
| suitable for running the program. It also checks if there is enough room between MEMLO | |
| and the program start address to move the text mode screen buffer there. If any of the | |
| checks return false, the loading of the program is aborted. | |
| The contents of this chunk come from the SYSCHKCHNK memory area of the linker config file. | |
| 2. "shadow RAM prepare" | |
| The second load chunk gets loaded to the selected program load address (default $2400). | |
| It moves the screen memory below the program load address, copies the character generator | |
| from ROM to its new place in RAM, and copies the parts of the program which reside in | |
| high memory below the ROM to their place. The high memory parts are included in this load chunk. | |
| At the beginning of this load chunk there is a .bss area, which is not part of the | |
| EXE file. Therefore the on-disk start address of this load chunk will be higher than the | |
| selected start address. This .bss area (segment LOWBSS) contains the buffers for the | |
| double buffering of ROM input and output data. If you add contents to this segment be aware | |
| that the contents won't be zero initialized by the startup code. | |
| The contents of this chunk come from the SRPREPCHNK memory area of the linker config file. | |
| 3. main program | |
| This load chunk is loaded just above the LOWBSS segment, replacing the code of | |
| the previous load chunk. It contains all remaining code and data sections of the program, | |
| including the startup code. | |
| The contents of this chunk come from the RAM memory area of the linker config file. | |
| ### Moving screen memory below the program start address | |
| When setting a graphics mode, the ROM looks at the RAMTOP location. RAMTOP | |
| describes the amount of installed memory in pages (RAMTOP is only one byte). | |
| The screen memory and display list are placed immediately below RAMTOP. | |
| Now in order to relocate the screen memory to lower memory, the startup code | |
| puts a value into RAMTOP which causes the ROM routines to allocate the display | |
| memory below the program start address and then it issues a ROM call to setup | |
| the regular text mode. | |
| ### Selecting a good program load address | |
| Due to the movement of the screen memory below the program start, there are some | |
| load addresses which are sub-optimal because they waste memory or prevent a | |
| higher resolution graphics mode from being enabled. | |
| There are restrictions at which addresses screen memory (display buffer and display | |
| list) can be placed. The display buffer cannot cross a 4K boundary and a display | |
| list cannot cross a 1K boundary. | |
| The startup code takes this into account when moving the screen memory down. | |
| If the program start address (aligned to the next lower page boundary) minus | |
| the screen buffer size would result in a screen buffer which spans a 4K | |
| boundary, the startup code lowers RAMTOP to this 4K boundary. | |
| The size of the screen buffer in text mode is 960 ($3C0) bytes. So, for | |
| example, a selected start address of $2300 would span the 4K boundary | |
| at $2000. The startup code would adjust the RAMTOP value in such way that | |
| the screen memory would be located just below this boundary (at $1C40). | |
| This results in the area [$2000-$22FF] being wasted. | |
| Additionally, the program might fail to load since the lowest address used | |
| by the screen memory could be below MEMLO. (The lowest address used in this | |
| example would be at $1C20, where the display list would allocated.) | |
| These calculations are performed by the startup code (in the first two | |
| load chunks), but the startup code only takes the default 40x24 text mode | |
| into account. If the program later wants to load TGI drivers which set | |
| a more memory consuming graphics mode, the user has to pick a higher | |
| load address. | |
| Using higher resolution modes there is a restriction in the ROM that it | |
| doesn't expect RAMTOP to be at arbitrary values. The Atari memory modules | |
| came only in 8K or 16K sizes, so the ROM expects RAMTOP to only have | |
| values in 8K steps. Therefore, when using the highest resolution modes | |
| the program start address must be at an 8K boundary. | |
| ### Character generator location | |
| The default `atarixl` linker config file (`atarixl.cfg`) leaves the | |
| character generator location at the same address where it is in ROM | |
| ($E000). This has the disadvatage to split the upper memory into | |
| two parts ([$D800-$DFFF] and | |
| [$E400-$FFF9]). For applications which | |
| require a large continuous upper memory area, an alternative linker | |
| config file (`atarixl-largehimem.cfg`) is provided. It relocates the | |
| character generator to $D800, providing a single big upper | |
| memory area at [$DC00-$FFF9]. | |
| With the character generator at a different address than in ROM, the routines | |
| which enable and disable the ROM also have to update the chargen pointer. | |
| This code is not enabled by default. In order to enable it, | |
| uncomment the line which sets CHARGEN\_RELOC in `libsrc/atari/Makefile.inc` | |
| and recompile the `atarixl` runtime library. | |
| ## 11. [Other hints](#toc11) | |
| ## 11.1 [Function keys](#toc11.1) | |
| Function keys are mapped to Atari + number key. | |
| ## 11.2 [Passing arguments to the program](#toc11.2) | |
| Command line arguments can be passed to `main()` when the used DOS supports it. | |
| 1. Arguments are separated by spaces. | |
| 2. Leading and trailing spaces around an argument are ignored. | |
| 3. The first argument passed to `main` is the program name. | |
| 4. A maximum number of 16 arguments (including the program name) are | |
| supported. | |
| ## 11.3 [Interrupts](#toc11.3) | |
| The runtime for the Atari uses routines marked as `.INTERRUPTOR` for | |
| interrupt handlers. Such routines must be written as simple machine language | |
| subroutines and will be called automatically by the VBI handler code | |
| when they are linked into a program. See the discussion of the `.CONDES` | |
| feature in the | |
| [assembler manual](https://cc65.github.io/doc/ca65.html). | |
| Please note that on the Atari targets the `.INTERRUPTOR`s are being | |
| run in NMI context. The other targets run them in IRQ context. | |
| ## 11.4 [Reserving a memory area inside a program](#toc11.4) | |
| (This section is primarily applicable to the `atari` target, but the | |
| principles apply to `atatixl` as well.) | |
| The Atari 130XE maps its additional memory into CPU memory in 16K | |
| chunks at address $4000 to $7FFF. One might want to | |
| prevent this memory area from being used by cc65. Other reasons to | |
| prevent the use of some memory area could be to reserve space for the | |
| buffers for display lists and screen memory. | |
| The Atari executable format allows holes inside a program, e.g. one | |
| part loads into $2E00 to $3FFF, going below the reserved | |
| memory area (assuming a reserved area from $4000 to | |
| $7FFF), and another part loads into $8000 to | |
| $BC1F. | |
| Each load chunk of the executable starts with a 4 byte header which | |
| defines its load address and size. In the following linker config files | |
| these headers are named HEADER and SECHDR (for the MEMORY layout), and | |
| accordingly NEXEHDR and CHKHDR (for the SEGMENTS layout). | |
| ### Low code and high data example | |
| Goal: Create an executable with 2 load chunks which doesn't use the | |
| memory area from $4000 to $7FFF. The CODE segment of | |
| the program should go below $4000 and the DATA and RODATA | |
| segments should go above $7FFF. | |
| The main problem is that the EXE header generated by the cc65 runtime | |
| lib is wrong. It defines a single load chunk with the sizes/addresses | |
| of the STARTUP, LOWCODE, ONCE, CODE, RODATA, and DATA segments, in | |
| fact, the whole user program (we're disregarding the "system check" | |
| load chunk here). | |
| The contents of the EXE header come from the EXEHDR and MAINHDR segments. | |
| The EXEHDR segment just contains the $FFFF value which is required | |
| to be the first bytes of the EXE file. | |
| The MAINHDR are defined in in crt0.s. This cannot be changed without | |
| modifying and recompiling the cc65 atari runtime library. Therefore | |
| the original contents of this segment must be discarded and be | |
| replaced by a user created one. This discarding is done by assigning the | |
| MAINHDR segment to the (new introduced) DISCARD memory area. The DISCARD memory area is | |
| thrown away in the new linker config file (written to file ""). | |
| We add a new FSTHDR segment for the chunk header of the first chunk. | |
| The user needs to create a customized linker config file which adds | |
| new memory areas and segments to hold the new header data for the first load | |
| chunk and the header data for the second load chunk. Also an assembly source file | |
| needs to be created which defines the contents of the new header data | |
| for the two load chunks. | |
| This is an example of a modified cc65 Atari linker configuration file | |
| (split.cfg): | |
| > ```` | |
| > SYMBOLS { | |
| > __STACKSIZE__: value = $800 type = weak; # 2K stack | |
| > __RESERVED_MEMORY__: value = $0000, type = weak; | |
| > } | |
| > FEATURES { | |
| > STARTADDRESS: default = $2E00; | |
| > } | |
| > MEMORY { | |
| > ZP: start = $82, size = $7E, type = rw, define = yes; | |
| > | |
| > HEADER: start = $0000, size = $2, file = %O; # first load chunk | |
| > | |
| > FSTHDR: start = $0000, size = $4, file = %O; # second load chunk | |
| > RAMLO: start = %S, size = $4000 - %S, file = %O; | |
| > | |
| > DISCARD: start = $4000, size = $4000, file = ""; | |
| > | |
| > SECHDR: start = $0000, size = $4, file = %O; # second load chunk | |
| > RAM: start = $8000, size = $3C20, file = %O; # $3C20: matches upper bound $BC1F | |
| > } | |
| > SEGMENTS { | |
| > EXEHDR: load = HEADER, type = ro; | |
| > | |
| > MAINHDR: load = DISCARD, type = ro; | |
| > | |
| > NEXEHDR: load = FSTHDR, type = ro; # first load chunk | |
| > STARTUP: load = RAMLO, type = ro, define = yes; | |
| > LOWCODE: load = RAMLO, type = ro, define = yes, optional = yes; | |
| > ONCE: load = RAMLO, type = ro, optional = yes; | |
| > CODE: load = RAMLO, type = ro, define = yes; | |
| > | |
| > CHKHDR: load = SECHDR, type = ro; # second load chunk | |
| > RODATA: load = RAM, type = ro, define = yes; | |
| > DATA: load = RAM, type = rw, define = yes; | |
| > BSS: load = RAM, type = bss, define = yes; | |
| > | |
| > ZEROPAGE: load = ZP, type = zp; | |
| > AUTOSTRT: load = RAM, type = ro; # defines program entry point | |
| > } | |
| > FEATURES { | |
| > CONDES: segment = ONCE, | |
| > type = constructor, | |
| > label = __CONSTRUCTOR_TABLE__, | |
| > count = __CONSTRUCTOR_COUNT__; | |
| > CONDES: segment = RODATA, | |
| > type = destructor, | |
| > label = __DESTRUCTOR_TABLE__, | |
| > count = __DESTRUCTOR_COUNT__; | |
| > } | |
| > | |
| > ```` | |
| A new memory area DISCARD was added. | |
| It gets loaded with the contents of the (now unused) MAINHDR segment. But the | |
| memory area isn't written to the output file. This way the contents of | |
| the MAINHDR segment get discarded. | |
| The newly added NEXEHDR segment defines the correct chunk header for the | |
| first intended load chunk. It | |
| puts the STARTUP, LOWCODE, ONCE, and CODE segments, which are the | |
| segments containing only code, into load chunk #1 (RAMLO memory area). | |
| The header for the second load chunk comes from the new CHKHDR | |
| segment. It puts the RODATA, DATA, BSS, and ZPSAVE segments into load | |
| chunk #2 (RAM memory area). | |
| The contents of the new NEXEHDR and CHKHDR segments come from this | |
| file (split.s): | |
| > ```` | |
| > .import __CODE_LOAD__, __BSS_LOAD__, __CODE_SIZE__ | |
| > .import __DATA_LOAD__, __RODATA_LOAD__, __STARTUP_LOAD__ | |
| > | |
| > .segment "NEXEHDR" | |
| > .word __STARTUP_LOAD__ | |
| > .word __CODE_LOAD__ + __CODE_SIZE__ - 1 | |
| > | |
| > .segment "CHKHDR" | |
| > .word __RODATA_LOAD__ | |
| > .word __BSS_LOAD__ - 1 | |
| > | |
| > ```` | |
| Compile with | |
| > ```` | |
| > cl65 -t atari -C split.cfg -o prog.com prog.c split.s | |
| > | |
| > ```` | |
| ### Low data and high code example | |
| Goal: Put RODATA and DATA into low memory and STARTUP, LOWCODE, ONCE, | |
| CODE, BSS, ZPSAVE into high memory (split2.cfg): | |
| > ```` | |
| > SYMBOLS { | |
| > __STACKSIZE__: value = $800 type = weak; # 2K stack | |
| > __RESERVED_MEMORY__: value = $0000, type = weak; | |
| > } | |
| > FEATURES { | |
| > STARTADDRESS: default = $2E00; | |
| > } | |
| > MEMORY { | |
| > ZP: start = $82, size = $7E, type = rw, define = yes; | |
| > | |
| > HEADER: start = $0000, size = $2, file = %O; # first load chunk | |
| > | |
| > FSTHDR: start = $0000, size = $4, file = %O; # second load chunk | |
| > RAMLO: start = %S, size = $4000 - %S, file = %O; | |
| > | |
| > DISCARD: start = $4000, size = $4000, file = ""; | |
| > | |
| > SECHDR: start = $0000, size = $4, file = %O; # second load chunk | |
| > RAM: start = $8000, size = $3C20, file = %O; # $3C20: matches upper bound $BC1F | |
| > } | |
| > SEGMENTS { | |
| > EXEHDR: load = HEADER, type = ro; # discarded old EXE header | |
| > | |
| > MAINHDR: load = DISCARD, type = ro; | |
| > | |
| > NEXEHDR: load = FSTHDR, type = ro; # first load chunk | |
| > RODATA: load = RAMLO, type = ro, define = yes; | |
| > DATA: load = RAMLO, type = rw, define = yes; | |
| > | |
| > CHKHDR: load = SECHDR, type = ro; # second load chunk | |
| > STARTUP: load = RAM, type = ro, define = yes; | |
| > ONCE: load = RAM, type = ro, optional = yes; | |
| > CODE: load = RAM, type = ro, define = yes; | |
| > BSS: load = RAM, type = bss, define = yes; | |
| > | |
| > ZEROPAGE: load = ZP, type = zp; | |
| > AUTOSTRT: load = RAM, type = ro; # defines program entry point | |
| > } | |
| > FEATURES { | |
| > CONDES: segment = ONCE, | |
| > type = constructor, | |
| > label = __CONSTRUCTOR_TABLE__, | |
| > count = __CONSTRUCTOR_COUNT__; | |
| > CONDES: segment = RODATA, | |
| > type = destructor, | |
| > label = __DESTRUCTOR_TABLE__, | |
| > count = __DESTRUCTOR_COUNT__; | |
| > } | |
| > | |
| > ```` | |
| New contents for NEXEHDR and CHKHDR are needed (split2.s): | |
| > ```` | |
| > .import __STARTUP_LOAD__, __BSS_LOAD__, __DATA_SIZE__ | |
| > .import __DATA_LOAD__, __RODATA_LOAD__ | |
| > | |
| > .segment "NEXEHDR" | |
| > .word __RODATA_LOAD__ | |
| > .word __DATA_LOAD__ + __DATA_SIZE__ - 1 | |
| > | |
| > .segment "CHKHDR" | |
| > .word __STARTUP_LOAD__ | |
| > .word __BSS_LOAD__ - 1 | |
| > | |
| > ```` | |
| Compile with | |
| > ```` | |
| > cl65 -t atari -C split2.cfg -o prog.com prog.c split2.s | |
| > | |
| > ```` | |
| ### Final note | |
| There are two other memory areas which don't appear directly in the | |
| linker config file. They are the stack and the heap. | |
| The cc65 runtime lib places the stack location at the end of available | |
| memory. This is dynamically set from the MEMTOP system variable at | |
| startup. The heap is located in the area between the end of the BSS | |
| segment and the top of the stack as defined by \_\_STACKSIZE\_\_. | |
| If BSS and/or the stack shouldn't stay at the end of the program, | |
| some parts of the cc65 runtime lib need to be replaced/modified. | |
| common/\_heap.s defines the location of the heap and atari/crt0.s | |
| defines the location of the stack by initializing c\_sp. | |
| ## 11.5 [Upgrading from an older cc65 version](#toc11.5) | |
| If you are using a customized linker config file you might get some errors | |
| regarding the MAINHDR segment. Like this: | |
| > ```` | |
| > ld65: Error: Missing memory area assignment for segment 'MAINHDR' | |
| > | |
| > ```` | |
| The old "HEADER" memory description contained six bytes: $FFFF | |
| and the first and last memory address of the program. For the "system | |
| check" load chunk this had to be split into two memory assignments The | |
| "HEADER" now only contains the $FFFF. The main program's first | |
| and last memory address were moved to a new segment, called "MAINHDR", | |
| which in the new linker config file goes into its own memory area (also | |
| called "MAINHDR"). | |
| A simple way to adapt your old linker config file is to add the | |
| following line to the "SEGMENTS" section: | |
| > ```` | |
| > MAINHDR: load = HEADER, type = ro; | |
| > | |
| > ```` | |
| ## 11.6 [Getting rid of the "system check" load chunk](#toc11.6) | |
| If, for some reason, you don't want to include the "system check" load | |
| chunk, you can do so by defining the symbol `__SYSTEM_CHECK__` when linking the | |
| program. The "system check" chunk doesn't include vital parts of the | |
| program. So if you don't want the system checks, it is save to leave them out. | |
| This is probably mostly interesting for debugging. | |
| When using cl65, you can leave it out with this command line: | |
| > ```` | |
| > cl65 -Wl -D__SYSTEM_CHECK__=1 <arguments> | |
| > | |
| > ```` | |
| The value you assign to `__SYSTEM_CHECK__` doesn't matter. If the | |
| `__SYSTEM_CHECK__` symbol is defined, the load chunk won't be included. | |
| ## 12. [License](#toc12) | |
| This software is provided 'as-is', without any expressed or implied | |
| warranty. In no event will the authors be held liable for any damages | |
| arising from the use of this software. | |
| Permission is granted to anyone to use this software for any purpose, | |
| including commercial applications, and to alter it and redistribute it | |
| freely, subject to the following restrictions: | |
| 1. The origin of this software must not be misrepresented; you must not | |
| claim that you wrote the original software. If you use this software | |
| in a product, an acknowledgment in the product documentation would be | |
| appreciated but is not required. | |
| 2. Altered source versions must be plainly marked as such, and must not | |
| be misrepresented as being the original software. | |
| 3. This notice may not be removed or altered from any source | |
| distribution. | |
| ███╗ ███╗███████╗███╗ ███╗ ██████╗ ██████╗ ██╗ ██╗ ███╗ ███╗ █████╗ ██████╗ | |
| ████╗ ████║██╔════╝████╗ ████║██╔═══██╗██╔══██╗╚██╗ ██╔╝ ████╗ ████║██╔══██╗██╔══██╗ | |
| ██╔████╔██║█████╗ ██╔████╔██║██║ ██║██████╔╝ ╚████╔╝ ██╔████╔██║███████║██████╔╝ | |
| ██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══██╗ ╚██╔╝ ██║╚██╔╝██║██╔══██║██╔═══╝ | |
| ██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝██║ ██║ ██║ ██║ ╚═╝ ██║██║ ██║██║ | |
| ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ | |
| # Mapping The Atari-Memory Map | |
| ## CASINI | |
| [CASINI](https://www.atariarchives.org/mapping/memorymap.php#2,3) | |
| ## DOSVEC | |
| [DOSVEC](https://www.atariarchives.org/mapping/memorymap.php#10,11) | |
| ## RAMTOP | |
| [RAMTOP](https://www.atariarchives.org/mapping/memorymap.php#106) | |
| ## RAMLO | |
| [RAMLO](https://www.atariarchives.org/mapping/memorymap.php#4,5) | |
| ## TRAMSZ | |
| [TRAMSZ](https://www.atariarchives.org/mapping/memorymap.php#6) | |
| ## TSTDAT | |
| [TSTDAT](https://www.atariarchives.org/mapping/memorymap.php#7) | |
| ## DOS | |
| [DOS](https://www.atariarchives.org/mapping/memorymap.php#5440) | |
| ## WARMST | |
| [WARMST](https://www.atariarchives.org/mapping/memorymap.php#8) | |
| ## DOSINI | |
| [DOSINI](https://www.atariarchives.org/mapping/memorymap.php#12,13) | |
| ## APPMHI | |
| [APPMHI](https://www.atariarchives.org/mapping/memorymap.php#14,15) | |
| ## MEMTOP | |
| [MEMTOP](https://www.atariarchives.org/mapping/memorymap.php#741,742) | |
| ## PMBASE | |
| [PMBASE](https://www.atariarchives.org/mapping/memorymap.php#54279) | |
| ## CHBASE | |
| [CHBASE](https://www.atariarchives.org/mapping/memorymap.php#54281) | |
| ## STATUS | |
| [STATUS](https://www.atariarchives.org/mapping/memorymap.php#48) | |
| ## DSTAT | |
| [DSTAT](https://www.atariarchives.org/mapping/memorymap.php#76) | |
| ## BRKKEY | |
| [BRKKEY](https://www.atariarchives.org/mapping/memorymap.php#17) | |
| ## BUFADR | |
| [BUFADR](https://www.atariarchives.org/mapping/memorymap.php#21,22) | |
| ## DBUF | |
| [DBUF](https://www.atariarchives.org/mapping/memorymap.php#7668) | |
| ## MEMLO | |
| [MEMLO](https://www.atariarchives.org/mapping/memorymap.php#743,744) | |
| ## PBUFSZ | |
| [PBUFSZ](https://www.atariarchives.org/mapping/memorymap.php#30) | |
| ## PBPNT | |
| [PBPNT](https://www.atariarchives.org/mapping/memorymap.php#29) | |
| ## ICAX2Z | |
| [ICAX2Z](https://www.atariarchives.org/mapping/memorymap.php#43) | |
| ## CRITIC | |
| [CRITIC](https://www.atariarchives.org/mapping/memorymap.php#66) | |
| ## CKEY | |
| [CKEY](https://www.atariarchives.org/mapping/memorymap.php#74) | |
| ## ATRACT | |
| [ATRACT](https://www.atariarchives.org/mapping/memorymap.php#77) | |
| ## COLRSH | |
| [COLRSH](https://www.atariarchives.org/mapping/memorymap.php#79) | |
| ## LMARGN | |
| [LMARGN](https://www.atariarchives.org/mapping/memorymap.php#82) | |
| ## LINE | |
| [LINE](https://www.atariarchives.org/mapping/memorymap.php#7588) | |
| ## ROWCRS | |
| [ROWCRS](https://www.atariarchives.org/mapping/memorymap.php#84) | |
| ## COLCRS | |
| [COLCRS](https://www.atariarchives.org/mapping/memorymap.php#85,86) | |
| ## DRAW | |
| [DRAW](https://www.atariarchives.org/mapping/memorymap.php#64764) | |
| ## DINDEX | |
| [DINDEX](https://www.atariarchives.org/mapping/memorymap.php#87) | |
| ## MODE75.BAS | |
| [MODE75.BAS](https://www.atariarchives.org/mapping/software/MODE75.BAS) | |
| ## SAVEMSC1.BAS | |
| [SAVEMSC1.BAS](https://www.atariarchives.org/mapping/software/SAVEMSC1.BAS) | |
| ## SAVMSC | |
| [SAVMSC](https://www.atariarchives.org/mapping/memorymap.php#88,89) | |
| ## SAVEMSC2.BAS | |
| [SAVEMSC2.BAS](https://www.atariarchives.org/mapping/software/SAVEMSC2.BAS) | |
| ## SAVEMSC3.BAS | |
| [SAVEMSC3.BAS](https://www.atariarchives.org/mapping/software/SAVEMSC3.BAS) | |
| ## SAVEMSC4.BAS | |
| [SAVEMSC4.BAS](https://www.atariarchives.org/mapping/software/SAVEMSC4.BAS) | |
| ## SAVEMSC5.BAS | |
| [SAVEMSC5.BAS](https://www.atariarchives.org/mapping/software/SAVEMSC5.BAS) | |
| ## NEWROW | |
| [NEWROW](https://www.atariarchives.org/mapping/memorymap.php#96) | |
| ## NEWCOL | |
| [NEWCOL](https://www.atariarchives.org/mapping/memorymap.php#97,98) | |
| ## LOGCOL | |
| [LOGCOL](https://www.atariarchives.org/mapping/memorymap.php#99) | |
| ## DMASK | |
| [DMASK](https://www.atariarchives.org/mapping/memorymap.php#672) | |
| ## ADRESS | |
| [ADRESS](https://www.atariarchives.org/mapping/memorymap.php#100,101) | |
| ## BUFCNT | |
| [BUFCNT](https://www.atariarchives.org/mapping/memorymap.php#107) | |
| ## ROWAC | |
| [ROWAC](https://www.atariarchives.org/mapping/memorymap.php#112,113) | |
| ## COLAC | |
| [COLAC](https://www.atariarchives.org/mapping/memorymap.php#114,115) | |
| ## DELTAR | |
| [DELTAR](https://www.atariarchives.org/mapping/memorymap.php#118) | |
| ## DELTAC | |
| [DELTAC](https://www.atariarchives.org/mapping/memorymap.php#119,120) | |
| ## ROWINC | |
| [ROWINC](https://www.atariarchives.org/mapping/memorymap.php#121) | |
| ## COLINC | |
| [COLINC](https://www.atariarchives.org/mapping/memorymap.php#122) | |
| ## SWPFLG | |
| [SWPFLG](https://www.atariarchives.org/mapping/memorymap.php#123) | |
| ## LOMEM | |
| [LOMEM](https://www.atariarchives.org/mapping/memorymap.php#128,129) | |
| ## STARP | |
| [STARP](https://www.atariarchives.org/mapping/memorymap.php#140,141) | |
| ## RUNSTK | |
| [RUNSTK](https://www.atariarchives.org/mapping/memorymap.php#142,143) | |
| ## VNTP.BAS | |
| [VNTP.BAS](https://www.atariarchives.org/mapping/software/VNTP.BAS) | |
| ## VNTD1.BAS | |
| [VNTD1.BAS](https://www.atariarchives.org/mapping/software/VNTD1.BAS) | |
| ## VNTD2.BAS | |
| [VNTD2.BAS](https://www.atariarchives.org/mapping/software/VNTD2.BAS) | |
| ## VVTP | |
| [VVTP](https://www.atariarchives.org/mapping/memorymap.php#134,135) | |
| ## STMTAB | |
| [STMTAB](https://www.atariarchives.org/mapping/memorymap.php#136,137) | |
| ## FRE | |
| [FRE](https://www.atariarchives.org/mapping/memorymap.php#218-223) | |
| ## TABMAP | |
| [TABMAP](https://www.atariarchives.org/mapping/memorymap.php#675-689) | |
| ## PTABW | |
| [PTABW](https://www.atariarchives.org/mapping/memorymap.php#201) | |
| ## FR0 | |
| [FR0](https://www.atariarchives.org/mapping/memorymap.php#212-217) | |
| ## INBUFF | |
| [INBUFF](https://www.atariarchives.org/mapping/memorymap.php#243,244) | |
| ## RADFLG | |
| [RADFLG](https://www.atariarchives.org/mapping/memorymap.php#251) | |
| ## IRQEN | |
| [IRQEN](https://www.atariarchives.org/mapping/memorymap.php#53774) | |
| ## VDSLST | |
| [VDSLST](https://www.atariarchives.org/mapping/memorymap.php#512,513) | |
| ## WSYNC | |
| [WSYNC](https://www.atariarchives.org/mapping/memorymap.php#54282) | |
| ## VDSLST.BAS | |
| [VDSLST.BAS](https://www.atariarchives.org/mapping/software/VDSLST.BAS) | |
| ## VKEYBD | |
| [VKEYBD](https://www.atariarchives.org/mapping/memorymap.php#520,521) | |
| ## VSEROR | |
| [VSEROR](https://www.atariarchives.org/mapping/memorymap.php#524,525) | |
| ## VSERIN | |
| [VSERIN](https://www.atariarchives.org/mapping/memorymap.php#522,523) | |
| ## AUDF1 | |
| [AUDF1](https://www.atariarchives.org/mapping/memorymap.php#53760) | |
| ## STIMER | |
| [STIMER](https://www.atariarchives.org/mapping/memorymap.php#53769) | |
| ## AUDF2 | |
| [AUDF2](https://www.atariarchives.org/mapping/memorymap.php#53762) | |
| ## AUDF4 | |
| [AUDF4](https://www.atariarchives.org/mapping/memorymap.php#53766) | |
| ## VPRCED | |
| [VPRCED](https://www.atariarchives.org/mapping/memorymap.php#514,515) | |
| ## VINTER | |
| [VINTER](https://www.atariarchives.org/mapping/memorymap.php#516,517) | |
| ## VBREAK | |
| [VBREAK](https://www.atariarchives.org/mapping/memorymap.php#518,519) | |
| ## VSEROC | |
| [VSEROC](https://www.atariarchives.org/mapping/memorymap.php#526,527) | |
| ## VIMIRQ | |
| [VIMIRQ](https://www.atariarchives.org/mapping/memorymap.php#534,535) | |
| ## VVBLKI | |
| [VVBLKI](https://www.atariarchives.org/mapping/memorymap.php#546,547) | |
| ## VVBLKD | |
| [VVBLKD](https://www.atariarchives.org/mapping/memorymap.php#548,549) | |
| ## SETVBV | |
| [SETVBV](https://www.atariarchives.org/mapping/memorymap.php#58460) | |
| ## SYSVBV | |
| [SYSVBV](https://www.atariarchives.org/mapping/memorymap.php#58463) | |
| ## VVBLKD.BAS | |
| [VVBLKD.BAS](https://www.atariarchives.org/mapping/software/VVBLKD.BAS) | |
| ## SRTIMR | |
| [SRTIMR](https://www.atariarchives.org/mapping/memorymap.php#555) | |
| ## CH | |
| [CH](https://www.atariarchives.org/mapping/memorymap.php#764) | |
| ## SDMCTL | |
| [SDMCTL](https://www.atariarchives.org/mapping/memorymap.php#559) | |
| ## VCOUNT | |
| [VCOUNT](https://www.atariarchives.org/mapping/memorymap.php#54283) | |
| ## LPENH | |
| [LPENH](https://www.atariarchives.org/mapping/memorymap.php#564) | |
| ## CDEVIC | |
| [CDEVIC](https://www.atariarchives.org/mapping/memorymap.php#570) | |
| ## GTIA9.BAS | |
| [GTIA9.BAS](https://www.atariarchives.org/mapping/software/GTIA9.BAS) | |
| ## GTIA10.BAS | |
| [GTIA10.BAS](https://www.atariarchives.org/mapping/software/GTIA10.BAS) | |
| ## GTIA11.BAS | |
| [GTIA11.BAS](https://www.atariarchives.org/mapping/software/GTIA11.BAS) | |
| ## CTIAGTIA.BAS | |
| [CTIAGTIA.BAS](https://www.atariarchives.org/mapping/software/CTIAGTIA.BAS) | |
| ## STICK0.BAS | |
| [STICK0.BAS](https://www.atariarchives.org/mapping/software/STICK0.BAS) | |
| ## BLIM | |
| [BLIM](https://www.atariarchives.org/mapping/memorymap.php#650) | |
| ## TXTROW | |
| [TXTROW](https://www.atariarchives.org/mapping/memorymap.php#656) | |
| ## TINDEX | |
| [TINDEX](https://www.atariarchives.org/mapping/memorymap.php#659) | |
| ## OLDROW | |
| [OLDROW](https://www.atariarchives.org/mapping/memorymap.php#90) | |
| ## OLDCOL | |
| [OLDCOL](https://www.atariarchives.org/mapping/memorymap.php#91,92) | |
| ## OLDCHR | |
| [OLDCHR](https://www.atariarchives.org/mapping/memorymap.php#93) | |
| ## OLDADR | |
| [OLDADR](https://www.atariarchives.org/mapping/memorymap.php#94,95) | |
| ## INVFLG | |
| [INVFLG](https://www.atariarchives.org/mapping/memorymap.php#694) | |
| ## SCRFLG | |
| [SCRFLG](https://www.atariarchives.org/mapping/memorymap.php#699) | |
| ## ATACHR | |
| [ATACHR](https://www.atariarchives.org/mapping/memorymap.php#763) | |
| ## SHFLOK | |
| [SHFLOK](https://www.atariarchives.org/mapping/memorymap.php#702) | |
| ## SHFLOK.BAS | |
| [SHFLOK.BAS](https://www.atariarchives.org/mapping/software/SHFLOK.BAS) | |
| ## BOTSCR.BAS | |
| [BOTSCR.BAS](https://www.atariarchives.org/mapping/software/BOTSCR.BAS) | |
| ## COLOR3 | |
| [COLOR3](https://www.atariarchives.org/mapping/memorymap.php#711) | |
| ## COLOR1 | |
| [COLOR1](https://www.atariarchives.org/mapping/memorymap.php#709) | |
| ## COLOR2.BAS | |
| [COLOR2.BAS](https://www.atariarchives.org/mapping/software/COLOR2.BAS) | |
| ## RUNAD | |
| [RUNAD](https://www.atariarchives.org/mapping/memorymap.php#736-737) | |
| ## INITAD | |
| [INITAD](https://www.atariarchives.org/mapping/memorymap.php#738-739) | |
| ## RAMSIZ | |
| [RAMSIZ](https://www.atariarchives.org/mapping/memorymap.php#740) | |
| ## MEMLO.BAS | |
| [MEMLO.BAS](https://www.atariarchives.org/mapping/software/MEMLO.BAS) | |
| ## CRSINH | |
| [CRSINH](https://www.atariarchives.org/mapping/memorymap.php#752) | |
| ## KEYDEL | |
| [KEYDEL](https://www.atariarchives.org/mapping/memorymap.php#753) | |
| ## CH1 | |
| [CH1](https://www.atariarchives.org/mapping/memorymap.php#754) | |
| ## CHACT | |
| [CHACT](https://www.atariarchives.org/mapping/memorymap.php#755) | |
| ## CHACT.BAS | |
| [CHACT.BAS](https://www.atariarchives.org/mapping/software/CHACT.BAS) | |
| ## CHBAS | |
| [CHBAS](https://www.atariarchives.org/mapping/memorymap.php#756) | |
| ## CHAR | |
| [CHAR](https://www.atariarchives.org/mapping/memorymap.php#762) | |
| ## FILDAT | |
| [FILDAT](https://www.atariarchives.org/mapping/memorymap.php#765) | |
| ## SSFLAG | |
| [SSFLAG](https://www.atariarchives.org/mapping/memorymap.php#767) | |
| ## HATABS | |
| [HATABS](https://www.atariarchives.org/mapping/memorymap.php#794-831) | |
| ## FORCREAD.BAS | |
| [FORCREAD.BAS](https://www.atariarchives.org/mapping/software/FORCREAD.BAS) | |
| ## CASBUF | |
| [CASBUF](https://www.atariarchives.org/mapping/memorymap.php#1021-1151) | |
| ## LBUFF | |
| [LBUFF](https://www.atariarchives.org/mapping/memorymap.php#1408-1535) | |
| ## SABYTE | |
| [SABYTE](https://www.atariarchives.org/mapping/memorymap.php#1801) | |
| ## DRVBYT | |
| [DRVBYT](https://www.atariarchives.org/mapping/memorymap.php#1802) | |
| ## IOCB0 | |
| [IOCB0](https://www.atariarchives.org/mapping/memorymap.php#832-847) | |
| ## ZBUFP | |
| [ZBUFP](https://www.atariarchives.org/mapping/memorymap.php#67,68) | |
| ## SETUP | |
| [SETUP](https://www.atariarchives.org/mapping/memorymap.php#4452) | |
| ## FCB | |
| [FCB](https://www.atariarchives.org/mapping/memorymap.php#4993-5120) | |
| ## SPARE | |
| [SPARE](https://www.atariarchives.org/mapping/memorymap.php#563) | |
| ## STATMENT.BAS | |
| [STATMENT.BAS](https://www.atariarchives.org/mapping/software/STATMENT.BAS) | |
| ## OPERATOR.BAS | |
| [OPERATOR.BAS](https://www.atariarchives.org/mapping/software/OPERATOR.BAS) | |
| ## SIN | |
| [SIN](https://www.atariarchives.org/mapping/memorymap.php#48551) | |
| ## COS | |
| [COS](https://www.atariarchives.org/mapping/memorymap.php#48561) | |
| ## ATAN | |
| [ATAN](https://www.atariarchives.org/mapping/memorymap.php#48759) | |
| ## SQR | |
| [SQR](https://www.atariarchives.org/mapping/memorymap.php#48869) | |
| ## VNTP | |
| [VNTP](https://www.atariarchives.org/mapping/memorymap.php#130,131) | |
| ## VNTD | |
| [VNTD](https://www.atariarchives.org/mapping/memorymap.php#132,133) | |
| ## STMCUR | |
| [STMCUR](https://www.atariarchives.org/mapping/memorymap.php#138,139) | |
| ## MOVEPM.BAS | |
| [MOVEPM.BAS](https://www.atariarchives.org/mapping/software/MOVEPM.BAS) | |
| ## PCOLR0 | |
| [PCOLR0](https://www.atariarchives.org/mapping/memorymap.php#704) | |
| ## COLOR4 | |
| [COLOR4](https://www.atariarchives.org/mapping/memorymap.php#712) | |
| ## PCOLR3 | |
| [PCOLR3](https://www.atariarchives.org/mapping/memorymap.php#707) | |
| ## GRACTL | |
| [GRACTL](https://www.atariarchives.org/mapping/memorymap.php#53277) | |
| ## GRAFM | |
| [GRAFM](https://www.atariarchives.org/mapping/memorymap.php#53265) | |
| ## GRAFP0 | |
| [GRAFP0](https://www.atariarchives.org/mapping/memorymap.php#53261) | |
| ## PRIOR | |
| [PRIOR](https://www.atariarchives.org/mapping/memorymap.php#53275) | |
| ## VDELAY | |
| [VDELAY](https://www.atariarchives.org/mapping/memorymap.php#53276) | |
| ## DMACTL | |
| [DMACTL](https://www.atariarchives.org/mapping/memorymap.php#54272) | |
| ## HITCLR | |
| [HITCLR](https://www.atariarchives.org/mapping/memorymap.php#53278) | |
| ## CONSOL | |
| [CONSOL](https://www.atariarchives.org/mapping/memorymap.php#53279) | |
| ## AUDCTL | |
| [AUDCTL](https://www.atariarchives.org/mapping/memorymap.php#53768) | |
| ## POKMSK | |
| [POKMSK](https://www.atariarchives.org/mapping/memorymap.php#16) | |
| ## POTGO | |
| [POTGO](https://www.atariarchives.org/mapping/memorymap.php#53771) | |
| ## SKCTL | |
| [SKCTL](https://www.atariarchives.org/mapping/memorymap.php#53775) | |
| ## AUDF3 | |
| [AUDF3](https://www.atariarchives.org/mapping/memorymap.php#53764) | |
| ## VTIMR1 | |
| [VTIMR1](https://www.atariarchives.org/mapping/memorymap.php#528,529) | |
| ## VTIMR2 | |
| [VTIMR2](https://www.atariarchives.org/mapping/memorymap.php#530,531) | |
| ## VTIMR4 | |
| [VTIMR4](https://www.atariarchives.org/mapping/memorymap.php#532,533) | |
| ## PACTL | |
| [PACTL](https://www.atariarchives.org/mapping/memorymap.php#54018) | |
| ## PBCTL | |
| [PBCTL](https://www.atariarchives.org/mapping/memorymap.php#54019) | |
| ## PORTA | |
| [PORTA](https://www.atariarchives.org/mapping/memorymap.php#54016) | |
| ## PORTB | |
| [PORTB](https://www.atariarchives.org/mapping/memorymap.php#54017) | |
| ## STICK0 | |
| [STICK0](https://www.atariarchives.org/mapping/memorymap.php#632) | |
| ## STICK1 | |
| [STICK1](https://www.atariarchives.org/mapping/memorymap.php#633) | |
| ## PTRIG0 | |
| [PTRIG0](https://www.atariarchives.org/mapping/memorymap.php#636) | |
| ## STICK2 | |
| [STICK2](https://www.atariarchives.org/mapping/memorymap.php#634) | |
| ## STICK3 | |
| [STICK3](https://www.atariarchives.org/mapping/memorymap.php#635) | |
| ## PTRIG4 | |
| [PTRIG4](https://www.atariarchives.org/mapping/memorymap.php#640) | |
| ## HSCROL | |
| [HSCROL](https://www.atariarchives.org/mapping/memorymap.php#54276) | |
| ## VSCROL | |
| [VSCROL](https://www.atariarchives.org/mapping/memorymap.php#54277) | |
| ## VSCROL.BAS | |
| [VSCROL.BAS](https://www.atariarchives.org/mapping/software/VSCROL.BAS) | |
| ## NMIEN | |
| [NMIEN](https://www.atariarchives.org/mapping/memorymap.php#54286) | |
| ## FR1 | |
| [FR1](https://www.atariarchives.org/mapping/memorymap.php#224-229) | |
| ## FLPTR | |
| [FLPTR](https://www.atariarchives.org/mapping/memorymap.php#252,253) | |
| ## BITMAP8.BAS | |
| [BITMAP8.BAS](https://www.atariarchives.org/mapping/software/BITMAP8.BAS) | |
| ## CIOV | |
| [CIOV](https://www.atariarchives.org/mapping/memorymap.php#58454) | |
| ## ISRSIR | |
| [ISRSIR](https://www.atariarchives.org/mapping/memorymap.php#6691) | |
| ## EGETCH | |
| [EGETCH](https://www.atariarchives.org/mapping/memorymap.php#63038) | |
| ## Return to Table of Contents | |
| [Return to Table of Contents](https://www.atariarchives.org/mapping/index.php) | |
| ## Previous Chapter | |
| [Previous Chapter](https://www.atariarchives.org/mapping/introduction.php) | |
| ## Next Chapter | |
| [Next Chapter](https://www.atariarchives.org/mapping/appendix1.php) | |
| █████╗ ████████╗ █████╗ ██████╗ ██╗ ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗██╗ ██████╗███████╗ | |
| ██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗██║ ██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║██║██╔════╝██╔════╝ | |
| ███████║ ██║ ███████║██████╔╝██║ ██║ ███╗██████╔╝███████║██████╔╝███████║██║██║ ███████╗ | |
| ██╔══██║ ██║ ██╔══██║██╔══██╗██║ ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║██║██║ ╚════██║ | |
| ██║ ██║ ██║ ██║ ██║██║ ██║██║ ╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║██║╚██████╗███████║ | |
| ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝╚══════╝ | |
| █████╗ ███╗ ██╗██████╗ █████╗ ██████╗ ██████╗ █████╗ ██████╗ ███████╗ | |
| ██╔══██╗████╗ ██║██╔══██╗ ██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔════╝ | |
| ███████║██╔██╗ ██║██║ ██║ ███████║██████╔╝██║ ███████║██║ ██║█████╗ | |
| ██╔══██║██║╚██╗██║██║ ██║ ██╔══██║██╔══██╗██║ ██╔══██║██║ ██║██╔══╝ | |
| ██║ ██║██║ ╚████║██████╔╝ ██║ ██║██║ ██║╚██████╗██║ ██║██████╔╝███████╗ | |
| ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═════╝ ╚══════╝ | |
| ██████╗ █████╗ ███╗ ███╗███████╗ ██████╗ ███████╗███████╗██╗ ██████╗ ███╗ ██╗ | |
| ██╔════╝ ██╔══██╗████╗ ████║██╔════╝ ██╔══██╗██╔════╝██╔════╝██║██╔════╝ ████╗ ██║ | |
| ██║ ███╗███████║██╔████╔██║█████╗ ██║ ██║█████╗ ███████╗██║██║ ███╗██╔██╗ ██║ | |
| ██║ ██║██╔══██║██║╚██╔╝██║██╔══╝ ██║ ██║██╔══╝ ╚════██║██║██║ ██║██║╚██╗██║ | |
| ╚██████╔╝██║ ██║██║ ╚═╝ ██║███████╗ ██████╔╝███████╗███████║██║╚██████╔╝██║ ╚████║ | |
| ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ | |
| # Atari Graphics and Arcade Game Design-Chapter 1 | |
| ## Download | |
| [Download](https://www.atariarchives.org/agagd/software/chapter1/PAINTPOT.BAS) | |
| ## Download | |
| [Download](https://www.atariarchives.org/agagd/software/chapter1/PAINTPOT.LST) | |
| ## View | |
| [View](https://www.atariarchives.org/agagd/displayfile.php?file=chapter1/PAINTPOT.LST) | |
| ## Download | |
| [Download](https://www.atariarchives.org/agagd/software/chapter1/GR10DEMO.BAS) | |
| ## Download | |
| [Download](https://www.atariarchives.org/agagd/software/chapter1/GR10DEMO.LST) | |
| ## View | |
| [View](https://www.atariarchives.org/agagd/displayfile.php?file=chapter1/GR10DEMO.LST) | |
| ## Download | |
| [Download](https://www.atariarchives.org/agagd/software/chapter1/GTIATRIC.BAS) | |
| ## Download | |
| [Download](https://www.atariarchives.org/agagd/software/chapter1/GTIATRIC.LST) | |
| ## View | |
| [View](https://www.atariarchives.org/agagd/displayfile.php?file=chapter1/GTIATRIC.LST) | |
| ## Return to Table of Contents | |
| [Return to Table of Contents](https://www.atariarchives.org/agagd/index.php) | |
| ## Previous Chapter | |
| [Previous Chapter](https://www.atariarchives.org/agagd/preface.php) | |
| ## Next Chapter | |
| [Next Chapter](https://www.atariarchives.org/agagd/chapter2.php) | |
| ###### # | |
| # # # #### ##### # ## # # # # #### ##### #### | |
| # # # # # # # # # # # # # # # # | |
| # # # #### # # # # # # # # #### # #### | |
| # # # # ##### # ###### # # # # # # | |
| # # # # # # # # # # # # # # # # # | |
| ###### # #### # ###### # # # ####### # #### # #### | |
| # 8-Bit Technical Resource Center: ATR: chpt.15: Display Lists | |
| ## Craig Lisowski: "ATR: chpt.16: Player/Missile graphics (P/MG)" | |
| [Craig Lisowski: "ATR: chpt.16: Player/Missile graphics (P/MG)"](https://www.atariarchives.org/cfn/05/07/0017.php) | |
| ## Craig Lisowski: "ATR: chpt.14: Hardware Chips" | |
| [Craig Lisowski: "ATR: chpt.14: Hardware Chips"](https://www.atariarchives.org/cfn/05/07/0015.php) | |
| ## [ date ] | |
| [[ date ]](https://www.atariarchives.org/cfn/05/07/index.php#16) | |
| ## [ author ] | |
| [[ author ]](https://www.atariarchives.org/cfn/05/07/author.php#16) | |
| ## [ thread ] | |
| [[ thread ]](https://www.atariarchives.org/cfn/05/07/thread.php#16) | |
| ## [ subject ] | |
| [[ subject ]](https://www.atariarchives.org/cfn/05/07/subject.php#16) | |
| ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗██╗ ██████╗ ███╗ ███╗ ██████╗ ██████╗ ███████╗███████╗ | |
| ██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║██║██╔════╝ ████╗ ████║██╔═══██╗██╔══██╗██╔════╝██╔════╝ | |
| ██║ ███╗██████╔╝███████║██████╔╝███████║██║██║ ██╔████╔██║██║ ██║██║ ██║█████╗ ███████╗ | |
| ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║██║██║ ██║╚██╔╝██║██║ ██║██║ ██║██╔══╝ ╚════██║ | |
| ╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║██║╚██████╗ ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗███████║ | |
| ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ | |
| # 56 graphic modes, Atari 8-bit computers | |
| > All about the 56 graphic modes of the Atari 8-bit computers | |
| ## Home | |
| [Home](https://www.atari800xl.eu/) | |
| ## Atari DS800 | |
| [Atari DS800](https://www.atari800xl.eu/hardware/computers/atari-ds800.html) | |
| ## PERITEL Atari 800 | |
| [PERITEL Atari 800](https://www.atari800xl.eu/hardware/computers/peritel-atari-800.html) | |
| ## PERITEL Atari 400 | |
| [PERITEL Atari 400](https://www.atari800xl.eu/hardware/computers/peritel-atari-400.html) | |
| ## Atari 1200XL | |
| [Atari 1200XL](https://www.atari800xl.eu/hardware/computers/atari-1200xl.html) | |
| ## PAL Atari 800XL | |
| [PAL Atari 800XL](https://www.atari800xl.eu/hardware/computers/pal-atari-800xl.html) | |
| ## SECAM Atari 800XL | |
| [SECAM Atari 800XL](https://www.atari800xl.eu/hardware/computers/secam-atari-800xl.html) | |
| ## 'Star' Arabic Atari 65XE | |
| ['Star' Arabic Atari 65XE](https://www.atari800xl.eu/hardware/computers/star-arabic-atari-65xe.html) | |
| ## Atari 8-Bit Computers FAQ | |
| [Atari 8-Bit Computers FAQ](https://www.atari800xl.eu/faq/atari-8bit-faq.html) | |
| ## Knowledge base | |
| [Knowledge base](https://www.atari800xl.eu/docs/kb/atari-8bit-kb-X-0000-knowledge-base.html) | |
| ## Rare Atari documents | |
| [Rare Atari documents](https://www.atari800xl.eu/docs/rare/atari-rare-documents-by-discovery-date.html) | |
| ## Reference documents | |
| [Reference documents](https://www.atari800xl.eu/docs/reference/atari-8bit-reference-documents.html) | |
| ## "Antic" magazine, Vol. 3 No. 5, entitled "Unlocking the 56 graphic modes — Instant exploration of Atari's display styles" | |
| ["Antic" magazine, Vol. 3 No. 5, entitled "Unlocking the 56 graphic modes — Instant exploration of Atari's display styles"](https://www.atarimagazines.com/v3n5/allmodes.html) | |
| ## GTIA | |
| [GTIA](https://www.atari800xl.eu/docs/kb/kb-hardware-0001-atari-8bit-ctia-gtia.html) | |
| ## "De Re Atari, Chapter 2, ANTIC and the Display List" | |
| ["De Re Atari, Chapter 2, ANTIC and the Display List"](https://www.atariarchives.org/dere/chapt02.php) | |
| ## Atari Archives.org | |
| [Atari Archives.org](https://www.atariarchives.org/) | |
| ## "De Re Atari, Chapter 5, Display List Interrupts" | |
| ["De Re Atari, Chapter 5, Display List Interrupts"](https://www.atariarchives.org/dere/chapt05.php) | |
| ## "Atari 8-bit Display List Interrupts: A Complete(ish) Tutorial" | |
| ["Atari 8-bit Display List Interrupts: A Complete(ish) Tutorial"](https://playermissile.com/dli_tutorial/) | |
| ## Player Missile.com | |
| [Player Missile.com](https://playermissile.com/) | |
| ## Templated | |
| [Templated](https://templated.co) | |
| ██████╗██╗ ██╗ █████╗ ██████╗ █████╗ ██████╗████████╗███████╗██████╗ ███████╗███████╗████████╗███████╗ | |
| ██╔════╝██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗ ██╔════╝██╔════╝╚══██╔══╝██╔════╝ | |
| ██║ ███████║███████║██████╔╝███████║██║ ██║ █████╗ ██████╔╝ ███████╗█████╗ ██║ ███████╗ | |
| ██║ ██╔══██║██╔══██║██╔══██╗██╔══██║██║ ██║ ██╔══╝ ██╔══██╗ ╚════██║██╔══╝ ██║ ╚════██║ | |
| ╚██████╗██║ ██║██║ ██║██║ ██║██║ ██║╚██████╗ ██║ ███████╗██║ ██║ ███████║███████╗ ██║ ███████║ | |
| ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝ | |
| # De Re Atari - Chapter 3 | |
| ## 3 GRAPHICS INDIRECTION (COLOR REGISTERS AND CHARACTER SETS) | |
| Indirection is a powerful concept in programming. In 6502 assembly | |
| language, there are three levels of indirection in referring to numbers. | |
| The first and most direct level is the immediate addressing mode in | |
| which the number itself is directly stated: | |
| `LDA #$F4` | |
| The second level of indirection is reached when the program refers to a memory location that holds the number: | |
| `LDA $0602` | |
| The third and highest level of indirection with the 6502 is attained | |
| when the program refers to a pair of memory locations which together | |
| contain the address of the memory location that holds the number. In the | |
| 6502, this indirection is complicated by the addition of an index: | |
| `LDA ($D0),Y` | |
| Indirection provides a greater degree of generality (and hence power) to | |
| the programmer. Instead of trucking out the same old numbers every time | |
| you want to get something done, you can simply point to them. By | |
| changing the pointer, you can change the behaviour of the program. | |
| Indirection is obviously an important capability. | |
| ### COLOR REGISTERS | |
| Graphics indirection is built into the ATARI Home Computer in two | |
| ways: with color registers and with character sets. Programmers first | |
| approaching this computer after programming other systems often think in | |
| terms of direct colors. A color register is a more complex beast than a | |
| color. A color specifies a permanent value. A color register is | |
| indirect; it holds any color value. The difference between the two is | |
| analogous to the difference between a box-end wrench and a socket | |
| wrench. The box-end wrench comes in one size only but a socket wrench | |
| can hold almost any size socket. A socket wrench is more flexible but | |
| takes a little more skill to use properly. Similarly, a color register | |
| is more flexible than a color but takes more skill to use effectively. | |
| There are nine color registers in the ATARI 400/800 Computer; four | |
| are for player-missile graphics and will be discussed in Section 4. The | |
| remaining five are not always used; depending on the graphics mode used, | |
| as few as two | |
| registers or as many as five will show up on the screen. In BASIC mode | |
| 0, only | |
| two and one-half registers are used because the hue value of the | |
| characters is | |
| ignored; characters take the same hue as playfield register 2 but take | |
| their | |
| luminance from register 1. The color registers are in CTIA at addresses | |
| $D016 | |
| through $D01A. They are "shadowed" from OS RAM locations into CTIA | |
| during | |
| vertical blank. Figure 3-1 gives color register shadow and hardware | |
| addresses. | |
| | Image Controlled | Hardware | | OS Shadow | | | |
| | --- | --- | --- | --- | --- | | |
| | Label | Address | Label | Address | | |
| | Player 0 | COLPM0 | D012 | PCOLR0 | 2C0 | | |
| | Player 1 | COLPM1 | D013 | PCOLR1 | 2C1 | | |
| | Player 2 | COLPM2 | D014 | PCOLR2 | 2C2 | | |
| | Player 3 | COLPM3 | D015 | PCOLR3 | 2C3 | | |
| | Playfield 0 | COLPF0 | D016 | COLOR0 | 2C4 | | |
| | Playfield 1 | COLPF1 | D017 | COLOR1 | 2C5 | | |
| | Playfield 2 | COLPF2 | D018 | COLOR2 | 2C6 | | |
| | Playfield 3 | COLPF3 | D019 | COLOR3 | 2C7 | | |
| | Background | COLBK | D01A | COLOR4 | 2C8 | | |
| Figure 3-1 Color Register Labels and Addresses | |
| For most purposes, the user controls the color registers by writing | |
| to the shadow locations. There are only two cases in which the | |
| programmer would write directly to the CTIA addresses. The first and | |
| most common is the display list interrupt which will be discussed in | |
| Section 5. The second arises when the user disables the OS vertical | |
| blank interrupt routines that move the shadow values into CTIA. Vertical | |
| blank interrupts are discussed in Section 8. | |
| Colors are encoded in a color register by a simple formula. The upper | |
| nybble gives the hue value, which is identical to the second parameter | |
| of the BASIC SETCOLOR command. Table 9-3 of the BASIC Reference Manual | |
| lists hue values. The lower nybble in the color register gives the | |
| luminance value of the color. It is the same as the third parameter in | |
| the BASIC SETCOLOR command. The lowest order bit of this nybble is not | |
| significant. Thus, there are eight luminances for each hue. There are a | |
| total of 128 colors from which to choose (8 luminances times 16 hues). | |
| In this book, the term 'color' denotes a hue-luminance combination. | |
| Once a color is encoded into a color register, it is mapped onto the | |
| screen | |
| by referring to the color register that holds it. In map display modes | |
| which | |
| support four color registers the screen data specifies which color | |
| register is | |
| to be mapped onto the screen. Since there are four color registers it | |
| takes only two bits to encode one pixel. Thus, each screen data byte | |
| holds data for four pixels. The value in each pair of bits specifies | |
| which color register provides the color for that pixel. | |
| In text display modes (BASIC's GRAPHICS modes 1 and 2) the selection | |
| of color registers is made by the top two bits of the character code. | |
| This leaves only six bits for defining the character, which is why these | |
| two modes have only 64 characters available. | |
| Color register indirection gives you four special capabilities. First, you | |
| can choose from 128 different colors for your displays. This allows you to | |
| choose the color that most nearly meets your needs. | |
| Second, you can manipulate the color registers in real time to | |
| produce pretty effects. The simplest version of this is demonstrated by | |
| the following BASIC line: | |
| `FOR I=0 TO 254 STEP 2:POKE 712,I:NEXT I` | |
| This line simply cycles the border color through all possible colors. | |
| The effect is quite pleasing and certainly grabs attention. The | |
| fundamental technique can be extended in a variety of ways. A special | |
| variation of this is to create simple cyclic animation by drawing a | |
| figure in four colors and then cycle the colors through the color | |
| registers rather than redrawing the figure. The following program | |
| illustrates the idea: | |
| ``` | |
| 10 GRAPHICS 23 | |
| 20 FOR X=0 TO 39 | |
| 30 FOR I=0 TO 3 | |
| 40 COLOR I | |
| 50 PLOT 4*X+I,0 | |
| 60 DRAWTO 4*X+I,95 | |
| 70 NEXT I | |
| 80 NEXT X | |
| 90 A=PEEK(712) | |
| 100 POKE 712,PEEK(710) | |
| 110 POKE 710,PEEK(709) | |
| 120 POKE 709,PEEK(708) | |
| 130 POKE 708,A | |
| 140 GOTO 90 | |
| ``` | |
| [Download](https://www.atariarchives.org/dere/software/chapter3/CH3PRG1.BAS) CH3PRG1.BAS (Saved BASIC) | |
| [Download](https://www.atariarchives.org/dere/software/chapter3/CH3PRG1.LST) / [View](https://www.atariarchives.org/dere/displayfile.php?file=chapter3/CH3PRG1.LST) CH3PRG1.LST (Listed BASIC) | |
| The third application of color registers is to logically key colors | |
| to situations. For example, a paged menu system can be made more | |
| understandable by changing the background color or the border color for | |
| each | |
| page in the menu. Perhaps the screen could flash red when an illegal key | |
| is | |
| pressed. The use of the color characters available in BASIC Graphics | |
| modes 1 and 2 can greatly extend the impact of textual material. An | |
| account sum could be shown in red if the account is in the red, or black | |
| if the account is in the | |
| black. Important words or phrases can be shown in special colors to make | |
| them | |
| stand out. The use of colors in map modes (no text) can also improve the | |
| utility of such graphics. A single graphics image (a monster, a boat, | |
| or whatever) could be presented in several different colors to represent | |
| several different versions of the same thing. It costs a great deal of | |
| RAM to store an image, but it costs very little to change the color of | |
| an existing image. For example, it would be much easier to show three | |
| different boats by presenting one boat shape in three different colors | |
| than three different boat shapes. | |
| The fourth and most important application of color registers is used with | |
| display list interrupts. A single color register can be used to put up to 128 | |
| colors onto a single screen. This very important capability will be discussed in | |
| Sect ton 5. | |
| ### CHARACTER SETS | |
| Graphics indirection is also provided through the use of redefinable | |
| character set. A standard character set is provided in ROM, but there is | |
| no reason why this particular character set must be used. The user can | |
| create and display any character set desired. There are three steps | |
| necessary to use a redefined character set. First, the programmer must | |
| define the character set. This is the most time-consuming step. Each | |
| character is displayed on the screen on an 8x8 grid; it is encoded in | |
| memory as an 8-byte table. Figure 3-2 depicts the encoding arrangement. | |
| | Character Image | Binary Representation | Hex Representation | | |
| | --- | --- | --- | | |
| |  | `00000000 00011000 00111100 01100110 01100110 01111110 01100110 00000000` | `00 18 3C 66 66 7E 66 00` | | |
| Figure 3-2 Character Encoding | |
| A full character set has 128 characters in it, each with a normal and | |
| an | |
| inverse video incarnation. Such a character set needs 1024 bytes of | |
| space and | |
| must start on a 1K boundary. Character sets for BASIC modes 1 and 2 have | |
| only 64 distinct characters, and so require only 512 bytes and must | |
| start on a 1/2K boundary. The first 8 bytes define the zeroth character, | |
| the next 8 bytes define the first character, and so on. Obviously, | |
| defining a new character set is a big job. Fortunately, there are | |
| software packages on the market to make this job easier. | |
| Once the character set is defined and placed into RAM, you must tell | |
| ANTIC | |
| where it can find the character set. This is done by poking the page | |
| number of | |
| the beginning of the character table into location $D409 (decimal | |
| 54281). The OS shadow location, which is the location you would normally | |
| use, is called CHBAS and resides at $2F4 (decimal 756). The third step | |
| in using character sets is to print the character you want onto the | |
| screen. This can be done directly from BASIC with simple PRINT | |
| statements or by writing numbers directly into the screen memory. | |
| A special capability of the system not supported in BASIC is the | |
| four-color | |
| character set option. BASIC Graphics modes 1 and 2 support five colors, | |
| but each character in these modes is really a two-color character; each | |
| one has a | |
| foreground color and a background color. The foreground color can be any | |
| of four single colors, but only one color at a time can be shown within | |
| a single | |
| character. This can be a serious hindrance when using character | |
| graphics. | |
| There are two other text modes designed especially for character | |
| graphics. They are ANTIC modes 4 and 5. Each character in these modes is | |
| only four pixels wide, but each pixel can have four colors (counting | |
| background) The characters are defined just like BASIC Graphics mode 0 | |
| characters, except that each pixel is twice as wide and has two bits | |
| assigned to it to specify the color register used. Unlike ANTIC modes 6 | |
| and 7 (BASIC modes 1 and 2), color register selection is not made by the | |
| character name byte but instead by the defined character set. Each byte | |
| in the character table is broken into four bit pairs, each of which | |
| selects the color for a pixel. (This is why there are only four | |
| horizontal pixels per character.) The highest bit (D7) of the character | |
| name byte modifies the color register used. Color register selection is | |
| made according to Figure 3-3: | |
| | | | | | |
| | --- | --- | --- | | |
| | `bit pair in character defn` | `D7 = 0` | `D7 = 1` | | |
| | `00 01 10 11` | `COLBAK PF0 PF1 PF2` | `COLBAK PF0 PF1 PF3` | | |
| Figure 3-3 Color Register Selection for Characters | |
| Using these text modes, multicolored graphics characters can be put onto the screen. | |
| Another interesting ANTIC character mode is the lowercase descenders | |
| mode (ANTIC mode 3). This mode displays 10 scan lines per mode line, but | |
| since characters use only eight bytes vertically, the lower two scan | |
| lines are | |
| normally left empty. If a character in the last quarter of the character | |
| set is | |
| displayed, the top two scan lines of the character will be left empty; | |
| the data | |
| that should have been displayed there will instead be shown on the | |
| bottom two | |
| lines. This allows the user to create lowercase characters with | |
| descenders. | |
| ### APPLICATIONS OF CHARACTER SETS | |
| Many interesting and useful application possibilities spring from | |
| character set indirection. The obvious application is the modified font. | |
| A different font can give a program a unique appearance. It is possible | |
| to have Greek, Cyrillic, or other special character sets. Going one | |
| step further, you can create graphics fonts. The ENERGY CZAR™ computer | |
| program uses a redefined character set for bar graphs. A character | |
| occupies eight pixels; this means that bar charts implemented with | |
| standard characters have a resolution of eight pixels, a rather poor | |
| resolution. ENERGY CZAR uses a special character set in which some of | |
| the less popular text symbols (ampersands, pound signs, and the like) | |
| have been replaced with special bar chart characters. One character is a | |
| one-pixel bar, another is a two-pixel bar, and so on to the full | |
| eight-pixel bar. The program can thus draw detailed bar charts with | |
| resolution of a single pixel. Figure 3-4 shows a typical display from | |
| this program. The mix of text with map graphics is only apparent; the | |
| entire display is constructed with characters. | |
|  | |
| Figure 3-4 ENERGY CZAR™ Bar Charts | |
| In many applications, character sets can be created that show special | |
| images. For example, by defining a terrain graphics character set with | |
| river characters, forest characters, mountain characters, and so forth, | |
| It is possible to make a terrain map of any country. Indeed, with | |
| imagination a map of terrain on a different planet can be done just as | |
| easily. When doing this, it is best to define five to eight characters | |
| for each terrain type. Each variation of a single type should be | |
| positioned slightly differently in the character pixel. By | |
| mixing the different characters together, It is possible to avoid the | |
| monotonous look that is characteristic of primitive character graphics. | |
| Most people won't realize that the resulting map uses character graphics | |
| until they study the map closely. Figure 3-5 shows a display of a | |
| terrain map created with character set graphics. The reproduction in | |
| black and white does not do justice to the original display, which has | |
| up to 18 colors. | |
|  | |
| Figure 3-5 Terrain Map With Character Set Graphics | |
| You could create an electronics character set with transistor characters, | |
| diode characters, wire characters, and so forth to produce an electronics | |
| schematics program. Or you could create an architectural character set with | |
| doorway characters, wall characters, corner characters, and so on to make an | |
| architectural blueprint program. The graphics possibilities opened up by | |
| character graphics with personal computers have not been fully explored. | |
| Characters can be turned upside down by pokeing a 4 into location | |
| 755. One possible application of this feature might be for displaying | |
| playing cards (as in a Blackjack game). The upper half of the card can | |
| be shown right side up; with a display list interrupt the characters can | |
| be turned upside down for the lower half of the card. This feature | |
| might also be of some use in displaying | |
| images with mirror reflections (reflection pools, lakes, etc. | |
| Even more exciting possibilities spring to mind when you realize that | |
| it is | |
| quite practical to change character sets while the program is running. A | |
| character set costs either 512 bytes or 1024 bytes; in either case it is | |
| quite | |
| inexpensive to keep multiple character sets in memory and flip between | |
| them | |
| during program execution. There are three time regimes for such | |
| character set multiplexing: human slow (more than 1 second); human fast | |
| (1/60 second to 1 second); and machine fast (faster than 1/60 sec). | |
| Human-slow character set multiplexing is useful for "change of | |
| scenery" work. For example, a space travel program might use one | |
| graphics character set for one planet, another set for space, and a | |
| third set for another planet. As the traveller changes locations, the | |
| program changes the character set to give exotic new scenery. An | |
| adventure program might change character sets as the player changes | |
| locales. | |
| Human-fast character set multiplexing is primarily of value for | |
| animation. | |
| This can be done in two ways: changing characters within a single | |
| character set, and changing whole character sets. The SPACE INVADERS | |
| (trademark of Taito America Corp.) program on the ATARI Home Computer | |
| uses the former technique. The invaders are actually characters. By | |
| rapidly changing the characters, the programmer was able to animate | |
| them. This was easy because there are only six different monsters; each | |
| has four different incarnations. | |
| High-speed cyclic animation of an entire screen is possible by | |
| setting up a | |
| number of character sets, drawing the screen image, and then simply | |
| cycling | |
| through the character sets. If each character has a slightly different | |
| incarnation in each of the character sets, that character will go | |
| through an | |
| animated sequence as the character sets are changed. In this way a | |
| screen full of objects could be made to cyclically move with a very | |
| simple loop. Once the character set data is in place and the screen has | |
| been drawn, the code to | |
| animate the screen would be this simple: | |
| ``` | |
| 1000 FOR I=1 TO 10 | |
| 1010 POKE 756,CHARBASE(I) | |
| 1020 NEXT I | |
| 1030 GOTO 1000 | |
| ``` | |
| Computer-fast character set animation is used to put multiple | |
| character sets onto a single screen. This makes use of the display list | |
| interrupt capability of the computer. Display list interrupts are | |
| discussed in Sect ton 5. | |
| The use of character sets for graphics and animation has many | |
| advantages and some limitations. The biggest advantage is that it costs | |
| very little RAM to produce detailed displays. A graphics display using | |
| BASIC mode 2 characters (such as the one shown in Figure 3-5) can give | |
| as much detail and one more color than a BASIC mode 7 display. Yet the | |
| character image will cost 200 bytes while the map image will cost 4000 | |
| bytes. The RAM cost for multiple character sets is only 512 bytes per | |
| set, so it is inexpensive to have multiple character sets. Screen | |
| manipulations with character graphics are much faster because you have | |
| less data to manipulate. However, character graphics are not as flexible | |
| as map graphics. You cannot put anything you want anywhere on the | |
| screen. This limitation would preclude the use of character graphics in | |
| some applications. However, there remain many graphics applications for | |
| which the program need display only a limited number of predefined | |
| shapes in fixed locations. In these cases, character graphics provide | |
| great utility. | |
| --- | |
| [](https://www.atariarchives.org/) | |
| [](https://www.atariarchives.org/dere/) | |
| ███╗ ███╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗ ████████╗██╗ ██╗███████╗ | |
| ████╗ ████║██╔═══██╗██║ ██║██║████╗ ██║██╔════╝ ╚══██╔══╝██║ ██║██╔════╝ | |
| ██╔████╔██║██║ ██║██║ ██║██║██╔██╗ ██║██║ ███╗ ██║ ███████║█████╗ | |
| ██║╚██╔╝██║██║ ██║╚██╗ ██╔╝██║██║╚██╗██║██║ ██║ ██║ ██╔══██║██╔══╝ | |
| ██║ ╚═╝ ██║╚██████╔╝ ╚████╔╝ ██║██║ ╚████║╚██████╔╝ ██║ ██║ ██║███████╗ | |
| ╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ | |
| ██████╗ ██╗███████╗██████╗ ██╗ █████╗ ██╗ ██╗ | |
| ██╔══██╗██║██╔════╝██╔══██╗██║ ██╔══██╗╚██╗ ██╔╝ | |
| ██║ ██║██║███████╗██████╔╝██║ ███████║ ╚████╔╝ | |
| ██║ ██║██║╚════██║██╔═══╝ ██║ ██╔══██║ ╚██╔╝ | |
| ██████╔╝██║███████║██║ ███████╗██║ ██║ ██║ | |
| ╚═════╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ | |
| **6 | |
| Moving the Display** | |
|  | |
| **Michael P. Surh** | |
| *A variation on the techniques which allow screen flipping also makes possible screen scrolling--even horizontally. Here's how.* | |
| The Atari computers are capable of moving their display memory (that | |
| is, the section of memory storing the screen display), and they can have | |
| more than one section of display memory stored at the same time. This | |
| feature is particularly useful for graphics displays and can be used for | |
| animation. It is possible to move the entire screen so that everything | |
| you see on the screen moves too, and the technique can be used to create | |
| smoother animation and drawing than would otherwise be possible. | |
| Before you try the following programs which demonstrate these | |
| tricks, you should have some understanding of how the Atari display | |
| works. The Atari has two separate registers that control where it keeps | |
| its display (the display list) in the overall computer memory. These | |
| registers work by storing the address of the first memory location used | |
| by the display list. The computer puts all of the screen data into the | |
| memory starting at that first address, and also reads the numbers stored | |
| there to display images on the screen. Since what you see on the screen | |
| is stored in this memory section, it is possible to modify the display | |
| directly by using POKEs instead of the usual BASIC commands PRINT, PLOT, | |
| and DRAWTO. | |
| If this is news to you, try Program 1 to modify a GRAPHICS 0 screen | |
| with POKEs. This program begins by printing the address for the start of | |
| the display list as read from both of the registers (see lines 20 | |
| through 50). Then the program POKEs numbers from 0 to 255 into | |
| successive locations in the display list. All of the alphanumeric and | |
| graphics characters appear on the screen. Also, try changing the | |
| graphics mode on line 10 (but remove line 40 if the graphics mode is not | |
| mixed with a GRAPHICS 0 text window). This program works on any | |
| graphics mode, but its effects are different with various modes. At | |
| times this graphics technique is better than using PRINT or PLOT and | |
| DRAWTO because it is faster, even though it is more difficult. | |
| The program works because the computer keeps the display of | |
| characters or graphics points stored as numbers in the reserved display | |
| memory. Each number stored there corresponds to a character or some | |
| graphics points on the screen. Whenever the computer prints or draws | |
| something, it is going into this memory section and changing something | |
| (this is what Program 1 did, without using PRINT or DRAWTO). Changing | |
| the display list changes the screen because the computer also reads this | |
| memory section from start to finish and sends it to the screen 60 times | |
| every second. | |
| ### Controlling Display Memory | |
| The two registers that store the location of the display list tell | |
| the computer where to read from or write to the display memory; without | |
| them, the computer would not be able to find it. There is a good reason | |
| for storing the display address in two locations--this allows | |
| sophisticated graphics and animation. Both of the registers act as | |
| pointers to the display memory, and they both store its starting | |
| address, but one pointer controls where the computer goes to write in | |
| the memory, and the other tells the computer where to start reading the | |
| information to be displayed. If you remember, the computer must do both | |
| operations to put the display on your screen, and it has a pointer for | |
| each function. | |
| The pointer controlling all writing to the screen is located at 88 | |
| and 89 in the memory, but the second pointer's location depends on your | |
| computer's memory size and the graphics mode you are using. See Program | |
| 1, line 20, to find out how to locate the pointer; its position is the | |
| variable PNTR and PNTR + 1. | |
| Usually the two pointers store the same address, so if the computer | |
| prints something on the third line, the information appears on the third | |
| line of the screen in exactly the same place as it was written in the | |
| display list. But if you change one of the two pointers, when the | |
| computer wants to read or write on the display memory and goes to what | |
| it thinks is the start of the memory section, it is in the wrong | |
| location. As a result, the information appears in the wrong place. | |
| Program 2 demonstrates what happens when you change the pointer | |
| controlling where the computer writes into the memory. The program | |
| starts by printing the word *left*all the way down the left | |
| margin of the screen. Then it clears the screen and starts over, but | |
| this time it changes the number stored in 88 each time it prints a word. | |
| The computer still prints on the left margin (at least it thinks it | |
| does!), but the words are stored in the memory offset from their correct | |
| positions, so they appear in the wrong places on the screen. | |
| Once the computer is given the wrong starting address, everything it | |
| prints afterward will appear in the wrong place. This is because the | |
| computer starts at the location specified by the register in 88 and 89 | |
| and counts through the memory until it is where it wants to print. If it | |
| starts in the wrong place, it winds up in the wrong place, and whatever | |
| is printed or drawn is in a different spot on the screen. If you want | |
| to prove that the computer is starting in the wrong place, hit BREAK | |
| (not SYSTEM RESET). The READY prompt and anything you type will line up | |
| at the new margin, and the lines will overlap onto both sides of the | |
| screen. | |
| Notice that whatever was written on the screen before location 88 | |
| was changed did not move; only the words printed after the change are | |
| displaced. Also, it is not always possible to print at the bottom of the | |
| screen, and it sometimes becomes impossible to use the PLOT function | |
| after the numbers in 88 or 89 have been changed. Since I can find no way | |
| to remedy these problems, I see little use for this even though it is | |
| an interesting trick. | |
| The other pointer tells the computer where to start *reading*the | |
| display memory. Changing the address stored here is much more | |
| interesting, because it makes the computer start reading in the wrong | |
| place, and the entire screen shifts. By controlling how much the pointer | |
| changes from its original value, you can make everything on the screen | |
| seem to move horizontally or vertically. | |
| Unfortunately, it is not very practical to move the screen | |
| vertically because garbage is sometimes displayed. You can see this in | |
| Program 3, which changes the pointer to move any graphics mode | |
| horizontally. This pointer is stored in a variable because it is placed | |
| just before the start of the display memory, and the display memory's | |
| location depends on the graphics mode and your computer's memory size. | |
| ### Moving the Screen | |
| When you try this program, you may notice certain features of the | |
| moving screen. First, in any of the mixed graphics modes, the text | |
| window of GRAPHICS 0 at the bottom of the screen remains stationary as | |
| the rest of the screen moves. In GRAPHICS 8 and 8 + 16, only the top | |
| half of the screen moves while the bottom is at rest. Also, the screen | |
| jumps each time the loop is reexecuted, and as the program runs through | |
| the loop, part of the screen fills with apparently random data | |
| (garbage). Last of all, the edge of the screen that moves out of view | |
| horizontally reappears on the other side of the screen. | |
| The text window of GRAPHICS 0 in the mixed graphics modes does not | |
| move because it has its own pointer to control the start of its display | |
| memory. This is also true for the bottom half of a GRAPHICS 8 or 8 + 16 | |
| display. Check Table 1 to find where to POKE to move the bottom half of | |
| GRAPHICS 8 and the text window for each of the mixed graphics modes. | |
| There is an advantage to this added complexity. Not only can you | |
| move part of the screen and leave the rest still, but you can also move | |
| the different parts in different directions or at different rates. In | |
| GRAPHICS 8 you can actually move all three parts at different speeds at | |
| the same time. | |
| That cures the problem of unmoving parts of the screen, but there | |
| are still more problems. When the program finishes its loop and starts | |
| over again, there is a large and noticeable jump on the screen. Also, | |
| there are unusual problems with the top of the screen; unwanted garbage | |
| occasionally appears or part of the display disappears off the top. This | |
| is particularly noticeable in GRAPHICS 0 through 2, which are text | |
| modes. Strange characters can appear, and if you erase them the display | |
| goes haywire. | |
| You can reduce the jump in the screen each time the loop is run. | |
| Change the loop in line 70 to match what is in Table 2 for the | |
| particular graphics mode. This also remedies the occasional appearance | |
| of mysterious characters at the top of the screen. Unfortunately, this | |
| means that the top line will periodically disappear and reappear. You | |
| could leave it blank to keep this unnoticed. | |
| Still, the method is satisfactory for the higher resolution graphics | |
| modes where the screen "bumps" are less obvious. And by carefully | |
| adapting the loop, you might find a decent compromise. | |
| Try the programs included with this article, and experiment | |
| with different graphics modes to get an idea of the possibilities and | |
| limitations of these unusual features. | |
| | Table 1. Where to POKE to Move Parts of the Screen | | | |
| | --- | --- | | |
| | **To Move GR.0 Text Window in:** GR.1 GR.2 GR.3 GR.4 GR.5 GR.6 GR.7 GR.8 | **POKE into PNTR - 4 +** 26 or 27 16 or l7 26 or 27 46 or 47 46 or 47 86 or 87 86 or 87 168 or 169 | | |
| | To move lower part of GR.8, POKE into PNTR - 4 + 100 or 101. | | | |
| | Table 2. Smoothing the Horizontal Motion of the Screen | | | |
| | --- | --- | | |
| | **Graphics Mode** 0 1 2 3 4 5 6 7 8 | **Change Line 70 to FOR LOOP =** X TO X + 39 X TO X + 19 X TO X + 19 X TO X + 9 X TO X + 9 X TO X + 19 X TO X + 19 X TO X + 39 X TO X + 39 | | |
| ### Program 1. Display Using POKE | |
| [Download](https://www.atariarchives.org/c2bag/software/chapter6/P189L1.BAS) P189L1.BAS (Saved BASIC) | |
| [Download](https://www.atariarchives.org/c2bag/software/chapter6/P189L1.LST) / [View](https://www.atariarchives.org/c2bag/displayfile.php?file=chapter6/P189L1.LST) P189L1.LST (Listed BASIC) | |
| ### Program 2. Changing the Pointer to Screen Memory | |
| [Download](https://www.atariarchives.org/c2bag/software/chapter6/P189L2.BAS) P189L2.BAS (Saved BASIC) | |
| [Download](https://www.atariarchives.org/c2bag/software/chapter6/P189L2.LST) / [View](https://www.atariarchives.org/c2bag/displayfile.php?file=chapter6/P189L2.LST) P189L2.LST (Listed BASIC) | |
| ### Program 3. Moving Horizontally | |
| [Download](https://www.atariarchives.org/c2bag/software/chapter6/P189L3.BAS) P189L3.BAS (Saved BASIC) | |
| [Download](https://www.atariarchives.org/c2bag/software/chapter6/P189L3.LST) / [View](https://www.atariarchives.org/c2bag/displayfile.php?file=chapter6/P189L3.LST) P189L3.LST (Listed BASIC) | |
| ██████╗ ██╗███████╗██████╗ ██╗ █████╗ ██╗ ██╗ ██╗ ██╗███████╗████████╗ | |
| ██╔══██╗██║██╔════╝██╔══██╗██║ ██╔══██╗╚██╗ ██╔╝ ██║ ██║██╔════╝╚══██╔══╝ | |
| ██║ ██║██║███████╗██████╔╝██║ ███████║ ╚████╔╝ ██║ ██║███████╗ ██║ | |
| ██║ ██║██║╚════██║██╔═══╝ ██║ ██╔══██║ ╚██╔╝ ██║ ██║╚════██║ ██║ | |
| ██████╔╝██║███████║██║ ███████╗██║ ██║ ██║ ███████╗██║███████║ ██║ | |
| ╚═════╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝╚══════╝ ╚═╝ | |
| ██╗███╗ ██╗████████╗███████╗██████╗ ██████╗ ██╗ ██╗██████╗ ████████╗███████╗ | |
| ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔════╝ | |
| ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██████╔╝██║ ██║██████╔╝ ██║ ███████╗ | |
| ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔══██╗██║ ██║██╔═══╝ ██║ ╚════██║ | |
| ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║╚██████╔╝██║ ██║ ███████║ | |
| ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝ | |
| # Atari 8-bit Display List Interrupts: A Complete(ish) Tutorial[¶](#atari-8-bit-display-list-interrupts-a-complete-ish-tutorial "Permalink to this headline") | |
| **Revision 8, updated 28 Dec 2019** | |
| This is a tutorial on Display List Interrupts (DLIs) for the Atari 8-bit series | |
| of computers. In a nutshell, DLIs provide a way to notify your program when a | |
| particular scan line is reached, allowing you to make changes mid-screen. | |
| No prior knowledge of DLIs is necessary before reading this tutorial. However, | |
| DLIs are an advanced programming technique in the sense that they require | |
| knowledge of 6502 assembly language, so this tutorial is going to assume that | |
| you are comfortable with that. | |
| All the examples here are assembled using the MAC/65-compatible assembler | |
| [ATasm](https://atari.miribilist.com/atasm/index.html) (and more specifically | |
| to this tutorial, the version built-in to [Omnivore](https://github.com/robmcmullen/omnivore)). | |
| Note | |
| All source code and XEX files are available in the [dli\_tutorial source code repository](https://github.com/playermissile/dli_tutorial) on github. | |
| Note | |
| This tutorial is Copyright © 2019 and licensed under the [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), except for the *assembly language source code* (both in this tutorial and in the repository), which is placed in the public domain via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). | |
| Before diving into DLIs, it is helpful to understand that they are | |
| very | |
| accurately named: Display List Interrupts literally interrupt the | |
| display list | |
| – they cause an event that is processed by your program as the computer | |
| is in | |
| the middle of drawing the screen. So it is necessary to understand what | |
| display | |
| lists are before understanding what it means to interrupt one, and even | |
| before that we must understand how the Atari uses the display list to | |
| generate the images shown on the screen. | |
| See also | |
| Here are some resources for learning more about display list interrupts: | |
| * [De Re Atari, Chapter 5](https://www.atariarchives.org/dere/chapt05.php) | |
| * [Yaron Nir’s tutorial using cc65](https://atariage.com/forums/topic/291991-cc65-writing-a-dli-tutorial/) | |
| ## Displays: A Tiny Overview of How TVs Work[¶](#displays-a-tiny-overview-of-how-tvs-work "Permalink to this headline") | |
| A TV screen is drawn by an electron beam tracing a path starting above the | |
| visible area, and drawing successive horizontal lines as the beam moves down | |
| the screen. Each line is drawn from left-to-right (as you look at the TV | |
| screen) and when it reaches the right hand side of the screen, the horizontal | |
| retrace starts where the beam is turned off and moved down to the next scan | |
| line below whereupon the beam is turned back on and the next line draws. When | |
| the full frame has been drawn, the beam is turned off again and the vertical | |
| retrace starts (starting the vertical blank interval). Once the beam is | |
| repositioned to the top leftmost position, the vertical blank interval ends, | |
| the beam is turned back on, and the next frame is started. | |
| On NTSC systems, the Atari draws 262 scan lines per frame, 60 times per second. | |
| On PAL systems it draws 312 scan lines per frame, 50 times per second. In | |
| either system, it draws scan lines from the top down, and left to right within | |
| a scan line. | |
| %20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/electron-beam.png) | |
| This simplified description is the mental model we will use to describe the | |
| video drawing process. Real TVs are much more complicated, but for the | |
| purposes of this tutorial are not important. The Atari was constrained to | |
| produce images that rendered on the displays of the time, but the details of | |
| how each type of display works (e.g. interlaced TV vs progressive scan | |
| monitor) doesn’t affect the signal output by the Atari. | |
| One detail of color production is worth mentioning: a unit called the | |
| color clock, which is the smallest portion of a scan line that can be | |
| displayed with an arbitrary color. There are 228 color clocks per scan line, | |
| of which about 160 were typically visible on a cathode-ray TV display in the | |
| 1970s when the Atari was developed. This corresponds to the 160 pixel | |
| horizontal resolution of Antic Modes B through E in the standard width | |
| playfield. Antic Mode F (Graphics 8 in BASIC) has 320 addressable pixels, | |
| corresponding to half a color clock, and only artifacting color is available. | |
| Color clocks also form the basis for the operating speed of the entire | |
| machine. For NTSC, the speed was chosen based on the use of a commonly | |
| available hardware component in use for TV displays, called an NTSC | |
| oscillating crystal. This component generates a pulse with a frequency of | |
| 14.31818 MHz. This frequency was then divided by eight to produce the | |
| 1.7897725 MHz clock at which the 6502 runs. By defining one CPU cycle to | |
| correspond to two color clocks, means there are 114 machine cycles per scan | |
| line. 262 scan lines per frame results in 29868 machine cycles every frame. | |
| And running at 1.7897725 Mhz means there are 1789772.5 machine cycles | |
| happening every second, which produces a frame rate of 59.92 Hz which can be | |
| displayed on a TV (even if it does not exactly sync up with broadcast NTSC). | |
| PAL systems produce the same 228 color clocks and 114 machine cycles per line, | |
| but display 312 scan lines. This results in 35568 cycles per frame. The PAL | |
| crystal oscillates with a frequency of 14.18757 MHz, divided by 8 to produce a | |
| CPU frequency of 1.77344625 Mhz, and 35568 cycles per frame produces a frame | |
| rate of 49.86 Hz; again, not syncing exactly with broadcast PAL but within | |
| tolerances to be displayed. | |
| See also | |
| * [All About Video Fields](https://lurkertech.com/lg/fields/) | |
| * [NTSC Demystified](https://sagargv.blogspot.com/2011/04/ntsc-demystified-part-1-b-video-and.html), (*haha*), a very long series of blog posts describing NTSC encoding | |
| * Obligatory link to the [NTSC article on Wikipedia](https://en.wikipedia.org/wiki/NTSC) | |
| * [Composite artifact colors](https://en.wikipedia.org/wiki/Composite_artifact_colors) article on Wikipedia | |
| * Section 4.2 in the [Altirra Hardware Reference Manual (PDF)](http://www.virtualdub.org/downloads/Altirra%20Hardware%20Reference%20Manual.pdf) for much more technical detail and far, far less hand-waving. | |
| * Discussion on NTSC pixel clocks and timing at [retrocomputing.stackexchange.com](https://retrocomputing.stackexchange.com/a/2206/6847) | |
| ## Display Lists: How the Atari Generates the Display[¶](#display-lists-how-the-atari-generates-the-display "Permalink to this headline") | |
| ANTIC is the special coprocessor that handles screen drawing for the Atari | |
| computers. It is tightly coupled with the 6502 processor, and in fact can be | |
| thought of as being the driver of the 6502 because the ANTIC can halt the 6502 | |
| when needed. Since only one chip can read memory at any time, ANTIC needs to | |
| halt the 6502 when it needs access to memory, so this Direct Memory Access | |
| (DMA) can cause 6502 instructions to appear to take more cycles than documented | |
| in a 6502 reference. In fact, the amount of time ANTIC “steals” will depend on | |
| many factors: the graphics mode, player/missiles being used, playfield size, | |
| and more. | |
| Since there are 228 color clocks and 114 machine cycles per scan line, this | |
| means that in one machine cycle, two color clocks are drawn on the screen. A | |
| typical machine instruction might take 5 machine cycles, so 10 color clocks | |
| could pass in the time to process a single instruction! This means we don’t | |
| have much time per scan line, so DLIs that attempt to change graphics in the | |
| middle of a line will have to be well optimized. | |
| It also means the 6502 is too slow to draw the screen itself, and this is | |
| where ANTIC’s special “instruction set” comes in. You program the ANTIC | |
| coprocessor using a display list, and ANTIC takes care of building the screen | |
| scan line by scan line, without any more intervention from the 6502 code. | |
| (Unless you ask for intervention! And that’s what a DLI is.) | |
| The display list is the special sequence of bytes that ANTIC interprets as a | |
| list of instruction. Each instruction causes ANTIC to draw a certain number of | |
| scan lines in a particular way. A DLI can be set on any ANTIC instruction. | |
| ANTIC supports display lists that produce at most 240 scan lines (even on PAL | |
| systems where many more scan lines are available), and the vertical blank | |
| interval always starts after 248 scan lines. When drawing scan lines, ANTIC | |
| skips 8 scan lines at to top of the display, so the output from the display | |
| list starts at the 9th scan line. A standard display list starts with 24 blank | |
| lines and 192 scan lines of display data, meaning that the TV will see 32 blank | |
| lines (the 8 automatically skipped plus the 24 in a standard display list) | |
| followed by 192 scan lines of display, then 24 blank lines, and finally the | |
| vertical blank that consumes the remaining 14 scan lines on NTSC (or 64 on | |
| PAL). | |
| ### Display List Instruction Set[¶](#display-list-instruction-set "Permalink to this headline") | |
| An ANTIC display list instruction consists of 1 byte with an optional 2 byte | |
| address. There are 4 types of instructions: blank lines, text modes, bitmap | |
| graphic modes, and jump instructions. Instructions are encoded into the byte | |
| using a bitmask where low 4 bits encode the instruction type and the high 4 | |
| bits encode the flags that affect that instruction: | |
| > | | | | | | | | | | |
| > | --- | --- | --- | --- | --- | --- | --- | --- | | |
| > | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | |
| > | DLI | LMS | VSCROLL | HSCROLL | Mode | | | | | |
| The 4 flags are: | |
| > * DLI (`$80`): enable a display list interrupt when processing this instruction | |
| > * LMS (`$40`): | |
| > trigger a Load Memory Scan, changing where ANTIC looks for screen data, | |
| > and requires an additional 2 byte address immediately following this | |
| > instruction byte. | |
| > * VSCROLL (`$20`): enable vertical scrolling for this mode line | |
| > * HSCROLL (`$10`): enable horizontal scrolling for this mode line | |
| There are 6 text modes and 8 bitmap graphic modes for a total of 14 modes, and | |
| are encoded into low 4 bits using values as shown in these tables: | |
| Text Modes[¶](#id6 "Permalink to this table") | |
| | | | | | | | | |
| | --- | --- | --- | --- | --- | --- | | |
| | Mode | Decimal | BASIC Mode | Description | Scan Lines | Colors | | |
| | 2 | 02 | 0 | 40 x 24 | 8 | 2 | | |
| | 3 | 03 | n/a | 40 x 19 | 10 | 2 | | |
| | 4 | 04 | n/a | 40 x 24 | 8 | 4 | | |
| | 5 | 05 | n/a | 40 x 12 | 16 | 4 | | |
| | 6 | 06 | 1 | 20 x 24 | 8 | 5 | | |
| | 7 | 07 | 2 | 20 x 12 | 16 | 5 | | |
| Bitmap Modes[¶](#id7 "Permalink to this table") | |
| | | | | | | | | |
| | --- | --- | --- | --- | --- | --- | | |
| | Mode | Decimal | BASIC Mode | Description | Scan Lines | Colors | | |
| | 8 | 08 | 3 | 40 x 24 | 8 | 4 | | |
| | 9 | 09 | 4 | 80 x 48 | 4 | 2 | | |
| | A | 10 | 5 | 80 x 48 | 4 | 4 | | |
| | B | 11 | 6 | 160 x 96 | 2 | 2 | | |
| | C | 12 | n/a | 160 x 192 | 1 | 2 | | |
| | D | 13 | 7 | 160 x 96 | 2 | 4 | | |
| | E | 14 | n/a | 160 x 192 | 1 | 4 | | |
| | F | 15 | 8 | 320 x 192 | 1 | 2\* | | |
| [\*](#id1)mode F is also used as the basis for the GTIA modes (BASIC Graphics modes 9, | |
| 10, & 11), but this is a topic outside the scope of this tutorial. | |
| Blank lines are encoded as a mode value of zero, the bits 6, 5, and 4 taking | |
| the meaning of the number of blank lines rather than LMS, VSCROLL, and | |
| HSCROLL. Note that the DLI bit is still available on blank lines, as bit 7 is | |
| not co-opted by the blank line instruction. | |
| Blank Line Instructions[¶](#id8 "Permalink to this table") | |
| | | | | | |
| | --- | --- | --- | | |
| | Hex | Decimal | Blank Lines | | |
| | 0 | 0 | 1 | | |
| | 10 | 16 | 2 | | |
| | 20 | 32 | 3 | | |
| | 30 | 48 | 4 | | |
| | 40 | 64 | 5 | | |
| | 50 | 80 | 6 | | |
| | 60 | 96 | 7 | | |
| | 70 | 112 | 8 | | |
| Jumps provide the capability to split a display list into multiple parts in | |
| different memory locations. They are encoded using a mode value of one, and | |
| require an additional 2 byte address where ANTIC will look for the next display | |
| list instruction. If bit 6 is also set, it becomes the Jump and wait for Vertical | |
| Blank (JVB) instruction, which is how ANTIC knows that the display list is | |
| finished. The DLI bit may also be set on a jump instruction, but if set on the | |
| JVB instruction it triggers a DLI on every scan line from there until the | |
| vertical blank starts on the 249th scan line. | |
| Note | |
| Apart from the `$41` JVB instruction, splitting display lists using other | |
| jumps like the `$01` instruction is not common. It has a side-effect of | |
| producing a single blank line in the display list. | |
| The typical method to change the currently active display list is to change the | |
| address stored at `SDLSTL` (in low byte/high byte format in addresses | |
| `$230` and `$231`). At the next vertical blank, the hardware display list | |
| at `DLISTL` (`$d402` and `$d403`) will be updated with the values stored | |
| here and the screen drawing will commence using the new display list. | |
| See also | |
| More resources about display lists are available: | |
| * <https://www.atariarchives.org/mapping/memorymap.php#560,561> | |
| * <https://www.atariarchives.org/mapping/appendix8.php> | |
| ### A Sample Display List[¶](#a-sample-display-list "Permalink to this headline") | |
| Here is a display list that contains different text modes mixed in a single screen. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/sample_display_list.png)](https://playermissile.com/_images/sample_display_list.png) | |
| * **Source Code:** [sample\_display\_list.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/sample_display_list.s) | |
| * **Executable:** [sample\_display\_list.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/sample_display_list.xex) | |
| ``` | |
| dlist .byte $70,$70,$70 ; 24 blank lines | |
| .byte $46,$00,$40 ; Mode 6 + LMS, setting screen memory to $4000 | |
| .byte 6 ; Mode 6 | |
| .byte $70 ; 8 blank lines | |
| .byte 7,7,7,7,7 ; 5 lines of Mode 7 | |
| .byte $70 ; 8 blank lines | |
| .byte 2 ; single line of Mode 2 | |
| .byte $70,$70,$70 ; 24 blank lines | |
| .byte 2,4 ; Mode 2 followed by mode 4 | |
| .byte $70 ; 8 blank lines | |
| .byte 2,5 ; Mode 2 followed by mode 5 | |
| .byte $41,<dlist,>dlist ; JVB, restart same display list on next frame | |
| ``` | |
| ### Cycle Stealing by ANTIC[¶](#cycle-stealing-by-antic "Permalink to this headline") | |
| The ANTIC coprocessor needs to access memory to perform its functions, and | |
| since the 6502 and ANTIC can’t both access at once, ANTIC will pause execution | |
| of the 6502 when it needs to read memory. It happens at specific points within | |
| the 114 cycles of each scan line, but where it happens (and how many times the | |
| 6502 gets paused during the scan line) depends on the graphics mode. | |
| For overhead, ANTIC will typically steal 3 cycles to read the display list, 5 | |
| cycles if player/missile graphics are enabled, and 9 cycles for memory | |
| refreshing. Scrolling requires additional cycle stealing because ANTIC needs | |
| to fetch more memory. | |
| Bitmapped modes (modes 8 - F) have cycles stolen corresponding to the number | |
| of bytes per line used in that mode. For example, mode E will use an | |
| additional 40 cycles, so in the context of writing a DLI for a game, the | |
| typical number of stolen cycles could be 57 out of the 114 cycles per scan | |
| line: 17 cycles for ANTIC overhead and 40 for the number of bytes per line. | |
| Text modes require additional cycles over bitmapped graphics modes, because | |
| ANTIC must fetch the font glyphs in addition to its other work. The first scan | |
| line of a font mode is almost entirely used by ANTIC and only a small number | |
| of cycles is available to the 6502. For normal 40-byte wide playfields, the | |
| first line of ANTIC modes 2 through 5 will yield at most about 30 cycles and | |
| subsequent lines about 60 cycles per scan line. | |
| About the worst-case scenario is one of the best modes for games: ANTIC mode | |
| 4. This text mode, combined with scrolling and player/missile graphics and can | |
| reduce the available cycles to fewer than 10 on the first line and about 50 on | |
| subsequent lines! | |
| See also | |
| Section 4.14 in the | |
| [`Altirra Hardware | |
| Reference Manual | |
| (PDF)<http://www.virtualdub.org/downloads/ | |
| Altirra%20Hardware%20Reference%20Manual.pdf>`\_](#id10) | |
| contains tables depicting exactly which cycles are stolen by ANTIC for | |
| each mode. | |
| ### Restrictions[¶](#restrictions "Permalink to this headline") | |
| > * display lists cannot cross a 1K boundary | |
| > * display list data cannot cross a 4k boundary, so you must use a display list command with the `LMS` bit if using a bitmapped display mode that will result in a larger memory usage | |
| ## Display List Interrupts: A Crash Course[¶](#display-list-interrupts-a-crash-course "Permalink to this headline") | |
| DLIs are non-maskable interrupts (NMIs), meaning they cannot be ignored. When | |
| an NMI occurs, the 6502 jumps to the address stored at `$fffa`, which points | |
| to an OS routine that checks the type of interrupt (either a DLI or a VBI) and | |
| vectors through the appropriate user vector. The NMI handler takes care of | |
| saving the processor status register and sets the interrupt flag, but *does | |
| not* save any processor registers. The user routine is responsible for saving | |
| any registers that it uses, restoring them when it is done using them, and must | |
| exit using the `RTI` instruction. | |
| Display list interrupts are not enabled by default. To use a DLI, the address | |
| vector at `VDSLST` (`$200` and `$201`) must be set to your routine, and | |
| then they must be enabled through a write to `NMIEN` at `$d40e`. | |
| Warning | |
| You must set the address of your DLI before enabling them, otherwise the DLI | |
| could be called and use whatever address is stored at `$200`. | |
| This initialization code can look like the following, where the constants | |
| `NMIEN_VBI` and `NMIEN_DLI` are defined as `$40` and `$80`, | |
| respectively, in hardware.s in the sample repository. Since `NMIEN` also | |
| controls the vertical blank interrupt, you must make sure that the VBI enable | |
| flag is also set. | |
| ``` | |
| ; load display list interrupt address | |
| lda #<dli | |
| sta VDSLST | |
| lda #>dli | |
| sta VDSLST+1 | |
| ; activate display list interrupt and vertical blank interrupt | |
| lda #NMIEN_DLI | NMIEN_VBI | |
| sta NMIEN | |
| ``` | |
| If your program has multiple DLIs, it may be necessary to set your DLIs in a | |
| vertical blank interrupt to guarantee that ANTIC will process them in the | |
| right order. Outside the VBI, your code could be running at an arbitrary scan | |
| line, perhaps between display list instructions that have their DLI bits set. | |
| In Yaron Nir’s tutorial a different technique is used, one not requiring a | |
| vertical blank interrupt but instead using the `RTCLOK` 3-byte zero page | |
| variable to instead infer that a VBI has *just* occurred. The last of the | |
| bytes, location `$14`, is incremented every vertical blank, so that | |
| technique is to wait until location `$14` changes, then set `NMIEN`: | |
| ``` | |
| lda RTCLOK+2 | |
| ?loop cmp RTCLOK+2 ; will be equal until incremented in VB | |
| beq ?loop | |
| ; activate display list interrupt and vertical blank interrupt | |
| lda #NMIEN_DLI | NMIEN_VBI | |
| sta NMIEN | |
| ``` | |
| ### Hardware & Shadow Registers[¶](#hardware-shadow-registers "Permalink to this headline") | |
| The Atari is a memory-mapped system, where hardware devices like the ANTIC and | |
| GTIA chips are *mapped* to locations in memory and data is passed back and | |
| forth by reading or writing to specific addresses. They are usually either | |
| read-only or write-only, and many times an address is used for wildly | |
| different features depending on whether the address is read from or written | |
| to. | |
| Some of these hardware locations also have *shadow* registers in low RAM | |
| (typically page 2) that are labeled as performing the same function as a | |
| hardware register, with two important differences. | |
| First, they can be both read and written to, so (assuming you always use the | |
| shadow register to update the hardware register) it is possible to find out | |
| the current state of a hardware register by reading its shadow. | |
| Second, the hardware register is only updated **once every vertical blank** by | |
| an operating system routine that copies the shadow value to its hardware | |
| counterpart. Note that it does not happen the other way around, so changing a | |
| hardware register *does not* update a shadow register. | |
| The shadow registers are a convenience for development in higher level | |
| languages like BASIC where speed is not paramount. But code within a DLI must | |
| use hardware registers directly to affect change on a scan line. | |
| The shadow registers can still be useful in DLI development, in that they will | |
| automatically reset the hardware registers to the values in the shadow | |
| registers every vertical blank. This can be used to reset features like | |
| graphics colors and the character set address for the top of the screen at the | |
| next frame. | |
| Note | |
| This only works if the operating system’s immediate vertical blank | |
| routine has not been replaced (i.e. you are only using the deferred | |
| vertical blank `VVBLKD` at `$224` and haven’t replaced the immediate vertical blank rountine `VVBLKI` at `$222`). | |
| Some hardware registers have no shadows, like player position and size, so | |
| your own code (in the deferred VBI or the final DLI) must reset these to their | |
| correct values for the top of the screen. | |
| Some Useful Shadow Registers[¶](#id9 "Permalink to this table") | |
| | | | | | | | |
| | --- | --- | --- | --- | --- | | |
| | Shadow | Hex | Hardware | Hex | Description | | |
| | GPRIOR | 26f | PRIOR | d01b | Player/playfield priority selection register | | |
| | PCOLR0 | 2c0 | COLPM0 | d012 | Color of player/missile 0 | | |
| | PCOLR1 | 2c1 | COLPM1 | d013 | Color of player/missile 1 | | |
| | PCOLR2 | 2c2 | COLPM2 | d014 | Color of player/missile 2 | | |
| | PCOLR3 | 2c3 | COLPM3 | d015 | Color of player/missile 3 | | |
| | COLOR0 | 2c4 | COLPF0 | d016 | Color of playfield 0 | | |
| | COLOR1 | 2c5 | COLPF1 | d017 | Color of playfield 1 | | |
| | COLOR2 | 2c6 | COLPF2 | d018 | Color of playfield 2 | | |
| | COLOR3 | 2c7 | COLPF3 | d019 | Color of playfield 3 | | |
| | COLOR4 | 2c8 | COLBK | d01a | Background color | | |
| | CHACT | 2f3 | CHACTL | d401 | Character mode (inverse, upside-down characters) | | |
| | CHBAS | 2f4 | CHBASE | d409 | Character base (page number of font) | | |
| ## Basic Display List Interrupts[¶](#basic-display-list-interrupts "Permalink to this headline") | |
| ### Our First Display List[¶](#our-first-display-list "Permalink to this headline") | |
| A common use of display lists is to change colors in the middle of the | |
| screen. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/first_dli.gif)](https://playermissile.com/_images/first_dli.gif) | |
| * **Source Code:** [first\_dli.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/first_dli.s) | |
| * **Executable:** [first\_dli.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/first_dli.xex) | |
| Here is our first display list interrupt: | |
| ``` | |
| dli pha ; only using A register, so save old value to the stack | |
| lda #$7a ; new background color | |
| sta COLBK ; store it in the hardware register | |
| pla ; restore the A register | |
| rti ; always end DLI with RTI! | |
| ``` | |
| This is all the code it takes to change the color of the background. The | |
| obvious effect is the flickering line in the background, which we will solve | |
| in the next section. | |
| Examining the code shows the boilerplate discussed [above](#dli-crash-course) where DLIs always end with the `RTI` instruction | |
| and any registers used must be saved before your code changes them, and | |
| restored upon exit. | |
| The work performed in the interrupt is just two instructions: a load of a | |
| color value and a store where it puts it in the *hardware* register for the | |
| background color. Again, as noted [above](#hardware-shadow-registers), | |
| hardware registers must be used in DLIs, not the shadow registers as shadow | |
| registers are ignored until the vertical blank. | |
| ### WSYNC: How to Avoid Flickering[¶](#wsync-how-to-avoid-flickering "Permalink to this headline") | |
| The Atari provides a way to sync with a scan line to avoid the flickering effect | |
| of the previous example. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/first_dli_with_wsync.png)](https://playermissile.com/_images/first_dli_with_wsync.png) | |
| * **Source Code:** [first\_dli\_with\_wsync.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/first_dli_with_wsync.s) | |
| * **Executable:** [first\_dli\_with\_wsync.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/first_dli_with_wsync.xex) | |
| The flickering is avoided by saving some value (any value, the bit pattern is | |
| not important) to the `WSYNC` memory location at `$d40a`. This causes the | |
| 6502 to stop processing instructions until the electron beam nears the end of | |
| the scan line, at which point the 6502 will resume executing instructions. | |
| Because the electron beam is usually off-screen at this point, it is safe to | |
| change color registers for at least the next several instructions without | |
| artifacts appearing on screen. | |
| ``` | |
| dli pha ; only using A register, so save old value to the stack | |
| lda #$7a ; new background color | |
| sta WSYNC ; any value saved to WSYNC will trigger the pause | |
| sta COLBK ; store it in the hardware register | |
| pla ; restore the A register | |
| rti ; always end DLI with RTI! | |
| ``` | |
| Note | |
| `WSYNC` (wait for horizontal blank) usually restarts the 6502 on or | |
| about cycle 105 out of 114, but there are cases that can delay that. See the | |
| Altirra Hardware Reference Manual for more information. | |
| ### A DLI Can Affect Many Scan Lines[¶](#a-dli-can-affect-many-scan-lines "Permalink to this headline") | |
| This example shows that a single DLI affect multiple scan lines, even crossing | |
| into subsequent ANTIC mode 4 lines in the display list. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/rainbow_wsync.png)](https://playermissile.com/_images/rainbow_wsync.png) | |
| * **Source Code:** [rainbow\_wsync.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/rainbow_wsync.s) | |
| * **Executable:** [rainbow\_wsync.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/rainbow_wsync.xex) | |
| DLIs can really be thought of as a way for your program to be told when a | |
| certain display list instruction is reached. Apart from the setup and teardown of | |
| the DLI subroutine itself and some timing limitations discussed in the next | |
| section, arbitrary amounts of code can be executed in a DLI. | |
| Note | |
| Author’s note: thinking that DLIs had to be short was a great source of | |
| confusion to me when trying to figure out how rainbow effects were | |
| generated. My thinking was that DLIs could only affect a single line, and | |
| for instance I could not figure out how to get a color change in the middle | |
| of a text mode. I don’t know why I thought that something bad would happen | |
| if a DLI went long, but I did. | |
| This DLI changes background colors 16 times, where each color change lasts 2 | |
| scan lines. So 32 scan lines means that it covers 4 display list entries of | |
| ANTIC mode 4. | |
| ``` | |
| dli pha ; save A & X registers to stack | |
| txa | |
| pha | |
| ldx #16 ; make 16 color changes | |
| lda #$a ; initial color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| ?loop sta COLBK ; change background color | |
| clc | |
| adc #$10 ; change color value, luminance remains the same | |
| dex ; update iteration count | |
| sta WSYNC ; make it the color change last ... | |
| sta WSYNC ; for two scan lines | |
| bne ?loop ; sta doesn't affect flags so this still checks result of dex | |
| lda #$00 ; reset background color to black | |
| sta COLBK | |
| pla ; restore X & A registers from stack | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| ## Display List Interrupts Getting Interrupted[¶](#display-list-interrupts-getting-interrupted "Permalink to this headline") | |
| Because DLIs are non-maskable interrupts and NMIs can’t be blocked, a DLI will | |
| interrupt whatever is happening, including another DLI. To summarize: | |
| > * DLIs can be interrupted by other DLIs | |
| > * DLIs can be interrupted by the vertical blank | |
| > * The vertical blank can be interrupted by a DLI | |
| > * a DLI on a JVB instruction will cause interrupts on every scan line until the vertical blank | |
| ### DLI Interrupting Another DLI[¶](#dli-interrupting-another-dli "Permalink to this headline") | |
| Here’s a similar DLI to the above, except it changes the luminance value | |
| instead of the color value to make the effect easier to see. It starts with a | |
| bright pink and gets dimmer down to a dark red after 32 scan lines. But this | |
| time, the display list has *two* mode 4 lines that have the DLI bit set, the | |
| 2nd and 4th: | |
| ``` | |
| dlist .byte $70,$70,$70 | |
| .byte $44,$00,$40 | |
| .byte $84 ; first DLI triggered on last scan line | |
| .byte 4 | |
| .byte $84 ; second DLI triggered on last scan line | |
| .byte 4,4,4,4,4,4,4,4 | |
| .byte 4,4,4,4,4,4,4,4 | |
| .byte 4,4,4,4 | |
| .byte $41,<dlist,>dlist | |
| ``` | |
| The first DLI takes 32 scan lines to complete, but it is only 16 scan lines | |
| through its operation when the second DLI hits: | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/dli_interrupting_dli.png)](https://playermissile.com/_images/dli_interrupting_dli.png) | |
| * **Source Code:** [dli\_interrupting\_dli.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/dli_interrupting_dli.s) | |
| * **Executable:** [dli\_interrupting\_dli.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/dli_interrupting_dli.xex) | |
| When a DLI is interrupted, its state is saved just as if a normal program was | |
| interrupted. The interrupting code is then executed, and upon its completion, | |
| the control returns to the DLI at the point where it left off. But at this | |
| point, due to the interrupting event, the restored DLI will be resumed some | |
| number of scan lines below where it was interrupted, likely resulting in | |
| unplanned behavior. | |
| ``` | |
| dli pha ; save A & X registers to stack | |
| txa | |
| pha | |
| ldx #16 ; make 16 color changes | |
| lda #$5f ; initial bright pink color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| ?loop sta COLBK ; change background color | |
| sec | |
| sbc #1 ; make dimmer by decrementing luminance value | |
| dex ; update iteration count | |
| sta WSYNC ; make it the color change last ... | |
| sta WSYNC ; for two scan lines | |
| bne ?loop ; sta doesn't affect processor flags so we are still checking result of dex | |
| lda #$00 ; reset background color to black | |
| sta COLBK | |
| pla ; restore X & A registers from stack | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| Because the display list vector `VDLSTL` is not changed, the same code will | |
| be called each time an interrupt occurs. | |
| The first DLI hits and starts with a bright background color on the first scan | |
| line of the third line of text. But because this display list takes a long | |
| time, the second DLI on the 4th text line gets triggered before the first DLI | |
| has hit its `RTI` instruction. ANTIC interrupts the first DLI and starts the | |
| 2nd DLI anyway. This effect is visible in the 5th line of text: the background | |
| color is bright again. | |
| But notice another artifact: the effect on the 5th line of text isn’t on its | |
| first scan line, but its second: | |
| %20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/dli_interrupting_dli_detail.png) | |
| This is due to the fact that a WSYNC was called on the previous scan line, but | |
| the interrupt happened as well. The interrupt takes some cycles to begin, and | |
| by the time that happened **and** ANTIC stole all of its cycles to set up the | |
| text mode line, there weren’t enough cycles left for the first `WSYNC` in the | |
| DLI code to happen on the same scan line. This forces that `WSYNC` to happen | |
| on the next line, causing the delay and the appearance of a 3rd scan line of the | |
| same color before the second DLI starts its color cycling. | |
| The second DLI completes and performs its `RTI`, but then it returns control | |
| to the first DLI, which is already halfway done with its color cycling. When it | |
| resumes control, it is in 9th line of text on the screen, so it has four more | |
| color changes before it hits its own `RTI`. | |
| ### Emulator Differences[¶](#emulator-differences "Permalink to this headline") | |
| The DLI interrupting another DLI is clearly an edge case, and edge cases are | |
| always good stress tests for emulators. A difference is clearly visible below | |
| when comparing a zoomed in portion of the display generated by the Altirra | |
| emulator as compared to the atari800 emulator (standalone or as embedded in | |
| Omnivore, they are the same code and produce the same result): | |
| %20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/emulator-differences.png) | |
| Notice how Altirra gets the color from the first DLI for two scan lines, 64 | |
| and 65, before the correct color appears on scan line 66. The output from | |
| Altirra shows that the NMI doesn’t happen until between scan line 63 and 64. | |
| But clearly, the `sta COLBK` at scan line 63 is taking effect on scan line | |
| 64, because scan line 64 has the background color `$57`. It appears the | |
| store of `$5f` on scan line 65, started on cycle 1 of that line, isn’t | |
| actually executed until much, much later since the `sec` doesn’t begin until | |
| cycle 108. This puts that color change in the horizontal blank period of scan | |
| line 65, which would seem to explain why Altirra shows two scan lines with the | |
| background color from the first DLI. | |
| This is the CPU history from the Altirra emulator: | |
| ``` | |
| 60: 3 | A=58 X=09 Y=00 ( I C) | 3030: 8D 0A D4 STA WSYNC | |
| 60: 7 | A=58 X=09 Y=00 ( I C) | 3033: 8D 0A D4 STA WSYNC | |
| 60:108 | A=58 X=09 Y=00 ( I C) | 3036: D0 F1 BNE $3029 | |
| 61:107 | A=58 X=09 Y=00 ( I C) | 3029: 8D 1A D0 L3029 STA COLBK | |
| 61:111 | A=58 X=09 Y=00 ( I C) | 302C: 38 SEC | |
| 61:113 | A=58 X=09 Y=00 ( I C) | 302D: E9 01 SBC #$01 | |
| 62: 1 | A=57 X=09 Y=00 ( I C) | 302F: CA DEX | |
| 62: 3 | A=57 X=08 Y=00 ( I C) | 3030: 8D 0A D4 STA WSYNC | |
| 62: 7 | A=57 X=08 Y=00 ( I C) | 3033: 8D 0A D4 STA WSYNC | |
| 62:108 | A=57 X=08 Y=00 ( I C) | 3036: D0 F1 BNE $3029 | |
| 63:107 | A=57 X=08 Y=00 ( I C) | 3029: 8D 1A D0 L3029 STA COLBK | |
| - NMI interrupt (DLI) | |
| 64: 5 | A=57 X=08 Y=00 ( I C) | E791: 2C 0F D4 LE791 BIT NMIST | |
| 64: 11 | A=57 X=08 Y=00 (N I C) | E794: 10 03 BPL $E799 | |
| 64: 13 | A=57 X=08 Y=00 (N I C) | E796: 6C 00 02 JMP (VDSLST) | |
| 64: 19 | A=57 X=08 Y=00 (N I C) | 301F: 48 PHA | |
| 64:102 | A=57 X=08 Y=00 (N I C) | 3020: 8A TXA | |
| 64:104 | A=08 X=08 Y=00 ( I C) | 3021: 48 PHA | |
| 64:107 | A=08 X=08 Y=00 ( I C) | 3022: A2 10 LDX #$10 | |
| 64:109 | A=08 X=10 Y=00 ( I C) | 3024: A9 5F LDA #$5F | |
| 64:111 | A=5F X=10 Y=00 ( I C) | 3026: 8D 0A D4 STA WSYNC | |
| 65: 1 | A=5F X=10 Y=00 ( I C) | 3029: 8D 1A D0 L3029 STA COLBK | |
| 65:108 | A=5F X=10 Y=00 ( I C) | 302C: 38 SEC | |
| 65:110 | A=5F X=10 Y=00 ( I C) | 302D: E9 01 SBC #$01 | |
| 65:112 | A=5E X=10 Y=00 ( I C) | 302F: CA DEX | |
| 66: 0 | A=5E X=0F Y=00 ( I C) | 3030: 8D 0A D4 STA WSYNC | |
| 66: 4 | A=5E X=0F Y=00 ( I C) | 3033: 8D 0A D4 STA WSYNC | |
| 66:108 | A=5E X=0F Y=00 ( I C) | 3036: D0 F1 BNE $3029 | |
| 67:107 | A=5E X=0F Y=00 ( I C) | 3029: 8D 1A D0 L3029 STA COLBK | |
| ``` | |
| The atari800 emulator hits the DLI two instructions earlier than Altirra, | |
| immediately after the two `sta WSYNC` commands (and therefore before the | |
| `sta COLBK` that causes Altirra to have a new color on scan line 64). In the | |
| atari800/Omnivore instruction history below: | |
| ``` | |
| 60 5 | 58 09 25 ---I-C f6 3336 8d 0a d4 sta WSYNC $d40a=58 (was d0) | |
| 60 106 | 58 09 25 ---I-C f6 3339 8d 0a d4 sta WSYNC $d40a=58 (was d0) | |
| 61 106 | 58 09 25 ---I-C f6 333c d0 f1 bne $332f (taken) | |
| 61 109 | 58 09 25 ---I-C f6 332f 8d 1a d0 sta COLBK $d01a=58 (was 0f) | |
| 61 113 | 58 09 25 ---I-C f6 3332 38 sec | |
| 62 1 | 58 09 25 ---I-C f6 3333 e9 01 sbc #$01 A=57 | |
| 62 3 | 57 09 25 ---I-C f6 3335 ca dex X=08 | |
| 62 5 | 57 08 25 ---I-C f6 3336 8d 0a d4 sta WSYNC $d40a=57 (was d0) | |
| 62 106 | 57 08 25 ---I-C f6 3339 8d 0a d4 sta WSYNC $d40a=57 (was d0) | |
| 63 0 | --DLI | |
| 63 106 | 57 08 25 ---I-C f3 c018 2c 0f d4 bit NMIRES $d40f=1c N=1 | |
| 63 110 | 57 08 25 N--I-C f3 c01b 10 03 bpl $c020 (not taken) | |
| 63 112 | 57 08 25 N--I-C f3 c01d 6c 00 02 jmp (VDSLST) ($0200)=$3325 | |
| 64 4 | 57 08 25 N--I-C f3 3325 48 pha $01f3=57 | |
| 64 7 | 57 08 25 N--I-C f2 3326 8a txa A=08 N=0 | |
| 64 9 | 08 08 25 ---I-C f2 3327 48 pha $01f2=08 | |
| 64 12 | 08 08 25 ---I-C f1 3328 a2 10 ldx #$10 X=10 | |
| 64 14 | 08 10 25 ---I-C f1 332a a9 5f lda #$5f A=5f | |
| 64 16 | 5f 10 25 ---I-C f1 332c 8d 0a d4 sta WSYNC $d40a=5f (was d0) | |
| 64 107 | 5f 10 25 ---I-C f1 332f 8d 1a d0 sta COLBK $d01a=5f (was 0f) | |
| 64 111 | 5f 10 25 ---I-C f1 3332 38 sec | |
| 64 113 | 5f 10 25 ---I-C f1 3333 e9 01 sbc #$01 A=5e | |
| 65 1 | 5e 10 25 ---I-C f1 3335 ca dex X=0f | |
| 65 3 | 5e 0f 25 ---I-C f1 3336 8d 0a d4 sta WSYNC $d40a=5e (was d0) | |
| 65 106 | 5e 0f 25 ---I-C f1 3339 8d 0a d4 sta WSYNC $d40a=5e (was d0) | |
| 66 106 | 5e 0f 25 ---I-C f1 333c d0 f1 bne $332f (taken) | |
| 66 109 | 5e 0f 25 ---I-C f1 332f 8d 1a d0 sta COLBK $d01a=5e (was 0f) | |
| ``` | |
| the DLI starts late on scan line 63 as (naively) expected and gets to the | |
| `sta WSYNC` early in scan line 64 while there is still time to hit the `sta | |
| COLBK` while still on scan line 64. This changes scan line 65 to be the | |
| correct background color for the second DLI. | |
| Note | |
| I’m not sure what’s going on with the differences in the WSYNC | |
| behavior between the two emulators. On Altirra, the two WSYNC commands | |
| seem to occur on scan line 62, but their effects aren’t felt | |
| immediately, so perhaps this is what’s causing the DLI to hit on scan | |
| line 64 instead of scan line 63. On atari800, the WSYNC commands cause | |
| their effects to be felt immediately, in the next command. I would | |
| presume that Altirra is closer to what’s going on with real hardware, as | |
| the author of Altirra has written the definitive guide to the internals | |
| of the machine, and Altirra has always been the leader in cycle-exact | |
| emulation. | |
| I think the takeaway from this section is: don’t let your DLI get interrupted | |
| by anything else, or it is likely that you will encounter emulation | |
| differences. | |
| ### VBI Interrupting A DLI[¶](#vbi-interrupting-a-dli "Permalink to this headline") | |
| Here is an example of the vertical blank interrupting a DLI. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/vbi_interrupting_dli.png)](https://playermissile.com/_images/vbi_interrupting_dli.png) | |
| * **Source Code:** [vbi\_interrupting\_dli.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/vbi_interrupting_dli.s) | |
| * **Executable:** [vbi\_interrupting\_dli.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/vbi_interrupting_dli.xex) | |
| The DLI is started at the bottom of the screen, gets interrupted by the VBI, | |
| and picks up again when VBI ends. Even though the electron beam is turned off, | |
| `WSYNC` is still called and performs its delay function when the scan line | |
| is off screen. The resulting image resumes its color cycling background on the | |
| top of the screen, stopping after 128 scan lines even though only a fraction | |
| of those are actually visible on screen. | |
| ### DLI Interrupting A VLI[¶](#dli-interrupting-a-vli "Permalink to this headline") | |
| And for completeness, here is an example of a DLI interrupting the vertical blank. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/dli_interrupting_vbi.png)](https://playermissile.com/_images/dli_interrupting_vbi.png) | |
| * **Source Code:** [dli\_interrupting\_vbi.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/dli_interrupting_vbi.s) | |
| * **Executable:** [dli\_interrupting\_vbi.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/dli_interrupting_vbi.xex) | |
| The vertical blank routine would have to be quite long *and* the DLI set very | |
| early in the display list before this would happen. | |
| Note | |
| In my own game development, I have run into this effect happening | |
| intermittently, where occasionally the VBI runs very long due to some | |
| complicated game logic that happens only under certain conditions. It’s | |
| something to be aware of. | |
| In this example, this DLI is set on the final blank line of the display list, | |
| so the display list begins with these bytes: | |
| ``` | |
| .byte $70,$70,$f0 | |
| ``` | |
| triggering the DLI on scan line 24. The vertical blank has run from scan lines | |
| 248 through 262 on one frame, and through 23 scan lines of the following frame | |
| before getting interrupted by the DLI. | |
| To visualize the processing in the vertical blank, this example changes | |
| background color as fast as it can once the vertical blank starts, up to the | |
| 100th scan line of the generated image. It gets interrupted on scan line 23 for | |
| the DLI. | |
| The DLI is one we’ve seen before, just changing background color with | |
| `WSYNC`. Once it has completed, it returns and the VBI routine picks up where | |
| it left off, changing background color as fast as it can. | |
| ### DLI on the JVB Instruction[¶](#dli-on-the-jvb-instruction "Permalink to this headline") | |
| A DLI on the JVB instruction at the end of the display list is possible, but | |
| has an interesting property: it triggers DLIs on every scan line until the | |
| vertical blank. | |
| If your DLI is not short enough, it will keep getting interrupted by the DLI | |
| on triggered by the next scan line, stacking up interrupts until mercifully | |
| the triggering process is stopped by the vertical blank after 248 scan lines | |
| have been generated. | |
| Note | |
| As each new frame is generated in an emulator, it will enumerate the | |
| scan lines starting from zero. There are 248 scan lines before the | |
| vertical blank, which will be displayed as scan lines 0 - 247. The scan | |
| line labeled 248 will be the first scan line of the vertical blank. | |
| After the vertical blank routine exits, the stacked-up DLI calls will have to | |
| unwind themselves so the most recently interrupted DLI (from scan line 247, | |
| the scan line just before the vertical blank) will resume and execute code | |
| until its `RTI`. This will pop data off the stack and return control to the | |
| DLI that was interrupted on scan line 246, and so-forth until all the | |
| interrupted DLIs have issued their `RTI` instructions. | |
| On a standard length display list that generates 24 blank lines followed by | |
| 192 output lines, the JVB instruction will be on scan line 224. Since the JVB | |
| technically generates a single blank line in the display list, the DLI will | |
| also be triggered on scan line 224. This case would produce 24 DLIs before the | |
| vertical blank. | |
| ## DLIs in a Nutshell[¶](#dlis-in-a-nutshell "Permalink to this headline") | |
| DLIs provide you with a way to notify your program at a particular vertical | |
| location on the screen. They pause (or interrupt) the normal flow of program | |
| code, save the state of the machine, call your DLI subroutine, and restore the | |
| state of the computer before returning control to the code that was | |
| interrupted. | |
| Warning | |
| Here are the requirements for successful use of DLIs: | |
| * your DLI routine must save any registers it clobbers | |
| * restore any registers you save before exiting | |
| * exit with an `RTI` | |
| * use `WSYNC` if necessary | |
| * be aware of cycles stolen by ANTIC: you could have only 60 cycles | |
| per scan line in higher resolution graphics modes, and as few as 10 (**!**) on the first line of text modes | |
| * store the address of your routine in `VDSLST` before enabling DLIs with `NMIEN` | |
| * guard against the DLI itself being interrupted | |
| Note that nowhere in that list was the requirement that the DLI be short. It | |
| doesn’t have to be, and in fact DLIs that span multiple scan lines are similar | |
| to kernels used in Atari 2600 programming. The difference is that ANTIC steals | |
| cycles depending on a bunch of factors, so the total cycle counting approach | |
| (or [Racing the Beam](https://mitpress.mit.edu/books/racing-beam)) is usually | |
| not possible. | |
| However, most DLIs that you will run across in the wild *are* short, because | |
| they typically don’t do a lot of calculations. Most of the setup work will | |
| generally be done outside of the DLI and the DLI itself just handles the result | |
| of that work. | |
| %20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/Atari_logo_hr.png) | |
| ## Advanced DLI Examples[¶](#advanced-dli-examples "Permalink to this headline") | |
| The following examples are available in both source code form and as XEX files | |
| at the [dli\_tutorial source code repository](https://github.com/playermissile/dli_tutorial) on github. | |
| They are coded using MAC/65 assembler syntax, but very few assembler-specific | |
| features are actually used, so they should be trivially ported to other | |
| assemblers. | |
| To get a copy of all the examples and source code, you can download and | |
| install [git](https://git-scm.com/) for your platform. Then open a command | |
| line prompt on your computer and enter the command: | |
| ``` | |
| git clone https://github.com/playermissile/dli_tutorial.git | |
| ``` | |
| to download the complete repository. | |
| You can also download individual assembly source and XEX files from links in | |
| each section. | |
| In an attempt to de-clutter the examples as much as possible, most of the | |
| boilerplate code (for initialization and setup tasks) has been placed in | |
| libraries that are included during the compilation process. These are files | |
| like `util.s`, `util_dli.s` and so forth, and are available in the source code repository or directly [here](https://github.com/playermissile/dli_tutorial/src). | |
| ## Topic #1: DLI Positioning[¶](#topic-1-dli-positioning "Permalink to this headline") | |
| The following examples deal with various techniques regarding placing the DLI | |
| on screen. | |
| ## #1.1: Multiple DLIs[¶](#multiple-dlis "Permalink to this headline") | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/multiple_dli_same_page.png)](https://playermissile.com/_images/multiple_dli_same_page.png) | |
| * **Source Code:** [multiple\_dli\_same\_page.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/multiple_dli_same_page.s) | |
| * **Executable:** [multiple\_dli\_same\_page.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/multiple_dli_same_page.xex) | |
| One of the problems with having a single DLI vector is: what do you do when you | |
| want to have more than one DLI? | |
| Some techniques that you will see in the wild: | |
| > * use `VCOUNT` to check where you are on screen and branch accordingly | |
| > * increment an index value and use that to determine which DLI has been called | |
| > * change the `VDLSTL` vector to point to the next DLI in the chain | |
| Here’s an optimization of the last technique that can save some valuable | |
| cycles: put your DLIs in the same page of memory and only change the low byte. | |
| ``` | |
| *= (* & $ff00) + 256 ; next page boundary | |
| dli pha ; only using A register, so save it to the stack | |
| lda #$55 ; new background color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| sta COLBK ; change background color | |
| lda #<dli2 ; point to second DLI | |
| sta VDSLST | |
| pla ; restore A register from stack | |
| rti ; always end DLI with RTI! | |
| dli2 pha ; only using A register, so save it to the stack | |
| lda #$88 ; new background color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| sta COLBK ; change background color | |
| pla ; restore A register from stack | |
| rti ; always end DLI with RTI! | |
| vbi lda #<dli ; set DLI pointer to first in chain | |
| sta VDSLST | |
| lda #>dli | |
| sta VDSLST+1 | |
| jmp XITVBV ; always exit deferred VBI with jump here | |
| ``` | |
| This is a simplistic example, but keeping the high byte constant inside the | |
| DLI saves 6 cycles (by obviating the need for changing the high byte with | |
| `LDA #>dli2; STA VDLSTL+1`). That may be enough for this optimization to be | |
| useful. | |
| ## #1.2: Moving the DLI Up and Down the Screen[¶](#moving-the-dli-up-and-down-the-screen "Permalink to this headline") | |
| The DLI subroutine itself doesn’t directly know what scan line caused the | |
| interrupt because all DLIs are routed through the same vector at `VDLSTL`. | |
| The only trigger is in the display list: the DLI bit on the display list | |
| instruction. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/moving_dli.gif)](https://playermissile.com/_images/moving_dli.gif) | |
| * **Source Code:** [moving\_dli.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/moving_dli.s) | |
| * **Executable:** [moving\_dli.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/moving_dli.xex) | |
| The display list can be modified in place to move the DLI to different lines | |
| without changing any DLI code. The code to move the DLI should be performed in | |
| the vertical blank to prevent the display list from being modified as ANTIC is | |
| using it to create the display: | |
| ``` | |
| move_dli_line | |
| ldx last_dli_line ; get line number on screen of old DLI | |
| lda dlist_line_lookup,x ; get offset into display list of that line number | |
| tax | |
| lda dlist_first,x ; remove DLI bit | |
| and #$7f | |
| sta dlist_first,x | |
| ldx dli_line ; get line number on screen of new DLI | |
| stx last_dli_line ; remember | |
| lda dlist_line_lookup,x ; get offset into display list of that line number | |
| tax | |
| lda dlist_first,x ; set DLI bit | |
| ora #$80 | |
| sta dlist_first,x | |
| rts | |
| ``` | |
| The example allows the display list to be set on blank lines at the top of the | |
| display, and on the last mode 4 line in the display list which displays the | |
| background below the last mode 4 line on the screen. | |
| ## Topic #2: Colors[¶](#topic-2-colors "Permalink to this headline") | |
| We have already seen several examples of using DLIs to show more colors on | |
| screen. The following examples are included to address more topics in common | |
| use in games or title screens. | |
| ## #2.1: Marching Rainbow Text[¶](#marching-rainbow-text "Permalink to this headline") | |
| Using code almost identical to the [rainbow](#rainbow-wsync) effect, a common effect seen in title screens can be created: | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/marching_rainbow.png)](https://playermissile.com/_images/marching_rainbow.png) | |
| * **Source Code:** [marching\_rainbow.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/marching_rainbow.s) | |
| * **Executable:** [marching\_rainbow.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/marching_rainbow.xex) | |
| Using a simple display list: | |
| ``` | |
| dlist .byte $70,$70,$70,$70,$70,$70 ; 48 blank lines | |
| .byte $46,<text,>text ; Mode 6 + LMS, setting screen memory to text | |
| .byte 6 ; Mode 6 | |
| .byte $70,$70 ; 16 blank lines | |
| .byte 7,7,7 ; 3 lines of Mode 7 | |
| .byte $70 ; 8 blank lines | |
| .byte $f0 ; 8 blank lines + DLI on last scan line | |
| .byte 7,7 ; 2 lines of Mode 7 | |
| .byte $41,<dlist,>dlist ; JVB, restart same display list on next frame | |
| ``` | |
| the DLI simply loads the `start_color` variable as the initial color each | |
| time it is called, then increments the value stored in the hardware color | |
| register for playfield color zero (`COLPF0`) as it makes `WSYNC` calls to | |
| advance one scan line down the screen. Each scan line increases luminance (i.e. | |
| gets brighter), until the low 4 bits controlling the luminance overflows into | |
| the high 4 bits, changing the color to the next in the Atari’s color palette at | |
| zero luminance. | |
| ``` | |
| dli pha ; save A & X registers to stack | |
| txa | |
| pha | |
| ldx #32 ; make 32 color changes | |
| lda start_color ; initial color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| ?loop sta COLPF0 ; change text color for UPPERCASE characters in gr2 | |
| clc | |
| adc #$1 ; change color value, making brighter | |
| dex ; update iteration count | |
| sta WSYNC ; sta doesn't affect processor flags | |
| bne ?loop ; we are still checking result of dex | |
| lda #text_color ; reset text color to normal color | |
| sta COLPF0 | |
| dec start_color ; change starting color for next time | |
| pla ; restore X & A registers from stack | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| At the end of the DLI, `start_color` is *decremented*, meaning that the next | |
| time the DLI is called it will start with one luminance value lower than it did | |
| last time. This gives the appearance of the previous rainbow of color being | |
| “pushed” down the screen and a new darker line being inserted at the top of the | |
| rainbow. | |
| Changing that `dec start_color` to `inc start_color` would have the | |
| opposite effect and the rainbow of color would appear to move upward. | |
| Alternatively, leaving the `dec start_color` but changing the `clc`, `adc | |
| #$1` to: | |
| ``` | |
| sec | |
| sbc #$1 ; change color value, making darker | |
| ``` | |
| would have the same effect as `inc start_color`. | |
| ## Topic #3: Character Sets[¶](#topic-3-character-sets "Permalink to this headline") | |
| The character set on the Atari is comprised of 128 glyphs, each of which is 8 | |
| bytes in size for a total of 1024 bytes for a complete font. The characters are | |
| defined in ANTIC font order, not ATASCII order, so the first 64 characters are | |
| the ATASCII characters 32 - 95 (symbols, numbers, and upper case letters), the | |
| next 32 are the graphic symbols on the control characters, and the final 32 are | |
| the lower case letters and a few remaining graphic symbols. | |
| See also | |
| More resources about character sets are available: | |
| * `CHBAS` in [Mapping the Atari](https://www.atariarchives.org/mapping/memorymap.php#756) | |
| * `CHBASE` in [Mapping the Atari](https://www.atariarchives.org/mapping/memorymap.php#54281) | |
| * [Mapping the Atari, Appendix 10](https://www.atariarchives.org/mapping/appendix10.php) | |
| ## #3.1: Changing Character Sets[¶](#changing-character-sets "Permalink to this headline") | |
| An extremely simple DLI is all that’s needed to change the character set at a | |
| particular scan line. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/simple_chbase.png)](https://playermissile.com/_images/simple_chbase.png) | |
| * **Source Code:** [simple\_chbase.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/simple_chbase.s) | |
| * **Executable:** [simple\_chbase.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/simple_chbase.xex) | |
| This example uses two character sets: the default character set at the top of | |
| the screen, and an character set designed for ANTIC 4 for the bottom. The | |
| screen is broken up into 3 bands, one set of 8 lines of ANTIC mode 2 and two | |
| sets each containing 8 lines of ANTIC mode 4. The top two bands have the normal | |
| character set (`CHBASE = $e000`) and the bottom band has a custom character | |
| set designed for the 5 color mode. | |
| The DLI is set on the 16th text line: the final line in the second band of 8 | |
| lines so that the character set change affects the 3rd band of 8 lines: | |
| ``` | |
| ; mixed mode 2 and mode 4 display list | |
| dlist_static | |
| .byte $70,$70,$70 | |
| .byte $42,$00,$80 | |
| .byte 2,2,2,2,2,2,2 ; first band has 8 lines of mode 2 | |
| .byte 4,4,4,4,4,4,4,$84 ; 2nd band: 8 lines of mode 4 + DLI on last line | |
| .byte 4,4,4,4,4,4,4,4 ; 3rd band: 8 lines of mode 4 | |
| .byte $41,<dlist_static,>dlist_static | |
| ``` | |
| The font for the top of the screen is set during the `init` routine using the | |
| the shadow register `CHBAS`, not the hardware register `CHBASE`. It will be | |
| reloaded every vertical blank by the operating system: | |
| ``` | |
| lda #$e0 | |
| sta CHBAS | |
| ``` | |
| The DLI is very simple, just loading the new character set, but this time using | |
| the hardware register `CHBASE`: | |
| ``` | |
| dli pha ; only using A register, so save it to the stack | |
| lda #>font_data ; page number of new font data | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| sta CHBASE ; store to hardware register to affect change immediately | |
| pla ; restore A register from stack | |
| rti ; always end DLI with RTI! | |
| ``` | |
| ## #3.x: Multiple Interleaved Character Sets for Soft Sprites[¶](#x-multiple-interleaved-character-sets-for-soft-sprites "Permalink to this headline") | |
| The author of the cc65 DLI tutorial, Yaron Nir, started a recent [topic on | |
| AtariAge](https://atariage.com/forums/topic/299571-introducing-interleaved-charactersets-any-game-screen-is-possible-almost/) that described a technique | |
| to use a DLI to change the character set on each line to facilitate the use of | |
| soft sprites. | |
| Soft sprites require shifting images within characters to achieve smooth | |
| motion, so the height of the soft sprites determines how many lines of | |
| characters needed, and the width in pixels as compared to the width of each | |
| character determines the number of characters must be placed horizontally. | |
| ANTIC mode 4 is 8 scan lines tall and has 4 pixels across. Vertically, you | |
| could have sprites of height 9 scan lines using 2 character sets, 17 scan lines | |
| with 3 character sets, 25 scan lines with 4 character sets, etc. Sprites would | |
| need 2 characters side-by-side for up to 5 pixels, 3 characters for 9 pixels, 4 | |
| characters for 13 pixels. etc. | |
| <image here> | |
| With only 128 glyphs per character set, bit patterns are at a premium. If, say, | |
| 64 characters are used for background, only 64 characters remain for sprites. | |
| Using a 4x3 grid for sprites takes 12 out of the 64 available sprite glyphs, so | |
| 5 (independent) soft sprites would be available since that would use 60 | |
| characters. Limiting directions can reduce that number; if your sprites move | |
| only horizontally, for instance, sprites of 13 pixels by 16 scan lines would | |
| only take a 3x2 grid, or only 6 characters. | |
| The advantage of using a DLI with multiple character sets is that more | |
| characters are available for sprites. | |
| ## Topic #4: Player/Missile Graphics[¶](#topic-4-player-missile-graphics "Permalink to this headline") | |
| Player/Missile Graphics is the sprite system provided by the GTIA: | |
| independently positioned overlays on the playfield graphics that don’t disturb | |
| the playfield. | |
| Note | |
| the word *sprite* in this sense wasn’t in use when the Atari was designed, and [several](https://graphics.fandom.com/wiki/Sprite) [sources](https://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29) [claim](http://groups.google.com/group/comp.sys.ti/msg/73e2451bcae4d91a) that it was coined by the designers of the Texas Instruments TI 9918 graphics chip at about the same timeframe. | |
| The GTIA provides 4 players with independent colors (from each other or the | |
| playfield) and 4 missiles with colors matching their respective player, or the | |
| 4 missiles can be combined into a 5th player with its own color (although this | |
| reuses one playfield color). The players are 8 bits wide and can be displayed | |
| as one, two, or four color clocks wide per bit. This corresponds a width on | |
| screen of 8, 16, and 32 color clocks, respectively. Widths for all players and | |
| missiles can be set independently. | |
| Each player and missile can be positioned at an arbitrary horizontal location | |
| by setting a hardware register, but vertical positioning requires copying data | |
| to particular locations in the memory area reserved for it. Each player spans | |
| the height of the screen, and it is only the bit pattern in its storage area | |
| that determines what is drawn on a particular scan line. | |
| Missiles are two bits wide each with all 4 missiles packed into a single byte | |
| for a particular scan line. Bit masking is required to set data for one | |
| missile without affecting the others. | |
| The quick summary for our purposes is that horizontal repositioning of players | |
| is fast, it takes only a single store instruction. Vertical repositioning of | |
| player image data is slow, it requires copying memory around. | |
| ## #4.1: Multiplexing Players Vertically[¶](#multiplexing-players-vertically "Permalink to this headline") | |
| Reusing players (multiplexing) vertically is straightforward, meaning that a | |
| single player can be used to display arbitrary images at different vertical | |
| locations on the screen, provided that there is no vertical overlap. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/simple_multiplex_player.png)](https://playermissile.com/_images/simple_multiplex_player.png) | |
| * **Source Code:** [simple\_multiplex\_player.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/simple_multiplex_player.s) | |
| * **Executable:** [simple\_multiplex\_player.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/simple_multiplex_player.xex) | |
| Using the hardware `HPOSPn` or `HPOSMn` registers, the DLI will | |
| immediately change where ANTIC will draw the player or missile. The next time | |
| ANTIC draws the player on a scan line, it will use this new position. | |
| in the appropriate player or missile X position register. This demo uses the | |
| page-alignment trick for the second DLI, and changes the position and size of | |
| the players at each interrupt. | |
| This demo splits the screen vertically into 3 horizontal bands, A, B & C, with | |
| the players extending the full height of the screen and labeled 0 through 3. | |
| This example uses the VBI to set the players for band A, the `dli` routine | |
| is the bottom of band A (and the top of band B) and therefore sets the players | |
| for band B, and the `dli2` routine is the bottom of band B (and the top of | |
| band C) and controls the players for band C. | |
| ``` | |
| vbi lda #<dli ; set DLI pointer to first in chain | |
| sta VDSLST | |
| lda #>dli | |
| sta VDSLST+1 | |
| lda #$40 ; set player positions and sizes ... | |
| sta HPOSP0 ; for the top of the screen | |
| lda #$60 | |
| sta HPOSP1 | |
| lda #$80 | |
| sta HPOSP2 | |
| lda #$a0 | |
| sta HPOSP3 | |
| lda #0 | |
| sta SIZEP0 | |
| sta SIZEP1 | |
| sta SIZEP2 | |
| sta SIZEP3 | |
| jmp XITVBV ; always exit deferred VBI with jump here | |
| *= (* & $ff00) + 256 ; next page boundary | |
| dli pha ; only using A register, so save it to the stack | |
| lda #$55 ; new background color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| sta COLBK ; change background color | |
| lda #$30 ; change position and sizes of players | |
| sta HPOSP0 | |
| lda #$40 | |
| sta HPOSP1 | |
| lda #$50 | |
| sta HPOSP2 | |
| lda #$60 | |
| sta HPOSP3 | |
| lda #1 | |
| sta SIZEP0 | |
| sta SIZEP1 | |
| sta SIZEP2 | |
| sta SIZEP3 | |
| lda #<dli2 ; point to second DLI | |
| sta VDSLST | |
| pla ; restore A register from stack | |
| rti ; always end DLI with RTI! | |
| dli2 pha ; only using A register, so save it to the stack | |
| lda #$84 ; new background color | |
| sta WSYNC ; first WSYNC gets us to start of scan line we want | |
| sta COLBK ; change background color | |
| lda #$40 ; change position and sizes of players | |
| sta HPOSP0 | |
| lda #$70 | |
| sta HPOSP1 | |
| lda #$90 | |
| sta HPOSP2 | |
| lda #$b0 | |
| sta HPOSP3 | |
| lda #3 | |
| sta SIZEP0 | |
| sta SIZEP1 | |
| sta SIZEP2 | |
| sta SIZEP3 | |
| pla ; restore A register from stack | |
| rti ; always end DLI with RTI! | |
| ``` | |
| In discussing the timing issues that cause errors at the band boundaries, the | |
| players in band A are positioned by the VBI, and so are in place from well off | |
| the top of the screen and are correctly positioned at the first scan line. | |
| Players 0, 1, and 2 are correct at the bottom of the band, but player 3 | |
| extends one scan line too far, into band B. | |
| The top of band B shows both position and size errors. When the first DLI hits | |
| on the last scan line of the 6th line of text, the background color is changed | |
| at the `WSYNC` and ANTIC moves on to start drawing the first scan line of | |
| the 7th line of text (which is the first line of text in band B.) Players 0, | |
| 1, and 2 are positioned correctly, which means their horizontal positions were | |
| set before ANTIC reached that portion of the scan line. The 3rd player remains | |
| in the same position as it was in band A, meaning that its horizontal position | |
| wasn’t set in time. ANTIC had stolen enough cycles setting up the mode 4 font | |
| that the 6502 didn’t get a chance to process the `sta HPOS3` before ANTIC | |
| had to draw that portion of the scan line. Since the code sets sizes after the | |
| horizontal positions, none of the sizes are set until the 2nd scan line of | |
| band B. | |
| The transition to band C with the `dli2` routine produces similar results, | |
| there just isn’t enough time with the `WSYNC` used for the color change | |
| *and* all the cycles stolen by ANTIC mode 4 to process the all of the player | |
| changes in the first scan line of the band. Players 0, 1, and 2 are moved, | |
| player 3 is not, and all 4 players don’t get the correct size until the 2nd | |
| scan line of the band. | |
| It’s possible to imagine a scenario where a scan line of a player is not | |
| visible at all. For example, if player 3 above had been positioned very far to | |
| the right and `HPOSP3` was changed to move player 3 to the far left side, it | |
| could be possible that ANTIC has already drawn the left side of the screen but | |
| hadn’t yet reached the right side where player 3 had been positioned. Because | |
| `HPOSP3` is now showing that player 3 is on the left side of the screen, | |
| ANTIC would not draw it at its old location on the right side of the screen. | |
| It’s also possible, with careful timing, to reuse a player on a single line. | |
| However, purposeful use of this would difficult given all the different | |
| horizontal locations of ANTIC’s cycle stealing. | |
| Mode 4 was chosen (in all of its cycle-stealing glory) for these examples to | |
| get an idea of the worst-case scenerio. Taking out the `WSYNC` and the color | |
| change did allow enough time that both the positions and sizes were changed | |
| without visible artifacts: | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/simple_multiplex_player_no_wsync.png)](https://playermissile.com/_images/simple_multiplex_player_no_wsync.png) | |
| but this is very simple code and the more real-world example in the next | |
| section will show that a buffer zone of several scan lines is necessary to | |
| make sure a player isn’t split across a band boundary or, as described above, | |
| even duplicating a line of the player or missing a scan line. | |
| ## #4.2: Multiplexing With Horizontal Motion[¶](#multiplexing-with-horizontal-motion "Permalink to this headline") | |
| Increasing the number of bands and adding independent player movement within | |
| each band requires some data structures and a DLI to control placement in each | |
| band. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/multiplex_player_movement.png)](https://playermissile.com/_images/multiplex_player_movement.png) | |
| * **Source Code:** [multiplex\_player\_movement.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/multiplex_player_movement.s) | |
| * **Executable:** [multiplex\_player\_movement.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/multiplex_player_movement.xex) | |
| The approach used in this example is to use a single DLI that uses an index | |
| value to determine which band it is operating within. This index value is used | |
| as an offset into arrays that hold the sprite X position, size, color, etc. | |
| There are a lot of independently moving objects in this demo: 12 bands, each | |
| with 4 players. There are very obvious timing issues in most bands on the | |
| first scan line after the DLI as sometimes the hardware registers for a player | |
| hasn’t been updated fully until the second scan line. | |
| ``` | |
| ; same DLI routine is used for each band, the band_dli_index is used to set | |
| ; player information for the appropriate band | |
| dli_band | |
| pha ; using A & X | |
| txa | |
| pha | |
| inc band_dli_index ; increment band index, VBI initialized to $ff, | |
| ldx band_dli_index ; so will become 0 for band A | |
| ; control band X positions of players | |
| lda bandp0_x,x ; x position of player 0 in this band | |
| sta HPOSP0 | |
| lda bandp0_color,x ; color of player 0 for this band | |
| sta COLPM0 | |
| lda bandp0_size,x ; size of player 0 for this band | |
| sta SIZEP0 | |
| lda bandp1_x,x ; as above, but for players 1 - 3 | |
| sta HPOSP1 | |
| lda bandp1_color,x | |
| sta COLPM1 | |
| lda bandp1_size,x | |
| sta SIZEP1 | |
| lda bandp2_x,x | |
| sta HPOSP2 | |
| lda bandp2_color,x | |
| sta COLPM2 | |
| lda bandp2_size,x | |
| sta SIZEP2 | |
| lda bandp3_x,x | |
| sta HPOSP3 | |
| lda bandp3_color,x | |
| sta COLPM3 | |
| lda bandp3_size,x | |
| sta SIZEP3 | |
| ?done pla ; restore A & X | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| The addreses `bandpN_x`, `bandpN_color`, and `bandpN_size` (where N is | |
| the player number) are declared as lists with the number of entries equal to | |
| the number of bands. `band_dli_index` is incremented each time the DLI | |
| starts, and uses that index into the lists so it places the players in the | |
| correct location for that band. | |
| Notice that is *all* the DLI does. It does not calculate movement or perform any | |
| player logic, it simply puts players on the screen in the appropriate place for | |
| that band. All the calculation is done in the vertical blank: | |
| ``` | |
| ; calculate new positions of players in all bands | |
| vbi ldx #0 | |
| ?move lda bandp0_x,x ; update X coordinate | |
| clc ; by adding velocity. | |
| adc bandp0_dx,x ; Note that velocity of $ff | |
| sta bandp0_x,x ; is same as -1 | |
| cmp #$30 ; check left edge | |
| bcs ?right ; if >=, it is still in playfield | |
| lda #1 ; nope, <, so make velocity positive | |
| sta bandp0_dx,x | |
| bne ?cont | |
| ?right cmp #$c0 ; check right edge | |
| bcc ?cont ; if <, it is still in playfield | |
| lda #$ff ; nope, >=, so make velocity negative | |
| sta bandp0_dx,x | |
| ?cont inx ; next player | |
| cpx #num_dli_bands * 4 ; loop through 12 bands * 4 players each | |
| bcc ?move | |
| lda #$ff ; initialize band index to get ready for band A | |
| sta band_dli_index | |
| jmp XITVBV ; always exit deferred VBI with jump here | |
| ``` | |
| Unlike the simple multiplexing demo in the previous section, this VBI does not | |
| set any positions of players. Instead, this demo sets the DLI bit on the last | |
| group of 8 blank lines at the beginning of the display list, before any mode 4 | |
| lines. This initial DLI will set the players for band A, and as you can see in | |
| the demo the players above band A use the same X position and size as band L. | |
| The colors are not the same as band L, however, because of the use of the | |
| shadow registers to set the initial color in the `init_pmg` subroutine. | |
| ## #4.3: Reusing Players Horizontally[¶](#reusing-players-horizontally "Permalink to this headline") | |
| Reusing players on the same scan line is possible, but requires cycle counting | |
| and has limitations, especially in text modes. The complicated cycle stealing | |
| performed by ANTIC will require consulting timing reference charts (such as in | |
| the Altirra Hardware Reference Manual) to determine how well it can be used for | |
| a particular graphics mode. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/reusing_player_horz.png)](https://playermissile.com/_images/reusing_player_horz.png) | |
| * **Source Code:** [reusing\_player\_horz.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/reusing_player_horz.s) | |
| * **Executable:** [reusing\_player\_horz.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/reusing_player_horz.xex) | |
| Here’s the DLI that produces the effect above, where player 3 has multiple | |
| copies at the same vertical position. Again there are 12 vertical bands (this | |
| time ANTIC mode 5), where the first copy of player 3 is at the left side of the | |
| screen and the other 3 shift slowly to the left as it moves down bands in | |
| order to find the minimum possible horizontal shift between copies. This is | |
| not a kernel (see the next section for that), so the DLI bit is set on each of | |
| the mode 5 lines. | |
| ``` | |
| dli pha ; using A & X | |
| txa | |
| pha | |
| dec copy1 ; move copies to the left one color clock each band | |
| dec copy2 | |
| sta WSYNC ; skip rest of last line of DLI line | |
| dec copy3 ; not enough time to do all 3 decrements before 1st WSYNC | |
| ldx #14 ; prepare for 14 scan lines in the loop | |
| sta WSYNC ; skip 1st line of mode 5 where ANTIC steals almost all cycles | |
| ?loop lda #48 ; set initial position of player 3 | |
| sta HPOSP3 | |
| nop ; we're still on the tail end of the prevous scan | |
| nop ; line, so we need to wait until the electron beam | |
| nop ; passes this first position before we set the | |
| nop ; next HPOS. | |
| nop | |
| nop | |
| lda copy1 ; can't place copies until after electron beam draws | |
| sta HPOSP3 ; the player in the previous location. If you try | |
| lda copy2 ; to move HPOSP3 too early, the previous location | |
| sta HPOSP3 ; won't even get drawn. Too late, and it won't draw | |
| lda copy3 ; anything in the current location. It's a battle. | |
| sta HPOSP3 | |
| dex | |
| beq ?done | |
| sta WSYNC | |
| bne ?loop | |
| ?done pla ; restore A & X | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| This requires a VBI to reset the starting horizontal position at the top of | |
| each frame. | |
| ``` | |
| vbi lda #68 ; reset position counters for each copy of player 3 | |
| sta copy1 | |
| lda #122 | |
| sta copy2 | |
| lda #156 | |
| sta copy3 | |
| jmp XITVBV ; always exit deferred VBI with jump here | |
| ``` | |
| There is a lot to unpack here. | |
| First: using a text mode is a mistake because ANTIC steals so many cycles on | |
| the first scan line that there’s no way to place copies on that scan line. On | |
| subsequent lines, there is enough time to make multiple copies of a player | |
| except for the last line that will have to contain the `RTI` instruction. | |
| However, because this is not using a kernel- style DLI where it takes control | |
| for all 192 lines, the `RTI` has to happen before the last scan line so | |
| there is enough time for the interrupt processing for the next DLI without the | |
| the current DLI getting interrupted, which would then stack interrupts and | |
| cause scan line offsets. | |
| Second: notice the bands places in which the number **3** isn’t drawn in the | |
| player, instead only a single scan line in the player 3 color appears. This | |
| means there are not enough available cycles to set the new position of the | |
| player before the electron beam has already passed the desired horizontal | |
| position. | |
| The takeaways here: | |
| > * the cycle counting necessary will be much easier using bitmap modes | |
| > * it will probably be more successful to use a kernel rather than multiple DLIs | |
| > * the author may revisit this technique at some point, but for now | |
| > will leave further exploration to the reader, assuming the reader is | |
| > much more patient regarding cycle counting than the author. | |
| ## #4.x: Multiplexing with Arbitrary Motion[¶](#x-multiplexing-with-arbitrary-motion "Permalink to this headline") | |
| Vertical movement within bands requires the moving memory around the | |
| player/missile graphics area (pointed to by `PMBASE`) as in normal usage, | |
| with the following limitations: | |
| > * players must stay within their assigned band, otherwise they will get split across bands when the DLI occurs. | |
| > * players should avoid the first few scan lines below the top of the band boundary to prevent splitting | |
| > * when moving a player vertically within a band, only erase data | |
| > from that band to prevent affecting the multiplexed players in other | |
| > bands | |
| <example goes here> | |
| ## #4.x: Multiplexing With Collision Detection[¶](#x-multiplexing-with-collision-detection "Permalink to this headline") | |
| If it is important to tell in which band a has collided occurred, the DLI that | |
| starts a new band will be required to save the collision status registers, | |
| which will determine if a collision happened in the *previous* band. It will | |
| then reset the collision registers so the following DLI can check what | |
| happened in this band. | |
| If the knowledge of the band is not important, you can just check the | |
| collision registers in the vertical blank, which will report if there have | |
| been any collisions with anything in any band. | |
| <example goes here> | |
| ## Topic #5: Kernels[¶](#topic-5-kernels "Permalink to this headline") | |
| The concept of a kernel comes from Atari 2600 programming. The 2600 does not | |
| have enough memory to store an entire frame – it has a line buffer, rather | |
| than a frame buffer. To create a graphic image with any vertical detail, the | |
| code must build the screen line-by-line, changing graphic information as the | |
| electron beam moves down the screen. | |
| Kernels for our purposes will be DLIs that take control for many scan lines to | |
| perform graphic operations that are not possible otherwise. We have seen | |
| horizontal positioning of players accomplished with a traditional DLI setup | |
| with interrupts on multiple display list commands. It could have been | |
| performed using a kernel, which (assuming the graphics mode is bitmapped | |
| rather than text) would have removed the restriction created by need for extra | |
| cycles near the `RTI` instruction. | |
| Kernels are a very advanced topic. The Atari 8-bit computers are the direct | |
| successor to the 2600, and the ANTIC and GTIA were designed to automate common | |
| tasks that in the 2600 requires kernel programming. Because so much is possible | |
| without kernels, this tutorial is not going to spend much time with this topic. | |
| However, a few examples are presented here to give you an idea of how they | |
| work. | |
| ## #5.1: Background Color Change Within Scan Line[¶](#background-color-change-within-scan-line "Permalink to this headline") | |
| A simple kernel can be used to change the background color to “split” the | |
| screen horizontally. Having learned a lesson or two, the author is using a | |
| graphics mode for the following example, mode E (the 160x192, 4 color mode): | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/background_color_kernel.png)](https://playermissile.com/_images/background_color_kernel.png) | |
| * **Source Code:** [background\_color\_kernel.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/background_color_kernel.s) | |
| * **Executable:** [background\_color\_kernel.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/background_color_kernel.xex) | |
| which does show much more (but not complete!) uniformity. The problem scan | |
| lines are the first and somewhere in the middle. Here’s the DLI: | |
| ``` | |
| dli pha ; using all registers | |
| txa | |
| pha | |
| tya | |
| pha | |
| ldy #192 | |
| sta WSYNC ; initialize to near beginning of first scan line of interest | |
| ?loop lda #90 ; set background color | |
| sta COLBK | |
| nop ; wait for some time | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| nop | |
| lda #70 ; after 1st copy is drawn but before electron beam | |
| sta COLBK | |
| dey | |
| sta WSYNC | |
| bne ?loop | |
| lda #0 | |
| sta COLBK | |
| ?done pla ; restore all registers | |
| tay | |
| pla | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| The code shows lots of waiting around. Using cycle counting of opcodes is the | |
| finest level of precision for direct manipulation of the graphics screen. | |
| There’s no way to get accuracy down to an individual color clock, unless the | |
| timing happens to work out that the instruction duration combined with the | |
| particular cycles on which ANTIC pauses the CPU to do its work happen to fall | |
| on the color clock you’re interested in. | |
| The issue on the first scan line is caused by the first `WSYNC` not being | |
| immediately followed by a branch instruction as in all subsequent calls to | |
| `WSYNC`. Solving this requires an extra delay added after that first | |
| `WSYNC`. | |
| Examining the display list will probably make it obvious where the problem scan | |
| line is in the middle of the screen: | |
| ``` | |
| ; mode E standard display list | |
| dlist_static_modeE | |
| .byte $70,$70,$70 | |
| .byte $4e,$00,$80 | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $4e,$00,$8f ; yep, it's right here | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e,$e | |
| .byte $41,<dlist_static_modeE,>dlist_static_modeE | |
| ``` | |
| Because ANTIC can’t cross a 4k memory boundary (it only has 12 address lines, | |
| 2^12 = 4096), the display list for full screen display of modes D, E, and F | |
| must be broken up into two sections of about 4K each. The `LMS` instruction | |
| `$4e` causes ANTIC to steal 2 cycles to read those two bytes that hold the | |
| screen address, which delays the timing by 2 cycles and forces the color | |
| change to happen later than desired. This problem wouldn’t happen with a | |
| display list of modes A, B, and C, for instance, because their maximum use of | |
| memory is less than 4k. | |
| Solving this problem requires some extra handling after 95 scan lines have | |
| passed in order to remove a bit of delay before changing the background color. | |
| But the author doesn’t find that this particular example would be very useful | |
| in actual games, so the next section will look at a technique using a kernel | |
| that is in common use in games: the multicolor player. | |
| ## #5.x: Multicolor Player[¶](#x-multicolor-player "Permalink to this headline") | |
| We have seen DLIs being used to change player position, size, and color. Until | |
| now, these demos have been limited to particular vertical bands on screen. | |
| Changing player attributes at an arbitrary location on screen will require a | |
| kernel-style DLI. | |
| Note | |
| Strictly speaking, this is not true. If players do not overlap | |
| vertically, or only a single player needs to have characteristics | |
| adjusted, a [moving DLI](#moving-dli) technique could work. | |
| <example goes here> | |
| ## Topic #6: Scrolling[¶](#topic-6-scrolling "Permalink to this headline") | |
| Note | |
| Scrolling is a large topic; so large, in fact, that I wrote an [additional tutorial](https://playermissile.com/scrolling_tutorial/index.html#scrolling-tutorial) about it! | |
| Display lists provide the ability to easily perform course scrolling without | |
| moving any display memory around. Instead, the visible display can be adjusted | |
| to provide scrolling at byte resolution by adjusting the address pointed to by | |
| any LMS instructions in the display list. The amount of graphical detail in a | |
| byte depends on the graphics mode: character modes by definition are one | |
| character per byte so the course scrolling limits are a single character | |
| vertically or horizontally. Bitmap modes can be 1 to 8 scan lines tall per | |
| byte, and 4 or 8 color clocks wide per byte. | |
| The Fine scrolling hardware registers provide the bridge between byte size and | |
| scan lines (vertically) or color clocks (horizontally; and note that a color | |
| clock in the smallest unit for horizontal scrolling, even in mode F). | |
| Vertically the `VSCROL` hardware register allows fine scrolling up to 16 | |
| scan lines, and horizontally the `HSCROL` register provides up to 16 color | |
| clocks fine scrolling. | |
| Continuous fine scrolling requires the use of both fine scrolling and course | |
| scrolling techniques, with the fine scrolling used until the size limit of the | |
| particular graphics mode is reached, then using course scrolling to move the | |
| display list to point to the next byte in memory while simultaneously | |
| resetting the fine scrolling register back to its starting point. Vertically, | |
| the size limit is the height in scan lines of the mode, and horizontally is | |
| the number of color clocks wide. | |
| See also | |
| * [De Re Atari, Chapter 6](https://www.atariarchives.org/dere/chapt06.php) | |
| * Mapping the Atari: [HSCROL](https://www.atariarchives.org/mapping/memorymap.php#54276) and [VSCROL](https://www.atariarchives.org/mapping/memorymap.php#54277) | |
| * my own [Fine Scrolling: A Complete(ish) Tutorial](https://playermissile.com/scrolling_tutorial/index.html#scrolling-tutorial) | |
| ## #6.1: Parallax Scrolling[¶](#parallax-scrolling "Permalink to this headline") | |
| The “Moon Patrol” effect is actually very straightforward on the Atari, since | |
| splitting up the screen vertically is among the strengths of ANTIC. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/parallax_scrolling.png)](https://playermissile.com/_images/parallax_scrolling.png) | |
| * **Source Code:** [parallax\_scrolling.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/parallax_scrolling.s) | |
| * **Executable:** [parallax\_scrolling.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/parallax_scrolling.xex) | |
| This effect does require a DLI because the `HSCROL` value is stored in an | |
| ANTIC hardware register and remains in effect until changed. It is nominally | |
| for full-screen scrolling, but since ANTIC has no memory of what it has done | |
| in the past, there is every reason to use the capability and modify it in the | |
| middle of the screen. The DLI is extremely simple, it just changes `HSCROL` | |
| to a previously-computed value at for band: | |
| ``` | |
| ; same DLI routine is used for each band, the band_dli_index is used to; | |
| ; determine which band we're in | |
| dli_band | |
| pha ; using A & X | |
| txa | |
| pha | |
| inc band_dli_index ; increment band index, VBI initialized to $ff, | |
| ldx band_dli_index ; so will be 0 for band B (band A doesn't scroll!) | |
| lda band_hscrol,x ; change HSCROL for this band | |
| sta HSCROL | |
| ?done pla ; restore A & X | |
| tax | |
| pla | |
| rti ; always end DLI with RTI! | |
| ``` | |
| The calculation of each band’s `HSCROL` value is performed in the VBI. | |
| ``` | |
| ; calculate new scrolling positions of bands | |
| vbi ldx #2 | |
| ?move lda band_hscrol_frac,x ; update scrolling position fraction | |
| clc ; by adding velocity fraction. | |
| adc band_hscrol_frac_delta,x | |
| sta band_hscrol_frac,x | |
| lda band_hscrol,x ; update scrolling position whole number | |
| adc #0 | |
| sta band_hscrol,x | |
| cmp #4 ; 4 color clocks in Antic 4; check if need a course | |
| bcc ?nope ; scroll | |
| ; course scroll needed, chech which region | |
| cpx #0 | |
| bne ?ckb | |
| jsr course_scroll_b | |
| bcc ?next ; CLC in subroutine to allow branch | |
| ?ckb cpx #1 | |
| bne ?chc | |
| jsr course_scroll_c | |
| bcc ?next ; CLC in subroutine to allow branch | |
| ?chc jsr course_scroll_d | |
| ?next lda #0 ; reset HSCROL for this band | |
| sta band_hscrol,x | |
| ?nope dex | |
| bpl ?move | |
| lda #$ff ; initialize band index to get ready for the first | |
| sta band_dli_index ; DLI which affects band B | |
| lda #0 ; always reset HSCROL to zero for top of new screen | |
| sta HSCROL | |
| jmp XITVBV ; always exit deferred VBI with jump here | |
| ``` | |
| For this demo, band C is running two times faster than band B, and band D is | |
| running two times faster than band C. To allow some future speed modification | |
| and to prevent the demo from running too fast, it is actually operating on two- | |
| byte, fixed-point math: fractions of an `HSCROL` value. Every VBI, the low | |
| byte (representing the fraction out of 256) changes by 32, 64, or 128 | |
| depending on the band (B, C, and D, respectively), and when the low byte | |
| overflows, the high byte (and therefore `HSCROL`) is updated. | |
| ## #6.2: Multiple Scrolling Regions[¶](#multiple-scrolling-regions "Permalink to this headline") | |
| Splitting the screen vertically allows multiple independent scrolling | |
| regions by changing the VSCROL and HSCROL values in the DLI so that the | |
| subsequent lines use different values. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/multiple_scrolling_regions.png)](https://playermissile.com/_images/multiple_scrolling_regions.png) | |
| * **Source Code:** [multiple\_scrolling\_regions.s](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/src/multiple_scrolling_regions.s) | |
| * **Executable:** [multiple\_scrolling\_regions.xex](https://raw.githubusercontent.com/playermissile/dli_tutorial/master/xex/multiple_scrolling_regions.xex) | |
| This example uses 2 regions with the DLI on a blank line separating them. More | |
| than 2 regions are possible using similar techniques, and is left as an | |
| exercise for the reader. | |
| ``` | |
| dlist .byte $70,$70,$70 | |
| dlist_upper_region | |
| .byte $74,$70,$90 ; 12 lines in region, VSCROLL + HSCROLL | |
| .byte $74,$70,$91 | |
| .byte $74,$70,$92 | |
| .byte $74,$70,$93 | |
| .byte $74,$70,$94 | |
| .byte $74,$70,$95 | |
| .byte $74,$70,$96 | |
| .byte $74,$70,$97 | |
| .byte $74,$70,$98 | |
| .byte $74,$70,$99 | |
| .byte $74,$70,$9a | |
| .byte $54,$70,$9b ; last line in scrolling region, HSCROLL only | |
| .byte $80 ; one blank line + DLI | |
| dlist_lower_region | |
| .byte $74,$70,$90 ; 12 lines in region, VSCROLL + HSCROLL | |
| .byte $74,$70,$91 | |
| .byte $74,$70,$92 | |
| .byte $74,$70,$93 | |
| .byte $74,$70,$94 | |
| .byte $74,$70,$95 | |
| .byte $74,$70,$96 | |
| .byte $74,$70,$97 | |
| .byte $74,$70,$98 | |
| .byte $74,$70,$99 | |
| .byte $74,$70,$9a | |
| .byte $54,$70,$9b ; last line in scrolling region, HSCROLL only | |
| .byte $41,<dlist,>dlist ; JVB ends display list | |
| ``` | |
| Using a blank line as the DLI reduces the possibility of timing issues due to | |
| the large number of cycles stolen by ANTIC mode 4. There are very few cycles | |
| stolen on a blank line, and even through the DLI used below is not very long, | |
| real-world examples would probably be longer and could use the leeway provided | |
| by the extra cycles. | |
| The scrolling code is taken largely from the [scrolling tutorial 2D | |
| scrolling code walkthrough](https://playermissile.com/scrolling_tutorial/index.html#code-walkthrough), so discussion of the workings | |
| of the scrolling code won’t be repeated here. The major difference is that the | |
| code needs to keep track of two separate scrolling regions. Think of the | |
| following as two-element arrays: | |
| ``` | |
| ; two bytes per variable, one per region | |
| vert_scroll = $90 ; variable used to store VSCROL value | |
| horz_scroll = $92 ; variable used to store HSCROL value | |
| scroll_dy = $a2 ; down = 1, up=$ff, no movement = 0 | |
| scroll_dx = $a4 ; right = 1, left=$ff, no movement = 0 | |
| ``` | |
| Updating the scrolling parameters for both regions is performed in the vertical | |
| blank, where the `X` register is used as the array index into the variables. | |
| `X = 0` refers to the upper region, and `X = 1` the lower region. | |
| ``` | |
| vbi dec delay_count ; wait for number of VBLANKs before updating | |
| bne ?exit ; fine/coarse scrolling | |
| ldx #0 ; process top region | |
| jsr process_movement ; update scrolling position | |
| inx ; process lower region | |
| jsr process_movement ; update scrolling position | |
| lda #delay ; reset counter | |
| sta delay_count | |
| ; every VBI have to set the scrolling registers for the upper | |
| ; region, otherwise the registers will still be set to the values | |
| ; for the lower region that were handled in the DLI | |
| ?exit lda horz_scroll | |
| sta HSCROL | |
| lda vert_scroll | |
| sta VSCROL | |
| jmp XITVBV ; exit VBI through operating system routine | |
| ``` | |
| The idea behind multiple scrolling regions is: independent control of the | |
| hardware scrolling registers. Coarse scrolling for each region is dependent | |
| only on the LMS addresses of the display list, so no DLI would be needed. | |
| However, fine scrolling does need the mid-screen changes provided by a DLI, | |
| otherwise the `VSCROL` and `HSCROL` values would affect all scrolling | |
| regions. | |
| The hardware scrolling registers are set in the vertical blank and would | |
| normally affect the entire screen. But because of the DLI, they only affect the | |
| upper region. The DLI changes the hardware registers, meaning all the scrolled | |
| lines in the lower region use those new values. | |
| ``` | |
| dli pha ; only using A register, so save old value to the stack | |
| lda horz_scroll+1 ; lower region HSCROL value | |
| sta HSCROL ; store in hardware register | |
| lda vert_scroll+1 ; lower region VSCROL value | |
| sta VSCROL ; initialize hardware register | |
| pla ; restore the A register | |
| rti ; always end DLI with RTI! | |
| ``` | |
| The two-element arrays at `horz_scroll` and `vert_scroll` hold the values | |
| to be stored in the hardware registers for the upper region at index 0 and the | |
| lower region at index 1. In the scrolling code processing starting with the | |
| `process_movement` subroutine, the arrays are indexed using the `X` | |
| register, while in the VBI and DLI the array indexes are fixed. Because the VBI | |
| always uses array index 0 and the DLI always uses array index 1, there is no | |
| need to use the `X` register as an index. | |
| We won’t examine all 4 scrolling directions here, but we will look at one as an | |
| example of how they were all modified. The `X` register is loaded in the | |
| vertical blank, then the `process_movement` subroutine is called for both | |
| regions. Inside that subroutine, it calls the appropriate fine scrolling | |
| subroutines for the directions needed. | |
| Scrolling to the right will be used as the example. The fine scrolling | |
| subroutine shows the `X` indexing of the `horz_scroll` array: | |
| ``` | |
| fine_scroll_right | |
| dec horz_scroll,x | |
| lda horz_scroll,x | |
| bpl ?done ; if non-negative, still in the middle of the character | |
| jsr coarse_scroll_right ; wrapped to $ff, do a coarse scroll... | |
| lda #horz_scroll_max-1 ; ...followed by reseting the HSCROL register | |
| sta horz_scroll,x | |
| ?done rts | |
| ``` | |
| If coarse scrolling is needed, the `X` register is examined to determine which set of display list instructions need their LMS address updated: | |
| ``` | |
| coarse_scroll_right | |
| lda #12 ; 12 lines to modify | |
| sta tmp_counter | |
| lda #1 ; dlist_upper_region+1 is low byte of address | |
| cpx #0 | |
| beq ?start | |
| lda #(1+36+1) ; dlist_upper_region+1+36+1 is dlist_region2+1 | |
| ?start stx ?smc_savex+1 ; save X register using self-modifying code | |
| tax | |
| ?loop inc dlist_upper_region,x | |
| inx ; skip to next low byte which is 3 bytes away | |
| inx | |
| inx | |
| dec tmp_counter | |
| bne ?loop | |
| ?smc_savex ldx #$ff | |
| rts | |
| ``` | |
| Because the `inc` instruction can only be indexed using the `X` register, | |
| the `X` value used as the region index must be saved. Rather than use a | |
| temporary variable, self-modifying code is used. The current `X` value is | |
| saved *as the argument* for an immediate load. | |
| Note | |
| if you haven’t seen this technique before, it is used quite often as a speed | |
| optimization. The standard stack-based technique: | |
| ``` | |
| txa ; 2 cycles | |
| pha ; 4 cycles | |
| pla ; 3 cycles | |
| tax ; 2 cycles | |
| ``` | |
| takes 13 cycles. Using a zero page variable: | |
| ``` | |
| stx zp ; 3 cycles | |
| ldx zp ; 3 cycles | |
| ``` | |
| takes 6 cycles. Using self-modifying code: | |
| ``` | |
| stx smc+1 ; 4 cycles, opcode is at address smc, value is at smc+1 | |
| smc ldx #$ff ; 2 cycles | |
| ``` | |
| also takes 6 cycles, but has the advantage of not needing dedicated storage | |
| in the zero page. Note that if you are optimizing for size, the self-modifying | |
| code version takes 5 bytes, while the stack and zero page versions | |
| only take 4. | |
| For improved code readability, I try to label any places where I use self | |
| modifying code with a `smc_` prefix. | |
| Compared to the example from the scrolling tutorial, the remaining changes | |
| involve removal of all user input. The joystick control of the scrolling | |
| direction is replaced by hardcoded values, and the **Option** and **Select** | |
| keys are not handled. | |
| To modify the code to handle more than 2 scrolling regions, the array size in | |
| the zero page would have to be increased, the DLI display list bit would have | |
| to be set in the dividing line between all regions in the display list, the DLI | |
| itself would have to be made aware of which region it was operating in, and the | |
| coarse scrolling subroutines would have to handle the additional display list | |
| regions for updating LMS addresses. | |
| [%20Tutorial%20%E2%80%94%20Player_Missile%20Podcast_files/logo-200.png)](https://playermissile.com/index.html "index") | |
| #### Previous page | |
| [← Player/Missile Podcast](https://playermissile.com/index.html "Previous page") | |
| #### Next page | |
| [→ Atari 8-bit Fine Scrolling: A Complete(ish) Tutorial](https://playermissile.com/scrolling_tutorial/index.html "Next page") | |
| ### [Page contents](https://playermissile.com/index.html) | |
| * Atari 8-bit Display List Interrupts: A Complete(ish) Tutorial | |
| + [Displays: A Tiny Overview of How TVs Work](#displays-a-tiny-overview-of-how-tvs-work) | |
| + [Display Lists: How the Atari Generates the Display](#display-lists-how-the-atari-generates-the-display) | |
| - [Display List Instruction Set](#display-list-instruction-set) | |
| - [A Sample Display List](#a-sample-display-list) | |
| - [Cycle Stealing by ANTIC](#cycle-stealing-by-antic) | |
| - [Restrictions](#restrictions) | |
| + [Display List Interrupts: A Crash Course](#display-list-interrupts-a-crash-course) | |
| - [Hardware & Shadow Registers](#hardware-shadow-registers) | |
| + [Basic Display List Interrupts](#basic-display-list-interrupts) | |
| - [Our First Display List](#our-first-display-list) | |
| - [WSYNC: How to Avoid Flickering](#wsync-how-to-avoid-flickering) | |
| - [A DLI Can Affect Many Scan Lines](#a-dli-can-affect-many-scan-lines) | |
| + [Display List Interrupts Getting Interrupted](#display-list-interrupts-getting-interrupted) | |
| - [DLI Interrupting Another DLI](#dli-interrupting-another-dli) | |
| - [Emulator Differences](#emulator-differences) | |
| - [VBI Interrupting A DLI](#vbi-interrupting-a-dli) | |
| - [DLI Interrupting A VLI](#dli-interrupting-a-vli) | |
| - [DLI on the JVB Instruction](#dli-on-the-jvb-instruction) | |
| + [DLIs in a Nutshell](#dlis-in-a-nutshell) | |
| + [Advanced DLI Examples](#advanced-dli-examples) | |
| + [Topic #1: DLI Positioning](#topic-1-dli-positioning) | |
| + [#1.1: Multiple DLIs](#multiple-dlis) | |
| + [#1.2: Moving the DLI Up and Down the Screen](#moving-the-dli-up-and-down-the-screen) | |
| + [Topic #2: Colors](#topic-2-colors) | |
| + [#2.1: Marching Rainbow Text](#marching-rainbow-text) | |
| + [Topic #3: Character Sets](#topic-3-character-sets) | |
| + [#3.1: Changing Character Sets](#changing-character-sets) | |
| + [#3.x: Multiple Interleaved Character Sets for Soft Sprites](#x-multiple-interleaved-character-sets-for-soft-sprites) | |
| + [Topic #4: Player/Missile Graphics](#topic-4-player-missile-graphics) | |
| + [#4.1: Multiplexing Players Vertically](#multiplexing-players-vertically) | |
| + [#4.2: Multiplexing With Horizontal Motion](#multiplexing-with-horizontal-motion) | |
| + [#4.3: Reusing Players Horizontally](#reusing-players-horizontally) | |
| + [#4.x: Multiplexing with Arbitrary Motion](#x-multiplexing-with-arbitrary-motion) | |
| + [#4.x: Multiplexing With Collision Detection](#x-multiplexing-with-collision-detection) | |
| + [Topic #5: Kernels](#topic-5-kernels) | |
| + [#5.1: Background Color Change Within Scan Line](#background-color-change-within-scan-line) | |
| + [#5.x: Multicolor Player](#x-multicolor-player) | |
| + [Topic #6: Scrolling](#topic-6-scrolling) | |
| + [#6.1: Parallax Scrolling](#parallax-scrolling) | |
| + [#6.2: Multiple Scrolling Regions](#multiple-scrolling-regions) | |
| © Copyright 2014-2024 Rob McMullen, Licensed under the [CC A-SA 4.0 Intl](http://creativecommons.org/licenses/by-sa/4.0/). | |
| Last updated on Jan 03 2024. | |
| Created using [Sphinx](https://www.sphinx-doc.org/) 4.5.0. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment