CPM80 Programmer's Guide
CPM80 Programmer's Guide
Consulting Editor
Professor F. H. Sumner, University of Manchester
Peter Whittle
B.Sc.
M
MACMILLAN
© Barry Morrell and Peter Whittle 1985
Published by
MACMILLAN EDUCATION LTD
Houndmills, Basingstoke, Hampshire RG21 2XS
and London
Companies and representatives
throughout the world
Typeset by TecSet Ltd, Sutton, Surrey
Preface ix
Acknowledgements X
1 Introduction 1
What is an operating system? 1
What is CP/M 80? 2
What does CP/M 80 consist of? 3
How do you interface with CP/M 80? 4
How do your programs interface with CP/M 80? 4
Why use machine code? 6
Using high level languages under CP/M 6
Summary 6
2 Program Development 8
Design 8
Program documentation 9
Cutting code 9
Assembling, loading and running 10
Basic principles 10
Building libraries 13
Testing and debugging 14
Optimisation 14
Summary 15
3 Design 16
Information gathering 16
The main design activity 17
General points 17
Subroutine coupling 18
Program portability 19
The final stages of design 20
Summary 21
v
vi CONTENTS
5 File Handling 36
Discs and files 36
Creating a simple file 38
Reading a simple file 41
Adding data to the end of a file 43
More about files 45
FCBs and their contents 45
Sectors and blocking 45
Types of file 47
Data areas 47
Passing filenames via the keyboard 49
Directory operations 51
Using random access with files 55
Random access of a sequentially-written file 56
Writing sectors in random order 58
Error handling with files 60
Summary 60
6 Disc Operations 62
Introduction 62
Protecting your discs and files 63
Getting hold of disc characteristics 64
Introduction to the disc data structures 64
The allocation maps 67
The Disc Parameter Blocks (DPBs) 68
Allocation block sizes 70
Summary 71
Index 159
Preface
This book is for people who want to learn how to program under the CP/M 80
Operating System. It is not a guide to using CP/M. Before reading it you should
have some knowledge of programming using Z80 machine code and programming
in a high level language such as BASIC. You should also have read a book on CP/M
operating such as Using CP/M by Peter Gosling (Macmillan, 1985).
The main part of the present book is meant to be read sequentially and, if you
have little experience of machine code programming, this is the way that you
should read it. If you are already an experienced programmer, you may want to
'dive' into the book at a particular point. In this case, you will find a list of
keywords at the front of each chapter which will tell you what is covered within
that chapter, and a more detailed summary at the end of most chapters.
The appendixes are meant to be used for reference purposes. They include
descriptions of BDOS functions, laid out in alphabetical order with a separate
page for each function. A list of functions grouped under appropriate headings is
also given.
Examples in this book are written using Research Machines Ltd's Z80 macro
assembler ZASM, mainly because it is a true Z80 assembler. However, with
minor changes they can run under other assemblers such as Microsoft's M80.
The main differences between ZASM and M80 are summarised in appendix F.
Acknowledgements
To reviewers and Research Machines Ltd for their help. In particular, to Research
Machines for their permission to use some of the information on program
portability in chapter 3.
CP/M and CP/NET are registered trademarks of Digital Research. Zilog and
ZBO are trademarks of Zilog Inc.
ProPascal is a registered trademark of Prospero Software.
Microsoft BASIC is a registered trademark of Microsoft.
ZASM is a registered trademark of Research Machines Ltd.
Wordstar is a registered trademark of MicroPro International Corporation.
1 Introduction
CP/M is an operating system which was first produced in 1975 by Gary Kildall,
a consultant at Intel Corporation, and its name stands for Control Program for
Microcomputers. It was first used on the 8-bit 8080 microprocessor and subse-
quently ran on a number of different computers.
1
2 CP/M 80 PROGRAMMER'S GUIDE
would be sensible to put these instructions into subroutines and make them
general enough to be of use in other programs.
Now, suppose you want to sell the computer to a lot of other people who will
write their own programs. It would be sensible to make your subroutines available
to them as well, in the form of a package. In fact,this is what happens and the
package is called an operating system. There are other parts to an operating
system besides the input/output subroutines. The main ones are as follows
• Some utilities, which are basically just programs that perform standard tasks
such as copying data from one device to another
• A means of communicating with the operating system from the keyboard using
commands, otherwise known as a command processor
Operating systems should provide at least one other facility: the basis for error
handling and recovery when things go wrong. Earlier versions of CP/M were
lacking in this respect; however, Personal CP/M and CP/M Plus give you much
more control when errors such as disc failures occur.
More sophisticated operating systems (including some versions of CP/M) provide
you with other facilities. For example, some allow you to run a number of
programs at the same time ('concurrently'). However, they all have one thing in
common: they provide the programmer with a consistent set of building blocks to
help him develop his programs.
There are now several million copies of CP/M in the field and it exists in a number
of versions aimed at different machines and markets. These fall into three main
families
This book describes how to write programs using the main versions of CP/M 80:
CP/M 1.4 and CP/M 2.2. It does not describe the additional facilities of CP/M
Plus 80 or Personal CP/M 80. However, the general principles apply to these as
well and this book should be a useful introduction to CP/M before you look at
either of these operating systems. Examples are given in Zilog Z80 machine code
because most versions of CP/M 80 are used with Z80 processors. You should
therefore have an understanding of Z80 machine code.
Appendix E summarises the differences between the various versions of CP/M.
For the present, all you need to understand is that CP/M is a control program, or
operating system.
INTRODUCTION 3
Their position in your computer's memory is shown in figure 1.1. This also shows
the names for some of the more useful addresses.
______ TPA
,.,......,...---.
r-_ _ _ ___......---
TBASE
(100Hex)
System parameters
BOOT
(OHex)---~-L----------------~
The BIOS defines the low level interface with your computer system which is
necessary for device input/output. It is different for every make of computer. The
BDOS defines file structure for discs and handles access to devices such as the
keyboard and screen. Both are combined into a single module with a common
entry point from your programs; this is referred to as the FDOS.
The CCP acts as an interface between the computer and yourself. It analyses
whatever you type on the keyboard (for example, TYPE, DIR) and asks the
FDOS to carry out the operations requested.
The TPA is a fancy name for an area where you can load and run programs. It
is also used by CP/M to hold system utilities such as PIP during their period of
operation. You can increase the size of the TPA to include the area covered by the
CCP, as long as you reload the CCP when your program has finished.
4 CP/M 80 PROGRAMMER'S GUIDE
Digital Research refer to user programs as transient programs. We will not use
this unfriendly name; instead, we will call them your programs.
You communicate with CP/M via the CCP by typing command lines such as DIR
and REN after each prompt. Each command line takes one of the following three
forms
command
command filel
command file] file2
command can be one of three things: a built-in function held within CP/M such as
DIR and TYPE; the name of a command such as PIP, which is loaded into the
TPA; or a program that you have written yourself. In each case, CP/M will try to
run the command or program, loading it if necessary from disc.
Suppose the following form of command line is used
command
CP/M has a number of interfaces and these are shown in figure 1.2. The layers
shown normally communicate with the next layer down; for example, your
programs normally communicate with the BDOS. However, there are exceptions,
and under special circumstances your programs may wish to communicate with
lower layers.
At the highest level is the interface between yourself and your application
INTRODUCTION 5
. . a.--
•
Normal method of
communication
Sometimes used
~ • •under special
circumstances
programs. Below this is the interface between your programs and the BDOS. Your
programs can access this interface using BDOS functions and this is the normal
method of programming under CP/M. Chapters 4, 5 and 6 tell you how to write
programs using BOOS functions and the functions themselves are described in
detail in appendix D.
At the next level down is the interface between your programs and the BIOS.
Your programs can access this interface, but programming at this level requires a
special knowledge of the BIOS itself and is not described in this book.
At the lowest level, your programs may be able to access the facilities of the
computer's own 'operating system', or firmware. This may or may not be described
in the manuals of your computer. If it is, you should access it only if you have no
alternative; firmware requires a lot of understanding and careful use, and programs
that access it directly are not easily transportable from one type of computer to
another.
6 CP/M 80 PROGRAMMER'S GUIDE
High level languages such as PASCAL and BASIC sometimes have standard sub-
routines which allow you to access the operating system. Using them you could,
for example, determine the operating system version number or read the disc
directory.
Even if there are no standard subroutines available, the high level language
might let you write your own routines in another language (including machine
code) and 'bolt' them on to your high level language program. This is called
mixed language programming support and it is described in more detail in
chapter 9.
Summary
• By now, you should understand what an operating system like CP/M is, even if
you do not know in detail how it works. You should also know the main parts
of CP/M.
• Perhaps the most important thing to understand before you go any further are
the reasons for using machine code and high level languages and, indeed, how
you use them. If you need further explanations about machine code, try
looking through either of the following books before you go any further:
INTRODUCTION 7
2 Program Development
Assembler Documentation
Assembling Libraries Running
Coding Loading Testing
Debugging Optimisation Text editor
Design Program source file Zasm
This chapter covers the main stages of program development. Some of the areas
covered may seem incongruous to the home programmer. However, the boundary
between the professional and the amateur programmer is blurred these days, when
home enthusiasts are selling their wares.
Even if financial reward is not the end point, it is still worth adopting a
professional approach to program development. You may save time in the short
term by adopting a sloppy approach, but many hours may be lost in the long term.
There are six main stages of program development
• Design
• Program documentation
• Coding
• Assembling, loading and running
• Testing and debugging
• Optimisation
Design
Programs that are 'thrown' together are likely to be less reliable than those that
are designed. The bugs that they contain can also take a long time to 'iron out'. If
the program is small this may not matter. If it is sophisticated a lot of time will be
lost.
Much more time should be spent on the design than on the actual coding;
probably two-thirds on design to one-third on coding. What often happens is the
8
PROGRAM DEVELOPMENT 9
reverse: something like 2 days for the design, 2 weeks of coding, 1 month of
debugging and 2 months of redesign!
Design is one of the most important areas of program development. For this
reason, an entire chapter has been devoted to it- see chapter 3.
Program documentation
If you have not been documenting as you go along, whenever you get a section
of code working, go back and document it. Do not postpone documentation until
you have done more coding; by then, you will have forgotten the points that are
worth documenting.
If you keep detailed design notes of any tabular structures you can modify
them when necessary and use them as a sizeable part of the final documentation.
It is best to keep them in a proper notebook: loose pages can easily be mislaid or
get out of sequence. However, if time is short, throw a few blank pages at the end
of each assembler listing and scribble notes on them.
There is one other point to mention in connection with screen documentation.
If you create screen pictures at the design stage using a text editor, or by some
other mechanism, you can get initial feedback from potential users and get
someone else to document them while you are producing the software.
Cutting code
Programming is an exact science and there is a strong need for clear thinking,
especially when coding. A piece of code that is 'fuzzy' and difficult to follow is
more likely to contain bugs. Code that is clear, concise and easy to read is more
likely to work correctly.
10 CP/M 80 PROGRAMMER'S GUIDE
When you first code a program you should concentrate on clarity. Don't worry
about the program's performance at this stage: you can optimise the implementa-
tion later, when the program is up and running. The most effective contribution
towards performance should have been made at the design stage, by selecting the
best algorithms. When you first code the design you should concentrate on
implementing it in the clearest possible way.
If you try to use 'clever' code from the start it will probably take you longer to
get the performance required, and the program will be more difficult for other
people to maintain. Always start off by assuming that someone else will maintain
your programs and that you will maintain other people's; that is a good guide-line -
if you are sloppy, this will give them no incentive to be clear and you will lose in
the long run.
While you are coding you should always try to use existing libraries of routines
where possible: there is not much point in re-inventing the wheel. If libraries are
non-existent, try building up your own for future use. For example, if you write
code to parse a command line (interpret the items in it), make the code more
general so that it can be used in other programs. This gives a higher cost initially,
but in the long run you save a lot of time and all your programs will be similar.
One final point to remember when coding is to make your code as portable as
possible. If you can isolate the areas of code that are machine-dependent, you will
make the program easier to transfer on to another system. Program portability is
covered further in chapter 3.
Basic principles
Machine code programs are in a form that the hardware can easily obey: a set of
binary numbers. However, they are not very easy to input into the computer and
they are certainly not easy for a human to understand.
To make programming in machine code a lot easier, you can use a program
called an assembler. This converts text that is easier for you to understand into
binary code that the computer can obey (a process called assembly). It also
generates a listing file which you can use when you debug the program.
Before you use an assembler, you must create a text file (the program source
file) which the assembler can understand. You do this using a text editor.
The rest of this section describes how to assemble, load and run a program by
using a simple program example. The stages involved are summarised in figure 2.1.
and parts of this figure are used in the rest of this chapter to highlight each stage.
PROGRAM DEVELOPMENT 11
PROG.ZSM
88
8-B
PROG.HEX Listing file
PROG.COM
First of all, you need to create the program source file on disc using a text
editor. If you are not familiar with a text editor you should learn to use one as
soon as possible. The areas that you need to learn about first are
For the present you can type in the following program using PIP. Be very
careful when you do this: if you make a mistake, start again. Now type
PIP PROG.ZSM=CON:
press RETURN and then input the following program source.
BOOS = 0005H
FN_PRINT_STRING = 9
WARM BOOT = 0
ORG lOOH
LD SP,lOOOH
LD DE,MESSAGE
LD C,FN_fRINT_STRING
CALL BOOS
JP WARM_BOOT
MESSAGE DB "Hello world!$"
END
12 CP/M 80 PROGRAMMER'S GUIDE
PROG.ZSM
When you have fmished doing
this, type CTRL/Z. This will create
the program source me PROG. ZSM.
PROG.ZSM
You can now assemble your program
using an assembler such as Research
Machines ZASM. To do this, type
88
ZASMPROG
8
You can now produce a memory image of
the program by typing
LOADPROG
PROG.HEX
~
E}B
The LOAD utility is supplied with your CP/M
system. It converts an intermediate form of the
program into an actual executable memory image.
LOAD reads in a .HEX me and creates a .COM me PROG.COM
of the same name.
The memory image will be held in the me PROG .COM and it can be loaded
and run by typing
PROG
PROGRAM DEVELOPMENT 13
When executed, the program will look like a CP/M command. This feature
allows you to 'invent' new CP/M commands.
Building libraries
The simplest way of producing a library is to keep it in source form. Using this
method, each library routine is kept in a separate file and individual routines can
be merged with your program using a text editor.
Many assemblers will also let you merge source files of routines during
assembly. They contain INCLUDE commands such as the following:
*I SCROP
where SCROP would be a file containing the source of a subroutine that handles
screen output. By putting this command in the source of your main program, the
source code in SCROP will be merged with the program when the program is
assembled.
The previous two methods of building libraries have one major disadvantage:
if you have a lot of programs which use one routine in particular and you modify
that routine, you have to re-assemble all of the programs. To get around this
problem, you can use a different type of library using relocatable (REL) library
files. Each of these files contains a copy of a routine's code. When the main
program has been assembled, you use a program called a linker to link the code of
each routine into the program.
Each REL file also contains information which describes
When you write your program, you can declare each name to be global (that
is, it can be used by any routine) or external to the program itself. The linker
looks at the names in each program instruction; if a name is declared as external,
it searches the REL files that you specify to try to merge the appropriate routines.
The types of library we have covered up to now will be sufficient for most
people. However, if you have a large program that contains, say, 200 routines, you
might use a librarian. This is a program that takes the relocatable code from several
ftles and merges them into one library file. As part of the operation, it marks each
routine with its own identification so that a special linker can easily find the
routine and link it into your programs.
To sum up on the subject of libraries you have a number of options, some of
which cost nothing and some of which you may have to pay extra for. Whichever
method you need depends upon the type of programs you write and what you can
afford. However, you should use, at least, the most basic method in the interests
of efficiency.
14 CP/M 80 PROGRAMMER'S GUIDE
When testing a program, you should always assume that it will not work. You
should put into it sufficient messages that tell you which parts of the program
have been reached. When the program fails, you can then insert diagnostic code to
tell you what is happening.
For the first few attempts you should use the simplest possible data as input.
Only when the program works with that should you try anything more complex.
You should next test the program using invalid data and, finally, the more complex,
valid, data.
If your program has been well-designed and is modular in structure, testing and
debugging it will be straightforward. You can print out registers at the beginning
and end of the offending modules and work out what is happening in between. If
the program has not been well-designed, testing and debugging can be very
complicated.
As debugging is such an important part of programming and an art in itself, a
separate chapter (chapter 7) has been devoted to it.
Optimisation
Optimisation can take three forms: making a program smaller, making it faster,
and improving its user interface. Normally, the greatest impact in these areas takes
place during the design phase and optimisation should not, therefore, be a major
job.
You should optimise a program only when you are satisfied that it is working.
To do so earlier is inefficient (you may decide during testing that a particular
routine needs rewriting, or scrapping, and if it contains some optimised code, time
would have been wasted).
If you are optimising a program for size, you should look at the largest modules
first because that is where the benefits are likely to be greatest. Try to find areas
of code that are the same, or similar, and turn them into routines. This can be
useful even at the smaller scale if you are short of space. For example, if your
program contains 20 calls to routines A and B and they always occur together,
combining them into one call to C, which itself calls A and then B, will give code
that is slightly slower but will save you about 60 bytes.
PROGRAM DEVELOPMENT 15
Summary
Design
Program documentation
Coding
Assembling, loading and running
Testing and debugging
Optimisation
The sooner you start coding your program, the longer it is going to take.
It's like building a house with nails, hammer, bricks and cement,
but no blueprint: CHAOS!
Henry Ledgard
The design process is not and should not be a rigid one. There is scope for innova-
tion, particularly when you are dealing with a completely new concept. However,
a certain amount of self-discipline is necessary if you want a project to run
smoothly.
Don't jump in at the deep end! You should always spend a significant amount
of time on design before you start coding. In doing this you will reduce the testing
and debugging processes which can easily take up half the program development
time. There is no harm in writing 'test bed' code during the design process to
explore the potential solutions of problems, but you should not be afraid to scrap
the code and rewrite it from scratch once you have learned what you wanted
from it.
lnfonnation gathering
The early part of the design process is mainly concerned with information
gathering. Think about the application in general terms. What type of people will
be using it and why are they using it? Do they need any special considerations? If
they are unfamiliar with computers, be prepared to put in a lot of work on the
user interface. Humans fmd it difficult to handle two new concepts simultaneously,
and if computer is a completely new concept to users you have to take this into
account.
Get a picture of what you need to do and research the subject if necessary.
Analyse any problem areas by asking yourself
16
DESIGN 17
The last of these three points is the most potent method of making life easier
for yourself: divide and conquer!
As a result of the information gathering process, itemise any special conditions
and the actions that you are going to take in relation to them. Tick them off when
they have been dealt with.
General points
When you have gathered the information that you need, you should then work
out a design solution in general terms. In particular, think out the main data
structures and how they can be linked together in the form of tables.
For example, suppose you want to write a program that outputs the same
data to a number of different screens at different times. What you could do is
put details of each device (for example, screen width and number of lines) into a
table as shown in figure 3 .1. You can then have a generalised piece of code which
handles output and which picks up from the table details of the appropriate
screen before writing to it.
Device list
--
Device table
SCREEN1 Screen 1 info.
/
SCREEN2 Screen 3 info.
SCREEN3
Programs with structures like this are described as being 'table-driven' and they
are easy to maintain. To add a new device you merely make up a new device table,
add a new entry to the device list and update the end of list pointer.
As well as looking at data structures you should organise your program so that
it has a good structure. Make the main part of the program a series of calls to
modules and put this in a prominent place. In designing the modules that will
18 CP/M 80 PROGRAMMER'S GUIDE
make up your program, try to make them as general as possible, so that they can
be used in different parts of the program and, indeed, in other programs that you
write. Have you designed modules for other programs which could be used here as
'building blocks'? This type of approach will save you a lot of time over the years.
In particular, it is easier to debug several small modules than one massive chunk of
code; and if you have tested them for use in other programs, that's half the
battle!
You should identify the key modules of your program and how they interact.
Programming bugs often occur at interfaces so you should always pay particular
attention to these and define them clearly.
Where there is a conditional statement (IF ... THEN ... ELSE) there is often
a bug lurking! So check each of the branches in your program.
When dealing with numbers, decide what level of accuracy you need and try
not to make your programs too sensitive. We know of one program that gave
different results on two related computers because the floating point arithmetic
units of each gave results to a slightly different degree of accuracy and the
program used the full floating point accuracy.
One area that ought to be considered throughout the development process, but
often is not, is validation. There is nothing worse than having your program fail at
the most important moment. More often than not, other people will be present
when it happens and they will not be impressed! You should therefore design tests
and test data as part of the main design process.
Before you start to design tests, identify the boundary conditions of your
program: in other words, where are its limitations? For example, suppose your
program needs to request a file name from the keyboard. What happens if you
type in an invalid filename? What happens if the file does not exist or is of the
wrong type (a .COM file instead of, say, .TXT)? What happens if you specify an
invalid drive name or a disc is not in the drive?
If your program opens an output file, what happens if the file already exists,
the disc is full or becomes full before you finish writing the file?
Moving away from files, how well does your program validate the user input?
Will it crash if you give it the wrong type of data (for example, text when it
expects a number)? What happens when you just type a carriage return?
When you design your tests, bear all these things in mind and look for other
boundary conditions. Test the areas beyond the boundaries, but do not forget to
test valid data as well, sampling it at random if possible.
Subroutine coupling
When subroutines share common data they are described as being tightly coupled.
This type of arrangement is shown in the diagram below.
DESIGN 19
They may even be very tightly coupled, as shown in the next diagram. In this
case, subroutines A and B access each other's data space.
Data Data
A B
Subroutines of the last type are very efficient in terms of machine resources,
but they are also difficult to debug as there is a lot of interaction. A much easier
system to debug is the loosely coupled one shown below.
Data
A Data
- B Data
- c
Here, each subroutine has its own, local, data which no other subroutine can
access and the interfaces between each are clearly defined.
In practice, you will probably use a combination of the loosely coupled and
tightly coupled systems: each subroutine may have its own local data but also
have access to a certain amount of 'global' data. However, whichever method you
use, you should be aware of its advantages and disadvantages.
Program portability
There are two aspects to this: you might want to write software that can run on
other computers, or you might want to take software from another computer to
your own.
The most important thing to remember is to use only published interfaces:
BDOS functions and BIOS calls (described in the Digital Research documentation).
You should use BDOS functions rather than BIOS calls, if possible. However, even
BIOS calls are better than talking to the firmware and hardware directly. You
should also try to restrict your programming to the common set of BDOS
functions; these are shown in appendix E.
20 CP/M 80 PROGRAMMER'S GUIDE
The first thing to check when converting software to run on your computer is
whether or not your TPA is large enough to hold the program. Most CP/M systems
have 32K of TPA available but your source machine may have more.
Doe.s the program need special devices or hardware that may not work on your
computer and does it contain any machine-specific subroutines?
More points that may cause problems when using programs from other
machines are listed below.
When writing portable software, many of these details can be set up in the
form of a table and will thus be easier to change. In particular, it is best to put
screen control sequences in a table and address each (variable-length) sequence
using a separate table of pointers; this is then easy to change.
You should also isolate BOOS calls, or indeed, any input/output calls, to
individual subroutines. This gives an overhead but ensures that the program is
more suitable for transfer to non-CP/M systems.
When writing portable software you should take care not to use memory in
page zero or in the operating system and firmware. In particular, you should not
use the RST addresses; these may be used by the firmware.
There is always a trade-off between portability and performance when writing
software. Sometimes it is more efficient to break the rules and take advantage of
the special features of a machine. However, you should always remember that you
might have to modify your program to make it run on a different machine.
• Check out the design. Play devil's advocate and try to break your design
• Check and recheck your work
DESIGN 21
• Document your design, particularly if you are passing parts on for other people
to code. It will also help if you need to modify the system later
• Don't write proper code (as opposed to 'test bed' code) during the design
phase. Concentrate on functionality and inter-module interfaces
Summary
• Don't jump in at the deep end! Resist the temptation to start coding
• Write 'test bed' code, if necessary, but don't be afraid to scrap it
• Gather whatever information you need
• Think about the people who will be using the program. Put in a lot of work on
the user interface, especially if the program is aimed at beginners to computing
• Analyse any problem areas:
• Think about the main data structures. Table-driven structures are worth
considering as they are easy to maintain
• Structure your program well and divide it into modules that could be used
elsewhere
• Don't re-invent the wheel!
• Pay particular attention to module interfaces and conditional statements
(IF ... THEN ... ELSE) as bugs are often found there
• Consider validation (testing) and design your test data
• Keep your subroutines as loosely coupled as possible (try not to let them
access one another's data space)
• Decide whether or not your program may be put on to a different computer.
If the answer is 'yes', design the program to be portable
• Before coding
This chapter introduces you to the mechanism by which your programs communi-
cate with CP/M: the BDOS function call. The first section describes the basic
principles involved: the rest explain how to use the simple BDOS functions.
CP/M interfaces your program to the hardware and to access its facilities you have
to make a request to the operating system. We will look at the way it does this by
using a simple analogy.
Suppose that you have a friend living some distance from you and that you
want him to do something for you. You might ask him to do it by putting your
request in a letter and sending it to him. Similarly, with CP/M, you tell the BDOS
what you want it to do by loading the C register with a number (the BDOS
function code) that describes the request. You then send this to the operating
system by calling the BDOS entry point at address OOOSH.
As a simple example, suppose that you want to exit from your program and
reset the system so that it is in a 'clean' state. You can do this as follows
BOOS = OOOSH
FN_SYSTEM_RESET = 0
EXIT: LO C,FN_SYSTEM_RESET
CALL BOOS
Here, the function code 0 is used to tell CP/M that you want to perform a
system reset. The piece of code loads function code 0 into register C and then
calls the BDOS to perform the reset. Notice how we use the symbol
22
USING SIMPLE BDOS FUNCTIONS 23
'FN_SYSTEM_RESET' to load register C and not the value 0. This makes the
program easier to read and understand.
Going back to the analogy, suppose that you want your friend to send you his
telephone number. Again, you go through the same procedure of putting your
request in a letter and sending it off. However, this time you get something back:
a telephone number.
CP/M has a number of BDOS functions that give you something back. One of
them is Return Version Number (function 12), which returns two numbers in the
registers H and L; an example of its use follows.
BOOS = 0005H
FN_SYSTEM_RESET = 0
FN_VERSIO~NO = 12
LD C,FN_VERSION_NO
CALL BOOS
EXIT: LD C,FN_SYSTEM_RESET
CALL BOOS
Here, again, you go _through the procedure of loading the C register with the
function number (this time, the value 12) and then calling the BDOS entry point.
However, when control passes back to your program, CP/M will have inserted in
registers H and L two numbers that describe which type and version of the
operating system your program is running under. The number in H describes the
system type.
H System type
0 CP/M
1 MP/M
2 CP/NET
The number in L describes the version number and the full range is shown on
page 140.
In this example, we check the system type and do a system reset if the system
is not CP/M. The normal mode of use offunction 12 would be where we want to
write a program that can run under different operating systems in the CP/M
family. This program can be made to run under all versions, but we can improve it
24 CP/M 80 PROGRAMMER'S GUIDE
CP/M allows you to send ASCII characters to the screen and printer using BDOS
function calls. This section describes how the following three are used.
Function Function
code
With these and some other function calls, you need to pass information other
than the function code to the operating system. For example, with Console
Output (function 2), you put the character you want to output in register E
before you call the BDOS.
BOOS = OOOSH
fN_CONSOLE_OUT = 2
LD E, 'Q'
LD C,f~CONSOLE_OUT
CALL BOOS
If you want to output more than one character to the screen, you can use Print
String (function 9). With this, you need to give CP/M two pieces of information
USING SIMPLE BDOS FUNCTIONS 25
The start address of the string is a 16-bit quantity, so we use the 16-bit register
pair DE to hold it. To define the end of the string, we use CP/M's special end-of-
line character $.
The following piece of code shows how you can output a string of text using
Print String (function 9).
BOOS = 0005H
F~PRINT_STRING = 9
ENO_Of_STRING = '$'
LO C,FN_PRINT_STRING
CALL BOOS ;Print message.
The string of text is stored in successive bytes, (where the last byte is a$
character) as shown below. This can be considered to be a one-dimensional array.
You could have put the$ at the end of the string, instead of using 'END_OF_
STRING'. We have written the code in the way shown above for ease of
maintenance: if the terminator character were to change, program modifications
can be minimised.
A consequence of the use of$ as a terminator is this: you cannot print a $
character using Print String. You must, instead, use Console Out (function 2).
The last BDOS function that we will look at in this section is List Output
(function 5). This allows you to send one ASCII character to the printer and it is
similar in use to Console Output (function 2): you load the ASCII character that
you want to print into register E, load the function code 5 into register C and
then call the BOOS. The character is not copied ('echoed') to the screen.
In the examples that we have shown in this section, only alphabetic ASCII
characters have been shown. You can, if you wish, use output control characters
as well, and the full range is shown in appendix C. Note that the effects of some of
26 CP/M 80 PROGRAMMER'S GUIDE
them differ between systems; you should thus check your particular CP/M
implementation and update the table in appendix C accordingly.
As well as being able to send information to the screen, your program may want
to accept information from the keyboard. CP/M allows you to do this using the
following two BDOS function calls.
Function
code Function
BOOS = 0005H
SPACE = 20H
fN_CONSOLE_IN = 1
WAIT_fOR_START_KEY:
LO C,fN_CONSOLE_IN
CALL BOOS
START_KEY_PRESSEO:
RET
This subroutine waits for a key to be pressed on the keyboard. When a key is
pressed, the BDOS call returns with the key character in the A register. The code
then tests to see if the key pressed was a space. If it was, control returns to the
USING SIMPLE BDOS FUNCTIONS 27
calling routine; otherwise, the code waits for another key to be pressed.
Sometimes it is useful to be able to read a single character from the keyboard,
but more often than not you will want to read a number of characters into a
buffer. You can do this using Read Console Buffer (function 10). With this, you
tell CP/M where the buffer is in your program, and how big it is, by
The following piece of code shows how to use Read Console Buffer.
BOOS = 0005H
fN_REAO_BUFfER = 10
BUffER_LENGTH = 80
BUffER: DEfB BUffER_LENGTH
OEfS BUffE~LENGTH+l
~~?f__..i~___.____.____.____.__4 .........___.__..___.___.? d
If you were then to type in the text 'John Smith' and press RETURN, the
buffer would now look like this:
Any text typed in up to the point where you pressed RETURN is always
stored from the third byte in the buffer onwards. The number of characters typed
28 CP/M 80 PROGRAMMER'S GUIDE
in is stored in the second byte. If you forget to reserve storage for the buffer,
CP/M will clobber your program by overwriting it! You have been warned!
A particular benefit of 'Read Console Buffer' is its simple editing facilities.
These allow you to backspace a character and delete a character, for instance.
Full details of the editing facilities are given in appendix D under the description
of Read Console Buffer.
In some situations you may want to test if a character has been input at the
keyboard and continue processing if it has not. You would need to do this, for
example, in an arcade-type games program, or a program that tests your reaction
time.
In this type of situation you could use Get Console Status (function 11 ). This
tests if a key has been pressed at the keyboard and returns the value 1 in register
A if it has; otherwise, it returns the value zero. When you know that a key has
been pressed, you can get its value by using either Console Input (function I) or
Read Console Buffer (function 10).
Suppose that you want to write a program that tests your reaction time. The
following flowchart shows what is needed.
USING SIMPLE BOOS FUNCTIONS 29
Put up sign-on
message
Start timer
30 CP/M 80 PROGRAMMER'S GUIDE
The part shown stippled is the most significant from our point of view. The
code for it might look like that shown below.
OR A ;Set flags:
- Z no key pressed
; - NZ key pressed.
JR NZ,STOP_THE_CLDCK
CALL INCREMENT_THE_CLOCK
JR TIMER_LOOP
Note how Get Console Status (function 11) has been used here to break out of
the timer loop when the key has been pressed. The complete reaction tester
program is shown in appendix A.
BDOS function
BDOS
BDOS console
handler
BDOS function
1
'
Function analyser
I
BDOS
BDOS console
handler
Note that you must not mix Direct Console 1/0 and the other input/output
functions described in this chapter; if some functions go through the BDOS and
others bypass it the results can be unpredictable.
To save you needing to mix them, Direct Console 1/0 provides you with most
of the facilities already described. You can use it to input or output a character.
32 CP/M 80 PROGRAMMER'S GUIDE
You can also use it to check the keyboard status, so you could still implement
programs like the reaction timer shown in the previous section.
The following piece of code outputs the character Y to the screen using Direct
Console 1/0.
BOOS = OOOOSH
fN OIRECT_IO = 6
Here, the character to be output is first loaded into the E register, just as in
Console Output (function 2).
When you are using Direct Console 1/0 for input it does not wait for a key to
be pressed: it gives you either the character input, if one is there, or zero. Thus,
you need to check that a character has been transferred (by testing for a non-zero
value) each time that you use it. A side effect of this is the fact that Direct
Console 1/0 cannot be used to input NULL characters (CTRL/@) as these are zero.
The following code shows how Direct Console 1/0 can be used to input the
character CTRL/C (3) without rebooting CP/M. You first load register E with the
value OFFH to show that you want to input a character. On return from the
BDOS call, any character transferred is in register A.
BOOS = OOOSH
fN_OIRECT_IO = 6
CTRL_C = 3
INPUT_MOOE = OFFH
EXIT:
USING SIMPLE BOOS FUNCTIONS 33
Notice how the keyboard status is checked to see if a character has been input.
Another useful feature of Direct Console 1/0 is the fact that when you use it
for input, text is not echoed to the screen. You could, thus, use it for password
detection, as in the example shown below. Here, the subroutine READ_PASS-
WORD_ECHO_STARS allows you to type in a password and store it in a buffer
so that you can process it outside the subroutine. Each time that you type a
character of the password, the subroutine will print a star (*) character so that
no-one can watch you typing the password. On entry to the subroutine you
should put the buffer address in registers HL and the number of bytes it contains
in register B.
BOOS = 0005H
rN OIRECT_IO = 6
INPUT_MODE = orrH
READ_fASSWORO_ECHO_STARS:
Read_password_loop:
CALL READ_A_CHARACTER
LO (HL),A ;Save it in buffer.
Wait_for_key: LO E,INPUT_MOOE
LO C,r~OIRECT_IO
CALL BOOS
OR A ;Key pressed?
JR Z,Wait_for_key ;No!
DISPLAY_A_CHARACTER:
PUSH Hl ;Save registers used.
PUSH BC
If you have the choice of using Direct Console 1/0 or the other input/output
functions, it is better to avoid Direct Console 1/0. You can input and output only
one character at a time with it. Also, the line editing facilities that are available
with Read Console Buffer cannot be used with Direct Console 1/0.
To conclude this section, we must stress one thing yet again: you should not
mix Direct Console 1/0 with the other console input/output functions.
Summary
• The BDOS function calls allow you to access operating system facilities. The
calls are summarised on page 109 and each one is described in detail in
appendix D. To use one of them you load the function code which identifies
the facility into register C, and then call the BOOS at address OOOSH
• In some cases you pass information to the BDOS via registers D and E. The
BOOS may also pass information back to you via one or more of the A, H and
L registers. The flow of information is shown in table 4.1
• Note that you must not mix Direct Console 1/0 and the other console input/
output functions described in this chapter. If some functions go through the
BDOS and others bypass it the results can be unpredictable.
To save you needing to mix them, Direct Console 1/0 provides you with
most of the facilities already described. You can use it to input or output a
character. You can also use it to check the keyboard status. However, you
cannot use it to read or output a string of characters in one call
• Define your own stack, otherwise you might overwrite CP/M's stack
• Remember that the BDOS corrupts 99 per cent of household registers!
USING SIMPLE BDOS FUNCTIONS 35
Discs are one of the most versatile ways of storing computer data . They are
compact and can hold a large amount of information which can be retrieved
quickly and easily . This chapter and the next describe the ways that you can
access discs and the files into which they are divided . The present chapter describes
file operations and the BDOS functions that they use. The next chapter covers
more sophisticated disc operations and their BDOS functions.
Disc file storage is similar to the storage method used in libraries, so we will look
at this first (see figure 5.1}. Each book in a library contains information and it
has an entry in a central index. This index is a type of directory which tells you
where to look for books; it consists of a card for each book. On each card is the
name of the book and its author, together with a number that tells you where
the book is in the library.
You can think of magnetic discs as containingfiles which correspond to the
books in a library. Each file can hold information such as text, programs and nw
data. For each file, there is an entry in a directory which holds, for example, the
filename and the position of the file on the disc.
Let us take this analogy a bit further. If you want to find a book in the library
and read it, you look at the index (directory) to fmd out where the book is. Then
you take the book from its place in the library, you read its contents and, finally,
close it and put it back. With the equivalent disc filing system, when you want to
36
FILE HANDLING 37
read a file, CP/M would look at the disc directory to see where the file is and open
it for use by your program. Your program would then read information from the
file and, finally, close it. The action of opening the file is comparable to looking
in the library's book index.
Going back to the library analogy for the last time, when the librarian wants to
put a new book into the library, he creates an entry for it in the index and then
puts the book into its correct position in the library. With discs, the procedure is
similar, but this time the actions are carried out by your program and CP/M.
When your program asks CP/M to create, or make a new file, CP/M creates a new
entry for the file in the disc's directory, specifying the filename. Your program
can then write records to the file and, finally, close it.
The librarian can put a book in the library, but no-one else will know where it
is until the library's index has been updated. Similarly with disc files, CP/M will
not know where the file is on the disc until the file has been written and closed,
at which point the directory is updated.
38 CP/M 80 PROGRAMMER'S GUIDE
To summarise the points we can learn from this library analogy: a disc is
organised into a number of files and these are indexed by a short directory. When
your program creates, or makes, a file three things happen
• CP/M opens the file (it looks in the directory to find out where the file exists
on the disc)
• Your program reads information from the file
• Finally, your program closes the file
Now let us look at how to create a disc file, and read from it, in more detail.
null equ 0
clrscreen equ 1fH
cr equ DOH
If equ OAH
end_of_string equ '$'
org TPA
LD SP ,my_top_stack
CALL disp1ay_signon_msg
CALL CREATE_OAT~fiLE
INC A test error return for -1
JR Z,there_was_an_error
FILE HANDLING 39
input_loop:
CALL REAO_LINE get line of text from kbd
CALL TEST_fiNISHEO see if line was just a '*'
JR Z,LE_fiNI
CALL WRITE__LINE_TO_fiLE
OR A test if successful, A = 0.
JR NZ,there_was_an_error
LE_fiNI:
CALL CLOSE_OATA_fiLE
JP EXIT
there_was_an_error:
CALL display_error_msg
JP EXIT
;------------------
datafile_fcb:
We will now look at the subroutines that form this program. First, look at the
subroutine CREATE_DATA_FILE; it is shown below.
CREATE_OATA_flLE:
LO DE ,datafile_fcb Ptr to FCB
LO C,FN_CREATE_f ILE
CALL BOOS
RET
The first BDOS function, Create File (function 22), creates an entry in the
directory for the file DATAFILE.DAT.It works in the same way as the simple
BDOS functions described in the previous chapter: you load register C with the
function code and pass information to CP/M using the register pair DE. If CP/M
is able to create an entry in the directory it will return a number between 0 and 3
40 CP/M 80 PROGRAMMER'S GUIDE
in the A register, otherwise it will return -1. The program above shows you how
to check this information.
The information needed by CP/M when it creates a file is the drive name,
filename and file type. This is held in a 36-byte data area called a File Control
Block, or FCB. When you use the Create File function, the register pair DE
contains the address of this PCB. For the present, we shall not look at the contents
of the PCB (they are described in detail later in this chapter).
Unes are read from the keyboard by the subroutine READ_LINE. The sub-
routine WRITE_LINE_TO_FILE shown below then uses the BDOS function
Write Sequential (function 21) to write a line to the file.
WRITE_LINE_TO_!ILE:
LD DE,datafile_fcb
LD C,fN_WRITE_SEQUENTIAL
CALL BOOS
RET
You must use the same PCB here as was used in the Create File operation.
You can pass its address to CP/M in the same way: via the DE register pair. If you
want, you can create your own disc data buffer. However, CP/M allocates a
default data buffer at the address 0080H and we will use this for the present.
Now, all we need do is ensure that the data we want written is placed in the 128
byte block of memory starting at address 0080H. This is done by the subroutine
READ_LINE.
Again, when the write operation is completed, CP/M returns a value in the A
register to tell you whether or not it was successful: a zero value indicates a
successful operation and any other value an unsuccessful one. The code in the
program above shows you how to use this information.
The final operation that you perform when creating this simple file is to close
the file using the BDOS function Close File (function 16). This is shown below in
the subroutine CLOSE_DATA_FILE.
FILE HANDLING 41
CLOSE_DATA_fiLE:
LD DE,datafile_fcb
LD C,FN_CLOSE_fiLE
CALL BOOS
RET
Again, you pass the address of the FCB to CP/M via register pair DE. If the
operation is successful, register A will hold a number from 0 to 3. If it is
unsuccessful, the register will hold the value -1.
If you now want to read data back from the file DATAFILE.DAT and print it on
the screen, you can do this with the program shown below.
org TPA
LD SP,my_top_stack
CALL display_signon_msg
CALL OPEN_DATA_fiLE
INC A test error return for -1
JR Z,there_was_an_error
display_loop:
CALL READ_LINE_fROM_fiLE get line of text from file
JR NZ,LE_FINI test for end of file
LEJINI:
CALL CLOSE_OATAJILE
JP EXIT
;------------------
there_was_a~error:
CALL display_error_msg
JP EXIT
;------------------
datafile_fcb:
;----------------
The first subroutine of this program, OPEN_DATA_FILE, is shown below.
OPEN_OATAJILE:
LO OE,datafile_fcb Ptr to fCB
LO C,fN_OPENJILE
CALL BOOS
RET
REAO_LINE_fROMJILE:
LO OE,datafile_fcb
LO C,fN_REAO_SEQUENTIAL
CALL BOOS
OR A set flags
RET
FILE HANDLING 43
In this subroutine, data is read using the BDOS function Read Sequential
(function 20). The lines of data are read back in sequential order. Again, CP/M
needs the address of an FCB to tell it which file to read, and a data area into
which it can read the data. As before, you pass the FCB address to CP/M via the
register pair DE and CP/M uses the default data buffer at address 0080H to hold
the data read from disc.
CP/M tells you if the read operation has been successful by putting a zero value
in the A register; any other value tells you that it was unsuccessful (for example,
the end of file may have been reached).
As each line of data is read from disc, it is printed on the screen by the
subroutine WRITE_LINE.
Whenever you read or write a file, the last operation you should perform is to
close the ftle. This is done in the subroutine CLOSE_DATA_FILE which is the
same as the routine used in the previous section, when the ftle was written.
There are not many applications where you would write a file once and not need
to change it at some later date. You might want to add further data to the end of
the file, or change some of the data you have already written. For the time being
we shall ignore the latter type of action (it is covered later in this chapter) and
concentrate on the former.
If you want to add data to the end of your file you can do this easily by
The following program writes one new line to the end of your file
DATAFILE.DAT.
null equ 0
clrscreen equ lFH
cr equ ODH
lf equ OAH
end_of_string equ '$'
org TPA
START:
LO SP,my_top_stack
CALL display_signo~msg
CALL OPE~OATA_fiLE
INC A test error return for -1
JR Z,there_was_an_error
input_loop:
CALL READ_LINE get line of text from kbd
CALL TEST_fiNISHEO see if line was just a '*'
JR Z,LE_fiNI
CALL WRITE_LINE_TO_fiLE
OR A test if successful, A = 0
JR NZ,there_was_an_error
LE_fiNI:
CALL CLOSE_OATA_fiLE
JP EXIT
;-------------------
datafile_fcb:
;----------------
fN_SYSTEM_RESET equ 0
EXIT:
LO C,fN_SYSTEM_RESET
CALL BOOS exit to CP/M
This is similar to the program used in the previous section, but this time when
the end of file is detected the subroutine WRITE_LINE_TO_FILE is called. This is
the same as the subroutine of the same name that was used to write the initial
file.
As in the previous operations which created and read from the file, you must
close the file when you have finished with it. This is done, again, by the subroutine
CLOSE_DATA_FILE.
The previous two sections described how to create a simple file and read from it.
This section looks at the contents of FCBs, files and data areas in more detail.
As we discovered in the first two sections of this chapter, your program uses a
File Control Block (FCB) to pass the name of the file (or filename) to CP/M,
and CP/M stores pointers to the information on disc in it. You must set up an
FCB yourself for each file accessed by your program, and pass its address to CP/M
in the register pair DE.
The FCB consists of a 33-byte block of memory for sequential access and a
36-byte block for the random access methods described later in this chapter. To
avoid mistakes, we suggest that you always reserve 36 bytes for every FCB.
The format of an FCB is shown in figure 5.2. When you create or open a file it
is your responsibility to complete the lower 13 bytes of your FCB (fields 1 to
4) and set the remaining fields to zero.
When you create a file, the contents of its FCB are stored in the disc directory.
The FCB is updated as you write records to the file. If, when you close the file,
records have been written to it, information in the FCB is written to the directory.
When you subsequently open the file again, this information is read back from the
directory and used by CP/M.
There is one final, important, point to remember about FCBs. After you have
created or opened a file, you should not alter the contents of its FCB. If you do,
this may confuse the system.
CP/M files can be regarded as sequences of 128-byte chunks which we will call
sectors. These are numbered from sector 0, and a file can contain any number of
sectors up to the full capacity of the drive. In practice, the file is divided into a
number of 16K byte segments called extents, and these extents could be spread
across an entire disc with other files intervening. However, your programs will
46 CP/M 80 PROGRAMMER'S GUIDE
regard a file that consists of several such extents as being one continuous chunk
of data.
If you want to write data as records of less than 128 bytes each, you can do
this in two ways
• By padding out the data in each record to form a complete 128-byte sector
• By blocking a number of records into each sector when they are written and
de-blocking sectors when they are read
FILE HANDLING 47
In both these cases, your program must do the extra work itself: CP/M does
not concern itself with the details of how 128-byte sectors are used.
Types of file
All files look the same to CP/M. However, from our point of view they can be
divided into two groups
• Text files
• Binary files {for example, .COM files)
Text files are regarded as containing ASCII characters. One 128-byte sector
can hold several lines of text and the last sector of a text file has its last 128 byte
sector 'padded' with CTRL/Z {lAH) characters. Programs like WordStar use this
type of file. Your program source files are other examples; in these, each 'line' of
the file is denoted by a carriage return linefeed sequence {ODH followed by OAH).
Binary files include .COM files and data files, and they contain what you can
regard as a string of binary data. Wherever the number {1AH) occurs in these files
it does not represent end of file {CTRL/Z). Instead, whenever the end of file
condition occurs, CP/M returns the appropriate value in register A after the
operation you performed.
Data areas
Now let us look at the other data area used by the BDOS file handling functions:
the disc data buffer. This is used to hold records that you want to write to a file
and those read from it. The start address of this buffer is called the Direct Memory
Access (DMA) Address. This name is a throwback to the earlier days of CP/M and
is not a very good one. However, we will retain its use here.
A default buffer is provided at address 0080H, but you can allocate your own
buffer if you want using the BDOS function Set DMA Address (function 26).
In most cases you would probably want to do this. You might, for example, want
to use a number of files simultaneously in the same program (and each normally
requires its own data area).
Another situation where you might want to use Set DMA Address is when you
want to write several sectors of data to the disc one after the other (see figure 5.3).
Here, it would be best if you organise your 128-byte sectors of data sequentially
in memory. You can then change the data buffer address to the start of each
chunk of memory (using Set DMA Address) before each Write Sequential
operation.
48 CP/M 80 PROGRAMMER'S GUIDE
--
Load data into
large buffer
Write data
/
Set DMA address Large buffer
Write data
LD B,sector_count
LD DE,sector_length
LD HL,buffer buffer is a large contiguous area
of data we want to write to file
write_buffer:
push HL save registers we use
push DE 'BOOS corrupts 99% of HOUSEHOLD
push BC registers'.
call write_line_to_file
jr nz,there_was_a~error
FILE HANDLING 49
;---------
set_dma_ptr:
LD C,FN_SET_DMA_ADDRESS
CALL BOOS
RET
;----------------
Up to the present point, filenames have been included in your program's source
code. If you wanted to use a different filename after the program had been
assembled, you would have had to start from scratch again by changing the
filename and then re-assembling the program.
This situation would not be much help with programs that use different files
at different times. For example, what would be the use of a text editor that
accepts only one filename for a text file? Fortunately, CP/M allows you to pass
filenames from the keyboard to a program when it is loaded by the Console
Command Processor (CCP).
When you load a program called, say, SORT, you can pass the names of two
files to it by typing a command such as
• It loads the text 'B: INFILE.DAT OUTFILE.DAT' {the command line tail)
into the default data buffer from address 0080H
• It then loads the file SORT.COM into memory
OOSOH
: I N F I L E . D A T V'IO U T F I L E . D A T
Character count
The first byte of the buffer contains the number of characters in the command
line tail and the characters themselves follow it. Note that spaces are also trans-
ferred and are shown as ''\/' in the diagram. Characters are translated into upper
case ASCII, where necessary, and the unused part of the data buffer is left
unchanged.
If you want to use the command line tail information you must move it from
the default data buffer before you use the latter for file transfers.
If you use Set DMA Address to redefme the position of the data buffer, the
command line tail will be left at address 0080H and it will not be overwritten.
Information from the command line tail is also stored in a default FCB. The
fust part is stored at address OOSCH, in fields 2 and 3
,..rp:=2-~=~-N-FI_L_E-;-v---.l~""jATJ-A-T....,~=------o
Drive code
Notice that the dots and colons have been removed. The second part of the
command line tail (OUTFILE.DAT) is stored within field 8, at the address 006CH:
006CH
The second drive code (at address 006CH) takes the default value 0. Your
filename OUTFILE is put immediately after this and the file type (DAT) follows.
The rest of field 8 and field 9 are set to zero.
The CCP translates alphabetic characters into upper case so as to be consistent
with CP/M's naming conventions. If the command line tail of SORT is blank,
there will be no information stored from addresses OOSCH and 006CH.
Note that you must move the second filename and file type (OUTFILE.DAT,
in this case) to a separate FCB before you open the file that uses the first part of
the FCB. If you do not, the Open File operation will overwrite this information!
FILE HANDLING 51
Directory operations
There are a number of BOOS functions that affect complete mes and their entries
in the directory. These are
Delete File (function 19) and Rename File (function 23) have the same effect
as the equivalent CP/M console commands that delete and rename a me. Why
would you want to use them inside a program? Well, suppose your program
processes a me called FILE1 and always keeps a back-up of the file (called
FILEl.BAK.) as it existed before processing. What you might do is follow the
procedure shown in figure 5.4.
~
End of processing TEMP
t
F I LE 1 renamed as
new back·up copy
Here, you use FILE1 as input file and create another file (say TEMP) as output
me. If your program run is successful, the program then deletes FILEl.BAK
(using Delete File) and renames FILE1 as FILEl.BAK (using Rename File). It
then renames TEMP as FILEl.
Delete File (function 19) is very similar to Create File. You load the filename
into an FCB pointed to by registers DE, load 19 into register C and then call the
BDOS.
52 CP/M 80 PROGRAMMER'S GUIDE
Rename File (function 23) involves a little more work and the following piece
of code shows it in use. You must set up the old filename in the first 16 bytes
of the FCB and the new filename in the next 16 bytes.
null equ 0
clrscreen equ lfH
cr equ DOH
lf equ OAH
end_of_string equ '$'
org TPA
START:
LD SP,my_top_stack
CALL display_signon~sg
LD OE,rename_fcb
CALL rename_file
INC A check error return for -1
JR NZ,EXIT
there_was_an_error:
CALL display_error_msg
JP EXIT
;------------------
RENAII£JCB:
RENAMEJCB.oldfile_namet
OEfB default_drive currently selected drive
OEfM 'TEMP name of oldfile
OEfM file type
OEfB 0,0,0,0
RENAMEJCB.newname:
OEfB default_drive
OEfM 'riLE! new name for file
DEfM new file type
DEfB 0,0,0,0
DEfB 0,0,0,0
;----------------
FILE HANDLING 53
RENAME_fiLE:
LO C,FN_RENAME_fiLE
CALL BOOS
RET
;-------------
F~SYSTEM_RESET equ 0
EXIT:
LD C,F~SYSTEM_RESET
CALL BOOS exit to CP/M
Search for First Entry and Search for Next Entry are a little more sophisticated.
They allow you to search the directory for the existence of one or more files and
you might use them where your program needs to list the directory or part of it.
When you use Search for First Entry (function 17), CP/M tries to find a
directory entry that matches the name you specify in an associated FCB. It will
then return a value that tells you whether or not the entry has been found.
An extension that makes Search for First Entry much more powerful is its
facility called wildcards, where you can use a ? character in the FCB in place of
any character. In this case, CP/M will take any character in that position as a
match.
Suppose you want to list all of the files in the directory that are text files and
have the file type .TXT. The following piece of code will do this:
zero equ 0
clrscreen equ 1FH
cr equ DOH
If equ OAH
end_of_string equ '$'
org TPA
START:
LD SP,my_top_stack
CALL display_signon_msg
CALL ZERO_NO~ILENAME_PART_OF_!CB
MORE:
CALL PRINT_! ILENAME print it.
LD DE,default_fcb
CALL SEARCH_NEXT_! ILE_ENTRY check if more entries
INC A test for -1 = not found
JR NZ,MORE and try again
ALL_DONE:
JP EXIT
;--------------
file_not_found:
LD DE,fi1e_not_found_msg
CALL PRINT_STRING
JP EXIT
fi1e_not_found_msg:
defb cr,1f
defm ' FILE NOT found'
defb cr,1f
defb end_of_string
;-----------------
FILE HANDLING 55
ZERO_NONLflLENAME_PART_OF_fCB:
RET
;-----------------
fN_SEARCH_flRST equ llH
SEARCH_flRST_flLE_ENTRY:
LD C,fN_SEARCH_fiRST
CALL BOOS
RET
;-------------
f~SEARCH_NEXT equ 12H
SEARCH_NEXT_flLE_ENTRY:
LD C,f~SEARCH_NEXT
CALL BOOS
RET
The BDOS function Search for First Entry finds the first entry that you want
in the directory. If the command line contains ????????.TXT as the filename, the
characters 'TXT' will be set up in the FCB. as the file type and the filename will
contain several '?' (wildcard) characters. CP/M will ignore the entire filename and
just look for a file of the type .TXT. The subroutine PRINLFILENAME then
prints the filename on the screen.
If you are using wildcards and want to look for any more .TXT files, you can
do so using the BDOS function Search for Next Entry (function 18). Its action is
similar to that of Search for First Entry and an example of its use is also shown in
the program above.
Up to now you have written and read files sequentially. Random access is a way
of getting at the sectors of a ftle in random order.
56 CP/M 80 PROGRAMMER'S GUIDE
The rest of this chapter describes the more elementary aspects of using random.
access; to understand it fully you need to know how files are organised on disc.
Thus, it is covered in more detail in chapter 8, after disc structures have been dealt
with.
The easiest way of producing and using a random access file is to write it
sequentially, just as you have done before, then read and update sectors in
random order.
You have already written a file DATAFILE.DAT in sequential order. If you
have, say, 10 sectors in it and you want to read sector 10, there would be no
hardship involved in reading the file sequentially to get to record 10. However,
if there are 1000 records in it and you want to read sector 900, reading 899
records to get to the one you want would be tedious. Even if the file is short, if
you frequently want to access different sectors in random order, resetting the
file and reading it sequentially would give a large overhead.
What you can do, instead, is open the file and use the BDOS function Read
Random (function 33) to get at sectors in random order. This is similar to Read
Sequential but takes an additional parameter - the sector number.
You set up the function in a similar way to the other disc operations: load the
address of the file FCB into the register pair DE, load 33 into register C and then
call the BDOS. However, you must also put the required sector number into a
special part of the FCB (bytes 33 to 35) known as the record pointer.
The record pointer is shown in figure 5.5. When using random access you must
always remember to allocate a full 36 bytes for the FCB to allow for this pointer.
If you allocate only 33 bytes, you will find that the next 3 bytes of your program
are overwritten.
Bytes 33 and 34 of the FCB are a 16-bit unsigned integer and are set to the
(two-byte) sector number. Byte 35 is set to zero. For example, if you want to
read sector 900 (384H), you would set byte 33 to 84H, byte 34 to 3H and byte
35 to 0.
If your read operation is successful, the data is returned in the current disc data
buffer and a zero value will be returned to your program in the A register. Any
other value in the A register means that CP/M has been unable to read the sector
FILE HANDLING 57
for one reason or another. These error returns will be described in more detail in
chapter 8, after some other concepts have first been introduced.
The program listing below shows how Read Random is used. It opens a flle
DATAFILE.DAT which contains lines of text, prompts you for a line number and
then displays the line. You have already produced this file; it contains a number
of sectors, each of which contains up to one line of text.
null equ 0
clrscreen equ lfH
CTRL_C equ 3
cr equ DOH
If equ OAH
end_of_string equ '$'
org TPA
START:
LO SP,my_top_stack
CALL display_signon_msg
CALL OPEN_OATAJILE
INC A test error return for -1
JR Z,file_error
display_loop:
CALL PROMPT_fOR_LINE_NUMBER
JR C,LE_fiNI
LE_fiNI:
CALL CLOSE_OATA_fiLE
JP EXIT
;------------------
58 CP/M 80 PROGRAMMER'S GUIDE
datafile_fcb:
REAO_RANOOM_LINE_!ROM_!ILE:
LD DE,datafile_fcb
LD C,f~READ_RANDOM
CALL BOOS
OR A set flags
RET
Suppose you want to build an employee record system for a business. In this
system, each employee might have an employee number which corresponds to a
sector on the file, and that sector holds the employee's record.
You might want to allocate a block of employee numbers to each department
so that people can be identified more readily. In addition, each block of numbers
might have 'holes' to allow for the addition of records later for new employees.
For example
FILE HANDLING 59
~ ~ M~ -~l
J
____J
MANAGEMENT
PERSONNEL
SALES - - - - - - - - - . . . . J
ACCOUNTS---------~
ADMIN - - - - - - - - - - - - _ _ _ J
WORKSHOPS-----------------
In figure 5.6, the shaded portions show records that have been written to the
file. The unshaded portions are 'holes', which you can think of as records that
have not yet been written. Now, how do we handle the 'holes'?
The easiest way would be to do it as we have previously: create a file sequen-
tially and fill it with 200 'dummy' sectors containing zeros. We can then use Write
Random to replace some of these sectors with the real employee data sectors.
However, a much easier way of achieving this structure is to use the BDOS
function Write Random with Zero Fill (function 40). This is the same as Write
Random, but it fills any preceding sectors that are not yet used with zeros. For
example, suppose the first few records of your file are as shown in figure 5. 7
(only the MANAGEMENT records have been written)
Record
No. 0 5 10 15 20
unused
If you now want to add the first record for PERSONNEL, you can do this
using Write Random With Zero Fill. This operation will write sector 10 but will
also fill sectors 6 to 9 with zeros. The remaining 5 records for PERSONNEL can
also be written using Write Random With Zero Fill, but the effect will be the same
as for Write Random because there are no 'holes'.
Now, suppose we want to write the first record in SALES (record 20). Write
Random with Zero Fill will write this record and fill the intervening records ( 16
to 19) with zeros.
Write Random with Zero Fill allows us to create a normal database file with
few operations, and that file is the same as though it were written sequentially.
The previous discussion has been kept deliberately short so that you can under-
stand the principles involved in using random access files. In practice, if you write
files containing 'holes', you need to know more about how they are laid out on
the disc, otherwise you will have problems. This aspect is covered in chapter 8,
after the main disc structures have been described.
CP/M may return to your program one of the error codes shown in table 5.1 as
the result of a ftle operation. Error codes are returned in the A register.
Error
code Meaning
Some of these codes will be more meaningful when you have read chapter 8.
Summary
• Each ftle has an entry in the disc's directory and this holds details such as the
filename and position of the ftle on disc
• When you create a file you first use BOOS function Create File (function 22),
write to it, then fmally close it with BDOS function Close File (function 16)
• When CP/M creates or opens a ftle it uses information in a File Control Block
(FCB) which is set up by you
• Data is read from disc and written to it as 128-byte blocks and these are put
into a disc data buffer
FILE HANDLING 61
The first section of this chapter introduces some of the terms used in disc handling
and describes some of the basic disc handling functions. The second section
describes how you can protect your discs and files from accidental overwriting.
Finally, the third section tells you how to get hold of disc characteristics.
Introduction
Two terms are used frequently in connection with CP/M disc handling: the
cu"ently selected drive and logged-in drives.
The cu"ently selected drive is used as the default drive for all file operations.
It is the drive indicated by the prompt on your screen; for example
A>
When you type B: <RETURN> the currently selected drive changes to drive B.
You can determine the currently selected drive from your programs by using
the BOOS function Return Cu"ent Drive (function 25). This returns the drive
number in register A. You can also change the currently selected drive using the
BOOS function Select Disc (function 14).
Logged-in drives are the drives that CP/M knows about. When you cold-start or
warm-start your system, the only logged-in drive becomes drive A, the system
drive. Whenever you access any other drive, that drive also becomes a logged-in
drive. For example, if you type
62
DISC OPERATIONS 63
PIP C:DATAFILE.DAT=RDR:
drive C also becomes a logged-in drive, even though the currently selected drive is
drive A.
CP/M keeps in memory a set of tables that describe drives and their contents
(they are covered in more detail later in this chapter). It knows about only one
drive at any moment (the currently selected drive) but it can access the tables of
all logged-in drives quite easily, if necessary.
You can find out which are the logged-in drives from your program by using
the BDOS function Return Logged-In Drives (function 24). This returns a 16-bit
value in the register pair HL, where each bit position corresponds to a drive using
the scheme
).....----H-1_7~<-H_L_A/----1(
bit 7 bit 0
drives
Discs protected by Write Protect Disc are protected only against programs
using CP/M BDOS functions. Programs that use BIOS or firmware routines can
bypass this method of protection.
You can find out which discs have been protected using Write Protect Disc by
calling the BOOS function Get Read-Only Indicators (function 29). This returns a
value in registers HL where each bit indicates the status of a specific drive.
drives
The first part of this section describes the disc data structures in memory in
general terms with a brief description of each one. The next 2 parts look at two of
the most useful data structures for the applications programmer - the allocation
maps and the disc parameter blocks. Finally, the sizing of allocation blocks is
discussed.
This section describes how you can get at some of the information CP/M uses for
its disc handling. The information includes disc sizes, details about the positioning
of files on the disc and the number of directory entries on the disc. You would
DISC OPERATIONS 65
not want to use it in all of your programs, but if you do need it (for example, to
produce disc statistics), the information is not difficult to get at. You should be
careful not to change it though, as you will confuse CP/M if you do not do this
correctly.
Figure 6.1 shows the data structures that CP/M uses for its disc handling. These
are all held in memory.
AT
[]] [J Disc Parameter Blocks
(DPBs)
LY
Check Vectors
Allocation Maps
There is one Disc Parameter Header (DPH) for each logical drive, that is, one
each for drives A, B, C, D, E, F, or however many drives your system has.
However, the BDOS only knows about one drive at any moment: the currently
selected drive. It maintains a pointer to the DPH of this drive so that it can get at
the drive details quickly. When you perform a disc operation with an embedded
drive reference to another drive, the BOOS
We will now look at the data structures that the pointers address.
There are sector translate tables for each type of disc (5.25 inch, 8 inch, etc.).
They are used to translate logical sectors into physical sectors on the disc so as to
improve the overall response of CP/M.
The scratchpad area is an area of memory reserved by the BDOS for its own
use.
The directory buffer is an area used by the BDOS for directory operations.
There is only one of these for the entire system.
The check vectors are areas of memory that are used only with exchangeable
discs. They contain information used as a check against changed discs. CP/M keeps
a type of 'checksum' in this area of all the directory entries of the disc. It can tell
when a disc has been changed by scanning the directory and comparing the result
with this 'checksum'. The length of the check vector depends on the number of
directory entries. There is one check vector for each exchangeable drive and the
pointer in the DPH directs you to this.
DISC OPERATIONS 67
The smallest unit of file allocation on a disc is called an allocation block and this
can be 1024, 2048, 4096, 8192 or 16384 bytes long, depending on the disc type
and a number of factors described later in this section.
Each disc is divided into a number of allocation blocks with the first block
starting immediately above the area on disc reserved for CP/M. This is called
Block 0.
CP/M keeps a record of which allocation blocks on a disc are in use and which
ones are not, and this is called an allocation map. It is basically a memory
representation of what the disc looks like. There is an allocation map for every
disc and it consists of a bit map, each bit of which corresponds to an allocation
block.
When you log into a disc, CP/M builds up an allocation map for it. The directory
consists of a number of pre-allocated blocks and these have their appropriate bits
set (the first few allocation blocks in the map).
CP/M then scans the directory and sets bits in the allocation map for whichever
allocation blocks are used by files.
Suppose that we start off with an empty disc and the allocation map looks like
the one just shown. Now suppose that your program tries to create a file on the
disc. CP/M will search the allocation map for the first free allocation block and
mark this as being used for your file. Further blocks are assigned to the file as
required and they may or may not be contiguous, depending on whether or not
other files are on the disc. Details of the allocation blocks used by a file are kept
in the file's directory entry, and when you use Open File (function 15) they are
copied into bytes 16 to 31 of the FCB. For discs with allocation blocks of 1024
bytes, this FCB area consists of 16 one-byte integers. For larger allocation blocks,
it consists of eight 16-bit integers.
68 CP/M 80 PROGRAMMER'S GUIDE
Thus, assuming that we are using 1024 byte allocation blocks, if a file uses
allocation blocks 20, 21, 25 and 26, these FCB bytes will contain
Byte of
FCB Contents
16 20
17 21
18 25
19 26
20 0
21 0
Your programs can read this information from the FCB at any time when the file
is open, but they must not change bytes 16 to 31.
If you want to look at the entire allocation map for a disc, you can do so by
using the BDOS function Get Address of Allocation Map (function 27). This
returns the base address of the current disc's allocation map.
----m
Bit corresponding to
allocation block 0
1-----
2-----
1
1
--Base address
To be safe, you should first use Reset Disc System, then Select Disc, and finally
Get Address of Allocation Map. This is the only way you can ensure that the
allocation map is accurate.
One of the example routines in appendix A uses Get Address of Allocation
Map to calculate the amount of free space on the current disc. This is similar to
the STAT utility provided with CP/M, but unlike STAT you can use it from your
own programs.
You may also need to know the following information about the disc
structures.
To get this information you have to access the current disc's DPB and we shall
look at this area in the next section.
Disc Parameter Blocks (DPBs) are tables of memory that describe the characteristics
of discs on your system, and there is one DPB for each disc type (for example,
DISC OPERATIONS 69
5.25 inch, 8 inch, single sided, double density, etc.). Hence, if you have several
discs of the same type on a system, several DPHs could point to the same DPB.
The contents of a DPB are shown in figure 6.3. You can get at the start address
of the DPB for the currently selected drive using the BDOS function Get DPB
Address.
Some of the contents are easily understood: the number of sectors on each
track of the disc, the maximum number of directory entries that can be stored on
the drive and the size of the check vector in bytes.
The number of reserved tracks normally refers to the number of tracks used to
store the operating system on the disc. However, it may also be used to split a
large disc (for example, a Winchester) into several logical drives. For example, if
you wanted to divide a 1 Megabyte disc into two logical 500 Kilobyte drives, the
arrangement is
Writing area
1.Mb 1Mb
Writing
area Reserved area
} Reserved
" " ' - - - - - - ' area
Logical Disc 1 Logical Disc 2
(shaded) (shaded)
70 CP/M 80 PROGRAMMER'S GUIDE
You can determine the allocation block size in bytes from the field B shown in
figure 6.3. The relationship is
and you can determine the size of the disc in bytes using this figure and the fifth
field of the DPB: disc size in allocation blocks - 1. The relationship is
The pre-allocated block map is a 16-bit field that describes how many allocation
blocks are pre-allocated for directory entries.
~==:===:*==),===:::=:::::::=;)
High-order bit low byte high byte
Bits of this field are set from the high-order bit of the low byte. Thus, if four
directory blocks are filled, the four high-order bits of the low byte are set, giving
it the value FOH, and the high byte is set to 0.
You might also want to calculate the size of the allocation map in bytes. You
can get this from the DPB as follows
An allocation block is the smallest unit of me allocation and its size depends on a
number of factors.
If the allocation block size were 128 bytes, the size of each sector written by
your program, then CP/M would have to remember a lot of information about
each me. The disc direction would be large and so would the allocation map and
check vector in memory. In practice, the smallest possible allocation block is 1K.
On the other hand, if the allocation block size is large (say 16Kb) and you
write a me containing only 100 bytes of data, then over 15Kb of disc space would
be wasted (it could not be used by other mes). If you have 100 such mes of this
type, the amount of disc space that these occupy for different sizes of allocation
block is as follows
DISC OPERATIONS 71
16K 1.6Mb
8K 800Kb
4K 400Kb
2K 200Kb
lK 100Kb
If we use 1K allocation blocks and 8-bit pointers, this limits the maximum disc
size to 255K. If we use 2K allocation blocks and 16-bit pointers, the discs
supported can be much bigger. In practice, 8-bit pointers are used only with lK
allocation blocks and for discs up to 255K in size. Blocks of larger size use 16-bit
pointers.
One other factor to consider is the way the BIOS handles sectors of data. Some
BlOSs optimise disc operations to read a whole allocation block into memory in
one step. Sectors are then blocked and deblocked from this.
Summary
• The cu"ently selected drive is used as the default drive for all file operations.
It is the drive indicated by the prompt on your screen
• CP/M remembers the details of all drives that it accesses and these are called
logged-in drives
• You can find out which is the currently selected drive from your program with
Return Cu"ent Drive (function 25)
• You can change the currently selected drive using Select Disc (function 14)
• You can find out which discs are logged in by using Return Logged-in Drives
(function 24)
• You can reset the currently selected drive to A and make this the only logged-in
drive by using Reset Disc System (function 13)
72 CP/M 80 PROGRAMMER'S GUIDE
• Write Protect Disc {function 28) protects the currently selected drive against
programs using BOOS functions
• Get Read-Only Indicators {function 29) tells you the status of a specific drive
• Set File Attributes {function 30) lets you write-protect a me
• There is one Disc Parameter Header (DPH) for every logical drive but the
BDOS only knows about one at any time: the one for the currently selected
drive. The DPH is predominantly a table with addresses of other data structures
• The smallest unit of me allocation on a disc is the allocation block. Each
allocation block has a bit position in an allocation map which tells CP/M where
blocks are free
• You can access the allocation map base address using Get Address of Allocation
Map {function 27)
• Disc Parameter Blocks (DPBs) describe the characteristics of discs on the
system
• The size of an allocation block is a compromise between disc space wasted and
memory used. For efficient use of disc space, lK is the ideal allocation block
size. For efficient use of memory, and a realistic size of directory, 16K is the
ideal size
7 Debugging your Programs
We have designed this chapter so that the first section is independent of the
computer language that you use. It covers the general principles of debugging and
describes a philosophy that applies as much to the home programmer using BASIC
as it does to the masochist using machine code.
The second section is meant for those assembler programmers who intend to
use the debugging tool ZSID. It explains how to use ZSID for the most common
actions needed when you are debugging a program. You may, however, fmd it
useful even if you are using the 8080 debugging tool DDT, and we have included
appropriate comments on DDT where necessary.
General principles
There are three types of bug that can get into your programs:
• Those that are introduced as a result of design errors. For example, you may be
using the wrong algorithm
• Those that are introduced by mistyping. This is most likely to occur when you
first type in your program source, because of the sheer quantity of code
involved. You might, for example, type GOTO instead of GOSUB in parts of a
BASIC program
• Those caused by an unexpected event, such as the corruption of a program on
disc
The chances of you getting the last type of bug in your program are slim, but
if you cannot fmd any logical reason for your program failing, do not be afraid to
look outside it and analyse your own behaviour as well as that of your equipment.
If the problem occurs a number of times, look for a pattern.
73
74 CP/M 80 PROGRAMMER'S GUIDE
The old adage 'Prevention is better than cure' could almost have been written with
debugging in mind. Careful thought in the design and coding processes will not
eliminate all of the bugs, but it will remove most of them.
The most significant step in removing bugs is to design your program correctly
in the first place. Do not forget that this also involves the design of test facilities
and test data.
Assume that everything will go wrong. Design contingencies into the program;
test for the expected, the unexpected and the impossible. If you do not take
action for a particular error, you should at least report it.
Design errors can be trapped by talking through your design with other people
and then getting them to check it over on paper themselves. The first method
clarifies your own understanding of how the program works and helps you to spot
errors. The second gives you a check on your design by someone with a different
mind (they probably will not make the same assumptions as you).
Most people cannot concentrate for more than 20 to 40 minutes at a stretch
and they will benefit from a 5 minute break after this time. Bear this in mind
when you are slogging at your design, coding, testing, whatever.
In particular, when you type your program source into the computer, do it in
sensible 'chunks' with frequent breaks for coffee and a chat. You are then less
likely to make mistyping errors. When you have typed it all in, run it through the
assembler/compiler and let that spot the obvious mistakes for you. Then check it
by eye, comparing it with the hand-written version. This may be boring but it will
save you a lot of wasted time over the years; you may even see some obvious
mistakes in your coding.
Bugs that make the program crash are the most obvious ones. The ones that do
not crash it but merely change its effect are the worst type: they are the hardest
to track down because their effect may not be noticed until much later in the
program. To trap this type you need to use a lot of logical thought and be
methodical.
Use lots of diagnostic statements. Add diagnostic message printouts to each
module to tell you that it has been entered and print out any important variables
that it uses. You will be surprised at the number of modules, subroutines, proce-
dures and functions that do not actually perform as you thought they did, or are
not invoked when you thought they would be.
Try to isolate any problems into as small an area of code as possible and single-
step through critical areas. Reduce the problem to a particular module, then look
carefully at it. Before diving into it make sure that it has its correct inputs. Do not
forget that this module may rely on data provided by another module at a
considerable distance away.
DEBUGGING YOUR PROGRAMS 75
Look at the state of the stack. You may fmd it useful to fll.l the stack area of
memory with a particular pattern before it is used so that you can check out
unexpected use of the stack. Check that the stack pointer is never above the top
of the stack (a POP without the corresponding PUSH). Check also that the stack
does not overflow into the area below the stack bottom. This can be caused by a
PUSH without the corresponding POP, a CALL without a corresponding RET, or
overuse of the stack.
When you find a bug it is better to flx it before checking for other bugs,
otherwise, you may later be checking another effect caused by the same bug!
However, strictly speaking this may mean re-running al.l tests from scratch again.
In practice, you need to use a little common-sense: for example, run only those
tests for the part of the program that has been changed by fixing the bug.
When you correct a bug, make sure you do not introduce a new variable with
the same name as one already in use. If it is not easy to fix the bug, do not be
afraid to scrap a piece of code and start again.
Update your program listing with a pen as you make each change; it is easy to
get out of step and think you have introduced a change, or forget that you have
done something.
Give your program a version number, and when you re-assemble the program,
change the version number. Also, try to keep the comments up to date. There is
nothing worse than having half a dozen copies of a program which claim to be the
same thing but are al.l slightly different. If you are interrupted and have to come
back to your debugging later, this will give you a big headache!
However, maintain old versions of the program so that if something goes wrong,
or an old fault recurs, you can always wind back to a previous state and start again.
It is better if someone other than the designer tests a program: the designer is
more likely to concentrate on the areas that proved difflcult in the design process
and skimp on the rest.
You should design your test data so as to take the program through every
branch; you should also predict the results. In particular, check boundary
conditions and the parts that are rarely carried out: these usual.ly occur at
76 CP/M 80 PROGRAMMER'S GUIDE
conditionals (IF statements, or the equivalent in the language you are using).
Compare the test results with the predicted results. If you cannot easily predict
results, can you run your program in parallel with an existing system and use its
inputs/ outputs?
Structure your testing according to the circumstances. You need to test
individual modules as well as test the whole program. There are two approaches
• Bottom-up testing: this starts by testing the primitive modules, writing a test
frame to exercise them to their limits one by one, then building up to the
entire program
• Top-down testing: this checks the logic of the main program first, with dummy
modules to do the actual work
Both these methods have their merits and work best in different circumstances.
Top-down testing allows you to check the main logic and have something at an
early stage which behaves similar to the finished product. Bottom-up testing may
be more helpful when debugging I/0 drivers or where you are unsure of a
particular technique. There is often more work involved in writing bottom-up test
drivers than in writing top-down dummy routines.
There are three main debugging facilities available under CP/M: SID, DDT and
ZSID.
All of them have features in common, but ZSID offers the best facilities for the
Z80 programmer. It assembles and disassembles code using Zilog mnemonics,
whereas SID and DDT handle 8080 code with Intel mnemonics. ZSID can also
handle files on any drive, whereas if you are inspecting files using DDT, the files
must be on the default drive.
For the purpose of this chapter, we will concentrate on ZSID. It is not our
intention to describe its facilities in detail. Rather, we will look at the main
functions that you would want to perform using a debugging tool and explain
how ZSID handles them. They are as follows
The following sections describe each of these functions in turn. In each section,
all items typed in by you are set in bold.
DEBUGGING YOUR PROGRAMS 77
ZSID
ZSID filename
where filename is of the type PROG.COM. Thus, you could load the sample
program of chapter 2 by typing
ZSID PROG.COM
You can run a piece of code using the G command. For example, the following
command runs the code from address 1OOH
GOlOO
You can also put into the code a single breakpoint (a place where the program will
halt) using the G command. For example, the following will insert a breakpoint at
address llOH and run the program up to that point
G01000110
Once the program has stopped, you can inspect the registers and data areas. If you
want, you can run it from the breakpoint by typing
If you want to put more than one breakpoint into the program, the G command
is not very efficient. Instead, you can use the S command, which 'patches'
memory. You need to put an RST 38H instruction (FFH) into each breakpoint
address and you can do this at addresses OllOH, 0120H and 0130H as follows
78 CP/M 80 PROGRAMMER'S GUIDE
SOllO
0110 CO FF
0111 DO.
S0120
0120 CC FF
012111.
S0130
0130 21 FF
013111.
You can run the program between these points using the G0100 command as
described above.
Most of this information is fairly obvious; however, some is not and needs a
little further explanation. On the top line, the first item shows the value of the A
register. The second item (B=0009) shows the contents of the Band C registers
(B is 00 and Cis 09). Similarly, the third and fourth items show the contents of
the DE and HL register pairs. The items on the second line show the values of the
alternate register set.
Occasionally, you will want to display a table or data area, and you can do this
using the D command. For example, the following will display a screenful of
memory starting at address 0080H, the default data buffer
00080
DEBUGGING YOUR PROGRAMS 79
0080: C3 03 D9 41 01 C3 00 A9 AF 3D C9 40 BF 50 00 EO
... ..... .
A = I! ' p ' .
0090: 50 50 50 50 41 41 41 41 42 42 42 42 43 43 43 43
p p p p A A A A B B B B c c c c
OOAO: C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3
.. . ....... ...
' ' '
0080: C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3
................
OOCO: BF FO BF 70 BF 71 BF 70 BF 70 BF 70 00 20 20 20
...p.q.p.p.p.
'Patching' data
The S command allows you to patch a block of data into memory. We have used
it before to insert one byte of data (FFH) which forms a breakpoint. If, instead of
typing the full stop after FFH, we continued, we could have typed data into
subsequent locations. For example, the following instructions will zeroise the first
flve bytes of the default data buffer
S0080
0080 01 00
0081 01 00
0082 11 00
0083 21 00
0084 66 00
0085 16.
With SID and ZSID only, you can type in ASCII characters using the S command
if you precede them with a" character. For example
S0080
0080 00 00
0081 00 ''TillS IS TEXT
008060.
Note that the next prompted address is set to beyond the end of the character
string.
80 CP/M 80 PROGRAMMER'S GUIDE
'Patching' a program
The A command of ZSID allows you to 'patch' a piece of code into memory using
Zilog mnemonics. It is really only useful when patching a program. Entering an
entire program is best done via an assembler.
You can assemble a piece of code from address lOOH by typing the following,
then a carriage return
AOlOO
ZSID will then print out the address 0100 at the beginning of the line and you can
type in a line of code, followed by a carriage return. ZSID will then print the next
address, and so on until you finish by just typing a full stop followed by a carriage
return.
The following instructions put the program example used in chapter 2 into
memory. Try them out for yourself.
AOlOO
0100 LD SP,lOOO
0103 LD DE,200
0106 LD C,9
0108 CALL 5
OlOB JP 0
OlOE.
To run the code, you need to put a ·string of text into memory at 200H. You can
do this as follows
S0200
0200 C3 "HeUo world!$
020E IE.
GOlOO
LOlOO
0100 LD SP,1000
0103 LD DE,200
0106 LD C,9
0108 CALL 5
010B JP 0
OlOE ...
The L command has several forms. The one used above is the most common and
it will print a screenful of disassembled code. If you want to print the next screenful
you merely need to type
Summary
• There are three types of bug that can get into your programs
• Careful thought during design and coding can eliminate most bugs
• It is better if someone other than the designer tests a program
• Structure your testing
8 Random Access Files
Chapter 5 explained the basic principles of using random access files. This chapter
explains more of the theory so that you can avoid the pitfalls that exist with
random access under CP/M.
The first section describes how files are written to disc using sequential access.
This is an easy way to start and is a useful stepping stone to the next section,
where we explain how random access files are written.
In the simplest case, an allocation block is 1024 bytes in size and is divided
into eight 128-byte sectors. For the present, we will consider allocation blocks of
this size.
When your program writes a file, CP/M allocates disc space to it in terms of
allocation blocks. Your program may write only 10 bytes of data before closing
the file, but CP/M will still give it 1 allocation block. When 16 allocation blocks
are allocated to a file, they are grouped together and called an extent. Each extent
is controlled by a directory entry.
All file operations are controlled by the FCB and we are interested in 3 areas in
particular
Suppose you want to write a file sequentially and the disc has the following
layout before you start (shaded areas show allocation blocks that are already in
use by other programs)
82
RANDOM ACCESS FILES 83
If you now create a me and write the first sector, CP/M will look for the first free
allocation block and put the allocation block number (1 0) in the first FCB pointer
as shown below. The current extent number (byte 12 of the FCB) and the sector
count (byte 15 of the FCB) are both initially set to zero.
FCB Allocation
Block
Byte
When your program writes sectors of data to the me, sector 1 goes into the
first 128 bytes of allocation block 10 and the sector count of the FCB is set to 1.
Sector 2 goes into the next 128 bytes of the allocation block and the sector count
is set to 2. This continues up to and including sector 8, at which point an entire
allocation block will have been filled.
When your program now writes sector 9 to disc, CP/M looks for the next free
allocation block on the disc, finds that it is allocation block 12, and puts 12 into
the second allocation pointer of the FCB as follows
FCB Allocation
Block
Byte
16
17
84 CP/M 80 PROGRAMMER'S GUIDE
When CP/M selects the next free allocation block for the me, it puts the
allocation block number into the first pointer of the FCB and the process starts
again.
This process is called creating a new extent. When you read a large me, a similar
process occurs and this is called opening a new extent.
If your disc system uses 2048 byte allocation blocks the process is similar, but
remember that the FCB now contains eight 16-bit pointers. Since each allocation
block now contains 16 sectors, a new allocation block is opened after 16 sectors
have been written. Also, a new extent is opened after 8 allocation blocks have
been written.
Systems with allocation blocks of 4096 bytes or more have a similar but more
complex mode of operation and are not described here.
Suppose you want to write the records of a me in random order and they are each
1 sector in size. Again, we will assume an allocation block size of 1024 bytes.
If you want to write the first record, the first allocation pointer in the FCB will
be given a value. Suppose it points to allocation block 40; then the data of record
1 will occupy the ftrst 128 bytes of allocation block 40. If you now write record
5, the data will go into the fifth 128 byte part of allocation block 40.
RANDOM ACCESS FILES 85
If you want to write record 240, assuming that the file were contiguous and
your system uses 1024 byte allocation blocks, this would map onto the 30th
allocation block of the file. We arrive at this result using the following relationship
The allocation block number is the whole number (integer) part of this expression;
the remainder is the sector number in the allocation block. Thus, in the case of
record 240, we get the result 240/8, or 30.
What happens is this
• The first directory entry is written to disc with allocation pointers 2 to 15 still
set to zero
• The current extent number is updated to 1
• A new set of allocation pointers is created in the FCB and they are preset to
zero
CP/M then searches for the next free allocation block (41) and gives this to
your file, setting the 14th allocation pointer of the FCB to 41 (the next free
allocation block).
Record 240 is written to the first sector of allocation block 41 and the sector
count is updated.
Now think about this: the file is at least 240 records in length, but so far it
occupies only 2 allocation blocks. If you try to read (say) record 130 or record
500, CP/M would in each case return in register A the error code 1, which means
'Reading unwritten data' (see table 8.1). Record 130 would occupy a place in the
first allocation block of extent 1, if it had been written. Record 500 is beyond the
end of the file and thus would have no allocation block as yet.
CP/M will attempt to warn you if you try to read unwritten data in the
following circumstances.
• When the sector you specify is beyond the end of the file
• When it is in an unwritten allocation block
• When it is in an unwritten extent
Error
code Meaning
Summary
• A disc is split into 2 parts: the area reserved for CP/M and a number of equal-
sized allocation blocks used for the directory and your files
• Allocation blocks can be 1K, 2K, 4K, 8K or 16K in size
• When a file is written, CP/M allocates space to it in terms of allocation blocks.
Even if the file contains only 10 bytes of data, it will still be one allocation
block in size
• File operations are controlled by the FCB and 3 areas are of interest
• Before a new extent is created or opened, the allocation pointers in the FCB
are first written to a directory entry. An additional directory entry is created
for each new extent
• The sector count is incremented when each sector is written, and resets to zero
when the first sector of a new extent is written
• The current extent number is incremented when a new extent is opened
• CP/M will attempt to warn you if you try to read unwritten data in the
following circumstances
When the sector you specify is beyond the end of the file
When it is in an unwritten allocation block
When it is in an unwritten extent
9 Using BDOS Functions from High Level
Languages
Why should you want to access BOOS functions from a high level language? Well,
the majority of high level languages are designed without a specific operating
system in mind, and some of the operations you might want to carry out will only
be possible via the operating system itself.
For example, you might want to scan the directory for the ftrst occurrence of a
filename, list the directory itself, or find out the size of a file. Most high level
languages will not allow you to do these things directly, but you can do them via
BDOS functions.
High level languages may not specifically give you access to BOOS functions or
other facilities of CP/M. However, most of them allow you to access machine code
and you can do most of the things you want via this medium.
The interface between the high level language and machine code varies
depending upon the language and the specific implementation. However, it is
usually well-defmed. In all cases, you need to know four things
Looking at the problem from the machine code end, your routine needs to do
the following
87
88 CP/M 80 PROGRAMMER'S GUIDE
In the rest of this chapter we will look at the interface with machine code of
two common languages: BASIC and Pascal.
BASICs in general do not have good facilities for accessing the operating system or
getting at machine code. They usually allow you to
The way in which you do the last of these operations depends on the type of
BASIC. For example, Microsoft BASIC allows you to call a routine at the address
AOOOH, say, in the following way
CALL&AOOOH
If you want to put a machine code routine at this address, you can reserve
space for it when you load a Microsoft BASIC program by typing something
like the following
BASIC MYPROG/M:AOOO
This loads a BASIC program from the file MYPROG and reserves an area from
address AOOOH to the operating system for your use. You can then get your
BASIC program to POKE the machine code into this area and CALL it.
With the type of BASIC you use, it may be that you can call only one machine
code routine, so you must use this routine as the starting point for a number of
operations, passing information to it from BASIC to tell it which operation you
want performed.
If you want to pass information to a machine code subroutine, you may have
to POKE specific memory locations. Similarly, you might have to PEEK at
memory to get information back from it. Alternatively, you may be able to do
both via a user function.
Suppose you want to use the BDOS function Search for First Entry (function
17). You may have to POKE the contents of the FCB into memory, then call a
user function to get at the machine code routine. The machine code routine
USING BDOS FUNCTIONS FROM HIGH LEVEL LANGUAGES 89
knows where the FCB is and calls the BDOS to do a Search for First Entry,
passing the result back to BASIC.
The method of accessing machine code from Pascal is similar to that of other
'professional' computer languages, for example, FORTRAN, C and BCPL. You
define one or more subroutines, procedures or functions that are EXTERNAL to
the main program and then link these to the main program via a linker.
One way of generating the final program is to compile the Pascal source,
assemble the machine code as a separate operation and then link the two together.
Pascal passes parameters in a well-defined way. For example, with ProPascal,
the parameters to a procedure are passed on the stack. They are pushed on to the
stack with the first parameter first, the next parameter second and so on. If, for
example, we use the following procedure call
and 'fred' is EXTERNAL, par! is first pushed onto the stack, then par2 is pushed
on top of it, then par3. Next, a call is made to the machine code routine and, as a
result, the return address is left on top of the stack.
In the machine code program, you must POP the return address from the stack
and save it somewhere. You can then POP the parameters in turn, starting from
par3.
The size of the parameters is defined for the particular language; for example,
an integer may be 4 bytes long, so you have to POP 2 words off the stack. You
should check the details of the appropriate language to find out how different
values are passed.
As well as passing your machine code program the value of a variable, Pascal
may pass it the address of the variable. Thus, it can act upon the actual variable
and, when you return to Pascal, the value will have been updated.
The program that follows shows how to access a machine code function
(fsize) from Pro Pascal. The Pascal program passes an FCB to fsize which then
uses the BDOS function Compute File Size to work out the size of the named
file. This value is passed back to the program and, finally, printed. A listing of the
function FSIZE can be found in appendix A.
PROGRAM size;
(* This program computes the size of a file *)
CONST
size_of_a_record = 128;
TYPE
byte = 0 •• 255;
90 CP/M 80 PROGRAMMER'S GUIDE
feb = record
drive_code byte;
fname array[l •• B] of char;
ftype array[l •• 3] of char;
buffer array[0 •• 26] of byte;
end;
VAR
index integer;
file_ctrl_block feb;
filespec string;
file_size INTEGER;
(* This function is written in m/c code and returns either the size
of the specified file in records if it exists or -1. It uses CP/M BOOS
function fN_COMPUTE_fiLE_SIZE. *)
BEGIN
WRITELN('Compute file size program vl.Ob');
WRITELN;
WRITE('Please enter name of file?');
READLN(filespec);
with file_ctrl_block do
begin
drive_code := 0;
for index := 1 to B do
fname[index] ·- filespec[index];
for index := 1 to 3 do
ftype[index] ·- filespec[index+B];
end;
Summary
• A specific high level language may not let you access operating system functions
but it will usually let you write machine code routines. You can then access
the operating system functions from these
• When you write such a routine you need to know the following
This appendix contains a number of programs that illustrate the use of BOOS
functions. Most of them are self explanatory but the following notes describe
some of their features.
This program uses routines described in chapter 4. It waits for you to press the
space bar and then checks how long it takes you to press another key.
Get Console Status is used to break out of the timer loop when the second key
has been pressed.
The function FSIZE calculates the size of a named ftle and can be used either with
the ProPascal program in chapter 9 or in your own assembler programs.
Its input parameter is the address of an FCB and this is passed on the stack.
FSIZE will return to you in the register pair HL either the size of the file in sectors
or -1 (if the file does not exist).
The Pascal program provided uses the assembler procedures GET_DPB and
GET_MAP and prints the amount of free space on a specified disc. GET_DPB
and GET_MAP have been written so as to be of use in your own assembler
programs and are suitably annotated.
GET_DPB returns a record containing the DPB for the specified drive.
GET_MAP returns the allocation map for this drive. Both procedures expect
two parameters: the drive code and an address of memory into which they will
place the information required.
APPENDIX A: EXAMPLE PROGRAMS 93
Symbols:
No errors detec~~~
96 CP/M 80 PROGRAMMER'S GUIDE
FSIZE function for Propes RHL Z80 Ass V 4.1 k 20-Har-84 Page 1
fSIZE function for Propas Rlt. ZBO Ass V 4.1 k 20-Mar-84 Page 2
Symbols:
No errors detected
98 CP/M 80 PROGRAMMER'S GUIDE
program free_disc_space;
(*
Program to calculate and display the amount of free space
on a disc.
*)
const
max_no_blocks_by_B = 4095; (* 32k/B -1 *)
type
byte = 0 •• 255;
word = 0 •• 65535;
dpb = record
no_sectors_a_track word;
bloc~shift_factor byte;
block_mask byte;
extent_mask byte;
size_in_blocks word;
no_dir_entries word;
reserved array[O •• J) of byte;
no_reserved_tracks word;
end;
var
free integer;
drive byte;
drive_name char;
my_dpb dpb;
an_alloc_map alloc_map;
const
unallocated = 0;
var
block_no integer;
free_space word;
(*
This function tests if a block is allocated or not,
if it is it returns a '1', otherwise it returns a '0'
for free block.
*)
APPENDIX A: EXAMPLE PROGRAMS 99
var
a_bit, thia_bit byte;
pwr2 word;
begin
pwr2 := 1;
this_bit := D;
(*
calculate bit no from block no (7-BLDCK_ND/8)
*)
a_bit := 7 - (block_number mod B);
while this_bit < a_bit do
begin
pwr2 :: pwr2*2;
this_bit := this_bit +1;
end;
begin
free_space :: D;
get_dpb(a_drive,my_dpb); (* get disc characteristics *)
begin
writeln('Simple Disc free space program vl.2a' );
writeln;
write('[nter drive name (A •• P) ?');
readln(drive_name);
drive :: ord(drive_name) - ord('A');
Symbols:
No errors detected
APPENDIX A: EXAMPLE PROGRAMS 103
Symbols:
No errora detected
Appendix B: CP/M Memory Map
The organisation of memory under CP/M is shown in the diagram below. Your
programs can be placed in the Transient Program Area (TPA).
"'
Top of memory
FFFFH
Memory used by firmware
or BIOS
CP/M BIOS
CP/M BOOS
CP/M CCP
TPA
100H
DEFAULT DISC BUFFER
SOH
SYSTEM TABLES
OOOOH
/
Bottom of memory
106
Appendix C: ASCII Code Tables
The first table below shows the standard ASCII 7-bit character set. In the second
table, we have broken this down to show the decimal value of each key as well as
the hexadecimal value. Where relevant, the CTRL code for a character is also
shown; this shows how you can generate the character by pressing a combination
of the CTRL key and another key.
Most of the actions in the first table are machine-independent; however, some
of them are not. You should thus check the implementation on your system and
update the second table accordingly.
~ 0 1 2 3 4 5 6 7
0 NUL DLE 0 @ p - p
1 SOH DC1 ! 1 A Q a q
2 STX DC2 II
2 B R b r
3 ETX DC3 £ 3 c s c s
4 EOT DC4 $ 4 D T d t
5 ENQ NAK % 5 E u e u
6 ACK SYN &, 6 F v f v
7 BEL ETB 7 G w q w
8 BS CAN ( 8 H X h X
9 HT EM ) 9 I y i y
A LF SUB * : J z -
j z
B VT ESC + i K [ k {-
c FF FS < L 1
"T
1-
D CR GS -' = M ] m }
E so RS . > N n -
F SI us I ? 0 # 0 DEL
107
108 CP/M 80 PROGRAMMER'S GUIDE
0
1
DOH
01H
NUL
SOH
Do nothing
Start of header
CTRL/11
CTRL/A
64
65
40H
41H •A
2 02H STX Start of text CTRL/B 66 42H B
3 03H ETX End of text CTRL/C 67 43H c
4 04H EDT End of transmission CTRL/0 68 44H 0
5 05H ENQ Enquiry CTRL/E 69 45H E
6 06H ACK Acknowledge CTRL/f 70 46H r
7 07H BEL Bell CTRL/G 71 47H G
8 OBH BS Backspace CTRL/H 72 4BH H
9 09H HT Horizontal tsb CTRL/1 73 49H I
10 OAH LF line feed CTRL/J 74 4AH J
11 OBH VT Vertical tab CTRL/K 75 4BH K
12 OCH rr Form feed CTRL/l 76 4CH l
13 DOH CR Carriage return CTRL/H 77 4DH H
14 DEH 50 Shift out CTRL/N 78 4EH N
15 OFH 51 Shift in CTRL/0 79 4FH 0
16 lOH OLE Data Link Escape CTRL/P 80 50H p
17 llH DC1 XON, Transmission ON CTRL/Q 81 51H Q
18 12H DC2 XON, Transmission ON CTRL/R 82 52H R
19 lJH DC3 XOFF, Transmission OFF CTRL/5 83 53H 5
20 14H DC4 XOFF,Transmission OFF CTRL/T 84 54H T
21 15H NAK Negative Acknowledge CTRL/U 85 55H u
22 16H SYN Synchronous Idle CTRL/V 86 56H v
23 17H ETB End of Transmission Block CTRL/W 87 57H w
24 lBH CAN Cancel CTRL/X 88 58H X
25 19H EM End of medium CTRL/Y 89 59H y
26 lAH SUB Substitute character · CTRL/Z 90 5AH z
27
28
29
lBH
1CH
lDH
ESC
rs
GS
Escape key
File separator
Group separator
CTRL/\
CTRL/
CTRL/]
91
92
93
5BH
5CH
50H
\
I
30 lEH RS Record separator CTRLr 94 5EH
31
32
lFH
20H
us Unit separator
Space
CTRL/_ 95
96
5FH
60H ...
n 21H ! 97 61H a
34 22H 98 62H b
35 23H £ 99 63H c
36 24H $ 100 64H d
37
JB
39
25H
26H
27H
"
clc
I
101
102
103
65H
66H
67H
e
g
f
This appendix starts with a summary of the BDOS-calling mechanism. The BDOS
functions are then listed on this page and the next, first in numeric order and then
in their appropriate groups.
The rest of the appendix contains detailed descriptions of all functions. The
functions themselves are laid out in alphabetic order, with the function code in
decimal at the top of each description and in hexadecimal at the bottom.
BOOS-calling mechanism
109
110 CP/M 80 PROGRAMMER'S GUIDE
16 CLOSE FILE
Registers on entry Registers on exit
Effect
This function is the reverse of Open File (function 15). It closes the me described
by the FCB whose address is held in DE by writing the enclosed details in the
appropriate disc directory. Wildcard characters(?) can be used in the menarne.
The directory code returned is in the range 0 to 3 for a successful operation. If
the mename cannot be found in the directory, FFH (255 decimal) is returned.
The FCB must have been used for an Open me or Create File function.
Example
BOOS = 0005H
FN_CLOSEJILE = 16
OATAriLEJCB:
LO OE,OATAriLEJCB
LO C,FN_CLOSEJILE
CALL BOOS
INC A
JN Z,EXIT
FAILEO_CLOSE: ;Identify failed file
;with message
EXIT:
lOH
112 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function allows you to calculate the size of a file. It returns the number of
128-byte sectors in the file by setting the record pointer in the FCB to the sector
immediately after the end of the file. You can then easily append data to the end
of the file.
The register pair DE must point to a 36-byte FCB for the file in question, and
the FCB must not contain any wildcard characters (?).
On return, bytes 33 and 35 of the FCB contain the file size as a 16-bit value;
in effect, this is the sector address of the sector following the end of the file. If
byte 35 contains 1 on return, the file contains the maximum sector count (65536).
Compute File Size will always give you an answer whether or not the file exists.
Thus, you should check that the file exists by using Search for First.
You can append data to the end of an existing file by merely calling Compute
File Size to set the random record position to the end of the file, then performing
a sequence of random writes starting at the sector address contained in the record
pointer.
When you write a file sequentially, the number of sectors of data in the file
corresponds to the number returned by Compute File Size. However, if you create
the file in random mode and leave 'holes' in it, Compute File Size will give a larger
number than the number of data sectors in the file~ If, for example, you write
only the last sector of an 8 Megabyte file in random mode (that is, sector 65535),
the file size is 65536 but only one sector of data is allocated.
23H
APPENDIX D: BOOS FUNCTIONS 113
Example
BOOS = 0005H
FN_COMPUTE_FILE_SIZE = 35
DATAFILE_FCB
LD DE,PATAFILE_FCB
LD C,FN_COMPUTE_FILE_SIZE
CALL BOOS
23H
114 CP/M 80 PROGRAMMER'S GUIDE
CONSOLE INPUT 1
Registers on entry Registers on exit
Effect
Example
BOOS = OOOSH
FN_CONSOLE_IN = 1
LO c,r~CONSOLE_IN
CALL BOOS
; ASCII character
;is in register A.
OlH
APPENDIX D: BDOS FUNCTIONS 115
2 CONSOLE OUTPUT
Registers on entry Registers on exit
C: 02H
E: ASCII character
Effect
The ASCII character in register E is sent to the screen. Tabs are expanded and
checks are made for start/stop scrolling (CTRL/S) from the keyboard.
Example
BOOS : 0005H
fN_CONSOLE_OUT = 2
LD E, 'C'
LO C,fN_CONSOLE_OUT
CALL BOOS
02H
116 CP/M 80 PROGRAMMER'S GUIDE
Effect
Creates an entry in the directory for a file named in the FCB addressed by DE.
This FCB must name a file that does not currently exist in the directory. You can
make sure that the file does not exist by using a preceding Delete File function.
Create File also activates the FCB for file operations; thus, a subsequent Open
File function is not needed.
After a successful operation, a value in the range 0 to 3 is returned in the A
register. For an unsuccessful operation, the value FFH (255 decimal) is returned,
indicating that the directory is full.
Note that if you attempt to create a file that already exists, you will corrupt
the disc structure.
Example
BOOS = 0005H
fN_CREATE_f ILE = 22
DATAflLEJCB:
LD DE,DATAflLEJCB
LD C,f~CREATEJILE
CALL BOOS
INC A
JN Z,PROCESS
fAILED_CREATIDN: ;Directory full
PROCESS:
16H
APPENDIX D: BOOS FUNCTIONS 117
19 DELETE FILE
Registers on entry Registers on exit
Effect
This function removes all files that match the FCB addressed by register pair DE.
The filename and filetype positions can contain wildcard characters (?) but the
drive code must be unambiguous.
If the file or files cannot be found, the value FFH (255 decimal) will be
returned. Otherwise, a value in the range 0 to 3 will be returned.
Example
BOOS = 0005H
FN_OELETE_FILE = 19
OATAFILE_FCB:
LO OE,OATAFILE_FCB
LO C,FN_OELETE_FILE
CALL BOOS
INC A
JN Z,EXIT
fAILEO_OELETION: ;Identify failed file
;with message
EXIT:
13H
118 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function allows you to display output on the screen or read from the key-
board. It bypasses BDOS control character functions such as CTRL/C, CTRL/S,
CTRL/Q and CTRL/P.
Upon entry, register E should contain FFH (indicating that you want console
input), FEH (indicating that you want the console status), or an ASCII character
that you want to output.
When you put FFH in register E, Direct Console I/0 returns zero in the A
register if no character is ready, otherwise register A contains the next character
input. Input is not echoed to the screen.
Versions of CP/M later than 2.2 accept FEH in register E as being a request for
the keyboard status. They will return either 0 if no character is waiting, or 1 if a
character has been input.
If the value in E is not FFH or FEH, CP/M assumes that it is a valid ASCII
character and sends it to the screen. In this case, the contents of the A register are
undefined.
You must not mix Direct Console I/0 and other console I/0 functions.
06H
APPENDIX D: BOOS FUNCTIONS 119
The following code outputs the character Y to the screen using Direct Console 1/0.
BOOS = 0005H
fN_OIRECT_IO = 6
The following code uses Direct Console 1/0 to input the character CTRL/C
without rebooting CP/M.
BOOS = 0005H
fN OIRECT_IO = 6
CTRL_C = J
INPUT_MOOE = orrH
EXIT:
06H
120 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function returns the address in memory of the allocation map for the
currently selected drive.
An allocation map is a record of which allocation blocks on a disc are in use
and which ones are not. There is an allocation map for each disc and it consists of
a bit map, each bit of which corresponds to an allocation block.
Note that the allocation information may be invalid if the disc selected has
been marked as read-only.
Example
BOOS = 0005H
FN_GET_ALLOCATION_AOORESS = 27
LD C,FN_GET_ALLOCATION_ADDRESS
CALL BOOS
;Allocation map address is in
;register pair HL
lBH
APPENDIX D: BDOS FUNCTIONS 121
Effect
This function allows you to test if a character has been input at the keyboard but
to continue processing if it has not.
If a character has been typed, the function returns the value 1; otherwise,
0 is returned.
Example
OR A ;Set flags:
- Z no key pressed
; - NZ key pressed.
JR NZ,STOP_THE_CLOCK
CALL INCREHENT_THE_CLOCK
JR TIHER_LOOP
STOP_THE_CLOCK:
OBH
122 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function returns the address of the BIOS Disc Parameter Block (DPB) in
register pair HL. Full details of the DPB contents are given in chapter 6.
Example
BOOS = OOOSH
fN_GET_OPB_AOORESS = 31
LO C,fN_GET_OPB_AOORESS
CALL BOOS
lFH
APPENDIX D: BOOS FUNCTIONS 123
Effect
Example
BOOS = 0005H
rN GET_IO_BYTE = 7
LO C,rN_GET_IO_BYTE
CALL BOOS
;1/0 byte value is in register A.
07H
124 CP/M 80 PROGRAMMER'S GUIDE
GETREAD-ONL YINDICATORS 29
Registers on entry Registers on exit
Effect
This function returns a value in register pair HL where each bit indicates the status
of a specific drive using the arrangement
bit 7 bit 0
I~ (
H L
drives
p
) .
1/ H A/
If a bit is set, the related drive has been write-protected using Write Protect Disc
(function 28).
Example
BOOS = 0005H
FN_GET_READ_ONL Y_INDICATORS = 25
lD C,FN_GET_READ_ONlY_INDICATORS
CAll BOOS
lD A,l
AND 1
JR Z,PROCESS ;Jump if drive A is not
;write protected
PROCESS:
lDH
APPENDIX D: BDOS FUNCTIONS 125
5 LIST OUTPUT
Registers on entry Registers on exit
C: OSH
E: ASCII character
Effect
The ASCII character in register E is sent to the logical listing device. Tabs are
expanded and checks are made for other CTRL codes.
Example
BOOS : 0005H
rN_LIST_OUT = 5
LO E, 'C'
LO C,r~LIST_OUT
CALL BOOS
OSH
126 CP/M 80 PROGRAMMER'S GUIDE
OPEN FILE 15
Registers on entry Registers on exit
Effect
This function opens a file whose entry exists in the disc directory with the
currently active user number. You should not try to access a file unless it has first
been successfully opened.
CP/M scans the referenced disc directory for a match in fields 1 to 14 of the
FCB referenced by DE. If you put a wildcard(?) character in any position of the
filename, any directory character will be accepted. Normally, you should not do
this.
If an entry is found in the directory, the relevant directory information will be
copied into bytes 16 to 31 of the FCB. You can then access the file with read and
write operations.
If the file cannot be found, Open File returns the value FFH (255 decimal) in
register A. If the file is found, a directory code in the range 0 to 3 is returned.
You can use the directory code to find the directory entry in the current DMA
buffer of your program. If you multiply the contents of the A register by 32 (shift
the A register left by 5 bits) this will give you the start address of the directory
entry in the buffer.
Note that the current record pointer in the FCB (byte 32) must be zeroed by
your program if the file is to be accessed sequentially from the first record.
Example
BOOS = 0005H
fN_OPEN_f ILE = 15
OATAfiLE_fCB:
LO OE,OATAfiLE_fCB
LO c,rN_OPEN_fiLE
CALL BOOS
INC A
JN Z,EXIT
fAILED_OPEN: ;Identify failed file
;with message
EXIT:
OFH
APPENDIX D: BDOS FUNCTIONS 127
9 PRINT STRING
Registers on entry Registers on exit
C: 09H
DE: String address
Effect
Sends to the screen a character string which is stored in memory at the address
held in the register pair DE. The end of the string should be marked by a $ (dollar)
character as shown below.
Tab characters (CTRL/1) are expanded so as to move the cursor to the next tab
position. A check is also made for start/stop scrolling characters (CTRL/S) and
printer echo (CTRL/P) on keyboard input.
This function will not normally print'$' characters (except under certain
versions of CP/M and under certain conditions - see chapter 7). To print '$'
characters, you must use Console Out (function 2).
Example
BOOS = 0005H
f~PRINT_STRING = 9
ENO_Of_STRING = '$'
lD C,fN_PRINT_STRING
CAll BOOS ;Print message.
09H
128 CP/M 80 PROGRAMMER'S GUIDE
PUNCH OUTPUT 4
Registers on entry Registers on exit
Effect
Example
BOOS = 0005H
fN_PUNCH_OUT = 4
lO E, 'C'
lO C,fN_PUNCH_OUT
CAll BOOS
04H
APPENDIX D: BOOS FUNCTIONS 129
C: OAH
DE: Buffer address
Effect
Reads a line input from the keyboard into a buffer whose address is in register
pair DE. Keyboard input ends either when the buffer overflows or a carriage
return or linefeed is typed.
The input buffer has the following form.
'-15o. . ~.HI~~~J~.-Io.....~..l_h_,_I"---'---'--s.L-1m......~.I--L-1_t~.-h-~..........~~ 0
Number of characters typed
If you have not reserved storage space for an input buffer, the BOOS will use
whatever memory the DE registers point to. You should therefore ensure that
sufficient space is allocated.
The size of the buffer should be in the range 1 to 255 bytes and you need a
region of memory equal to the buffer length plus 2 bytes. If the buffer length is
marked as zero, the BOOS will treat it as having a size of 1 byte and will transfer
1 character.
A number of line-editing facilities are available during input. Their description
and key combinations are given in the following table.
OAH
130 CP/M 80 PROGRAMMER'S GUIDE
Key Effect
combination
Certain operations return the cursor to the leftmost position (for example,
CTRL/U). In these cases, the cursor will be placed in the column position where
the prompt ended, thus making operator data input and line correction more
legible. In earlier releases of CP/M, the cursor returned to the extreme left margin.
Example
BOOS = 0005H
FN_REAO_BUfTER = 10
BUFFER_LENGTH = BO
BUFFER: OEFS BUFFER_LENGTH
DEFB BUFFER_LENGTH+l
OAH
APPENDIX D: BDOS FUNCTIONS 131
33 READ RANDOM
Registers on entry Registers on exit
Effect
This function reads a 128-byte sector of data from a file into the current data
buffer. The file must have been opened using Open File (function 15).
The sector number in the file is specified by the contents of bytes 33 to 35 of
the FCB. Byte 33 is the least-significant byte and byte 35 the most-significant
byte. Bytes 33 and 34 together form a 16-bit sector number in the range 0 to
65535. Byte 35 is used by CP/M to calculate the size of the file and you should
set it to zero before using the function.
To use Read Random, you first set the sector number in bytes 33 and 34, set
byte 35 to zero and then call the BDOS. Upon return, register A contains one of
the error codes shown in table D.3 (on page 132) or zero, if the operation was
successful. In the latter case, the current DMA address will hold the block
required. The sector number in bytes 33 and 34 will not have been incremented,
so a subsequent read will read the same record.
CP/M automatically sets the logical extent and current sector values in the FCB.
Thus, you can read or write the file sequentially from the current randomly
accessed position. If you do this, you should note that the last block that was
randomly read will be reread when you switch to sequential reading, and the last
record will be rewritten when you switch to sequential writing.
21H
132 CP/M 80 PROGRAMMER'S GUIDE
READ RANDOM 33
Example
BOOS = OOOSH
FN_READ_RANOOM = 33
OATAFllE_FCB
ERROR:
PROCESS:
*Does not normally occur under proper system operation. If it does, the error can be cleared
by re-reading extent zero (as long as the disc is not physically write-protected).
21H
APPENDIX D: BDOS FUNCTIONS 133
20 READ SEQUENTIAL
Registers on entry Registers on exit
Effect
Reads the next 128-byte sector of data from the file into memory at the current
DMA address. The file is described by the FCB whose address is held in DE and
this FCB must have been activated by an Open File or Create File function.
If the read is successful, a zero value will be returned in register A, otherwise a
non-zero value will be returned. This could happen, for example, if end-of-file is
detected.
The data sector is read from a position in the disc logical extent given by byte
32 of the FCB. This byte is then incremented by CP/M to point to the next sector.
If byte 32 overflows, the next logical extent is opened and byte 32 reset to zero
ready for the next read.
Example
BOOS = 0005H
rN_READ_SEQUENTIAl = 20
DATAr llEJCB:
lD DE,DATArllEJCB
lD C,r~READ_SEQUENTIAl
CAll BOOS
JP Z,PROCESS
FAilED_READ: ;End of file reached
PROCESS:
14H
134 CP/M 80 PROGRAMMER'S GUIDE
READER INPUT 3
Registers on entry Registers on exit
Effect
This function is a throwback to the days when input/output was on paper tape. It
reads the next ASCII character from the logical reader into register A.
Example
BOOS = 0005H
F"N_REAO£R_IN = 3
LO C,F"N_READE~IN
CALL BOOS
;ASCII character
;is in register A.
03H
APPENDIX D: BDOS FUNCTIONS 135
23 RENAME FILE
Registers on entry Registers on exit
Effect
Before calling this function, the FCB addressed by register pair DE is set up by
you to hold two file descriptions. The first 16 bytes of the FCB contain a descrip-
tion of the old file, the next 16 bytes a description of the new flle.
The drive code at byte 0 is used to select the drive; the drive code at byte 16 of
the FCB is assumed to be zero.
After a successful operation, a value in the range 0 to 3 is returned in A. If the
old fllename cannot be found in the directory, FFH (255 decimal) is returned.
Note that this function will not accept the wildcard character (?) in the FCB.
You must also ensure that the new flle name does not already exist.
Example
BOOS = OD05H
FN_RENAMEJ ILE = 23
DATAriLEJCB:
LD OE,DATAFILEJCB
LO C,FN_RENAMEJILE
CALL BODS
INC A
JN Z,PROCESS
FAILED_RENAME: ;Old filename not in directory
PROCESS:
17H
136 CP/M 80 PROGRAMMER'S GUIDE
C: ODH
Effect
Write Protect Disc (function 28) lets you protect a disc against writing from
within your program. Reset Disc System restores the entire disc system to a reset
state where all discs are set to read/write. Drive A will be the only currently
selected drive and the default data buffer address will be set to 0080H.
Example
BOOS = 0005H
fN_RESET DISC_SYSTEH = 13
LD C,f~RESET_DISC_SYSTEH
CALL BOOS
ODH
APPENDIX D: BDOS FUNCTIONS 137
37 RESET DRIVE
Registers on entry Registers on exit
C: 25H A: OOH
DE: Drive info
Effect
Resets one or more drives so that any write protection caused by a call to Write
Protect Disc (function 28) is cancelled and the drives are returned to the Read/
Write status.
Drives are specified by setting bits in the register pair DE using the following
arrangement.
----It(
bit 7 bit 0
r----D ~~~-E
)
drives
P ,7 'H A/
To maintain compatibility with MP/M, CP/M returns a zero value in register A.
Example
BOOS : 0005H
FN_RESET_DRIVE = 37
25H
138 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function returns the drive number of the currently selected drive. The drive
numbers are in the range 0 to 15 and correspond to drives A to P.
Example
The following example checks if drive A is the currently selected drive and warns
the operator if it is not.
BOOS = 0005H
FN_RETURN_CURRENT_ORIVE = 25
ORIVE_A = 0
LO C,FN_RETURN_CURRENT_ORIVE
CALL BOOS
CP ORIVE_A
JR Z,PROCESS ;Jump if drive A is currently
;selected drive
PROCESS:
19H
APPENDIX D: BOOS FUNCTIONS 139
Effect
This function returns a 16-bit value in the register pair HL, where each bit position
corresponds to a drive using the following scheme.
drives
If the appropriate bit is set, the drive is logged-in as a result of either of the
following
Example
The following example checks if drive B is logged-in and warns the operator if it is
not.
BOOS = 0005H
FN_RETURN_LOGGED_IN_DRIVES = 24
DRIVE_B_BIT_PATTERN = 2
LD C,FN_RETURN_LOGGED_IN_DRIVES
CALL BOOS
Effect
Provides information that helps you write programs which can be used under
different versions of CP/M.
When control passes back to your program, CP/M will have inserted in registers
Hand L two numbers that describe which type and version of the operating system
your program is running under. The number in H describes the system type as
shown in the left-hand table below. The number in L describes the version number
as shown in the right-hand table below.
3F 3.15
Example
The following example checks the system and version number and exits if the
system is not CP/M.
BOOS = 0005H
fN_VERSION_NO = 12
LD C,rN_VERSION_NO
CALL BOOS
PROCESS:
OCH
APPENDIX D: BDOS FUNCTIONS 141
Effect
This function searches the directory for a match with the file given in the FCB
addressed by DE. If the file is not found, the value FFH (25 5 decimal) is returned,
otherwise a number in the range 0 to 3 is returned.
A wildcard facility is available: if you insert a? character in any position in the
filename, filetype or extent field, these positions will be ignored during the search.
If the drive code field contains a? character, the default disc will be searched. In
this case, the function will return any matched entry belonging to any user
number.
Note that you should not attempt any disc operation between Search for First
Entry and Search for Next Entry.
Example
BOOS = 0005H
rN_SEARCH_rOR_fiRST = 17
LO C,rN_SEARCH_fO~IRST
CALL BOOS
INC A
JN Z,ENTRY_NOT_fOUNO
PROCESS: ;Process directory entry
ENTRY~OT_fOUNO:
11H
142 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function is similar to Search for First Entry (function 17). However, in this
case, the directory is scanned from the last matched entry. When no more directory
items match, the value FFH (decimal255) is returned. Return codes are the same
as for Search for First Entry.
You should not attempt any disc operations between calls to Search for First
and Search for Next.
Example
BOOS = 0005H
rN_SEARCH_fOR_NEXT = 18
LD C,rtLSEARCH_fOR_NEXT
CALL BODS
INC A
JN Z,NO_MORE_ENTRIES
PROCESS: ;Process this directory
;entry
NO_MORE_ENTRIES:
12H
APPENDIX D: BDOS FUNCTIONS 143
14 SELECT DISC
Registers on entry Registers on exit
C: OEH
E: Selected disc
Effect
This function allows you to change the currently selected drive by program. You
put the appropriate drive number in register E and then call the BDOS entry point.
Drivenumbersarein the range 0 to 15, where 0 refers to drive A and 15 to drive P.
The drive is logged-in until the next cold start, warm start (pressing CTRL/C)
or call to Disc System Reset (function 13). If the disc in the drive is changed, the
drive will go to a read-only status on the next disc operation to that drive.
FCBs that specify drive code zero refer to the currently selected drive; those
that specify codes 1 to 16 refer to a specific drive (AtoP).
Example
BOOS = 0005H
FN_SElECT_OISC = 14
OEH
144 CP/M 80 PROGRAMMER'S GUIDE
C: 1AH
DE: New DMA address
Effect
This function defmes the start address of the disc data buffer for file operations
(the DMA address). The disc data buffer itself will be 128 bytes in size.
The DMA address will be reset to 0080H when one of the following occurs.
• A cold start
• A warm start (pressing CTRL/C)
• Reset Disc System (function 13) is called
Example
BOOS = OOOSH
F~SET_DM~AOORESS = 26
OATA_BUFFER:
LO DE,OATA_BUFFER
LO C,FN_SET_DMA_ADDRESS
CALL BOOS
1AH
APPENDIX D: BDOS FUNCTIONS 145
Effect
The file attributes are the most significant bits in bytes 1 to 11 of the FCB. They
have the following significance.
Example
BOOS = 0005H
FN_SETJILE_ATTRIBUTES = 30
OATAFILEJCB:
LO OE,OATAFILEJCB
LO C,FN_SETJILE_ATTRIBUTES
CALL BOOS
lEH
146 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function either changes the current user number or tells you what it is.
If register E contains FFH, the value of the current user number is returned in
register A; it is in the range 0 to 15.
If register E contains any other value, the current user number is changed to
the modulus 16 of this value (the remainder when the value is divided by 16).
Example
BOOS = 0005H
r~SET_USE~CODE = 32
USER_CODE 3
LO E,USER_COOE
LO C,rN_SET_USER_COOE
CALL BOOS
The next example returns the user code and stops the program if it is not 1.
BOOS = 0005H
rN GET_USER_COOE = 32
EXIT:
PROCESS:
20H
APPENDIX D: BOOS FUNCTIONS 147
C: 08H
E: 1/0 byte value
Effect
Example
BOOS = 0005H
rN_SET_IO_BYTE = B
LD c,rN_SET_IO_BYTE
LD E,lO
CALL BOOS
08H
148 CP/M 80 PROGRAMMER'S GUIDE
Effect
When you are reading or writing a file sequentially, this function returns the
random sector address of the current data sector in bytes 33 to 35 of the FCB
addressed by register pair DE.
The information returned can be useful in two ways.
• You may scan a file sequentially to extract the positions of various fields. As
each field is found, you can then call Set Random Sector to find the random
sector position for the current sector. If the size of each data unit is 128 bytes,
you can then put the sector position of each field in a table, together with the
key of the field, for later retrieval. Once you have scanned the entire file and
built up this table, you can move instantly to a specific sector by performing
a random read.
You can generalise the procedure to handle sectors containing variable-
length records. Your program need store only the byte position relative to the
start of the buffer, along with the field key and sector number, to find the
exact start position of the field at a later time.
• If you want to process a file from an 'offset' position along the file, you can
use Set Random Sector to help you. All you do is access the file sequentially
until the offset is reached, use Set Random Sector to mark the position of the
offset and then access the file with random read and write operations from
that point
Example
BOOS = 0005H
FN_SET_RANDO~SECTOR
= 36
DATAriLEJCB
LD DE,DATAFILEJCB
LD C,FN_SET_RANDOM_SECTOR
CALL BOOS
24H
APPENDIX D: BOOS FUNCTIONS 149
0 SYSTEM RESET
Registers on entry Registers on exit
C: OOH
Effect
Reloads the BDOS and CCP and then passes control to the CCP. The CCP
re-initialises the disc system by selecting and logging in disc drive A. It will then
select the drive previously selected by the CCP as the default drive.
This function has exactly the same effect as a jump to location BOOT.
Example
BOOS = OOOSH
fN_SYSTEH_RESET : 0
EXIT: LD C,fN_SYSTEH_RESET
CALL BODS
OOH
150 CP/M 80 PROGRAMMER'S GUIDE
C: 1CH
Effect
This function allows you to protect the currently selected drive until one of the
following occurs.
Any attempt to write to the disc will produce the message BDOS ERR on d: R/0.
Note that discs protected by Write Protect Disc are protected only against
programs using CP/M BDOS functions. Programs that use BIOS or firmware
routines can bypass this method of protection.
Example
BOOS : 0005H
f~WRITE_PROTECT_DISC = 28
LD C,f~WRITE_PROTECT_DISC
CALL BOOS
1CH
APPENDIX D: BDOS FUNCTIONS 151
34 WRITE RANDOM
Registers on entry Registers on exit
Effect
This function writes a 128-byte sector of data to a file from the current data
buffer. The ftl.e must have been opened using Open File (function 15).
The sector number in the file is specified by the contents of bytes 33 to 35 of
the FCB. Byte 33 is the least-significant byte and byte 35 the most-significant
byte. Bytes 33 and 34 together form a 16-bit sector number in the range 0 to
65535. Byte 35 is used by CP/M; you should set it to zero before using the
function.
To use Write Random, you first set the sector number in bytes 33 and 34, set
byte 35 to zero and then call the BOOS. Upon return, register A contains one of
the error codes shown in Table 0.3 (on page 132) or zero, if the operation was
successful. The sector number in bytes 33 and 34 will not have been incremented,
so a subsequent write will rewrite the same record.
CP/M automatically sets the logical extent and current sector values in the
FCB. Thus, you can read or write the file sequentially from the current randomly
accessed position. If you do this, you should note that the last sector that was
randomly read will be reread when you switch to sequential reading, and the last
sector will be rewritten when you switch to sequential writing.
Example
BOOS = 0005H
FN_WRI TE_RANOOM = 34
DATAfiLE_FCB
ERROR: 22H
PROCESS:
152 CP/M 80 PROGRAMMER'S GUIDE
Effect
This function is similar to Write Random (function 34) with the exception that a
previously unused sector is filled with zeros before the data is written.
Example
BOOS = 0005H
fN_WRITE_RANOOM_ZERO_flll = 40
OATAfiLE_fCB:
LO DE,OATAfiLE_fCB
LO C,f~WRITE_RANDO~ZERO_fiLL
CALL BOOS
ERROR_IN_WRITE:
PROCESS:
28H
APPENDIX D: BDOS FUNCTIONS 153
21 WRITE SEQUENTIAL
Registers on entry Registers on exit
Effect
Writes a 128-byte sector of data from the current DMA address to the file. The
file is described by the FCB whose address is held in DE and this FCB must have
been activated by an Open File or Create File function.
If the write is successful, a zero value will be returned in register A, otherwise a
non-zero value will be returned. This could happen, for example, if the disc is full.
The data sector is written to a position in the disc logical extent given by byte
32 of the FCB. This byte is then incremented by CP/M to point to the next sector.
If byte 32 overflows, the next logical extent is opened and byte 32 reset to zero
ready for the next write.
Write operations can take place on an existing file. In this case, new sectors will
be appended to the file.
Example
BOOS = 0005H
rN_WRITE_SEQUENTIAL = 21
DATAriLEJCB:
LD DE,DATAriLEJCB
LD C,rN_WRITE_SEQUENTIAL
CALL BOOS
JP Z,PROCESS
rAILED_WRITE: ;Disc full or hardware error
PROCESS:
15H
Appendix E: BDOS Functions of CP/M 80
Family
155
FN 1.4 2.2 MP/M II CP/NET 1.2 CP/M PLUS PERSONAL CP/M FN
0 SYSTEM RESET SYSTEM RESET SYSTEM RESET SYSTEM RESET SYSTEM RESET SYSTEM RESET 0
I READ CONSOLE CONSOLE INPUT CONSOLE INPUT CONSOLE INPUT CONSOLE INPUT CONSOLE INPUT I
2 WRITE CONSOLE CONOUT CONOUT CONOUT CONOUT CONOUT 2
3 READER INPUT READER INPUT RAW CONSOLE INPUT READER INPUT AUXILIARY INPUT AUXILIARY INPUT J
4 PUNCH OUTPUT PUNCH OUTPUT RAW CO~SOLE OUTPUT PUNCH OUTPUT AUXILIARY OUTPUT AUXILIARY OUTPUT 4
5 LIST OUTPUT LIST OUTPUT LIST OUTPUT LIST OUTPUT LIST OUTPUT l 1ST OUTPUT 5
6 DIRECT CONSOLE 1/0 DIRECT CONSOLE 1/0 DIRECT CONSOLE 1/0 DIRECT CONSOLE 1/0 DIRECT CONSOLE 1/0 6
7 GET 1/0 STATUS GET 1/0 BYTE GET 1/0 BYTE GET 1/0 BYTE AUXILIARY STATUS AUXILIARY 1/P STATUS 7
8 SET 1/0 STATUS SET 1/0 BYTE SET 1/0 BYTE SET 1/0 BYTE AUXILIARY 0/P STATUS AUXILIARY 0/P STA IUS 8
9 PRINT CONTROL BUFFER PRINT STRING PRINT STRING PRINT STRING PRINT STRING PRINT STRING 9
10 READ CONSOLE BUFFER READ CONSOLE BUFFER READ CONSOLE BUFFER READ CONSOLE BUFFER READ CONSOLE BUFFER READ CONSOLE BUffER 10
II GET CONSOLE STATUS GET CONSOLE STATUS GET CONSOLE STATUS GET CONSOLE STATUS GET CONSOLE STATUS GET CONSOLE STATUS II
12 LIFT HEAD RETURN VERSION NUMBER RETURN VERSION NUMBER RE TURN VERSION NUMBER RETURN VERSION NUMBER RETURN VERSION NUMBER 12
IJ !NIT BOOS RESET DISC SYSTEM RESET DISC SYSTHl RESET DISC SYSTEM RESET DISC SYSTEM RESET DISC SYSTEM lJ
14 SELECT DISC SELECT DISC SELECT DISC SELECT DISC SELECT DISC SELECT DISC 14
15 OPEN fILE OPEN FILE OPE~ fILE OPEN FILE OPEN FILE OPEN FILE 15
16 CLOSE fILE CLOSE fiLE CLOSE fILE CLOSE FILE CLOSE fiLE CLOSE fiLE 16
17 SEARCH fILE SEARCH NEXT SEARCH ~EXT SEARCH NEXT SEARCH NEXT SEARCH NEXT I7
18 SEARCH NEXT SEARCH NEXT SEARCH ~EXT SEARCH NEXT SEARCH NEXT SEARCH NEXT IB
19 DELETE FILE DELETE fILE DELETE fILE DELE IE fILE DELETE FILE DELETE fiLE 19
20 READ SEQUENTIAL READ SEQUENTIAL READ SEQUENTIAL READ SEQUENTIAL READ SEQUENTIAL READ SEQUENTIAL 20
21 WRITE SEQUENTIAL WRITE SEQUENTIAL WRITE SEQUENTIAL l•RITE SEQUENTIAL WRI IE SEQUENTIAL WRITE SEQUENTIAL 21
22 CREATE FILE CREATE fiLE CREATE fiLE CREATE FILE CREATE FILE CREATE FILE 22
2J RENAME fiLE RENAME FILE RENAME fILE RENAME fiLE RENAME fiLE RENAME fiLE 2J
24 RET LOGGED-IN DRIVES RET .LOGGED-IN DRIVES RET LOGGED-IN DRIVES RET LOGGED-IN DRIVES RET LOGGED-IN DRIVES RET LOGGED-IN DRIVES 24
25 GET DRIVE NO GET CURRENT DRIVE GET CURRENT DRIVE GET CURRENT DRIVE RETURN CURRENT DISC RETURN CURRENT DISC 25
26 SET D11A ADDRESS SET OMA ADORE 55 SET OMA ADORE 55 SET OMA ADDRESS SET DMA ADDRESS SET DMA ADDRESS 26
27 GET ALLOC GET ADD ALLOCN MAP GET ADD ALLOCN HAP GET ADD ALLDCN MAP GET ADD All OCN MAP GET ADD ALLOCN MAP 27
28 l·JRITE PROTECT DISC WRITE PROTECT DISC WRITE PROTECT DISC WRITE PROTECT DISC WRITE PROTECT DISC 2B
29 GET R/0 INDICATORS GET R/0 INDICATORS GET R/0 INDICATORS GET R/0 INDICATORS GET R/0 INDICATORS 29
SET fILE ATTRIBUTES SET FILE ATTRIBUTES SET fiLE ATTRIBUTES SET FILE ATTRIBUTES SET FILE ATTRIBUTES JO
Vl JO
Jl GET DISC PAR. ADO. GET DISC PAR. ADD. GET DISC PAR. ADD. GET DISC PAR. ADD. GET DISC PAR. ADD. Jl
0\ J2
-I JJ
SET/GET USER CODE
READ RANDOM
SET /GET USER CODE
READ RANDOM
SET/GET USER CODE
READ RANDOM
SET /GET USER CODE
READ RANDOM
SET /GET USER CODE
READ RANDOM
J2
H
J4 WRITE RANDOM WRITE RANOO~l WRITE RANDOM WRIT[ RANDOM WRITE RANDOM J4
J5 COMPUTE fiLE SIZE COMPUTE fILE SIZE COMPUTE FILE 51 ZE COMPUTE fiLE SIZE COMPUTE FILE SIZE J5
16 SET RANDOM SECTOR SET RA~DOM SECTOR SET RANDOM SECTOR SET RANDOM SECTOR SET RANDOM SECTOR J6
J7 RESET DRIVE RESET DRIVE RESET DRIVE RESET DRIVE RESET DRIVE J7
38 ACCESS DRIVE ACCESS DRIVE ACCESS DRIVE JB
J9 FREE DRIVE FREE DRIVE fREE DRIVE J9
40 WRITE RANDOM ZERO fill WRfTE RA~DOM ZERO fIll WRITE RANDOM ZERO fIll WRIT[ RANDOM ZERO fIll ~JRITE RANDOM ZERO fIll 40
4i TEST & WRITE RECORD TEST & WRITE RECORD 41
42 LOCK RECORD LOCK RECORD LOCK RECORD 42
4J UNLOCK RECORD UNLOCK RECORD UNLOCK RECORD 43
44 SET MULTI-SECTOR CT. SET MUL II-SECTOR CT. 44
45 SET BOOS ERROR MODE SET BOOS ERROR MODE SET BOOS ERROR MODE SET BOOS ERROR MODE 45
46 GET DISC FREE SPACE GET DISC fREE SPACE 46
47 CHAIN TO PROGRAM CHAIN TO PROGRAM 47
48 flUSH BUFFERS FLUSH BUFFERS FLUSH BliTERS 4B
49 GET/SET SCB 49
50 DIRECT BIOS CALL 50
59 LOAD OVERLAY 59
60 CALL RSX 60
64 LOGIN 64
65 LOGOFF 65
66 SEND MSG ON NETWORK 66
67 GET MSG FROM NETWORK 67
68 GET ~'ETWORK STATUS 68
69 GET CONF I G TABLE ADD 69
70 SET COMPATIBILITY All 70
71 GET SERVER CONF I G 71
9B FREE BlOCKS 9B
99 TRUNCATE FILE 99
100 SET DIR LABEL SET DIR LABEL IOD
101 RETURN DIR LABEL RETURN DIR LABEL 101
102 READ FILE XFCB READ FILE DATE STAMPS 102
AND PASSWORD MODE
10} WRITE FILE XFCB WRITE FILE XFCB 103
104 SET DATE & TIME SET DA IE & TIME 104
105 GET DATE & TIME GET DA IE & TIME 105
106 SET DEFAULT P/WORD SET DEFAULT P/WORD SET DEFAULT P/WORD 106
107 RETURN SERIAl NO RETURN SERIAl NO 107
lOB GET /SET PROGRAM lOB
RETURN CODE
109 GET /SE T CON MODE GET/SET CON MODE 109
110 GET /SET 0/P DELIMITERS GET/SET 0/P DELIMITERS 110
Ill PRINT BlOCK PRINT BlOCK Ill
112 LIST BLOCK LIST BlOCK 112
ID DIRECT SCREEN FNS 113
124 BYTE BlOCK COPY 124
125 BYTE BlOCK ALTER 125
120 ABSOLUTE MEMORY REQ 128
129 RUOC MEMORY REQ 129
DO MEMORY FREE 130
131 POLL IH
132 FlAG WAIT 132
13} FlAG SET 1H
134 CREATE ONE 134
OPEN ONE 135
U5
U6 DELETE ONE
VI
-.l
- I
U7 READ ONE
136
IH
UB CONDITIONAL RE AO ONE 138
D9 WRITE ONE 139
140 CONDITIONAl WRITE ONE 140
141 DELAY 141
142 DISPATCH 142
14} TERMINATE PROCESS 143
144 CREATE PROCE 55 144
145 SET PRIORITY 145
146 ATTACH CONSOLE 146
147 DETACH CONSOLE 147
14B SET CONSOLE 148
149 ASSIGN CONSOLE 149
150 SEND CLI COHMAND 150
151 CALL RSP 151
152 PARSE FILENAME PARSE F ILENAHE 152
153 GET CONSOLE NO 153
154 SYSTEM DATA ADDRESS 154
155 GET DATE & TIME 155
156 RETURN PROCESS 156
DESCRIPTOR ADDRESS
157 ABORT PROCESS 157
15B ATTACH LIST 158
159 DETACH LIST 159
160 SET LIST 160
161 COND ATTACH liST 161
162 COND AIT ACH CON 162
16} RETURN MP/M VERSION NO 163
164 GET liST NO 164
Appendix F: The ZASM Macro Assembler
Examples in this book are written using Research Machines Ltd's Z80 macro
assembler ZASM.
The main differences between ZASM and Microsoft's M80 macro assembler,
for example, are
• ZASM allows you to have longer symbol names and your programs can
therefore be more readable than those generated by M80, which allows a
maximum of only 6 significant characters
• ZASM is a true Z80 assembler using Zilog mnemonic conventions
• ZASM will generate the following types of file directly
.COM files
.HEX files
.REL files
158
Index
159
160 INDEX
Drive INCLUDE 13
currently selected 62
default 62 Keyboard input 26, 28, 30, 114, 118
logged-in 62
logical 69 Librarian 13
status of 64, 124 Libraries 13
Link 13
Editor 10 Linker 13
End of file 47 List Output 24, 125
Error handling Listing me 12
with files 60 Loading a program 8, 10
Extent 45, 82 Logged-in drive 62
creating new 84 Logical drive 69
opening 84 Loose coupling 19
EXTERNAL 14,89
Machine code 6
FCB 40,45 Machine code routine
default 50 passing parameters 8 7
FDOS 3 Make File 39, 116
File 36 Memory
appending 43 displaying 78
binary 47 usage 3, 106
closing 40, Ill MP/M 156, 157
creating 39, 116
deleting 51 , 117 Open File 42, 126
end of 47 Operating system 1
opening 42, 126 Optimisation 8, 14
protection of 63
reading 41
renaming 51, 135 Page zero 20
sizeof 89,112 PASCAL 89
type of 40, 46 Passing parameters 87
Patching
File attributes 63, 64, 145
data 79
Filename 40, 46 programs 80
passing via keyboard 49 Portability, program 19
Filetype 40, 46 Pre-allocated block map 69
Firmware 5 Print String 24, 127
FSIZE 89, 96 Printer output 24, 125
Function code 22 Program
debugging 8, 14, 73
Get documentation 8, 9
Address of Allocation Map 68, 120 loading 8, 10
Allocation Address 68, 120 optimisation 8, 14
Console Status 28, 121 portability 19
Disc Parameters address 69, 122 running 8, 10
DPB address 69, 122 source me 10
1/0 Byte 123 testing 8, 14
Read-only Indicators 64, 124 Protecting discs and files 63
User Code 146 Punch Output 128
Global 14
Random access 55, 56, 82
High level languages and CP/M 6, 87 Raw data 30
INDEX 161