; 20220930 bkw, aka Urchlay on libera IRC, aka urchlay@slackware.uk: ; Example code for calling CIO through the back door, like BASIC does. ; Provided under the terms of the WTFPL: Do WTF you want to with this. ; Verbose documentation here. To skip to the actual code, search for ; three ; characters. ; There's a lot of old code that calls the OS ROM's print-character ; and read-character addresses directly. These were never published ; by Atari as part of their API... and in fact they changed between ; the 400/800 and XL/XE, which is a major reason why certain software ; is "OS B only" or "XL only". People coming from other platforms such ; at the C=64 or Apple II were used to their ROMs having fixed ; addresses to JSR to, for print-character and read-character, and ; so they used $F6A4 to print and $F6E2 to read... which were ; never guaranteed by Atari not to change. And they did change, ; in the XL OS. ; The pedantically correct way to print a character is to set up ; a 1-byte buffer for IOCB #0, and call CIOV with ICCOM set to ; $09 (aka 'put record'). Even Atari decided this was too much ; work, so they also provided a handy "put-one-byte" vector in ; EDITRV, which gets copied to ICPTL/H when the OS opens the E: ; device... BASIC uses this to print characters, and you can, too. It ; works on any revision of the Atari OS, because it's part of the ; OS specification: if it *didn't* work on some OS version, neither ; would Atari BASIC, which would count as a show-stopper! ; Atari didn't provide a similar slot in the IOCB for the ; get-one-byte vector... and generally, if you're interested in ; reading input one character at a time, you don't want IOCB #0 (E:) ; anyway. You want the K: device (which returns immediately after ; each keypress, rather than waiting for a whole line of input). The ; correct way to read from the keyboard is to open an IOCB (other ; than #0) to the K: device, set up that IOCB, including a 1-byte ; buffer, and call CIOV with ICCOM set to $05 (aka get-record). But ; it turns out that the K: device has a get-one-byte routine that (a) ; can be found in a published location (KEYBDV table) that doesn't ; change with ROM revision, and (b) works without even having an IOCB ; open for K:. ; The vectors are stored as "address minus one", because they're ; intended to be called via the RTS instruction (probably Atari did ; this because the JSR instruction doesn't have an indexed mode like ; JMP does). Read on, to see how to call them. The calling sequence ; isn't as convenient as the illegal entry points (or the Commodore's ; Kernal, which does publish print-acumulator and get-1-byte ; vectors), but it's a lot less code than the 'proper' IOCB setup ; would be. And if you copy/paste from this file, you just call these ; subroutines in your code (as convenient as the Commodore). ; You are welcome to copy the code in this file into your own ; project. It's unencumbered: I release it under the WTFPL. I would ; just say it's public domain, but I have been told by people who ; ought to know that some countries don't actually recognize public ; domain in their law. WTFPL explicitly says you can do whatever you ; want with this. ; Examples: ; You could make your own "memo pad mode" with this: ; ; main: ; jsr getchr ; jsr printchr ; jmp main ; Print a null-terminated string, up to 256 bytes long: ; ; ldx #0 ; msgloop: ; lda message,x ; beq msgdone ; jsr printchrx ; inx ; bne msgloop ; msgdone: ; rts ; or whatever other code goes here... ; ; message: .byte "Hello, World!",$9b,$00 ; Environment: ; The code depends on a few symbols (equates) being defined. How you ; do this depends on the assembler you're using. ; .include "atari.inc" ; for ca65 ; .include "sysequ.m65" ; for atasm ; include atari8.h ; dasm, if it actually had this file :( ; If your assembler doesn't have a file of Atari symbols, use: ; ICPTL = $0346 ; ICPTH = $0347 ; KEYBDV = $E420 ; EDITRV = $E400 ; only if you change getchr to use this. ; ...of course your assembler might want EQU or .EQU instead of = signs. ; .org ; your assembler may want org without the dot, or *= ;;; Start of actual code. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Subroutine: printchr ; ; Print ATASCII character in A, without preserving registers. ; Assumes IOCB #0 is opened on the E: device, which is how the ; Atari boots up. Uses "call-by-RTS" (weird looking but standard). ; ; Note that this will work even if the E: handler has been replaced, ; e.g. with COL80 or COL64 or such. ; ; Hint: if you want to print graphics instead of actual cursor controls ; or insert/delete/clear/etc, print an Escape character ($1B) before each, ; or set DSPFLG ($2FE) to a non-zero value. ; printchr: tay ; save A (character to print). lda ICPTH ; set up stack, so it looks like a JSR to the pha ; put-one-byte address for E:, lda ICPTL ; which the OS has conveniently stashed pha ; in IOCB #0. tya ; restore A (put-one-byte argument). rts ; "return" to put-one-byte, which will return to printchr's caller. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Subroutine: getchr ; ; Read ATASCII character from keyboard, return in A, without ; preserving registers. ; ; Uses the published and immutable KEYBDV address in the ROM, meaning ; it (a) doesn't require an IOCB open to the K: device, and (b) ; it will not use any replacement K: handler that might be loaded ; (however, unlike E:, replacing the OS K: device is so rare that ; I've never heard of it being done). ; ; Hint: This is a "blocking" function call: it waits until a key is ; pressed. If you want to poll (only read input when it's available), ; check CH ($02FC): if it's $FF, no key is pressed. ; ; Note: if you really do want to read from the E: device, change ; the two KEYBDV's below to EDITRV. E: will read an entire line, ; including editing (backspace, insert/delete, cursor moves, etc) ; the first time it's called, and return only the first character ; read. Further calls will return the rest of the characters, one at ; a time, with $9B (EOL) as the last one. ; getchr: lda KEYBDV+5 ; set up stack, so it looks like a JSR to the pha ; get-one-byte address for K:, lda KEYBDV+4 ; which the OS ROM keeps in the pha ; KEYBDV table ($E420). rts ; "return" to get-one-byte, which will return to getchr's caller. ; These next two are 'wrappers' for the above, which preserve ; the X register. Very convenient for use in a loop. If you don't ; need these, don't copy them into your code. If you do need them, ; remember that they call printchr and getchr, so you have to copy ; those also. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Subroutine: printchrx ; ; Print ATASCII character in A, preserving X register. ; ; Preserves X register (but nothing else), so it can be called from ; within a loop that uses X for a counter, without having to worry ; about it. ; ; On exit, A holds a copy of the X register, if you can think of ; a use for that. ; ; Calls printchr. ; printchrx: tay ; save A (character to print). txa ; save X, pha ; on stack. tya ; restore A. jsr printchr ; print the character. pla ; restore X, tax ; from stack. rts ; this a regular RTS (returns to printchrx's caller). ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Subroutine: getchrx ; ; Read ATASCII char from keyboard, return in A, preserving X register. ; Actually, the return value here is also in Y, if you can think of a ; use for that. ; ; Calls getchr. ; getchrx: txa ; save X, pha ; on stack. jsr getchr ; get the character. tay ; save A (our return value). pla ; restore X, tax ; from stack. tya ; restore return value to A. rts ; regular RTS.