4. THE KEYBOARD AND CASSETTE SYSTEM

 

4.1 Keyboard

 

HARDWARE

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.

USEFUL LOCATIONS

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 OR’ed with #80 by the keyboard routine to indicate that the keypress has not been processed.

‘I’his 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

Key required

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 ; "

RETURN

SHIFT (LEFT)

Z X C

VBN

M comma period

/ SHIFT (RIGHT)

LEFT ARROW

DOWN ARROW

SPACE

UP ARROW

RIGHT ARROW

Accumulator

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

7

4

2 0 2

0 2 0

2 4 4

7 7

4

4

4

4

4

X register

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

EF

DF BF 7F

F7 FB FD

FE FD FB

F7 EF

DF

BF

FE

F7

7F

 

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.

SUBROUTINES REQUIRED

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.

 

LOCATIONS USED

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).

SUBROUTINES REQUIRED

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.

SAVING DATA

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.

LOADING DATA

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.

EXAMPLE

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.

PROGRAM LISTINGS

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

  1. At #B35A, the 6522 is switched into cassette mode, with interrupts disabled, etc
  2. Following this, a subroutine is called in order to latch on to the small header coming in. Once it has established that it is reading the header, it will return.
  3. At #B360, the routine waits until the header finishes; one- #24 has been received, the actual data is loaded.
  4. The recall of data follows the same order as the save subroutine, so the first item encountered is the type of data flag. This is stored into 28 and is used to indicate which type of data is subsequently read.
  5. For a number, the data are stored back into the floating-point accumulator at #DO to gD5.
  6. For strings, the length is read, and then a subroutine (called at #B382) allocates the required amount of string space. The address of this area is stored at #D1,#D2 by this ROM sub- routine.
  7. Finally, the 6522 is reset, interrupts are enabled again, and the subroutine comes to an end with either: (a) the RTS instruction (for a number) (b) a jump to a special ROM address, after removing the top return address on the stack. This jump (at #B398) is different for each ROM version.

4.8 Conclusions

The intention of this chapter was to show how versatile the Oric’s 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.