These couple of articles are intended as a reference for anyone wishing to access cassette or disk files from machine code.
Part 1 : The Cassette System
Why would you want to use the cassette system from machine code? One of the reasons is the ability to deal with corrupted tapes. Suppose you have this rather long BASIC program saved on tape, and half way through it returns an IO error. There may be only 1 bit corrupted yet the whole file is deemed unusable. If you wrote your own routines, you could ignore the error and carry on loading the file, giving you a chance to try & spot the error & fix it.
The Dragon's cassette system uses the internal DAC to create two distinct sounds, one representing binary bit 0 & the other bit 1. Each byte is then turned into its appropriate bits & sent to the tape. The cassette can then be read in by detecting the different frequencies.
However, you don't need to know how it works in order to use the system. Rather than just dumping the raw data to tape, the Dragon has it's own tape file format and it is probably easier to use this format rather than adopt your own.
A Dragon cassette file consists of the following:
1. A leader block of $55 multiplied by the 16 bit number in location $90:91 (default 128). 2. A namefile block. 3. A blank section of tape for processing of the namefile block. 4. Another leader block of $90:91 bytes of $55 5. One or more data blocks. 6. An end-of-file block.
A header block, data block or EOF file block consists of:
1. A leader byte - $55
2. A sync byte - $3C
3. A block type byte: 00=namefile block
01=data block
FF=end-of-file block
4. A block length byte (0-255)
5. 0-255 bytes of data. For a namefile block this
consists of:
5.1 An 8 byte program name
5.2 A file ID byte where:
00=BASIC program
01=Data file
03=Binary file
5.3 An ASCII flag where:
00=Binary file
FF=ASCII file
5.4 A gap flag to indicate whether the
data stream is continuous (00) as
in binary or BASIC files, or in blocks
where the tape keeps stopping (FF) as
in data files.
5.5 Two bytes for the default EXEC address
of a binary file.
5.6 Two bytes for the default load address
of a binary file.
For a data block, this consists of the actual data to load/save and there is no data associated with an EOF block.
6. A checksum byte which is: sum of all the data bytes + block type + block length. 7. A trailer byte - $55
The namefile data listed under 5.1-5.6 is stored in locations 474-488 when a cassette file is read by the Dragon's BASIC commands.
There are 3 ROM calls necessary for writing tape files:
$801B - write the tape leader [$A008] - write a data block $8018 - turn the motor off
Most of the work is handled by these routines, and the user has to do very little. For example, in order to write a binary file.
HEADER - this defines the header of the
tape
FCC \EXAMPLE1\ - the filename
FCB $02 - a binary type
FCB $00 - binary data
FCB $00 - continuous data
FDB $89B4 - EXEC address, in this example I've
pointed it to the ?SN ERROR
routine
FDB $0500 - load address, in this example it
will load on the text screen
DATA - this defines the data to write
FCC \A TAPE SAVE DEMO\
Having set up the data, the file can now be written:
TAPE JSR $801B - write the leader
LDX #HEADER - point to the header
STX $7E - $7E:$7F is used to point to
the data to write
CLRA - code for namefile block
STA $7C - $7C is used for the block
type
LDA #15 - 15 bytes to write
STA $7D - $7D is used for the length
JSR [$A008] - write the header block
JSR $801B - write the next leader
LDX #DATA - point to the data
STX $7E
LDA #01 - data block
STA $7C
LDA #16 - 16 bytes of data
STA $7D
JSR [$A008] - write the data block
LDA #$FF - end of file block
STA $7C
CLR $7D - no data bytes
JSR [$A008] - write the EOF block
JSR $8018 - turn the cassette motor off
RTS
Running this program will write the tape file. You should now be able to CLOADM the file, which when loaded displays the text 'A TAPE SAVE DEMO' in the middle of the screen (all be it the spaces have turned black), and by typing EXEC should display ?SN ERROR.
The first article explained the theory of tape handling by the Dragon but probably of more use is the ability to read Dragon tape files. Once again using existing ROM calls makes life significantly easier. For loading tapes, the relevent calls are:
$8021 - turn cassette on for reading. [$A006] - read a tape block $8018 - turn cassette motor off
Reading a file is therefore straightforward. Call $8021 to open the file, following by [$A006] repeatedly until an end of file block is detected, then $8018 to switch the tape off.
As an example, to read the file written by the program in the last article:
BUFFER RMB 15 - buffer for header info
LOADER JSR $8021 - turn tape on for read
LDX #BUFFER - point tape buffer
STX $7E - to our buffer (7E:7F)
NXTBLK JSR [$A006] - read a block into buffer
TST $81 - error indicator
BNE ERROR - non-zero if an error
LDA $7C - block type
BEQ HEADER - 00 = header to process
CMPA #$FF - FF = EOF block
BEQ FINISH - close & quit
BRA NXTBLK - must be data, loop back
for next.
HEADER LDX BUFFER+11 - get the EXEC address
STX 157 - store in the EXEC vector
LDX BUFFER+13 - get the load address
STX $7E - store for the next block
BRA NXTBLK - get the next data block
FINISH JSR $8018 - switch tape off
RTS
ERROR JSR $8018 - switch tape off
LDB #$2A - code for IO error
JMP $8344 - call the error routine
This routine starts by calling $8021 to switch the cassette on. It then points the cassette buffer ($7E:7F) to a temporary buffer in order to load the header block (details of the structure of this block in last article). The header block is handled by the HEADER routine which extracts the only 2 pieces of information relevent to this program: where the program is to load & what EXEC address to default to. Any other data for the header can be extracted at this time (such as ID type), but if you are going to do a lot of processing, then you should switch the tape off/on as follows:
JSR $8018 - tape off
- process header -
JSR $8021 - tape back on for reading.
Between reading the header block & the first data block is the only time you can switch the tape on/off unless you are reading a gapped file (ie. a data file) which has gaps of silence interspurcing the data blocks.
The program load address is then stored in the cassette buffer pointer in order that data can then be loaded directly to that address. The routine then fetches the next block.
When data blocks are read in, the cassette buffer pointer ($7E:7F) is updated automatically so you can call the Block In routine without any further processing. Block In also returns the block length just read in location $7C.
If an error occurs (as detected by testing location $81) the tape is switched off and the BASIC error handler called to generate an IO error.
When an EOF block is detected, the tape is switched off, and the program finishes.