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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
|
; ----------------------------------------------------------------------------
; 64x32 software text driver
; Uses 4x5 font in 5x6 character cells
; Based on disassembled COL80 driver
; This driver is missing quite a few things a full E: driver should have:
; - No support for cursor controls, insert/delete, or even clear screen
; (instead the control-codes are printed as normal characters, if they're
; above 31 decimal)
; - No support for low ASCII at all: any attempt to input or print a character
; in the range 0-31 decimal is just ignored (this includes the arrow keys)
; - Does not attempt to survive a warmstart
; - Will fail disastrously if an application sets COLCRS/ROWCRS to any
; out-of-range values
; - Only displays the cursor during get-byte operations
; - Backspace key is supported during get-byte, but printing a backspace
; with put-byte prints a tilde instead
; On the other hand, this driver is tiny, and plays nice with cc65's stdio
; support (though NOT conio!)
processor 6502 ; DASM-specific
; ----------------------------------------------------------------------------
include equates.inc
s_dev_open_lo = $E410 ; (not named in OS sources)
s_dev_open_hi = $E411 ; ""
k_dev_get_lo = $E424 ; ""
k_dev_get_hi = $E425 ; ""
; ----------------------------------------------------------------------------
; Constants
LAST_ROW = $1F
EOL = $9B
INVERTED_MASK = $F8
; ----------------------------------------------------------------------------
; Defaults
DEFAULT_TEXT_COLOR = $00
DEFAULT_BG_COLOR = $08
;DEFAULT_LMARGN = $00 ; not used
DEFAULT_RMARGN = $3F
; ----------------------------------------------------------------------------
; ZP storage
; Reuse some of the E:/S: devices' storage
; These all get recalculated on col64_putbyte entry,
; so it's OK to mix with GR.8 PLOT/DRAWTO stuff
seg.u "data"
org $5A ; aka OLDROW
mask_lo ds 1
mask_hi ds 1
screen_ptr_lo ds 1
screen_ptr_hi ds 1
font_index ds 1
shift_amount ds 1
line_count ds 1
glyph_data_lo ds 1
glyph_data_hi ds 1
; ----------------------------------------------------------------------------
; Non-ZP storage (cassette buffer for now)
org CASBUF
lo_nybble_flag ds 1
inverse_mask ds 1
line_buffer_index ds 1
line_buffer ds $80
seg "code"
; ----------------------------------------------------------------------------
; XEX segment header
START_ADDRESS_1 = $9A00
org START_ADDRESS_1-6
word $FFFF
word START_ADDRESS_1
word END_ADDR_1
; ----------------------------------------------------------------------------
; Font data should be aligned on a page boundary
; (for speed; still works unaligned). Each 5 bytes stores 2 glyphs
; side-by-side. When rendering into 5x6 cells, the bottom line and
; the left column are set to all 0, so the glyphs can take up the
; entire 4x5 space.
; Only character codes 32 through 127 are supported; this is 96
; characters. At 2 glyphs per 5 bytes, that's 96/2*5=240 bytes,
; which means the whole font fits into one page and can be
; accessed with X or Y indexed mode rather than (indirect),Y.
font_data:
include font4x5.inc
; ----------------------------------------------------------------------------
; Mask tables, used by setup_mask. Indexed by (COLCRS % 8), since they
; would repeat every 8 bytes anyway.
mask_lo_tab:
byte $07, $F8, $C1, $FE, $F0, $83, $FC, $E0
; In mask_hi_tab, $00 is never a valid mask, while $FF means "don't touch any
; bits in 2nd byte". As a minor optimization, $00 appears in the table in
; place of $FF. This allows us to just test the Z flag after loading the mask
; hi byte, instead of needing to compare against $FF.
mask_hi_tab:
byte $00, $3F, $00, $0F, $7F, $00, $1F, $00
; ----------------------------------------------------------------------------
; Indexed by COLCRS%8, how many bits to shift the font data right
; The mask_*_tab stuff above could be calculated at runtime from this,
; the tables are only there for speed.
shift_amount_tab:
byte $00, $05, $02, $07, $04, $01, $06, $03
; ----------------------------------------------------------------------------
; Line address tables, used by setup_screen_ptr.
line_addr_tab_hi:
byte $00, $00, $01, $02, $03, $04, $05, $06
byte $07, $08, $09, $0a, $0b, $0c, $0d, $0e
byte $0f, $0f, $10, $11, $12, $13, $14, $15
byte $16, $17, $18, $19, $1a, $1b, $1c, $1d
line_addr_tab_lo:
byte $00, $f0, $e0, $d0, $c0, $b0, $a0, $90
byte $80, $70, $60, $50, $40, $30, $20, $10
byte $00, $f0, $e0, $d0, $c0, $b0, $a0, $90
byte $80, $70, $60, $50, $40, $30, $20, $10
; ----------------------------------------------------------------------------
; Byte offset within scanline, indexed by COLCRS value
; Used by setup_screen_ptr
column_byte_tab:
byte $00, $00, $01, $01, $02, $03, $03, $04
byte $05, $05, $06, $06, $07, $08, $08, $09
byte $0A, $0A, $0B, $0B, $0C, $0D, $0D, $0E
byte $0F, $0F, $10, $10, $11, $12, $12, $13
byte $14, $14, $15, $15, $16, $17, $17, $18
byte $19, $19, $1A, $1A, $1B, $1C, $1C, $1D
byte $1E, $1E, $1F, $1F, $20, $21, $21, $22
byte $23, $23, $24, $24, $25, $26, $26, $27
; ----------------------------------------------------------------------------
; Handler table (HATABS will point to this).
; See the HATABS and EDITRV entries in Mapping the Atari for details.
col64_vector_tab:
word col64_open-1 ; OPEN vector
word col64_close-1 ; CLOSE vector
word col64_getbyte-1 ; GET BYTE vector
word col64_putbyte-1 ; PUT BYTE vector
word col64_close-1 ; GET STATUS vector
word col64_close-1 ; SPECIAL vector
jmp col64_init ; Jump to initialization code (JMP LSB/MSB)
; ----------------------------------------------------------------------------
; Assembly version of GRAPHICS 8+16 command.
init_graphics_8:
lda #$08 ; graphics mode 8
sta ICAX2Z
lda #$0C ; R/W access
sta ICAX1Z
jsr open_s_dev
; Set default colors
lda #DEFAULT_BG_COLOR
sta COLOR2
sta COLOR4
lda #DEFAULT_TEXT_COLOR
sta COLOR1
; Protect ourselves from the OS
lda #<START_ADDRESS_1
sta MEMTOP
lda #>START_ADDRESS_1
sta MEMTOP+1
rts
; ----------------------------------------------------------------------------
; Call the OPEN vector for the S: device, using the ROM vector table
; at $E410. The table stores address-minus-one of each routine, which is
; meant to actually be called via the RTS instruction (standard 6502
; technique, but confusing the first time you encounter it)
open_s_dev:
lda s_dev_open_hi
pha
lda s_dev_open_lo
pha
rts
; ----------------------------------------------------------------------------
; Callback for the internal get-one-byte, used by the OS to implement the
; CIO GET RECORD and GET BYTES commands. This routine takes no arguments,
; and returns the read byte in the accumulator.
; Internally, COL64 maintains a line buffer. Each time col64_getbyte is
; called, it returns the next character in the buffer. If the buffer's
; empty (or if the last call returned the last character), a new line
; of input is read from the user (and the first character is returned).
; This is exactly how the OS E: device works.
col64_getbyte:
lda BUFCNT
beq get_line
get_next_byte:
ldx line_buffer_index
lda line_buffer,x
dec BUFCNT
inc line_buffer_index
jmp return_success
; ----------------------------------------------------------------------------
; Get a line of input from the user, terminated by the Return key.
get_line:
lda #$00
sta BUFCNT
sta line_buffer_index
show_cursor:
lda #$00
sta TMPCHR
lda #INVERTED_MASK
sta inverse_mask
jsr render_glyph
jsr get_keystroke
cpy #$01
beq keystroke_ok
dey ; yes, we really care about 1-byte optimizations
sty line_buffer_index
sty BUFCNT
keystroke_ok:
cmp #$20
bcc show_cursor ; ignore low ASCII
cmp #EOL
bne check_backs_key
jmp return_key_hit
check_backs_key:
cmp #$7E
bne check_clear_key
jmp backs_key_hit
check_clear_key:
cmp #$7D
bne normal_key_hit
jmp clear_key_hit
normal_key_hit:
ldx BUFCNT
bpl buffer_character
; jmp beep ; if we implemented it...
jmp show_cursor
buffer_character:
sta line_buffer,x
jsr col64_putbyte
inc BUFCNT
jmp show_cursor
return_key_hit:
jsr print_space
lda #EOL
ldx BUFCNT
sta line_buffer,x
inc BUFCNT
jsr col64_putbyte
jmp get_next_byte
clear_key_hit:
; jsr clear_screen ; if we implemented it...
lda #$00
sta line_buffer_index
sta BUFCNT
jmp get_line
backs_key_hit:
jsr print_space
lda BUFCNT
beq backs_key_done
dec COLCRS
lda COLCRS
clc
adc #$01
cmp LMARGN
bne backs_same_line
lda RMARGN
sta COLCRS
dec ROWCRS
backs_same_line:
dec BUFCNT
backs_key_done:
jmp show_cursor
; ----------------------------------------------------------------------------
; Print a space character at the current cursor position. Does not
; update the cursor position.
print_space:
lda #$00
sta inverse_mask
sta TMPCHR
jsr render_glyph
rts
; ----------------------------------------------------------------------------
; Get a keystroke (blocking). Just calls the OS K: get-one-byte routine
; (call by pushing address-minus-one then doing an RTS)
get_keystroke:
lda k_dev_get_hi
pha
lda k_dev_get_lo
pha
rts
; ----------------------------------------------------------------------------
; Unimplemented CIO callbacks here. Also various other routines jump here
; to return success to the caller.
col64_close:
return_success:
ldy #$01
rts
; ----------------------------------------------------------------------------
; CIO OPEN command callback
col64_open:
jsr init_graphics_8
lda #$00
sta ROWCRS
sta COLCRS
sta BUFCNT
sta LMARGN
lda #DEFAULT_RMARGN
sta RMARGN
rts
; ----------------------------------------------------------------------------
; CIO PUT BYTE command callback
; The byte to put is passed to us in the accumulator.
col64_putbyte:
; EOL (decimal 155)?
cmp #EOL
;;; bne check_clear
bne regular_char
lda RMARGN
sta COLCRS
jmp skip_write
;;;check_clear:
;;; ; save memory by not including clear_screen
;;; ; (also, this lets us print the } character)
;;; ; Clear (decimal 125)?
;;; cmp #$7D
;;; bne regular_char
;;; jmp clear_screen
;;; .endif
;;;
; See if this is an inverse video char (code >= 128)
regular_char:
tax
bpl not_inverse
lda #INVERTED_MASK
sta inverse_mask
bne skip_ninv
not_inverse:
lda #$00
sta inverse_mask
skip_ninv:
txa
and #$7F
sec
sbc #$20
bcs not_low_ascii
jmp return_success
not_low_ascii:
sta TMPCHR
lda DINDEX ; OS stores current graphics mode here
cmp #$08
beq graphics_ok
; If we're not in GRAPHICS 8 mode, reinitialize ourselves
jsr col64_open
graphics_ok:
jsr render_glyph
skip_write:
; Move the cursor 1 space to the right. This will
; advance us to the next line if we're at the margin,
; and scroll the screen if needed
jsr advance_cursor
; Could implement SSFLAG logic here
;check_ssflag:
; The OS keyboard interrupt handler will toggle SSFLAG (start/stop fla
; any time the user presses ctrl-1
;lda SSFLAG
;bne check_ssflag
jmp return_success
; ----------------------------------------------------------------------------
; Call the routines that actually print the character.
; render_glyph prints the character in TMPCHR at the current
; COLCRS and ROWCRS, and does NOT advance the cursor.
; TMPCHR should already have bit 7 stripped; render_glyph will
; use inverse_mask, so the caller should have set that up as well.
render_glyph:
jsr setup_mask
jsr setup_screen_ptr
jsr setup_font_index
jmp write_font_data
; ----------------------------------------------------------------------------
; mask is used to avoid overwriting pixels outside the character cell
; we're currently writing. Since 5 pixel wide cells don't align on byte
; boundaries in screen RAM, we have to read/modify/write 2 bytes, and
; the mask also has to be 2 bytes wide.
; Also we set up shift_amount here.
setup_mask:
lda COLCRS
and #$07
tax
lda mask_lo_tab,x
sta mask_lo
lda mask_hi_tab,x
sta mask_hi
lda shift_amount_tab,x
sta shift_amount
rts
; ----------------------------------------------------------------------------
; Make (screen_ptr_lo) point to the first byte of screen RAM on the top scanline
; of the current char cell. Assumes COLCRS/ROWCRS are never out of range!
setup_screen_ptr:
; first the row... table lookup quicker than mult by 240
ldx ROWCRS
clc
lda SAVMSC
adc line_addr_tab_lo,x
sta screen_ptr_lo
lda SAVMSC+1
adc line_addr_tab_hi,x
sta screen_ptr_hi
; now do the column
ldx COLCRS
lda screen_ptr_lo
clc
adc column_byte_tab,x
sta screen_ptr_lo
lda #0
adc screen_ptr_hi
sta screen_ptr_hi
rts
; ----------------------------------------------------------------------------
; Set up font_index to point to the font_data bitmap for the character in
; TMPCHR. Also sets lo_nybble_flag to let the caller know whether the
; bitmap is in the upper or lower 4 bits of the bytes pointed to.
; Calculation is:
; lo_nybble_flag = (TMPCHR & 1) ? $FF : $00;
; font_index = (TMPCHR >> 1) * 5;
setup_font_index:
lda #$00
sta lo_nybble_flag
lda TMPCHR
lsr ; a = (TMPCHR >> 1)
tay ; y = a
bcc font_hi_nybble
dec lo_nybble_flag ; = $FF
font_hi_nybble:
clc
asl ; a *= 2
asl ; a *= 2
sta font_index
tya
adc font_index
sta font_index
rts
; ----------------------------------------------------------------------------
; When write_font_data is called:
; - font_index is the 1-byte index into font_data where the current glyph is
; - lo_nybble_flag is 0 for high nybble of glyph data, $FF for low
; - mask_lo/hi is our 16-bit pixel mask (1's are "leave original data")
; - shift_amount is # of times to shift glyph data right
; - screen_ptr_lo/hi points to the 1st byte on the top line
; Loop 5 times, each time thru the loop:
; - extract 4-bit glyph data, store in glyph_data_lo
; - shift right shift_amount times
; ...write data...
; - add 40 to 16-bit screen_ptr_lo/hi (to get to next line)
write_font_data:
lda #$05
sta line_count
wfont_line_loop:
lda #$00
tay
sta glyph_data_hi
ldx font_index
lda font_data,x
bit lo_nybble_flag
beq use_hi_nybble
asl
asl
asl
asl
use_hi_nybble: ; 4-bit glyph data now in hi nybble
and #$F0
eor inverse_mask
sta glyph_data_lo
ldx shift_amount
beq wfont_no_shift
wfont_shift_loop:
lsr glyph_data_lo
ror glyph_data_hi
dex
bne wfont_shift_loop
wfont_no_shift:
lda mask_lo
and (screen_ptr_lo),y
ora glyph_data_lo
sta (screen_ptr_lo),y
lda mask_hi
beq wfont_skip_hi
iny
and (screen_ptr_lo),y
ora glyph_data_hi
sta (screen_ptr_lo),y
wfont_skip_hi:
dec line_count
bmi wfont_done
bne wfont_not_bottom
stx font_index ; X always 0: for last line, cheat and use the space glyph
stx lo_nybble_flag
wfont_not_bottom:
lda #$28 ; 40 bytes to next line
clc
adc screen_ptr_lo
sta screen_ptr_lo
bcc wfont_noinc
inc screen_ptr_hi
wfont_noinc:
inc font_index
bne wfont_line_loop ; branch always
wfont_done:
rts
; ----------------------------------------------------------------------------
; Not the fastest scroller in the west... TODO: make faster :)
; glyph_data_lo points to line N
; screen_ptr_lo points to line N+1
scroll_screen:
lda #0
sta COLCRS
sta ROWCRS
jsr setup_screen_ptr
scroll_line_loop:
lda screen_ptr_lo
sta glyph_data_lo
lda screen_ptr_hi
sta glyph_data_hi
ldx ROWCRS
cpx #LAST_ROW
beq scroll_blank
inx
stx ROWCRS
jsr setup_screen_ptr
ldy #0
scroll_byte_loop:
lda (screen_ptr_lo),y
sta (glyph_data_lo),y
iny
cpy #$F0
bne scroll_byte_loop
beq scroll_line_loop
scroll_blank:
jsr setup_screen_ptr
ldy #0
tya
sblank_loop:
sta (screen_ptr_lo),y
iny
cpy #$F0
bne sblank_loop
rts
; ----------------------------------------------------------------------------
; Move the cursor one space to the right (to the next line if at the margin,
; and scroll screen if on the last row)
advance_cursor:
inc COLCRS
lda RMARGN
cmp COLCRS
bcs same_line
lda LMARGN
sta COLCRS
lda ROWCRS
cmp #LAST_ROW
bcc no_scroll
jsr scroll_screen
; Move to last row after scrolling
lda #LAST_ROW-1
sta ROWCRS
no_scroll:
inc ROWCRS
same_line:
rts
; ----------------------------------------------------------------------------
; Initialization. If we don't want the handler to survive a warmstart, we
; can load this bit into e.g. the cassette buffer (throw away after running)
col64_init:
ldy #$00
next_hatab_slot:
lda HATABS,y
beq register_x_handler
iny
iny
iny
cpy #$20
bcc next_hatab_slot
jmp return_success
register_x_handler:
lda #$58
sta HATABS,y
lda #<col64_vector_tab
iny
sta HATABS,y
lda #>col64_vector_tab
iny
sta HATABS,y
jmp return_success
main_entry_point:
jsr col64_init
lda #$0C
sta ICCOM
ldx #$00
jsr CIOV
lda #$58
sta font_index
lda #$03
sta ICCOM
lda #font_index
sta ICBAL
lda #$00
sta ICBAH
ldx #$00
jsr CIOV
ldy #$07
lda #<col64_vector_tab
sta HATABS,y
lda #>col64_vector_tab
iny
sta HATABS,y
no_e_handler:
lda #<START_ADDRESS_1
sta MEMTOP
lda #>START_ADDRESS_1
sta MEMTOP+1
jmp return_success
END_ADDR_1 = *-1
; XEX segment (run address)
word INITAD
word INITAD+1
word main_entry_point
|