The raw source is in the Git repository at Codeberg.
Although my I usually attempt to produce web pages with ‘mobile first’ in mind, I can’t see any reasonable way to make an assembler listing fit. I can’t imagine why you’d be attempting to view 5000 lines of assembler on a phone anyway; it’s akin to appraising a house through the letter box.
The source code itself is (presumably) copyrighted by DEC and its successor companies. My annotations are licensed under a Creative Commons Attribution 4.0 International License.
1 ; 2 ; DZ80 V3.4.1 8080 Disassembly of VT100.bin 3 ; Initial disassembly: 2021-12-03 08:56 4 ; Last updated: 2022-08-09 11:00 5 ; 6 ; Comments by Paul Flo Williams <paul@frixxon.co.uk<, 7 ; released under Creative Commons Attribution 4.0 International licence. 8 ; 9 ; Documentation random things: 10 ; 1. Sequences not mentioned in User Guide: 11 ; a) DECHCP and its VT52 equivalent ESC ]; 12 ; b) DECGON/DECGOFF, 13 ; c) SS2 and SS3. 14 ; DECHCP and DECGON are dependent on GPO anyway, but SS2/SS3 work for unexpanded VT100. 15 ; DECHCP is mentioned in TM p.A-21 16 ; 17 ; Comments on notation in comments: 18 ; A <- x means "A becomes/is set to x", which frees up "=" to be the straightforward mathematical "is equal to" 19 ; HL <- x means HL becomes x, mostly implying that HL is now a pointer to something at location x. 20 ; 21 ; References to TM here are all to EK-VT100-TM-003, "VT100 Series Video Terminal Technical Manual" 22 ; 23 ; TM Table 4-2-3, p.4-18, explains the RST interrupt handling. 24 ; 25 ; Table 4-2-3 Interrupt Addresses 26 ; 27 ; 00H Power-up (Not hardware driven) 28 ; 08H Keyboard 29 ; 10H Receiver 30 ; 18H Receiver and keyboard 31 ; 20H Vertical frequency 32 ; 28H Vertical frequency and keyboard 33 ; 30H Vertical frequency and receiver 34 ; 38H Vertical frequency, receiver and keyboard 35 ; 36 00 00 org 00h 37 0000 F3 start: di 38 0001 31 4E 20 lxi sp,stack_top 39 0004 C3 3B 00 jmp post 40 ; 41 00 08 restart1: org 08h 42 0008 CD FD 00 call keyboard_int 43 000B FB ei 44 000C C9 ret 45 ; 46 00 10 restart2: org 10h 47 0010 CD CC 03 call receiver_int 48 0013 FB ei 49 0014 C9 ret 50 ; 51 00 18 restart3: org 18h 52 0018 CD CC 03 call receiver_int 53 001B CD FD 00 call keyboard_int 54 001E FB ei 55 001F C9 ret 56 ; 57 00 20 restart4: org 20h 58 0020 CD CF 04 call vertical_int 59 0023 C9 ret 60 ; 61 00 28 restart5: org 28h 62 0028 CD CF 04 call vertical_int 63 002B C9 ret 64 ; 65 00 30 org 30h 66 0030 CD CC 03 restart6: call receiver_int 67 0033 CD CF 04 call vertical_int 68 0036 FB ei 69 0037 C9 ret 70 ; 71 00 38 restart7: org 38h 72 0038 C3 30 00 jmp restart6 73 ; 74 ; post 75 ; self_test 76 77 ; The only difference between "proper" POST and the Self Test invoked by DECTST is making sure that POST 78 ; doesn't repeat indefinitely. As register E is a mask of requested tests and repeat indication, POST sets 79 ; the mask just as if DECTST had been invoked with "just self-test, no repeats." 80 ; 81 ; TM §4.2.8, "Power-Up and Self-Test", p. 4-19, describes POST. 82 ; 83 003B 1E 01 post: mvi e,1 ; Pretend this is DECTST and we requested POST without repeats 84 ; 85 ; Self-test starts by checksumming ROMs, which is a rotate-and-XOR operation across all four 2K ROMs 86 ; individually. The checksum is expected to produce a zero result, which is ensured by including a 87 ; byte somewhere in each ROM that can mix-in to make zero. Checksum bytes are marked in this listing 88 ; where they have been identified. 89 ; 90 003D F3 self_test: di 91 003E 3E 0F mvi a,0fh 92 0040 D3 62 out iow_nvr_latch ; "standby" to NVR latch (see port definition for details) 93 0042 2F cma ; A <- 0xf0 94 0043 D3 42 out iow_brightness ; set mid brightness 95 0045 AF xra a 96 0046 57 mov d,a 97 0047 6F mov l,a 98 0048 67 mov h,a ; HL <- 0, starting address of ROMs 99 ; Checksumming ROMS 1 to 4 100 0049 3C next_rom: inr a 101 004A 47 mov b,a 102 004B D3 82 out iow_keyboard ; Place ROM number on LEDs 103 004D 0E 08 mvi c,8 ; Each ROM is 8 x 256 = 2K bytes 104 004F 07 next_byte: rlc ; checksum is rotate and XOR 105 0050 AE xra m 106 0051 2C inr l 107 0052 C2 4F 00 jnz next_byte 108 0055 24 inr h 109 0056 0D dcr c 110 0057 C2 4F 00 jnz next_byte 111 005A B7 ora a ; A spare byte in each ROM is programmmed to ensure the correct 112 005B C2 5B 00 hang_rom: jnz hang_rom ; checksum is always zero. Failed? Hang with ROM number on keyboard. 113 005E 78 mov a,b 114 005F FE 04 cpi 4 115 0061 C2 49 00 jnz next_rom ; Loop for all four ROMs 116 ; 117 0064 3C inr a 118 0065 D3 82 out iow_keyboard ; RAM test next; failure leaves "5" on the LEDs 119 0067 0E AA mvi c,0aah ; C is test pattern 120 0069 06 2C mvi b,HIGH ram_top+1 121 006B DB 42 in ior_flags 122 006D E6 02 ani iob_flags_avo ; Test for presence of AVO 123 006F C2 74 00 jnz next_pattern ; jump if absent 124 0072 06 40 mvi b,HIGH avo_ram_top+1 125 0074 60 next_pattern: mov h,b ; Start at top of RAM (L zero either from ROM test, or from last loop) 126 0075 2B dcx h 127 0076 36 00 zero_ram: mvi m,0 128 0078 2B dcx h 129 0079 7C mov a,h 130 007A FE 1F cpi HIGH ram_start - 1 131 007C C2 76 00 jnz zero_ram 132 ; Now work back up through RAM, applying pattern 133 007F 23 inx h 134 0080 7E patt_loop: mov a,m ; A <- current contents, which should be zero 135 0081 B7 ora a 136 0082 CA 90 00 jz zero_good 137 0085 E6 0F ani 0fh ; A <- low 4 bits of memory value 138 0087 7C mov a,h ; 139 0088 C2 A1 00 jnz ram_fail ; if low 4 bits are not zero, we've failed 140 008B FE 30 cpi HIGH avo_ram_start 141 008D DA A1 00 jc ram_fail ; Don't fail RAM if we're looking at AVO, because its RAM is 4 bits wide 142 0090 71 zero_good: mov m,c ; Now place pattern 143 0091 7E mov a,m ; and read back 144 0092 A9 xra c ; should be identical (i.e. XOR to zero) 145 0093 CA A8 00 jz patt_good 146 0096 E6 0F ani 0fh ; Again, AVO RAM is only 4 bits wide 147 0098 7C mov a,h 148 0099 C2 A1 00 jnz ram_fail 149 009C FE 30 cpi HIGH avo_ram_start 150 009E D2 A8 00 jnc patt_good ; so don't fail RAM for 4 high bit being wrong, in AVO space 151 00A1 16 01 ram_fail: mvi d,1 ; D <- accumulates test failures 152 00A3 FE 2C cpi HIGH ram_top+1 153 00A5 DA A5 00 hang_ram: jc hang_ram ; Hang for baseboard RAM, not AVO 154 00A8 AF patt_good: xra a 155 00A9 23 inx h 156 00AA B5 ora l 157 00AB C2 80 00 jnz patt_loop 158 00AE B4 ora h 159 00AF B8 cmp b 160 00B0 C2 80 00 jnz patt_loop 161 00B3 79 mov a,c ; A <- pattern 162 00B4 07 rlc ; 1st time: 0xaa -< 0x55 set carry; 2nd time: 0x55 -< 0xaa, reset carry 163 00B5 4F mov c,a ; C <- new pattern 164 00B6 DA 74 00 jc next_pattern 165 00B9 D5 push d 166 00BA CD A4 02 call clear_scratch 167 00BD CD EB 02 call init_terminal 168 00C0 CD 5B 17 call recall_nvr 169 00C3 D1 pop d 170 00C4 CA CB 00 jz recall_ok 171 00C7 7A mov a,d 172 00C8 F6 02 ori 2 ; add "nvr failed" to test results 173 00CA 57 mov d,a 174 00CB 3E 2F recall_ok: mvi a,2fh ; c111 d1 = "standby" (inv) 175 00CD 32 C9 21 sta vint_nvr 176 00D0 D3 62 out iow_nvr_latch 177 ; Keyboard test 178 00D2 01 FF 0F lxi b,0fffh ; good long loop 179 00D5 FB ei ; We rely on keyboard_int going off to detect end-of-scan 180 00D6 3E 08 beep_loop: mvi a,8 ; If we entered POST from DECTST, and we requested repeating tests, 181 00D8 A3 ana e 182 00D9 3E 7F mvi a,7fh ; clear the "SPKR. CLICK" bit in the byte we send to the keyboard, 183 00DB C2 E0 00 jnz skip_click 184 00DE 3E FF mvi a,0ffh ; If we're not repeating test, go wild, beep-boy! 185 00E0 D3 82 skip_click: out iow_keyboard 186 00E2 0B dcx b 187 00E3 78 mov a,b 188 00E4 B1 ora c 189 00E5 C2 D6 00 jnz beep_loop 190 00E8 D3 82 out iow_keyboard ; quieten keyboard again 191 00EA 3A 68 20 lda key_flags ; Check key_flags that have been maintained by keyboard_int 192 00ED B7 ora a 193 00EE FA F5 00 jm seen_eos ; We should have seen an end of scan after that loop 194 00F1 7A mov a,d 195 00F2 F6 04 ori 4 ; add "keyboard failed" to test results 196 00F4 57 mov d,a 197 00F5 D5 seen_eos: push d 198 00F6 CD A2 03 call init_devices 199 00F9 D1 pop d 200 00FA C3 75 08 jmp continue_tests 201 ; 202 ; The keyboard interrupt detects modifier keys and "end of scan" and places them into a 203 ; flags location, and places other keys into what the Technical Manual calls the SILO, 204 ; here called key_silo. 205 ; 206 ; The TM describes the SETUP key going into key_flags but it doesn't; its key code of 7bh 207 ; falls just under the cut-off established here and it goes into the SILO. 208 ; 209 ; Interpretation of keys happens in see process_keys. 210 ; 211 00FD F5 keyboard_int: push psw 212 00FE DB 82 in ior_keyboard 213 0100 E5 push h 214 0101 C5 push b 215 0102 47 mov b,a ; B <- key row/column 216 0103 D6 7C sui 7ch ; 07ch and up are the modifier keys: CTRL, SHIFT and CAPS LOCK 217 0105 FA 1A 01 jm into_key_buf ; normal keys will go into SILO 218 0108 67 mov h,a ; H <- key code - 7ch 219 0109 24 inr h ; H <- 1 = CTRL, 2 = SHIFT, 3 = CAPS, 4 = EOS 220 010A 3E 10 mvi a,10h ; Not at all sure why A could not have been initialised to 221 010C 0F rrc ; 08h at this point, and we could lose the RRC instruction. 222 010D 07 shift_key: rlc 223 010E 25 dcr h 224 010F C2 0D 01 jnz shift_key 225 0112 21 68 20 lxi h,key_flags 226 0115 B6 ora m 227 0116 77 mov m,a 228 0117 C3 2D 01 jmp kexit ; Done for this key, exit interrupt 229 ; 230 011A 21 68 20 into_key_buf: lxi h,key_flags 231 011D 3E 07 mvi a,7 232 011F A6 ana m 233 0120 FE 04 cpi 4 ; Number of keys waiting to be processed? 234 0122 F2 2D 01 jp kexit ; No room if already got 4 235 0125 34 inr m ; Increment number of keys waiting 236 0126 21 6A 20 lxi h,key_silo 237 0129 CD DE 13 call add_a_to_hl 238 012C 70 mov m,b ; Add this key to queue 239 012D C1 kexit: pop b 240 012E E1 pop h 241 012F F1 pop psw 242 0130 C9 ret 243 ; 244 ; proc_func_key 245 ; 246 ; Having placed keyboard scan codes through the switch array, we now have a key code that identifies 247 ; a non-ASCII, or "function" key. 248 ; 249 ; BREAK is 081h, NO SCROLL is 082h (although it has already been acted upon, and doesn't get here) 250 ; Numeric keypad keys all have their ASCII codes with the high bit set, i.e. KP COMMA is 0ach, and 251 ; the digits 0 to 9 have codes 0b0h to 0b9h. 252 ; Arrow keys are codes 0c1h to 0c4h (bit 7 plus 41h to 44h, i.e. 'A' to 'D') 253 ; PF1 to PF4 are 0d0h to 0d3h (bit 7 plus 50h to 053h, i.e 'p' to 's') 254 255 0131 E6 7F proc_func_key: ani 7fh ; All keys entering here at the bit 7 set, so remove it 256 0133 4F mov c,a ; C <- adjusted key code 257 0134 3A 7B 20 lda in_setup 258 0137 B7 ora a 259 0138 C2 FF 01 jnz setup_cursor 260 013B 79 mov a,c 261 013C FE 01 cpi 1 ; BREAK was 081h, adjusted to 01h 262 013E CA C3 01 jz break_pressed 263 0141 3A A7 21 lda setup_b2 264 0144 E6 20 ani sb2_ansi 265 0146 C2 75 01 jnz ansi_keys ; jump to ANSI mode processing 266 0149 3A 78 21 lda keypad_mode 267 014C B7 ora a 268 014D C2 5F 01 jnz vt52_app_mode 269 0150 79 mov a,c ; A <- adjusted key code 270 0151 FE 41 cpi 'A' ; If it isn't a cursor key, it's a number from the numeric 271 0153 FA 5B 01 jm send_c ; keypad and they are sent just as '0' to '9' on VT52 272 0156 3E 1B mvi a,C0_ESC ; otherwise it's a cursor key and they have leading ESC 273 0158 CD 18 0F call send_key_byte 274 015B 79 send_c: mov a,c 275 015C C3 0D 08 jmp send_key_end 276 ; 277 015F 3E 1B vt52_app_mode: mvi a,C0_ESC 278 0161 CD 18 0F call send_key_byte 279 0164 79 mov a,c ; A <- adjusted key code 280 0165 FE 41 cpi 'A' ; Is it an arrow key? 281 0167 F2 5B 01 jp send_c ; we've sent ESC already, so just send 'A' to 'D' now 282 016A 3E 3F mvi a,'?' ; else numeric keypad keys are KP0 -< ESC ? p 283 016C CD 18 0F send_plus_col4: call send_key_byte 284 016F 79 mov a,c ; A <- adjusted key code 285 0170 C6 40 adi 40h ; '0' -< 'p' 286 0172 C3 0D 08 jmp send_key_end 287 ; 288 0175 3A 78 21 ansi_keys: lda keypad_mode 289 0178 B7 ora a 290 0179 C2 9E 01 jnz ansi_app_mode 291 017C 79 mov a,c ; A <- adjusted key code 292 017D FE 41 cpi 'A' ; If it isn't a cursor key (and here we're in numeric keypad mode), 293 017F FA 5B 01 jm send_c ; send the plain digit 294 0182 FE 50 cpi 'P' ; PF1 to PF4? 295 0184 FA 9E 01 jm ansi_app_mode ; No, deal with arrow keys 296 0187 3E 1B esc_o_last: mvi a,C0_ESC ; PF1 to PF4 are the same in numeric and application modes, 297 0189 CD 18 0F call send_key_byte ; sending ESC O P to ESC O S 298 018C 3E 4F mvi a,'O' 299 018E CD 18 0F send_ac: call send_key_byte 300 0191 C3 5B 01 jmp send_c 301 ; 302 0194 3E 1B esc_brack_last: mvi a,C0_ESC 303 0196 CD 18 0F call send_key_byte 304 0199 3E 5B mvi a,'[' 305 019B C3 8E 01 jmp send_ac 306 ; 307 019E 3A BC 21 ansi_app_mode: lda mode_ckm ; Responses may also depend on DECCKM (cursor key mode) 308 01A1 B7 ora a 309 01A2 CA B5 01 jz decckm_reset ; CKM is reset 310 01A5 79 mov a,c ; A <- adjusted key code 311 01A6 FE 41 cpi 'A' ; Cursor keys? 312 01A8 F2 87 01 jp esc_o_last ; Yes, in ANSI + CKM mode, these are ESC O A to ESC O D 313 01AB 3E 1B ansi_digits: mvi a,C0_ESC 314 01AD CD 18 0F call send_key_byte 315 01B0 3E 4F mvi a,'O' 316 01B2 C3 6C 01 jmp send_plus_col4 317 ; 318 01B5 79 decckm_reset: mov a,c ; A <- adjusted key code 319 01B6 FE 41 cpi 'A' ; Is this a digit key? 320 01B8 FA AB 01 jm ansi_digits ; ANSI-mode digits are same with CKM set or reset 321 01BB FE 50 cpi 'P' ; PF1 to PF4? 322 01BD FA 94 01 jm esc_brack_last ; ANSI-mode arrows are ESC [ A to ESC [ D 323 01C0 C3 87 01 jmp esc_o_last ; ANSI + CKM reset PF1 to PF4 are ESC O P to ESC O S 324 ; 325 ; 326 ; 327 ; 328 01C3 21 15 08 break_pressed: lxi h,pk_noclick ; Return to a "tidy keyboard processing" point 329 01C6 E5 push h 330 01C7 3A A5 21 lda local_mode ; BREAK has no function in local mode 331 01CA B7 ora a 332 01CB C0 rnz 333 01CC CD 53 08 call make_keyclick 334 01CF 01 0E 02 lxi b,020eh ; B <- "not DTR" for PUSART; C <- 14 vert. frames =~ 0.2333 s 335 01D2 21 68 20 lxi h,key_flags 336 01D5 7E mov a,m 337 01D6 E6 10 ani key_flag_ctrl ; CTRL + BREAK sends answerback message 338 01D8 C2 00 09 jnz c0_answerback 339 01DB 7E mov a,m ; A <- key flags 340 01DC E6 20 ani key_flag_shift ; SHIFT + BREAK is a longer break 341 01DE CA E4 01 jz not_shift ; but if we're not doing that, keep counter short 342 01E1 01 D2 00 lxi b,00d2h ; Long break C <- 210 vertical frames =~ 3.5 s 343 01E4 3E 25 not_shift: mvi a,25h ; A <- base command: "not RTS, rx enable, tx enable" 344 01E6 F6 08 ori 8 ; A <- mix in "send break character" 345 01E8 B0 ora b ; A <- possibly mix in disconnect (dropping DTR) for SHIFT+BREAK 346 01E9 D3 01 out iow_pusart_cmd ; send PUSART command 347 01EB 3A 7F 20 lda frame_count ; get current vertical frame count 348 01EE 81 add c ; add number we want 349 01EF 4F mov c,a ; C <- end number of frames 350 01F0 C5 wait_break: push b 351 01F1 CD 93 14 call update_kbd 352 01F4 C1 pop b 353 01F5 3A 7F 20 lda frame_count 354 01F8 B9 cmp c ; are we there yet? 355 01F9 C2 F0 01 jnz wait_break 356 01FC C3 94 03 jmp ready_comms 357 ; 358 01FF 21 12 08 setup_cursor: lxi h,pk_click ; Push a return address that will tidy up keyboard 359 0202 E5 push h 360 0203 AF xra a 361 0204 32 30 21 sta csi_params 362 0207 32 B8 21 sta csi_private 363 020A 21 A3 21 lxi h,brightness 364 020D 79 mov a,c 365 020E D6 41 sui 'A' 366 0210 47 mov b,a ; B <- adjusted cursor key 367 0211 7E mov a,m ; Get current brightness 368 0212 CA 22 02 jz brighter ; up arrow - screen brighter 369 0215 05 dcr b 370 0216 CA 26 02 jz dim_screen ; down arrow - screen more dim 371 0219 05 dcr b 372 021A CA 15 18 jz cuf_action ; right arrow - normal cursor movement 373 021D 05 dcr b 374 021E CA 1E 18 jz cub_action ; left arrow - normal cursor movement 375 0221 C9 ret 376 ; 377 0222 3D brighter: dcr a 378 0223 F8 rm ; limit at 0 379 0224 77 mov m,a ; write new brightness 380 0225 C9 ret 381 ; 382 0226 3C dim_screen: inr a 383 0227 FE 20 cpi 20h ; limit at 1fh 384 0229 C8 rz 385 022A 77 mov m,a ; write new brightness 386 022B C9 ret 387 ; 388 022C 3A A7 21 no_scroll_key: lda setup_b2 389 022F 2F cma ; complement sense of switches 390 0230 E6 10 ani 10h ; A <- 10h if autoxon is OFF 391 0232 21 A5 21 lxi h,local_mode 392 0235 B6 ora m 393 0236 C2 41 08 jnz clear_keyboard ; Can't send XOFF if autoxon is OFF or we're in local mode 394 0239 3A BF 21 lda why_xoff 395 023C 01 11 02 lxi b,2<<8|C0_XON 396 023F A0 ana b ; NZ if we had previously sent XOFF, in which case 397 0240 C2 45 02 jnz send_xany ; we'll send an XON 398 0243 0E 13 mvi c,C0_XOFF 399 0245 79 send_xany: mov a,c 400 0246 D6 11 sui C0_XON ; If we've sent XON, A = 0, XOFF -< A = 2 401 0248 32 C4 21 sta noscroll 402 024B CD 7E 0F call send_xonoff 403 024E C3 12 08 jmp pk_click 404 ; 405 0251 F3 after_tests: di 406 0252 31 4E 20 lxi sp,stack_top 407 0255 21 00 20 lxi h,ram_start ; Just done tests, so this is the where failures are recorded 408 0258 E5 push h 409 0259 7E mov a,m ; A <- test results 410 025A 1F rar ; Set carry if AVO failed tests 411 025B DA 65 02 jc no_avo 412 025E DB 42 in ior_flags 413 0260 E6 02 ani iob_flags_avo 414 0262 CA 69 02 jz have_avo ; AVO is an "absence" flag 415 0265 3C no_avo: inr a 416 0266 32 C8 21 sta avo_missing 417 0269 CD A2 03 have_avo: call init_devices 418 026C 3A 50 20 lda screen_cols 419 026F 3D dcr a 420 0270 32 53 21 sta right_margin 421 0273 E1 pop h 422 0274 7E mov a,m ; A <- test results 423 0275 36 7F mvi m,7fh ; Write line terminator over test results, at start of screen layout 424 0277 B7 ora a ; Did any fail? 425 0278 CA 98 02 jz done_results ; no, don't bother displaying 426 027B F2 85 02 jp no_cycle_bit ; jump if we aren't cycling through results 427 027E 21 CB 21 lxi h,test_field ; To show that tests are being repeated and failing, the entire screen 428 0281 36 0A mvi m,0ah ; is toggled between normal and reverse field, so initialise this. 429 0283 E6 7F ani 7fh ; Remove cycle bit when storing results 430 0285 32 BD 21 no_cycle_bit: sta test_results 431 0288 C6 30 adi '0' ; convert to printable range 432 028A 2A F6 20 lhld cursor_address 433 028D 77 mov m,a ; Display results at cursor position 434 028E FE 34 cpi '4' ; was it just modem test that failed? 435 0290 CA 98 02 jz done_results ; OK, don't go into local mode just for that 436 0293 3E 20 mvi a,20h ; 437 0295 32 A5 21 sta local_mode ; any other failures, go into local 438 0298 CD F2 0B done_results: call set_charsets 439 029B 01 11 00 lxi b,C0_XON 440 029E CD 7E 0F call send_xonoff 441 02A1 C3 AE 03 jmp idle_loop 442 ; 443 02A4 21 4E 20 clear_scratch: lxi h,stack_top 444 02A7 11 B2 0F lxi d,avo_ram_start - stack_top 445 02AA 06 00 mvi b,0 446 02AC CD 83 10 call memset ; Clear all of scratch RAM above stack 447 02AF 2F cma ; A <- 0ffh (because memset always zeroes A) 448 02B0 32 04 21 sta saved_rend ; saved character rendering 449 02B3 21 04 20 lxi h,line1_dma 450 02B6 22 52 20 shld UNREAD_X2052 451 02B9 21 D0 22 lxi h,main_video ; (home address) 452 02BC 22 F6 20 shld cursor_address 453 02BF C9 ret 454 ; 455 ; init_video_ram 456 ; 457 02C0 CD 15 0A init_video_ram: call to_ground ; initialise character processing routine 458 02C3 21 00 20 lxi h,ram_start ; HL <- start of screen RAM 459 02C6 11 D9 02 lxi d,screen_layout 460 02C9 06 12 mvi b,12h 461 02CB CD 8B 03 call memcopy ; initial video RAM layout 462 02CE 21 00 30 lxi h,avo_ram_start 463 02D1 11 00 10 lxi d,avo_ram_top - avo_ram_start + 1 464 02D4 06 FF mvi b,0ffh 465 02D6 C3 83 10 jmp memset ; attribute RAM blank (default rendition) 466 ; 467 ; This is the initial configuration of screen RAM. TM §4.6 explains the 468 ; screen refresh and three termination bytes on each line. 469 ; This layout almost matches TM Figure 4-7-3, p.4-89, except that that figure seems to have 470 ; a typo (repeated 7F 70 06 line, when second should be 7F 70 0C) - other figures in that 471 ; chapter get this right. 472 ; 473 02D9 7F 70 03 screen_layout: db 07fh,70h,03h 474 02DC 7F F2 D0 db 07fh,0f2h,0d0h 475 02DF 7F 70 06 db 07fh,70h,06h 476 02E2 7F 70 0C db 07fh,70h,0ch 477 02E5 7F 70 0F db 07fh,70h,0fh 478 02E8 7F 70 03 db 07fh,70h,03h 479 ; 480 ; init_terminal 481 ; Initialise a bunch of terminal settings: timers, scroll, cursor and receiver buffer 482 ; 483 02EB 21 12 02 init_terminal: lxi h,0212h ; long, visible, cursor timer 484 02EE 22 2D 21 shld cursor_timer 485 02F1 3E 35 mvi a,35h 486 02F3 32 2C 21 sta blink_timer 487 02F6 3E 01 mvi a,1 488 02F8 32 5B 20 sta scroll_dir 489 02FB 32 76 21 sta tparm_solicited ; By default reports cannot be sent unsolicited 490 02FE 21 FF 07 lxi h,07ffh ; Never used 491 0301 22 49 21 shld UNREAD_X2149 ; Never used 492 0304 3E 02 mvi a,2 493 0306 32 73 20 sta key_rpt_pause 494 0309 3E F7 mvi a,0f7h ; All attributes off, normal character set 495 030B 32 FA 20 sta char_rend 496 030E DB 42 in ior_flags 497 0310 E6 04 ani iob_flags_gpo 498 0312 3E 01 mvi a,1 499 0314 C2 1A 03 jnz no_gpo ; AVO and GPO flags are both "absence" flags 500 0317 32 79 20 sta gpo_flags 501 031A 3E FF no_gpo: mvi a,0ffh 502 031C 32 0E 21 sta saved_curs_row ; invalidate cursor row 503 031F 32 BA 21 sta cursor_visible 504 0322 26 80 mvi h,80h 505 0324 6C mov l,h 506 0325 22 C0 20 shld rx_head ; Initialise rx_head and rx_tail of receive buffer 507 0328 C9 ret 508 ; 509 0329 3E 40 reset_pusart: mvi a,40h ; Reset PUSART so we can write a mode byte again 510 032B D3 01 out iow_pusart_cmd 511 032D 3A 58 21 lda tx_rx_speed 512 0330 D3 02 out iow_baud_rate 513 0332 3A A4 21 lda pusart_mode 514 0335 D3 01 out iow_pusart_cmd 515 0337 CD 94 03 call ready_comms 516 033A 3E 10 mvi a,10h 517 033C 32 C9 21 sta vint_nvr 518 033F D3 62 out iow_nvr_latch 519 0341 C9 ret 520 ; 521 ; update_dc011 522 ; 523 ; This is the only routine that writes to I/O port 0c2h, which drives DC011, the circuit 524 ; that provides most of the timing signals to the Video Processor. Because setting 525 ; columns or refresh rate always sets or resets interlaced mode, we may need to write 526 ; the column mode twice. 527 ; 528 ; See TM §4.6.2, p. 4-55 529 ; 530 0342 3A A2 21 update_dc011: lda columns_132 531 0345 B7 ora a 532 0346 CA 4B 03 jz is80 533 0349 3E 10 mvi a,10h 534 034B 47 is80: mov b,a ; B <- 0 for 80 columns, 10h for 132 columns 535 034C D3 C2 out iow_dc011 ; this write also sets interlaced mode 536 034E 3A 7C 20 lda refresh_rate 537 0351 D3 C2 out iow_dc011 ; also sets non-interlaced mode 538 0353 FE 20 cpi refresh_60Hz 539 0355 21 70 09 lxi h,0970h ; 50Hz fill, per TM Figure 4-7-3, p. 4-89 540 0358 C2 5E 03 jnz fill_req 541 035B 21 70 03 lxi h,0370h ; 60Hz fill 542 035E 22 01 20 fill_req: shld line0_dma 543 0361 3A A8 21 lda setup_b3 544 0364 E6 10 ani sb3_interlace 545 0366 C8 rz 546 0367 78 mov a,b ; A <- 0 for 80 columns, 10h for 132 columns 547 0368 D3 C2 out iow_dc011 ; write columns again, so we get interlaced mode 548 036A C9 ret 549 ; 550 ; update_dc012 551 ; DC012 is the video controller chip, and here we update the field colour (reverse/normal field) and 552 ; whether the basic attribute (important in the absence of the AVO) is underline or reverse video. 553 ; 554 036B 3A A6 21 update_dc012: lda setup_b1 555 036E E6 20 ani sb1_lightback 556 0370 C2 75 03 jnz is_light 557 0373 3E 01 mvi a,1 558 0375 F6 0A is_light: ori 0ah ; A <- 0ah/0bh = "set reverse field on/off", respectively 559 0377 D3 A2 out iow_dc012 560 0379 3A 5B 21 lda basic_rev_video ; 0 = underline, 1 = reverse video 561 037C F6 0C ori 0ch ; "set basic attribute" command 562 037E D3 A2 out iow_dc012 563 0380 C9 ret 564 ; 565 0381 3A A2 21 clear_display: lda columns_132 566 0384 B7 ora a 567 0385 CA 63 0B jz init_80col 568 0388 C3 77 0B jmp init_132col 569 ; 570 ; memcopy - copy B bytes from (DE) to (HL) 571 038B 1A memcopy: ldax d 572 038C 77 mov m,a 573 038D 23 inx h 574 038E 13 inx d 575 038F 05 dcr b 576 0390 C2 8B 03 jnz memcopy 577 0393 C9 ret 578 ; 579 0394 3A A5 21 ready_comms: lda local_mode 580 0397 B7 ora a 581 0398 3E 01 mvi a,1 ; "RTS" 582 039A C2 9F 03 jnz skip_dtr ; don't set DTR if we're in local mode 583 039D 3E 05 mvi a,5 ; "RTS" and "DTR" 584 039F C3 7B 1F skip_dtr: jmp modem_signals ; 585 ; 586 ; Initialise display, PUSART, and the video devices DC011 and DC012 587 ; 588 03A2 CD 81 03 init_devices: call clear_display 589 03A5 CD DB 1D call program_pusart 590 03A8 CD 6B 03 call update_dc012 591 03AB C3 42 03 jmp update_dc011 592 ; 593 03AE CD 88 14 idle_loop: call keyboard_tick 594 03B1 CD 87 05 call receiver_tick 595 03B4 21 77 21 lxi h,pending_setup 596 03B7 7E mov a,m 597 03B8 B7 ora a 598 03B9 36 00 mvi m,0 599 03BB C4 20 1A cnz in_out_setup 600 03BE 3A A5 21 lda local_mode 601 03C1 B7 ora a 602 03C2 CA AE 03 jz idle_loop 603 03C5 AF xra a 604 03C6 32 44 21 sta keyboard_locked 605 03C9 C3 AE 03 jmp idle_loop 606 ; 607 03CC F5 receiver_int: push psw 608 03CD C5 push b 609 03CE E5 push h 610 03CF DB 00 in ior_pusart_data 611 03D1 E6 7F ani 7fh 612 03D3 CA 3A 04 jz exit_rx_int ; quick exit for received NULs 613 03D6 4F mov c,a ; C <- character received 614 03D7 3A A5 21 lda local_mode 615 03DA B7 ora a 616 03DB C2 3A 04 jnz exit_rx_int ; if we're in local mode, reject received characters too 617 03DE DB 01 in ior_pusart_cmd ; read PUSART status 618 03E0 E6 38 ani 38h ; do we have framing, overrun or parity errors? 619 03E2 CA ED 03 jz no_rx_errors ; no, phew 620 03E5 0E 1A mvi c,C0_SUB ; Rx errors are treated as SUB, shown as checkboard on screen 621 03E7 3E 27 mvi a,27h ; PUSART command enables RTS*, DTR*, transmit and receive 622 03E9 F6 10 ori 10h ; mix-in "error reset" 623 03EB D3 01 out iow_pusart_cmd 624 03ED 79 no_rx_errors: mov a,c ; A <- received character (or SUB) 625 03EE FE 7F cpi 7fh ; Is this DEL? 626 03F0 CA 37 04 jz pass_on 627 03F3 3A A7 21 lda setup_b2 628 03F6 E6 10 ani sb2_autoxon ; test XON 629 03F8 79 mov a,c ; A <- received character 630 03F9 CA 09 04 jz no_autoxon ; jump if we don't do auto XON/XOFF 631 03FC 21 C2 21 lxi h,received_xoff 632 03FF FE 11 cpi C0_XON ; have we received XON? 633 0401 CA 3E 04 jz rx_xon 634 0404 FE 13 cpi C0_XOFF ; have we received XOFF? 635 0406 CA 44 04 jz rx_xoff 636 0409 21 C0 20 no_autoxon: lxi h,rx_head 637 040C 4E mov c,m ; Get pointer 638 040D 44 mov b,h ; as 16-bits 639 040E 02 stax b ; Place in buffer 640 040F 79 mov a,c ; A <- rx_head pointer 641 0410 3C inr a ; move along 642 0411 E6 BF ani 0bfh ; And wrapping, so location after 20bf is 2080 643 0413 77 mov m,a ; Write rx_head back 644 0414 47 mov b,a ; B <- rx_head 645 0415 3A C1 20 lda rx_tail ; A <- rx_tail 646 0418 90 sub b ; A <- rx_tail - rx_head (= space in buffer) 647 0419 C2 27 04 jnz not_caught 648 041C 71 mov m,c ; Put old head back, so we lose newest character 649 041D 78 mov a,b ; A <- new rx_head 650 041E 3C inr a ; move along 651 041F E6 BF ani 0bfh ; and wrap 652 0421 6F mov l,a ; make a pointer 653 0422 36 1A mvi m,C0_SUB ; place SUB in buffer to mark lost character 654 0424 C3 31 04 jmp do_xoff 655 ; 656 0427 F2 2C 04 not_caught: jp nowrap ; If rx_head < rx_tail 657 042A C6 40 adi 40h ; space is "over the wrap" 658 042C FE 20 nowrap: cpi 32 ; Have we got half a buffer full? 659 042E C2 37 04 jnz pass_on 660 0431 01 13 01 do_xoff: lxi b,1<<8|C0_XOFF ; Half buffer full, so sent XOFF 661 0434 CD 7E 0F call send_xonoff 662 0437 CD 47 0E pass_on: call try_tx2 663 exit_rx_int: 664 043A E1 pop h 665 043B C1 pop b 666 043C F1 pop psw 667 043D C9 ret 668 ; 669 043E 3E FE rx_xon: mvi a,0feh ; Clear bit 0 of received_xoff 670 0440 A6 ana m 671 0441 C3 47 04 jmp track_xon_xoff 672 ; 673 0444 3E 01 rx_xoff: mvi a,1 ; Set bit 0 of received_xoff 674 0446 B6 ora m 675 0447 77 track_xon_xoff: mov m,a ; Update received_xoff 676 0448 C3 37 04 jmp pass_on 677 ; 678 ; 679 ; Map from an unshifted key to the symbol it has when shifted. This table has no terminator, 680 ; so it is exhaustive, i.e. every code other than the letter keys that passes through here 681 ; must be found somewhere. Taking the table as ending at the byte of key_scan_map, every 682 ; ASCII code from 20h to 7fh is here, either as the unshifted code or the shifted one. 683 ; 684 044B 30 29 key_shift_map: db '0', ')' 685 044D 31 21 db '1', '!' 686 044F 32 40 db '2', '@' 687 0451 33 23 db '3', '#' 688 0453 34 24 db '4', '$' 689 0455 35 25 db '5', '%' 690 0457 36 5E db '6', '^' 691 0459 37 26 db '7', '&' 692 045B 38 2A db '8', '*' 693 045D 39 28 db '9', '(' 694 045F 2D 5F db '-', '_' 695 0461 3D 2B db '=', '+' 696 0463 60 7E db '`', '~' 697 0465 5B 7B db '[', '{' 698 0467 5D 7D db ']', '}' 699 0469 3B 3A db 03bh, ':' ; 03bh is semicolon, which asm8080 hates in quotes :-) 700 046B 2F 3F db '/', '?' 701 046D 27 22 db 027h, 022h ; 027h is single quote, 022h is double quote 702 046F 2C 3C db ',', '<' 703 0471 2E 3E db '.', '<' 704 0473 5C 7C db '\', '|' 705 0475 20 20 db 20h,20h 706 0477 7F 7F db 7fh,7fh 707 ; 708 ; Key map. Keys are received from the keyboard in a column/row form, shown in TM Figure 4-4-4, 709 ; "Keyboard Switch Array". This table is arranged in natural order, by columns as 710 ; labelled in that figure, except there are just 11 rows for each column (most of the remaining 711 ; key codes being either absent or corresponding to key modifier functions like SHIFT, which 712 ; were weeded out by the keyboard interrupt. 713 ; 714 ; Because the first three keys of column 0 (rows 0 to 2) are absent from the array (i.e. these 715 ; scan codes are never sent by the keyboard), this table is overlapped by three bytes with 716 ; key_shift_map, hence the equate below. So, at first glance, each column should have 11 rows, 717 ; but column 0 appears to have 8. 718 ; 719 ; Numeric keypad entries have their "normal" ASCII code with the top bit set, so that KP9 is 720 ; '9'|80h, i.e. 0b9h. However, asm8080 won't let me write '9'|80h in a DB directive without 721 ; parenthesizing, so I'm just leaving hex values here. Whinge over. 722 ; 723 ; For stability, because of its big reversed "L" shape, the RETURN key has two switches, 724 ; with scan codes 04h and 64h. This table converts 04h to 0 and 64h to 13 (i.e. C0_CR) so 725 ; that it only counts once. 726 ; 727 04 76 key_scan_map equ $-3 728 ; Column 0 - this starts with row 3 (see comments above). 729 0479 7F 00 70 6F db 07fh, 0, 'p', 'o', 'y', 't', 'w', 'q' 79 74 77 71 730 ; Column 1 - c3 is arrow right 731 0481 C3 00 00 00 key_scan_col1: db 0c3h, 0, 0, 0, ']', '[', 'i', 'u', 'r', 'e', '1' 5D 5B 69 75 72 65 31 732 ; Column 2 - c4 is arrow left, c2 is arrow down, 81 is break 733 048C C4 00 C2 81 key_scan_col2: db 0c4h, 0, 0c2h, 81h, '`', '-', '9', '7', '4', '3',C0_ESC 60 2D 39 37 34 33 1B 734 ; Column 3 - c1 is arrow up, d2 is PF3, d0 is PF1, backspace and 735 ; tab have their natural codes 736 0497 C1 D2 D0 08 key_scan_col3: db 0c1h, 0d2h, 0d0h,C0_BS, '=', '0', '8', '6', '5', '2',C0_HT 3D 30 38 36 35 32 09 737 ; Column 4 - b7 is num7, d3 is PF4, d1 is PF2 738 04A2 B7 D3 D1 B0 key_scan_col4: db 0b7h, 0d3h, 0d1h, 0b0h,C0_LF, '\', 'l', 'k', 'g', 'f', 'a' 0A 5C 6C 6B 67 66 61 739 ; Column 5 - bx is numeric keypad, 740 04AD B8 8D B2 B1 key_scan_col5: db 0b8h, 08dh, 0b2h, 0b1h, 0, 27h, 3bh, 'j', 'h', 'd', 's' 00 27 3B 6A 68 64 73 741 ; Column 6 742 04B8 AE AC B5 B4 key_scan_col6: db 0aeh, 0ach, 0b5h, 0b4h,C0_CR, '.', ',', 'n', 'b', 'x', 082h 0D 2E 2C 6E 62 78 82 743 ; Column 7 744 04C3 B9 B3 B6 AD key_scan_col7: db 0b9h, 0b3h, 0b6h, 0adh, 0, '/', 'm', 20h, 'v', 'c', 'z' 00 2F 6D 20 76 63 7A 745 746 ; This is the checksum byte for ROM 1 (0000-07FF) 747 04CE FF db 0ffh ; CHECKSUM 748 ; 749 04CF F5 vertical_int: push psw 750 04D0 E5 push h 751 04D1 D5 push d 752 04D2 CD 4E 10 call shuffle ; finalise shuffle, if ready 753 04D5 C5 push b 754 04D6 3E 09 mvi a,9 ; "clear vertical frequency interrupt" 755 04D8 D3 A2 out iow_dc012 756 04DA FB ei 757 04DB 3A 65 20 lda smooth_scroll 758 04DE B7 ora a 759 04DF C2 0B 05 jnz update_scroll 760 04E2 21 51 20 lxi h,scroll_pending 761 04E5 B6 ora m 762 04E6 CA 3E 05 jz check_bell ; (not been asked to scroll, so skip to next job) 763 04E9 3E 01 mvi a,1 ; Now start a smooth scroll, so mark it as in progress 764 04EB 32 65 20 sta smooth_scroll 765 04EE B6 ora m ; reading scroll_pending (direction) setting flags too 766 04EF 36 00 mvi m,0 ; scroll_pending <- 0 767 04F1 3E 01 mvi a,1 ; scrolling up if 768 04F3 32 5B 20 sta scroll_dir 769 04F6 3A 56 21 lda bottom_margin 770 04F9 F2 05 05 jp connectx ; scroll direction is positive 771 04FC 3E 99 mvi a,99h ; else we're scrolling down (BCD 99h = -1) 772 04FE 32 5B 20 sta scroll_dir 773 0501 3A 55 21 lda top_margin 774 0504 3D dcr a 775 0505 CD 2F 12 connectx: call connect_extra ; 776 0508 32 7A 20 sta shuffle_ready ; A is not zero here 777 050B 01 5B 20 update_scroll: lxi b,scroll_dir 778 050E 0A ldax b ; A <- scroll direction (-1 or 1) 779 050F 21 5A 20 lxi h,scroll_scan 780 0512 86 add m ; A <- scan line ± 1 781 0513 27 daa ; keep arithmetic decimal, as there are 10 scan lines 782 0514 E6 0F ani 0fh ; A <- scan line (single BCD digit) 783 0516 77 mov m,a ; update scroll_scan 784 0517 57 mov d,a ; D <- scroll latch 785 0518 E6 03 ani 3 ; Update scroll latch in DC012, two low bits first 786 051A D3 A2 out iow_dc012 787 051C 7A mov a,d ; A <- scroll latch 788 051D 1F rar ; 789 051E A7 ana a ; 790 051F 1F rar ; shift right two bits 791 0520 F6 04 ori 4 ; mark this as "high bits of scroll latch" 792 0522 D3 A2 out iow_dc012 ; Update scroll latch in DC012, two high bits 793 0524 7A mov a,d ; A <- scroll latch 794 0525 B7 ora a ; done? 795 0526 C2 3E 05 jnz check_bell ; still scrolling, so skip the termination stuff 796 0529 32 65 20 sta smooth_scroll ; now we've finished that scroll 797 052C 0A ldax b ; A <- scroll direction 798 052D B7 ora a 799 052E 3A 56 21 lda bottom_margin 800 0531 FA 38 05 jm do_shuf ; A is -1 if screen is scrolling down 801 0534 3A 55 21 lda top_margin ; no, we're going up 802 0537 3D dcr a 803 0538 CD CE 11 do_shuf: call calc_shuf1 804 053B 32 7A 20 sta shuffle_ready ; A is not zero here 805 053E 21 78 20 check_bell: lxi h,bell_duration 806 0541 7E mov a,m 807 0542 B7 ora a 808 0543 CA 4F 05 jz nobell 809 0546 35 dcr m ; decrease remaining duration 810 0547 E6 04 ani 4 ; and flip speaker click bit every 8 cycles 811 0549 0F rrc ; bit 2 -< bit 1 812 054A 0F rrc ; bit 1 -< bit 0 813 054B 0F rrc ; bit 0 -< bit 7 (speaker click) 814 054C 32 46 21 sta kbd_online_mask 815 054F 21 2C 21 nobell: lxi h,blink_timer 816 0552 7E mov a,m 817 0553 3D dcr a 818 0554 C2 69 05 jnz no_blink 819 0557 36 35 mvi m,35h ; reset blink timer if it's expired, then do blink 820 0559 3E 08 mvi a,8 821 055B D3 A2 out iow_dc012 ; DC012 <- "toggle blink flip flop" 822 055D 21 CB 21 lxi h,test_field 823 0560 7E mov a,m ; If tests are repeatedly failing, the screen toggles between 824 0561 B7 ora a ; normal and reverse field at the blink rate 825 0562 CA 6A 05 jz out_ports 826 0565 D3 A2 out iow_dc012 827 0567 EE 01 xri 1 828 0569 77 no_blink: mov m,a ; update blink_timer or test_field, as necessary 829 056A 3E 40 out_ports: mvi a,iow_kbd_scan ; Keyboard scan is started by vertical refresh 830 056C 32 48 21 sta kbd_scan_mask ; When this mask is sent to the keyboard, it gets cleared 831 056F 21 7F 20 lxi h,frame_count ; increment frame count, used for timing purposes 832 0572 34 inr m 833 0573 3A A3 21 lda brightness 834 0576 D3 42 out iow_brightness 835 0578 3A 58 21 lda tx_rx_speed 836 057B D3 02 out iow_baud_rate 837 057D 3A C9 21 lda vint_nvr 838 0580 D3 62 out iow_nvr_latch 839 0582 C1 pop b 840 0583 D1 pop d 841 0584 E1 pop h 842 0585 F1 pop psw 843 0586 C9 ret 844 ; 845 0587 3A A5 21 receiver_tick: lda local_mode ; Do nothing in local mode 846 058A 21 7B 20 lxi h,in_setup ; or if we are in SET-UP 847 058D B6 ora m 848 058E C0 rnz 849 058F CD 75 06 call test_rx_q 850 0592 C8 rz ; No characters waiting 851 0593 47 reflect_char: mov b,a ; B <- received char 852 0594 3A 7B 20 lda in_setup 853 0597 B7 ora a 854 0598 78 mov a,b ; A <- received char 855 0599 C2 A9 05 jnz no_exec 856 059C 3A 79 20 lda gpo_flags 857 059F 07 rlc ; Test if we are currently passing characters to GPO 858 05A0 78 mov a,b ; A <- received char 859 05A1 DA A9 05 jc no_exec ; and if we are, don't interpret control characters 860 05A4 FE 20 cpi 20h 861 05A6 DA B2 08 jc exec_c0 862 05A9 2A 40 21 no_exec: lhld char_action 863 05AC E9 pchl 864 ; 865 ; Once DECGON has been received, all characters are passed through to the graphics 866 ; port until DECGOFF, ESC 2, is detected. This requires two states, an escape detector, 867 ; installed first, and a "2" detector. If detect ESC and then the next character isn't 868 ; "2", these routines will pass through the ESC and other character, and then head back 869 ; to escape detector. 870 ; 871 05AD FE 1B gfx_det_esc: cpi C0_ESC 872 05AF C2 CC 05 jnz gfx_send_char 873 05B2 21 B8 05 lxi h,gfx_det_final 874 05B5 C3 18 0A jmp install_action 875 ; 876 ; This state detects the final character of DECGOFF: '2'. Any other character will 877 ; need to be forwarded to the graphics port, after catching up by sending the preceding 878 ; ESC. 879 ; 880 ; Detecting DECGOFF will transition to ground state. 881 ; 882 05B8 FE 32 gfx_det_final: cpi '2' 883 05BA C2 C5 05 jnz gfx_send_esc 884 05BD 3E 01 mvi a,1 ; mark as present (bit 0) but not using (bit 7) 885 05BF 32 79 20 sta gpo_flags 886 05C2 C3 15 0A jmp to_ground 887 ; 888 05C5 47 gfx_send_esc: mov b,a ; Save the character that we weren't looking for 889 05C6 3E 1B mvi a,C0_ESC ; while we send through the ESC we previously detected 890 05C8 CD D2 05 call gfx_tx_char 891 05CB 78 mov a,b 892 05CC CD D2 05 gfx_send_char: call gfx_tx_char 893 05CF C3 62 0C jmp gfx_set_state ; back to detecting ESC 894 ; 895 05D2 4F gfx_tx_char: mov c,a 896 05D3 C5 wait_gpo_rdy: push b 897 05D4 CD 88 14 call keyboard_tick 898 05D7 C1 pop b 899 05D8 DB 42 in ior_flags ; TM §6.4.1 "When data is passed, the GRAPHICS FLAG goes 900 05DA E6 04 ani iob_flags_gpo ; high and stays high until the data is stored in [RAM]" 901 05DC C2 D3 05 jnz wait_gpo_rdy 902 05DF 79 mov a,c 903 05E0 D3 E2 out iow_graphics 904 05E2 C9 ret 905 ; 906 ; This early entry point (fall through) for print_char is only called from one place, when 907 ; we insert a control character in an answerback string, and at that point this point is 908 ; called with A = 1, in order to represent the control character onscreen as a diamond shape, 909 ; which is ROM glyph 1. It would be important at that point to ensure that the mappings 910 ; didn't disrupt the display, and calling single_shift would do that for ASCII range, but 911 ; codes 00h to 01fh pass straight through print_char anyway and produce Special Graphics codes, 912 ; without any mappings getting in the way. In fact, that is exactly what happens when CAN 913 ; and SUB are used to cancel an escape/control sequence and a checkboard character is printed; 914 ; the print_char entry point is used. Am I missing something? 915 ; 916 05E3 CD 46 0C print_nomap: call single_shift 917 ; fall through 918 ; 919 ; There is a bug with character set mapping at the line marked [* BUG *] below. 920 ; 921 ; To find out which character should be printed, the number in gl_invocation (normally 0 or 1), 922 ; is added to the address g0_charset, which is 20fdh, and the encoding designated into that set 923 ; will be used to print a character. If a single shift is in effect, the number in gl_invocation 924 ; will have been increased by 2, so G0 invoked into GL will have become G2 invoked into GL for 925 ; a single character. If SHIFT OUT (^N) was already in effect, such that G1 was invoked into GL, 926 ; and then a single shift is applied, G3 will be invoked into GL. 927 ; 928 ; At this point, the address of g0_charset (20fdh), in HL, has 3 added to it to find the mapping, 929 ; except that the addition only affects the L register, making HL now point to 2000h, when it 930 ; should be pointing to 2100h. 931 ; 932 ; Because 2000h always contains the value 7fh, as the start of the video DMA stream, this bug 933 ; will make it appear as if the United Kingdom character set had been invoked, regardless of the 934 ; SET-UP condition. 935 ; 936 ; To demonstrate this, the following three sequences should produce a string of three '#' (hash) 937 ; characters, but the third one will produce two '#' and a '£' (pound). 938 ; 939 ; 940 ; a) SI # SO # SI # (designations being G0, G1, G0) 941 ; b) SI # ESC N # # ( " " G0, G2, G0) 942 ; c) SI # SO # ESC N # ( " " G0, G1, G3) 943 ; 944 05E6 F5 print_char: push psw 945 05E7 FE 7F cpi 7fh ; Discard DEL 946 05E9 CA 73 06 jz delete_exit 947 05EC E5 push h 948 05ED D5 push d 949 05EE C5 push b 950 05EF 4F mov c,a ; C <- character to be printed 951 05F0 21 FC 20 lxi h,gl_invocation ; Now perform character set mapping 952 05F3 56 mov d,m ; D <- character set (0 = G0, 1 = G1) invoked 953 05F4 23 inx h ; HL <- g0_charset 954 05F5 7A mov a,d ; A <- charset 955 05F6 85 add l ; advance to invoked charset 956 05F7 6F mov l,a ; HL <- mapped charset [* BUG *] 957 05F8 7A mov a,d ; A <- charset 958 05F9 D6 02 sui 2 ; Speculatively remove single shift 959 05FB F2 FF 05 jp charset_range ; detect whether this worked 960 05FE 7A mov a,d ; A <- charset, which was fine anyway 961 05FF 32 FC 20 charset_range: sta gl_invocation ; Unshifted, ready for next character 962 0602 56 mov d,m ; D <- character map (see charset_list) 963 0603 3A FA 20 lda char_rend 964 0606 B2 ora d 965 0607 47 mov b,a ; B <- mixed map + rendition 966 0608 7A mov a,d ; A <- character map 967 0609 07 rlc ; Set carry flag for Special Graphics or Alternate ROM Graphics 968 060A 79 mov a,c ; A <- character 969 060B D2 16 06 jnc normal_mapping ; jump for "normal" sets 970 060E D6 5F sui 5fh ; Special graphics are encoded from 05fh, but are in ROM from glyph 0 971 0610 FE 20 cpi 20h ; ... but there are only 20h of them, so may unmap for rest of range 972 0612 D2 16 06 jnc normal_mapping 973 0615 4F mov c,a ; Preserve our adjusted mapping for graphics only 974 0616 7A normal_mapping: mov a,d ; A <- character map 975 0617 E6 40 ani 40h ; Only United Kingdom will be non-zero here 976 0619 79 mov a,c ; A <- character 977 061A CA 24 06 jz not_uk_enc 978 061D FE 23 cpi 23h ; Only difference between UK and ASCII is '#', 023h 979 061F C2 24 06 jnz not_uk_enc ; So leave everything else untouched 980 0622 0E 1E mvi c,1eh ; '#' becomes '£', at ROM glyph 01eh (TM Figure 4-6-18, p.4-78) 981 0624 3A FB 20 not_uk_enc: lda char_rvid ; A <- 0 = normal video, 80h = reverse video 982 0627 B1 ora c 983 0628 4F mov c,a ; C <- ROM glyph + normal/reverse video bit 984 0629 3A 42 21 lda pending_wrap 985 062C B7 ora a 986 062D CA 41 06 jz skip_wrap 987 0630 21 F8 20 lxi h,curs_col 988 0633 3A 53 21 lda right_margin ; test for cursor at right-hand edge 989 0636 BE cmp m 990 0637 C2 41 06 jnz skip_wrap 991 063A 36 00 mvi m,0 ; wrap to next line 992 063C C5 push b 993 063D CD 55 09 call index_down ; and move cursor down 994 0640 C1 pop b 995 0641 2A F6 20 skip_wrap: lhld cursor_address ; HL <- cursor address in screen RAM 996 0644 71 mov m,c ; display it 997 0645 7C mov a,h ; Now adjust address to point at attributes 998 0646 C6 10 adi 10h 999 0648 67 mov h,a 1000 0649 70 mov m,b ; Apply rendition (and mapping) 1001 064A 78 mov a,b 1002 064B 32 F5 20 sta rend_und_curs ; Record this rendition and mapping 1003 064E 21 F4 20 lxi h,char_und_curs 1004 0651 71 mov m,c ; Store character under cursor 1005 0652 21 F8 20 lxi h,curs_col 1006 0655 3A 53 21 lda right_margin 1007 0658 BE cmp m 1008 0659 C2 68 06 jnz no_wrap_needed 1009 065C 79 mov a,c ; A <- glyph + normal/reverse 1010 065D 32 F4 20 sta char_und_curs 1011 0660 3A A8 21 lda setup_b3 1012 0663 E6 40 ani sb3_autowrap 1013 0665 C3 6D 06 jmp may_need_wrap 1014 ; 1015 0668 34 no_wrap_needed: inr m ; move cursor right 1016 0669 CD 36 16 call move_updates 1017 066C AF xra a 1018 066D 32 42 21 may_need_wrap: sta pending_wrap 1019 0670 C1 pop b 1020 0671 D1 pop d 1021 0672 E1 pop h 1022 0673 F1 delete_exit: pop psw 1023 0674 C9 ret 1024 ; 1025 ; test_rx_q 1026 ; See if there are any received characters waiting to be processed, 1027 ; provided the user hasn't pressed NO SCROLL. 1028 ; Returns NZ if there is a character available; character in A. 1029 ; Returns Z if no scroll in operation or no characters waiting. 1030 ; 1031 0675 3A C4 21 test_rx_q: lda noscroll 1032 0678 B7 ora a 1033 0679 C2 85 06 jnz no_test_q 1034 067C 21 C0 20 lxi h,rx_head 1035 067F 7E mov a,m ; A <- rx_head 1036 0680 23 inx h ; HL <- rx_tail 1037 0681 96 sub m ; A <- rx_head - rx_tail 1038 0682 C2 87 06 jnz rx_not_empty ; jump if characters received 1039 0685 AF no_test_q: xra a 1040 0686 C9 ret 1041 ; 1042 0687 6E rx_not_empty: mov l,m ; L <- rx_tail 1043 0688 26 20 mvi h,20h ; Make HL pointer to character 1044 068A 56 mov d,m ; D <- first character in buffer 1045 068B 21 C1 20 lxi h,rx_tail 1046 068E 7E mov a,m 1047 068F 3C inr a ; advance rx_tail pointer 1048 0690 E6 BF ani 0bfh ; with wrap 1049 0692 F6 80 ori 80h 1050 0694 77 mov m,a ; write new rx_tail 1051 0695 2B dcx h ; HL <- rx_head 1052 0696 96 sub m ; A <- tail - head 1053 0697 F2 9C 06 jp nowrap2 1054 069A C6 40 adi 40h ; space is "over the wrap" 1055 069C FE 30 nowrap2: cpi 30h ; Is the buffer three-quarters empty? 1056 069E C2 A7 06 jnz not_empty_yet 1057 06A1 01 11 01 lxi b,1<<8|C0_XON 1058 06A4 CD 7E 0F call send_xonoff 1059 06A7 7A not_empty_yet: mov a,d ; A <- first character in buffer 1060 06A8 B7 ora a 1061 06A9 C9 ret 1062 ; 1063 06AA 3A 68 20 process_keys: lda key_flags 1064 06AD 5F mov e,a 1065 06AE E6 80 ani key_flag_eos ; Don't bother processing anything until end of scan, 1066 06B0 C8 rz ; so exit immediately 1067 06B1 21 41 08 lxi h,clear_keyboard 1068 06B4 E5 push h ; always exit by clearing down keyboard flags and silo 1069 06B5 7B mov a,e ; A <- key_flags 1070 06B6 E6 07 ani 7 ; isolate count 1071 06B8 FE 04 cpi 4 1072 06BA FA C2 06 jm process_silo ; if fewer than 4 keys waiting, process them 1073 06BD AF xra a ; else we've overflowed 1074 06BE 32 67 20 sta new_key_scan ; there are no new keys 1075 06C1 C9 ret ; throw away flags and silo on way out 1076 ; 1077 06C2 57 process_silo: mov d,a ; D <- number of keys in silo 1078 06C3 0E 00 mvi c,0 ; C <- 0, number of keys in silo matching history 1079 06C5 06 04 mvi b,4 ; loop through (up to) 4 history entries 1080 06C7 21 6E 20 lxi h,key_history 1081 06CA 7E next_history: mov a,m ; grab next key from history 1082 06CB B7 ora a 1083 06CC CA E8 06 jz zero_history ; if there wasn't a key here, skip search 1084 06CF E5 push h 1085 06D0 C5 push b 1086 ; Having grabbed a key from the key history, we try to find it in the silo. Finding 1087 ; it still down confirms it as something other than a bounce, and we'll leave it for 1088 ; processing. If we don't find it, we'll zero out the entry in the history. 1089 ; This loop employs a neat trick of distinguishing between a silo match and 1090 ; exhausting the the search. If we find a match, we'll jump out of the comparison 1091 ; loop with Z flag set. The normal way of running a count would also leave us 1092 ; looping from a number down to zero though, meaning the Z flag would be set in 1093 ; either case. So here, the loop is run from one count less and B is decremented 1094 ; and loops while it is still positive. So, exhausting the silo leaves us with 1095 ; Z flag *unset*. 1096 06D1 06 03 mvi b,3 1097 06D3 21 6A 20 lxi h,key_silo 1098 06D6 BE compare_silo: cmp m ; Does old key exist in silo? 1099 06D7 CA DF 06 jz silo_match ; yes, process 1100 06DA 23 inx h ; next location in silo 1101 06DB 05 dcr b 1102 06DC F2 D6 06 jp compare_silo 1103 06DF C1 silo_match: pop b 1104 06E0 E1 pop h 1105 06E1 CA E7 06 jz found_in_silo ; If we found key in silo (result of cmp), add to history 1106 06E4 36 00 mvi m,0 ; zero out the key from history 1107 06E6 0D dcr c ; Reduce the number of matching keys, to overcome next instruction 1108 06E7 0C found_in_silo: inr c ; Increase the number of matching keys 1109 06E8 23 zero_history: inx h ; next history entry 1110 06E9 05 dcr b 1111 06EA C2 CA 06 jnz next_history 1112 ; 1113 06ED 7B mov a,e ; A <- key flags 1114 06EE E6 08 ani 8 ; According to TM, this bit was intended for SETUP, but this 1115 06F0 C0 rnz ; must be out of date; can't see a way of setting this bit now. 1116 06F1 B2 ora d ; Include the number of keys in silo 1117 06F2 C2 FB 06 jnz keys_avail 1118 06F5 3E E1 mvi a,-31 ; When there are no keys down, reset the key repeat timer 1119 06F7 32 72 20 sta key_rpt_timer 1120 06FA C9 ret 1121 ; 1122 06FB 21 47 07 keys_avail: lxi h,check_history ; next stage 1123 06FE E5 push h 1124 06FF 3A 72 20 lda key_rpt_timer 1125 0702 3C inr a 1126 0703 CA 09 07 jz rpt_expired 1127 0706 32 72 20 sta key_rpt_timer 1128 0709 3A A6 21 rpt_expired: lda setup_b1 1129 070C E6 40 ani sb1_autorep ; If we're not auto-repeating, timer expiry is irrelevant 1130 070E C8 rz 1131 070F 7B mov a,e ; A <- key flags 1132 0710 E6 10 ani 10h ; CTRL pressed? 1133 0712 C0 rnz ; A key with control cannot repeat (TM §4.4.9.5) 1134 0713 3A 50 21 lda latest_key_scan 1135 0716 21 5F 08 lxi h,nonrepeat ; Five keys are not allowed to auto-repeat 1136 0719 06 05 mvi b,5 1137 071B BE chk_nonrep: cmp m ; If this is one of them, reject it (return early) 1138 071C C8 rz 1139 071D 23 inx h 1140 071E 05 dcr b 1141 071F C2 1B 07 jnz chk_nonrep 1142 0722 21 73 20 lxi h,key_rpt_pause ; Pause before repeating begins (about half a second) 1143 0725 35 dcr m 1144 0726 C0 rnz 1145 0727 36 02 mvi m,2 ; Two counts per repeat (30 per second) 1146 0729 3A 72 20 lda key_rpt_timer 1147 072C FE FF cpi 0ffh 1148 072E C0 rnz 1149 072F 79 mov a,c ; A <- number of matching keys (in history) 1150 0730 FE 01 cpi 1 ; Only one key can repeat at a time 1151 0732 C0 rnz 1152 0733 06 04 mvi b,4 ; The key could be in any of the history slots 1153 0735 21 6E 20 lxi h,key_history 1154 0738 7E check_rpt_slot: mov a,m 1155 0739 B7 ora a 1156 073A C2 43 07 jnz got_repeater 1157 073D 23 inx h 1158 073E 05 dcr b 1159 073F C2 38 07 jnz check_rpt_slot 1160 0742 C9 ret ; We should have found one, but failed! 1161 ; 1162 0743 E1 got_repeater: pop h ; Pop the return address we stacked earlier 1163 0744 C3 76 07 jmp set_latest 1164 ; 1165 0747 79 check_history: mov a,c ; A <- number of matching keys in history 1166 0748 FE 04 cpi 4 ; Still got false keys down? 1167 074A F0 rp ; if so, exit 1168 074B 01 6A 20 lxi b,key_silo 1169 074E 21 6E 20 comp_silo_hist: lxi h,key_history 1170 0751 7E try_next_hist: mov a,m ; A <- key from history 1171 0752 B7 ora a 1172 0753 CA 5B 07 jz next_hist_key 1173 0756 0A ldax b ; Is this key in silo same as this history key? 1174 0757 BE cmp m 1175 0758 CA 66 07 jz next_silo_key ; OK, found it 1176 075B 23 next_hist_key: inx h ; try next key in history 1177 075C 7D mov a,l ; 1178 075D FE 72 cpi LOW key_history + 4 1179 075F C2 51 07 jnz try_next_hist ; try again, unless at end of history buffer 1180 0762 0A ldax b ; A <- key from silo 1181 0763 C3 6C 07 jmp found_new_key 1182 ; 1183 0766 03 next_silo_key: inx b ; point to next silo entry 1184 0767 15 dcr d ; decrease number of keys in silo 1185 0768 C2 4E 07 jnz comp_silo_hist 1186 076B C9 ret 1187 ; 1188 076C 47 found_new_key: mov b,a ; B <- scan code 1189 076D 3A 67 20 lda new_key_scan 1190 0770 B8 cmp b 1191 0771 78 mov a,b ; A <- scan code 1192 0772 32 67 20 sta new_key_scan 1193 0775 C0 rnz 1194 0776 E1 set_latest: pop h 1195 0777 32 50 21 sta latest_key_scan 1196 077A FE 7B cpi 7bh ; is SETUP pressed? 1197 077C CA 1A 1A jz setup_pressed ; yes, mark as pending 1198 077F FE 6A cpi 6ah ; is NO SCROLL pressed? 1199 0781 CA 2C 02 jz no_scroll_key 1200 0784 43 mov b,e ; B <- key flags 1201 0785 5F mov e,a ; E <- scan code 1202 0786 3A 44 21 lda keyboard_locked ; Don't process while keyboard locked - clear SILO 1203 0789 B7 ora a 1204 078A C2 41 08 jnz clear_keyboard 1205 ; 1206 ; Each column of the key switch array contains 11 entries, so need to take a column 1207 ; number in the top four bits of E, divide by 16, and then multiply by 11. 1208 ; 1209 078D 7B mov a,e ; A <- scan code 1210 078E E6 F0 ani 0f0h ; A <- 16 * column (because shifted) 1211 0790 0F rrc 1212 0791 0F rrc 1213 0792 57 mov d,a ; D <- 4 * column 1214 0793 0F rrc 1215 0794 0F rrc ; A <- column 1216 0795 82 add d ; A <- 5 * column 1217 0796 57 mov d,a ; D <- 5 * column 1218 0797 7B mov a,e ; A <- scan code = 16 * column + row 1219 0798 92 sub d ; A <- 11 * column + row 1220 0799 21 76 04 lxi h,key_scan_map 1221 079C 5F mov e,a 1222 079D 16 00 mvi d,0 ; DE <- A (offset) 1223 079F 19 dad d ; HL <- offset into array for this scan code 1224 07A0 4E mov c,m ; C <- key code 1225 07A1 79 mov a,c ; A <- key code 1226 07A2 B7 ora a ; 1227 07A3 FA 31 01 jm proc_func_key ; jump to deal with function keys 1228 07A6 FE 20 cpi 20h 1229 07A8 DA FF 07 jc done_modifiers ; unmapped, ESC, BS, TAB, LF, RETURN 1230 07AB 78 mov a,b ; A <- key flags 1231 07AC E6 70 ani 70h ; any modifiers pressed? 1232 07AE CA FF 07 jz done_modifiers ; no, simple path 1233 ; Given that some modifiers have been pressed, work out a candidate shift code 1234 07B1 79 mov a,c ; A <- key code 1235 07B2 FE 7B cpi 7bh ; candidates for table shift 1236 07B4 D2 C2 07 jnc use_shift_map 1237 07B7 FE 61 cpi 61h ; also shift these by table 1238 07B9 DA C2 07 jc use_shift_map 1239 ; Otherwise, we are just left with lowercase letter range (61h to 07ah), 1240 ; and they are shifted to uppercase by a simple logical and. 1241 07BC E6 DF ani 0dfh ; make uppercase from lowercase 1242 07BE 4F mov c,a ; C <- shifted key code 1243 07BF C3 D7 07 jmp exam_key_mods 1244 ; 1245 07C2 78 use_shift_map: mov a,b ; A <- key flags 1246 07C3 E6 30 ani 30h ; are CTRL or SHIFT pressed? 1247 07C5 CA D7 07 jz exam_key_mods ; no, skip shift table 1248 07C8 21 4B 04 lxi h,key_shift_map ; HL <- start of shift table 1249 07CB 7E next_shift: mov a,m ; get unshifted code from table 1250 07CC B9 cmp c ; compare with ours 1251 07CD CA D5 07 jz pick_shift ; match, go and pick up shift code 1252 07D0 23 inx h ; go past shifted code 1253 07D1 23 inx h ; to next key 1254 07D2 C3 CB 07 jmp next_shift ; round again 1255 ; 1256 07D5 23 pick_shift: inx h ; advance to shift code 1257 07D6 4E mov c,m ; C <- shifted key code 1258 07D7 78 exam_key_mods: mov a,b ; A <- key flags 1259 07D8 E6 10 ani 10h ; mask with CTRL 1260 07DA CA FF 07 jz done_modifiers ; not CTRL, straight out, we've got final code 1261 07DD 79 mov a,c ; A <- shifted key code 1262 07DE FE 41 cpi 'A' 1263 07E0 DA E8 07 jc below_alpha 1264 07E3 FE 5B cpi '[' ; (the real '[' has already been shifted to '{') 1265 07E5 DA FC 07 jc apply_ctrl ; apply to 'A' to 'Z' 1266 07E8 FE 3F below_alpha: cpi '?' 1267 07EA CA FC 07 jz apply_ctrl ; '?' 3fh =< 1fh (US) 1268 07ED FE 20 cpi 20h ; 1269 07EF CA FC 07 jz apply_ctrl ; SP 20h =< 00h (NUL) 1270 07F2 FE 7B cpi 7bh 1271 07F4 DA 41 08 jc clear_keyboard 1272 07F7 FE 7F cpi 7fh 1273 07F9 D2 41 08 jnc clear_keyboard 1274 ; Codes 07bh to 07eh drop through here, allowing us to 1275 ; produce control codes from 1bh (ESC) to 1eh (RS) 1276 07FC E6 9F apply_ctrl: ani 9fh ; e.g. 'A' 01000001 & 10011111 -< 00000001 1277 07FE 4F mov c,a ; C <- final code 1278 07FF B1 done_modifiers: ora c 1279 0800 F5 push psw 1280 0801 3A A7 21 lda setup_b2 1281 0804 E6 80 ani sb2_marginbell 1282 0806 CA 0C 08 jz mbell_disabled 1283 0809 32 54 21 sta margin_bell ; Typing a key could potentially trigger a margin bell 1284 080C F1 mbell_disabled: pop psw 1285 080D F6 80 send_key_end: ori 80h ; Set high bit to mark as last character we're sending 1286 080F CD 18 0F call send_key_byte 1287 ; 1288 0812 CD 53 08 pk_click: call make_keyclick 1289 ; 1290 ; Now we've identified the latest key, place it in a zeroed location in the key history, 1291 ; if it isn't already there. 1292 ; 1293 0815 3A 50 21 pk_noclick: lda latest_key_scan 1294 0818 16 04 mvi d,4 1295 081A 21 6E 20 lxi h,key_history 1296 081D BE next_hist: cmp m 1297 081E CA 41 08 jz clear_keyboard 1298 0821 23 inx h 1299 0822 15 dcr d 1300 0823 C2 1D 08 jnz next_hist 1301 0826 16 04 mvi d,4 1302 0828 21 6E 20 lxi h,key_history 1303 082B 7E next_hist2: mov a,m 1304 082C B7 ora a 1305 082D CA 38 08 jz got_hist_place 1306 0830 23 inx h 1307 0831 15 dcr d 1308 0832 C2 2B 08 jnz next_hist2 1309 0835 C3 41 08 jmp clear_keyboard 1310 ; 1311 0838 3A 50 21 got_hist_place: lda latest_key_scan 1312 083B 77 mov m,a ; place key in history 1313 083C 3E E1 mvi a,-31 ; reset key repeat timer 1314 083E 32 72 20 sta key_rpt_timer 1315 ; 1316 ; clear_keyboard 1317 ; At the end of keyboard processing, clear key_flags and the keyboard silo. 1318 ; 1319 0841 AF clear_keyboard: xra a 1320 0842 21 68 20 lxi h,key_flags 1321 0845 56 mov d,m ; D <- key_flags 1322 0846 77 mov m,a ; zero key_flags 1323 0847 23 inx h ; HL <- last_key_flags 1324 0848 72 mov m,d ; last_key_flags <- key_flags 1325 0849 23 inx h ; HL <- key_silo 1326 084A 16 04 mvi d,4 ; 4 locations to clear 1327 084C 77 clear_silo: mov m,a ; zero silo location 1328 084D 23 inx h ; next location 1329 084E 15 dcr d 1330 084F C2 4C 08 jnz clear_silo 1331 0852 C9 ret 1332 ; 1333 0853 3A A7 21 make_keyclick: lda setup_b2 1334 0856 E6 40 ani sb2_keyclick 1335 0858 C8 rz ; exit if keyclick is not enabled 1336 0859 3E 80 mvi a,iow_kbd_click 1337 085B 32 47 21 sta kbd_click_mask ; When this mask is sent to the keyboard, it gets cleared 1338 085E C9 ret 1339 ; 1340 ; Table of keys that are not allowed to auto-repeat (TM §4.4.9.5) 1341 ; These are: SETUP, ESC, NO SCROLL, TAB, RETURN 1342 ; 1343 085F 7B 2A 6A 3A nonrepeat: db 7bh, 2ah, 6ah, 3ah, 64h 64 1344 1345 ; Entry point for DECTST, which has form ESC [ 2 ; Ps y 1346 0864 3A 30 21 tst_action: lda csi_p1 ; The first parameter to DECTST 1347 0867 D6 02 sui 2 ; must be the value 2 1348 0869 C0 rnz ; else quit early! 1349 086A 57 mov d,a ; D <- 0 1350 086B 3A 31 21 lda csi_p2 ; Now grab the test(s) we'd like to perform 1351 086E 5F mov e,a ; E <- test values (each bit set is a test) 1352 rep_tests: 1353 086F 3E 01 mvi a,1 ; Test mask 0x01 - power up self test 1354 0871 A3 ana e ; if POST is requested, 1355 0872 C2 3D 00 jnz self_test ; do it. (POST knows to return here.) 1356 0875 7A continue_tests: mov a,d ; A <- cumulative test results 1357 0876 B7 ora a 1358 0877 C4 A7 08 cnz note_failure 1359 087A D5 push d ; preserve current results on stack ... 1360 087B CD BE 17 call print_wait ; "Wait", we could be here a while 1361 087E D1 pop d ; ... and restore 1362 087F 3E 02 mvi a,2 ; Test mask 0x02 - data loop back test 1363 0881 A3 ana e 1364 0882 C4 11 1F cnz data_loop_test 1365 0885 3E 08 mvi a,8 ; A <- display mask for failing tests 1366 0887 DC A7 08 cc note_failure 1367 088A 3E 04 mvi a,4 ; Test mask 0x04 - modem control test 1368 088C A3 ana e 1369 088D C4 62 1F cnz modem_test 1370 0890 3E 10 mvi a,10h ; A <- display mask for failing tests 1371 0892 DC A7 08 cc note_failure 1372 0895 7A mov a,d ; Have we failed any tests? 1373 0896 B7 ora a 1374 0897 C2 A0 08 jnz skip_repeat ; if so, don't go round again 1375 089A 3E 08 mvi a,8 ; Test mask 0x08 1376 089C A3 ana e ; means repeat selected tests indefinitely until 1377 089D C2 6F 08 jnz rep_tests ; failure or power off 1378 skip_repeat: 1379 08A0 7A mov a,d ; Get test results 1380 08A1 32 00 20 sta ram_start ; squirrel away (see after_tests) 1381 08A4 C3 51 02 jmp after_tests 1382 ; 1383 ; Add bits to D register as tests fail. Current mask to add to failure is in A register. 1384 ; This corresponds directly to the characters displayed on screen for test failure, 1385 ; except bit 7. The displayed characters are shown in TM Table 5-6. 1386 ; Pulling this into ASCII range is done in see after_tests. 1387 ; 1388 ; 7 6 5 4 3 2 1 0 1389 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 1390 ; D: |cycle| | |modem| data| kbd | RAM | AVO | 1391 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 1392 ; 1393 08A7 B2 note_failure: ora d ; A <- new failure mask | previous failures 1394 08A8 57 mov d,a ; D <- new failure tally 1395 08A9 3E 08 mvi a,8 ; 8 is a request to cycle tests 1396 08AB A3 ana e ; E is tests requested 1397 08AC C8 rz 1398 08AD 3E 80 mvi a,80h ; Mark "cycle tests" in failure mask 1399 08AF B2 ora d 1400 08B0 57 mov d,a 1401 08B1 C9 ret 1402 ; 1403 08B2 FE 1B exec_c0: cpi C0_ESC 1404 08B4 CA 9E 09 jz c0_escape ; start extended processing 1405 08B7 FE 10 cpi C0_DLE ; Are we in column 0 or column 1? 1406 08B9 DA D4 08 jc process_col0 ; Column 0 controls go through a jump table 1407 08BC 5F mov e,a ; E <- character 1408 08BD D6 18 sui 18h 1409 08BF E6 FD ani 0fdh ; Z if A was 018h (CAN) or 01ah (SUB) 1410 08C1 C0 rnz ; No? No action for anything else in this column 1411 08C2 7B mov a,e ; A <- character 1412 08C3 FE 1A cpi C0_SUB 1413 08C5 CA CB 08 jz cancel_seq 1414 08C8 FE 18 cpi C0_CAN 1415 08CA C0 rnz 1416 08CB CD 15 0A cancel_seq: call to_ground 1417 08CE 3E 02 mvi a,2 ; ROM glyph 2 is checkerboard (error indicator) 1418 08D0 C3 E6 05 jmp print_char 1419 ; 1420 08D3 C9 ret ; UNREACHABLE 1421 ; 1422 ; It happens that the only C0 controls that the VT100 "processes" are those from column 0, 1423 ; i.e. with codes 00h to 0fh, and they are looked up here. Codes from column 1 are either 1424 ; dealt with earlier for communication control (XON and XOFF), invoke extended processing 1425 ; (ESC) or just cancel that extended processing (CAN and SUB). 1426 ; 1427 08D4 D6 05 process_col0: sui 5 ; Codes 00h to 04h are ignored (actually, NUL never got here) 1428 08D6 F8 rm 1429 08D7 21 E5 08 lxi h,c0_actions 1430 08DA 87 add a ; Addresses are two bytes long, so double up our code, 1431 08DB 5F mov e,a ; use DE to hold a 16-bit offset 1432 08DC 16 00 mvi d,0 1433 08DE 19 dad d ; and add to the table base address 1434 08DF 5E mov e,m ; extract action routine 1435 08E0 23 inx h ; address 1436 08E1 56 mov d,m ; DE <- action routine 1437 08E2 EB xchg ; HL <-< DE (can't jump to DE) 1438 08E3 AF xra a ; convenience clear - used for SHIFT IN/OUT 1439 08E4 E9 pchl ; Jump to action routine, so its return will continue 1440 ; other processing 1441 ; JUMP TABLE 1442 c0_actions: 1443 08E5 00 09 dw c0_answerback ; 05 ENQ (answerback) 1444 08E7 97 09 dw do_nothing_ack ; 06 ACK (acknowledge) - does nothing 1445 08E9 38 09 dw c0_bell ; 07 BEL (bell) 1446 08EB 41 09 dw c0_backspace ; 08 BS (backspace) 1447 08ED F9 0D dw c0_horiz_tab ; 09 HT (horizontal tab) 1448 08EF 55 09 dw index_down ; 0a LF (line feed) 1449 08F1 55 09 dw index_down ; 0b VT (vertical tab) 1450 08F3 55 09 dw index_down ; 0c FF (form feed) 1451 08F5 4B 09 dw c0_return ; 0d CR (carriage return) 1452 08F7 FB 08 dw c0_shift_out ; 0e SO (shift out) 1453 08F9 FC 08 dw c0_shift_in ; 0f SI (shift in) 1454 1455 08FB 3C c0_shift_out: inr a 1456 08FC 32 FC 20 c0_shift_in: sta gl_invocation 1457 08FF C9 ret 1458 ; 1459 0900 3A A5 21 c0_answerback: lda local_mode ; Can't send answerback in local mode 1460 0903 B7 ora a 1461 0904 C0 rnz 1462 0905 2A 7B 21 lhld aback_buffer 1463 0908 7C mov a,h ; If the first two characters of the answerback buffer 1464 0909 BD cmp l ; are the same, they are two delimiters and the message 1465 090A C8 rz ; is empty, so exit 1466 090B 21 72 21 lxi h,pending_report 1467 090E 3E 10 mvi a,pend_aback 1468 0910 B6 ora m 1469 0911 77 mov m,a 1470 0912 C9 ret 1471 ; 1472 ; aback_report 1473 ; Produce the answerback report by dumping the buffer from aback_buffer, raw, to the serial stream. 1474 ; There can be up to 20 characters in this buffer, but may be fewer, as determined by the first 1475 ; byte, the delimiter. 1476 ; 1477 0913 21 72 21 aback_report: lxi h,pending_report 1478 0916 7E mov a,m 1479 0917 E6 EF ani ~pend_aback 1480 0919 77 mov m,a 1481 091A 21 7B 21 lxi h,aback_buffer 1482 091D 46 mov b,m ; B <- delimiter 1483 091E 23 inx h ; next character 1484 091F 0E 14 mvi c,20 ; we're going to send 20 characters at most 1485 0921 11 5C 21 lxi d,report_buffer 1486 0924 7E more_aback: mov a,m ; grab next character 1487 0925 B8 cmp b ; if it is the delimiter, exit 1488 0926 CA 30 09 jz aback_repend 1489 0929 12 stax d ; add character to report 1490 092A 13 inx d ; next report location 1491 092B 23 inx h ; next answerback location 1492 092C 0D dcr c 1493 092D C2 24 09 jnz more_aback 1494 0930 1B aback_repend: dcx d ; go back one character 1495 0931 1A ldax d ; grab the last character 1496 0932 F6 80 ori 80h ; mark it as the end of string, by setting bit 7 1497 0934 12 stax d ; put it back 1498 0935 C3 F3 0E jmp send_report 1499 ; 1500 0938 21 78 20 c0_bell: lxi h,bell_duration 1501 093B 7E mov a,m 1502 093C C6 08 adi 8 1503 093E D8 rc 1504 093F 77 mov m,a 1505 0940 C9 ret 1506 ; 1507 0941 21 F8 20 c0_backspace: lxi h,curs_col 1508 0944 7E mov a,m 1509 0945 B7 ora a 1510 0946 C8 rz ; Can't backspace beyond the start of the line 1511 0947 35 dcr m ; Move cursor one column left 1512 0948 C3 36 16 jmp move_updates 1513 ; 1514 ; c0_return - move the cursor to the start of the line, the CR action 1515 094B AF c0_return: xra a 1516 094C 32 F8 20 sta curs_col ; Column zero 1517 094F C3 36 16 jmp move_updates 1518 ; 1519 0952 CD 4B 09 nextline: call c0_return 1520 ; 1521 ; C0 codes LF, VT and FF, as well as escape sequence IND, are all processed the same way 1522 0955 3A A8 21 index_down: lda setup_b3 ; Check newline (LNM) mode, to see if moving down also 1523 0958 E6 20 ani sb3_newline ; implies moving to the start of the (next) line. 1524 095A C4 4B 09 cnz c0_return 1525 095D 21 F9 20 lxi h,curs_row 1526 0960 56 mov d,m 1527 0961 3A 56 21 lda bottom_margin 1528 0964 BA cmp d 1529 0965 CA 73 09 jz at_margin_b 1530 0968 CD BF 11 call last_row ; B <- last row number 1531 096B 78 mov a,b 1532 096C BA cmp d 1533 096D C8 rz 1534 096E 14 inr d ; Not at bottom of screen, so just increment row number 1535 096F 72 mov m,d 1536 0970 C3 36 16 jmp move_updates 1537 ; 1538 0973 3A A6 21 at_margin_b: lda setup_b1 1539 0976 E6 80 ani sb1_smooth 1540 0978 CA 98 09 jz jump_scroll_up 1541 097B 0E 01 mvi c,1 1542 097D CD 8E 10 call wait_scroll 1543 0980 3E FF mvi a,0ffh 1544 0982 32 C3 21 sta row_clearing 1545 0985 34 inr m ; scroll_pending <- 1 1546 0986 CD E6 0F call move_lines_up 1547 0989 3A C3 21 wait_clear1: lda row_clearing 1548 098C B7 ora a 1549 098D C2 89 09 jnz wait_clear1 1550 0990 32 F4 20 sta char_und_curs ; blank under cursor (00h) 1551 0993 3D dcr a 1552 0994 32 F5 20 sta rend_und_curs ; and default rendition (0ffh) 1553 ; 1554 ; This return instruction is used as a handy jump point for the C0 ACK control, 1555 ; which the terminal ignores. 1556 0997 C9 do_nothing_ack: ret 1557 ; 1558 0998 CD 1A 10 jump_scroll_up: call start_jump_up 1559 099B C3 12 10 jmp inv_saved_row 1560 ; 1561 ; The routine that kicks off all escape and control sequence processing in the terminal. 1562 ; 1563 099E AF c0_escape: xra a 1564 099F 32 7D 20 sta inter_chars 1565 09A2 32 B8 21 sta csi_private 1566 09A5 21 AB 09 lxi h,recog_esc 1567 09A8 C3 18 0A jmp install_action 1568 ; 1569 09AB FE 30 recog_esc: cpi '0' ; all characters <= '0' are finals 1570 09AD D2 BD 09 jnc esc_final 1571 09B0 21 7D 20 lxi h,inter_chars ; so this is an intermediate character that we will 1572 09B3 4F mov c,a ; store for later 1573 09B4 7E mov a,m 1574 09B5 B7 ora a ; check that we haven't already had an intermediate 1575 09B6 CA BB 09 jz store_inter 1576 09B9 0E FF mvi c,0ffh ; invalidate the intermediate if so 1577 09BB 71 store_inter: mov m,c ; store valid or invalid intermediate 1578 09BC C9 ret 1579 ; 1580 09BD 32 7E 20 esc_final: sta final_char 1581 09C0 21 15 0A lxi h,to_ground ; Setup a return address for after the escape sequence's action 1582 09C3 E5 push h 1583 09C4 3A 7D 20 lda inter_chars ; Is there an intermediate character? (Or one we invalidated 1584 09C7 B7 ora a ; earlier by storing 0ffh?) 1585 09C8 C2 DC 09 jnz esc_inter 1586 09CB 3A A7 21 lda setup_b2 ; Given that there are no intermediates, we are now dealing 1587 09CE E6 20 ani sb2_ansi ; with a simple ESC Final sequence, which we will now lookup 1588 09D0 21 50 0A lxi h,ansiesct ; in either the ANSI (VT100) table, or VT52 table. 1589 09D3 C2 D9 09 jnz do_esc_find 1590 09D6 21 1C 0A lxi h,vt52esct 1591 09D9 C3 F7 09 do_esc_find: jmp find_action 1592 ; 1593 09DC 47 esc_inter: mov b,a ; B <- intermediate 1594 09DD 3A A7 21 lda setup_b2 ; Check if we're in ANSI mode 1595 09E0 E6 20 ani sb2_ansi ; and if not, exit immediately because 1596 09E2 78 mov a,b ; there are no VT52 escape sequences with 1597 09E3 C8 rz ; intermediate characters 1598 09E4 FE 28 cpi '(' ; SCS sequence - designate to G0 1599 09E6 11 FD 20 lxi d,g0_charset ; DE <- location of G0 mapping, for when we've looked up charset 1600 09E9 CA 32 0C jz scs_g0 1601 09EC FE 29 cpi ')' ; SCS sequence - designate to G1 1602 09EE CA 31 0C jz scs_g1 1603 09F1 FE 23 cpi '#' ; ESC # Final are the presentation sequences (DECDWL, etc.) 1604 09F3 21 7B 0A lxi h,esc_hash_table 1605 09F6 C0 rnz ; If the intermediate was something else, or invalid (0ffh), do nothing 1606 ; drop through to find an action for the final character of the ESC # sequence 1607 1608 ; find_action is used to search a table for a final character and extract the address of the 1609 ; appropriate action routine. A secondary entry point, 1610 ; find_action_a is used to select an action when a Ps parameter value is already in register A 1611 ;(see sgr_action for an example.) 1612 ; find_action will jump to the found action routine, which means a normal return from that routine 1613 ; will return to find_action's caller. In some cases we see someone jumping to this routine, having 1614 ; stowed another return address on the stack in advance. 1615 ; As well as calling an action routine, record the found entry in found_action, which allows the 1616 ; caller to try several tables in sequence, if needed (i.e. CSI execution.) 1617 ; This routine preserves B, as it is used to store the selective parameter to be 1618 ; passed to the action routine. 1619 ; 1620 09F7 11 B9 21 find_action: lxi d,found_action ; we'll record success here 1621 09FA AF xra a 1622 09FB 12 stax d ; but initally say "nothing found" 1623 09FC 3A 7E 20 lda final_char 1624 09FF 4F find_action_a: mov c,a 1625 0A00 AF next_action: xra a 1626 0A01 86 add m ; A <- next comparison value 1627 0A02 C8 rz ; return if reached end of table 1628 0A03 23 inx h ; increment to action routine low byte 1629 0A04 B9 cmp c ; is this the value we want? 1630 0A05 CA 0D 0A jz exec_action ; found value/character we were looking for 1631 0A08 23 inx h ; else increment over the action routine address 1632 0A09 23 inx h ; to the next comparison value 1633 0A0A C3 00 0A jmp next_action ; and try again 1634 ; 1635 0A0D 12 exec_action: stax d ; record entry that matched 1636 0A0E 7E mov a,m ; A <- low byte of action address 1637 0A0F 23 inx h ; hop to high byte 1638 0A10 66 mov h,m ; H <- high byte of action address 1639 0A11 6F mov l,a ; L <- low byte of action address 1640 0A12 AF xra a 1641 0A13 E9 pchl ; jump to action routine 1642 ; 1643 ; pop_to_ground is only jumped to if initialisation for DECGON fails, in see gfx_init. We now pop the 1644 ; original return address before returning to ground state. Because gfx_init was reached through either 1645 ; the ANSI or VT52 escape jump tables, and the stack had already been primed with the address to_ground, 1646 ; gfx_init could have just returned instead of jumping to this point, which pops the stack and then 1647 ; falls through to the original destination anyway. _test_ t/decgon-init.txt 1648 ; 1649 0A14 E1 pop_to_ground: pop h ; throw away a return address if DECGON fails 1650 ; 1651 ; This address gets stored on the stack before find_action is jumped to, for escape sequences 1652 0A15 21 E6 05 to_ground: lxi h,print_char 1653 0A18 22 40 21 install_action: shld char_action 1654 0A1B C9 ret 1655 ; 1656 ; VT52 escape sequence action table (0a1c - 0a4f) 1657 ; Each entry is three bytes: character after ESC has been 1658 ; recognised, and then a word of an action routine. 1659 ; JUMP TABLE 1660 vt52esct: ; Cursor movement 1661 0A1C 41 db 'A' 1662 0A1D 9B 0A dw vt52curmove 1663 0A1F 42 db 'B' 1664 0A20 9B 0A dw vt52curmove 1665 0A22 43 db 'C' 1666 0A23 9B 0A dw vt52curmove 1667 0A25 44 db 'D' 1668 0A26 9B 0A dw vt52curmove 1669 1670 ; "Graphics" mode - selects special character set 1671 0A28 46 db 'F' 1672 0A29 0A 0C dw vt52gfxenter 1673 0A2B 47 db 'G' 1674 0A2C 0F 0C dw vt52gfxexit 1675 1676 0A2E 48 db 'H' 1677 0A2F 96 0A dw vt52curhome 1678 1679 0A31 49 db 'I' 1680 0A32 A3 0B dw reverse_index 1681 1682 ; Erase to end of screen 1683 0A34 4A db 'J' 1684 0A35 9B 0A dw vt52curmove 1685 1686 0A37 4B db 'K' 1687 0A38 9B 0A dw vt52curmove 1688 1689 0A3A 59 db 'Y' 1690 0A3B 11 0B dw vt52_det_cup 1691 1692 0A3D 5A db 'Z' 1693 0A3E C6 0C dw identify 1694 1695 0A40 3D db '=' 1696 0A41 DF 0B dw app_keypad 1697 1698 0A43 3E db '<' 1699 0A44 E5 0B dw num_keypad 1700 1701 0A46 31 db '1' ; DECGON - graphics mode 1702 0A47 57 0C dw gfx_init 1703 1704 0A49 3C db '<' 1705 0A4A EB 0B dw ansi_mode 1706 1707 0A4C 5D db ']' 1708 0A4D 60 0D dw print_screen 1709 1710 0A4F 00 db 0 ; end of table 1711 ; 1712 ; Same as above but for ANSI (VT100) mode 1713 ; JUMP TABLE 1714 0A50 63 ansiesct: db 'c' ; RIS - reset to initial state 1715 0A51 00 00 dw start 1716 1717 0A53 45 db 'E' ; NEL - next line 1718 0A54 52 09 dw nextline 1719 1720 0A56 4D db 'M' ; RI - reverse index 1721 0A57 A3 0B dw reverse_index 1722 1723 0A59 31 db '1' ; DECGON - graphics mode 1724 0A5A 57 0C dw gfx_init 1725 1726 0A5C 5B db '[' ; CSI - control sequence introducer 1727 0A5D 8E 0A dw recog_csi 1728 1729 0A5F 48 db 'H' ; Set tab at current column 1730 0A60 DF 0D dw set_tab_here 1731 1732 0A62 44 db 'D' ; IND - index (move down without changing column) 1733 0A63 55 09 dw index_down 1734 1735 0A65 37 db '7' ; DECSC - save cursor and attributes 1736 0A66 68 0C dw save_cursor 1737 1738 0A68 38 db '8' 1739 0A69 71 0C dw restore_cursor ; DECRC - restore cursor and attributes 1740 1741 0A6B 3D db '=' 1742 0A6C DF 0B dw app_keypad 1743 1744 0A6E 3E db '<' 1745 0A6F E5 0B dw num_keypad 1746 1747 0A71 5A db 'Z' 1748 0A72 C6 0C dw identify 1749 1750 0A74 4E db 'N' ; SS2 - mentioned in TM p.D-3, but not UG 1751 0A75 46 0C dw single_shift 1752 1753 0A77 4F db 'O' ; SS3 - mentioned in TM p.D-3 but not UG 1754 0A78 46 0C dw single_shift 1755 1756 0A7A 00 db 0 ; end of table 1757 ; 1758 ; JUMP TABLE 1759 esc_hash_table: ; Triple byte entries again, with final character followed by address of routine 1760 0A7B 33 db '3' ; DECDHL - double height line, top half 1761 0A7C A8 12 dw dhl_top_action 1762 1763 0A7E 34 db '4' ; DECDHL - double height line, bottom half 1764 0A7F AC 12 dw dhl_bot_action 1765 1766 0A81 35 db '5' ; DECSWL - single-width line 1767 0A82 4A 13 dw line_attr_swl 1768 1769 0A84 36 db '6' ; DECDWL - double-width line 1770 0A85 A4 12 dw line_attr_dwl 1771 1772 0A87 37 db '7' ; DECHCP - hard copy 1773 0A88 60 0D dw print_screen 1774 1775 0A8A 38 db '8' ; DECALN - screen alignment display 1776 0A8B A7 0D dw align_pattern 1777 1778 0A8D 00 db 0 ; end of table 1779 ; 1780 ; Received ESC [ (CSI), so set up processing for next character 1781 0A8E 21 C2 16 recog_csi: lxi h,start_csi 1782 0A91 22 40 21 shld char_action 1783 0A94 E1 pop h 1784 0A95 C9 ret 1785 ; 1786 0A96 3E 48 vt52curhome: mvi a,'H' ; setup to pretend this is ANSI sequence 1787 0A98 32 7E 20 sta final_char 1788 0A9B 21 00 00 vt52curmove: lxi h,0 1789 0A9E 22 30 21 shld csi_params 1790 0AA1 E1 pop h 1791 ; 1792 0AA2 21 15 0A execute_seq: lxi h,to_ground ; we are always going to transition to ground state next 1793 0AA5 E5 push h ; pushing so we can just return from this routine 1794 0AA6 3A 7D 20 lda inter_chars ; Did we get any intermediate characters? 1795 0AA9 B7 ora a ; If so, quit early, as no VT100 sequences allow them 1796 0AAA C0 rnz 1797 0AAB 21 D9 0A lxi h,fixed_param_t ; search the table of fixed parameter sequences first 1798 0AAE CD F7 09 call find_action 1799 0AB1 3A B9 21 lda found_action ; and if any of those matched (and just got executed), 1800 0AB4 B7 ora a ; we can stop looking 1801 0AB5 C0 rnz 1802 0AB6 21 30 21 lxi h,csi_params ; HL <- start of parameter list. We'll iterate through this 1803 0AB9 3A 4B 21 lda num_params 1804 0ABC B7 ora a ; if there weren't any parameters, we'll treat the sequence 1805 0ABD 5F mov e,a ; as if there was a single, defaulted, parameter 1806 0ABE C2 C2 0A jnz loop_params 1807 0AC1 1C inr e ; pretend we have one parameter 1808 0AC2 7E loop_params: mov a,m ; retrieve this parameter 1809 0AC3 E5 push h ; preserve param list pointer 1810 0AC4 D5 push d ; preserve param count 1811 0AC5 21 F5 0A lxi h,sel_param_t ; search the selective parameters table 1812 0AC8 47 mov b,a ; B <- selective parameter for action routine 1813 0AC9 CD F7 09 call find_action 1814 0ACC D1 pop d ; restore param count 1815 0ACD E1 pop h ; and list 1816 0ACE 3A B9 21 lda found_action ; If we couldn't find an action at all, no point iterating ... 1817 0AD1 B7 ora a 1818 0AD2 C8 rz ; ... so quit early 1819 0AD3 23 inx h ; point to next parameter 1820 0AD4 1D dcr e 1821 0AD5 C2 C2 0A jnz loop_params ; and go round again 1822 0AD8 C9 ret 1823 ; 1824 ; Table of sequences that have a fixed number of parameters, whether defaulted or not. 1825 ; All other sequences have "Ps" selective parameters, in the next table. 1826 ; JUMP TABLE 1827 0AD9 44 fixed_param_t: db 'D' ; CUB - cursor left n columns 1828 0ADA 1E 18 dw cub_action 1829 0ADC 42 db 'B' ; CUD - cursor down n rows 1830 0ADD 07 18 dw cud_action 1831 0ADF 43 db 'C' ; CUF - cursor forward n columns 1832 0AE0 15 18 dw cuf_action 1833 0AE2 48 db 'H' ; CUP - cursor position 1834 0AE3 53 18 dw curpos_action 1835 0AE5 41 db 'A' ; CUU - cursor up n rows 1836 0AE6 FE 17 dw cuu_action 1837 0AE8 72 db 'r' ; DECSTBM - set top and bottom margins 1838 0AE9 A6 15 dw stbm_action 1839 0AEB 66 db 'f' ; HVP - horizontal and vertical position 1840 0AEC 53 18 dw curpos_action ; (same routine as CUP) 1841 0AEE 78 db 'x' ; DECREQTPARM - request terminal parameters 1842 0AEF DF 12 dw tparm_action 1843 0AF1 79 db 'y' ; DECTST - invoke confidence test 1844 0AF2 64 08 dw tst_action 1845 0AF4 00 db 0 ; end of table 1846 1847 ; Table of sequences with selective parameters. There action routines will be called 1848 ; multiple times, with a single parameter each time. This way, conflicts between parameters 1849 ; get resolved naturally, by having later parameters override earlier ones. 1850 ; JUMP TABLE 1851 0AF5 63 sel_param_t: db 'c' ; DA - device attributes 1852 0AF6 C1 0C dw da_action 1853 0AF8 71 db 'q' ; DECLL - load LEDs 1854 0AF9 16 0C dw decll_action 1855 0AFB 6E db 'n' ; DSR - device status report 1856 0AFC 03 0D dw dsr_action 1857 0AFE 4A db 'J' ; ED - erase in display 1858 0AFF 41 15 dw ed_action 1859 0B01 4B db 'K' ; EL - erase in line 1860 0B02 FC 14 dw el_action 1861 0B04 6C db 'l' ; RM - reset mode 1862 0B05 ED 13 dw rm_action 1863 0B07 6D db 'm' ; SGR - select graphic rendition 1864 0B08 7F 0C dw sgr_action 1865 0B0A 68 db 'h' ; SM - set mode 1866 0B0B F2 13 dw sm_action 1867 0B0D 67 db 'g' ; TBC - tabulation clear 1868 0B0E CF 0D dw tbc_action 1869 0B10 00 db 0 ; end of table 1870 ; 1871 ; In VT52 mode, recognised ESC Y, which now needs two more characters, row and column. 1872 ; 1873 0B11 21 19 0B vt52_det_cup: lxi h,vt52_get_coord 1874 0B14 22 40 21 shld char_action 1875 0B17 E1 pop h 1876 0B18 C9 ret 1877 ; 1878 ; vt52_get_coord 1879 ; Two characters after ESC Y should be row and column. This state gathers them both, using a flag 1880 ; to record whether we've seen the row already. 1881 ; 1882 ; NOTE: This routine shows that the VT100 will execute C0 controls when coordinates are expected 1883 ; for this sequence, making ESC Y BEL SP SP identical in effect to ESC Y SP SP BEL. 1884 ; Would be interesting to check this against later terminals' behaviour and VSRM. 1885 ; 1886 ; There is a "sub-state" flag here, called got_vt52_row. It is not initialised when ESC Y is 1887 ; recognised and is only cleared when a column has been received, which means that not only will 1888 ; single-character C0 controls be executed in the middle of a sequence, but that a second ESC Y 1889 ; received after the row character will not stop the very next printable character being 1890 ; interpreted as a column coordinate and terminating the sequence. 1891 ; 1892 ; For example: 1893 ; a) ESC Y ESC Y 0 0 executes as ESC Y 0 0 1894 ; b) ESC Y 0 ESC Y 0 also executes as ESC Y 0 0 1895 ; 1896 0B19 47 vt52_get_coord: mov b,a 1897 0B1A FE 20 cpi 20h ; Do we have a C0 control? Pointless test as these are processed 1898 0B1C DA B2 08 jc exec_c0 ; before other characters are passed to action routines, like this. 1899 0B1F FE 1B cpi C0_ESC ; Similarly, ESC cannot reach here. 1900 0B21 21 51 21 lxi h,got_vt52_row ; HL <- flag location, kind of sub-state 1901 0B24 C2 2E 0B jnz store_coord ; this character wasn't ESC, so treat as coordinate 1902 0B27 21 AB 09 lxi h,recog_esc ; UNREACHABLE 1903 0B2A 22 40 21 shld char_action ; UNREACHABLE 1904 0B2D C9 ret ; UNREACHABLE 1905 ; 1906 0B2E 7E store_coord: mov a,m ; Check whether we've had a coord already 1907 0B2F B7 ora a 1908 0B30 C2 42 0B jnz vt52_move ; if we have, perform the move 1909 0B33 36 01 mvi m,1 ; got_vt52_row <- 1 "had a row, now need column" 1910 0B35 78 mov a,b ; grab raw character 1911 0B36 D6 20 sui 20h ; turn it into a zero-based row number 1912 0B38 32 52 21 sta vt52_row_coord ; keep it for later 1913 0B3B 21 19 0B lxi h,vt52_get_coord 1914 0B3E 22 40 21 shld char_action ; keep the same action, really 1915 0B41 C9 ret 1916 ; 1917 0B42 36 00 vt52_move: mvi m,0 ; got_vt52_row <- 0 -- clear the "had a row" flag 1918 0B44 78 mov a,b ; grab raw character 1919 0B45 D6 20 sui 20h ; turn it into a zero-based column number 1920 0B47 FE 50 cpi 50h ; which can only take us up to column 80! 1921 0B49 D2 4F 0B jnc skip_col_upd ; Could end up only moving the cursor row, if the column is invalid 1922 0B4C 32 F8 20 sta curs_col ; in range, so update 1923 0B4F CD BF 11 skip_col_upd: call last_row ; B <- last row number 1924 0B52 3A 52 21 lda vt52_row_coord 1925 0B55 04 inr b 1926 0B56 B8 cmp b 1927 0B57 D2 5D 0B jnc skip_row_upd 1928 0B5A 32 F9 20 sta curs_row 1929 0B5D CD 15 0A skip_row_upd: call to_ground 1930 0B60 C3 36 16 jmp move_updates 1931 ; 1932 ; init_80col 1933 ; Create an 80 column display, which involves clearing the 132-column flag and setting up the 1934 ; screen RAM with terminators at the righ places. Margins, cursor position and DC011 updates 1935 ; are shared with the 132-column initialisation, below. 1936 ; 1937 0B63 AF init_80col: xra a 1938 0B64 32 A2 21 sta columns_132 1939 0B67 CD AD 10 call init_screen 1940 0B6A 0E 50 mvi c,80 1941 0B6C CD F9 10 call make_screen 1942 0B6F CD 4C 11 call make_line_t 1943 0B72 0E 50 mvi c,80 1944 0B74 C3 89 0B jmp col_common 1945 ; 1946 ; init_132col 1947 ; Create a 132 column display, in the same manner as described above. 1948 ; 1949 0B77 3E 01 init_132col: mvi a,1 1950 0B79 32 A2 21 sta columns_132 1951 0B7C CD AD 10 call init_screen 1952 0B7F 0E 84 mvi c,132 1953 0B81 CD F9 10 call make_screen 1954 0B84 CD 4C 11 call make_line_t 1955 0B87 0E 84 mvi c,132 1956 0B89 AF col_common: xra a 1957 0B8A 32 55 21 sta top_margin ; Reset top margin again (make_line_t just did this) 1958 0B8D CD BF 11 call last_row ; B <- last row number 1959 0B90 78 mov a,b 1960 0B91 32 56 21 sta bottom_margin ; Deja margin 1961 0B94 79 mov a,c ; A <- screen columns 1962 0B95 32 50 20 sta screen_cols 1963 0B98 CD 42 03 call update_dc011 1964 0B9B CD 48 18 call cursor_home 1965 0B9E 3E 01 mvi a,1 1966 0BA0 C3 AA 0F jmp wait_n_frames 1967 ; 1968 0BA3 21 F9 20 reverse_index: lxi h,curs_row ; Move cursor up one row, in the same column 1969 0BA6 3A 55 21 lda top_margin 1970 0BA9 47 mov b,a ; B <- top margin 1971 0BAA 7E mov a,m ; A <- cursor row 1972 0BAB B8 cmp b 1973 0BAC CA B8 0B jz at_margin_t ; Need to scroll up if we are at the top margin 1974 0BAF B7 ora a 1975 0BB0 C8 rz 1976 0BB1 21 F9 20 lxi h,curs_row ; Just move the cursor up one row 1977 0BB4 35 dcr m 1978 0BB5 C3 36 16 jmp move_updates 1979 ; 1980 0BB8 3A A6 21 at_margin_t: lda setup_b1 1981 0BBB E6 80 ani sb1_smooth 1982 0BBD CA D9 0B jz jump_scroll_dn 1983 0BC0 0E 01 mvi c,1 1984 0BC2 CD 8E 10 call wait_scroll 1985 0BC5 3E FF mvi a,0ffh 1986 0BC7 32 C3 21 sta row_clearing 1987 0BCA 35 dcr m ; scroll_pending <- -1 1988 0BCB CD EB 0F call move_lines_dn 1989 0BCE 3A C3 21 wait_clear2: lda row_clearing 1990 0BD1 B7 ora a 1991 0BD2 C2 CE 0B jnz wait_clear2 1992 0BD5 32 F4 20 sta char_und_curs 1993 0BD8 C9 ret 1994 ; 1995 0BD9 CD 2D 10 jump_scroll_dn: call start_jump_dn 1996 0BDC C3 12 10 jmp inv_saved_row 1997 ; 1998 0BDF 21 78 21 app_keypad: lxi h,keypad_mode 1999 0BE2 36 01 mvi m,1 2000 0BE4 C9 ret 2001 ; 2002 0BE5 21 78 21 num_keypad: lxi h,keypad_mode 2003 0BE8 36 00 mvi m,0 2004 0BEA C9 ret 2005 ; 2006 0BEB 21 A7 21 ansi_mode: lxi h,setup_b2 2007 0BEE 7E mov a,m 2008 0BEF F6 20 ori sb2_ansi 2009 0BF1 77 mov m,a 2010 ; 2011 0BF2 3A A8 21 set_charsets: lda setup_b3 2012 0BF5 E6 80 ani sb3_uk 2013 0BF7 26 08 mvi h,8 ; H <- ASCII Set 2014 0BF9 CA FE 0B jz not_uk 2015 0BFC 26 48 mvi h,48h ; H <- United Kingdom Set 2016 0BFE 6C not_uk: mov l,h ; L <- same set as H 2017 0BFF 22 FD 20 shld g0_charset ; Place in all four designators 2018 0C02 22 FF 20 shld g2_charset 2019 0C05 AF xra a 2020 0C06 32 FC 20 sta gl_invocation ; Invoke G0 into GL 2021 0C09 C9 ret 2022 ; 2023 0C0A 26 88 vt52gfxenter: mvi h,88h ; H <- Special Graphics Character Set 2024 0C0C C3 11 0C jmp vt52gfx1 2025 ; 2026 0C0F 26 08 vt52gfxexit: mvi h,8 ; H <- ASCII Set 2027 0C11 6C vt52gfx1: mov l,h ; L <- same set as h 2028 0C12 22 FD 20 shld g0_charset ; Map G0 and G1 in a single operation 2029 0C15 C9 ret 2030 ; 2031 ; decll_action 2032 ; 2033 ; DECLL - ESC [ Ps q [* BUG *] 2034 ; 2035 ; Single Ps parameter: 2036 ; 0 - clears all LEDs 2037 ; 1 - lights L1 2038 ; 2 - lights L2 2039 ; 3 - lights L3 2040 ; 4 - lights L4 2041 ; 2042 ; This routine either masks four lowest bits to zero for Ps = 0, 2043 ; on constructs a single-bit-on mask that it can OR with the existing 2044 ; LED state value. No writing of the LED state to the keyboard is done 2045 ; here, so presumably that's a periodic thing elsewhere. 2046 ; 2047 ; However, this routine has a bug. It checks for a zero parameter, and then 2048 ; subtracts 5 from any non-zero parameter, bombs out for a positive result, 2049 ; and then attempts to make a LED mask with the result (Ps - 5). However, 2050 ; values of Ps between 133 and 255 will still be negative when 5 is subtracted, 2051 ; meaning that some of them will make masks to affect bits 4 - 7, and these will 2052 ; be sent to the keyboard without further censoring. The keyboard byte itself 2053 ; is described in TM Figure 4-4-2 (see iow_keyboard) 2054 ; 2055 ; This bug accounts for the strange behaviour documented by John 'Sloppy' Millington 2056 ; in his "VT100 Oddities" document: https://vt100.net/dec/vt100/oddities 2057 ; 2058 ; To find out which bit will be set by this routine, calculate (260 - Ps) % 9. 2059 ; Result Effect 2060 ; 0 - 3 lights one of the LEDs L1 to L4 in the normal fashion 2061 ; 4 lights the keyboard locked LED such that it cannot be unlit 2062 ; 5 lights the online/local LED such that it cannot be unlit 2063 ; 6 sets "start scan", which will result in a mad repeat rate 2064 ; 7 sets "speaker click", which will make the terminal beep continuously 2065 ; 8 leaves a 1 in the carry flags, so it is completely harmless 2066 ; 2067 0C16 21 45 21 decll_action: lxi h,led_state 2068 0C19 78 mov a,b ; On entry to action routines, B is always the selection value 2069 0C1A B7 ora a ; zero is special case (all off) 2070 0C1B C2 23 0C jnz make_led_mask 2071 0C1E 7E mov a,m ; A <- current LED state 2072 0C1F E6 F0 ani 0f0h ; mask all LEDs to zero 2073 0C21 77 mov m,a ; place back 2074 0C22 C9 ret 2075 ; 2076 0C23 D6 05 make_led_mask: sui 5 2077 0C25 F0 rp ; maximum allowed parameter value is 4 2078 0C26 47 mov b,a ; Because LEDs register are reversed in order from the number of 2079 0C27 AF xra a ; the selection parameter, b will count up to zero to find bit 2080 0C28 37 stc ; start with a "1" bit for our mask 2081 0C29 17 rot_m: ral ; shift mask bit left through register 2082 0C2A 04 inr b 2083 0C2B C2 29 0C jnz rot_m 2084 0C2E B6 ora m ; A <- new mask 2085 0C2F 77 mov m,a ; led_state <- old value | new mask 2086 0C30 C9 ret 2087 ; 2088 ; SCS - select character set 2089 ; On entry, de is 20fdh (G0), and the earlier entry point, scs_g1, increments 2090 ; it so that G1 is affected. 2091 ; 2092 0C31 13 scs_g1: inx d 2093 0C32 3A 7E 20 scs_g0: lda final_char 2094 0C35 47 mov b,a ; B <- charset name 2095 0C36 21 4A 0C lxi h,charset_list-2 2096 0C39 23 nxtmap: inx h 2097 0C3A 23 inx h 2098 0C3B 7E mov a,m 2099 0C3C B7 ora a 2100 0C3D C8 rz ; end of table without finding a match? 2101 0C3E B8 cmp b ; is this the charset we're looking for? 2102 0C3F C2 39 0C jnz nxtmap 2103 0C42 23 inx h ; yes, so grab internal charset number 2104 0C43 7E mov a,m 2105 0C44 12 stax d ; and store it in either G0 or G1 2106 0C45 C9 ret 2107 ; 2108 ; single_shift 2109 ; This routine is used to implement an action for SS2 and SS3, neither of which are 2110 ; documented as being supported in the VT100 User Guide, though they are mentioned 2111 ; the Technical Manual as being supported by the "VT100 Family." 2112 ; 2113 ; SS2 (ESC N) is supposed to invoke G2 into GL for a single printable character, 2114 ; after which the terminal reverts to the previous invocation, G0 or G1. SS3 (ESC O) 2115 ; is supposed to do the same with G3. 2116 ; 2117 ; What they actually do on the VT100 is to act identically, in increasing the current 2118 ; invocation by two sets, so that they will both invoke G2 into GL if G0 was previously 2119 ; there, or invoke G3 into GL if SHIFT OUT was previously in effect. After a single 2120 ; displayed character, the invocation drops by two sets, leaving the terminal as before. 2121 ; 2122 ; This behaviour is cheaper to implement than the correct one, and doesn't make any 2123 ; difference because both G2 and G3 have the default character set (ASCII or UK) 2124 ; designated at start-up, and there is no way to designate anything else. 2125 ; 2126 ; In fact, the only way you can tell the difference between the VT100 ignoring these 2127 ; controls completely and implementing them is by designating the Special Graphics Set 2128 ; into G0, invoking it to GL and then using SS2 and sending a character that would be 2129 ; different between the two sets. For example: 2130 ; 2131 ; ESC ( 0 a ESC N a a 2132 ; 2133 ; On a terminal that didn't support this sequence at all, the above sequence would 2134 ; display three lowercase "a" characters. On the VT100, this will display a checkerboard, 2135 ; a lowercase "a", and another checkerboard. 2136 ; 2137 0C46 21 FC 20 single_shift: lxi h,gl_invocation 2138 0C49 34 inr m 2139 0C4A 34 inr m 2140 0C4B C9 ret 2141 ; 2142 0C4C 41 48 charset_list: db 'A',48h ; United Kingdom Set 2143 0C4E 42 08 db 'B',08h ; ASCII Set 2144 0C50 30 88 db '0',88h ; Special Graphics 2145 0C52 31 00 db '1',0 ; Alternate Character ROM Standard Character Set 2146 0C54 32 80 db '2',80h ; Alternate Character ROM Special Graphics 2147 0C56 00 db 0 ; end of table 2148 2149 0C57 21 79 20 gfx_init: lxi h,gpo_flags 2150 0C5A 7E mov a,m 2151 0C5B B7 ora a 2152 0C5C CA 14 0A jz pop_to_ground 2153 0C5F 36 81 mvi m,81h 2154 0C61 E1 pop h 2155 0C62 21 AD 05 gfx_set_state: lxi h,gfx_det_esc 2156 0C65 C3 18 0A jmp install_action 2157 ; 2158 0C68 21 02 21 save_cursor: lxi h,gfx_saved 2159 0C6B 11 F8 20 lxi d,gfx_state 2160 0C6E C3 77 0C jmp copy_state 2161 ; 2162 0C71 21 F8 20 restore_cursor: lxi h,gfx_state 2163 0C74 11 02 21 lxi d,gfx_saved 2164 0C77 06 0B copy_state: mvi b,0bh 2165 0C79 CD 8B 03 call memcopy 2166 0C7C C3 36 16 jmp move_updates 2167 ; 2168 ; SGR - select graphics rendition 2169 0C7F 21 8A 0C sgr_action: lxi h,sgr_ps_table ; we're looking for rendition value in this table 2170 0C82 78 mov a,b ; this is the selective parameter we're finding 2171 0C83 B7 ora a 2172 0C84 CA 97 0C jz sgr_off ; zero (all attributes off) is special 2173 0C87 C3 FF 09 jmp find_action_a ; employing tail recursion to take us back to execute_seq 2174 ; 2175 ; JUMP TABLE 2176 0C8A 01 sgr_ps_table: db 1 ; bold 2177 0C8B A2 0C dw sgr_bold 2178 0C8D 04 db 4 ; underscore 2179 0C8E A7 0C dw sgr_underscore 2180 0C90 05 db 5 ; blink 2181 0C91 B3 0C dw sgr_blink 2182 0C93 07 db 7 ; negative (reverse) image 2183 0C94 BB 0C dw sgr_reverse 2184 0C96 00 db 0 ; end of table 2185 2186 0C97 32 FB 20 sgr_off: sta char_rvid ; A is zero on entry, so clear reverse video 2187 0C9A 21 FA 20 lxi h,char_rend 2188 0C9D 7E mov a,m 2189 0C9E F6 F7 ori 0f7h ; and set all the normal rendition bits 2190 0CA0 77 mov m,a 2191 0CA1 C9 ret 2192 ; 2193 0CA2 3E FB sgr_bold: mvi a,rend_bold 2194 0CA4 C3 B5 0C jmp sgr_mask 2195 ; 2196 ; If we don't have AVO, then we are limited to the "base attribute", bit 7 of each character in 2197 ; screen RAM, which will render as underscore if the cursor is underline, or reverse video if the 2198 ; cursor is a block. 2199 ; 2200 0CA7 3A C8 21 sgr_underscore: lda avo_missing 2201 0CAA B7 ora a 2202 0CAB C2 BB 0C jnz set_base_attr 2203 0CAE 3E FD mvi a,rend_under 2204 0CB0 C3 B5 0C jmp sgr_mask 2205 ; 2206 0CB3 3E FE sgr_blink: mvi a,rend_blink 2207 0CB5 21 FA 20 sgr_mask: lxi h,char_rend 2208 0CB8 A6 ana m 2209 0CB9 77 mov m,a 2210 0CBA C9 ret 2211 ; 2212 ; Reverse attribute is available without AVO, if the cursor is a block. All the same, in that case, 2213 ; it is the only attribute available, and sgr_underscore will also set the base attribute. 2214 ; 2215 ; If you have AVO, the reverse is one of four attributes available. 2216 ; 2217 ; (Two names for the same entry point, just to clarify the above, and why sgr_underscore is jumping here.) 2218 ; 2219 sgr_reverse: 2220 0CBB 3E 80 set_base_attr: mvi a,80h 2221 0CBD 32 FB 20 sta char_rvid 2222 0CC0 C9 ret 2223 2224 ; ANSI entry point - ESC [ c 2225 0CC1 3A 30 21 da_action: lda csi_p1 2226 0CC4 B7 ora a ; This sequence only allows no parameter or a parameter of 0 2227 0CC5 C0 rnz 2228 ; ANSI/VT52 entry point - ESC Z 2229 0CC6 21 72 21 identify: lxi h,pending_report 2230 0CC9 7E mov a,m 2231 0CCA F6 04 ori pend_ident 2232 0CCC 77 mov m,a 2233 0CCD C9 ret 2234 ; 2235 0CCE 3E FB ident_report: mvi a,~pend_ident 2236 0CD0 CD BF 0D call prepare_report 2237 0CD3 3A A7 21 lda setup_b2 2238 0CD6 E6 20 ani sb2_ansi ; Are we in ANSI mode? 2239 0CD8 C2 E3 0C jnz ansi_identity 2240 ; now producing VT52 report, which is just ESC / Z. As we're at the character 2241 ; after the prepared template of "ESC [", we'll write the terminating 'Z' first 2242 ; (with bit 7 set), and then move backwards to overwrite the '[' with '/'. 2243 0CDB 36 DA mvi m,'Z'|80h ; in reverse order, ladies and gentlemen 2244 0CDD 2B dcx h 2245 0CDE 36 2F mvi m,'/' 2246 0CE0 C3 F3 0E jmp send_report 2247 ; 2248 ; Going to produce the sequence ESC [ ? 1 ; <n< c 2249 ; where <n< is produced from the installed options. 2250 0CE3 36 3F ansi_identity: mvi m,'?' ; add the fixed part of response 2251 0CE5 23 inx h ; to the buffer 2252 0CE6 3E 31 mvi a,'1' 2253 0CE8 CD B9 0D call rep_char_semi 2254 0CEB DB 42 in ior_flags 2255 0CED 47 mov b,a ; B <- flags byte 2256 0CEE 2F cma ; complement because the AVO and GPO flags 2257 0CEF E6 06 ani 6 ; are "missing" flags, not "present" flags 2258 0CF1 4F mov c,a ; C <- AVO and GPO presence flags 2259 0CF2 78 mov a,b ; A <- flags byte 2260 0CF3 E6 08 ani iob_flags_stp ; check STP 2261 0CF5 CA F9 0C jz stp_not_found ; not present, skip 2262 0CF8 0C inr c ; STP adds 1 to the result 2263 0CF9 79 stp_not_found: mov a,c ; A <- final answer 2264 0CFA F6 30 ori '0' ; ASCII-ify this digit 2265 0CFC 77 mov m,a ; into buffer 2266 0CFD 23 inx h 2267 0CFE 36 E3 mvi m,'c'|80h ; terminate with 'c' + high bit 2268 0D00 C3 F3 0E jmp send_report 2269 ; 2270 0D03 3A B8 21 dsr_action: lda csi_private ; This sequence doesn't accept any private characters 2271 0D06 B7 ora a 2272 0D07 C0 rnz ; so quit early if supplied 2273 0D08 78 mov a,b ; B is the selection parameter 2274 0D09 21 72 21 lxi h,pending_report 2275 0D0C FE 06 cpi 6 ; is it "please report active position"? 2276 0D0E CA 32 0D jz get_active_pos 2277 0D11 FE 05 cpi 5 ; is it "please report status"? 2278 0D13 C0 rnz ; if not, quit 2279 0D14 7E mov a,m 2280 0D15 F6 08 ori pend_devstat 2281 0D17 77 mov m,a 2282 0D18 C9 ret 2283 ; 2284 0D19 3E F7 devstat_report: mvi a,~pend_devstat 2285 0D1B CD BF 0D call prepare_report 2286 0D1E 06 03 mvi b,3 ; Assume the worst, which is a report of '3' 2287 0D20 3A BD 21 lda test_results 2288 0D23 B7 ora a 2289 0D24 78 mov a,b 2290 0D25 C2 29 0D jnz devr2 ; If any tests failed, we were right 2291 0D28 AF xra a ; Didn't find any errors, so report '0' 2292 0D29 F6 30 devr2: ori '0' ; Convert our value to ASCII digiit 2293 0D2B 77 mov m,a ; add it to the report buffer 2294 0D2C 23 inx h 2295 0D2D 36 EE mvi m,'n'|80h ; 'n' makes this a DSR sequence 2296 0D2F C3 F3 0E jmp send_report 2297 ; 2298 0D32 7E get_active_pos: mov a,m 2299 0D33 F6 01 ori pend_curpos ; Received DSR triggers a cursor position report 2300 0D35 77 mov m,a 2301 0D36 C9 ret 2302 ; 2303 0D37 3E FE curpos_report: mvi a,~pend_curpos 2304 0D39 CD BF 0D call prepare_report 2305 0D3C 3A F9 20 lda curs_row 2306 0D3F 47 mov b,a 2307 0D40 3A 01 21 lda origin_mode 2308 0D43 B7 ora a 2309 0D44 CA 4A 0D jz skip_tm ; Not in origin mode, so don't need to add margin 2310 0D47 3A 55 21 lda top_margin ; In origin mode, so get cursor row relative to start of area 2311 0D4A 4F skip_tm: mov c,a ; C <- margin (or 0) 2312 0D4B 78 mov a,b ; A <- cursor row (absolute) 2313 0D4C 91 sub c ; A <- cursor row (relative to top of scrolling area) 2314 0D4D 3C inr a ; Reports call the top row 1, not 0, to match CUP/HVP 2315 0D4E CD 7A 0D call ascii_decimal 2316 0D51 CD BB 0D call report_semi 2317 0D54 3A F8 20 lda curs_col 2318 0D57 3C inr a ; Left column is 1, to match CUP/HVP 2319 0D58 CD 7A 0D call ascii_decimal 2320 0D5B 36 D2 mvi m,'R'|80h ; 'R' makes this a CPR sequence 2321 0D5D C3 F3 0E jmp send_report 2322 ; 2323 ; print_screen 2324 ; 2325 ; Triggered in VT52 mode by ESC ], and in VT100 mode by ESC # 7, though that is not mentioned in the 2326 ; User Guide. All of the work is done by the GPO, which is explained in the block diagrams in the TM. 2327 ; The VT100 main terminal board just commands the print and waits for the GPO to become ready again. 2328 ; 2329 0D60 DB 42 print_screen: in ior_flags 2330 0D62 E6 04 ani iob_flags_gpo 2331 0D64 C0 rnz ; Exit if we don't have the graphics processor option 2332 0D65 0E 81 mvi c,81h 2333 0D67 CD 8E 10 call wait_scroll 2334 0D6A 3E FF mvi a,0ffh 2335 0D6C D3 E2 out iow_graphics 2336 0D6E 00 nop 2337 0D6F CD 88 14 wait_gpo_rdy2: call keyboard_tick 2338 0D72 DB 42 in ior_flags 2339 0D74 E6 04 ani iob_flags_gpo 2340 0D76 C2 6F 0D jnz wait_gpo_rdy2 2341 0D79 C9 ret 2342 ; 2343 ; ascii_decimal 2344 ; Convert the unsigned number in A to an ASCII decimal string, loading it 2345 ; into the buffer pointed to by HL. 2346 ; 2347 0D7A 5F ascii_decimal: mov e,a 2348 0D7B 16 30 mvi d,'0' ; digit to suppress 2349 0D7D 0E 64 mvi c,100 ; try hundreds first 2350 0D7F CD 99 0D call ascii_digit ; produce an ASCII hundreds digit 2351 0D82 CA 88 0D jz tens ; suppress leading zero 2352 0D85 77 mov m,a ; store hundreds digit 2353 0D86 23 inx h ; next place in buffer 2354 0D87 15 dcr d ; if we've had a hundreds digit, then can't suppress tens! 2355 0D88 0E 0A tens: mvi c,10 ; now try tens 2356 0D8A CD 99 0D call ascii_digit ; produce an ASCII tens digit 2357 0D8D CA 93 0D jz units ; suppress leading zero (if possible!) 2358 0D90 15 dcr d ; again, can't suppress next digit (but we don't try) 2359 0D91 77 mov m,a ; store tens digit 2360 0D92 23 inx h 2361 0D93 7B units: mov a,e ; get the remaining single-digit number 2362 0D94 F6 30 ori '0' ; And make it ASCII 2363 0D96 77 mov m,a ; add to buffer 2364 0D97 23 inx h ; and increment buffer pointer 2365 0D98 C9 ret 2366 ; 2367 ; ascii_digit 2368 ; Convert value in E to an ASCII digit for the decimal places given by C, which will probably 2369 ; start at 100 and be reduced to 10 on the next call. Returns ASCII digit in A and will 2370 ; return with Z flag set if that digit matches D (which is set to '0' before entry). The Z 2371 ; flag enables skipping of leading zeroes on reported numbers. Look at routine above for the 2372 ; cute trick to not suppress tens digits if we've produced a hundreds digit already. 2373 ; 2374 0D99 7B ascii_digit: mov a,e 2375 0D9A 06 30 mvi b,'0' ; lowest digit we could return 2376 0D9C 04 repeat_sub: inr b ; assume subtraction will succeed 2377 0D9D 91 sub c ; before attempting (because of flag states) 2378 0D9E F2 9C 0D jp repeat_sub ; and if it worked (no overflow), keep going 2379 0DA1 81 add c ; last subtraction failed, so restore original minuend 2380 0DA2 05 dcr b ; and reduce the ASCII digit too 2381 0DA3 5F mov e,a ; E <- remaining number 2382 0DA4 78 mov a,b ; A <- ASCII digit for this place 2383 0DA5 BA cmp d ; Is it zero? (for leading digit suppression) 2384 0DA6 C9 ret 2385 ; 2386 0DA7 3E 45 align_pattern: mvi a,'E' 2387 0DA9 32 C7 21 sta cls_char 2388 0DAC CD 81 03 call clear_display 2389 0DAF 3E 45 mvi a,'E' 2390 0DB1 32 F4 20 sta char_und_curs 2391 0DB4 AF xra a 2392 0DB5 32 C7 21 sta cls_char ; Back to normal clear screen 2393 0DB8 C9 ret 2394 ; 2395 ; rep_char_semi 2396 ; Add the character in A to the buffer pointed to by HL, and increment 2397 ; pointer. Follow it up with a semi-colon. 2398 0DB9 77 rep_char_semi: mov m,a 2399 0DBA 23 inx h 2400 0DBB 36 3B report_semi: mvi m,3bh ; Semi-colon, which asm8080 will not allow in quotes, boo. 2401 0DBD 23 inx h 2402 0DBE C9 ret 2403 ; 2404 ; prepare_report 2405 ; Setup a scratch buffer at 215ch with the start of an control sequence, 2406 ; ESC [, ready to be appended to, and return the address of the next 2407 ; character in HL. On entry, A contains the inverse bit mask of the report 2408 ; that we are preparing, so it can be removed from the pending pile. 2409 ; 2410 0DBF 21 72 21 prepare_report: lxi h,pending_report 2411 0DC2 A6 ana m 2412 0DC3 77 mov m,a ; this report won't be processed again 2413 0DC4 21 5C 21 lxi h,report_buffer 2414 0DC7 36 1B mvi m,C0_ESC 2415 0DC9 23 inx h 2416 0DCA 36 5B mvi m,'[' 2417 0DCC 23 inx h 2418 0DCD C9 ret 2419 ; 2420 0DCE 42 db 42h ; CHECKSUM 2421 ; TBC - tabulation clear 2422 0DCF 78 tbc_action: mov a,b ; A <- which tabs to clear (0 = here, 3 = all) 2423 0DD0 B7 ora a 2424 0DD1 C2 D8 0D jnz check_tbc_opt 2425 0DD4 CD E5 0D call clear_this_tab 2426 0DD7 C9 ret 2427 ; 2428 0DD8 FE 03 check_tbc_opt: cpi 3 ; If the parameter is not zero or defaulted, 2429 0DDA C0 rnz ; it must be 3. 2430 0DDB CD EC 0D call clear_all_tabs 2431 0DDE C9 ret 2432 ; 2433 0DDF CD 23 0E set_tab_here: call tab_offs_curs 2434 0DE2 B6 ora m ; set the appropriate tab bit 2435 0DE3 77 mov m,a 2436 0DE4 C9 ret 2437 ; 2438 0DE5 CD 23 0E clear_this_tab: call tab_offs_curs 2439 0DE8 2F cma ; complement the tab bit mask because we're clearing 2440 0DE9 A6 ana m 2441 0DEA 77 mov m,a 2442 0DEB C9 ret 2443 ; 2444 ; clear_all_tabs 2445 ; 2446 ; Zeroes the entire 17-byte tab_settings area. Can be invoked either from SET-UP or by control sequence. 2447 ; 2448 0DEC 21 91 21 clear_all_tabs: lxi h,tab_settings 2449 0DEF AF clrnxt: xra a 2450 0DF0 77 mov m,a 2451 0DF1 23 inx h 2452 0DF2 7D mov a,l 2453 0DF3 FE A2 cpi LOW tab_settings + tablen 2454 0DF5 C2 EF 0D jnz clrnxt 2455 0DF8 C9 ret 2456 ; 2457 ; c0_horiz_tab 2458 ; Move the cursor to the next horizontal tab. Movement off the end of the tab array is checked for, 2459 ; column by column, but movement beyond the 80th column will be caught by a margin check at the end. 2460 ; 2461 0DF9 CD 23 0E c0_horiz_tab: call tab_offs_curs 2462 0DFC 0C next_tab_col: inr c ; increment cursor column 2463 0DFD B7 ora a ; clear carry 2464 0DFE 1F rar ; rotate tab mask, so carry will mean next column's tab mask will 2465 0DFF DA 0B 0E jc next_tab_loc ; be in next location 2466 0E02 57 try_tab_loc: mov d,a ; D <- tab mask 2467 0E03 A6 ana m ; Is there a tab set here? 2468 0E04 7A mov a,d ; A <- tab mask 2469 0E05 CA FC 0D jz next_tab_col ; no, try next column 2470 0E08 C3 15 0E jmp chk_rmargin ; yes tab, check we don't stray beyond right margin before setting 2471 ; 2472 0E0B 23 next_tab_loc: inx h ; tab mask for next column is in next tab setting entry 2473 0E0C 7D mov a,l 2474 0E0D FE A2 cpi LOW tab_settings + tablen ; have we gone off end of array? 2475 0E0F 3E 80 mvi a,80h ; set up a fresh tab mask for new location 2476 0E11 C2 02 0E jnz try_tab_loc ; not off end, try again 2477 0E14 0D dcr c ; off end, so column number is before our prospective increment, above 2478 0E15 3A 53 21 chk_rmargin: lda right_margin 2479 0E18 B9 cmp c 2480 0E19 DA 1D 0E jc stop_at_margin 2481 0E1C 79 mov a,c ; We found a tab, not beyond right margin, all good 2482 0E1D 32 F8 20 stop_at_margin: sta curs_col 2483 0E20 C3 36 16 jmp move_updates 2484 ; 2485 ; tab_offs_curs 2486 ; Grab the cursor column and return HL with a pointer to correct location in tab settings, 2487 ; and A with a single-bit mask that addresses that location, so we can determine whether 2488 ; there is a tab set (or use the mask to change it). 2489 ; Returns cursor column in C (as a bonus for the single caller that needs it.) 2490 ; 2491 0E23 3A F8 20 tab_offs_curs: lda curs_col 2492 0E26 4F mov c,a 2493 ; tab_offs 2494 ; Alternate entry point used for SET-UP A, where we are iterating over every column in 2495 ; order to determine whether to print a 'T' or not. Register A contains column number. 2496 ; 2497 0E27 57 tab_offs: mov d,a ; D <- cursor column, while we destroy A 2498 0E28 21 91 21 lxi h,tab_settings 2499 ; col_in_bits 2500 ; Third entry point (value for money, this one), where A is the number of a bit offset 2501 ; into a table pointed to by HL. For this entry point, D must be equal to A on entry. 2502 ; On return, HL is advanced to the appropriate location and A is the bit mask 2503 ; 2504 ; (It strikes me that the two preceding instructions could have been reversed, and this 2505 ; entry point made one byte earlier, to grab the "mov d,a" instruction.) 2506 ; 2507 0E2B 0F col_in_bits: rrc ; divide cursor position by 8, in order to get byte offset 2508 0E2C 0F rrc 2509 0E2D 0F rrc 2510 0E2E E6 1F ani 1fh ; range is really 0 to 16 for 132-column display 2511 0E30 85 add l ; offset to HL, assuming HL is aligned to allow single-byte add 2512 0E31 6F mov l,a ; HL is now byte containing tab bit 2513 0E32 7A mov a,d ; A <- cursor column again 2514 0E33 E6 07 ani 7 ; divided by 8 before, now just want remainder 2515 0E35 57 mov d,a ; which we will use as a count for a bit shift 2516 0E36 3E 80 mvi a,80h ; Bit 7 is tab at column 0, bit 6 is column 1, etc. 2517 0E38 15 t_shft: dcr d ; 2518 0E39 F8 rm ; if count's gone negative, A contains correct mask 2519 0E3A 0F rrc ; rotate right for next column 2520 0E3B C3 38 0E jmp t_shft 2521 ; 2522 ; 2523 ; try_tx_byte 2524 ; Returns with B containing local OR setup 2525 ; 2526 0E3E 3A A5 21 try_tx_byte: lda local_mode ; Do nothing in local mode 2527 0E41 21 7B 20 lxi h,in_setup ; or setup. 2528 0E44 B6 ora m 2529 0E45 47 mov b,a 2530 0E46 C0 rnz 2531 0E47 DB 42 try_tx2: in ior_flags 2532 0E49 E6 01 ani iob_flags_xmit ; Can't send to PUSART until XMIT high 2533 0E4B C8 rz 2534 0E4C 21 C1 21 lxi h,tx_xo_flag ; Is there an XON/XOFF to transmit? 2535 0E4F 7E mov a,m 2536 0E50 B7 ora a 2537 0E51 36 00 mvi m,0 2538 0E53 2B dcx h ; HL <- tx_xo_char 2539 0E54 C8 rz ; No, return 2540 0E55 7E mov a,m ; A <- XON/XOFF 2541 0E56 D3 00 out iow_pusart_data 2542 0E58 C9 ret 2543 ; 2544 0E59 F3 try_report: di 2545 0E5A CD 3E 0E call try_tx_byte 2546 0E5D FB ei 2547 0E5E 78 mov a,b ; local mode OR setup 2548 0E5F B7 ora a 2549 0E60 C2 6D 0E jnz X0e6d_ ; jump if local mode or setup 2550 0E63 DB 42 in ior_flags 2551 0E65 E6 01 ani iob_flags_xmit ; Can't send to PUSART until XMIT high 2552 0E67 C8 rz 2553 0E68 3A C2 21 lda received_xoff ; Have we received XOFF? 2554 0E6B B7 ora a 2555 0E6C C0 rnz ; We have, so can't transmit 2556 0E6D 21 73 21 X0e6d_: lxi h,sending_report 2557 0E70 7E mov a,m 2558 0E71 B7 ora a 2559 0E72 CA 79 0E jz pick_report ; If we aren't sending a report already, pick the next one 2560 0E75 2A 74 21 lhld report_action ; otherwise, continue doing the current one 2561 0E78 E9 pchl 2562 ; 2563 0E79 3A 72 21 pick_report: lda pending_report ; Do we have any reports that need sending? 2564 0E7C B7 ora a 2565 0E7D C8 rz ; No, return 2566 0E7E 1E 00 mvi e,0 ; Turn one bit into a report table offset 2567 0E80 1F next_report: rar 2568 0E81 DA 89 0E jc find_report 2569 0E84 1C inr e 2570 0E85 1C inr e ; 16-bit offsets 2571 0E86 C3 80 0E jmp next_report 2572 ; 2573 0E89 16 00 find_report: mvi d,0 ; Ready for 16-bit add 2574 0E8B 21 94 0E lxi h,report_table 2575 0E8E 19 dad d ; Get to report routine 2576 0E8F 7E mov a,m ; A <- low byte of report routine 2577 0E90 23 inx h 2578 0E91 66 mov h,m ; H <- high byte of report routine 2579 0E92 6F mov l,a ; L <- low byte of report routine 2580 0E93 E9 pchl ; jump to it! 2581 ; 2582 ; Address of routines that will produce reports that have 2583 ; been asked for. In the ROM, they generally live very close to the 2584 ; that set the "pending report" bit :-) 2585 ; JUMP TABLE 2586 report_table: 2587 0E94 37 0D dw curpos_report ; DSR 6 2588 0E96 F0 12 dw tparm_report ; DECREQTPARM 2589 0E98 CE 0C dw ident_report ; DECID/DA 2590 0E9A 19 0D dw devstat_report ; DSR 5 2591 0E9C 13 09 dw aback_report ; Response to answerback request (C0 ENQ) 2592 0E9E A0 0E dw key_report ; Sending a cursor key sequence 2593 ; 2594 ; key_report 2595 ; 2596 ; Responsible for transmitting both single key presses and more multi-key presses, such 2597 ; as cursor and other function keys. These are all handled as reports so that they can't 2598 ; reports aren't interleaved with key presses or vice versa. 2599 ; 2600 ; When a key report is being prepared, sending CR will also add LF if newline mode is 2601 ; enabled, and both of those controls will enter the queue with high bits set. This routine 2602 ; will clear "sending report" as soon as it encounters a character with high bit set. Does 2603 ; this have any material impact on the sending of the following LF? Not sure how to test this. 2604 ; 2605 0EA0 21 5C 20 key_report: lxi h,curkey_queue 2606 0EA3 46 mov b,m ; B <- first character 2607 0EA4 54 mov d,h 2608 0EA5 5D mov e,l ; DE <- HL 2609 0EA6 13 inx d ; DE points to second character 2610 0EA7 0E 08 mvi c,8 2611 0EA9 1A shift_queue: ldax d ; move all bytes up one place 2612 0EAA 77 mov m,a ; in queue, because we've removed the first 2613 0EAB 23 inx h 2614 0EAC 13 inx d 2615 0EAD 0D dcr c 2616 0EAE C2 A9 0E jnz shift_queue 2617 0EB1 21 43 21 lxi h,curkey_qcount 2618 0EB4 35 dcr m ; mark that we've popped a byte 2619 0EB5 7E mov a,m 2620 0EB6 4F mov c,a ; C <- remaining count 2621 0EB7 C2 C1 0E jnz q_not_empty 2622 0EBA 21 72 21 lxi h,pending_report 2623 0EBD 7E mov a,m 2624 0EBE E6 DF ani ~pend_key ; If we emptied the queue, we're done with this report 2625 0EC0 77 mov m,a 2626 0EC1 3A A5 21 q_not_empty: lda local_mode ; If we're in local mode, keyboard is locked 2627 0EC4 B7 ora a 2628 0EC5 C2 CF 0E jnz upd_kbd_locked 2629 0EC8 79 mov a,c ; A <- remaining count 2630 0EC9 FE 05 cpi 5 2631 0ECB D2 D2 0E jnc key_tx ; If got less than half a queue to transmit, can clear keyboard 2632 0ECE AF xra a ; else, clear it 2633 0ECF 32 44 21 upd_kbd_locked: sta keyboard_locked 2634 0ED2 78 key_tx: mov a,b ; A <- first character 2635 0ED3 E6 80 ani 80h ; isolate the high bit 2636 0ED5 17 ral ; rotate it into carry 2637 0ED6 3F cmc ; complement 2638 0ED7 1F rar ; and back again 2639 0ED8 32 73 21 sta sending_report ; say that we're sending a report if character didn't have high bit set 2640 0EDB 21 A0 0E lxi h,key_report ; Further characters in the queue will come through here 2641 0EDE 22 74 21 shld report_action 2642 ; Fall through. Now send the character we've just retrieved from the queue 2643 ; 2644 ; report_char 2645 ; 2646 ; The routine that is responsible for either transmitted reports (including key presses) 2647 ; to the host or, if we are in LOCAL or SETUP mode, reflecting them internally to cause 2648 ; actions on the screen (so that cursor keys work, we can type normal characters, etc.) 2649 ; 2650 0EE1 78 report_char: mov a,b 2651 0EE2 E6 7F ani 7fh ; This may be last character, so mask off top bit 2652 0EE4 47 mov b,a 2653 0EE5 3A A5 21 lda local_mode ; Are we in local mode 2654 0EE8 21 7B 20 lxi h,in_setup 2655 0EEB B6 ora m ; or setup? 2656 0EEC 78 mov a,b ; A <- character to send 2657 0EED C2 93 05 jnz reflect_char ; jump if in local mode or setup -- denied! 2658 0EF0 D3 00 out iow_pusart_data ; got a report character out of the door 2659 0EF2 C9 ret 2660 2661 ; this routine is jumped to when a report has been prepared 2662 0EF3 21 01 0F send_report: lxi h,cont_report 2663 0EF6 22 74 21 shld report_action 2664 0EF9 AF xra a 2665 0EFA 32 71 21 sta rep_send_offset 2666 0EFD 3C inr a 2667 0EFE 32 73 21 sta sending_report 2668 0F01 21 71 21 cont_report: lxi h,rep_send_offset 2669 0F04 7E mov a,m ; A <- offset into report buffer 2670 0F05 34 inr m ; increment offset to next character 2671 0F06 21 5C 21 lxi h,report_buffer 2672 0F09 85 add l ; add offset to report buffer start 2673 0F0A 6F mov l,a ; point to character to send 2674 0F0B 46 mov b,m ; B <- next character of report 2675 0F0C 78 mov a,b 2676 0F0D B7 ora a 2677 0F0E F2 E1 0E jp report_char ; jump if not last character 2678 0F11 AF xra a 2679 0F12 32 73 21 sta sending_report 2680 0F15 C3 E1 0E jmp report_char 2681 ; 2682 ; send_key_byte 2683 ; Cursor keys and other function keys send multiple bytes, so they are gathered before transmission 2684 ; like any other report. 2685 ; Individual bytes of the sequence are sent through here, with the final byte of the sequence having 2686 ; the high bit set. 2687 ; 2688 0F18 E5 send_key_byte: push h 2689 0F19 D5 push d 2690 0F1A 57 mov d,a ; D <- byte to store 2691 0F1B 3A A7 21 lda setup_b2 2692 0F1E E6 10 ani sb2_autoxon 2693 0F20 CA 2B 0F jz noautox ; Auto XON/XOFF is OFF 2694 0F23 7A mov a,d 2695 0F24 D6 91 sui 91h 2696 0F26 E6 FD ani 0fdh 2697 0F28 CA 61 0F jz xhigh ; Jump if this is XON or XOFF with high bit set 2698 0F2B 21 72 21 noautox: lxi h,pending_report 2699 0F2E 7E mov a,m 2700 0F2F F6 20 ori pend_key ; mark the cursor key report as in progress 2701 0F31 77 mov m,a 2702 0F32 21 43 21 add_a_key: lxi h,curkey_qcount 2703 0F35 7E mov a,m ; A <- count (= offset) 2704 0F36 34 inr m 2705 0F37 21 5C 20 lxi h,curkey_queue ; HL <- buffer address 2706 0F3A 5F mov e,a ; E <- offset 2707 0F3B 85 add l 2708 0F3C 6F mov l,a ; Get to free location 2709 0F3D 72 mov m,d ; Store key byte 2710 0F3E 3E 8D mvi a,8dh ; 2711 0F40 BA cmp d ; Is this CR with high bit? 2712 0F41 C2 4E 0F jnz key_not_cr 2713 0F44 3A A8 21 lda setup_b3 2714 0F47 E6 20 ani sb3_newline 2715 0F49 16 8A mvi d,8ah ; Newline mode implies LF after CR 2716 0F4B C2 32 0F jnz add_a_key ; so go round again. 2717 0F4E 3A A5 21 key_not_cr: lda local_mode ; If we are in local mode 2718 0F51 B7 ora a 2719 0F52 C2 5B 0F jnz apply_lock ; Lock keyboard 2720 0F55 3E 05 mvi a,5 ; 2721 0F57 BB cmp e ; Set carry if offset is greater than 5 2722 0F58 3E 00 mvi a,0 2723 0F5A 1F rar ; Rotate that carry into high bit of A, which will lock keyboard 2724 0F5B 32 44 21 apply_lock: sta keyboard_locked 2725 0F5E D1 popret: pop d 2726 0F5F E1 pop h 2727 0F60 C9 ret 2728 ; 2729 ; Found XON or XOFF with high bit set, in send_key_byte 2730 ; 2731 0F61 7A xhigh: mov a,d 2732 0F62 E6 7F ani 7fh ; bring back to C0 range 2733 0F64 57 mov d,a 2734 0F65 21 C1 21 lxi h,tx_xo_flag 2735 0F68 7E mov a,m ; Is there an XON/XOFF to transmit? 2736 0F69 B7 ora a ; 2737 0F6A C2 75 0F jnz sendx ; Yes 2738 0F6D 2B dcx h ; HL <- tx_xo_char 2739 0F6E 7E mov a,m ; A <- last XON/XOFF transmitted 2740 0F6F BA cmp d ; is it the same as this one? 2741 0F70 C2 75 0F jnz sendx ; No 2742 0F73 23 inx h ; HL <- tx_xo_flag 2743 0F74 72 mov m,d ; Flag this one to go 2744 0F75 4A sendx: mov c,d ; C <- XON/XOFF 2745 0F76 06 02 mvi b,2 2746 0F78 CD 7E 0F call send_xonoff 2747 0F7B C3 5E 0F jmp popret 2748 ; 2749 ; send_xonoff 2750 ; On entry: 2751 ; C is the character we went to sent, XON or XOFF 2752 ; B is the reason we're sending: 2753 ; 1 = receive buffer exhaustion [XOFF] or space available [XON]) 2754 ; 2 = user-initiated (pressing NO SCROLL) 2755 ; 2756 0F7E 3A A7 21 send_xonoff: lda setup_b2 ; If we aren't configured for 2757 0F81 E6 10 ani sb2_autoxon ; auto XON/XOFF, 2758 0F83 C8 rz ; exit 2759 0F84 3A A5 21 lda local_mode ; Similarly, if we're in local mode 2760 0F87 B7 ora a ; then there is no point 2761 0F88 C0 rnz ; in sending XOFF 2762 0F89 79 mov a,c 2763 0F8A 21 BF 21 lxi h,why_xoff 2764 0F8D FE 13 cpi C0_XOFF 2765 0F8F 78 mov a,b ; A <- mask for why we're XON/XOFF-ing 2766 0F90 CA 9E 0F jz is_xoff 2767 0F93 2F cma ; If we're sending asking for XON, be aware that there is more 2768 0F94 A6 ana m ; than one reason for the XOFF, so only release the mask for 2769 0F95 77 mov m,a ; this reason. 2770 0F96 F5 push psw 2771 0F97 E6 02 ani 2 ; If we're released NO SCROLL, mark that 2772 0F99 32 C4 21 sta noscroll 2773 0F9C F1 pop psw 2774 0F9D C0 rnz 2775 0F9E B6 is_xoff: ora m ; Add this XOFF reason to why_xoff 2776 0F9F 77 mov m,a 2777 0FA0 79 mov a,c 2778 0FA1 21 C0 21 lxi h,tx_xo_char ; If this Xany isn't the same as the last one, 2779 0FA4 BE cmp m ; then we'll send it 2780 0FA5 C8 rz ; else quit 2781 0FA6 77 mov m,a ; write to tx_xo_char 2782 0FA7 23 inx h ; HL <- tx_xo_flag 2783 0FA8 77 mov m,a ; flag so that tx_xo_char gets sent 2784 0FA9 C9 ret 2785 ; 2786 ; wait_n_frames 2787 ; Only ever called to wait one frame, when screens are (re)initialised and we want the DMA 2788 ; to catch up. 2789 ; 2790 0FAA 21 7F 20 wait_n_frames: lxi h,frame_count 2791 0FAD 86 add m 2792 0FAE BE test_frames: cmp m 2793 0FAF C8 rz 2794 0FB0 E5 push h 2795 0FB1 F5 push psw 2796 0FB2 3A 7B 20 lda in_setup 2797 0FB5 B7 ora a 2798 0FB6 CC 88 14 cz keyboard_tick 2799 0FB9 F1 pop psw 2800 0FBA E1 pop h 2801 0FBB C3 AE 0F jmp test_frames 2802 ; 2803 ; Change the extra line's attributes so it becomes part of the scrolling region and is normal width. 2804 ; 2805 0FBE 2A 4E 20 reset_extra: lhld line_25_addr 2806 0FC1 7C mov a,h 2807 0FC2 F6 F0 ori 0f0h 2808 0FC4 67 mov h,a 2809 0FC5 22 4E 20 shld line_25_addr 2810 ; 2811 ; clear_row 2812 ; On entry: HL is a pointer to start of line in screen RAM. 2813 ; 2814 0FC8 3A 50 20 clear_row: lda screen_cols 2815 ; 2816 ; clear_part_row 2817 ; On entry: HL is a pointer to screen RAM. A is number of positions to clear (forward) 2818 ; 2819 clear_part_row: 2820 0FCB 47 mov b,a ; B <- number of columns to clear 2821 0FCC 7C mov a,h ; Make sure that HL is proper pointer (no attributes) 2822 0FCD E6 0F ani 0fh 2823 0FCF F6 20 ori 20h 2824 0FD1 67 mov h,a 2825 0FD2 C6 10 adi 10h ; Now make an attribute RAM pointer, for DE 2826 0FD4 57 mov d,a 2827 0FD5 5D mov e,l 2828 0FD6 3E FF mvi a,0ffh ; A <- default rendition 2829 0FD8 36 00 clr_next_col: mvi m,0 ; clear screen RAM 2830 0FDA 12 stax d ; and attribute RAM 2831 0FDB 13 inx d ; next column 2832 0FDC 23 inx h ; ... in both 2833 0FDD 05 dcr b 2834 0FDE C2 D8 0F jnz clr_next_col 2835 0FE1 3C inr a ; zero A (was 0ffh throughout loop) 2836 0FE2 32 C3 21 sta row_clearing ; done clearing of row 2837 0FE5 C9 ret 2838 ; 2839 ; for scrolling up 2840 0FE6 06 FF move_lines_up: mvi b,0ffh 2841 0FE8 C3 ED 0F jmp move_lines 2842 ; 2843 ; for scrolling down 2844 0FEB 06 01 move_lines_dn: mvi b,1 2845 ; 2846 ; at this point, B is either -1 or +1, for the direction of movement 2847 ; 2848 0FED 21 55 21 move_lines: lxi h,top_margin 2849 0FF0 56 mov d,m ; D <- top margin 2850 0FF1 23 inx h ; HL ^ bottom_margin 2851 0FF2 5E mov e,m ; E <- bottom margin 2852 0FF3 78 mov a,b ; A <- direction 2853 0FF4 B7 ora a 2854 0FF5 7A mov a,d ; A <- top margin 2855 0FF6 F2 FA 0F jp grab_phys ; jump if scrolling down (i.e. at top margin) 2856 0FF9 7B mov a,e ; A <- bottom margin (because scrolling up 2857 0FFA CD E6 13 grab_phys: call phl_num ; A <- physical line number of this row (HL points to entry in LATOFS) 2858 0FFD 7B mov a,e 2859 0FFE 92 sub d ; A <- bottom margin - top margin 2860 0FFF 4F mov c,a ; C <- no. lines to scroll - 1 2861 1000 3A 2B 21 lda latofs_last ; physical line number of "25th line" 2862 1003 56 shuffle_latofs: mov d,m ; D <- physical line number of old row (margin row, initially) 2863 1004 77 mov m,a ; replace this with row in A (initially, 25th row, because that will come into view) 2864 1005 78 mov a,b ; A <- direction 2865 1006 85 add l ; next logical line (one line nearer beginning when scrolling up) 2866 1007 6F mov l,a 2867 1008 0D dcr c ; decrement number of lines to shift 2868 1009 7A mov a,d ; A <- previous row (one we just overwrote) 2869 100A F2 03 10 jp shuffle_latofs 2870 100D E6 7F ani 7fh ; remove any line attributes from line about to disappear 2871 100F 32 2B 21 sta latofs_last ; and this becomes the new "25th row" 2872 ; fall through 2873 ; 2874 ; Other routines that perform major reorganisation of the screen, including changing the 2875 ; size of a line, jump here so that they can invalidate cursor row before do all the normal 2876 ; post cursor move actions. That way, we get a full recalculation of the cursor position, 2877 ; because we're claiming it was previously on row -1, which can't be the same as the current row. 2878 ; 2879 1012 3E FF inv_saved_row: mvi a,0ffh 2880 1014 32 0E 21 sta saved_curs_row 2881 1017 C3 36 16 jmp move_updates 2882 ; 2883 ; 2884 ; start a jump scroll up 2885 101A CD 91 11 start_jump_up: call wait_for_x 2886 101D 3A 56 21 lda bottom_margin 2887 1020 CD 2F 12 call connect_extra ; Clear extra line and connect it to screen list 2888 1023 CD E6 0F call move_lines_up ; Move logical lines 2889 1026 3A 55 21 lda top_margin 2890 1029 3D dcr a 2891 102A C3 3E 10 jmp scroll_common 2892 ; 2893 ; start a jump scroll down 2894 102D CD 91 11 start_jump_dn: call wait_for_x 2895 1030 3A 55 21 lda top_margin 2896 1033 3D dcr a 2897 1034 CD 2F 12 call connect_extra 2898 1037 CD EB 0F call move_lines_dn 2899 103A 3A 56 21 lda bottom_margin 2900 103D 3D dcr a 2901 103E CD CE 11 scroll_common: call calc_shuf1 2902 1041 FB ei 2903 1042 32 7A 20 sta shuffle_ready ; A is not zero here 2904 1045 21 56 21 lxi h,bottom_margin 2905 1048 7E mov a,m 2906 1049 2B dcx h 2907 104A 96 sub m 2908 104B FE 17 cpi 17h ; Even jump scrolls can only be completed in one go 2909 104D C0 rnz ; if they apply to full screen, else they must be 2910 ; delayed until frame refresh. (TM §4.7.5) 2911 ; 2912 ; shuffle 2913 ; 2914 ; This performs a test-and-clear on the shuffle_ready semaphore and then moves the lines pointers 2915 ; around as described on TM p.4-92. The connecting of the extra line has 2916 ; been done in advance, and the working out of the other connections for shuffle data 1 and 2. 2917 ; Now all that remains is for the pointers to be updated. 2918 ; 2919 104E 21 7A 20 shuffle: lxi h,shuffle_ready 2920 1051 7E mov a,m 2921 1052 36 00 mvi m,0 2922 1054 B7 ora a 2923 1055 C8 rz 2924 1056 2A 56 20 lhld shufdt2 2925 1059 EB xchg 2926 105A 2A 58 20 lhld shufad2 ; Place the DMA address in shufdt2 into shufad2 2927 105D 72 mov m,d 2928 105E 23 inx h 2929 105F 73 mov m,e 2930 1060 2A 79 21 lhld shufdt1 2931 1063 EB xchg 2932 1064 2A 75 20 lhld shufad1 ; Place the DMA address in shufdt1 into shufad1 2933 1067 72 mov m,d 2934 1068 23 inx h 2935 1069 73 mov m,e 2936 106A 21 56 20 reset_shuffle: lxi h,shufdt2 2937 106D 22 58 20 shld shufad2 2938 1070 22 75 20 shld shufad1 2939 1073 C9 ret 2940 ; 2941 ; curs_line_addr 2942 ; Returns pointer to current line in screen RAM 2943 ; 2944 ; On entry: -- 2945 ; On exit: HL is address of first location of line in screen RAM 2946 ; 2947 1074 CD E3 13 curs_line_addr: call curs_phl_num 2948 1077 E6 80 ani 80h ; Grab double-width marker 2949 1079 32 57 21 sta curr_line_dbl ; and store it 2950 107C CD 8D 13 call curs_phl_addr ; HL <- start of cursor line in screen RAM 2951 107F 22 4E 21 shld start_line_addr ; and store it 2952 1082 C9 ret 2953 ; 2954 ; memset - set DE bytes from (HL) to B 2955 1083 70 memset: mov m,b 2956 1084 23 inx h 2957 1085 1B dcx d 2958 1086 7A mov a,d 2959 1087 B3 ora e 2960 1088 C2 83 10 jnz memset 2961 108B C9 ret 2962 ; 2963 ; wait_scroll 2964 ; 2965 ; Certain actions wait for a quiet screen, i.e. scrolling to finish, before they 2966 ; take place. The TM says that entering SET-UP mode is one of those. Examination of the 2967 ; code shows three other actions that will wait: 2968 ; 1. Changing the top and bottom margins 2969 ; 2. Scrolling up or down the display (RI or LF), because there is only one extra line, 2970 ; and scrolling needs to have finished using it before we can connect is again 2971 ; 3. Print screen, which waits before telling GPO to go 2972 ; 2973 ; oddity - this routine uses C, but no one ever jumps to the line below, which 2974 ; sets C to 0. This routine feels like C=0 could be legitimate, but worthwhile finding 2975 ; out why it's never called like that. So the line below is UNUSED but is not 2976 ; this ROM's checksum address. 2977 ; 2978 ; C is a combination of two flags, in bit 0 and bit 7 (for ease of testing with rotate 2979 ; and carry testing). 2980 ; C = 01h allows keyboard processing to take place while waiting 2981 ; C = 80h makes routine exit through wait_for_x (still needs bottoming out) 2982 ; 2983 ; When using for scrolling, i.e. processing index or reverse_index, this routine is called 2984 ; with C=1, and that results in it exiting with HL pointing to scroll_pending. The callers 2985 ; then change scroll_pending through this pointer, which was quite hard to find! 2986 ; 2987 108C 0E 00 mvi c,0 ; UNUSED 2988 108E 21 51 20 wait_scroll: lxi h,scroll_pending 2989 1091 F3 di 2990 1092 3A 65 20 lda smooth_scroll ; in smooth scroll? 2991 1095 B6 ora m ; or scroll pending? 2992 1096 FB ei 2993 1097 CA A7 10 jz scroll_done 2994 109A 79 mov a,c 2995 109B 1F rar 2996 109C C5 push b ; save our wait flags 2997 109D DC 88 14 cc keyboard_tick 2998 10A0 CD 93 14 call update_kbd 2999 10A3 C1 pop b ; restore wait flags 3000 10A4 C3 8E 10 jmp wait_scroll 3001 ; 3002 10A7 79 scroll_done: mov a,c 3003 10A8 17 ral ; Test bit 7 3004 10A9 D0 rnc 3005 10AA C3 91 11 jmp wait_for_x 3006 ; 3007 10AD 21 70 06 init_screen: lxi h,0670h ; DMA address (2006h) 3008 10B0 22 04 20 shld line1_dma ; blank screen by pointing direct to fill lines 3009 10B3 AF xra a ; A <- 0 3010 10B4 32 51 20 sta scroll_pending ; there is no scroll pending 3011 10B7 32 65 20 sta smooth_scroll ; we are not smooth scrolling now 3012 10BA 32 5A 20 sta scroll_scan 3013 10BD D3 A2 out iow_dc012 ; DC012 <- "low order scroll latch = 00" 3014 10BF 3E 04 mvi a,4 3015 10C1 D3 A2 out iow_dc012 ; DC012 <- "high order scroll latch = 00" 3016 10C3 CD 6A 10 call reset_shuffle ; feels like cleaning up shuffle 3017 10C6 FB ei 3018 10C7 3E 01 mvi a,1 3019 10C9 CD AA 0F call wait_n_frames ; allow DMA to blank screen 3020 10CC 21 D0 22 lxi h,main_video ; (first byte of screen definition) 3021 10CF 7C mov a,h 3022 10D0 C6 10 adi 10h ; Attribute RAM is Screen RAM + 1000h 3023 10D2 57 mov d,a 3024 10D3 5D mov e,l ; DE <- start of attribute RAM for default display 3025 10D4 01 2F 0D lxi b,0d2fh ; 22d0h + 0d2fh = 2fffh 3026 ; 3027 ; clear screen, including AVO's additional 1K of screen RAM 3028 ; 3029 10D7 3A C7 21 cls: lda cls_char 3030 10DA 77 mov m,a ; "clear" screen RAM (maybe to 'E', for alignment display) 3031 10DB 23 inx h 3032 10DC 3E FF mvi a,0ffh 3033 10DE 12 stax d ; Normal attributes 3034 10DF 13 inx d 3035 10E0 0B dcx b 3036 10E1 78 mov a,b 3037 10E2 B1 ora c 3038 10E3 C2 D7 10 jnz cls 3039 ; 3040 10E6 3A C7 21 lda cls_char 3041 10E9 32 F4 20 sta char_und_curs 3042 10EC AF xra a 3043 10ED 32 F9 20 sta curs_row 3044 10F0 32 F8 20 sta curs_col 3045 10F3 3E FF mvi a,0ffh 3046 10F5 32 F5 20 sta rend_und_curs 3047 10F8 C9 ret 3048 ; 3049 ; make_screen 3050 ; Write all the line terminators for a screen of a given width in normal screen RAM. 3051 ; Writes pointers only; makes no attempt to zero the display. 3052 ; 3053 ; On entry: C is number of columns on screen 3054 ; 3055 10F9 AF make_screen: xra a ; A <- 0 3056 10FA 32 51 20 sta scroll_pending ; there is no scroll pending 3057 10FD 3A 65 20 wait_smooth: lda smooth_scroll ; are we in middle of smooth scroll? 3058 1100 B7 ora a 3059 1101 C2 FD 10 jnz wait_smooth 3060 1104 21 D0 22 lxi h,main_video ; HL <- start of screen RAM 3061 1107 06 00 mvi b,0 ; BC <- number of columns 3062 1109 09 dad b ; advance to end of first line 3063 110A CD BF 11 call last_row ; B <- last row number (23 or 13) 3064 110D 0C inr c ; C <- number of columns + 1 (oddity in make_lines) 3065 110E CD 36 11 call make_lines 3066 1111 E5 push h 3067 1112 06 01 mvi b,1 ; Need a 24th line 3068 1114 CD 36 11 call make_lines 3069 1117 E1 pop h 3070 1118 36 7F mvi m,7fh ; Terminate 23rd line (already done this?) 3071 111A 23 inx h ; HL <- 24th line DMA address (big endian) 3072 111B 22 54 20 shld UNREAD_X2054 ; Store this 3073 111E 36 70 mvi m,70h ; Make DMA address for 24th line 7006h (i.e. fill lines) 3074 1120 23 inx h 3075 1121 36 06 mvi m,6 3076 1123 79 mov a,c ; A <- number of columns + 1 3077 1124 CD DE 13 call add_a_to_hl ; Get to end of 24th line 3078 1127 36 7F mvi m,7fh ; Write terminator (first time!) 3079 1129 0D dcr c ; C <- number of columns 3080 112A 3E 03 mvi a,3 ; Point initial screen DMA to line 1 (2003h) 3081 112C 32 02 20 sta line0_dma + 1 3082 112F 21 F2 D0 lxi h,main_video_be ; byte-swapped video address 3083 1132 22 04 20 shld line1_dma ; Point to our new screen 3084 1135 C9 ret 3085 ; 3086 ; Given a pointer HL, to end of a line, make another B lines of (C - 1) columns wide 3087 ; (create a full screen display) 3088 ; HL isn't advanced after writing low byte of screen address, so C is passed in 3089 ; with number of columns + 1 (small oddity) 3090 ; 3091 1136 36 7F make_lines: mvi m,7fh ; write terminator 3092 1138 23 inx h ; advance to point at high byte (DMA order) 3093 1139 54 mov d,h ; DE <- HL 3094 113A 5D mov e,l 3095 113B 13 inx d ; In DMA terms, DE is byte-swapped, so advance 2 locations 3096 113C 13 inx d 3097 113D 7A mov a,d ; And add normal line attributes 3098 113E F6 F0 ori 0f0h 3099 1140 77 mov m,a ; Write high byte + attrs first 3100 1141 23 inx h 3101 1142 73 mov m,e ; Then low byte 3102 1143 79 mov a,c ; A <- columns + 1 (didn't increment HL already) 3103 1144 CD DE 13 call add_a_to_hl ; advance to next line 3104 1147 05 dcr b 3105 1148 C2 36 11 jnz make_lines 3106 114B C9 ret 3107 ; 3108 ; make_line_t 3109 ; 3110 ; Create the physical line address and logical offset tables for screen rows. 3111 ; The logical offset table is what the TM calls LATOFS. 3112 ; 3113 ; On entry, C is number of columns 3114 ; 3115 114C 21 C2 20 make_line_t: lxi h,pline_addr ; Start of table of physical line addresses 3116 114F 0C inr c ; Each line is columns + 3 terminators bytes long 3117 1150 0C inr c 3118 1151 0C inr c 3119 1152 11 D0 22 lxi d,main_video 3120 1155 06 00 mvi b,0 ; Make BC <- columns + 3 (for 16-bit additions) 3121 1157 3E 19 mvi a,25 3122 1159 73 store_physline: mov m,e ; Store address of physical line in table 3123 115A 23 inx h 3124 115B 72 mov m,d 3125 115C 23 inx h 3126 115D EB xchg ; HL <- screen RAM address 3127 115E 09 dad b ; get to next line, past terminators too 3128 115F EB xchg ; DE <- next line screen RAM address 3129 1160 3D dcr a ; Do all 24 lines 3130 1161 C2 59 11 jnz store_physline 3131 1164 32 55 21 sta top_margin ; new screen, reset top margin 3132 1167 CD BF 11 call last_row ; B <- last row number 3133 116A 78 mov a,b 3134 116B 32 56 21 sta bottom_margin ; and bottom margin 3135 116E 3C inr a ; row number of one line beyond bottom margin, 3136 116F 32 2B 21 sta latofs_last ; as last place in latofs 3137 1172 2A F2 20 lhld pline_extra ; Now grab screen address of 0-based row 24 3138 1175 3E 17 mvi a,23 3139 1177 B8 cmp b 3140 1178 CA 7E 11 jz ok24 ; unless this is a 14-row screen, in which case 3141 117B 2A DE 20 lhld pline_addr+(14*2) ; use 0-based row 14 (20deh) 3142 117E 7C ok24: mov a,h 3143 117F F6 F0 ori 0f0h 3144 1181 67 mov h,a 3145 1182 22 4E 20 shld line_25_addr ; extra line attributes to single width 3146 1185 21 13 21 lxi h,latofs ; Now write LATOFS, with all logical lines pointing 3147 1188 AF xra a ; to the same physical lines, so just 0 to 23. 3148 1189 77 next_lof: mov m,a 3149 118A 2C inr l ; [* fragile *] LATOFS can't go just anywhere 3150 118B 3C inr a 3151 118C 05 dcr b 3152 118D F2 89 11 jp next_lof 3153 1190 C9 ret 3154 ; 3155 1191 F3 wait_for_x: di 3156 1192 3A 7A 20 lda shuffle_ready 3157 1195 B7 ora a 3158 1196 CA A0 11 jz no_shuffle 3159 1199 FB do_tick: ei 3160 119A CD 88 14 call keyboard_tick 3161 119D C3 91 11 jmp wait_for_x 3162 ; 3163 11A0 3A 65 20 no_shuffle: lda smooth_scroll ; are we in middle of smooth scroll? 3164 11A3 B7 ora a 3165 11A4 C2 B0 11 jnz next_scan 3166 11A7 3A 51 20 lda scroll_pending 3167 11AA B7 ora a 3168 11AB FB ei 3169 11AC C8 rz ; no scroll pending, so exit 3170 11AD C3 99 11 jmp do_tick 3171 ; 3172 11B0 3A 5B 20 next_scan: lda scroll_dir 3173 11B3 21 5A 20 lxi h,scroll_scan 3174 11B6 86 add m 3175 11B7 27 daa 3176 11B8 E6 0F ani 0fh 3177 11BA CA 99 11 jz do_tick 3178 11BD FB ei 3179 11BE C9 ret 3180 ; 3181 ; last_row 3182 ; Returns with B set to last row number, i.e. 13 or 23 3183 ; 3184 last_row: 3185 11BF E5 push h 3186 11C0 06 17 mvi b,23 ; assume we have 24 lines 3187 11C2 3A A2 21 lda columns_132 ; unless we're in 132-column mode 3188 11C5 21 C8 21 lxi h,avo_missing 3189 11C8 A6 ana m ; AND we DON'T have AVO 3190 11C9 E1 pop h 3191 11CA C8 rz 3192 11CB 06 0D mvi b,13 ; in which case, we only have 14 lines 3193 11CD C9 ret 3194 ; 3195 ; calc_shuf1 3196 ; Work out which line will be the new top line and take the line that will disappear and store it as 3197 ; the new extra line. 3198 ; 3199 ; On entry: A is the top or bottom margin - 1 (i.e. RI at top of full screen will enter here with A = 23) 3200 ; 3201 11CE B7 calc_shuf1: ora a 3202 11CF F2 E8 11 jp pos_margin 3203 11D2 21 04 20 lxi h,line1_dma ; HL <- address of video line (big-endian) 3204 11D5 E5 push h 3205 11D6 7E mov a,m ; A <- high byte the video address 3206 11D7 23 inx h ; HL points to low byte 3207 11D8 6E mov l,m ; L <- low byte of video address 3208 11D9 F6 F0 ori 0f0h ; Change attributes of high byte to be scrolling region, normal size 3209 11DB 67 mov h,a ; H <- high byte of video address 3210 11DC 22 4E 20 shld line_25_addr ; stow it as new extra line address 3211 11DF CD 90 12 call next_line_addr ; DE <- DMA address of next line 3212 11E2 E1 pop h ; HL <- address that will need changing to point to line 2 (new top line) 3213 11E3 42 mov b,d 3214 11E4 4B mov c,e ; BC <- DMA address of line 2 3215 11E5 C3 0A 12 jmp store_shufad1 3216 ; 3217 11E8 CD 7F 12 pos_margin: call to_line_n 3218 11EB CD 90 12 call next_line_addr ; DE <- DMA address of line we're losing from bottom 3219 11EE E5 push h 3220 11EF D5 push d 3221 11F0 EB xchg ; HL <- DMA address of losing line 3222 11F1 CD 90 12 call next_line_addr ; DE <- address of line after (fill address) 3223 11F4 42 mov b,d 3224 11F5 4B mov c,e ; BC <- fill address 3225 11F6 D1 pop d ; DE <- losing line address, again 3226 11F7 7A mov a,d 3227 11F8 F6 F0 ori 0f0h ; Restore its line attributes 3228 11FA 67 mov h,a 3229 11FB 6B mov l,e ; HL <- (corrected) losing line address 3230 11FC 22 4E 20 shld line_25_addr ; which now becomes available as the extra line 3231 11FF E1 pop h ; HL <- new bottom line address 3232 1200 7C mov a,h 3233 1201 F6 9F ori 9fh ; testing for double-width attributes 3234 1203 3C inr a 3235 1204 C2 11 12 jnz not_sinwid1 ; If line isn't single-width, there are two pointers to next line 3236 1207 CD 99 12 call eoline_addr 3237 120A F3 store_shufad1: di ; Make shufad1 & shufdt1 consistent before vertical refresh 3238 120B 22 75 20 shld shufad1 ; Now store the address of the terminating info to be updated 3239 120E C3 28 12 jmp store_shufdt1 3240 ; 3241 1211 CD 9D 12 not_sinwid1: call real_addr 3242 1214 3A 50 20 lda screen_cols 3243 1217 0F rrc ; Need to get to halfway terminator because line is double-width 3244 1218 57 mov d,a ; D <- half screen width 3245 1219 CD DE 13 call add_a_to_hl 3246 121C 23 inx h ; HL <- next DMA address 3247 121D F3 di 3248 121E 22 75 20 shld shufad1 ; This is the address we'll write to during shuffle 3249 1221 7A mov a,d ; A <- half screen width 3250 1222 CD DE 13 call add_a_to_hl ; Get to the address we aren't using at the moment 3251 1225 70 mov m,b ; and make it consistent with the one we'll update during shuffle 3252 1226 23 inx h 3253 1227 71 mov m,c 3254 1228 60 store_shufdt1: mov h,b 3255 1229 69 mov l,c 3256 122A 22 79 21 shld shufdt1 3257 122D FB ei 3258 122E C9 ret 3259 ; 3260 ; connect_extra 3261 ; In advance of the shuffle happening, connect the extra line to the screen lines. 3262 ; If we are indexing down, the extra line will be connected to the first line 3263 ; (within margins). If we are reverse indexing then we are shifting all the lines 3264 ; down and the extra line connection will be made to the second line. At the same 3265 ; time, we write shufad2 and shufdt2, per TM Figure 4-7-9, so that one of the 3266 ; existing lines will get a join to the extra line when the shuffle gets performed. 3267 ; 3268 ; On entry: A is the row number of the bottom margin, or the top margin - 1 (so may be negative) 3269 ; 3270 122F F5 connect_extra: push psw 3271 1230 CD BE 0F call reset_extra ; make the cursor line scrollable 3272 1233 F1 pop psw 3273 1234 B7 ora a 3274 1235 21 04 20 lxi h,line1_dma ; set HL in case we're at the top and 3275 1238 FA 48 12 jm at_top ; the extra line will be connected after the fill line 3276 123B CD 7F 12 call to_line_n ; otherwise work out which line will be connected to extra 3277 123E 7C mov a,h ; A <- high byte of screen RAM address with attrs 3278 123F F6 9F ori 9fh 3279 1241 3C inr a 3280 1242 C2 52 12 jnz not_sinwid2 ; jump if line attrs were not single width 3281 1245 CD 99 12 call eoline_addr ; HL <- next line DMA address 3282 1248 F3 at_top: di 3283 1249 22 58 20 shld shufad2 ; points to line DMA address 3284 124C 46 mov b,m 3285 124D 23 inx h 3286 124E 4E mov c,m ; BC <- line DMA address + attrs 3287 124F C3 71 12 jmp conn_shuf2 3288 ; 3289 1252 CD 9D 12 not_sinwid2: call real_addr 3290 1255 3A 50 20 lda screen_cols ; As the line we're connecting to extra isn't single 3291 1258 0F rrc ; width, work out where the terminator is (i.e. half way) 3292 1259 47 mov b,a 3293 125A CD DE 13 call add_a_to_hl 3294 125D 23 inx h ; HL <- next DMA address 3295 125E F3 di 3296 125F 22 58 20 shld shufad2 ; This is the address we'll write to during shuffle 3297 1262 78 mov a,b ; A <- half width 3298 1263 46 mov b,m ; Grab the current DMA address 3299 1264 23 inx h 3300 1265 4E mov c,m 3301 1266 CD DE 13 call add_a_to_hl ; move to full width (HL points one byte too far, to DMA low byte) 3302 1269 EB xchg ; DE <- DMA low byte 3303 126A 2A 4E 20 lhld line_25_addr 3304 126D EB xchg 3305 126E 73 mov m,e ; Place LOW byte of extra line address 3306 126F 2B dcx h ; Move back to high byte 3307 1270 72 mov m,d ; Place HIGH byte of extra line address 3308 ; Mid-line address (the active one) will be updated during shuffle 3309 1271 2A 4E 20 conn_shuf2: lhld line_25_addr ; Line 24 will have its end pointer updated 3310 1274 22 56 20 shld shufdt2 ; to point to the extra line during shuffle 3311 1277 CD 99 12 call eoline_addr 3312 127A 70 mov m,b ; Update extra line's next DMA address now 3313 127B 23 inx h 3314 127C 71 mov m,c 3315 127D FB ei 3316 127E C9 ret 3317 ; 3318 ; to_line_n 3319 ; On entry: 3320 ; A is the row number of physical screen to get to (0 gets to address line1_dma points to) 3321 ; On exit: 3322 ; HL points to first character of screen RAM of line 3323 ; 3324 127F 3C to_line_n: inr a 3325 1280 47 mov b,a 3326 1281 21 04 20 lxi h,line1_dma 3327 1284 7E adv_line: mov a,m ; A <- high byte of screen DMA address (and attrs) 3328 1285 23 inx h 3329 1286 6E mov l,m ; L <-low byte 3330 1287 67 mov h,a ; H <- high byte 3331 1288 05 dcr b 3332 1289 C8 rz 3333 128A CD 99 12 call eoline_addr ; advance to next DMA address 3334 128D C3 84 12 jmp adv_line 3335 ; 3336 ; Given start of line address in HL, returns DE with address of next line 3337 ; 3338 1290 E5 next_line_addr: push h 3339 1291 CD 99 12 call eoline_addr 3340 1294 56 mov d,m ; D <- high byte (incl. attrs) 3341 1295 23 inx h 3342 1296 5E mov e,m ; E <- low byte 3343 1297 E1 pop h 3344 1298 C9 ret 3345 ; 3346 ; Given start of line address in HL, add screen width to get end of line 3347 1299 CD DB 13 eoline_addr: call add_cols_to_hl 3348 129C 23 inx h 3349 ; real_addr 3350 ; 3351 ; On entry, HL is an address in screen RAM with the line attribute and scrolling region bits 3352 ; in the top four bits. Mask out these attributes and add the 2000h offset to get a real address. 3353 ; 3354 129D 7C real_addr: mov a,h 3355 129E E6 0F ani 0fh 3356 12A0 F6 20 ori 20h 3357 12A2 67 mov h,a 3358 12A3 C9 ret 3359 ; 3360 12A4 CD B0 12 line_attr_dwl: call double_line 3361 12A7 50 db 50h 3362 ; 3363 12A8 CD B0 12 dhl_top_action: call double_line 3364 12AB 30 db 30h 3365 ; 3366 12AC CD B0 12 dhl_bot_action: call double_line 3367 12AF 10 db 10h 3368 ; 3369 ; double_line 3370 ; 3371 ; The single byte at the return address is the line attributes to be applied to the current line. 3372 ; We are not going to go back there, so no attempt is made to restore the stacked address. 3373 ; As these are all double-width line attributes, the right margin must be adjusted, and the cursor 3374 ; pulled back onto screen if necessary. 3375 ; 3376 12B0 CD 91 11 double_line: call wait_for_x 3377 12B3 21 13 21 lxi h,latofs ; Find current physical line number in LATOFS ` 3378 12B6 3A F9 20 lda curs_row ; | duplicates 3379 12B9 85 add l ; < curs_phl_num 3380 12BA 6F mov l,a ; | 3381 12BB 7E mov a,m ; , 3382 12BC F6 80 ori 80h ; Mark the current row in LATOFS as double width 3383 12BE 77 mov m,a 3384 ; We've now set bit 7 of the physical line number in LATOFS, but this doesn't affect 3385 ; the next routine, even though it again looks up a physical line number in LATOFS, 3386 ; as the (corrupted) line number it receives will be doubled in order to extract an 3387 ; address from the table at pline_addr. 3388 ; 3389 12BF CD 8D 13 call curs_phl_addr ; HL <- address of cursor line in screen RAM? 3390 12C2 CD C3 13 call halve_line 3391 12C5 E1 pop h ; Grab return address (which we will discard) 3392 12C6 7E mov a,m ; A <- line attributes to be applied 3393 12C7 CD 95 13 call add_line_attrs 3394 12CA 3A 53 21 lda right_margin 3395 12CD 21 F8 20 lxi h,curs_col 3396 12D0 B7 ora a ; clear carry because RAR brings it into bit 7 (why not use RRC?) 3397 12D1 1F rar ; A <- right margin / 2 3398 12D2 BE cmp m 3399 12D3 D2 12 10 jnc inv_saved_row ; jump if right margin / 2 is still greater than current cursor column 3400 ; 3401 ; The next two lines look odd because they aren't storing a screen RAM address in 3402 ; cursor_address, as you'd expect. However, we're about to go into move_updates, 3403 ; which is going to attempt to restore the old character under the cursor and the 3404 ; old rendition in attribute RAM. If we allowed that to happen with the cursor just 3405 ; beyond the margin, this restoration could wreck the new terminators, so by 3406 ; storing the address of these locations in the cursor address, move_updates 3407 ; will place the character under the cursor in char_und_curs, a cunning no-op. 3408 ; 3409 12D6 21 F4 20 lxi h,char_und_curs 3410 12D9 22 F6 20 shld cursor_address 3411 12DC C3 12 10 jmp inv_saved_row 3412 ; 3413 ; Process DECREQTPARM - ESC [ <sol< x 3414 12DF 3A 30 21 tparm_action: lda csi_p1 3415 12E2 FE 02 cpi 2 ; request parameter can only be 0 or 1 3416 12E4 D0 rnc 3417 12E5 32 76 21 sta tparm_solicited 3418 ; If they are allowed, exiting SET-UP will send a TPARM report 3419 setup_tparm: 3420 12E8 21 72 21 lxi h,pending_report 3421 12EB 7E mov a,m 3422 12EC F6 02 ori pend_tparm 3423 12EE 77 mov m,a 3424 12EF C9 ret 3425 ; 3426 ; tparm_report 3427 ; 3428 ; Produce a DECREPTPARM sequence: 3429 ; ESC [ <sol<; <par<; <nbits<; <xspeed<; <rspeed<; <clkmul<; <flags< x 3430 ; 3431 12F0 3E FD tparm_report: mvi a,~pend_tparm 3432 12F2 CD BF 0D call prepare_report 3433 12F5 3A 76 21 lda tparm_solicited ; A <- solicited flag (0 or 1) 3434 12F8 F6 32 ori '2' ; convert to ASCII digit '2' or '3' 3435 12FA CD B9 0D call rep_char_semi ; <sol< 3436 12FD 3A A9 21 lda setup_b4 ; A <- comms + power settings 3437 1300 F5 push psw ; save for later, when we'd like bits/char 3438 1301 E6 C0 ani 0c0h ; keep just parity bits 3439 1303 F6 18 ori 18h ; mix in half an ASCII '0' 3440 1305 87 add a ; shift even/odd into carry 3441 1306 3C inr a ; and convert "no parity" into '1' 3442 1307 F2 10 13 jp skip_odd_even ; skip to display if parity is off (new bit 7) 3443 130A 1F rar ; bring carry back into bit 7 3444 130B 07 rlc ; and rotate it into bit 0 3445 130C F6 04 ori 4 ; making odd parity '4' and even '5'|80h 3446 130E E6 7F ani 7fh ; clean up that high bit 3447 1310 CD B9 0D skip_odd_even: call rep_char_semi ; <par< 3448 1313 06 31 mvi b,'1' ; prepare "8 bits" answer 3449 1315 F1 pop psw ; A <- comms + power settings again 3450 1316 E6 20 ani 20h ; bit 5 is zero for "7 bits" 3451 1318 C2 1C 13 jnz skip_bit_adj ; display if we've got right answer already 3452 131B 04 inr b ; change to '2' for "7 bits" 3453 131C 78 skip_bit_adj: mov a,b 3454 131D CD B9 0D call rep_char_semi ; <nbits< 3455 1320 3A AB 21 lda tx_spd 3456 1323 0F rrc 3457 1324 CD 7A 0D call ascii_decimal ; <xspeed< 3458 1327 CD BB 0D call report_semi 3459 132A 3A AC 21 lda rx_spd 3460 132D 0F rrc 3461 132E CD 7A 0D call ascii_decimal ; <rspeed< 3462 1331 CD BB 0D call report_semi 3463 1334 3E 31 mvi a,'1' ; <clkmul< is a constant 3464 1336 CD B9 0D call rep_char_semi 3465 1339 3A AA 21 lda setup_b5 ; report STP switch values 3466 133C E6 F0 ani 0f0h ; as with all other setup blocks, only top 4 bits 3467 133E 0F rrc 3468 133F 0F rrc 3469 1340 0F rrc 3470 1341 0F rrc ; which we shift down to report as number 0 to 15 3471 1342 CD 7A 0D call ascii_decimal 3472 1345 36 F8 mvi m,'x'|80h ; 'x' for DECREPTPARM sequence final 3473 1347 C3 F3 0E jmp send_report 3474 ; 3475 ; DECSWL - single-width-line 3476 line_attr_swl: 3477 134A 21 57 21 lxi h,curr_line_dbl 3478 134D 7E mov a,m 3479 134E B7 ora a 3480 134F C8 rz ; Nothing to do if current line is already single width 3481 1350 CD 91 11 call wait_for_x 3482 1353 21 13 21 lxi h,latofs 3483 1356 3A F9 20 lda curs_row ; Retrieve logical line number entry 3484 1359 85 add l 3485 135A 6F mov l,a 3486 135B 7E mov a,m 3487 135C E6 7F ani 7fh ; Zero bit 7, the double width indicator 3488 135E 77 mov m,a ; place back 3489 135F CD 8D 13 call curs_phl_addr ; HL <- screen address of cursor row 3490 1362 3A 50 20 lda screen_cols 3491 1365 0F rrc 3492 1366 47 mov b,a ; B <- half number of columns 3493 1367 CD DE 13 call add_a_to_hl ; Move halfway across line, to where the "half" terminator is 3494 136A AF xra a 3495 136B 77 zap_2nd_half: mov m,a ; Remove terminator, DMA address and all following characters 3496 136C 23 inx h 3497 136D 05 dcr b 3498 136E C2 6B 13 jnz zap_2nd_half 3499 1371 3E F0 mvi a,0f0h 3500 1373 32 0E 21 sta saved_curs_row 3501 1376 CD 95 13 call add_line_attrs 3502 1379 AF xra a 3503 137A 32 57 21 sta curr_line_dbl 3504 137D C3 36 16 jmp move_updates 3505 ; 3506 ; curs_phl_loc 3507 1380 3A F9 20 curs_phl_loc: lda curs_row 3508 1383 CD E6 13 call phl_num 3509 ; phl_loc 3510 ; Given a physical line number in A, look up the address of this line 3511 ; in an address table (doubling and adding, so we're returning a proper 3512 ; address in HL). 3513 1386 21 C2 20 phl_loc: lxi h,pline_addr 3514 1389 87 add a 3515 138A 85 add l ; [* fragile *] pline_addr table must not go over 8-bit boundary 3516 138B 6F mov l,a 3517 138C C9 ret 3518 ; 3519 ; curs_phl_addr 3520 ; 3521 ; Get the address of the physical line in screen RAM of the cursor row 3522 ; Returns this in HL. 3523 ; 3524 138D CD 80 13 curs_phl_addr: call curs_phl_loc 3525 ld_hl_from_hl: 3526 1390 7E mov a,m 3527 1391 23 inx h 3528 1392 66 mov h,m 3529 1393 6F mov l,a 3530 1394 C9 ret 3531 ; 3532 ; add_line_attrs 3533 ; 3534 ; Apply the line attributes in A to the current (cursor) row. This involves finding the start address 3535 ; of the previous line and marching across to its terminating bytes, because they determine the attributes 3536 ; for this line, as well as the address. Two wrinkles: 3537 ; 3538 ; 1) If this is the first line, then we have to modify the attributes as stored in the initial 3539 ; screen layout (at address line1_dma) 3540 ; 2) LATOFS stores the physical line number with bit 7 high if that line (the previous one, in our case) 3541 ; is double-wdith. In that case, the terminators have been placed half across the line, instead of 3542 ; screen width columns across. 3543 ; 3544 1395 E6 70 add_line_attrs: ani 70h ; Censor attributes (don't affect scrolling region, for example) 3545 1397 47 mov b,a 3546 1398 3A F9 20 lda curs_row ; It's the terminating bytes for the previous line that decides 3547 139B 3D dcr a ; the attributes (and address) for this one 3548 139C 21 04 20 lxi h,line1_dma ; Set up for initial line address if we were at row 0 anyway 3549 139F FA BD 13 jm set_attrs ; yep, we were 3550 13A2 CD E6 13 call phl_num ; As we weren't at beginning, grab physical line number from LATOFS 3551 13A5 57 mov d,a ; D <- physical line number 3552 13A6 E6 7F ani 7fh ; Get rid of any double line marker 3553 13A8 CD 86 13 call phl_loc ; HL <- address in physical line table 3554 13AB CD 90 13 call ld_hl_from_hl ; HL <- start of line in screen RAM 3555 13AE 3A 50 20 lda screen_cols 3556 13B1 0F rrc ; A <- screen width / 2 3557 13B2 5F mov e,a ; E <- screen width / 2 3558 13B3 23 inx h ; 3559 13B4 14 inr d ; 3560 13B5 15 dcr d ; set flags for phys line number from LATOFS (i.e. was it doubled?) 3561 13B6 16 00 mvi d,0 ; Set up DE to be offset of half-way across line 3562 13B8 19 dad d 3563 13B9 FC BD 13 cm set_attrs ; If this line was double-width, do middle first (CALL) 3564 13BC 19 dad d ; and then (or just) the end of line 3565 13BD 7E set_attrs: mov a,m ; A <- first (high) byte of DMA address + attrs 3566 13BE E6 8F ani 8fh ; Remove line attrs, keeping scroll flags + address 3567 13C0 B0 ora b ; Place new line attributes 3568 13C1 77 mov m,a ; Store new high byte of DMA address 3569 13C2 C9 ret 3570 ; 3571 ; On entry (from double_line at least), HL is the screen address of start of current line 3572 ; This routine halves the length of the line by moving this line's terminator and next screen address to half way. 3573 13C3 3A 50 20 halve_line: lda screen_cols 3574 13C6 0F rrc ; A <- screen width / 2 3575 13C7 54 mov d,h ; 3576 13C8 5D mov e,l ; DE <- start of current line 3577 13C9 CD DE 13 call add_a_to_hl ; HL is half way across line 3578 13CC EB xchg ; DE <- half way, HL <- start 3579 13CD CD DB 13 call add_cols_to_hl ; HL <- addr of right margin + 1 (terminator) 3580 13D0 06 03 mvi b,3 3581 13D2 7E end_byte_loop: mov a,m ; get post-EOL bytes (terminator first, then two screen addresses) 3582 13D3 12 stax d ; place half way 3583 13D4 13 inx d 3584 13D5 23 inx h 3585 13D6 05 dcr b 3586 13D7 C2 D2 13 jnz end_byte_loop 3587 13DA C9 ret 3588 ; 3589 13DB 3A 50 20 add_cols_to_hl: lda screen_cols 3590 13DE 85 add_a_to_hl: add l 3591 13DF 6F mov l,a 3592 13E0 D0 rnc 3593 13E1 24 inr h 3594 13E2 C9 ret 3595 ; 3596 ; curs_phl_num 3597 ; Return physical line number of cursor row in A 3598 ; phl_num 3599 ; Return physical line number of logical row A, in A 3600 ; 3601 13E3 3A F9 20 curs_phl_num: lda curs_row 3602 ; 3603 13E6 21 13 21 phl_num: lxi h,latofs 3604 13E9 85 add l 3605 13EA 6F mov l,a 3606 13EB 7E mov a,m 3607 13EC C9 ret 3608 ; 3609 ; RM - reset modes 3610 13ED 0E 00 rm_action: mvi c,0 3611 13EF C3 F4 13 jmp do_modes 3612 ; 3613 ; SM - set modes 3614 13F2 0E FF sm_action: mvi c,0ffh 3615 ; 3616 ; At this point, we are either setting (C = 0ffh) or resetting (C = 0) modes. The mode number is in B. 3617 13F4 3A B8 21 do_modes: lda csi_private 3618 13F7 B7 ora a 3619 13F8 21 26 14 lxi h,ansi_mode_t ; We're dealing with ANSI modes 3620 13FB CA 04 14 jz mode_lookup ; if there isn't a private character 3621 13FE FE 3F cpi '?' ; But if the private character isn't '?', quit 3622 1400 C0 rnz 3623 1401 21 0A 14 lxi h,dec_mode_t ; Look up DEC private modes 3624 1404 78 mode_lookup: mov a,b ; A <- mode number 3625 1405 41 mov b,c ; B <- set/reset 3626 1406 CD FF 09 call find_action_a 3627 1409 C9 ret 3628 ; JUMP TABLE 3629 140A 01 dec_mode_t: db 1 ; DECCKM - cursor key 3630 140B 2A 14 dw decckm_mode 3631 140D 02 db 2 ; DECANM - ANSI/VT52 3632 140E 2F 14 dw decanm_mode 3633 1410 03 db 3 ; DECCOLM - column 3634 1411 57 14 dw deccolm_mode 3635 1413 04 db 4 ; DECSCLM - scrolling 3636 1414 47 14 dw decsclm_mode 3637 1416 05 db 5 ; DECSCNM - screen 3638 1417 4E 14 dw decscnm_mode 3639 1419 06 db 6 ; DECOM - origin 3640 141A 3F 14 dw decom_mode 3641 141C 07 db 7 ; DECAWM - auto wrap 3642 141D 66 14 dw decawm_mode 3643 141F 08 db 8 ; DECARM - auto repeating 3644 1420 5F 14 dw decarm_mode 3645 1422 09 db 9 ; DECINLM - interlace 3646 1423 6D 14 dw decinlm_mode 3647 1425 00 db 0 ; end of table 3648 ; 3649 ; Only a single ANSI mode can be set/reset: LNM - line feed new line mode 3650 1426 14 ansi_mode_t: db 20 3651 1427 38 14 dw lnm_mode 3652 1429 00 db 0 ; end of table 3653 3654 142A 21 BC 21 decckm_mode: lxi h,mode_ckm 3655 142D 70 mov m,b 3656 142E C9 ret 3657 ; 3658 142F CD 76 14 decanm_mode: call apply_mask_sp 3659 1432 A7 21 dw setup_b2 3660 1434 20 db sb2_ansi 3661 1435 C3 F2 0B jmp set_charsets 3662 3663 1438 CD 76 14 lnm_mode: call apply_mask_sp 3664 143B A8 21 dw setup_b3 3665 143D 20 db sb3_newline 3666 143E C9 ret 3667 3668 ; decom_mode [* BUG *] 3669 ; This routine changes origin mode and sends the cursor to the home position. 3670 ; However, this routine contains a bug because it calls cursor_home, and that routine 3671 ; clears the first two CSI parameters before dropping into the general cursor positioning routine. 3672 ; 3673 ; That means that, if DECOM is the first mode in an RM or SM sequence, the second mode will be 3674 ; cleared by cursor_home and won't be executed. This can be shown by using a highly visible change 3675 ; for the second parameter, such as DECSCNM (screen mode - light or dark). 3676 ; 3677 ; For example: 3678 ; 1. Go into reverse screen mode: ESC [ ? 5 h 3679 ; 2. Go into normal screen mode: ESC [ ? 5 l 3680 ; 3. Set origin mode and reverse screen mode: ESC [ ? 6 ; 5 h 3681 ; 3682 ; The third sequence will completely ignore DECSCNM (private mode 5). However, third and 3683 ; subsequent modes will still be processed, so this works: 3684 ; 3685 ; 4. Set origin mode and reverse screen mode: ESC [ ? 6 ; ; 5 h 3686 ; 3687 143F 21 01 21 decom_mode: lxi h,origin_mode 3688 1442 70 mov m,b 3689 1443 CD 48 18 call cursor_home ; *BUG* (see cursor_home) 3690 1446 C9 ret 3691 ; 3692 1447 CD 76 14 decsclm_mode: call apply_mask_sp 3693 144A A6 21 dw setup_b1 3694 144C 80 db sb1_smooth 3695 144D C9 ret 3696 3697 144E CD 76 14 decscnm_mode: call apply_mask_sp 3698 1451 A6 21 dw setup_b1 3699 1453 20 db sb1_lightback 3700 1454 C3 6B 03 jmp update_dc012 3701 3702 1457 21 A2 21 deccolm_mode: lxi h,columns_132 3703 145A 70 mov m,b 3704 145B CD 81 03 call clear_display 3705 145E C9 ret 3706 ; 3707 145F CD 76 14 decarm_mode: call apply_mask_sp 3708 1462 A6 21 dw setup_b1 3709 1464 40 db sb1_autorep 3710 1465 C9 ret 3711 3712 1466 CD 76 14 decawm_mode: call apply_mask_sp 3713 1469 A8 21 dw setup_b3 3714 146B 40 db sb3_autowrap 3715 146C C9 ret 3716 3717 146D CD 76 14 decinlm_mode: call apply_mask_sp 3718 1470 A8 21 dw setup_b3 3719 1472 10 db sb3_interlace 3720 1473 C3 42 03 jmp update_dc011 3721 3722 ; apply_mask_sp 3723 ; The stacked return address is the address of a setup switch block and the following byte 3724 ; is a bit mask that must either be set (B = 0ffh) or reset (B = 0). 3725 ; 3726 1476 E1 apply_mask_sp: pop h ; HL <- return address 3727 1477 5E mov e,m ; E <- byte after 3728 1478 23 inx h ; 3729 1479 56 mov d,m ; D <- byte after that 3730 147A 23 inx h ; 3731 147B 7E mov a,m ; A <- mask value 3732 147C 23 inx h ; increment hl again, to make new return address 3733 147D EB xchg ; DE <--< HL (DE is now return address) 3734 147E 4F mov c,a ; C <- mask value 3735 147F 2F cma ; A <- all the other bits set 3736 1480 A6 ana m ; clear our bit in location 3737 1481 77 mov m,a ; and write it back 3738 1482 78 mov a,b ; get all ones if we'd like to set, or all zeros to reset 3739 1483 A1 ana c ; either mask for set, or zero for reset 3740 1484 B6 ora m ; (we already reset the mode, so now possibly set it) 3741 1485 77 mov m,a ; apply to location 3742 1486 EB xchg ; HL <- return address again 3743 1487 E9 pchl ; return 3744 ; 3745 1488 CD 93 14 keyboard_tick: call update_kbd 3746 148B E6 10 ani 10h ; Probably intended to prevent processing when keyboard locked, 3747 148D CC AA 06 cz process_keys ; but see notes below. 3748 1490 C3 59 0E jmp try_report 3749 ; 3750 ; If the keyboard is ready to receive and isn't locked, make up the status byte to transmit 3751 ; to it. Then do the cursor timer housekeeping. 3752 ; Return value: 3753 ; If keyboard is not ready to receive, A is 0 3754 ; If UNWRIT_X2077 had a purpose, that value would be returned in A 3755 ; If smooth scrolling or pending scroll, A will be 1 or -1 (direction of scroll) 3756 ; Otherwise it will be the OR of the high and low bytes of the cursor timer. 3757 ; 3758 ; Because the keyboard locked flag in the keyboard status is 10h, it feels as if this routine 3759 ; was originally intended to return when the keyboard was locked, which would naturally mean 3760 ; that there was point calling process_keys. 3761 ; 3762 1493 DB 42 update_kbd: in ior_flags 3763 1495 E6 80 ani iob_flags_kbd 3764 1497 C8 rz ; Keyboard not ready to receive 3765 1498 21 44 21 lxi h,keyboard_locked 3766 149B 7E mov a,m 3767 149C B7 ora a 3768 149D CA A2 14 jz kbd_not_locked 3769 14A0 3E 10 mvi a,iow_kbd_locked 3770 14A2 21 A5 21 kbd_not_locked: lxi h,local_mode 3771 14A5 B6 ora m ; Local mode is 20h, to set LOCAL LED 3772 14A6 21 45 21 lxi h,led_state ; Make up a keyboard command from all status bytes 3773 14A9 B6 ora m 3774 14AA 23 inx h ; HL <- kbd_online_mask (can also affect click if bell is sounding) 3775 14AB B6 ora m 3776 14AC 23 inx h ; HL <- kbd_click_mask 3777 14AD B6 ora m 3778 14AE 36 00 mvi m,0 ; zero "click" after use: kbd_click_mask <- 0 3779 14B0 23 inx h ; HL <- kbd_scan_mask 3780 14B1 B6 ora m 3781 14B2 36 00 mvi m,0 ; zero "start scan" after use: kbd_scan_mask <- 0 3782 14B4 D3 82 out iow_keyboard 3783 14B6 21 74 20 lxi h,num_kbd_updates 3784 14B9 34 inr m 3785 14BA 3A 77 20 lda UNWRIT_X2077 3786 14BD B7 ora a 3787 14BE C0 rnz 3788 14BF 3A 65 20 lda smooth_scroll ; are we in middle of smooth scroll? 3789 14C2 21 51 20 lxi h,scroll_pending; or are we about to start one? 3790 14C5 B6 ora m 3791 14C6 C0 rnz ; exit if either scroll condition is true 3792 14C7 2A 2D 21 lhld cursor_timer ; Maintain cursor timer 3793 14CA 2B dcx h 3794 14CB 7C mov a,h 3795 14CC B5 ora l 3796 14CD CA D4 14 jz curs_timer_up 3797 14D0 22 2D 21 shld cursor_timer 3798 14D3 C9 ret 3799 ; 3800 ; Cursor time has counted down to zero 3801 14D4 3A BA 21 curs_timer_up: lda cursor_visible ; toggle cursor visibility 3802 14D7 EE FF xri 0ffh 3803 14D9 32 BA 21 sta cursor_visible 3804 14DC 21 12 02 lxi h,0212h ; Cursor will be on for about 2/3 second 3805 14DF C2 E5 14 jnz curs_was_off 3806 14E2 21 09 01 lxi h,0109h ; Cursor will be off for about 1/3 second 3807 14E5 22 2D 21 curs_was_off: shld cursor_timer ; Set new timer 3808 14E8 2A F6 20 lhld cursor_address 3809 14EB 46 mov b,m ; B <- current char at cursor position 3810 14EC 3A 59 21 lda curs_char_rend ; A <- 0x80 or 0, depending on whether we're using base rendition 3811 14EF A8 xra b ; Flip bit, if bit fit to flip 3812 14F0 77 mov m,a ; Place back on screen 3813 14F1 11 00 10 lxi d,1000h ; Make an address in attribute RAM 3814 14F4 19 dad d 3815 14F5 3A 5A 21 lda curs_attr_rend ; Again, flip cursor rendition 3816 14F8 AE xra m 3817 14F9 77 mov m,a ; and replace 3818 14FA C9 ret 3819 ; 3820 14FB 74 db 74h ; CHECKSUM 3821 ; 3822 14FC 78 el_action: mov a,b ; A <- selective parameter 3823 14FD B7 ora a 3824 14FE CA 15 15 jz el_to_end ; default is "erase from cursor to end of line" 3825 1501 3D dcr a 3826 1502 CA 0A 15 jz el_to_start ; Ps = 1 means "beginning of line to cursor" 3827 1505 3D dcr a 3828 1506 C0 rnz ; No options beyond Ps = 2 3829 1507 CD 15 15 call el_to_end ; Ps = 2 means "erase whole line" 3830 150A 3A F8 20 el_to_start: lda curs_col 3831 150D 47 mov b,a 3832 150E 04 inr b ; number of locations is cursor column (0-based) + 1 3833 150F 2A 4E 21 lhld start_line_addr ; from the start of the line 3834 1512 C3 29 15 jmp blank_chars 3835 ; 3836 1515 3A F8 20 el_to_end: lda curs_col 3837 1518 47 mov b,a 3838 1519 3A 57 21 lda curr_line_dbl 3839 151C B7 ora a ; NZ flag if current line is double width 3840 151D 3A 50 20 lda screen_cols 3841 1520 CA 24 15 jz not_double 3842 1523 0F rrc ; divide columns by 2, on double width line 3843 1524 90 not_double: sub b 3844 1525 47 mov b,a ; B <- screen columns - cursor column 3845 1526 2A F6 20 lhld cursor_address 3846 ; 3847 ; blank the number of characters in B, from the screen RAM position in HL 3848 ; 3849 1529 7C blank_chars: mov a,h ; Convert screen address in HL 3850 152A C6 10 adi 10h ; to attribute RAM address in DE 3851 152C 57 mov d,a 3852 152D 5D mov e,l 3853 152E 3E FF mvi a,0ffh ; default rendition 3854 1530 12 blank_loop: stax d ; blank attributes 3855 1531 36 00 mvi m,0 ; blank screen 3856 1533 23 inx h ; next addresses in screen RAM 3857 1534 13 inx d ; and attribute RAM 3858 1535 05 dcr b 3859 1536 C2 30 15 jnz blank_loop 3860 1539 32 F5 20 sta rend_und_curs ; default rendition is 0ffh 3861 153C AF xra a 3862 153D 32 F4 20 sta char_und_curs ; and character under cursor is blank, 00h 3863 1540 C9 ret 3864 ; 3865 ; ed_action 3866 ; 3867 ; Implements the ED (erase in display) control sequence, which has a single selective parameter 3868 ; 3869 ; Lines that are wholly erased are made single width. The cursor line's width attribute is only 3870 ; changed if the cursor is in column 0 when "erase to end of screen" is invoked, or the entire 3871 ; display is erased. 3872 ; 3873 1541 78 ed_action: mov a,b ; A <- selective parameter 3874 1542 B7 ora a 3875 1543 CA 6A 15 jz ed_to_end ; Ps = null/0 is "erase from cursor to end of display" 3876 1546 3D dcr a 3877 1547 CA 55 15 jz ed_to_start ; Ps = 1 is "erase from start of screen to cursor" 3878 154A 3D dcr a 3879 154B C0 rnz ; No options beyond Ps = 2, which is "erase entire display" 3880 154C CD 55 15 call ed_to_start 3881 154F CD 6A 15 call ed_to_end 3882 1552 C3 4A 13 jmp line_attr_swl 3883 ; 3884 ; 3885 1555 21 F9 20 ed_to_start: lxi h,curs_row 3886 1558 7E mov a,m ; A <- cursor row 3887 1559 F5 push psw 3888 155A 47 mov b,a ; B <- cursor row 3889 155B AF xra a ; 3890 155C 77 mov m,a ; zero cursor row, for the sake of erase_n_lines 3891 155D 0E FF mvi c,-1 ; Pretend cursor row is -1 3892 155F CD 86 15 call erase_n_lines 3893 1562 F1 pop psw 3894 1563 77 mov m,a ; Now restore proper cursor row 3895 1564 CD 74 10 call curs_line_addr ; HL <- start of cursor line in screen RAM 3896 1567 C3 0A 15 jmp el_to_start ; this line does not have line attributes changed 3897 ; 3898 156A 3A F8 20 ed_to_end: lda curs_col 3899 156D B7 ora a ; If the cursor is at the start of the line, the line is made 3900 156E CC 4A 13 cz line_attr_swl ; single width, otherwise not 3901 1571 CD 15 15 call el_to_end 3902 1574 21 F9 20 lxi h,curs_row 3903 1577 7E mov a,m ; A <- cursor row (for restoration later) 3904 1578 F5 push psw 3905 1579 4F mov c,a ; C <- cursor row 3906 157A 3E 17 mvi a,23 3907 157C 91 sub c ; A <- number of complete lines to be erased 3908 157D 47 mov b,a 3909 157E CD 86 15 call erase_n_lines 3910 1581 F1 pop psw 3911 1582 77 mov m,a ; restore original cursor row 3912 1583 C3 74 10 jmp curs_line_addr ; correct relevant pointers 3913 ; 3914 ; erase_n_lines 3915 ; On entry: HL points to curs_row 3916 ; B is number of lines to erase (this may be zero) 3917 ; C is one less than the row number of the first line to be *wholly* erased 3918 1586 05 erase_n_lines: dcr b 3919 1587 F8 rm ; done erasing whole lines 3920 1588 0C inr c ; increment row number 3921 1589 71 mov m,c ; write cursor row 3922 158A C5 push b 3923 158B E5 push h 3924 158C CD E3 13 call curs_phl_num ; A <- physical line number of cursor row, HL <- LATOFS entry 3925 158F E6 7F ani 7fh ; ignore any double-width marker 3926 1591 77 mov m,a ; Erased lines become single width 3927 1592 3E 70 mvi a,70h ; single width line attributes 3928 1594 CD 95 13 call add_line_attrs 3929 1597 CD 74 10 call curs_line_addr ; HL <- start of line in screen RAM 3930 159A 3A 50 20 lda screen_cols 3931 159D 47 mov b,a 3932 159E CD 29 15 call blank_chars 3933 15A1 E1 pop h 3934 15A2 C1 pop b 3935 15A3 C3 86 15 jmp erase_n_lines 3936 ; 3937 ; Set top and bottom margins 3938 15A6 0E 81 stbm_action: mvi c,81h 3939 15A8 CD 8E 10 call wait_scroll 3940 15AB CD BF 11 call last_row ; B <- last row number 3941 15AE 3A 30 21 lda csi_p1 ; A <- first parameter (top margin) 3942 15B1 B7 ora a 3943 15B2 CA B6 15 jz p1_def ; If the first parameter is not zero (or zero by default) 3944 15B5 3D dcr a ; reduce by one for internal numbering 3945 15B6 57 p1_def: mov d,a ; D <- top margin 3946 15B7 3A 31 21 lda csi_p2 ; A <- second parameter (bottom margin) 3947 15BA B7 ora a ; Is the bottom margin defaulted, or zero? 3948 15BB C2 C0 15 jnz supp_b ; No, use it 3949 15BE 78 mov a,b ; else make it the bottom of the screen 3950 15BF 3C inr a 3951 15C0 3D supp_b: dcr a 3952 15C1 5F mov e,a ; E <- bottom margin 3953 15C2 78 mov a,b ; A <- last row 3954 15C3 BB cmp e ; If last row < bottom margin 3955 15C4 D8 rc ; don't do anything 3956 15C5 7A mov a,d ; A <- top margin 3957 15C6 BB cmp e ; If top margin < bottom margin 3958 15C7 D0 rnc ; don't do anything 3959 15C8 21 55 21 lxi h,top_margin 3960 15CB 72 mov m,d ; store top margin 3961 15CC 23 inx h 3962 15CD 73 mov m,e ; store bottom margin 3963 15CE 4B mov c,e ; C <- bottom margin 3964 15CF 42 mov b,d ; B <- top margin 3965 15D0 79 mov a,c 3966 15D1 90 sub b 3967 15D2 3C inr a ; A <- bottom - top + 1 3968 15D3 5F mov e,a ; E <- lines in scrolling region 3969 15D4 21 04 20 lxi h,line1_dma 3970 15D7 78 mov a,b ; A <- top margin 3971 15D8 B7 ora a 3972 15D9 CA E8 15 jz n_scroll_reg ; jump if top margin is top row 3973 15DC 50 mov d,b ; D <- top margin 3974 15DD 7E top_mar_loop: mov a,m ; A <- high byte of DMA address 3975 15DE E6 7F ani 7fh ; remove scroll region flag 3976 15E0 77 mov m,a ; put back 3977 15E1 CD 2B 16 call advance_line 3978 15E4 15 dcr d 3979 15E5 C2 DD 15 jnz top_mar_loop ; follow line links until we're at first line of new scroll region 3980 15E8 7E n_scroll_reg: mov a,m ; A <- high byte of DMA address 3981 15E9 F6 80 ori 80h ; add scroll region flag 3982 15EB 77 mov m,a ; put back 3983 15EC CD 2B 16 call advance_line 3984 15EF 1D dcr e 3985 15F0 C2 E8 15 jnz n_scroll_reg 3986 15F3 3E 17 mvi a,23 3987 15F5 91 sub c ; A <- number of non-scrollable lines at bottom 3988 15F6 CA 05 16 jz done_lines 3989 15F9 57 mov d,a ; D <- line count 3990 15FA 7E bot_mar_loop: mov a,m ; A <- high byte of DMA address 3991 15FB E6 7F ani 7fh ; remove scroll region flag 3992 15FD 77 mov m,a ; put back 3993 15FE CD 2B 16 call advance_line 3994 1601 15 dcr d 3995 1602 C2 FA 15 jnz bot_mar_loop 3996 1605 78 done_lines: mov a,b ; A <- top margin 3997 1606 32 55 21 sta top_margin ; store 3998 1609 79 mov a,c ; A <- bottom margin 3999 160A 32 56 21 sta bottom_margin ; store 4000 160D 21 13 21 lxi h,latofs ; Now work our way through the logical address offset table, 4001 1610 0E 18 mvi c,24 ; halving each line again, if appropriate. This way, we grab 4002 ; the terminating line attributes at the full length of each line, 4003 ; which we've just modified, to re-copy them into the middle of each 4004 ; double-width line. 4005 1612 7E half_line_loop: mov a,m ; Grab physical number of logical line 4006 1613 B7 ora a ; 4007 1614 F2 23 16 jp skip_single ; if it isn't double width, skip it 4008 1617 E5 push h 4009 1618 CD 86 13 call phl_loc ; HL <- addresss of pline_addr entry for this line 4010 161B 7E mov a,m 4011 161C 23 inx h 4012 161D 66 mov h,m 4013 161E 6F mov l,a ; HL <- start of screen RAM for line 4014 161F CD C3 13 call halve_line 4015 1622 E1 pop h 4016 1623 23 skip_single: inx h 4017 1624 0D dcr c 4018 1625 C2 12 16 jnz half_line_loop 4019 1628 C3 48 18 jmp cursor_home 4020 ; 4021 ; advance_line 4022 ; On entry: HL points to high byte of DMA address, A contains that byte 4023 ; On exit: HL points to high byte of next DMA address 4024 ; 4025 162B 23 advance_line: inx h ; HL points to low byte of DMA address 4026 162C 6E mov l,m ; L <- low byte 4027 162D E6 0F ani 0fh ; A <- high byte already, which 4028 162F F6 20 ori 20h ; we convert into proper address (i.e. discard line attributes) 4029 1631 67 mov h,a ; HL <- points to first character of next line 4030 1632 23 inx h ; and increment 4031 1633 C3 DB 13 jmp add_cols_to_hl ; so on return HL <- high byte of next DMA address 4032 ; 4033 ; move_updates 4034 ; This routine is called any time that cursor row or column have been updated. This has a lot of work to do: 4035 ; 1. Place the old character and rendition where the cursor was 4036 ; 2. Possibly adjusting cursor position if we've moved to a double-width line 4037 ; 3. Triggering margin bell if this movement was caused by receiving printable characters 4038 ; 4. Saving the character and rendition of the new cursor position. 4039 ; 4040 1636 21 0D 00 move_updates: lxi h,0dh ; set a tiny cursor timer 4041 1639 22 2D 21 shld cursor_timer 4042 163C AF xra a ; and record cursor visibility as off 4043 163D 32 BA 21 sta cursor_visible ; so it will come back quickly after movement 4044 1640 2A F6 20 lhld cursor_address 4045 1643 3A F4 20 lda char_und_curs 4046 1646 47 mov b,a ; B <- char under cursor 4047 1647 77 mov m,a ; place char back on screen 4048 1648 7C mov a,h ; move HL to point to attribute RAM 4049 1649 C6 10 adi 10h 4050 164B 67 mov h,a 4051 164C 3A F5 20 lda rend_und_curs 4052 164F 77 mov m,a ; place old rendition 4053 1650 3A 50 20 lda screen_cols 4054 1653 3D dcr a 4055 1654 5F mov e,a ; E <- last column number 4056 1655 3A 7B 20 lda in_setup 4057 1658 B7 ora a 4058 1659 C2 7B 16 jnz skip_half_col ; In setup, can't have double-width cursor line 4059 165C 3A F9 20 lda curs_row 4060 165F 21 0E 21 lxi h,saved_curs_row 4061 1662 BE cmp m 4062 1663 CA 7F 16 jz same_row 4063 1666 77 mov m,a ; save the new row 4064 1667 CD 74 10 call curs_line_addr 4065 166A AF xra a ; We didn't get here by typing, so clear the flag 4066 166B 32 54 21 sta margin_bell ; that allows the margin bell to be triggered by movement 4067 166E 21 57 21 lxi h,curr_line_dbl ; Is current line double width? 4068 1671 7E mov a,m 4069 1672 B7 ora a 4070 1673 CA 7B 16 jz skip_half_col ; No, so last column number is correct 4071 1676 7B mov a,e ; Double-width, so halve last column number (e.g. 79 -< 39) 4072 1677 3D dcr a 4073 1678 B7 ora a ; clear carry for the rotate 4074 1679 1F rar 4075 167A 5F mov e,a 4076 167B 7B skip_half_col: mov a,e ; A <- last column number (adjusted, if necessary, for double width) 4077 167C 32 53 21 sta right_margin 4078 167F 21 53 21 same_row: lxi h,right_margin ; test if cursor has moved beyond right margin 4079 1682 11 F8 20 lxi d,curs_col 4080 1685 1A ldax d ; A <- cursor column 4081 1686 BE cmp m 4082 1687 DA 8C 16 jc less_rmargin ; jump if less than margin 4083 168A 7E mov a,m ; otherwise place margin 4084 168B 12 stax d ; into current column 4085 168C 3A BB 21 less_rmargin: lda last_curs_col 4086 168F C6 08 adi 8 ; Margin bell is triggered exactly 8 columns from right edge 4087 1691 96 sub m 4088 1692 C2 A1 16 jnz no_margin_bell 4089 1695 23 inx h ; HL <- margin_bell 4090 1696 B6 ora m ; A was zero, so now NZ if we want a margin bell 4091 1697 CA A1 16 jz no_margin_bell 4092 169A CD 38 09 call c0_bell 4093 169D AF xra a ; now don't permit another margin bell until typed char 4094 169E 32 54 21 sta margin_bell ; sets this flag again (see process_keys) 4095 16A1 3A F8 20 no_margin_bell: lda curs_col 4096 16A4 32 BB 21 sta last_curs_col 4097 16A7 2A 4E 21 lhld start_line_addr 4098 16AA CD DE 13 call add_a_to_hl 4099 16AD 22 F6 20 shld cursor_address ; Make new cursor address 4100 16B0 7E mov a,m ; Grab character "under" cursor 4101 16B1 32 F4 20 sta char_und_curs ; store it until cursor moves 4102 16B4 47 mov b,a ; B <- new character under cursor 4103 16B5 7C mov a,h ; move HL to point to attribute RAM 4104 16B6 C6 10 adi 10h 4105 16B8 67 mov h,a 4106 16B9 7E mov a,m ; A <- rendition "under" cursor 4107 16BA 32 F5 20 sta rend_und_curs ; store it until cursor moves 4108 16BD 2A F6 20 lhld cursor_address ; HL <- new cursor address 4109 16C0 70 mov m,b ; place new/old character back 4110 16C1 C9 ret 4111 ; 4112 ; Entry point for CSI state, initialising parameters, private flag, intermediate 4113 ; and final characters 4114 16C2 47 start_csi: mov b,a ; B <- received char while we initialise state 4115 16C3 21 F1 16 lxi h,gather_params ; private flag and params are first part of control sequence 4116 16C6 22 40 21 shld char_action ; set up processing for next character 4117 16C9 AF xra a 4118 16CA 32 2F 21 sta param_value ; zero out csi_param state 4119 16CD 32 4B 21 sta num_params 4120 16D0 32 7D 20 sta inter_chars 4121 ; zero out the params array 4122 16D3 21 30 21 lxi h,csi_params 4123 16D6 0E 0F mvi c,0fh 4124 16D8 AF xra a 4125 16D9 77 zero_csi: mov m,a ; zero this parameter 4126 16DA 23 inx h 4127 16DB 0D dcr c 4128 16DC C2 D9 16 jnz zero_csi 4129 ; 4130 16DF 21 B8 21 lxi h,csi_private ; 4131 16E2 36 00 mvi m,0 ; mark that we haven't seen a private character 4132 16E4 78 mov a,b ; A <- received char 4133 16E5 FE 40 cpi '@' ; is it a final character? 4134 16E7 D2 F2 16 jnc gather2 ; yes 4135 16EA FE 3C cpi '<' ; less than flag character range? 4136 16EC DA F2 16 jc gather2 ; yes, start building parameter value 4137 16EF 77 mov m,a ; store flag character 4138 16F0 C9 ret 4139 ; 4140 ; This is the entry point for all subsequent characters while we're in this state. 4141 ; 4142 ; This section has to deal with parameter values, which are sequences of ASCII digits '0' to '9', 4143 ; separated with ';' and recognising intermediate and final characters. Intermediates are from 4144 ; SP (20h) to '/' (2fh), Finals are from '@' (40h) to '~' (7eh) and most other characters will 4145 ; invalidate the sequence. Because the VT100 doesn't support any sequences with intermediates, 4146 ; it gathers them, along with other invalid characters, and only disposes of them when a Final 4147 ; character has been found and the sequence is executed. 4148 ; 4149 ; This section has a bug at the marked line. Any character other than a digit is supposed to be 4150 ; passed through to finish_param, which puts aside the current character while the gathered current 4151 ; numeric parameter (param_value) is placed at the end of the list (csi_params). Then, the current 4152 ; character is restored from the stack and we drop through to detect_i_f, to determine what to do 4153 ; with the current character. Characters above the digit range, like the semicolon or final characters, 4154 ; will be dealt with correctly here, but characters below digit range (intermediates) have already 4155 ; been corrupted by having 30h subtracted from their value, leaving them in the range 0f0h to 0ffh, 4156 ; and detect_i_f will incorrectly treat them as final characters, leading to immediate execution of 4157 ; the sequence, with a final character in this high range, which fails to match any of the tables. 4158 ; 4159 ; A valid but unsupported sequence like: ESC [ 9 ! p 4160 ; will be executed by the VT100 on reaching the '!', leaving the parser in "ground" state, which 4161 ; means that the final character "p" will be displayed on the screen. 4162 ; 4163 ; With this bug, the bytes from the label not_final to the next "ret" (17 bytes) are all unreachable. 4164 ; 4165 ; If the buggy line is replaced by: 4166 ; 4167 ; cmp '0' 4168 ; 4169 ; then all the rest works as intended. 4170 ; 4171 16F1 47 gather_params: mov b,a 4172 16F2 78 gather2: mov a,b 4173 16F3 FE 3A cpi '9'+1 ; above digit range? 4174 16F5 D2 17 17 jnc finish_param ; then we've finished with this parameter (at least) 4175 16F8 D6 30 sui '0' ; convert to actual digit value [* BUG *] 4176 16FA FA 17 17 jm finish_param ; might have hit an intermediate, or something invalid 4177 16FD 4F mov c,a ; C <- value of this digit 4178 16FE 21 2F 21 lxi h,param_value 4179 1701 7E mov a,m ; A <- current param value 4180 1702 FE 1A cpi 1ah ; if the current param value <= 26, multiplying by 4181 ; 10 would send us out of range, 4182 1704 D2 13 17 jnc par_range ; so limit it to 255 4183 ; otherwise, multiply current value by 10 4184 1707 07 rlc ; A <- param * 2 4185 1708 47 mov b,a ; B <- param * 2 4186 1709 07 rlc ; A <- param * 4 4187 170A 07 rlc ; A <- param * 8 4188 170B 80 add b ; A <- param * 8 + param * 2 (= param * 10) 4189 170C DA 13 17 jc par_range ; don't think this could be OOR now? 4190 170F 81 add c ; A <- param + current digit value 4191 1710 D2 15 17 jnc par_store ; but this could be OOR (e.g. 259) 4192 1713 3E FF par_range: mvi a,0ffh ; out of range parameters are limited at 255 4193 1715 77 par_store: mov m,a ; store new param_value 4194 1716 C9 ret 4195 ; 4196 ; We've now collected a CSI parameter value in param_value (212fh) 4197 ; and we want to store it in the list of parameters for this sequence. 4198 ; We do this regardless of what we're going to do next, which might be to 4199 ; start a new parameter, invalidate the sequence, or execute it. 4200 ; 4201 1717 11 30 21 finish_param: lxi d,csi_params ; base of csi_params array 4202 171A F5 push psw ; push the character we're working on 4203 171B 21 4B 21 lxi h,num_params 4204 171E 4E mov c,m ; retrieve offset into array 4205 171F 06 00 mvi b,0 4206 1721 EB xchg ; HL <- base of array, DE <- num_params 4207 1722 09 dad b ; HL <- addr of next param to store 4208 1723 01 2F 21 lxi b,param_value 4209 1726 0A ldax b ; A <- new param value 4210 1727 77 mov m,a ; store in array 4211 1728 AF xra a 4212 1729 02 stax b ; now we've stowed parameter, zero current value 4213 172A 1A ldax d ; A <- num params (array offset) 4214 172B FE 0F cpi 0fh ; Limit ourselves to storing 16 params 4215 172D CA 32 17 jz no_more_params ; Have we just stored the 16th parameter? 4216 1730 3C inr a ; Still OK 4217 1731 12 stax d 4218 1732 F1 no_more_params: pop psw ; A <- character we're working on 4219 ; 4220 ; This is a control sequence state where we're only collecting intermediates and 4221 ; finals. This state also allows ';' parameter separators to appear, which would be 4222 ; illegal after intermediate characters. However, the VT100 doesn't support any 4223 ; sequences with intermediates, so this state can harmlessly use the next few lines 4224 ; as part of two different states. 4225 ; 4226 1733 47 detect_i_f: mov b,a ; B <- copy of original character 4227 1734 FE 3B cpi 03bh ; if it's a ';' separator, we're done for now 4228 1736 C8 rz 4229 1737 E6 C0 ani 0c0h ; Control sequence finals are 040h to 07fh 4230 1739 78 mov a,b 4231 173A CA 43 17 jz not_final ; not a final character 4232 173D 32 7E 20 sta final_char 4233 1740 C3 A2 0A jmp execute_seq 4234 ; 4235 ; At this stage, we know the current character didn't form part of a parameter value 4236 ; so, having tidied away the last parameter value, we now know that we're not 4237 ; dealing with a parameter separator or a final character. This could only be an 4238 ; intermediate or an illegal character (e.g. ':') or a character out of place, such 4239 ; as another private flag. We'll untangle these later but for now we'll add them 4240 ; into the intermediate store and change the action to collect the rest of the 4241 ; sequence until a final arrives, without storing further numeric parameters. 4242 ; 4243 1743 21 7D 20 not_final: lxi h,inter_chars ; Mix this character into the intermediate store, and we'll 4244 1746 86 add m ; untangle invalidity here when it's time to execute the sequence. 4245 1747 D2 4C 17 jnc inter_range_ok ; Mindful that we wouldn't want to wrap addition round to where 4246 174A 3E FF mvi a,0ffh ; we appear to have a legal intermediate again. 4247 174C 77 inter_range_ok: mov m,a ; Store our intermediate (or illegal mix) 4248 174D 21 33 17 lxi h,detect_i_f ; Only collect "intermediates" and detect finals from now on. 4249 1750 22 40 21 shld char_action 4250 1753 C9 ret 4251 ; 4252 ; store_nvr 4253 ; Store current settings back in NVR. The opposite of see recall_nvr. 4254 ; 4255 1754 16 00 store_nvr: mvi d,0 ; direction is write 4256 1756 06 01 mvi b,1 ; try once 4257 1758 C3 62 17 jmp settings_nvr 4258 ; 4259 175B CD C0 02 recall_nvr: call init_video_ram 4260 175E 06 0A mvi b,10 ; try 10 times 4261 1760 16 01 mvi d,1 ; direction is read 4262 ; 4263 1762 C5 settings_nvr: push b 4264 1763 D5 push d 4265 1764 CD BE 17 call print_wait ; "Wait" 4266 1767 D1 pop d 4267 1768 F3 di ; all NVR access is done with interrupts disabled 4268 1769 21 7B 21 lxi h,aback_buffer 4269 176C 1E 33 mvi e,33h ; Number of bytes in NVR, including checksum 4270 176E 0E 01 mvi c,1 ; C <- initial checksum 4271 1770 AF xra a 4272 1771 32 AE 21 rw_byte_loop: sta nvr_addr 4273 1774 79 mov a,c 4274 1775 32 AD 21 sta nvr_checksum 4275 1778 D5 push d 4276 1779 E5 push h 4277 177A 7A mov a,d ; A <- direction flag 4278 177B B7 ora a ; Flag Z if storing settings 4279 177C 7E mov a,m ; A <- settings byte 4280 177D 32 AF 21 sta nvr_data ; stow copy for working on 4281 1780 CC AE 18 cz write_nvr_byte 4282 1783 CD A3 18 call read_nvr_byte 4283 1786 E1 pop h 4284 1787 D1 pop d 4285 1788 3A AF 21 lda nvr_data ; A <- read back settings byte 4286 178B 1D dcr e 4287 178C CA 9E 17 jz finished_rw 4288 178F 77 mov m,a ; place settings byte in scratch RAM 4289 1790 3A AD 21 lda nvr_checksum ; get checksum 4290 1793 07 rlc ; rotate and exclusive-or this byte into it 4291 1794 AE xra m 4292 1795 4F mov c,a ; C <- checksum 4293 1796 23 inx h ; point to next settings byte 4294 1797 3A AE 21 lda nvr_addr ; increase NVR address 4295 179A 3C inr a 4296 179B C3 71 17 jmp rw_byte_loop 4297 ; 4298 179E BE finished_rw: cmp m ; compare final byte (A) with checksum 4299 179F C1 pop b 4300 17A0 0E 00 mvi c,0 ; return result 4301 17A2 CA AC 17 jz nvrchk_ok 4302 17A5 05 dcr b ; checksum wrong, so try again (on recall only) 4303 17A6 C2 62 17 jnz settings_nvr 4304 17A9 CD D0 17 call init_scratch ; Otherwise give up and put reasonable defaults in scratch 4305 17AC 79 nvrchk_ok: mov a,c 4306 17AD B7 ora a ; This routine returns Z flag for "ok, read NVR and checksum matched" 4307 17AE F5 push psw 4308 17AF 0E 40 mvi c,iob_flags_lba7 4309 17B1 3E 63 mvi a,99 ; last address in NVR 4310 17B3 CD 2B 19 call set_nvr_addr_a 4311 17B6 21 F2 D0 lxi h,main_video_be ; To match screen address main_video 4312 17B9 22 04 20 shld line1_dma ; Put normal screen back 4313 17BC F1 pop psw 4314 17BD C9 ret 4315 ; 4316 ; A tiny area of scratch RAM is used for the tiny screen definition used when 4317 ; NVR is being accessed and "Wait" is displayed. 4318 ; 4319 17BE 11 EC 17 print_wait: lxi d,wait_display 4320 17C1 06 07 mvi b,7 4321 17C3 21 CC 21 lxi h,wait_addr ; Copy "Wait" to video RAM, with terminators 4322 17C6 CD 8B 03 call memcopy ; that point to fill lines. 4323 17C9 21 71 CC lxi h,wait_addr_be 4324 17CC 22 04 20 shld line1_dma 4325 17CF C9 ret 4326 ; 4327 ; init_scratch 4328 ; Initialise all scratch locations from the answerback buffer up to rx_spd, inclusive 4329 ; Returns C = 1, for the benefit of recall_nvr, which wants to return non-zero 4330 ; if NVR couldn't be read (or checksum didn't match.) (see recall_nvr) 4331 ; 4332 17D0 21 7B 21 init_scratch: lxi h,aback_buffer 4333 17D3 06 27 mvi b,27h ; Answerback buffer + tab settings 4334 17D5 36 80 scribble_loop: mvi m,80h ; scribble (80h is "end of string" for answerback and tab-every-8) 4335 17D7 23 inx h 4336 17D8 05 dcr b 4337 17D9 C2 D5 17 jnz scribble_loop ; HL finishes pointing to columns_132 4338 17DC 11 F3 17 lxi d,scratch_defs 4339 17DF 06 0B mvi b,11 ; 11 locations to initialise 4340 17E1 CD 8B 03 call memcopy 4341 17E4 0E 01 mvi c,1 ; C <- 1 is for the benefit of recall_nvr 4342 17E6 3E 30 mvi a,30h 4343 17E8 32 78 20 sta bell_duration ; going to sound bell 4344 17EB C9 ret 4345 ; 4346 ; "Wait" is followed by terminator and address that leads back to the fill lines. 4347 ; 4348 17EC 57 61 69 74 wait_display: db 'Wait',7fh,70h,06h 7F 70 06 4349 4350 ; Initialisation values for scratch area from columns_132 to rx_spd, inclusive 4351 ; Note that failure of NVR leaves the terminal in VT52 mode rather than ANSI mode! 4352 ; 4353 17F3 00 scratch_defs: db 0 ; columns_132 <- 80 columns 4354 17F4 08 db 8 ; brightness <- three quarters brightness 4355 17F5 6E db 6eh ; pusart_mode <- 1 stop bit, no parity, 8 bits, 16x clock 4356 17F6 20 db 20h ; local_mode <- "local" 4357 17F7 D0 db 0d0h ; setup_b1 <- smooth scroll, autorepeat, dark background, block cursor 4358 17F8 50 db 50h ; setup_b2 <- no margin bell, keyclick on, VT52 mode, auto xon/off 4359 17F9 00 db 0 ; setup_b3 <- ASCII, no autowrap, no newline, no interlace 4360 17FA 20 db 20h ; setup_b4 <- no parity, 8 bits, power 60 Hz 4361 17FB 00 db 0 ; setup_b5 <- (don't care) 4362 17FC E0 db 0e0h ; tx_spd <- 9600 baud 4363 17FD E0 db 0e0h ; rx_spd <- 9600 baud 4364 ; 4365 ; CUU - cursor up n rows 4366 17FE 3A 55 21 cuu_action: lda top_margin 4367 1801 01 FF 00 lxi b,00ffh ; B <- limit of movement (row 0), C <- direction (-1 = up) 4368 1804 C3 0F 18 jmp row_mv 4369 ; 4370 ; CUD - cursor down n rows 4371 1807 CD BF 11 cud_action: call last_row ; B <- last row number (i.e. 13, or 23 with AVO) 4372 180A 3A 56 21 lda bottom_margin 4373 180D 0E 01 mvi c,1 ; C <- direction of movement (+1 = down) 4374 ; 4375 ; row_mv 4376 ; On entry, B is limit of movement, which is 0 or last row (cursor movement can't cause scrolling) 4377 ; C is direction of movement, -1 for up, or +1 for down 4378 ; 4379 180F 21 F9 20 row_mv: lxi h,curs_row ; HL <- coordinate location we're affecting (row) 4380 1812 C3 25 18 jmp cur_mv 4381 ; 4382 ; CUF - cursor forward n columns 4383 1815 3A 53 21 cuf_action: lda right_margin 4384 1818 01 01 FF lxi b,0ff01h ; B <- limit of movement (column 255!), c is direction of movement (+1) 4385 181B C3 22 18 jmp col_mv ; shared code with CUB 4386 ; 4387 ; CUB - cursor backward (left) n columns 4388 181E AF cub_action: xra a 4389 181F 01 FF 00 lxi b,00ffh ; B <- limit of movement (column 0), c is direction of movement (-1) 4390 1822 21 F8 20 col_mv: lxi h,curs_col 4391 ; Row movement can also be processed here, by having HL initialised to the curs_row address and 4392 ; appropriate limits set 4393 1825 57 cur_mv: mov d,a ; D <- appropriate margin, to limit movement 4394 1826 3A B8 21 lda csi_private ; Check that we haven't got any private characters 4395 1829 B7 ora a ; because there is no private meaning for any of the 4396 182A C2 15 0A jnz to_ground ; cursor movement sequences 4397 182D 3A 30 21 lda csi_p1 ; Defaulting the first parameter: if the parameter 4398 1830 B7 ora a ; hasn't been supplied, or is supplied as zero, 4399 1831 C2 35 18 jnz got_p1 4400 1834 3C inr a ; then treat it as one 4401 1835 5F got_p1: mov e,a ; E <- number of times to move 4402 1836 7E mov a,m ; A <- current column 4403 1837 BA repeat_move: cmp d ; check against margin 4404 1838 CA 44 18 jz stop_move 4405 183B B8 cmp b 4406 183C CA 44 18 jz stop_move 4407 183F 81 add c 4408 1840 1D dcr e ; Do this movement until limited (conditions above) 4409 1841 C2 37 18 jnz repeat_move ; or until we've exhaused the count 4410 1844 77 stop_move: mov m,a ; store final column address 4411 1845 C3 36 16 jmp move_updates 4412 ; 4413 ; cursor_home 4414 ; 4415 ; Because this routine clears the first two CSI parameter locations in order to act like a CUP 4416 ; or HVP sequence with defaulted row and column positions, it provokes a bug in DECOM. 4417 ; _See_ decom_mode for details. 4418 ; 4419 1848 21 00 00 cursor_home: lxi h,0 4420 184B 22 30 21 shld csi_params 4421 184E 3E FF mvi a,0ffh 4422 1850 32 0E 21 sta saved_curs_row 4423 4424 ; 4425 ; curpos_action 4426 ; 4427 ; The sequences CUP (CSI H) and HVP (CSI f) both come here, as they have identifical effects on all 4428 ; models of DEC VTs. 4429 ; 4430 ; This routine has a bug in that it starts off by storing the origin mode flag in the C register, 4431 ; but can lose track of that and use C for a row number instead, but then make a further decision 4432 ; as if it still contained the flag. 4433 ; 4434 ; The fault path is triggered if we are not in origin mode, which means the code validates the 4435 ; row parameter against the last row of the screen, not the bottom margin. At this point, C is 4436 ; changed from storing the origin mode flag to storing the requested new row. Next, the column 4437 ; number is validated by checking the column number against either the right margin (origin mode) 4438 ; or the last column on the screen (non origin mode). If we are in origin mode, the test proceeds 4439 ; as intended, but if we were not in origin mode, using the right margin or last column depends 4440 ; on whether the new internal row number is 0 or not. If it is non-zero, we check the right margin 4441 ; (i.e. acting like origin mode) instead of last column. 4442 ; 4443 ; This bug constrains the cursor column update to the right margin even if origin mode is not in 4444 ; effect. However, the VT100 doesn't have settable left and right margins, and the right margin is 4445 ; only ever different from last column number on double width lines. So there is no case in which 4446 ; this code will allow the setting of a column number beyond the right margin, which means the 4447 ; corruption of the middle-of-line terminators on double width lines cannot occur. (In any case, all 4448 ; cursor moves are then re-validated by see move_updates.) 4449 ; 4450 ; The effect of the bug is that cursor moves from a line that is double-width to a line that is 4451 ; single width are constrained by the right margin of the current line, provided the new row 4452 ; is anything other than row 1, which is incorrect. 4453 ; 4454 ; This is demonstrated by test t/margin-bug.txt. [* BUG *] 4455 ; 4456 1853 3A 01 21 curpos_action: lda origin_mode 4457 1856 4F mov c,a 4458 1857 21 30 21 lxi h,csi_p1 4459 185A 7E mov a,m ; Pn1 is row 4460 185B B7 ora a ; default/0 will be top row 4461 185C CA 60 18 jz def_row 4462 185F 3D dcr a ; Otherwise, our internal rows are 0-based, not 1-based 4463 1860 47 def_row: mov b,a ; B <- row 4464 1861 79 mov a,c ; A <- origin mode flag 4465 1862 B7 ora a 4466 1863 CA 69 18 jz add_top ; jump if not in origin mode 4467 1866 3A 55 21 lda top_margin ; In origin mode, move within margins 4468 1869 80 add_top: add b 4469 186A 47 mov b,a ; B <- adjusted row 4470 186B 79 mov a,c ; A <- origin mode flag 4471 186C B7 ora a 4472 186D 3A 56 21 lda bottom_margin ; A <- bottom margin 4473 1870 C2 79 18 jnz chk_bot_limit ; jump if origin mode 4474 1873 48 mov c,b ; C <- new row 4475 1874 CD BF 11 call last_row ; B <- last row on screen 4476 1877 78 mov a,b ; A <- last row 4477 1878 41 mov b,c ; B <- new row 4478 1879 B8 chk_bot_limit: cmp b ; comparing new row against either bottom margin or last row 4479 187A DA 7E 18 jc skip_row ; if last row < new row, go store last row 4480 187D 78 mov a,b ; else new row is ok 4481 187E 32 F9 20 skip_row: sta curs_row ; store row 4482 1881 23 inx h ; HL points to csi_p2 4483 1882 7E mov a,m ; A <- Pn2 (column) 4484 1883 B7 ora a ; default/0 will be left column 4485 1884 CA 88 18 jz def_col 4486 1887 3D dcr a ; Otherwise, our internal cols are 0-based, not 1-based 4487 1888 47 def_col: mov b,a ; B <- col 4488 1889 79 mov a,c ; A <- 1 if origin mode flag had been set, new row if unset 4489 188A B7 ora a ; 4490 188B C2 95 18 jnz use_rmargin ; jump if origin mode 4491 188E 3A 50 20 lda screen_cols 4492 1891 3D dcr a ; A <- last column number 4493 1892 C3 98 18 jmp chk_rlimit 4494 ; 4495 1895 3A 53 21 use_rmargin: lda right_margin 4496 1898 B8 chk_rlimit: cmp b 4497 1899 DA 9D 18 jc nolim_rm ; if right margin < requested column, store margin 4498 189C 78 mov a,b ; else A <- request column 4499 189D 32 F8 20 nolim_rm: sta curs_col ; store 4500 18A0 C3 36 16 jmp move_updates 4501 ; 4502 ; read_nvr_byte 4503 ; 4504 18A3 0E 40 read_nvr_byte: mvi c,iob_flags_lba7 4505 18A5 CD 28 19 call set_nvr_addr 4506 18A8 CD C1 18 call read_nvr_data 4507 18AB C3 BC 18 jmp nvr_idle 4508 ; 4509 ; write_nvr_byte 4510 ; 4511 ; Writing a byte to the NVR involves addressing the location and an erase cycle first, 4512 ; which involves commanding the erase operation and waiting; there is no positive 4513 ; acknowledgement of completion. 4514 ; 4515 18AE 0E 40 write_nvr_byte: mvi c,iob_flags_lba7 4516 18B0 CD 28 19 call set_nvr_addr 4517 18B3 CD A8 19 call erase_nvr ; includes 20 ms delay 4518 18B6 CD 77 19 call nvr_accept 4519 18B9 CD D6 19 call write_nvr ; includes 20 ms delay 4520 18BC 3E 30 nvr_idle: mvi a,30h ; c000 d0 = "accept data" (inv) 4521 18BE D3 62 out iow_nvr_latch 4522 18C0 C9 ret 4523 ; 4524 read_nvr_data: 4525 18C1 DB 42 w1h: in ior_flags ; ER1400 shows command acceptance on rising 4526 18C3 A1 ana c ; edge of clock. Wait for high, low, 4527 18C4 CA C1 18 jz w1h ; command, then wait for high again. 4528 18C7 DB 42 w1l: in ior_flags 4529 18C9 A1 ana c 4530 18CA C2 C7 18 jnz w1l 4531 18CD 3E 2D mvi a,2dh ; c110 d1 = "read" (inv) 4532 18CF D3 62 out iow_nvr_latch 4533 18D1 DB 42 w2h: in ior_flags 4534 18D3 A1 ana c 4535 18D4 CA D1 18 jz w2h 4536 18D7 DB 42 w2l: in ior_flags 4537 18D9 A1 ana c 4538 18DA C2 D7 18 jnz w2l 4539 18DD 3E 2F mvi a,2fh ; c111 d1 = "standby" (inv) 4540 18DF D3 62 out iow_nvr_latch 4541 18E1 21 D3 21 lxi h,nvr_bits 4542 18E4 06 0E mvi b,14 ; shifting 14 bits out of NVR 4543 18E6 DB 42 w3h: in ior_flags 4544 18E8 A1 ana c 4545 18E9 CA E6 18 jz w3h 4546 18EC DB 42 w3l: in ior_flags 4547 18EE A1 ana c 4548 18EF C2 EC 18 jnz w3l 4549 18F2 3E 25 mvi a,25h ; c010 d1 = "shift data out" (inv) 4550 18F4 D3 62 out iow_nvr_latch 4551 18F6 DB 42 w4h: in ior_flags ; \ 4552 18F8 A1 ana c ; | 4553 18F9 CA F6 18 jz w4h ; | 4554 18FC DB 42 in ior_flags ; | 4555 18FE 77 mov m,a ; | - squirrel away raw flags buffer value 4556 18FF 23 inx h ; < 14 bits 4557 1900 DB 42 w4l: in ior_flags ; | 4558 1902 A1 ana c ; | 4559 1903 C2 00 19 jnz w4l ; | 4560 1906 05 dcr b ; | 4561 1907 C2 F6 18 jnz w4h ; / 4562 190A 3E 2F mvi a,2fh ; c111 d1 = "standby" (inv) 4563 190C D3 62 out iow_nvr_latch ; 4564 190E 11 D3 21 lxi d,nvr_bits 4565 1911 06 0E mvi b,14 ; 14 bits again 4566 1913 21 00 00 lxi h,0 4567 1916 29 accum_bits: dad h ; shift HL left, clearing bit 0 of L 4568 1917 1A ldax d ; grab next bit, which is raw flags buffer read 4569 1918 E6 20 ani iob_flags_nvr ; isolate bit 5 4570 191A 07 rlc ; data is bit 6 4571 191B 07 rlc ; data is bit 7 4572 191C 07 rlc ; data is bit 0 4573 191D B5 ora l ; Add to HL 4574 191E 6F mov l,a 4575 191F 13 inx d ; next raw read 4576 1920 05 dcr b ; for all 14 bits 4577 1921 C2 16 19 jnz accum_bits 4578 1924 22 AF 21 shld nvr_data ; stow all 14 bits 4579 1927 C9 ret 4580 ; 4581 ; used for both read and write operations 4582 ; only routine that uses nvr_addr 4583 ; 4584 ; Addresses are represented by 20 bits are split into a tens and units part, each represented by ten 4585 ; bits. Addresses are clocked into the ER1400 with all address bits held high except for the single 4586 ; bit of the digit that represents your tens or units. 4587 ; 4588 ; In order to get the timing right, this routine pre-calculates all 20 bits of the address in memory, 4589 ; in the nvr_bits array, including the complete C1, C2, C3 for each NVR latch byte. 4590 ; 4591 1928 3A AE 21 set_nvr_addr: lda nvr_addr 4592 192B 06 FF set_nvr_addr_a: mvi b,-1 ; The address is broken up into tens and units 4593 192D 04 addr_tens: inr b 4594 192E D6 0A sui 0ah 4595 1930 F2 2D 19 jp addr_tens 4596 1933 C6 0A adi 0ah ; Now B is tens, A is units 4597 1935 21 D3 21 lxi h,nvr_bits 4598 1938 1E 23 mvi e,23h ; c001 d1 "accept address" (inv) 4599 193A 16 14 mvi d,20 ; 20 address bits 4600 193C 73 addr_const: mov m,e ; some kind of constant lead-in? 4601 193D 23 inx h 4602 193E 15 dcr d 4603 193F C2 3C 19 jnz addr_const 4604 1942 36 2F mvi m,2fh ; c111 d1 "standby" (inv) 4605 1944 21 D3 21 lxi h,nvr_bits 4606 1947 5F mov e,a ; 4607 1948 16 00 mvi d,0 ; DE <- units address 4608 194A 19 dad d ; Advance HL by units address 4609 194B 36 22 mvi m,22h ; c001 d0 "accept address" (inv) 4610 194D 21 D3 21 lxi h,nvr_bits 4611 1950 3E 0A mvi a,0ah 4612 1952 80 add b ; A = address tens + 10 4613 1953 5F mov e,a ; 4614 1954 19 dad d ; Advance HL by 10 + tens address 4615 1955 36 22 mvi m,22h ; c001 d0 "accept address" (inv) 4616 1957 DB 42 wa1l: in ior_flags 4617 1959 A1 ana c 4618 195A C2 57 19 jnz wa1l 4619 195D 21 D3 21 lxi h,nvr_bits ; Back to beginning of bits, ready for latching on clock lows 4620 1960 06 15 mvi b,21 ; must be complete in 21 clock periods (including high after last bit) 4621 1962 DB 42 wa1h: in ior_flags 4622 1964 A1 ana c 4623 1965 CA 62 19 jz wa1h 4624 1968 05 dcr b 4625 1969 F8 rm ; limited cycles for entire write 4626 196A DB 42 wa2l: in ior_flags 4627 196C A1 ana c 4628 196D C2 6A 19 jnz wa2l 4629 1970 7E mov a,m 4630 1971 D3 62 out iow_nvr_latch 4631 1973 23 inx h 4632 1974 C3 62 19 jmp wa1h 4633 ; 4634 1977 2A AF 21 nvr_accept: lhld nvr_data ; grab 14 bits of data 4635 197A 29 dad h ; shift the bits 2 places left so that the 4636 197B 29 dad h ; next add will start forcing data into carry 4637 197C 11 D3 21 lxi d,nvr_bits 4638 197F 06 0E mvi b,14 ; going to process 14 bits 4639 1981 3E 20 next_split: mvi a,20h ; c000 d0 = "accept data" (inv) 4640 1983 29 dad h ; shift top by of HL into carry flag 4641 1984 17 ral ; and pull it into bit 0 of A 4642 1985 12 stax d ; place in nvr_bits buffer 4643 1986 13 inx d 4644 1987 05 dcr b 4645 1988 C2 81 19 jnz next_split 4646 198B 3E 2F mvi a,2fh ; c111 d1 = "standby" (inv) 4647 198D 12 stax d 4648 198E 21 D3 21 lxi h,nvr_bits 4649 1991 06 0F mvi b,15 ; 14 data bits + standby terminator 4650 1993 DB 42 wd1h: in ior_flags 4651 1995 A1 ana c 4652 1996 CA 93 19 jz wd1h 4653 1999 DB 42 wd1l: in ior_flags 4654 199B A1 ana c 4655 199C C2 99 19 jnz wd1l 4656 199F 7E mov a,m 4657 19A0 D3 62 out iow_nvr_latch 4658 19A2 23 inx h 4659 19A3 05 dcr b 4660 19A4 C2 93 19 jnz wd1h 4661 19A7 C9 ret 4662 ; 4663 erase_nvr: 4664 19A8 DB 42 wl1h: in ior_flags 4665 19AA A1 ana c 4666 19AB CA A8 19 jz wl1h 4667 19AE DB 42 wl1l: in ior_flags 4668 19B0 A1 ana c 4669 19B1 C2 AE 19 jnz wl1l 4670 19B4 3E 2B mvi a,2bh ; c101 d1 = "erase" (inv) 4671 19B6 D3 62 out iow_nvr_latch 4672 19B8 CD C0 19 call wait_nvr 4673 19BB 3E 2F mvi a,2fh ; c111 d1 = "standby" (inv) 4674 19BD D3 62 out iow_nvr_latch 4675 19BF C9 ret 4676 ; 4677 ; If we're expected to clock data out of NVR and we don't want it, 4678 ; we just wait out a number of clocks. 4679 ; 4680 19C0 21 3B 01 wait_nvr: lxi h,315 ; 315 x LBA 7 cycles at 63.5 µs per cycle = 20 ms 4681 19C3 DB 42 w20h: in ior_flags 4682 19C5 A1 ana c 4683 19C6 CA C3 19 jz w20h 4684 19C9 DB 42 w20l: in ior_flags 4685 19CB A1 ana c 4686 19CC C2 C9 19 jnz w20l 4687 19CF 2B dcx h 4688 19D0 7C mov a,h 4689 19D1 B5 ora l 4690 19D2 C2 C3 19 jnz w20h 4691 19D5 C9 ret 4692 ; 4693 ; After an "accept data" operation, command the NVR to write the data. Then wait. 4694 ; The datasheet says that the ER1400 will finish in a maximum of 24 ms. 4695 write_nvr: 4696 19D6 DB 42 wwwh: in ior_flags 4697 19D8 A1 ana c 4698 19D9 CA D6 19 jz wwwh 4699 19DC DB 42 wwwl: in ior_flags 4700 19DE A1 ana c 4701 19DF C2 DC 19 jnz wwwl 4702 19E2 3E 29 mvi a,29h ; c100 d1 = "write" (inv) 4703 19E4 D3 62 out iow_nvr_latch 4704 19E6 CD C0 19 call wait_nvr 4705 19E9 3E 2F mvi a,2fh ; c111 d1 = "standby (inv) 4706 19EB D3 62 out iow_nvr_latch 4707 19ED C9 ret 4708 ; 4709 ; Processing next "received" character for SET-UP, which means handling a keypress, as we're now in 4710 ; LOCAL mode, and keypresses aren't going to the host, but being reflected internally. For the 4711 ; mechanism here, see report_char. 4712 ; 4713 ; On entry, A and B both contain the received character (see reflect_char) 4714 ; 4715 19EE FE 20 setup_action: cpi 20h ; If SPACE is pressed, 4716 19F0 0E 43 mvi c,43h ; it is treated the same as RIGHT ARROW, 4717 19F2 CA FF 01 jz setup_cursor ; so perform cursor movement 4718 19F5 21 B0 1B lxi h,setup_ready 4719 19F8 E5 push h ; Push return address on stack 4720 19F9 FE 0D cpi C0_CR 4721 19FB CA 4B 09 jz c0_return 4722 19FE FE 09 cpi C0_HT 4723 1A00 CA F9 0D jz c0_horiz_tab 4724 1A03 FE 3A cpi '9'+1 ; Keys above numeric range are handled separately 4725 1A05 D2 60 1B jnc setup_keys 4726 1A08 D6 30 sui '0' ; Numeric keys are handled through a jump table, 4727 1A0A F8 rm ; bring down into range and reject any keys lower than '0' 4728 1A0B 87 add a ; double up code because addresses are two bytes 4729 1A0C 21 A2 1A lxi h,setup_key_t ; HL <- base of key table 4730 1A0F CD DE 13 call add_a_to_hl ; 4731 1A12 CD 90 13 call ld_hl_from_hl 4732 1A15 78 mov a,b ; A <- received char 4733 1A16 11 AC 21 lxi d,rx_spd ; DE <- rx_spd, which is convenient for toggle speed keys, at least 4734 1A19 E9 pchl ; jump to action routine 4735 ; 4736 ; Keyboard processing has detected SETUP key, which either takes us into or out of SET-UP 4737 1A1A 32 77 21 setup_pressed: sta pending_setup 4738 1A1D C3 12 08 jmp pk_click 4739 ; 4740 ; Pressing "SET UP" will mark a SET-UP action as pending, and then this routine will be called 4741 ; to either enter or exit. 4742 ; 4743 1A20 3A 7B 20 in_out_setup: lda in_setup 4744 1A23 EE FF xri 0ffh ; Toggle in/out of SET-UP 4745 1A25 32 7B 20 sta in_setup 4746 1A28 CA 61 1A jz exit_setup 4747 ; 4748 ; TM says that, on entering SET-UP, it waits for scrolls to finish. 4749 ; 4750 1A2B 0E 80 enter_setup: mvi c,80h 4751 1A2D CD 8E 10 call wait_scroll 4752 1A30 2A 40 21 lhld char_action ; We're going to redirect all key actions 4753 1A33 22 11 21 shld saved_action ; so save previous state 4754 1A36 21 EE 19 lxi h,setup_action ; and register the SET-UP action 4755 1A39 22 40 21 shld char_action 4756 1A3C 21 00 00 lxi h,0 4757 1A3F 22 43 21 shld curkey_qcount ; keyboard_locked <- 0 too 4758 1A42 22 72 21 shld pending_report ; sending_report <- 0 too 4759 1A45 2A 04 20 setup_addrs: lhld line1_dma 4760 1A48 22 C5 21 shld saved_line1_dma 4761 1A4B 2A F8 20 lhld curs_col ; HL <- curs_row and curs_col 4762 1A4E 22 0D 21 shld saved_curs_col ; save them 4763 1A51 CD BA 1B call extra_addr 4764 1A54 22 4E 21 shld start_line_addr 4765 1A57 AF xra a 4766 1A58 32 F8 20 sta curs_col 4767 1A5B CD F9 1B call setup_display 4768 1A5E C3 36 16 jmp move_updates 4769 ; 4770 1A61 2A 11 21 exit_setup: lhld saved_action ; Restore character processing state (we could have been in the middle 4771 1A64 22 40 21 shld char_action ; of receiving a control sequence when SET-UP was pressed.) 4772 1A67 CD BA 1B call extra_addr ; HL <- extra line screen RAM 4773 1A6A 11 00 10 lxi d,1000h 4774 1A6D 19 dad d ; HL <- start of cursor line attribute RAM 4775 1A6E 3A 50 20 lda screen_cols ; Default attributes for entire line 4776 1A71 36 FF restore_attr: mvi m,0ffh 4777 1A73 23 inx h 4778 1A74 3D dcr a 4779 1A75 C2 71 1A jnz restore_attr 4780 1A78 21 0D 21 lxi h,saved_curs_col 4781 1A7B 7E mov a,m ; A <- saved cursor column 4782 1A7C 32 F8 20 sta curs_col 4783 1A7F 3E FF mvi a,0ffh ; A <- 0ffh 4784 1A81 23 inx h ; HL points to saved_curs_row 4785 1A82 77 mov m,a ; destroy it 4786 1A83 CD 36 16 call move_updates 4787 1A86 2A C5 21 lhld saved_line1_dma 4788 1A89 22 04 20 shld line1_dma 4789 1A8C AF xra a 4790 1A8D 32 C4 21 sta noscroll 4791 1A90 32 C2 21 sta received_xoff 4792 1A93 DB 42 in ior_flags ; Exiting SET-UP will trigger DECREPTPARM if we are 4793 1A95 E6 08 ani iob_flags_stp ; allowed to send them unsolicited, and if we don't have STP. 4794 1A97 CA A1 1A jz skip_tparm 4795 1A9A 3A 76 21 lda tparm_solicited 4796 1A9D B7 ora a 4797 1A9E CC E8 12 cz setup_tparm 4798 1AA1 C9 skip_tparm: ret 4799 ; 4800 ; Jump table for digit keys pressed in either SET-UP mode 4801 ; 4802 1AA2 CF 1A setup_key_t: dw restart ; Key 0 - reset 4803 1AA4 09 1B dw do_nothing_key ; Key 1 - has no function in SET-UP 4804 1AA6 D0 1A dw toggle_tab ; Key 2 - set or clear tab (SET-UP A only) 4805 1AA8 F2 1A dw setup_clr_tabs ; Key 3 - clear all tabs (SET-UP A only) 4806 1AAA FE 1A dw toggle_online ; Key 4 - line/local 4807 1AAC 0A 1B dw toggle_a_b ; Key 5 - SET-UP A/B 4808 1AAE 22 1B dw toggle_bit ; Key 6 - Toggle 1/0 (SET-UP B only) 4809 1AB0 40 1B dw cycle_tx_speed ; Key 7 - Transmit speed 4810 1AB2 41 1B dw cycle_rx_speed ; Key 8 - Receive speed 4811 1AB4 B6 1A dw switch_columns ; Key 9 - 80/132 columns 4812 ; 4813 1AB6 CD 52 1B switch_columns: call setup_a_only 4814 1AB9 AF xra a 4815 1ABA 32 0E 21 sta saved_curs_row 4816 1ABD 32 0D 21 sta saved_curs_col 4817 1AC0 3A A2 21 lda columns_132 4818 1AC3 B7 ora a 4819 1AC4 F5 push psw 4820 1AC5 CC 77 0B cz init_132col 4821 1AC8 F1 pop psw 4822 1AC9 C4 63 0B cnz init_80col 4823 1ACC C3 45 1A jmp setup_addrs ; same address-setting code as entering SET-UP 4824 ; 4825 1ACF C7 restart: rst 0 4826 ; 4827 1AD0 CD 52 1B toggle_tab: call setup_a_only 4828 ; 4829 1AD3 3A F8 20 lda curs_col 4830 1AD6 B7 ora a ; Can't have a tab at first column on line 4831 1AD7 C8 rz ; so quit early 4832 1AD8 CD 23 0E call tab_offs_curs 4833 1ADB AE xra m ; flip tab state 4834 1ADC 77 mov m,a ; and write back 4835 1ADD CD 23 0E call tab_offs_curs 4836 1AE0 A6 ana m 4837 1AE1 06 54 mvi b,'T' ; Assume there'll be a tab stop 4838 1AE3 C2 E8 1A jnz is_tab 4839 1AE6 06 00 no_tab: mvi b,0 ; B <- blank 4840 1AE8 78 is_tab: mov a,b ; A <- 'T'/blank 4841 1AE9 32 F4 20 sta char_und_curs 4842 1AEC 3A F8 20 lda curs_col 4843 1AEF C3 EA 1E jmp disp_char_col 4844 ; 4845 1AF2 CD 52 1B setup_clr_tabs: call setup_a_only 4846 1AF5 CD EC 0D call clear_all_tabs 4847 1AF8 CD CD 1C call clear_extra 4848 1AFB C3 E6 1A jmp no_tab 4849 ; 4850 ; SET-UP key 4 toggles line/local 4851 1AFE 21 A5 21 toggle_online: lxi h,local_mode 4852 1B01 7E mov a,m 4853 1B02 EE 20 xri 20h ; Toggle local mode 4854 1B04 77 mov m,a 4855 1B05 AF xra a 4856 1B06 32 44 21 sta keyboard_locked 4857 ; this is just a convenient "ret" for a key that does nothing in SET-UP 4858 1B09 C9 do_nothing_key: ret 4859 ; 4860 1B0A CD 4B 09 toggle_a_b: call c0_return 4861 1B0D 21 5A 22 lxi h,setup_video+7 ; Screen location of final character of "SET-UP A" (or "B") 4862 1B10 7E mov a,m ; A <- character 4863 1B11 EE 03 xri 3 ; A <- "A" ^ "B" 4864 1B13 77 mov m,a ; Write it back 4865 1B14 32 65 22 sta setup_video+12h ; And same thing on bottom line, because it's double height 4866 1B17 E6 01 ani 1 ; And the low bit of the ASCII "A" conveniently matches 4867 1B19 32 BE 21 sta in_setup_a ; our flag for which screen we're on 4868 1B1C CA D3 1C jz draw_setup_b 4869 1B1F C3 35 1C jmp draw_setup_a 4870 ; 4871 ; toggle_bit allows any of the SET-UP B configuration blocks to be updated, with 4872 ; addressing made easy by a cunning screen layout; The screen looks like this: 4873 ; 4874 ; 1 1101 2 1111 3 0100 4 0010 5 0000 <- (optional block 5) 4875 ; 4876 ; As each setup block is only 4 bits in the top half of each setup byte, the 4877 ; placement of the bit blocks 4 columns apart, allows the col_in_bits 4878 ; routine to directly address the correct bit (after the starting column offset 4879 ; has been removed.) 4880 ; 4881 1B22 CD 59 1B toggle_bit: call setup_b_only 4882 1B25 21 A6 21 lxi h,setup_b1 ; First block on screen (lowest in memory) 4883 1B28 3A F8 20 lda curs_col 4884 1B2B D6 02 sui 2 ; Ignore the "1 " at the start of line 4885 1B2D FE 28 cpi 28h ; To accommodate possible block 5 4886 1B2F D0 rnc ; Return if out of range 4887 1B30 00 nop ; Feels like last-minute code removal here 4888 1B31 00 nop 4889 1B32 00 nop 4890 1B33 00 nop 4891 1B34 57 mov d,a ; D <- A because col_in_bits uses both 4892 1B35 CD 2B 0E call col_in_bits 4893 1B38 AE xra m ; toggle the appropriate bit 4894 1B39 77 mov m,a ; and write back 4895 1B3A CD DB 1D call program_pusart 4896 1B3D C3 D3 1C jmp draw_setup_b 4897 ; 4898 ; cycle_tx_speed 4899 ; cycle_rx_speed 4900 ; 4901 ; SET-UP initialises DE to point to rx_spd before jumping to action routines, so cycle_tx_speed 4902 ; just adjusts this location before dropping through to the generic routine. 4903 ; SET-UP B ONLY 4904 ; 4905 1B40 1B cycle_tx_speed: dcx d ; DE <- tx_spd 4906 1B41 EB cycle_rx_speed: xchg ; HL <-< DE, so HL points at speed location 4907 1B42 CD 59 1B call setup_b_only ; A is 0 on exit! 4908 1B45 32 F8 20 sta curs_col 4909 1B48 7E mov a,m ; Read current speed 4910 1B49 C6 10 adi 10h ; add to the value in top 4 bits 4911 1B4B 77 mov m,a ; Store new value 4912 1B4C CD DB 1D call program_pusart 4913 1B4F C3 D3 1C jmp draw_setup_b 4914 ; 4915 ; setup_a_only 4916 ; setup_b_only 4917 ; These guard routines are called by functions that should only work in one of the two 4918 ; SET-UP modes, because key dispatching happens through the same table for both screens. 4919 ; These routines are called by action routines and they will just return if the correct 4920 ; SET-UP screen is in use. Otherwise, they'll pop the stack, to quit the action routine 4921 ; early and return to the action routine's caller. 4922 ; 4923 1B52 3A BE 21 setup_a_only: lda in_setup_a 4924 1B55 B7 ora a 4925 1B56 C0 rnz ; Correct screen; return to caller 4926 1B57 E1 pop h 4927 1B58 C9 ret ; else discard the action routine 4928 ; 4929 1B59 3A BE 21 setup_b_only: lda in_setup_a 4930 1B5C B7 ora a 4931 1B5D C8 rz ; Correct screen; return to caller 4932 1B5E E1 pop h 4933 1B5F C9 ret ; else discard the action routine 4934 ; 4935 ; In SET-UP mode, deal with keys other than digits (which are handled through a table) 4936 ; 4937 1B60 3A 69 20 setup_keys: lda last_key_flags ; All other keys will need SHIFT to be pressed 4938 1B63 E6 20 ani key_flag_shift 4939 1B65 C8 rz ; so if it isn't, exit 4940 1B66 78 mov a,b 4941 1B67 FE 53 cpi 'S' ; SHIFT S stores settings in NVR 4942 1B69 C2 72 1B jnz try_recall 4943 1B6C CD 54 17 call store_nvr 4944 1B6F C3 81 1B jmp post_nvr 4945 ; 4946 1B72 FE 52 try_recall: cpi 'R' ; SHIFT R recalls settings from NVR 4947 1B74 C2 86 1B jnz try_answerback 4948 1B77 CD 5B 17 call recall_nvr 4949 1B7A FB ei 4950 1B7B CD A2 03 call init_devices 4951 1B7E CD 2B 1A call enter_setup 4952 1B81 FB post_nvr: ei 4953 1B82 CD F9 1B call setup_display 4954 1B85 C9 ret 4955 ; 4956 1B86 FE 41 try_answerback: cpi 'A' 4957 1B88 C0 rnz 4958 1B89 CD 59 1B call setup_b_only 4959 1B8C CD 4B 09 call c0_return 4960 1B8F 3E 41 mvi a,'A' ; extra line is initialised with "A= " and then you type a 4961 1B91 CD E6 05 call print_char ; delimiter, rest of message and another delimiter to finish. 4962 1B94 3E 3D mvi a,'=' 4963 1B96 CD E6 05 call print_char 4964 1B99 3E 20 mvi a,' ' 4965 1B9B CD E6 05 call print_char 4966 1B9E 21 9F 1E lxi h,aback_entry 4967 1BA1 22 40 21 shld char_action 4968 1BA4 E1 pop h 4969 1BA5 C9 ret 4970 ; 4971 ; These 10 bytes are all valid 8080 instructions but are unreachable. The first byte is a little out of 4972 ; place, hence my assumption that it's the checksum for ROM 4 (1800h - 1fffh), as there are no unused 4973 ; non-zero bytes elsewhere in this ROM. The other 9 bytes would result in a lowercase character in the 4974 ; A register being made uppercase, returning all others unchanged. As this is very far from the keyboard 4975 ; routines, perhaps this is the remnants of some VT50 compatibility code. Who knows? 4976 ; 4977 1BA6 98 sbb b ; = 98h. Probably CHECKSUM (first byte of unused code) 4978 1BA7 FE 61 cpi 61h ; UNREACHABLE (see note above) 4979 1BA9 F8 rm 4980 1BAA FE 7B cpi 7bh 4981 1BAC F0 rp 4982 1BAD E6 DF ani 0dfh 4983 1BAF C9 ret 4984 ; 4985 ; Gets us ready for next setup action. Used as a handy return point by being pushed on the stack. 4986 ; 4987 1BB0 CD 94 03 setup_ready: call ready_comms 4988 1BB3 21 EE 19 lxi h,setup_action 4989 1BB6 22 40 21 shld char_action 4990 1BB9 C9 ret 4991 ; 4992 ; extra_addr 4993 ; Returns with HL containing address of the extra line in screen RAM. 4994 ; This is the line that gets used as the cursor line in SET-UP mode, so in SET-UP A, it 4995 ; shows all the tab stops; in SET-UP B the cursor moves along it to select a switch to be 4996 ; changed, and the answerback message gets entered here. 4997 ; 4998 1BBA 2A 4E 20 extra_addr: lhld line_25_addr 4999 1BBD C3 C3 1B jmp real_addr2 5000 ; 5001 1BC0 21 CC 21 wait_screen: lxi h,wait_addr 5002 ; fall through, pointlessly, as HL is already in screen range 5003 ; local copy of real_addr 5004 1BC3 7C real_addr2: mov a,h 5005 1BC4 E6 0F ani 0fh 5006 1BC6 F6 20 ori 20h 5007 1BC8 67 mov h,a 5008 1BC9 C9 ret 5009 ; 5010 ; finish_setup 5011 ; 5012 ; The final part of drawing the setup screen is connecting the cursor line (23rd line = row 22), to the 5013 ; "Wait" line that we're using as 24th line (row 23). 5014 ; 5015 1BCA CD BA 1B finish_setup: call extra_addr ; HL <- extra line in screen RAM 5016 1BCD CD C8 0F call clear_row 5017 1BD0 E5 push h ; HL points to where terminator should be/is 5018 1BD1 36 7F mvi m,7fh ; OK, put terminator there 5019 1BD3 23 inx h ; point to where screen DMA address will be 5020 1BD4 EB xchg ; Stow it in DE 5021 1BD5 CD C0 1B call wait_screen ; HL <- wait screen line 5022 1BD8 7C mov a,h ; Add line attributes for use as DMA high byte 5023 1BD9 E6 0F ani 0fh 5024 1BDB F6 70 ori 70h 5025 1BDD 12 stax d ; Next line address is "Wait" line 5026 1BDE 13 inx d 5027 1BDF 7D mov a,l 5028 1BE0 12 stax d 5029 1BE1 E1 pop h ; HL <- back to terminator address 5030 ; 5031 ; Now write a line containing a 'T' at every tab stop position. 5032 ; Other columns aren't written, so they remain blank. 5033 ; 5034 1BE2 3A 50 20 lda screen_cols 5035 1BE5 5F mov e,a ; E <- column number 5036 1BE6 06 54 mvi b,'T' ; B <- character drawn at tab stops 5037 1BE8 7B tloop: mov a,e 5038 1BE9 CD 27 0E call tab_offs 5039 1BEC A6 ana m ; If the bit in A is not set in (HL), 5040 1BED CA F4 1B jz tnotab ; then there is no tab set here. 5041 1BF0 7B mov a,e ; Otherwise, draw a 'T' at this column 5042 1BF1 CD EA 1E call disp_char_col 5043 1BF4 1D tnotab: dcr e ; Loop for all columns 5044 1BF5 C2 E8 1B jnz tloop 5045 1BF8 C9 ret 5046 ; 5047 1BF9 01 AD 1C setup_display: lxi b,setup_a_string 5048 1BFC 21 53 22 lxi h,setup_video 5049 1BFF 3E FA mvi a,0fah ; "SET-UP x" is bold and blinking 5050 1C01 CD 71 1C call display_zstr 5051 1C04 0E 01 mvi c,1 ; terminate 1 line 5052 1C06 06 10 mvi b,10h ; line attributes *for next line*: bottom half, double height 5053 1C08 CD 8A 1C call term_line 5054 1C0B 01 AD 1C lxi b,setup_a_string 5055 1C0E 3E FA mvi a,0fah ; still bold and blinking 5056 1C10 CD 71 1C call display_zstr 5057 1C13 0E 01 mvi c,1 5058 1C15 06 50 mvi b,50h ; line attributes for next line: double width 5059 1C17 CD 8A 1C call term_line 5060 ; "To exit..." is underlined 5061 1C1A 01 B6 1C lxi b,to_exit_string 5062 1C1D 3E FD mvi a,rend_under 5063 1C1F CD 71 1C call display_zstr 5064 1C22 0E 13 mvi c,13h ; terminate 19 lines 5065 1C24 CD 88 1C call term_line1 ; use entry point that sets single width line attributes 5066 ; Now the 3 top lines and 18 middle blank lines have been written, 5067 ; we're about to terminate the 22nd line and point to the current 5068 ; cursor line for the 23rd line of the setup screen, which is where 5069 ; the cursor will be movign, and also where answerback can be entered. 5070 ; This cursor line is the 25th line of the main screen, which is 5071 ; known to have nothing needing preserving on it, as scrolling has been 5072 ; completed. 5073 1C27 36 7F mvi m,7fh ; Terminate the last blank line, so that's 22 lines terminated. 5074 1C29 23 inx h ; HL now points to where we're going to the write DMA addres 5075 1C2A EB xchg ; Pop this into DE temporarily 5076 1C2B CD BA 1B call extra_addr ; HL <- extra line address (25th line of main screen, i.e. 5077 1C2E 7C mov a,h ; main_video + (ncols + 3 ) * 24 = 2a98h (80) or 2f78h (132) 5078 1C2F F6 70 ori 70h ; Add line attributes 5079 1C31 12 stax d ; Write high byte 5080 1C32 13 inx d ; advance to low byte of DMA address 5081 1C33 7D mov a,l ; A <- low byte of cursor line address 5082 1C34 12 stax d ; Write low byte 5083 ; 5084 1C35 CD C0 1B draw_setup_a: call wait_screen ; HL <- wait screen 5085 ; 5086 ; The final line of the screen, which for SET-UP is the column numbers, 5087 ; will go in the same place the "Wait" display is created. 5088 ; Now draw the column numbers at the bottom. We start with '1' and increment the 5089 ; number, discarding tens. Every tenth position we change between normal video 5090 ; and reverse video. 5091 1C38 3A 50 20 lda screen_cols 5092 1C3B 47 mov b,a 5093 1C3C 3E 31 mvi a,'1' 5094 1C3E 77 n_col: mov m,a ; write current digit 5095 1C3F 23 inx h 5096 1C40 4F mov c,a ; preserve number + rendition 5097 1C41 E6 0F ani 0fh ; just consider units column 5098 1C43 79 mov a,c ; restore number 5099 1C44 C2 49 1C jnz noflip ; If we've just written a '0', we'll 5100 1C47 EE 80 xri 80h ; flip between normal and reverse video 5101 1C49 3C noflip: inr a ; next column digit 5102 1C4A 27 daa ; staying decimal 5103 1C4B D2 50 1C jnc normok ; carry from daa if we are in reverse video 5104 1C4E F6 80 ori 80h ; so restore the reverse video bit 5105 1C50 E6 8F normok: ani 8fh ; remove tens digit 5106 1C52 F6 30 ori '0' ; and ASCII-ify it 5107 1C54 05 dcr b 5108 1C55 C2 3E 1C jnz n_col 5109 ; 5110 1C58 CD 9F 1C call end_screen 5111 1C5B CD CA 1B call finish_setup 5112 1C5E 3E 01 mvi a,1 ; 1 = "SET-UP A" 5113 ; 5114 ; Now we've drawn the SET-UP screen, switch the Video DMA to point to it. 5115 ; 5116 1C60 32 BE 21 display_setup: sta in_setup_a 5117 1C63 21 53 22 lxi h,setup_video ; Our address, 2253h, will be sent to Video RAM as 3000h+(2)253h 5118 1C66 7C mov a,h 5119 1C67 65 mov h,l ; Video RAM address are big-endian, so swap bytes 5120 1C68 E6 0F ani 0fh 5121 1C6A F6 30 ori 30h ; Force top bits to say: not scrolling region, double-height top half 5122 1C6C 6F mov l,a 5123 1C6D 22 04 20 shld line1_dma 5124 1C70 C9 ret 5125 ; 5126 ; display_zstr 5127 ; Display the zero-terminated string pointed to by BC, at the screen address given by HL, 5128 ; with character attributes in A. 5129 ; Returns screen address in DE, attribute address in HL 5130 ; 5131 1C71 E5 display_zstr: push h ; Given a pointer to character memory, 5132 1C72 11 00 10 lxi d,1000h ; Attributes are 1000h further on (somewhere beyond 3000h) 5133 1C75 19 dad d 5134 1C76 D1 pop d ; swap so that HL <- attribute memory, DE <- character memory 5135 1C77 F5 n_ch: push psw ; push rendition while we grab character 5136 1C78 0A ldax b ; Grab next character 5137 1C79 B7 ora a 5138 1C7A CA 86 1C jz z_term ; Zero byte to finish 5139 1C7D 12 stax d ; Place in character memory 5140 1C7E F1 pop psw ; A <- rendition 5141 1C7F 77 mov m,a ; Place rendition in attribute memory 5142 1C80 23 inx h ; inc. attr ptr 5143 1C81 13 inx d ; inc. char ptr 5144 1C82 03 inx b ; inc. string ptr 5145 1C83 C3 77 1C jmp n_ch 5146 ; 5147 1C86 F1 z_term: pop psw ; clean the last rendition push 5148 1C87 C9 ret 5149 ; 5150 ; term_line1 5151 ; term_line 5152 ; 5153 ; This routine (with two entry points) helds construct SET-UP displays by providing a fast way 5154 ; of terminating lines and writing video DMA addresses, with line attributes. 5155 ; 5156 ; DE points to just beyond the last character of the line 5157 ; B holds the upper four bits of the video address, while is scroll region flag and line attributes (size); 5158 ; term_line1 is a convenient second entry point to say "single height, single width" 5159 ; C is the number of lines to terminate, effective performing line feeds, which is used to make the blank 5160 ; space in the middle of the SET-UP screen. 5161 ; 5162 1C88 06 70 term_line1: mvi b,70h 5163 1C8A EB term_line: xchg ; DE <-< HL 5164 ; 5165 1C8B 36 7F term_loop: mvi m,7fh ; (HL) <- line terminator 5166 1C8D 23 inx h 5167 1C8E 54 mov d,h ; The characters for the next line will start two bytes 5168 1C8F 5D mov e,l ; further on, so get DE pointing to where they'll be 5169 1C90 13 inx d 5170 1C91 13 inx d 5171 1C92 7A mov a,d ; Now make up a DMA address high byte for DE 5172 1C93 E6 0F ani 0fh ; Mask off high nybble, '2', as that's a given 5173 1C95 B0 ora b ; and put in scrolling region and line attributes instead 5174 1C96 77 mov m,a ; Write DMA high byte 5175 1C97 23 inx h ; Point to DMA low byte 5176 1C98 73 mov m,e ; ... and that's our real address low byte 5177 1C99 23 inx h ; Move on, so that HL points to next character 5178 1C9A 0D dcr c ; If C is non-zero here, the next line is empty and 5179 1C9B C2 8B 1C jnz term_loop ; we'll just terminate again (line feed operation) 5180 1C9E C9 ret 5181 ; 5182 ; Write the terminator to mark this line as finished, and then write the next DMA address 5183 ; to point back to this terminator, to produce fill lines. 5184 ; 5185 1C9F 36 7F end_screen: mvi m,7fh ; write terminator 5186 1CA1 54 mov d,h ; DE <- address of terminator, so we write back to here 5187 1CA2 5D mov e,l 5188 1CA3 23 inx h ; Point to high byte of DMA address 5189 1CA4 7A mov a,d ; A <- high byte of terminator address 5190 1CA5 E6 0F ani 0fh 5191 1CA7 F6 70 ori 70h ; Write not scrolling, normal lines attributes, 2000h 5192 1CA9 77 mov m,a ; Write modified high byte 5193 1CAA 23 inx h ; Point to low byte 5194 1CAB 73 mov m,e ; Point low byte back to terminator too 5195 1CAC C9 ret 5196 ; 5197 1CAD 53 45 54 2D setup_a_string: db 'SET-UP A',0 55 50 20 41 00 5198 1CB6 54 4F 20 45 to_exit_string: db 'TO EXIT PRESS ',22h,'SET-UP',22h,0 ; asm8080 doesn't like nested quotes 58 49 54 20 50 52 45 53 53 20 22 53 45 54 2D 55 50 22 00 5199 ; 5200 ; clear_extra 5201 ; Clear the extra line 5202 ; 5203 clear_extra: 5204 1CCD CD BA 1B call extra_addr 5205 1CD0 C3 C8 0F jmp clear_row 5206 ; 5207 ; SET-UP B screen initialisation 5208 ; 5209 ; Every change made to setup fields causes this line to be redrawn. 5210 ; 5211 1CD3 CD CD 1C draw_setup_b: call clear_extra 5212 1CD6 CD C0 1B call wait_screen ; HL <- start of screen line 5213 1CD9 E5 push h 5214 1CDA 3E 4E mvi a,4eh ; Clear 78 positions 5215 1CDC CD CB 0F call clear_part_row ; on current line to blanks and default rendition 5216 1CDF E1 pop h ; HL <- start of screen line 5217 1CE0 E5 push h 5218 1CE1 23 inx h ; Leave first two locations blank for now, 5219 1CE2 23 inx h ; as we'll draw switch block numbers later. 5220 1CE3 1E 00 mvi e,0 ; E <- switch number (starting from high bit in setup_b1) 5221 1CE5 06 04 mvi b,4 ; assume 4 setup blocks 5222 1CE7 DB 42 in ior_flags ; read flag buffer 5223 1CE9 E6 08 ani iob_flags_stp ; check for STP 5224 1CEB F5 push psw 5225 1CEC CA F0 1C jz next_block 5226 1CEF 04 inr b ; If we have STP, add another switch block 5227 ; must be drawing the setup blocks loop 5228 1CF0 0E 04 next_block: mvi c,4 ; C <- switches per block 5229 1CF2 CD CC 1D nx_sw: call bit_from_setup ; A <- 1 or 0 of switch bit E 5230 1CF5 F6 B0 ori 0b0h ; ASCII-ify in reverse video 5231 1CF7 77 mov m,a ; place on screen 5232 1CF8 1C inr e ; next switch bit 5233 1CF9 23 inx h ; next column on screen 5234 1CFA 0D dcr c 5235 1CFB C2 F2 1C jnz nx_sw 5236 ; 5237 ; Gaps of four blanks between switch blocks (we fill in the switch block numbers later) 5238 1CFE 0E 04 mvi c,4 5239 1D00 36 00 blank4: mvi m,0 ; write blank to screen 5240 1D02 1C inr e ; next switch bit 5241 1D03 23 inx h ; next column on screen 5242 1D04 0D dcr c 5243 1D05 C2 00 1D jnz blank4 5244 1D08 05 dcr b ; next switch block 5245 1D09 C2 F0 1C jnz next_block 5246 ; 5247 ; Now draw the blanks between the last switch block and the baud rate fields 5248 1D0C 0E 04 mvi c,4 ; At least 4 blanks 5249 1D0E F1 pop psw 5250 1D0F F5 push psw 5251 1D10 C2 17 1D jnz draw_blanks ; We pushed the "test for STP" result, above 5252 1D13 79 mov a,c ; Without an STP switch block, 5253 1D14 C6 08 adi 8 ; we need another 8 blanks 5254 1D16 4F mov c,a 5255 1D17 36 00 draw_blanks: mvi m,0 ; write blank to screen 5256 1D19 23 inx h ; next column on screen 5257 1D1A 0D dcr c 5258 1D1B C2 17 1D jnz draw_blanks 5259 ; 5260 ; Baud rate fields 5261 1D1E 11 4B 1D lxi d,t_speed_string 5262 1D21 3A AB 21 lda tx_spd 5263 1D24 CD 61 1D call copy_baud_str 5264 ; 5265 1D27 11 56 1D lxi d,r_speed_string 5266 1D2A 3A AC 21 lda rx_spd 5267 1D2D CD 61 1D call copy_baud_str 5268 ; 5269 1D30 CD 9F 1C call end_screen 5270 ; 5271 ; Finally, draw the numbers on the switch blocks 5272 1D33 11 08 00 lxi d,8 ; 8 characters between switch block numbers 5273 1D36 0E 04 mvi c,4 ; Again, 4 blocks by default 5274 1D38 F1 pop psw ; Flags NZ means "got STP" 5275 1D39 CA 3D 1D jz no_block_5 5276 1D3C 0C inr c ; Add that fifth block for STP 5277 1D3D 3E 31 no_block_5: mvi a,'1' ; Switch block number one coming up 5278 1D3F E1 pop h ; HL <- start of screen line 5279 1D40 77 next_block2: mov m,a 5280 1D41 3C inr a ; next block number 5281 1D42 19 dad d ; skip past block bits we drew earlier 5282 1D43 0D dcr c 5283 1D44 C2 40 1D jnz next_block2 5284 1D47 AF xra a ; A <- 0 means "in SET-UP B" 5285 1D48 C3 60 1C jmp display_setup 5286 ; 5287 1D4B 20 20 20 54 t_speed_string: db ' T SPEED ' 20 53 50 45 45 44 20 5288 1D56 20 20 20 52 r_speed_string: db ' R SPEED ' 20 53 50 45 45 44 20 5289 5290 ; copy_baud_str 5291 ; Comms speeds are encoded internally as values 0 to 15 in the upper 4 bits 5292 ; of the locations tx_spd and rx_spd. 5293 ; The baud strings are 5 characters long, stored sequentially in the table 5294 ; below this routine, without terminators, so this routine copies the 5295 ; appropriate string to the buffer pointed to by HL. 5296 ; 5297 ; The placement of these values in the upper 4 bits of a byte is the same as the 5298 ; switch block settings that are also on the SET-UP B screen. It feels as if they 5299 ; might originally have been planned as switch block settings. 5300 ; 5301 1D61 4F copy_baud_str: mov c,a ; C <- speed value (memcopy corrupts A) 5302 1D62 06 0B mvi b,11 ; speed strings above are 11 characters long 5303 1D64 CD 8B 03 call memcopy ; place in buffer 5304 1D67 79 mov a,c ; A <- speed value, in upper 4 bits 5305 1D68 0F rrc ; To avoid shifting this value down 4 bits and 5306 1D69 0F rrc ; multiplying by 5 (x * 4 + x * 1), we combine 5307 1D6A 4F mov c,a ; the shift and multiply by calculating 5308 1D6B 0F rrc ; x / 4 + x / 16. Same result but shorter by 5309 1D6C 0F rrc ; four instructions. 5310 1D6D 81 add c ; 5311 1D6E 4F mov c,a ; 5312 1D6F 06 00 mvi b,0 ; BC <- offset of string from baud_strings 5313 1D71 11 7C 1D lxi d,baud_strings 5314 1D74 EB xchg ; Swap HL <-< DE because can only add BC to HL 5315 1D75 09 dad b ; HL <- string location 5316 1D76 EB xchg ; HL <-< DE because DE is "copy from" pointer 5317 1D77 06 05 mvi b,5 ; 5 chars to copy 5318 1D79 C3 8B 03 jmp memcopy ; copy and return 5319 ; 5320 ; These strings aren't delimited because they are all five characters long 5321 1D7C 20 20 20 35 baud_strings: db ' 50' 30 5322 1D81 20 20 20 37 db ' 75' 35 5323 1D86 20 20 31 31 db ' 110' 30 5324 1D8B 20 20 31 33 db ' 134' 34 5325 1D90 20 20 31 35 db ' 150' 30 5326 1D95 20 20 32 30 db ' 200' 30 5327 1D9A 20 20 33 30 db ' 300' 30 5328 1D9F 20 20 36 30 db ' 600' 30 5329 1DA4 20 31 32 30 db ' 1200' 30 5330 1DA9 20 31 38 30 db ' 1800' 30 5331 1DAE 20 32 30 30 db ' 2000' 30 5332 1DB3 20 32 34 30 db ' 2400' 30 5333 1DB8 20 33 36 30 db ' 3600' 30 5334 1DBD 20 34 38 30 db ' 4800' 30 5335 1DC2 20 39 36 30 db ' 9600' 30 5336 1DC7 31 39 32 30 db '19200' 30 5337 5338 ; bit_from_setup 5339 ; Retrieve bit E from setup blocks 1 to 5, numbered with bit 0 being bit 7 5340 ; of setup block 1. 5341 ; Returns 1 or 0 in A for bit state (don't need a mask from this routine, 5342 ; as we're just going to print the result.) 5343 ; 5344 1DCC E5 bit_from_setup: push h 5345 1DCD 21 A6 21 lxi h,setup_b1 5346 1DD0 53 mov d,e ; It's a quirk of col_in_bits that the bit number 5347 1DD1 7B mov a,e ; must be provided in A and D 5348 1DD2 CD 2B 0E call col_in_bits 5349 1DD5 A6 ana m 5350 1DD6 E1 pop h 5351 1DD7 C8 rz 5352 1DD8 3E 01 mvi a,1 5353 1DDA C9 ret 5354 ; 5355 ; Combine rx_spd and tx_spd configuration locations into a single byte, which the 5356 ; Technical Manual says allows both speeds to be altered by a single write to the PUSART. 5357 ; 5358 1DDB 3A AC 21 program_pusart: lda rx_spd ; A <- receive speed, in bits 7-4 5359 1DDE E6 F0 ani 0f0h ; mask and shift down to low 4 bits 5360 1DE0 0F rrc 5361 1DE1 0F rrc 5362 1DE2 0F rrc 5363 1DE3 0F rrc 5364 1DE4 47 mov b,a 5365 1DE5 3A AB 21 lda tx_spd ; A <- transmit speed, in bits 7-4 5366 1DE8 E6 F0 ani 0f0h ; mask and combine with receive speed 5367 1DEA B0 ora b 5368 1DEB 32 58 21 sta tx_rx_speed ; store combined speed byte 5369 1DEE E6 F0 ani 0f0h ; Now look at just transmit speed 5370 1DF0 FE 20 cpi 20h ; Is it 110 baud? 5371 1DF2 3A A4 21 lda pusart_mode 5372 1DF5 CA FF 1D jz two_stop ; jump if 110 baud 5373 1DF8 E6 3F ani 3fh 5374 1DFA F6 80 ori 80h ; Set 1½ stop bits (according to TM Figure 4-3-3 and 8251A datasheet) 5375 1DFC C3 03 1E jmp stow_m ; Why not one stop bit? 5376 ; 5377 1DFF E6 3F two_stop: ani 3fh ; Set two stop bits for transmit at low speed (TM §1.1) 5378 1E01 F6 C0 ori 0c0h 5379 1E03 32 A4 21 stow_m: sta pusart_mode 5380 1E06 DB 42 in ior_flags 5381 1E08 E6 08 ani iob_flags_stp 5382 1E0A CA 19 1E jz add_parity 5383 1E0D 21 A7 21 lxi h,setup_b2 5384 1E10 7E mov a,m 5385 1E11 F6 10 ori sb2_autoxon ; If we have STP, force Auto XON 5386 1E13 77 mov m,a 5387 1E14 3E 6E mvi a,6eh ; And set serial to 1 stop bit, no parity, 8 data bits 5388 1E16 C3 31 1E jmp stow_m2 5389 ; 5390 ; Make another PUSART mode by combining parity information and other bits 5391 1E19 3A A9 21 add_parity: lda setup_b4 5392 1E1C E6 C0 ani sb4_paritybits 5393 1E1E 47 mov b,a ; B <- |pe|p1| | | | | | | 5394 1E1F 3A A9 21 lda setup_b4 5395 1E22 E6 20 ani sb4_8bits ; A <- | | |8b| | | | | | 5396 1E24 0F rrc ; A <- | | | |8b| | | | | 5397 1E25 F6 20 ori 20h 5398 1E27 B0 ora b ; A <- |pe|p1| 1|8b| | | | | 5399 1E28 0F rrc 5400 1E29 0F rrc 5401 1E2A 47 mov b,a ; B <- | | |pe|p1| 1|8b| | | 5402 1E2B 3A A4 21 lda pusart_mode ; grab existing mode 5403 1E2E E6 C3 ani 0c3h ; and preserve opposite bits from B 5404 1E30 B0 ora b ; combine 5405 1E31 32 A4 21 stow_m2: sta pusart_mode ; place back 5406 1E34 3A A9 21 lda setup_b4 5407 1E37 E6 10 ani sb4_50Hz 5408 1E39 CA 3E 1E jz is_60 5409 1E3C 3E 10 mvi a,10h 5410 1E3E C6 20 is_60: adi 20h ; A <- 20h for 60Hz, 30h for 50Hz 5411 1E40 32 7C 20 sta refresh_rate ; Ends up in DC011 video processor 5412 1E43 3A A6 21 lda setup_b1 5413 1E46 E6 10 ani sb1_curblock 5414 1E48 CA 4D 1E jz is_und 5415 1E4B 3E 01 mvi a,1 5416 1E4D CD 6E 1E is_und: call cursor_type ; A <- 1 for block cursor, 0 for underline 5417 1E50 CD 29 03 call reset_pusart 5418 1E53 3A F8 20 lda curs_col 5419 1E56 FE 15 cpi 15h 5420 1E58 CC 42 03 cz update_dc011 ; Update DC011 for interlace setting 5421 1E5B FE 1D cpi 1dh 5422 1E5D CC 42 03 cz update_dc011 ; Update DC011 for power hertz 5423 1E60 FE 0C cpi 0ch ; 5424 1E62 CC F2 0B cz set_charsets ; Update charsets on ANSI/VT52 change. There is no difference 5425 ; between charset designation in ANSI/VT52 mode, but this 5426 ; action merely mirrors the SM/RM ANSI sequence action 5427 ; (see ansi_mode) 5428 1E65 FE 12 cpi 12h 5429 1E67 CC F2 0B cz set_charsets ; Update charsets on ASCII/UK change 5430 1E6A CD 36 16 call move_updates 5431 1E6D C9 ret 5432 ; 5433 ; cursor_type 5434 ; 5435 ; Called with A = 0 for underline cursor, 1 = block cursor. 5436 ; Sets whether the basic character attribute has to match the cursor selection (not required if AVO 5437 ; is present), and flags the cursor selection to video processor through basic_rev_video (which gets 5438 ; sent to DC012.) 5439 ; 5440 1E6E B7 cursor_type: ora a ; NZ if block cursor 5441 1E6F CA 79 1E jz und_cursor 5442 1E72 32 5B 21 sta basic_rev_video ; block cursor selection will set basic attribute to reverse video 5443 1E75 AF xra a 5444 1E76 C3 94 1E jmp set_curs_rend 5445 ; 5446 1E79 DB 42 und_cursor: in ior_flags 5447 1E7B E6 02 ani iob_flags_avo 5448 1E7D C2 90 1E jnz und_curs_base 5449 1E80 32 59 21 sta curs_char_rend ; no character attribute (bit 7) change for cursor (because AVO present) 5450 1E83 3E 01 mvi a,1 5451 1E85 32 5B 21 sta basic_rev_video 5452 1E88 3E 02 mvi a,2 5453 1E8A 32 5A 21 sta curs_attr_rend 5454 1E8D C3 6B 03 jmp update_dc012 5455 ; 5456 ; Without AVO, picking a cursor type will also set the basic attribute for characters 5457 ; 5458 1E90 AF und_curs_base: xra a 5459 1E91 32 5B 21 sta basic_rev_video 5460 1E94 32 5A 21 set_curs_rend: sta curs_attr_rend 5461 1E97 3E 80 mvi a,80h ; Set up character attribute use for cursor 5462 1E99 32 59 21 sta curs_char_rend 5463 1E9C C3 6B 03 jmp update_dc012 5464 ; 5465 ; The first character of an answerback message comes here, in order to store it as the answerback 5466 ; delimiters. The action then changes to answer_action for all the rest, up to a matching delimiter. 5467 ; 5468 1E9F CD DD 1E aback_entry: call answer_print 5469 1EA2 21 7B 21 lxi h,aback_buffer 5470 1EA5 77 mov m,a ; First character becomes the delimiter 5471 1EA6 23 inx h 5472 1EA7 22 B4 21 shld answerback_ptr 5473 1EAA 47 mov b,a ; B <- delimiter 5474 1EAB 11 14 00 lxi d,20 5475 1EAE CD 83 10 call memset ; Fill the answerback buffer with the delimiter 5476 1EB1 21 B8 1E lxi h,answer_action ; Switch to the "rest of the message" handler 5477 1EB4 22 40 21 shld char_action 5478 1EB7 C9 ret 5479 ; 5480 ; answer_action 5481 ; 5482 ; This routine handles keys that are pressed while we're entering an answerback message. 5483 ; 5484 1EB8 CD DD 1E answer_action: call answer_print 5485 1EBB 2A B4 21 lhld answerback_ptr 5486 1EBE 47 mov b,a ; B <- character pressed 5487 1EBF 3A 7B 21 lda aback_buffer ; A <- delimiter 5488 1EC2 4F mov c,a ; C <- delimiter 5489 1EC3 B8 cmp b ; Have we had the delimiter again? 5490 1EC4 CA D3 1E jz delim_found 5491 1EC7 7D mov a,l 5492 1EC8 FE 90 cpi LOW aback_bufend ; have we exhausted the buffer? 5493 1ECA CA D3 1E jz delim_found 5494 1ECD 70 mov m,b ; add character to buffer 5495 1ECE 23 inx h 5496 1ECF 22 B4 21 shld answerback_ptr 5497 1ED2 C9 ret 5498 ; 5499 1ED3 71 delim_found: mov m,c ; delimit the buffer 5500 1ED4 CD CD 1C call clear_extra 5501 1ED7 CD 4B 09 call c0_return 5502 1EDA C3 B0 1B jmp setup_ready 5503 ; 5504 ; answer_print: 5505 ; 5506 ; Going to print_char, but making sure that control characters are shown as a diamond. 5507 ; 5508 1EDD F5 answer_print: push psw 5509 1EDE FE 20 cpi 20h 5510 1EE0 D2 E5 1E jnc skip_ctrl_sub 5511 1EE3 3E 01 mvi a,1 ; A <- diamond glyph to represent control characters 5512 1EE5 CD E3 05 skip_ctrl_sub: call print_nomap 5513 1EE8 F1 pop psw 5514 1EE9 C9 ret 5515 ; 5516 ; disp_char_col 5517 ; Display the character in register B at the column in register 5518 ; A on the current line. 5519 ; 5520 1EEA E5 disp_char_col: push h 5521 1EEB 21 4E 20 lxi h,line_25_addr 5522 1EEE 4F mov c,a ; C <- column 5523 1EEF 3A A2 21 lda columns_132 5524 1EF2 B7 ora a 5525 1EF3 C2 FC 1E jnz c132 5526 1EF6 79 mov a,c ; A <- column 5527 1EF7 FE 50 cpi 80 5528 1EF9 F2 0F 1F jp col_oor ; column <= 80 in 80-column mode? Exit 5529 1EFC 7E c132: mov a,m ; A <- low byte of addr \ 5530 1EFD 23 inx h ; HL <- point to high byte | load_hl_from_hl, unrolled 5531 1EFE 66 mov h,m ; H <- high byte of addr | 5532 1EFF 6F mov l,a ; L <- low byte / 5533 1F00 7C mov a,h ; \ 5534 1F01 E6 0F ani 0fh ; | screen_addr, unrolled 5535 1F03 F6 20 ori 20h ; | 5536 1F05 67 mov h,a ; / 5537 1F06 79 mov a,c ; A <- column 5538 1F07 85 add l ; \ 5539 1F08 6F mov l,a ; | add A to HL 5540 1F09 D2 0D 1F jnc no_h_inc ; | 5541 1F0C 24 inr h ; / 5542 1F0D 70 no_h_inc: mov m,b ; Place character on screen 5543 1F0E 78 mov a,b ; A <- character 5544 1F0F E1 col_oor: pop h 5545 1F10 C9 ret 5546 ; 5547 ; Called from DECTST 5548 1F11 D5 data_loop_test: push d 5549 1F12 AF xra a 5550 1F13 32 A5 21 sta local_mode 5551 1F16 06 00 mvi b,0 ; Start with lowest speed 5552 1F18 78 test_next_spd: mov a,b 5553 1F19 32 58 21 sta tx_rx_speed ; Storing same speed as both transmit and receive 5554 1F1C D3 02 out iow_baud_rate 5555 1F1E 0E 01 mvi c,1 ; Going to send 01h, 02h, 04h, 08h, 10h, 20h, 40h 5556 1F20 79 test_tx_ch: mov a,c 5557 1F21 D3 00 out iow_pusart_data ; send a character 5558 1F23 21 00 C0 lxi h,0c000h ; a long counter 5559 1F26 C5 test_wait_ch: push b 5560 1F27 E5 push h 5561 1F28 CD 75 06 call test_rx_q ; Set NZ for characters available 5562 1F2B E1 pop h 5563 1F2C C1 pop b 5564 1F2D C2 48 1F jnz test_got_ch 5565 1F30 23 inx h 5566 1F31 7C mov a,h 5567 1F32 B5 ora l 5568 1F33 C2 26 1F jnz test_wait_ch 5569 1F36 3E 05 data_failed: mvi a,5 ; command byte: rx enable, tx enable 5570 1F38 D3 01 out iow_pusart_cmd 5571 1F3A 3A 58 21 lda tx_rx_speed 5572 1F3D D3 02 out iow_baud_rate 5573 1F3F 3E 25 mvi a,25h ; Into local mode and (briefly) light LEDs L2 and L4 5574 1F41 32 A5 21 sta local_mode ; (these are cleared during see after_tests) 5575 1F44 AF xra a 5576 1F45 37 stc ; set "test failed" 5577 1F46 D1 pop d 5578 1F47 C9 ret 5579 ; 5580 1F48 E6 7F test_got_ch: ani 7fh ; Ignore parity 5581 1F4A B9 cmp c ; check received character is the same 5582 1F4B C2 36 1F jnz data_failed 5583 1F4E 79 mov a,c 5584 1F4F 07 rlc ; rotate set bit to next higher position 5585 1F50 FE 80 cpi 80h 5586 1F52 4F mov c,a 5587 1F53 C2 20 1F jnz test_tx_ch 5588 1F56 78 mov a,b ; A <- speed 5589 1F57 C6 11 adi 11h ; advance tx and rx speed together 5590 1F59 47 mov b,a 5591 1F5A FE 10 cpi 10h ; Last speed is 0ffh (+ 11h = 10h) 5592 1F5C C2 18 1F jnz test_next_spd 5593 1F5F AF xra a ; clear carry - "test passed" 5594 1F60 D1 pop d 5595 1F61 C9 ret 5596 ; 5597 ; modem_test 5598 ; For this test, try commanding 7 combinations of three modem signals (except for all off) 5599 ; and read back the resultant signals that should come from a modem or, in this case, 5600 ; an appropriate loopback connector. 5601 ; 5602 1F62 D5 modem_test: push d 5603 1F63 16 07 mvi d,7 ; D <- signal combination 5604 1F65 7A modem_loop: mov a,d 5605 1F66 CD 7B 1F call modem_signals ; command signals through PUSART and NVR latch 5606 1F69 CD A1 1F call read_modem ; and read the response 5607 1F6C BA cmp d ; which should be identical 5608 1F6D C2 36 1F jnz data_failed 5609 1F70 15 dcr d 5610 1F71 C2 65 1F jnz modem_loop 5611 1F74 3E 05 mvi a,5 ; command byte: rx enable, tx enable 5612 1F76 D3 01 out iow_pusart_cmd ; PUSART back to normal 5613 1F78 AF xra a 5614 1F79 D1 pop d 5615 1F7A C9 ret 5616 ; 5617 ; modem_signals 5618 ; 5619 ; Register A is a modem signal enable mask: 5620 ; 01h - RTS (ready to send) 5621 ; 02h - SPDS (speed select) 5622 ; 04h - DTR (data terminal ready) 5623 ; 5624 ; RTS and DTR are controlled by sending a PUSART command word. 5625 ; SPDS is an output from the NVR latch. 5626 ; 5627 ; NVR latch bit 5 drives BV2 /SPDS, which goes to the EIA pin 11, "speed select" 5628 ; 5629 1F7B 47 modem_signals: mov b,a 5630 1F7C E6 02 ani 2 ; A <- bit 1 5631 1F7E 0F rrc ; A <- bit 0 5632 1F7F 0F rrc ; A <- bit 7 5633 1F80 0F rrc ; A <- bit 6 5634 1F81 0F rrc ; A <- bit 5 (20h / 00h) 5635 1F82 F6 10 ori 10h ; A <- bit 5 + bit 4 (30h / 10h) 5636 1F84 32 C9 21 sta vint_nvr 5637 1F87 D3 62 out iow_nvr_latch 5638 1F89 0E 05 mvi c,5 ; command byte: rx enable, tx enable 5639 1F8B 78 mov a,b 5640 1F8C E6 01 ani 1 5641 1F8E CA 93 1F jz no_rts 5642 1F91 0E 25 mvi c,25h ; command byte: RTS (request to send), rx enable, tx enable 5643 1F93 78 no_rts: mov a,b 5644 1F94 E6 04 ani 4 5645 1F96 CA 9D 1F jz no_dtr 5646 1F99 79 mov a,c 5647 1F9A F6 02 ori 2 ; mix in DTR (data terminal ready) 5648 1F9C 4F mov c,a 5649 1F9D 79 no_dtr: mov a,c 5650 1F9E D3 01 out iow_pusart_cmd 5651 1FA0 C9 ret 5652 ; 5653 ; read_modem 5654 ; 5655 ; Reads modem buffer (read only I/O port 22h) 5656 ; Not documented in TM 5657 ; Looking at print set MP-00633-00 sheet 3 of 6, Modem Buffer is E41, an 81LS97 addressed 5658 ; by signal BV2 MODEM RD L 5659 ; DB 07 H is /CTS 5660 ; DB 06 H is /SPDI 5661 ; DB 05 H is /RI -- ignored here 5662 ; DB 04 H is /CD 5663 ; 5664 ; This is designed to return a mask that matches the number passed to modem_signals, i.e. 5665 ; 01h will indicate that RTS (ready to send) worked, 5666 ; because the modem returns CTS (clear to send) and CD (carrier detect) 5667 ; 02h will indicate that SPDS (speed select) worked, 5668 ; because the modem returns SPDI (speed indicator) 5669 ; 04h will indicate that DTR (data terminal ready) worked 5670 ; because the PUSART status indicates DSR (data set ready) 5671 ; 5672 1FA1 DB 22 read_modem: in ior_modem 5673 1FA3 47 mov b,a ; B <- modem signals 5674 1FA4 0E 01 mvi c,1 ; Assume RTS worked 5675 1FA6 78 mov a,b 5676 1FA7 E6 90 ani 90h ; mask /CTS and /CD 5677 1FA9 CA B3 1F jz test_spdi 5678 1FAC FE 90 cpi 90h ; expecting both signals in same state 5679 1FAE 3E FF mvi a,0ffh 5680 1FB0 C0 rnz ; return with failure if not 5681 1FB1 0E 00 mvi c,0 ; if CTS and CD are not enabled, mark 0 so far 5682 1FB3 78 test_spdi: mov a,b ; A <- modem signals 5683 1FB4 E6 40 ani 40h ; mask /SPDI 5684 1FB6 CA BD 1F jz test_ri ; not enabled 5685 1FB9 79 mov a,c 5686 1FBA F6 02 ori 2 ; mix in "SPDS worked" to result 5687 1FBC 4F mov c,a 5688 1FBD DB 01 test_ri: in ior_pusart_cmd ; read PUSART status byte 5689 1FBF 0F rrc ; Looking for DSR (bit 7) and we want it to be low 5690 1FC0 0F rrc ; to 5, 5691 1FC1 2F cma ; invert 5692 1FC2 E6 20 ani 20h ; and mask 5693 1FC4 A8 xra b ; xor with /RI from modem signals 5694 1FC5 E6 20 ani 20h ; and mask again (making the first mask pointless) 5695 1FC7 3E FF mvi a,0ffh ; failure if they weren't in the same state 5696 1FC9 C0 rnz 5697 1FCA 78 mov a,b ; A <- modem signals 5698 1FCB E6 20 ani 20h ; if /RI not found, skip marking DTR 5699 1FCD C2 D4 1F jnz modem_result 5700 1FD0 79 mov a,c 5701 1FD1 F6 04 ori 4 ; mix in "DTR worked" to result 5702 1FD3 4F mov c,a 5703 1FD4 B7 modem_result: ora a 5704 1FD5 79 mov a,c 5705 1FD6 C9 ret 5706 ; 5707 ; Zeroes till end of ROM ... 5708 ; 5709 1F FF org 1fffh 5710 1FFF 00 db 0 5711 5712 ; C0 Codes 5713 00 08 C0_BS equ 08h 5714 00 09 C0_HT equ 09h 5715 00 0A C0_LF equ 0ah 5716 00 0D C0_CR equ 0dh 5717 00 10 C0_DLE equ 10h 5718 00 11 C0_XON equ 11h 5719 00 13 C0_XOFF equ 13h 5720 00 18 C0_CAN equ 18h 5721 00 1A C0_SUB equ 1ah 5722 00 1B C0_ESC equ 1bh 5723 5724 ; I/O Ports 5725 ; From TM Table 4-2-2 "List of Hex I/O Addresses", p.4-17 5726 ; 5727 00 00 ior_pusart_data equ 0 5728 00 00 iow_pusart_data equ 0 5729 5730 00 01 ior_pusart_cmd equ 1 5731 00 01 iow_pusart_cmd equ 1 5732 5733 00 02 iow_baud_rate equ 2 5734 5735 00 22 ior_modem equ 22h 5736 5737 ; This is the flags buffer. This defined in the VT100 Print Set, MP-00633-00, 5738 ; VT100 Basic Video (Sheet 6 of 6). It is an 81LS97 chip, buffering the signal 5739 ; names (and active H/L) shown in the comments 5740 ; TM §4.6.2.7 says that LBA 7 is used for clocking the NVR, and the print set shows this 5741 00 42 ior_flags equ 42h 5742 00 01 iob_flags_xmit equ 01h ; BV3 XMIT FLAG H 5743 00 02 iob_flags_avo equ 02h ; BV1 ADVANCED VIDEO L 5744 00 04 iob_flags_gpo equ 04h ; BV1 GRAPHICS FLAG L 5745 00 08 iob_flags_stp equ 08h ; BV3 OPTION PRESENT H 5746 00 10 iob_flags_unk4 equ 10h ; BV4 EVEN FIELD L 5747 00 20 iob_flags_nvr equ 20h ; BV2 NVR DATA H 5748 00 40 iob_flags_lba7 equ 40h ; LBA 7 H 5749 00 80 iob_flags_kbd equ 80h ; BV6 KBD TBMT H 5750 5751 ; On Print Set MP-00633-00, this appears to drive the D/A latch E49, a 74LS174 device, 5752 ; with five bits of data and a sixth "init" value. 5753 ; POST sends 0f0h and the other writes are from a scratch location that goes from 5754 ; 0 (brightest) to 1fh (dimmest), making it look as if the POST write says "mid-brightness, init" 5755 ; 5756 00 42 iow_brightness equ 42h 5757 5758 ; iow_nvr_latch 5759 ; 5760 ; 7 6 5 4 3 2 1 0 5761 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 5762 ; | | |/SPDS| --- | C3 | C2 | C1 |data | 5763 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 5764 ; 5765 ; Not defined in TM - this from print set MP-00633-00. 5766 ; Bit 4 is not connected. TM §4.2.7 says it was intended to disable receiver interrupts, 5767 ; though it isn't used. In the earliest print set available, this drives a signal 5768 ; called BV2 REC INT ENA H, which is ANDed with the "Receive Ready" BV3 REC FLAG H 5769 ; signal from the PUSART. 5770 ; 5771 ; Bit 5 drives BV2 /SPDS (active low), which goes to the EIA connector as "speed select" 5772 ; Other modem signals are driven by PUSART command words. 5773 ; 5774 ; The NVR latch is often driven (early initialisation and modem test, for instance) with 5775 ; the command bits C3, C2 and C1 set to 0, the ER1400 "accept data" which the Technical 5776 ; Manual explains as a handy idle command because the all zeroes protects the NVR from 5777 ; spontaneous writes during power down. 5778 5779 00 62 iow_nvr_latch equ 62h 5780 5781 00 82 ior_keyboard equ 82h 5782 5783 ; iow_keyboard 5784 ; 5785 ; 7 6 5 4 3 2 1 0 5786 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 5787 ; | | | | |L1 on|L2 on|L3 on|L4 on| 5788 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 5789 ; | | | `-- keyboard locked LED 5790 ; | | `-- online/local LED 5791 ; | `-- start scan 5792 ; `-- speaker click 5793 5794 00 82 iow_keyboard equ 82h 5795 00 01 iow_kbd_led4 equ 01h 5796 00 02 iow_kbd_led3 equ 02h 5797 00 04 iow_kbd_led2 equ 04h 5798 00 08 iow_kbd_led1 equ 08h 5799 00 10 iow_kbd_locked equ 10h 5800 00 20 iow_kbd_local equ 20h 5801 00 40 iow_kbd_scan equ 40h 5802 00 80 iow_kbd_click equ 80h 5803 5804 ; Detailed in TM p.4-70 5805 00 A2 iow_dc012 equ 0a2h 5806 5807 00 C2 iow_dc011 equ 0c2h 5808 5809 00 E2 iow_graphics equ 0e2h 5810 5811 ; 5812 ; Equates for RAM locations, 2000h - 2bffh (3 KiB) 5813 ; 5814 ; The Video Processor is hard-wired to DMA from this region, always starting a frame at 2000h. 5815 ; 5816 ; Memory layout 5817 1F FF rom_top equ 1fffh 5818 ; 5819 20 00 ram_start equ 2000h ; The VT100 without AVO has 3K of RAM, from 2000h to 2bffh 5820 2B FF ram_top equ 2bffh 5821 30 00 avo_ram_start equ 3000h 5822 3F FF avo_ram_top equ 3fffh ; AVO adds another 4K of RAM 5823 5824 20 01 line0_dma equ 2001h 5825 20 04 line1_dma equ 2004h 5826 ; There are two names for this location because, although it is stack_top, 5827 ; the stack always decrements before a push, so 204eh itself is available for some other 5828 ; purpose, and we'd like a name for that. 5829 20 4E stack_top equ 204eh 5830 ; 5831 ; Extra line address, i.e. the line that is available to be scrolled onto the screen. 5832 ; From soon after reset, set to FA98, which is address of 25th (spare) line. 5833 ; If we go to bottom of screen, and execute LF, it becomes F2D0, i.e. the old 1st line. 5834 ; 5835 20 4E line_25_addr equ 204eh 5836 ; screen_cols is either 80 or 132 5837 20 50 screen_cols equ 2050h 5838 ; 5839 ; zeroed on width change. read in vertint 5840 ; When processing index_down, when smooth scrolling is enabled and we reach the bottom margin, 5841 ; this gets set to 1 and when processing reverse index and the top margin is reached, it'll get 5842 ; set to -1, to indicate the direction of the pending scroll. 5843 ; Pending scrolls are acted upon during vertical interrupts, as they will then cause single scan line 5844 ; changes until the extra line has fully scrolled into view (over 10 scans). 5845 20 51 scroll_pending equ 2051h 5846 ; stores pointer to screen DMA address (can only see a single write, and no reads) 5847 ; this name will have _dma on the end 5848 20 52 UNREAD_X2052 equ 2052h 5849 ; Pointer to 25th line DMA address in screen RAM. Never read; confirmed by tests. 5850 20 54 UNREAD_X2054 equ 2054h 5851 ; stores a line address for shuffle 5852 20 56 shufdt2 equ 2056h 5853 ; stores a line address for shuffle 5854 20 58 shufad2 equ 2058h 5855 ; Scan line of current scroll (to DC012 scroll latch). Zeroed on width change 5856 20 5A scroll_scan equ 205ah 5857 ; scroll direction, as initialised to 01h but could be 99h (=-1 daa) 5858 20 5B scroll_dir equ 205bh 5859 ; Buffer where cursor and function keys are prepared for sending to host 5860 ; offset into buffer is curkey_qcount 5861 20 5C curkey_queue equ 205ch ; 10 locs 5862 ; updated in vertical int, non-zero seems to hold up width change (fast loop, so must be waiting 5863 ; on vertical int to update it) Perhaps scroll to finish? 5864 20 65 smooth_scroll equ 2065h 5865 20 66 UNUSED_X2066 equ 2066h 5866 ; scan code of the latest new key seen in silo 5867 20 67 new_key_scan equ 2067h 5868 ; key_flags low 3 bits are number of keys waiting to be processed, which 5869 ; can only be up to 4. 5870 ; Bits 4-7 are the "shift" key flags, which get processed by the interrupt 5871 ; routine instead of being buffered. 5872 ; 5873 ; 7 6 5 4 3 2 1 0 5874 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 5875 ; |EOS |CAPS |SHIFT|CTRL | | OVR key count | 5876 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 5877 ; 5878 20 68 key_flags equ 2068h 5879 00 10 key_flag_ctrl equ 10h 5880 00 20 key_flag_shift equ 20h 5881 00 40 key_flag_caps equ 40h 5882 00 80 key_flag_eos equ 80h 5883 ; last_key_flags are updated at the end of the keyboard processing 5884 20 69 last_key_flags equ 2069h 5885 ; Space for 4 keys pending processing 206ah to 206dh. TM says "SILO" throughout 5886 20 6A key_silo equ 206ah ; 4 locs 5887 ; Buffer for previous keys which we'll attempt to match with silo 5888 20 6E key_history equ 206eh ; 4 locs 5889 20 72 key_rpt_timer equ 2072h 5890 ; used while processing keys - initialised to 2 and decremented 5891 20 73 key_rpt_pause equ 2073h 5892 ; incremented every time we send byte to keyboard 5893 20 74 num_kbd_updates equ 2074h 5894 ; storing a line address, possibly also to do with shuffle 5895 20 75 shufad1 equ 2075h 5896 ; Confirmed unwritten by tests. Reading non-zero causes premature exit from update_kbd 5897 20 77 UNWRIT_X2077 equ 2077h 5898 ; C0 BEL and margin bell add to this location, and it is processed in vertical interrupt with other keyboard bits 5899 20 78 bell_duration equ 2078h 5900 ; 5901 ; At start-up, GPO presence is read from the I/O flags buffer and sets bit 0 of these flags. 5902 ; Bit 7 is set if we are currently passing characters to the GPO, between DECGON and DECGOFF. 5903 ; No other bits are used. 5904 20 79 gpo_flags equ 2079h 5905 ; tested and cleared during vert. int. 5906 ; shuffle cannot happen until this is nonzero. made nonzero when extra line is connected 5907 20 7A shuffle_ready equ 207ah 5908 ; checked in proc_func_key 5909 ; 0ffh = in setup, 0 = not in setup 5910 20 7B in_setup equ 207bh 5911 ; refresh_rate is 20h for 60Hz, 30h for 50Hz 5912 20 7C refresh_rate equ 207ch 5913 00 20 refresh_60Hz equ 20h 5914 00 30 refresh_50Hz equ 30h 5915 20 7D inter_chars equ 207dh ; intermediate chars are added together in this byte 5916 20 7E final_char equ 207eh ; final character of ESC or CSI sequence 5917 ; Count of vertical interrupts, at 60 Hz, used for timing BREAK and synchronising switch to SET-UP screen 5918 20 7F frame_count equ 207fh 5919 ; Received characters go into this buffer, at the location 2000h + rx_head, 5920 ; and they are pulled out from 2000h + rx_tail. Both rx_head and rx_tail are 5921 ; initialised to 80h, and when they increment past 0bfh, that's where they 5922 ; wrap back to, so the buffer is 64 characters long. 5923 20 80 rx_buffer equ 2080h 5924 ; initialised to 80h, seems to be where next received character is placed, 5925 ; and will run up to 0bfh, then wrap back to 80h 5926 ; Receiver int 5927 20 C0 rx_head equ 20c0h 5928 20 C1 rx_tail equ 20c1h 5929 ; 20c2 is a table of 25 (including pline_extra) addresses of physical lines in the screen RAM. 5930 ; X20de is because it is the address of row 14 (0-based), so for a 14 line 5931 ; display it is the line beyond the last on the screen. Looking at the code 5932 ; around X117e, pline_extra seems to have a similar function. 5933 20 C2 pline_addr equ 20c2h ; 28 locs 5934 ; address of last line in screen ram? 5935 20 F2 pline_extra equ 20f2h 5936 ; move_updates seems to place this where cursor *was* 5937 20 F4 char_und_curs equ 20f4h 5938 20 F5 rend_und_curs equ 20f5h 5939 ; Must be cursor address in screen RAM 5940 20 F6 cursor_address equ 20f6h 5941 ; The graphics state that DECSC saves, and DECRC restores, is stored from 20f8h to 2101h. 5942 ; Not all of these bytes have been decoded yet. 5943 20 F8 gfx_state equ 20f8h ; live graphics state (cursor position, SGR, etc.) 5944 20 F8 curs_col equ 20f8h ; first location of state seems to be X (column) 5945 20 F9 curs_row equ 20f9h ; this is absolute cursor row (regardless of origin mode) 5946 ; Default rendition is 0f7h (see sgr_off) 5947 ; blink masks with 0feh (11111110b) 5948 ; underscore masks with 0fdh (11111101b) 5949 ; bold masks with 0fbh (11111011b) 5950 20 FA char_rend equ 20fah 5951 00 FE rend_blink equ 0feh 5952 00 FD rend_under equ 0fdh 5953 00 FB rend_bold equ 0fbh 5954 ; reverse video is treated differently, with normal video being 5955 ; 0 and reverse video setting this to 80h. 5956 20 FB char_rvid equ 20fbh 5957 20 FC gl_invocation equ 20fch ; 0 or 1, depending on whether G0 or G1 is invoked into GL 5958 20 FD g0_charset equ 20fdh 5959 20 FE g1_charset equ 20feh 5960 20 FF g2_charset equ 20ffh ; can't designate a charset into G2 or G3 but they are initialised 5961 21 00 g3_charset equ 2100h ; with the same defaults as G0 and G1 5962 ; origin mode DECOM 1/0 5963 21 01 origin_mode equ 2101h 5964 5965 21 02 gfx_saved equ 2102h ; saved graphics state 5966 21 04 saved_rend equ 2104h ; saved char_rend 5967 ; enter_setup stores curs_col and curs_row here 5968 21 0D saved_curs_col equ 210dh 5969 ; initialised to 0ffh at startup. Something to do with margins, but looks more like a flag than value 5970 ; Seems to be very commonly invalidated by storing 0ffh (many places) and 0f0h (one place) 5971 21 0E saved_curs_row equ 210eh 5972 21 0F UNUSED_X210f equ 210fh 5973 21 10 UNUSED_X2110 equ 2110h 5974 ; When we enter SET-UP, the previous action for received characters, char_action, is 5975 ; saved here. 5976 21 11 saved_action equ 2111h 5977 ; 5978 ; Detailed in TM §4.7.3.2, p. 4-92 5979 ; Each entry in this array contains the number of a line on the physical screen. What the TM 5980 ; doesn't say is that bit 7 might be set on the physical line number; this means that the line 5981 ; is double width, which in turn means that a second set of terminating bytes have been placed 5982 ; half-way across the line. 5983 ; 5984 ; LATOFS runs from 2113h (row 0) to 212a (row 23). I don't yet know whether X212b is related, 5985 ; as perhaps being the physical number of the 25th line. 5986 21 13 latofs equ 2113h 5987 ; Bottom row number + 1 is written in latofs_last, even for 14-line screens 5988 21 2B latofs_last equ 212bh 5989 ; Counts down from 35h, toggles blink flip-flop every time it reaches zero 5990 21 2C blink_timer equ 212ch 5991 ; 16-bit counts down in update_kbd and then flips cursor visibility 5992 21 2D cursor_timer equ 212dh 5993 21 2F param_value equ 212fh ; current CSI parameter value we're collecting 5994 5995 ; TWO names for same location here. csi_params is used to refer to the array, when the 5996 ; code is going to iterate over params and the name csi_p1 is used when we're explicitly 5997 ; looking for the first parameter. 5998 21 30 csi_params equ 2130h ; list of 16 params for CSI sequence 5999 21 30 csi_p1 equ 2130h ; parameter p1 is start of array 6000 21 31 csi_p2 equ 2131h 6001 ; None of these names for further parameters are used in the source because there are no 6002 ; sequences on a VT100 with more than two fixed parameters. All other sequences have 6003 ; selection parameters, and they are addressed by iterating over the array as a whole. 6004 ; Nevertheless, the presence of names in the symbol table aids debugging. 6005 21 32 csi_p3 equ 2132h 6006 21 33 csi_p4 equ 2133h 6007 21 34 csi_p5 equ 2134h 6008 21 35 csi_p6 equ 2135h 6009 21 36 csi_p7 equ 2136h 6010 21 37 csi_p8 equ 2137h 6011 21 38 csi_p9 equ 2138h 6012 21 39 csi_p10 equ 2139h 6013 21 3A csi_p11 equ 213ah 6014 21 3B csi_p12 equ 213bh 6015 21 3C csi_p13 equ 213ch 6016 21 3D csi_p14 equ 213dh 6017 21 3E csi_p15 equ 213eh 6018 ; The 16th numeric parameter is here for convenience of processing. It can be written 6019 ; to, but it is never used during execution of a control sequence. 6020 21 3F csi_p16 equ 213fh 6021 ; 6022 21 40 char_action equ 2140h ; address of routine used to process next incoming character 6023 ; If we write to location on right-hand edge of screen and auto-wrap is on, 6024 ; need for a wrap on the next write is recorded here. Always cleared after 6025 ; a non-edge character is written. 6026 21 42 pending_wrap equ 2142h 6027 ; used in send_key_byte to hold offset into curkey_queue 6028 21 43 curkey_qcount equ 2143h 6029 21 44 keyboard_locked equ 2144h 6030 ; Supposed to be a mask for four bits (0-3, here) that are masked with other 6031 ; bytes to form an instruction for the keyword. However, a bug (see decll_action) 6032 ; means that this byte can have bits 4-7 set, causing interference with the 6033 ; keyboard, and they cannot be then unset without resetting the terminal. 6034 ; 6035 ; 7 6 5 4 3 2 1 0 6036 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6037 ; | | | | |L1 on|L2 on|L3 on|L4 on| 6038 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6039 21 45 led_state equ 2145h 6040 ; online/local mask for keyboard (directly ORed with led_state) 6041 21 46 kbd_online_mask equ 2146h 6042 ; spkr./click mask (directly ORed with led_state) 6043 21 47 kbd_click_mask equ 2147h 6044 21 48 kbd_scan_mask equ 2148h 6045 ; Never see this being read, or jumped through but looks like address location 6046 ; I believe this always contains 07ffh. Confirmed by tests. 6047 21 49 UNREAD_X2149 equ 2149h 6048 21 4A UNREAD_X214a equ 214ah 6049 21 4B num_params equ 214bh ; offset into params array of the current param 6050 21 4C UNUSED_X214c equ 214ch 6051 21 4D UNUSED_X214d equ 214dh 6052 ; 6053 ; Start of the cursor line in video RAM 6054 21 4E start_line_addr equ 214eh 6055 ; a key scan code - checked when processing auto repeat 6056 21 50 latest_key_scan equ 2150h 6057 ; This flag is only cleared when a column has been received, rather than when ESC Y has been 6058 ; recognised, which means that intervening C0 controls won't affect the very next printable 6059 ; characters from being recognised as a row or column. 6060 21 51 got_vt52_row equ 2151h 6061 21 52 vt52_row_coord equ 2152h 6062 ; 6063 ; Internal column number of right margin of the current line. 6064 ; This will be 79 or 131 for single-width lines. One of the jobs of see move_updates 6065 ; is to halve this for double-width lines. 6066 ; 6067 21 53 right_margin equ 2153h 6068 ; 6069 ; Set up if a margin bell is required. Stealth read at 1696h. 6070 21 54 margin_bell equ 2154h 6071 21 55 top_margin equ 2155h 6072 21 56 bottom_margin equ 2156h 6073 ; Gets 80h double-width marker if appropriate, or 0 for single line 6074 21 57 curr_line_dbl equ 2157h 6075 ; This tx_spd and rx_spd combined to make a single byte, sent to iow_baud_rate 6076 21 58 tx_rx_speed equ 2158h 6077 ; 6078 ; Next two locations control how the cursor is going to work, either by changing the 6079 ; base attribute in the video RAM for each character, which is what must happen without 6080 ; AVO present, or by changing attribute RAM, when AVO is present. Cursor setup records 6081 ; both options in these locations, which get XORed with video RAM and attribute RAM, 6082 ; respectively. 6083 ; 6084 ; without AVO, this is 80h, so toggles between normal and reverse video (or underline) 6085 21 59 curs_char_rend equ 2159h 6086 ; 0 for underline cursor, 2 for block 6087 21 5A curs_attr_rend equ 215ah 6088 ; 0 = basic attribute is underline, 1 = basic attribute is reverse video 6089 21 5B basic_rev_video equ 215bh 6090 ; Reports to be sent out from the terminal are built in this buffer. 6091 ; Longest report is DECREPTPARM, which can reach 21 characters, making 6092 ; this buffer extend from 215ch to 2170h. 6093 ; DECREPTPARM: ESC [ 2 ; 1 ; 1 ; 120 ; 120 ; 1 ; 15 c 6094 21 5C report_buffer equ 215ch 6095 ; Offset into report_buffer of the character to be sent next 6096 21 71 rep_send_offset equ 2171h 6097 6098 ; When a sequence is received that requires a response, the need for a response is placed in 6099 ; location pending_report, and then dealt with later, as part of the periodic keyboard 6100 ; tick routine. 6101 ; 6102 ; pend_<x< are the individual bit flags to say what needs reporting 6103 21 72 pending_report equ 2172h 6104 00 01 pend_curpos equ 01h ; DSR 6 (cursor position) has been requested 6105 00 02 pend_tparm equ 02h ; DECREQTPARM has been received 6106 00 04 pend_ident equ 04h ; DECID has been received 6107 00 08 pend_devstat equ 08h ; DSR 5 (device status) has been requested 6108 00 10 pend_aback equ 10h ; Answerback has been triggered 6109 00 20 pend_key equ 20h ; Keys to host (single or multi-byte) 6110 6111 ; When a report has been prepared and send_report has been called, this goes to 1, 6112 ; then back to zero when last character of report has been grabbed from buffer 6113 21 73 sending_report equ 2173h 6114 ; 6115 ; Used to hold the address of a routine that will produce a report, then holds 6116 ; the address of the routine that pumps them out, which is cont_report for all 6117 ; reports other than cursor keys, because they are prepared in advance, one at 6118 ; a time, but several cursor key reports can be buffered. 6119 21 74 report_action equ 2174h 6120 6121 ; Flag whether DECREPTPARM can be sent unsolicited (i.e. whenever SET-UP is exited), 6122 ; or only on request. This is controlled by the parameter sent with DECREQTPARM, 6123 ; so this flag will be 0 = unsolicited reports allowed, 1 = solicited only 6124 ; Status on reset will be 1, i.e. no unsolicited reports 6125 21 76 tparm_solicited equ 2176h 6126 ; When SETUP is detected in keyboard processing, this flag is set to indicate that 6127 ; SET-UP should be entered. 6128 21 77 pending_setup equ 2177h 6129 ; keypad_mode: 0 = numeric, 1 = application 6130 21 78 keypad_mode equ 2178h 6131 ; For line shuffling 6132 21 79 shufdt1 equ 2179h 6133 ; TM §4.7.11 details SET-UP scratch RAM (saying it is subject to change.) 6134 ; It confirms that the first area here is the answerback message, 22 bytes, with 20 characters and 2 delimiters. 6135 21 7B aback_buffer equ 217bh 6136 21 90 aback_bufend equ 2190h 6137 ; 132/8 = 16.5 bytes, so tab buffer is 17 bytes, 2191h to 21a1h 6138 21 91 tab_settings equ 2191h 6139 00 11 tablen equ 17 ; bytes in tab settings area 6140 ; 1 = 132 columns, 0 = 80 columns 6141 21 A2 columns_132 equ 21a2h 6142 ; brightest = 0, dimmest = 1fh 6143 21 A3 brightness equ 21a3h 6144 ; pusart_mode, based on TM §4.7.11 6145 21 A4 pusart_mode equ 21a4h 6146 ; 6147 ; local_mode flag is ORed into the keyboard byte in order to set the Local LED, 6148 ; so the value 20h is used as "local". Data loopback test will indicate failure 6149 ; by placing 25h, in order to briefly light LEDs L2 and L4 - these then get removed 6150 ; by after_tests. 6151 21 A5 local_mode equ 21a5h 6152 ; 21a6: SET-UP B block 1 6153 ; 6154 ; 7 6 5 4 3 2 1 0 6155 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6156 ; | | | | | | | | | 6157 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6158 ; | | | `-- 1 = cursor block, 0 = cursor underline 6159 ; | | `-- 1 = light background, 0 = dark background 6160 ; | `-- 1 = autorepeat on, 0 = autorepeat off 6161 ; `-- 1 = smooth scroll, 0 = jump scroll 6162 ; 6163 21 A6 setup_b1 equ 21a6h 6164 00 10 sb1_curblock equ 10h 6165 00 20 sb1_lightback equ 20h 6166 00 40 sb1_autorep equ 40h 6167 00 80 sb1_smooth equ 80h 6168 ; 6169 ; 21a7: SET-UP B block 2 6170 ; 6171 ; 7 6 5 4 3 2 1 0 6172 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6173 ; | | | | | | | | | 6174 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6175 ; | | | `-- 1 = auto xon/xoff, 0 = no auto xon/off 6176 ; | | `-- 1 = ANSI mode, 0 = VT52 (see ansi_mode) 6177 ; | `-- 1 = keyclick on, 0 = keyclick off 6178 ; `-- 1 = margin bell on, 0 = margin bell off 6179 ; 6180 21 A7 setup_b2 equ 21a7h 6181 00 10 sb2_autoxon equ 10h 6182 00 20 sb2_ansi equ 20h 6183 00 40 sb2_keyclick equ 40h 6184 00 80 sb2_marginbell equ 80h 6185 6186 ; 21a8: SET-UP B block 3 6187 ; 6188 ; 7 6 5 4 3 2 1 0 6189 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6190 ; | | | | | | | | | 6191 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6192 ; | | | `-- 1 = interlace on, 0 = interlace off (see decinlm_mode) 6193 ; | | `-- 1 = ?, 0 = ? 6194 ; | `-- 1 = autowrap, 0 = no autowrap (see decawm_mode) 6195 ; `-- 1 = UK, 0 = ASCII 6196 21 A8 setup_b3 equ 21a8h 6197 00 10 sb3_interlace equ 10h 6198 00 20 sb3_newline equ 20h 6199 00 40 sb3_autowrap equ 40h 6200 00 80 sb3_uk equ 80h 6201 6202 ; 21a9: SET-UP B block 4 6203 ; 6204 ; 7 6 5 4 3 2 1 0 6205 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6206 ; | | | | | | | | | 6207 ; +-----+-----+-----+-----+-----+-----+-----+-----+ 6208 ; | | | `-- 1 - power 50 Hz, 0 = power 60 Hz 6209 ; | | `-- bits/char: 1 = 8 bits, 0 = 7 bits 6210 ; | `-- 1 = parity on, 0 = parity off 6211 ; `-- 1 = even parity, 0 = odd parity 6212 21 A9 setup_b4 equ 21a9h 6213 00 10 sb4_50Hz equ 10h 6214 00 20 sb4_8bits equ 20h 6215 00 40 sb4_parityon equ 40h 6216 00 80 sb4_evenparity equ 80h 6217 00 C0 sb4_paritybits equ (sb4_parityon|sb4_evenparity) 6218 ; 6219 21 AA setup_b5 equ 21aah ; only visible with STP option installed 6220 21 AB tx_spd equ 21abh ; encoded transmit speed in high 4 bits 6221 21 AC rx_spd equ 21ach ; encoded receive speed in high 4 bits 6222 21 AD nvr_checksum equ 21adh 6223 ; 6224 21 AE nvr_addr equ 21aeh 6225 21 AF nvr_data equ 21afh ; 2 locs 6226 21 B1 UNUSED_X21b1 equ 21b1h 6227 21 B2 UNUSED_X21b2 equ 21b2h 6228 21 B3 UNUSED_X21b3 equ 21b3h 6229 21 B4 answerback_ptr equ 21b4h 6230 21 B6 UNUSED_X21b6 equ 21b6h 6231 21 B7 UNUSED_X21b7 equ 21b7h 6232 21 B8 csi_private equ 21b8h ; CSI sequence private character is stored here 6233 21 B9 found_action equ 21b9h 6234 ; initialised to 0ffh at startup 6235 21 BA cursor_visible equ 21bah 6236 ; updated post cursor move, used for margin bell calculations because it matters whether 6237 ; we got to the current column by typing or cursor positioning sequences. 6238 21 BB last_curs_col equ 21bbh 6239 21 BC mode_ckm equ 21bch 6240 ; POST results go here 6241 21 BD test_results equ 21bdh 6242 ; in_setup_a is 1 for SET-UP A, 0 for SET-UP B. 6243 21 BE in_setup_a equ 21beh 6244 ; 6245 ; If the host is allowed to send characters (receive buffer has enough space, 6246 ; user has pressed Ctrl/Q, we've come out of SETUP, etc.) then this will be zero, 6247 ; and an XON will probably have been sent. Otherwise, this records why the host has 6248 ; been told to stop, as a bit mask. 01h means the receive buffer is getting full, 6249 ; and 02h means the user has pressed NO SCROLL or Ctrl/S. 6250 ; 03h means the receive buffer was already getting full, and the user also panicked 6251 ; and hit NO SCROLL! 6252 ; 6253 21 BF why_xoff equ 21bfh 6254 ; XON/XOFF character to transmit is pulled from here, as long as 21c1h is not zero 6255 21 C0 tx_xo_char equ 21c0h 6256 21 C1 tx_xo_flag equ 21c1h 6257 ; Contents of this ANDed with 0feh if we've received XON, ORed with 1 if we've received XOFF 6258 21 C2 received_xoff equ 21c2h 6259 ; gets zeroed by clear_part_row 6260 ; flag marked when smooth scroll and need to clear extra line, so non-interrupt path 6261 ; sets this flag and then waits until the clearance happens in vertical interrupt and 6262 ; the flag gets zeroed. 6263 21 C3 row_clearing equ 21c3h 6264 ; no_scroll_key stores 2 (sent XOFF) or 0 (sent XON) here 6265 21 C4 noscroll equ 21c4h 6266 ; When we enter SET-UP mode, we save the previous video pointer here 6267 21 C5 saved_line1_dma equ 21c5h 6268 ; This is zero, except for alignment display, which makes it 'E' (and then zeroes again) 6269 21 C7 cls_char equ 21c7h 6270 ; Recorded early in start-up. Non zero if no AVO found 6271 21 C8 avo_missing equ 21c8h 6272 ; gets sent to iow_nvr_latch during vertical int 6273 ; Vertical interrupt outputs to the NVR latch, so any code that is writing to the latch while 6274 ; interrupts are still enabled also stores the value here, so it persists. 6275 21 C9 vint_nvr equ 21c9h 6276 21 CA UNUSED_X21ca equ 21cah 6277 ; updated when blink timer goes off in vertical interrupt 6278 ; repeat test field 6279 ; If DECTST has requested some tests to be repeatedly executed and they fail, the screen field 6280 ; is toggled between normal and reverse on each cycle. This can be seen by sending the sequence 6281 ; ESC [ 2 ; 10 y (data loopback with repeat). Data loopback will always fail in the absence of 6282 ; a loopback connector, so the screen will show "8" for failure in the first character position, 6283 ; and the entire screen will blink between normal and reverse field. 6284 ; 6285 21 CB test_field equ 21cbh 6286 ; This area holds the mini screen definition for "Wait", with terminators that 6287 ; point back to the fill lines. The entire screen definition is just 7 bytes long. 6288 21 CC wait_addr equ 21cch 6289 ; wait screen address in DMA (big-endian) form, with normal line attributes 6290 CC 71 wait_addr_be equ 0cc71h 6291 21 D3 nvr_bits equ 21d3h ; must be at least 14 locations 6292 ; setup_video is where the SET-UP characters are drawn, and then the Video DMA is switched 6293 ; to point to here while we are in this mode. 6294 22 53 setup_video equ 2253h 6295 ; 6296 ; Normal screen display starts here in scratch RAM, and line1_dma gets initialised to point to it. 6297 ; screen_layout (the initial 18 bytes of definition) point here literally, and any other time 6298 ; we want to get back to this location, main_video_be will be loaded into HL and then 6299 ; written to line1_dma. This establishes the first line as part of the scrolling region, 6300 ; with normal lines attributes. 6301 ; 6302 22 D0 main_video equ 22d0h 6303 D0 F2 main_video_be equ 0d0f2h 6304 ; 6305 end