This is a continuation of my series of articles on CP/M. It follows my second CP/M article.
The series starts with my first CP/M article. If you have not read the previous articles, I recommend that you read them first.
Writing to files…
To make it a bit easier, we will write to one file. I assume opening and closing a second file is not too different, except for lots of relocation of data defining the file.
Recall that CP/M uses the first 256 bytes of memory to store information for the transient program.
In the last article we looked at the command tail stored at 80h. Now let's look at the file control blocks at 5Ch.
Offset |
Length |
Contents |
00h |
1 byte |
drive number, 0 for current drive, 1 for A |
01h |
8 bytes |
file name, fill in with spaces if necessary |
09h |
3 bytes |
file name extensions, fill in with spaces if necessary |
0C |
20 bytes |
no idea what that is |
20h |
1 byte |
record number in current section of file (for sequential access) |
21 |
3 bytes |
record number (for random access) |
|
36 bytes
|
|
The BDOS offers a few useful functions to work with files.
0Fh |
open file named in FCB |
DE = FCB |
10h |
close file named in FCB |
DE = FCB |
14h |
read record from file into DMA |
DE = FCB |
15h |
write record from DMA to file |
DE = FCB |
16h |
create a file named in FCB |
DE = FCB |
1Ah |
set the DMA |
DE = DMA |
In the table "FCB" is the FCB address (=5Ch) and "DMA" is the DMA address (set by system call 1Ah).
The demo program is supposed to
- Write the file name "file.txt" into the FCB.
- Create the file for the FCB.
- Open the file.
- Write the string "Hello, file.$" to the file.
- Read that same string from the file to a different memory location.
- Output the string from that location.
Note the "$" symbol at the end of the string. It acts as the string delimiter and I didn't feel like adding it to the string after reading it from the file, so I just put it into the file. I think it makes the program easier to read.
The test program is a bit longer than the last two.
First comes the part with the constants.
I invented my own names for all the system calls I use. For example "freadr" is "file read record".
You can find the real names of all BDOS system calls here.
Note the completely unnecessary constants like "tlength" and "ctail" which survived from the previous program. All the system calls beginning with "f" are new.
All memory locations for data are defined in the last part of the file. They are "msg", "result" and "name".
Second comes the part with creating, opening and writing to the file.
The file name is 12 bytes long and consists of a drive letter (0 for current drive, 1 for A and so on), 8 characters for the actual file name and 3 characters for the file name extension. LDIR loads a byte from where HL is pointing to to where DE is pointing to, increments HL and DE and repeats the process as often as register BC says. The file name is stored at "name".
Calling fmake and fopen then creates and opens the file.
Calling setdma then configures the file control block's DMA address as "msg" (whereever that is, but we put a message there) and fwriter writes the record at "msg" to the file.
Third comes the part with resetting the record index, reading the same record from the file and writing it to the console.
Calling setdma sets the DMA address to a different location ("result" contains as many spaces as the text in "msg" is long).
Turns out writing a record to the file changed the sequential access record index in the FCB so I have to reset it to 0.
Calling freadr then reads the record (at index 0) into the (new) DMA address and fclose closes the file.
Finally the contents of memory at "result" is written to the console. (This is where the "$" sign at the end of the string comes in handy because cwrites uses the "$" sign as string delimiter and stops output at the byte preceeeding the "$" sign.)
The fourth part defines the three memory locations we worked with above.
ASCII 26 (substitute) marks the end of a file in CP/M. The file can still contain data after the ASCII 26 (and probably will) but the operating system will pretend that the file ends there (because it does).
You can download the source file in copy-and-pastable form here.
After assemling and running the program the result we can see that this might have worked as intended.
After assembling and loading the source file, we get FILETEST.COM.
Running FILETEST.COM produces the output "Hello, file."
We now have a file FILE.TXT on the same drive.
And that file contains the string "Hello, file.$".
The only problem is that running the program again will produce a second file FILE.TXT. I have no idea why that happens (or why that is even possible).
New instructions used in filetest.mac:
LD HL,immediate |
instruction |
loads an immediate into 16 bit register HL |
BC |
register |
a 16 bit register used as a counter |
LD BC,immediate |
instruction |
loads an immediate into 16 bit register BC |
LDIR |
instruction |
copies from address stored in HL to address stored in DE and decreases BC until BC=0 |
LD (HL),immediate |
instruction |
loads an immediate into the address stored in HL |