Terminal program for MailStation devices
1; vim:syntax=z8a:ts=8
2;
3; msTERM
4; putchar
5;
6; Copyright (c) 2019 joshua stein <jcs@jcs.org>
7;
8; Permission to use, copy, modify, and distribute this software for any
9; purpose with or without fee is hereby granted, provided that the above
10; copyright notice and this permission notice appear in all copies.
11;
12; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19;
20
21 .module putchar
22
23 .include "mailstation.inc"
24
25 ; screen contents (characters) array in upper memory
26 .equ _screenbuf, #0xc000
27 .equ _screenbufend, #0xc3ff
28
29 ; per-character attributes array in upper memory
30 .equ _screenattrs, #0xc400
31 .equ _screenattrsend, #0xc7ff
32
33 .area _DATA
34
35font_data::
36 .include "font/spleen-5x8.inc"
37
38 ; lookup table for putchar
39 ; left-most 5 bits are col group for lcd_cas
40 ; last 3 bits are offset into col group
41cursorx_lookup_data::
42 .include "cursorx_lookup.inc"
43
44_cursorx:: ; cursor x position, 0-indexed
45 .db #0
46_cursory:: ; cursor y position, 0-indexed
47 .db #0
48_saved_cursorx:: ; cursor x position, 0-indexed
49 .db #0
50_saved_cursory:: ; cursor y position, 0-indexed
51 .db #0
52_putchar_sgr:: ; current SGR for putchar()
53 .db #0
54
55
56 .area _CODE
57
58; void lcd_cas(unsigned char col)
59; enable CAS, address the LCD column col (in h), and disable CAS
60_lcd_cas::
61 push ix
62 ld ix, #0
63 add ix, sp
64 push de
65 push hl
66 ld hl, (p2shadow)
67 ld a, (hl)
68 and #0b11110111 ; CAS(0) - turn port2 bit 3 off
69 ld (hl), a
70 out (#0x02), a ; write p2shadow to port2
71 ld de, #LCD_START
72 ld a, 4(ix)
73 ld (de), a ; write col argument
74 ld a, (hl)
75 or #0b00001000 ; CAS(1) - turn port2 bit 3 on
76 ld (hl), a
77 out (#0x02), a
78 pop hl
79 pop de
80 ld sp, ix
81 pop ix
82 ret
83
84
85; void clear_screen(void)
86_clear_screen::
87 di
88 push hl
89 in a, (#SLOT_DEVICE)
90 ld h, a
91 in a, (#SLOT_PAGE)
92 ld l, a
93 push hl
94 ld a, #DEVICE_LCD_RIGHT
95 out (#SLOT_DEVICE), a
96 call _clear_lcd_half
97 ld a, #DEVICE_LCD_LEFT
98 out (#SLOT_DEVICE), a
99 call _clear_lcd_half
100 pop hl
101 ld a, h
102 out (#SLOT_DEVICE), a
103 ld a, l
104 out (#SLOT_PAGE), a
105 pop hl
106 ei
107 ret
108
109_clear_screen_bufs::
110 di
111 push bc
112 push de
113 push hl
114 xor a
115 ld (_cursorx), a
116 ld (_cursory), a
117 ld (_saved_cursorx), a
118 ld (_saved_cursory), a
119 ld (_putchar_sgr), a
120zero_screenbuf:
121 ld hl, #_screenbuf
122 ld de, #_screenbuf + 1
123 ld bc, #_screenbufend - _screenbuf
124 ld (hl), #' '
125 ldir
126zero_screenattrs:
127 ld hl, #_screenattrs
128 ld de, #_screenattrs + 1
129 ld bc, #_screenattrsend - _screenattrs
130 ld (hl), #0
131 ldir
132clear_screen_out:
133 pop hl
134 pop de
135 pop bc
136 ei
137 ret
138
139
140; void clear_lcd_half(void)
141; zero out the current LCD module (must already be in SLOT_DEVICE)
142; from v2.54 firmware at 0x2490
143_clear_lcd_half::
144 push bc
145 push de
146 ld b, #20 ; do 20 columns total
147clear_lcd_column:
148 ld h, #0
149 ld a, b
150 dec a ; columns are 0-based
151 ld l, a
152 push hl
153 call _lcd_cas
154 pop hl
155 push bc ; preserve our column counter
156 ld hl, #LCD_START
157 ld (hl), #0 ; zero out hl, then copy it to de
158 ld de, #LCD_START + 1 ; de will always be the next line
159 ld bc, #128 - 1 ; iterate (LCD_HEIGHT - 1) times
160 ldir ; ld (de), (hl), bc-- until 0
161 pop bc ; restore column counter
162 djnz clear_lcd_column ; column--, if not zero keep going
163clear_done:
164 pop de
165 pop bc
166 ret
167
168
169; void redraw_screen(void)
170_redraw_screen::
171 push bc
172 push de
173 push hl
174 ld b, #0
175redraw_rows:
176 ld d, b ; store rows in d
177 ld b, #0
178redraw_cols:
179 push bc ; XXX figure out what is corrupting
180 push de ; bc and de in stamp_char, these shouldn't be needed
181 push hl
182 ld h, #0 ; cols
183 ld l, b
184 push hl
185 ld h, #0 ; rows
186 ld l, d
187 push hl
188 call _stamp_char
189 pop hl
190 pop hl
191 pop hl
192 pop de
193 pop bc
194redraw_cols_next:
195 inc hl
196 inc b
197 ld a, b
198 cp #LCD_COLS
199 jr nz, redraw_cols
200 ld b, d
201 inc b
202 ld a, b
203 cp #LCD_ROWS
204 jr nz, redraw_rows
205redraw_screen_out:
206 pop hl
207 pop de
208 pop bc
209 ret
210
211
212; void scroll_lcd(void)
213; scroll entire screen up by FONT_HEIGHT rows, minus statusbar
214_scroll_lcd::
215 di
216 push bc
217 push de
218 push hl
219 in a, (#SLOT_DEVICE)
220 ld h, a
221 in a, (#SLOT_PAGE)
222 ld l, a
223 push hl
224 ld a, #DEVICE_LCD_LEFT
225 out (#SLOT_DEVICE), a
226 call _scroll_lcd_half
227 ld a, #DEVICE_LCD_RIGHT
228 out (#SLOT_DEVICE), a
229 call _scroll_lcd_half
230 pop hl
231 ld a, h
232 out (#SLOT_DEVICE), a
233 ld a, l
234 out (#SLOT_PAGE), a
235shift_bufs:
236 ld b, #0
237screenbuf_shift_loop:
238 ld h, b
239 ld l, #0
240 call screenbuf_offset
241 ld de, #_screenbuf
242 add hl, de ; hl = screenbuf[b * LCD_COLS]
243 push hl
244 ld de, #LCD_COLS
245 add hl, de ; hl += LCD_COLS
246 pop de ; de = screenbuf[b * LCD_COLS]
247 push bc
248 ld bc, #LCD_COLS
249 ldir ; ld (de), (hl), de++, hl++, bc--
250 pop bc
251 inc b
252 ld a, b
253 cp #TEXT_ROWS - 1
254 jr nz, screenbuf_shift_loop
255screenattrs_shift:
256 ld b, #0
257screenattrs_shift_loop:
258 ld h, b
259 ld l, #0
260 call screenbuf_offset
261 ld de, #_screenattrs
262 add hl, de ; hl = screenattrs[b * LCD_COLS]
263 push hl
264 ld de, #LCD_COLS
265 add hl, de
266 pop de
267 push bc
268 ld bc, #LCD_COLS
269 ldir
270 pop bc
271 inc b
272 ld a, b
273 cp #TEXT_ROWS - 1
274 jr nz, screenattrs_shift_loop
275last_row_zero:
276 ld a, #TEXT_ROWS - 1
277 ld h, a
278 ld l, #0
279 call screenbuf_offset
280 ld de, #_screenbuf
281 add hl, de
282 ld d, #0
283 ld e, #LCD_COLS - 1
284 add hl, de
285 ld b, #LCD_COLS
286 ld a, (_putchar_sgr)
287last_row_zero_loop:
288 ld (hl), #' '
289 dec hl
290 djnz last_row_zero_loop
291scroll_lcd_out:
292 pop hl
293 pop de
294 pop bc
295 ei
296 ret
297
298
299; void scroll_lcd_half(void)
300; scroll current LCD module up by FONT_HEIGHT rows, minus statusbar and
301; zero out the last line of text (only to the LCD)
302_scroll_lcd_half::
303 push ix
304 ld ix, #0
305 add ix, sp
306 push bc
307 push de
308 push hl
309 ; alloc 2 bytes on the stack for local storage
310 push hl
311 ld a, #LCD_HEIGHT - (FONT_HEIGHT * 2) ; iterations of pixel row moves
312scroll_init:
313 ld -1(ix), a ; store iterations
314 ld b, #20 ; do 20 columns total
315scroll_lcd_column:
316 ld -2(ix), b ; store new column counter
317 ld a, b
318 sub #1 ; columns are 0-based
319 ld h, #0
320 ld l, a
321 push hl
322 call _lcd_cas
323 pop hl
324scroll_rows:
325 ld b, #0
326 ld c, -1(ix) ; bc = row counter
327 ld hl, #LCD_START + 8 ; start of next line
328 ld de, #LCD_START
329 ldir ; ld (de), (hl), bc-- until 0
330scroll_zerolast:
331 ld hl, #LCD_START
332 ld d, #0
333 ld e, -1(ix)
334 add hl, de
335 ld b, #FONT_HEIGHT
336scroll_zerolastloop: ; 8 times: zero hl, hl++
337 ld (hl), #0
338 inc hl
339 djnz scroll_zerolastloop
340 ld b, -2(ix)
341 djnz scroll_lcd_column ; column--, if not zero keep going
342 pop hl
343 pop de
344 pop bc
345 ld sp, ix
346 pop ix
347 ret
348
349
350; address of screenbuf or screenattrs offset for a row/col in hl, returns in hl
351screenbuf_offset:
352 push bc
353 push de
354 ; uses hl
355 ex de, hl
356 ld hl, #0
357 ld a, d ; row
358 cp #0
359 jr z, multiply_srow_out ; only add rows if > 0
360 ld bc, #LCD_COLS
361multiply_srow:
362 add hl, bc
363 dec a
364 cp #0
365 jr nz, multiply_srow
366multiply_srow_out:
367 ld d, #0 ; col in e
368 add hl, de ; hl = (row * LCD_COLS) + col
369 pop de
370 pop bc
371 ret ; hl
372
373
374; void stamp_char(unsigned int row, unsigned int col)
375; row at 4(ix), col at 6(ix)
376_stamp_char::
377 push ix
378 ld ix, #0
379 add ix, sp
380 push bc
381 push de
382 push hl
383 ld hl, #-15 ; stack bytes for local storage
384 add hl, sp
385 ld sp, hl
386 in a, (#SLOT_DEVICE)
387 ld -3(ix), a ; stack[-3] = old slot device
388 in a, (#SLOT_PAGE)
389 ld -4(ix), a ; stack[-4] = old slot page
390find_char:
391 ld h, 4(ix)
392 ld l, 6(ix)
393 call screenbuf_offset
394 push hl
395 ld de, #_screenbuf
396 add hl, de ; hl = screenbuf[(row * LCD_COLS) + col]
397 ld a, (hl)
398 ld -5(ix), a ; stack[-5] = character to stamp
399 pop hl
400 ld de, #_screenattrs
401 add hl, de ; hl = screenattrs[(row * LCD_COLS) + col]
402 ld a, (hl)
403 ld -6(ix), a ; stack[-6] = character attrs
404calc_font_data_base:
405 ld h, #0
406 ld l, -5(ix) ; char
407 add hl, hl ; hl = char * FONT_HEIGHT (8)
408 add hl, hl
409 add hl, hl
410 ld de, #font_data
411 add hl, de
412 ld -7(ix), l
413 ld -8(ix), h ; stack[-8,-7] = char font data base addr
414calc_char_cell_base:
415 ld h, #0
416 ld l, 4(ix) ; row
417 add hl, hl
418 add hl, hl
419 add hl, hl ; hl = row * FONT_HEIGHT (8)
420 ld de, #LCD_START
421 add hl, de ; hl = 4038 + (row * FONT_HEIGHT)
422 ld -9(ix), l
423 ld -10(ix), h ; stack[-10,-9] = lcd char cell base
424fetch_from_table:
425 ld a, 6(ix) ; col
426 ld hl, #cursorx_lookup_data
427 ld b, #0
428 ld c, a
429 add hl, bc
430 ld b, (hl)
431 ld a, b
432pluck_col_group:
433 and #0b11111000 ; upper 5 bits are col group
434 srl a
435 srl a
436 srl a
437 ld -11(ix), a ; stack[-11] = col group
438pluck_offset:
439 ld a, b
440 and #0b00000111 ; lower 3 bits are offset
441 ld -12(ix), a ; stack[-12] = offset
442 ld -15(ix), #0 ; stack[-15] = previous lcd col
443 ld d, #FONT_HEIGHT ; for (row = FONT_HEIGHT; row >= 0; row--)
444next_char_row:
445 ld a, d
446 dec a
447 ld h, -8(ix) ; char font data base
448 ld l, -7(ix)
449 ld b, #0
450 ld c, a
451 add hl, bc
452 ld a, (hl) ; font_addr + (char * FONT_HEIGHT) + row
453 ld b, -6(ix)
454 bit #ATTR_BIT_REVERSE, b
455 jr nz, reverse
456 bit #ATTR_BIT_CURSOR, b
457 jr nz, reverse
458 jr not_reverse
459reverse:
460 cpl ; flip em
461 and #0b00011111 ; mask off bits not within FONT_WIDTH
462not_reverse:
463 ld -13(ix), a ; stack[-13] = working font data
464 ld a, -6(ix)
465 bit #ATTR_BIT_UNDERLINE, a
466 jr z, not_underline
467 ld a, d
468 cp #FONT_HEIGHT
469 jr nz, not_underline
470underline:
471 ld -13(ix), #0xff
472not_underline:
473 ld a, 6(ix) ; col
474 cp #LCD_COLS / 2 ; assume a char never spans both LCD sides
475 jr nc, rightside
476leftside:
477 ld a, #DEVICE_LCD_LEFT
478 jr swap_lcd
479rightside:
480 ld a, #DEVICE_LCD_RIGHT
481swap_lcd:
482 out (#SLOT_DEVICE), a
483 ld e, #FONT_WIDTH ; for (col = FONT_WIDTH; col > 0; col--)
484next_char_col: ; inner loop, each col of each row
485 ld -14(ix), #0b00011111 ; font data mask that will get shifted
486determine_cas:
487 ld c, #0
488 ld b, -11(ix) ; col group
489 ld a, -12(ix) ; bit offset
490 add #FONT_WIDTH
491 sub e ; if offset+(5-col) is >= 8, advance col
492 cp #LCD_COL_GROUP_WIDTH
493 jr c, skip_advance ; if a >= 8, advance (dec b)
494 dec b
495 ld c, -12(ix) ; bit offset
496 ld a, #LCD_COL_GROUP_WIDTH
497 sub c
498 ld c, a ; c = number of right shifts
499skip_advance:
500do_lcd_cas:
501 ld a, -15(ix) ; previous lcd cas
502 cp b
503 jr z, prep_right_shift
504 ld h, #0
505 ld l, b
506 push hl
507 call _lcd_cas
508 pop hl
509 ld -15(ix), b ; store lcd col for next round
510 ; if this character doesn't fit entirely in one lcd column, we need to
511 ; span two of them and on the left one, shift font data and masks right
512 ; to remove right-most bits that will be on the next column
513prep_right_shift:
514 ld a, c
515 cp #0
516 jr z, prep_left_shift
517 ld b, c
518 ld c, -14(ix) ; matching mask 00011111
519 ld a, -13(ix) ; load font data like 00010101
520right_shift:
521 srl a ; shift font data right #b times
522 srl c ; and mask to match
523 djnz right_shift ; -> 10101000
524 ld -14(ix), c
525 jr done_left_shift
526prep_left_shift:
527 ld c, -14(ix) ; mask
528 ld a, -12(ix) ; (bit offset) times, shift font data
529 cp #0
530 ld b, a
531 ld a, -13(ix) ; read new font data
532 jr z, done_left_shift
533left_shift:
534 sla a
535 sla c
536 djnz left_shift
537done_left_shift:
538 ld b, a
539 ld a, c
540 cpl
541 ld -14(ix), a ; store inverted mask
542 ld a, b
543read_lcd_data:
544 ld h, -10(ix)
545 ld l, -9(ix)
546 ld b, a
547 ld a, d
548 dec a
549 ld c, a
550 ld a, b
551 ld b, #0
552 add hl, bc ; hl = 4038 + (row * FONT_HEIGHT) + row - 1
553 ld b, a ; store new font data
554 ld a, (hl) ; read existing cell data
555 and -14(ix) ; mask off new char cell
556 or b ; combine data into cell
557 ld (hl), a
558 dec e
559 jp nz, next_char_col
560 dec d
561 jp nz, next_char_row
562stamp_char_out:
563 ld a, -3(ix) ; restore old slot device
564 out (#SLOT_DEVICE), a
565 ld a, -4(ix) ; restore old slot page
566 out (#SLOT_PAGE), a
567 ld hl, #15 ; remove stack bytes
568 add hl, sp
569 ld sp, hl
570 pop hl
571 pop de
572 pop bc
573 ld sp, ix
574 pop ix
575 ret
576
577
578; void uncursor(void)
579; remove cursor attribute from old cursor position
580_uncursor::
581 push de
582 push hl
583 ld a, (_cursory)
584 ld h, a
585 ld a, (_cursorx)
586 ld l, a
587 call screenbuf_offset
588 ld de, #_screenattrs
589 add hl, de ; screenattrs[(cursory * TEXT_COLS) + cursorx]
590 ld a, (hl)
591 res #ATTR_BIT_CURSOR, a ; &= ~(ATTR_CURSOR)
592 ld (hl), a
593 ld a, (_cursorx)
594 ld l, a
595 push hl
596 ld a, (_cursory)
597 ld l, a
598 push hl
599 call _stamp_char
600 pop hl
601 pop hl
602 pop hl
603 pop de
604 ret
605
606; void recursor(void)
607; force-set cursor attribute
608_recursor::
609 push de
610 push hl
611 ld a, (_cursory)
612 ld h, a
613 ld a, (_cursorx)
614 ld l, a
615 call screenbuf_offset
616 ld de, #_screenattrs
617 add hl, de ; screenattrs[(cursory * TEXT_COLS) + cursorx]
618 ld a, (hl)
619 set #ATTR_BIT_CURSOR, a
620 ld (hl), a
621 pop hl
622 pop de
623 ret
624
625
626; int putchar(int c)
627_putchar::
628 push ix
629 ld ix, #0
630 add ix, sp ; char to print is at 4(ix)
631 push de
632 push hl
633 call _uncursor
634 ld a, 4(ix)
635 cp #'\b' ; backspace
636 jr nz, not_backspace
637backspace:
638 ld a, (_cursorx)
639 cp #0
640 jr nz, cursorx_not_zero
641 ld a, (_cursory)
642 cp #0
643 jp z, putchar_fastout ; cursorx/y at 0,0, nothing to do
644 dec a
645 ld (_cursory), a ; cursory--
646 ld a, #LCD_COLS - 2
647 ld (_cursorx), a
648 jp putchar_draw_cursor
649cursorx_not_zero:
650 dec a
651 ld (_cursorx), a ; cursorx--;
652 jp putchar_draw_cursor
653not_backspace:
654 cp #'\r'
655 jr nz, not_cr
656 xor a
657 ld (_cursorx), a ; cursorx = 0
658 jr not_crlf
659not_cr:
660 cp #'\n'
661 jr nz, not_crlf
662 xor a
663 ld (_cursorx), a ; cursorx = 0
664 ld a, (_cursory)
665 inc a
666 ld (_cursory), a ; cursory++
667not_crlf:
668 ld a, (_cursorx)
669 cp #LCD_COLS
670 jr c, not_longer_text_cols ; cursorx < TEXT_COLS
671 xor a
672 ld (_cursorx), a ; cursorx = 0
673 ld a, (_cursory)
674 inc a
675 ld (_cursory), a
676not_longer_text_cols:
677 ld a, (_cursory)
678 cp #TEXT_ROWS
679 jr c, scroll_out
680scroll_up_screen:
681 call _scroll_lcd
682 xor a
683 ld (_cursorx), a
684 ld a, #TEXT_ROWS - 1
685 ld (_cursory), a ; cursory = TEXT_ROWS - 1
686scroll_out:
687 ld a, 4(ix)
688 cp a, #'\r'
689 jr z, cr_or_lf
690 cp a, #'\n'
691 jr z, cr_or_lf
692 jr store_char_in_buf
693cr_or_lf:
694 jp putchar_draw_cursor
695store_char_in_buf:
696 ld a, (_cursory)
697 ld h, a
698 ld a, (_cursorx)
699 ld l, a
700 call screenbuf_offset
701 push hl
702 ld de, #_screenbuf
703 add hl, de ; hl = screenbuf[(cursory * LCD_COLS) + cursorx]
704 ld a, 4(ix)
705 ld (hl), a ; store character
706 pop hl
707 ld de, #_screenattrs
708 add hl, de ; hl = screenattrs[(cursory * LCD_COLS) + cursorx]
709 ld a, (_putchar_sgr)
710 ld (hl), a ; = putchar_sgr
711 ld a, (_cursorx)
712 ld l, a
713 push hl
714 ld a, (_cursory)
715 ld l, a
716 push hl
717 call _stamp_char
718 pop hl
719 pop hl
720advance_cursorx:
721 ld a, (_cursorx)
722 inc a
723 ld (_cursorx), a
724 cp #LCD_COLS ; if (cursorx >= LCD_COLS)
725 jr c, putchar_draw_cursor
726 xor a
727 ld (_cursorx), a
728 ld a, (_cursory)
729 inc a
730 ld (_cursory), a
731check_cursory:
732 cp #TEXT_ROWS ; and if (cursory >= TEXT_ROWS)
733 jr c, putchar_draw_cursor
734 call _scroll_lcd
735 ld a, #TEXT_ROWS - 1
736 ld (_cursory), a ; cursory = TEXT_ROWS - 1
737putchar_draw_cursor:
738 ld a, (_cursory)
739 ld h, a
740 ld a, (_cursorx)
741 ld l, a
742 call screenbuf_offset
743 ld de, #_screenattrs
744 add hl, de ; hl = screenattrs[(cursory * LCD_COLS) + cursorx]
745 ld a, (hl) ; read existing attrs
746 set #ATTR_BIT_CURSOR, a
747 ld (hl), a ; = putchar_sgr | ATTR_CURSOR
748 ld a, (_cursorx)
749 ld l, a
750 push hl
751 ld a, (_cursory)
752 ld l, a
753 push hl
754 call _stamp_char
755 pop hl
756 pop hl
757putchar_fastout:
758 pop hl
759 pop de
760 ld sp, ix
761 pop ix
762 ret
763
764
765; void putchar_attr(unsigned char row, unsigned char col, char c, char attr)
766; directly manipulates screenbuf/attrs without scrolling or length checks
767; row at 4(ix), col at 5(ix), c at 6(ix), attr at 7(ix)
768_putchar_attr::
769 push ix
770 ld ix, #0
771 add ix, sp
772 push de
773 push hl
774store_char:
775 ld h, 4(ix)
776 ld l, 5(ix)
777 call screenbuf_offset
778 push hl
779 ld de, #_screenbuf
780 add hl, de ; screenbuf[(row * TEXT_COLS) + col]
781 ld a, 6(ix)
782 ld (hl), a
783store_attrs:
784 pop hl
785 ld de, #_screenattrs
786 add hl, de ; screenattrs[(row * TEXT_COLS) + col]
787 ld a, 7(ix)
788 ld (hl), a
789 ld l, 5(ix)
790 push hl
791 ld l, 4(ix)
792 push hl
793 call _stamp_char
794 pop hl
795 pop hl
796 pop hl
797 pop de
798 ld sp, ix
799 pop ix
800 ret