-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathzx-spectrum-rom.asm
17580 lines (14878 loc) · 548 KB
/
zx-spectrum-rom.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;************************************************************************
;** An assembly file listing to generate a 16K Rom for the ZX Spectrum **
;************************************************************************
;
; Copyright (c) Amstrad plc. All rights reserved.
;
; Acknowledgements
; -----------------
; Sean Irvine for default list of section headings
; (author unknown).
; Dr. Ian Logan for labels and functional disassembly.
; Dr. Frank O'Hara for labels and functional disassembly.
;
; Credits
; -------
; Alex Pallero Gonzales for corrections.
; Mike Dailly for comments.
; Alvin Albrecht for comments.
; Hob of c.s.s for full relocatability implementation and testing.
;
; z00m^SinDiKAT sjasmplus adaptation and dirty reformat.
;
; obsolete labels
; L1C16 JUMP-C-R
OUTPUT "48.ROM"
; System variables definitions
include "zx-spectrum-sysvars.asm"
;*****************************************
;** Part 1. RESTART ROUTINES AND TABLES **
;*****************************************
;------
; Start
;------
; At switch on, the Z80 chip is in interrupt mode 0.
; This location can also be 'called' to reset the machine.
; Typically with PRINT USR 0.
ORG $0000
;;;$0000
START: DI ; disable interrupts.
XOR A ; signal coming from START.
LD DE,$FFFF ; top of possible physical RAM.
JP START_NEW ; jump forward to common code at START_NEW.
;--------------
; Error restart
;--------------
; The error pointer is made to point to the position of the error to enable
; the editor to show the error if it occurred during syntax checking.
; It is used at 37 places in the program.
; An instruction fetch on address $0008 may page in a peripheral ROM
; although this was not an original design concept.
;;;$0008
ERROR_1: LD HL,(CH_ADD) ; fetch the character address from CH_ADD.
LD (X_PTR),HL ; copy it to the error pointer X_PTR.
JR ERROR_2 ; forward to continue at ERROR_2.
;------------------
; Print a character
;------------------
; The A register holds the code of the character that is to be sent to
; the output stream of the current channel.
; The alternate register set is used to output a character in the A register
; so there is no need to preserve any of the current registers.
; This restart occurs 21 times.
;;;$0010
PRINT_A: JP PRINT_A_2 ; jump forward to continue at PRINT_A_2.
DEFB $FF, $FF, $FF ; five unused locations.
DEFB $FF, $FF
;--------------------
; Collect a character
;--------------------
; The contents of the location currently addressed by CH_ADD are fetched.
; A return is made if the value represents a character that has
; relevance to the BASIC parser. Otherwise CH_ADD is incremented and the
; tests repeated. CH_ADD will be addressing somewhere -
; 1) in the basic program area during line execution.
; 2) in workspace if evaluating, for example, a string expression.
; 3) in the edit buffer if parsing a direct command or a new basic line.
; 4) in workspace if accepting input but not that from INPUT LINE.
;;;$0018
GET_CHAR: LD HL,(CH_ADD) ; fetch the address from CH_ADD.
LD A,(HL) ; use it to pick up current character.
;;;$001C
TEST_CHAR: CALL SKIP_OVER ; routine SKIP_OVER tests if the character
RET NC ; is relevant. Return if it is so.
;-----------------------
; Collect next character
;-----------------------
; As the BASIC commands and expressions are interpreted, this routine is
; called repeatedly to step along the line. It is used 83 times.
;;;$0020
NEXT_CHAR: CALL CH_ADD_1 ; routine CH_ADD_1 fetches the next immediate character.
JR TEST_CHAR ; jump back to TEST_CHAR until a valid
; character is found.
DEFB $FF, $FF, $FF ; unused
;-------------------
; Calculator restart
;-------------------
; This restart enters the Spectrum's internal, floating-point,
; stack-based, FORTH-like language.
; It is further used recursively from within the calculator.
; It is used on 77 occasions.
;;;$0028
FP_CALC: JP CALCULATE ; jump forward to the CALCULATE routine.
DEFB $FF, $FF, $FF ; spare - note that on the ZX81, space being a
DEFB $FF, $FF ; little cramped, these same locations were
; used for the five-byte END_CALC literal.
;------------------------------------
; Create free locations in work space
;------------------------------------
; This restart is used on only 12 occasions to create BC spaces
; between workspace and the calculator stack.
;;;$0030
BC_SPACES: PUSH BC ; save number of spaces.
LD HL,(WORKSP) ; fetch WORKSP.
PUSH HL ; save address of workspace.
JP RESERVE ; jump forward to continuation code RESERVE.
;---------------------------
; Maskable interrupt routine
;---------------------------
; This routine increments the Spectrum's three-byte FRAMES counter
; fifty times a second (sixty times a second in the USA ).
; Both this routine and the called KEYBOARD subroutine use
; the IY register to access system variables and flags so a user-written
; program must disable interrupts to make use of the IY register.
;;;$0038
MASK_INT: PUSH AF ; save the registers.
PUSH HL ; but not IY unfortunately.
LD HL,(FRAMES1) ; fetch two bytes at FRAMES1.
INC HL ; increment lowest two bytes of counter.
LD (FRAMES1),HL ; place back in FRAMES1.
LD A,H ; test if the result
OR L ; was zero.
JR NZ,KEY_INT ; forward to KEY_INT if not.
INC (IY+$40) ; otherwise increment FRAMES3 the third byte.
; now save the rest of the main registers and read and decode the keyboard.
;;;$0048
KEY_INT: PUSH BC ; save the other
PUSH DE ; main registers.
CALL KEYBOARD ; routine KEYBOARD executes a stage in the process of reading a key-press.
POP DE
POP BC ; restore registers.
POP HL
POP AF
EI ; enable interrupts.
RET ; return.
;----------------
; ERROR_2 routine
;----------------
; A continuation of the code at 0008.
; The error code is stored and after clearing down stacks,
; an indirect jump is made to MAIN_4, etc. to handle the error.
;;;$0053
ERROR_2: POP HL ; drop the return address - the location
; after the RST 08H instruction.
LD L,(HL) ; fetch the error code that follows.
; (nice to see this instruction used.)
; Note. this entry point is used when out of memory at REPORT_4.
; The L register has been loaded with the report code but X_PTR
; is not updated.
;;;$0055
ERROR_3: LD (IY+$00),L ; store it in the system variable ERR_NR.
LD SP,(ERR_SP) ; ERR_SP points to an error handler on the
; machine stack. There may be a hierarchy
; of routines.
; to MAIN_4 initially at base.
; or REPORT_G on line entry.
; or ED_ERROR when editing.
; or ED_FULL during ED_ENTER.
; or IN_VAR_1 during runtime input etc.
JP SET_STK ; jump to SET_STK to clear the calculator
; stack and reset MEM to usual place in the
; systems variables area.
; and then indirectly to MAIN_4, etc.
DEFB $FF, $FF, $FF ; unused locations
DEFB $FF, $FF, $FF ; before the fixed-position
DEFB $FF ; NMI routine.
;-------------------------------
; Non-maskable interrupt routine
;-------------------------------
; There is no NMI switch on the standard Spectrum.
; When activated, a location in the system variables is tested
; and if the contents are zero a jump made to that location else
; a return is made. Perhaps a disabled development feature but
; if the logic was reversed, no program would be safe from
; copy-protection and the Spectrum would have had no software base.
; The location NMIADD was later used by Interface 1 for other purposes
; ironically to make use of the Spectrum's RS232 TAB character
; which was not understood when the Interface was designed.
; On later Spectrums, and the Brazilian Spectrum, the logic of this
; routine was reversed.
;;;$0066
RESET: PUSH AF ; save the
PUSH HL ; registers.
LD HL,(NMIADD) ; fetch the system variable NMIADD.
LD A,H ; test address
OR L ; for zero.
JR NZ,NO_RESET ; skip to NO_RESET if NOT ZERO
JP (HL) ; jump to routine ( i.e. START )
;;;$0070
NO_RESET: POP HL ; restore the
POP AF ; registers.
RETN ; return to previous interrupt state.
;----------------------
; CH ADD + 1 subroutine
;----------------------
; This subroutine is called from RST 20, and three times from elsewhere
; to fetch the next immediate character following the current valid character
; address and update the associated system variable.
; The entry point TEMP_PTR1 is used from the SCANNING routine.
; Both TEMP_PTR1 and TEMP_PTR2 are used by the READ command routine.
;;;$0074
CH_ADD_1: LD HL,(CH_ADD) ; fetch address from CH_ADD.
;;;$0077
TEMP_PTR1: INC HL ; increase the character address by one.
;;;$0078
TEMP_PTR2: LD (CH_ADD),HL ; update CH_ADD with character address.
LD A,(HL) ; load character to A from HL.
RET ; and return.
;----------
; Skip over
;----------
; This subroutine is called once from RST 18 to skip over white-space and
; other characters irrelevant to the parsing of a basic line etc. .
; Initially the A register holds the character to be considered
; and HL holds it's address which will not be within quoted text
; when a basic line is parsed.
; Although the 'tab' and 'at' characters will not appear in a basic line,
; they could be present in a string expression, and in other situations.
; Note. although white-space is usually placed in a program to indent loops
; and make it more readable, it can also be used for the opposite effect and
; spaces may appear in variable names although the parser never sees them.
; It is this routine that helps make the variables 'Anum bEr5 3BUS' and
; 'a number 53 bus' appear the same to the parser.
;;;$007D
SKIP_OVER: CP $21 ; test if higher than space.
RET NC ; return with carry clear if so.
CP $0D ; carriage return ?
RET Z ; return also with carry clear if so.
; all other characters have no relevance
; to the parser and must be returned with
; carry set.
CP $10 ; test if 0-15d
RET C ; return, if so, with carry set.
CP $18 ; test if 24-32d
CCF ; complement carry flag.
RET C ; return with carry set if so.
; now leaves 16d-23d
INC HL ; all above have at least one extra character
; to be stepped over.
CP $16 ; controls 22d ('at') and 23d ('tab') have two.
JR C,SKIPS ; forward to SKIPS with ink, paper, flash,
; bright, inverse or over controls.
; Note. the high byte of tab is for RS232 only.
; it has no relevance on this machine.
INC HL ; step over the second character of 'at'/'tab'.
;;;$0090
SKIPS: SCF ; set the carry flag
LD (CH_ADD),HL ; update the CH_ADD system variable.
RET ; return with carry set.
;-------------
; Token tables
;-------------
; The tokenized characters 134d (RND) to 255d (COPY) are expanded using
; this table. The last byte of a token is inverted to denote the end of
; the word. The first is an inverted step-over byte.
;;;$0095
TKN_TABLE: DEFB '?'+$80
DEFB "RN",'D'+$80
DEFB "INKEY",'$'+$80
DEFB "P",'I'+$80
DEFB "F",'N'+$80
DEFB "POIN",'T'+$80
DEFB "SCREEN",'$'+$80
DEFB "ATT",'R'+$80
DEFB "A",'T'+$80
DEFB "TA",'B'+$80
DEFB "VAL",'$'+$80
DEFB "COD",'E'+$80
DEFB "VA",'L'+$80
DEFB "LE",'N'+$80
DEFB "SI",'N'+$80
DEFB "CO",'S'+$80
DEFB "TA",'N'+$80
DEFB "AS",'N'+$80
DEFB "AC",'S'+$80
DEFB "AT",'N'+$80
DEFB "L",'N'+$80
DEFB "EX",'P'+$80
DEFB "IN",'T'+$80
DEFB "SQ",'R'+$80
DEFB "SG",'N'+$80
DEFB "AB",'S'+$80
DEFB "PEE",'K'+$80
DEFB "I",'N'+$80
DEFB "US",'R'+$80
DEFB "STR",'$'+$80
DEFB "CHR",'$'+$80
DEFB "NO",'T'+$80
DEFB "BI",'N'+$80
; The previous 32 function-type words are printed without a leading space
; The following have a leading space if they begin with a letter
DEFB "O",'R'+$80
DEFB "AN",'D'+$80
DEFB $3C,'='+$80 ; <=
DEFB $3E,'='+$80 ; >=
DEFB $3C,$3E+$80 ; <>
DEFB "LIN",'E'+$80
DEFB "THE",'N'+$80
DEFB "T",'O'+$80
DEFB "STE",'P'+$80
DEFB "DEF F",'N'+$80
DEFB "CA",'T'+$80
DEFB "FORMA",'T'+$80
DEFB "MOV",'E'+$80
DEFB "ERAS",'E'+$80
DEFB "OPEN ",'#'+$80
DEFB "CLOSE ",'#'+$80
DEFB "MERG",'E'+$80
DEFB "VERIF",'Y'+$80
DEFB "BEE",'P'+$80
DEFB "CIRCL",'E'+$80
DEFB "IN",'K'+$80
DEFB "PAPE",'R'+$80
DEFB "FLAS",'H'+$80
DEFB "BRIGH",'T'+$80
DEFB "INVERS",'E'+$80
DEFB "OVE",'R'+$80
DEFB "OU",'T'+$80
DEFB "LPRIN",'T'+$80
DEFB "LLIS",'T'+$80
DEFB "STO",'P'+$80
DEFB "REA",'D'+$80
DEFB "DAT",'A'+$80
DEFB "RESTOR",'E'+$80
DEFB "NE",'W'+$80
DEFB "BORDE",'R'+$80
DEFB "CONTINU",'E'+$80
DEFB "DI",'M'+$80
DEFB "RE",'M'+$80
DEFB "FO",'R'+$80
DEFB "GO T",'O'+$80
DEFB "GO SU",'B'+$80
DEFB "INPU",'T'+$80
DEFB "LOA",'D'+$80
DEFB "LIS",'T'+$80
DEFB "LE",'T'+$80
DEFB "PAUS",'E'+$80
DEFB "NEX",'T'+$80
DEFB "POK",'E'+$80
DEFB "PRIN",'T'+$80
DEFB "PLO",'T'+$80
DEFB "RU",'N'+$80
DEFB "SAV",'E'+$80
DEFB "RANDOMIZ",'E'+$80
DEFB "I",'F'+$80
DEFB "CL",'S'+$80
DEFB "DRA",'W'+$80
DEFB "CLEA",'R'+$80
DEFB "RETUR",'N'+$80
DEFB "COP",'Y'+$80
;-----------
; Key tables
;-----------
; These six look-up tables are used by the keyboard reading routine
; to decode the key values.
; The first table contains the maps for the 39 keys of the standard
; 40-key Spectrum keyboard. The remaining key [SHIFT $27] is read directly.
; The keys consist of the 26 upper-case alphabetic characters, the 10 digit
; keys and the space, ENTER and symbol shift key.
; Unshifted alphabetic keys have $20 added to the value.
; The keywords for the main alphabetic keys are obtained by adding $A5 to
; the values obtained from this table.
;;;$0205
MAIN_KEYS: DEFB $42 ; B
DEFB $48 ; H
DEFB $59 ; Y
DEFB $36 ; 6
DEFB $35 ; 5
DEFB $54 ; T
DEFB $47 ; G
DEFB $56 ; V
DEFB $4E ; N
DEFB $4A ; J
DEFB $55 ; U
DEFB $37 ; 7
DEFB $34 ; 4
DEFB $52 ; R
DEFB $46 ; F
DEFB $43 ; C
DEFB $4D ; M
DEFB $4B ; K
DEFB $49 ; I
DEFB $38 ; 8
DEFB $33 ; 3
DEFB $45 ; E
DEFB $44 ; D
DEFB $58 ; X
DEFB $0E ; SYMBOL SHIFT
DEFB $4C ; L
DEFB $4F ; O
DEFB $39 ; 9
DEFB $32 ; 2
DEFB $57 ; W
DEFB $53 ; S
DEFB $5A ; Z
DEFB $20 ; SPACE
DEFB $0D ; ENTER
DEFB $50 ; P
DEFB $30 ; 0
DEFB $31 ; 1
DEFB $51 ; Q
DEFB $41 ; A
;;;$022C
; The 26 unshifted extended mode keys for the alphabetic characters.
; The green keywords on the original keyboard.
E_UNSHIFT: DEFB $E3 ; READ
DEFB $C4 ; BIN
DEFB $E0 ; LPRINT
DEFB $E4 ; DATA
DEFB $B4 ; TAN
DEFB $BC ; SGN
DEFB $BD ; ABS
DEFB $BB ; SQR
DEFB $AF ; CODE
DEFB $B0 ; VAL
DEFB $B1 ; LEN
DEFB $C0 ; USR
DEFB $A7 ; PI
DEFB $A6 ; INKEY$
DEFB $BE ; PEEK
DEFB $AD ; TAB
DEFB $B2 ; SIN
DEFB $BA ; INT
DEFB $E5 ; RESTORE
DEFB $A5 ; RND
DEFB $C2 ; CHR$
DEFB $E1 ; LLIST
DEFB $B3 ; COS
DEFB $B9 ; EXP
DEFB $C1 ; STR$
DEFB $B8 ; LN
;;;$0246
; The 26 shifted extended mode keys for the alphabetic characters.
; The red keywords below keys on the original keyboard.
EXT_SHIFT: DEFB $7E ; ~
DEFB $DC ; BRIGHT
DEFB $DA ; PAPER
DEFB $5C ; \ ;
DEFB $B7 ; ATN
DEFB $7B ; {
DEFB $7D ; }
DEFB $D8 ; CIRCLE
DEFB $BF ; IN
DEFB $AE ; VAL$
DEFB $AA ; SCREEN$
DEFB $AB ; ATTR
DEFB $DD ; INVERSE
DEFB $DE ; OVER
DEFB $DF ; OUT
DEFB $7F ; (Copyright character)
DEFB $B5 ; ASN
DEFB $D6 ; VERIFY
DEFB $7C ; |
DEFB $D5 ; MERGE
DEFB $5D ; ]
DEFB $DB ; FLASH
DEFB $B6 ; ACS
DEFB $D9 ; INK
DEFB $5B ; [
DEFB $D7 ; BEEP
;;;$0260
; The ten control codes assigned to the top line of digits when the shift
; key is pressed.
CTL_CODES: DEFB $0C ; DELETE
DEFB $07 ; EDIT
DEFB $06 ; CAPS LOCK
DEFB $04 ; TRUE VIDEO
DEFB $05 ; INVERSE VIDEO
DEFB $08 ; CURSOR LEFT
DEFB $0A ; CURSOR DOWN
DEFB $0B ; CURSOR UP
DEFB $09 ; CURSOR RIGHT
DEFB $0F ; GRAPHICS
;;;$026A
; The 26 red symbols assigned to the alphabetic characters of the keyboard.
; The ten single-character digit symbols are converted without the aid of
; a table using subtraction and minor manipulation.
SYM_CODES: DEFB $E2 ; STOP
DEFB $2A ; *
DEFB $3F ; ?
DEFB $CD ; STEP
DEFB $C8 ; >=
DEFB $CC ; TO
DEFB $CB ; THEN
DEFB $5E ; ^
DEFB $AC ; AT
DEFB $2D ; -
DEFB $2B ; +
DEFB $3D ; =
DEFB $2E ; .
DEFB $2C ; ,
DEFB $3B ; ;
DEFB $22 ; "
DEFB $C7 ; <=
DEFB $3C ; <
DEFB $C3 ; NOT
DEFB $3E ; >
DEFB $C5 ; OR
DEFB $2F ; /
DEFB $C9 ; <>
DEFB $60 ; pound
DEFB $C6 ; AND
DEFB $3A ; :
;;;$0284
; The ten keywords assigned to the digits in extended mode.
; The remaining red keywords below the keys.
E_DIGITS: DEFB $D0 ; FORMAT
DEFB $CE ; DEF FN
DEFB $A8 ; FN
DEFB $CA ; LINE
DEFB $D3 ; OPEN#
DEFB $D4 ; CLOSE#
DEFB $D1 ; MOVE
DEFB $D2 ; ERASE
DEFB $A9 ; POINT
DEFB $CF ; CAT
;*******************************
;** Part 2. KEYBOARD ROUTINES **
;*******************************
; Using shift keys and a combination of modes the Spectrum 40-key keyboard
; can be mapped to 256 input characters
;----------------------------------------------------------------------------
;
; 0 1 2 3 4 -Bits- 4 3 2 1 0
; PORT PORT
;
; F7FE [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] | [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 0 ] EFFE
; ^ | v
; FBFE [ Q ] [ W ] [ E ] [ R ] [ T ] | [ Y ] [ U ] [ I ] [ O ] [ P ] DFFE
; ^ | v
; FDFE [ A ] [ S ] [ D ] [ F ] [ G ] | [ H ] [ J ] [ K ] [ L ] [ ENT ] BFFE
; ^ | v
; FEFE [SHI] [ Z ] [ X ] [ C ] [ V ] | [ B ] [ N ] [ M ] [sym] [ SPC ] 7FFE
; ^ $27 $18 v
; Start End
; 00100111 00011000
;
;----------------------------------------------------------------------------
; The above map may help in reading.
; The neat arrangement of ports means that the B register need only be
; rotated left to work up the left hand side and then down the right
; hand side of the keyboard. When the reset bit drops into the carry
; then all 8 half-rows have been read. Shift is the first key to be
; read. The lower six bits of the shifts are unambiguous.
;------------------
; Keyboard scanning
;------------------
; from keyboard and S_INKEY
; returns 1 or 2 keys in DE, most significant shift first if any
; key values 0-39 else 255
;;;$028E
KEY_SCAN: LD L,$2F ; initial key value
; valid values are obtained by subtracting
; eight five times.
LD DE,$FFFF ; a buffer to receive 2 keys.
LD BC,$FEFE ; the commencing port address
; B holds 11111110 initially and is also
; used to count the 8 half-rows
;;;$0296
KEY_LINE: IN A,(C) ; read the port to A - bits will be reset
; if a key is pressed else set.
CPL ; complement - pressed key-bits are now set
AND $1F ; apply 00011111 mask to pick up the
; relevant set bits.
JR Z,KEY_DONE ; forward to KEY_DONE if zero and therefore
; no keys pressed in row at all.
LD H,A ; transfer row bits to H
LD A,L ; load the initial key value to A
;;;$029F
KEY_3KEYS: INC D ; now test the key buffer
RET NZ ; if we have collected 2 keys already
; then too many so quit.
;;;$02A1
KEY_BITS: SUB $08 ; subtract 8 from the key value
; cycling through key values (top = $27)
; e.g. 2F> 27>1F>17>0F>07
; 2E> 26>1E>16>0E>06
SRL H ; shift key bits right into carry.
JR NC,KEY_BITS ; back to KEY_BITS if not pressed
; but if pressed we have a value (0-39d)
LD D,E ; transfer a possible previous key to D
LD E,A ; transfer the new key to E
JR NZ,KEY_3KEYS ; back to KEY_3KEYS if there were more
; set bits - H was not yet zero.
;;;$02AB
KEY_DONE: DEC L ; cycles 2F>2E>2D>2C>2B>2A>29>28 for
; each half-row.
RLC B ; form next port address e.g. FEFE > FDFE
JR C,KEY_LINE ; back to KEY_LINE if still more rows to do.
LD A,D ; now test if D is still FF ?
INC A ; if it is zero we have at most 1 key
; range now $01-$28 (1-40d)
RET Z ; return if one key or no key.
CP $28 ; is it capsshift (was $27) ?
RET Z ; return if so.
CP $19 ; is it symbol shift (was $18) ?
RET Z ; return also
LD A,E ; now test E
LD E,D ; but first switch
LD D,A ; the two keys.
CP $18 ; is it symbol shift ?
RET ; return (with zero set if it was).
; but with symbol shift now in D
;-------------------------------
; Scan keyboard and decode value
;-------------------------------
; from interrupt 50 times a second
;;;$02BF
KEYBOARD: CALL KEY_SCAN ; routine KEY_SCAN
RET NZ ; return if invalid combinations
; then decrease the counters within the two key-state maps
; as this could cause one to become free.
; if the keyboard has not been pressed during the last five interrupts
; then both sets will be free.
LD HL,KSTATE_0 ; point to KSTATE_0
;;;$02C6
K_ST_LOOP: BIT 7,(HL) ; is it free ? ($FF)
JR NZ,K_CH_SET ; forward to K_CH_SET if so
INC HL ; address 5-counter
DEC (HL) ; decrease counter
DEC HL ; step back
JR NZ,K_CH_SET ; forward to K_CH_SET if not at end of count
LD (HL),$FF ; else mark it free.
;;;$02D1
K_CH_SET: LD A,L ; store low address byte.
LD HL,KSTATE_4 ; point to KSTATE_4
; (ld l, $04)
CP L ; have 2 been done ?
JR NZ,K_ST_LOOP ; back to K_ST_LOOP to consider this 2nd set
; now the raw key (0-38) is converted to a main key (uppercase).
CALL K_TEST ; routine K_TEST to get main key in A
RET NC ; return if single shift
LD HL,KSTATE_0 ; point to KSTATE_0
CP (HL) ; does it match ?
JR Z,K_REPEAT ; forward to K_REPEAT if so
; if not consider the second key map.
EX DE,HL ; save KSTATE_0 in DE
LD HL,KSTATE_4 ; point to KSTATE_4
CP (HL) ; does it match ?
JR Z,K_REPEAT ; forward to K_REPEAT if so
; having excluded a repeating key we can now consider a new key.
; the second set is always examined before the first.
BIT 7,(HL) ; is it free ?
JR NZ,K_NEW ; forward to K_NEW if so.
EX DE,HL ; bring back KSTATE_0
BIT 7,(HL) ; is it free ?
RET Z ; return if not.
; as we have a key but nowhere to put it yet.
; continue or jump to here if one of the buffers was free.
;;;$02F1
K_NEW: LD E,A ; store key in E
LD (HL),A ; place in free location
INC HL ; advance to interrupt counter
LD (HL),$05 ; and initialize to 5
INC HL ; advance to delay
LD A,(REPDEL) ; pick up system variable REPDEL
LD (HL),A ; and insert that for first repeat delay.
INC HL ; advance to last location of state map.
LD C,(IY+$07) ; pick up MODE (3 bytes)
LD D,(IY+$01) ; pick up FLAGS (3 bytes)
PUSH HL ; save state map location
; Note. could now have used.
; ld l,$41; ld c,(hl); ld l,$3B; ld d,(hl).
; six and two threes of course.
CALL K_DECODE ; routine K_DECODE
POP HL ; restore map pointer
LD (HL),A ; put decoded key in last location of map.
;;;$0308
K_END: LD (LASTK),A ; update LASTK system variable.
SET 5,(IY+$01) ; update FLAGS - signal new key.
RET ; done
;-------------------
; Repeat key routine
;-------------------
; A possible repeat has been identified. HL addresses the raw (main) key.
; The last location holds the decoded key (from the first context).
;;;$0310
K_REPEAT: INC HL ; advance
LD (HL),$05 ; maintain interrupt counter at 5
INC HL ; advance
DEC (HL) ; decrease REPDEL value.
RET NZ ; return if not yet zero.
LD A,(REPPER) ; REPPER
LD (HL),A ; but for subsequent repeats REPPER will be used.
INC HL ; advance
LD A,(HL) ; pick up the key decoded possibly in another context.
JR K_END ; back to K_END
;---------------
; Test key value
;---------------
; also called from S_INKEY
; begin by testing for a shift with no other.
;;;$031E
K_TEST: LD B,D ; load most significant key to B
; will be $FF if not shift.
LD D,$00 ; and reset D to index into main table
LD A,E ; load least significant key from E
CP $27 ; is it higher than 39d i.e. FF
RET NC ; return with just a shift (in B now)
CP $18 ; is it symbol shift ?
JR NZ,K_MAIN ; forward to K_MAIN if not
; but we could have just symbol shift and no other
BIT 7,B ; is other key $FF (ie not shift)
RET NZ ; return with solitary symbol shift
;;;$032C
K_MAIN: LD HL,MAIN_KEYS ; address: MAIN_KEYS
ADD HL,DE ; add offset 0-38
LD A,(HL) ; pick up main key value
SCF ; set carry flag
RET ; return (B has other key still)
;------------------
; Keyboard decoding
;------------------
; also called from S_INKEY
;;;$0333
K_DECODE: LD A,E ; pick up the stored main key
CP $3A ; an arbitrary point between digits and letters
JR C,K_DIGIT ; forward to K_DIGIT with digits,space,enter
DEC C ; decrease MODE ( 0='KLC', 1='E', 2='G')
JP M,K_KLC_LET ; to K_KLC_LET if was zero
JR Z,K_E_LET ; to K_E_LET if was 1 for extended letters.
; proceed with graphic codes.
; Note. should selectively drop return address if code > 'U' ($55).
; i.e. abort the KEYBOARD call.
; e.g. cp 'V'; jr c addit; pop af; ;;addit etc. (5 bytes of instruction).
; (S_INKEY never gets into graphics mode.)
;; addit
ADD A,$4F ; add offset to augment 'A' to graphics A say.
RET ; return.
; Note. ( but [GRAPH] V gives RND, etc ).
; the jump was to here with extended mode with uppercase A-Z.
;;;$0341
K_E_LET: LD HL,E_UNSHIFT-$41; base address of E_UNSHIFT-$41
; ( $01EB in standard ROM )
INC B ; test B is it empty i.e. not a shift
JR Z,K_LOOK_UP ; forward to K_LOOK_UP if neither shift
LD HL,EXT_SHIFT-$41; Address: $0205 EXT_SHIFT-$41 base
;;;$034A
K_LOOK_UP: LD D,$00 ; prepare to index
ADD HL,DE ; add the main key value
LD A,(HL) ; pick up other mode value
RET ; return
; the jump was here with mode = 0
;;;$034F
K_KLC_LET: LD HL,SYM_CODES-$41; prepare base of SYM_CODES
BIT 0,B ; shift=$27 sym-shift=$18
JR Z,K_LOOK_UP ; back to K_LOOK_UP with symbol-shift
BIT 3,D ; test FLAGS is it 'K' mode (from OUT_CURS)
JR Z,K_TOKENS ; skip to K_TOKENS if so
BIT 3,(IY+$30) ; test FLAGS2 - consider CAPS LOCK ?
RET NZ ; return if so with main code.
INC B ; is shift being pressed ?
; result zero if not
RET NZ ; return if shift pressed.
ADD A,$20 ; else convert the code to lower case.
RET ; return.
; the jump was here for tokens
;;;$0364
K_TOKENS: ADD A,$A5 ; add offset to main code so that 'A'
; becomes 'NEW' etc.
RET ; return
; the jump was here with digits, space, enter and symbol shift (< $xx)
;;;$0367
K_DIGIT: CP $30 ; is it '0' or higher ?
RET C ; return with space, enter and symbol-shift
DEC C ; test MODE (was 0='KLC', 1='E', 2='G')
JP M,K_KLC_DGT ; jump to K_KLC_DGT if was 0.
JR NZ,K_GRA_DGT ; forward to K_GRA_DGT if mode was 2.
; continue with extended digits 0-9.
LD HL,E_DIGITS-$30 ; $0254 - base of E_DIGITS
BIT 5,B ; test - shift=$27 sym-shift=$18
JR Z,K_LOOK_UP ; to K_LOOK_UP if sym-shift
CP $38 ; is character '8' ?
JR NC,K_8_AND_9 ; to K_8_AND_9 if greater than '7'
SUB $20 ; reduce to ink range $10-$17
INC B ; shift ?
RET Z ; return if not.
ADD A,$08 ; add 8 to give paper range $18 - $1F
RET ; return
; 89
;;;$0382
K_8_AND_9: SUB $36 ; reduce to 02 and 03 bright codes
INC B ; test if shift pressed.
RET Z ; return if not.
ADD A,$FE ; subtract 2 setting carry
RET ; to give 0 and 1 flash codes.
; graphics mode with digits
;;;$0389
K_GRA_DGT: LD HL,CTL_CODES-$30; $0230 base address of CTL_CODES
CP $39 ; is key '9' ?
JR Z,K_LOOK_UP ; back to K_LOOK_UP - changed to $0F, GRAPHICS.
CP $30 ; is key '0' ?
JR Z,K_LOOK_UP ; back to K_LOOK_UP - changed to $0C, delete.
; for keys '0' - '7' we assign a mosaic character depending on shift.
AND $07 ; convert character to number. 0 - 7.
ADD A,$80 ; add offset - they start at $80
INC B ; destructively test for shift
RET Z ; and return if not pressed.
XOR $0F ; toggle bits becomes range $88-$8F
RET ; return.
; now digits in 'KLC' mode
;;;$039D
K_KLC_DGT: INC B ; return with digit codes if neither
RET Z ; shift key pressed.
BIT 5,B ; test for caps shift.
LD HL,CTL_CODES-$30; prepare base of table CTL_CODES.
JR NZ,K_LOOK_UP ; back to K_LOOK_UP if shift pressed.
; must have been symbol shift
SUB $10 ; for ascii most will now be correct
; on a standard typewriter.
CP $22 ; but '@' is not - see below.
JR Z,K_AT_CHAR ; forward to to K_AT_CHAR if so
CP $20 ; '_' is the other one that fails
RET NZ ; return if not.
LD A,$5F ; substitute ascii '_'
RET ; return.
;;;$03B2
K_AT_CHAR: LD A,$40 ; substitute ascii '@'
RET ; return.
;-------------------------------------------------------------------------
; The Spectrum Input character keys. One or two are abbreviated.
; From $00 Flash 0 to $FF COPY. The routine above has decoded all these.
; | 00 Fl0| 01 Fl1| 02 Br0| 03 Br1| 04 In0| 05 In1| 06 CAP| 07 EDT|
; | 08 LFT| 09 RIG| 0A DWN| 0B UP | 0C DEL| 0D ENT| 0E SYM| 0F GRA|
; | 10 Ik0| 11 Ik1| 12 Ik2| 13 Ik3| 14 Ik4| 15 Ik5| 16 Ik6| 17 Ik7|
; | 18 Pa0| 19 Pa1| 1A Pa2| 1B Pa3| 1C Pa4| 1D Pa5| 1E Pa6| 1F Pa7|
; | 20 SP | 21 ! | 22 " | 23 # | 24 $ | 25 % | 26 & | 27 ' |
; | 28 ( | 29 ) | 2A * | 2B + | 2C , | 2D - | 2E . | 2F / |
; | 30 0 | 31 1 | 32 2 | 33 3 | 34 4 | 35 5 | 36 6 | 37 7 |
; | 38 8 | 39 9 | 3A : | 3B ; | 3C < | 3D = | 3E > | 3F ? |
; | 40 @ | 41 A | 42 B | 43 C | 44 D | 45 E | 46 F | 47 G |
; | 48 H | 49 I | 4A J | 4B K | 4C L | 4D M | 4E N | 4F O |
; | 50 P | 51 Q | 52 R | 53 S | 54 T | 55 U | 56 V | 57 W |
; | 58 X | 59 Y | 5A Z | 5B [ | 5C \ | 5D ] | 5E ^ | 5F _ |
; | 60 ukp| 61 a | 62 b | 63 c | 64 d | 65 e | 66 f | 67 g |
; | 68 h | 69 i | 6A j | 6B k | 6C l | 6D m | 6E n | 6F o |
; | 70 p | 71 q | 72 r | 73 s | 74 t | 75 u | 76 v | 77 w |
; | 78 x | 79 y | 7A z | 7B { | 7C | | 7D } | 7E ~ | 7F (c)|
; | 80 128| 81 129| 82 130| 83 131| 84 132| 85 133| 86 134| 87 135|