Cassette from Machine Code

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
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
FDB $0500   - load address, in this example it
              will load on the text screen

DATA        - this defines the data to write

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

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

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.

Tape Editor V1.1