aboutsummaryrefslogtreecommitdiff
path: root/io.s
blob: 98dc2190d68ba01440114f75d4170c4b1cc7dc34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
 ; 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 <wherever> ; 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.