4. THE KEYBOARD AND CASSETTE SYSTEM
The hardware which enables the keyboard to work has already been described in Chapter 1. To summarize, the keyboard is scanned every 30 ms using port A of the 8912 and port B of the 6522. This is done by writing to each column and row in the keyboard matrix identifying just one key at a time. At any moment there may be any number of keys pressed, but although the automatic scanning routine only looks for one key (or two, if you count the shift and control keys) it is still possible to look for multiple keypresses.
The keyboard routines in ROM leave behind a number of useful locations.
The most important address is #2DF which contains the ASCII value of the last keypress. This value is ORed with #80 by the keyboard routine to indicate that the keypress has not been processed.
Ihis location is subject to delays when the same key is pressed twice because of the autorepeat feature, so often you will want a faster access to the keyboard. Location 4208 is set to a unique value when a key is pressed, but there is no direct correspondence between this value and the ASCII sequence you will need to use a good deal of trial and error. The value here is a combination of two 3-bit column and row numbers.
For example, when A is pressed (in both upper-case and lowercase) you will find that location 4208 contains #AE.
The two shift keys and the control key are not recorded in location #208, but instead at 4209. This makes it possible to differentiate between the left and right shift keys useful for games, etc.
USEFUL ROM ADDRESSES
When fast key action is not required, a machine code program can quickly get the ASCII code of the last keypress with one of two calls:
1. To read a key without waiting, returning the ASCII code in the accumulator, call subroutine #E905 (version 1.0) or #EB78 (version 1.1). This is identical to using KEY$ in BASIC.
2. To wait for a key to be pressed (i.e., like GET in BASIC), call either #C5F8 (version 1.0) or #C5E8 (version 1.1).
INDEPENDENT KEYPRESS ROUTINE
The normal method of detecting keypresses is slow and inefficient, since the whole keyboard must be scanned 33 times a second and interrupts must be running for this to happen.
More importantly, the limitation of being able to read only one key at a time can be a real hurdle when writing a game program.
Program 4.1 shows a short subroutine that examines only one key and sets the zero flag to reflect the state of the key. In other words, the zero flag is set when the key is not pressed and clear when the key is pressed.
This subroutine can be used for any number of keys simultaneously. It requires two registers to be set up: the accumulator should contain the row number (0 7) and the X register should be set to the column number. The column number is one bit cleared in a byte containing OFF, i.e., #7F, #BF, #DF, #EF, #F7, #FB, g FD, or #FE. As with location #208, the required values do not fall in a recognizable pattern Table 4.1 gives the A and X values for each possible key. Version 1.1 users must change the instruction at #4005 to JSR #F590.
Table 4.1 Keypress values
1 2 3
4 5 6
7 8 9
0 - =
\ ESC Q
W E R
T Y U
I 0 P
[ ] DEL
CTRL A S
D F G
H J K
L ; "
Z X C
M comma period
/ SHIFT (RIGHT)
0 2 0
2 0 2
0 7 3
7 3 7
3 1 1
6 6 1
1 6 5
5 5 5
5 5 5
2 6 6
1 1 6
6 1 3
7 3 3
2 0 2
0 2 0
2 4 4
DF BF 7F
F7 FB FD
FE FE FD
FB F7 7F
BF DF BF
7F F7 FB
FD FE FE
FD FB F7
7F BF DF
EF DF BF
7F F7 FB
FD FE FE
FD FB 7F
DF BF 7F
F7 FB FD
FE FD FB
4.2 Cassette input/output
This section will describe the various ways in which the cassette system can be used.
There are three programs described in this part of the chapter, each giving an extra facility that can be used from BASIC.
The routines in ROM that allow cassette I/0 are neatly structured so that saving and loading can be done either:
1. As a complete section of memory.
2. One byte at a time.
3. One bit at a time.
The third option is not used in this chapter; most applications are only concerned with whole bytes.
However, Sec. 9. l speech synthesis shows how bits can be read from the cassette hardware.
Saving and loading bytes is often more useful than saving a large area since you can have a free hand as to the exact format of your data on tape.
This is one subject where the two versions of ROM differ greatly: both the subroutine addresses and the usage of page 0 and page 2 are altered.
Generally, version 1.1. uses page 2 to store filenames and flags, whereas version 1.0 uses the BASIC input buffer area #3F to #67.
4.3 Saving an area of memory
The sequence of events when saving a block of memory (remember that a BASIC program is just a block of memory) is:
1. Disable interrupts and change the 6522 into cassette mode.
2. Print the message SAVING and the filename on the top line ofthe screen.
3. Save a header record, composed of:
(a) 259 occurrences of #16 (this is the actual header).
(b) The value #24 to indicate the start of the record.
(c) For version 1.0 #5E to #66 or for version 1.1 #2A0 to #2B0. This information is saved backwards and includes the start and end addresses and other flags.
(d) A filename, ending with #0 this is either #35 onwards, for version 1.0, or #27F onwards, for version 1.1.
4. Save the block of memory, byte by byte.
5. Re-enable interrupts and reset the 6522 back to its normal mode.
LOCATIONS USED WHEN SAVING
From the previous paragraph, you will notice that all the important information is saved as a 9-byte block of data. Here is how version 1.0 uses its flags and buffers:
#5F, #60 Start address
#61, #62 End address.
#63 Autoload flag set to zero if no autoload required.
#64 Machine code of BASIC set to zero for BASIC.
#67 Speed. Zero means fast, one means slow.
#35 #44 Filename, terminated by #00.
In version 1.1, the same flags are stored as follows:
#2A9, #2AA Start address
#2AB, #2AC End address.
#2AD Autoload flag zero means no autoload.
#2AE Machine code flag set to zero if BASIC.
#24D Speed. Zero means fast, one means slow,
#27F #28E Filename, terminated with zero.
Although the addresses of these flags differ between ROM versions they are in an identical format. This allows programs saved to tape by one type of machine to work on a different type.
Note that the machine code and autoload flags will only be recognized by the CLOAD"" command if you use the subroutines as described in this chapter, they will be ignored.
Another point is that the speed flag is used by the routine that saves individual bytes. If unchanged, the speed will remain the same as the previous cassette operation.
In order to save a block of memory, having set up the speed, start address, etc., you must call a series of subroutines:
1. For version 1.0:
JSR E6CA (interrupts off)
JSR E57B (save)
JSR E804 (interrupts on)
2. For version 1.1:
JSR E76A (interrupts off)
JSR E585 (print saving)
JSR E607 (save header record)
JSR E62E (save area of memory)
JSR E93D (interrupts on)
4.4 Loading an area of memory
Loading back data is basically the reverse of saving, except that:
1. On version 1.1, the loading program may be just verifying the tape against memory.
2. The filename has to be matched against each filename on tape.
The sequence of events when loading (or verifying) is:
1. Disable interrupts and alter the 6522 ready for cassette I/0.
2. Print the message searching
3. Lock onto the file header, until a sequence of three #16s is detected.
4. Wait until #24 is detected and then read in the header record.
5. Store the filename coming in.
6. If the filename on the tape is different from the required name then go back to sequence 3. (Version 1.1 also prints FOUND XX.)
7. Change message to loading (or verifying).
8. Load or verify the file on tape.
9. Re-enable interrupts, etc.
See Sec. 4.3 for the important locations these are the same when loading. When loading, it is not necessary to provide the information that will be loaded in from the header record. The essential details are:
Version 1.0: #67 the tape speed (zero when fast, one when slow).
#35 #44 the filename, terminated by #00.
Version 1.1: #24D tape speed (zero when fast, one when slow).
#27F #28E the filename, terminated by #00.
#25B the verify flag set to zero for load, one for verify.
#25A the join flag set to zero for a normal load.
On version 1.1, the count of verify errors is stored at #25C,D. On both versions an error flag is kept at #2B1 this indicates errors in loading any byte. Location #2B1 will contain zero when there are no errors.
Note that when you use a series of subroutines as described in this chapter, you will not get messages such as errors found or the count of verify errors.
One improvement made in version 1.1 is that location #21F is checked before any message is displayed on the top line this prevents the HIRES screen from being overwritten.
The filename on tape is stored at #49 to #56 (version 1.0) or #293 to #2A2 (version 1.1).
In order to load a tape file, call the following subroutines: 1. Version 1.0:
JSR E6CA (disable interrupts, etc.)
JSR E4A8 (search and load)
JSR E804 (enable interrupts, etc.)
2. Version 1.1:
JSR E76A (disable interrupts, etc.)
JSR E57D (print searching message)
JSR E4AC (find file)
JSR E59B (print loading)
JSR E4E0 (load file, or verify)
JSR E93D (enable interrupts)
Note that these subroutines are not exactly the same as a CLOAD command. As mentioned before, no error messages are printed and, in addition to this, the program will not autorun.
On version 1.1, the routine that prints a message on the top line is patched via a jump at #241. This may be (carefully!) altered in order to add your own processing at either the search or the load phase.
Yet a further important difference between the two ROM versions exists when a BASIC program is loaded. On version 1.1 a subroutine is called which relinks all the lines in the program. This prevents problems arising when the links have been corrupted during loading, and allows the join facility to create an executable program. This is not done on version 1.0, so be warned that if you deliberately upset the links (one reason would be to stop LIST from working) you will find that version 1.1 ROMs correct your vandalism! If you are mixing machine code with BASIC, be sure to end your BASIC program with a link between #00 and #FF (see Chapter 2), or you may find some of your machine code gets corrupted.
SUMMARY OF ROM SUBROUTINES
In order for you to save and load data, byte by byte, here is a list of all important addresses. Note that in order to do any cassette I/0, you must first call the subroutine which disables interrupts. For version 1.0 this is WE6CA and for version 1.1 it is #E76A. When you have finished your cassette I/0, you should call #E804 (version 1.0) or #E93D (version 1.1).
1. Version 1.0:
Clear top line #E563
Print message on top line (Addressed by A=low,Y=high, X=start position) #F436
Find header #E696
Read one byte into accumulator #E630
Output header #E6BA
Output byte from the accumulator #E5C6
2. Version 1.1:
Clear top line #E5F5
Print message on top line (addressed by A=low, Y=high, at position X) #F865
Find header #E735
Read one byte into accumulator #E6C9
Output header #E75A
Output byte from accumulator #E65E
Note that the header referred to above is just the sequence of 259 lots of #16 not the header record.
Owners of version 1.1 will not need this routine (Program 4.2), as VERIFY is one of the features of the updated ROM.
To use this program, POKE #67 with zero or one (fast or slow tape speed) and CALL #B400.
If any differences are found, the errors found message is printed, and the program finishes. This will happen immediately after the error is found, unlike the version 1.1 verify routine which waits until the end. Another difference is that the program here leaves the address of the error at #0,1, so that further investigation is possible.
Note that you are able to load this program on top of an existing BASIC program because although the end-of-BASIC pointer ( #9C) is corrupted, the actual verify routine will subsequently correct it.
You will notice an unfamiliar address #E807. This is the same as #E804 (which enables interrupts, etc.), except that the subroutine is called at a later address in order to prevent the top line being cleared.
4.6 CLOAD with an exit
One irritation when loading a program is that there is no easy way to stop a CLOAD. Control-C does not work, of course (the keyboard is not scanned during cassette I/0), and the only resort is the Reset button,
While there is simply not enough time between loading each byte to scan the whole keyboard, it is possible to examine one particular key.
The following program loads the next program it finds, but will exit if I is pressed. We use the keyboard routine discussed in the first part of this chapter, but since the 6522 is in cassette mode we must make a temporary alteration to port B. Before looking for a keypress, one bit in port B is set to be input; after looking at the key, port B is set back to output.
4.7 Data saving and loading
The version 1.1 ROM gives the facility to save and recall complete data arrays. A similar routine is available to version 1.0 owners, published in Oric Owner magazine.
However, dealing with whole arrays is not always convenient, and one annoying feature is the long headers saved before each record. As has been discussed earlier, 259 bytes are saved to form the header. The purpose of this is to allow time between the cassette recorder stopping and starting, but at slow speed this amounts to 5 seconds!
The length of these headers has been greatly reduced in the following subroutines, and also depends on the tape speed. It is assumed that you are not using the cassette relay if you are, then you may need to increase the length of the header at #B307.
The following routines can be accessed from BASIC via the ! and & extension commands.
Data are saved using the! command. A short header is written first, followed by the actual data. For example, ! "START" would write a record containing the word START onto tape.
When a number is written out, it is saved as a floating-point number. A string is saved with the length first, followed by each character of the string.
To load back data you use the & function. This returns the next record from tape. For example, A$ = & (0) (the argument in brackets can be any numeric expression) would read the next string on tape into A$.
When loading data, it is important that the correct type of data is recognized; if you get the type wrong, you will get a TYPE MISMATCH error.
At fast speed, you must not put too much processing between the retrieval of each record unless a similar delay was incurred when the data were saved.
There is a short header recorded with each record on tape this gives a small amount of leeway between each record, but it is advisable to disconnect the REMOTE jack socket as the cassette should not be made to start and stop continually.
The following BASIC program (Program 4.5) shows how to use the data saving routines. Remember that it is only a guide you can save and recall both strings and numbers.
Note the two DOKE commands in line 30 these set up the addresses for the extension commands to work.
There are two versions, one for each version of ROM. You will probably want to code the routines into DATA statements, so that they can become part of a BASIC program. You may find Chapter 3 useful in understanding how the subroutines work.
1. The program for version 1.0 ROMs is listed in Program 4.6.
EXPLANATION OF SAVING DATA THE ! COMMAND
Note that the entry addresses are the same for version 1.0 and version 1.1 ROMs, although many of the subroutine calls are different.
1. At #B300, the first step is to call the formula evaluation subroutine. This reads in whatever follows the ! command.
2. At #B303, the 6522 is set up for cassette handling and interrupts are disabled.
3. Depending on the tape speed, either 8 (for slow speed) or 32 (for fast speed) bytes of header are written to tape.
4. To indicate an end to the header, #24 is written to tape.
5. At #B324, the first byte saved is the type indicator at #28. This will have been set to #0 if a number followed the! command or #FF if a string was processed.
6. For a number, the floating-point accumulator at #DO to #D5 is saved on tape, in reverse order.
7. For a string, the length is output, followed by each byte of the string itself.
8. In order to release the temporary string created by the formula evaluation routine a special ROM subroutine is called at #B356
9. At #B320, the subroutine to reset the 6522 is called, restarting clocks, enabling interrupts, etc.
EXPLANATION OF LOADING DATA THE & COMMAND
The intention of this chapter was to show how versatile the Orics tape system can be. You are not limited to saving and loading in a fixed way, but can devise your own file organization on tape. Also, you will see that it is possible to do extra processing between reading each byte. While your program is loading, why not make the colours on your screen slowly change, or move a message around? There are other tape routines in this book see the Merge program in Chapter 8 and the speech synthesis idea in Chapter 9.