home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Oakland CPM Archive
/
oakcpm.iso
/
cpm
/
asmutl
/
meyertut.ark
/
MEYER03.TXT
< prev
next >
Wrap
Text File
|
1987-12-04
|
8KB
|
191 lines
CP/M Assembly Language
Part III: Calling BDOS
by Eric Meyer
Last time we learned about the structure of the 8080 CPU,
and wrote a simple little program to move around bytes (or words)
of data with it. We found, however, that we couldn't tell we had
done anything. Now we need to learn how to communicate with the
terminal, print messages, and get keyboard input.
1. The CALL and JMP instructions
It's good practice to divide assembler code into separate
modules. As you probably can imagine, once you have a few dozen
(or hundred) lines of code, things start looking disorganized.
Then, of course, you need to be able to run each module of
code in the proper sequence. By nature, the 8080 CPU just goes
along executing one instruction after another, as it finds them.
But there is a pair of instructions that cause it to go and
execute code at some other location instead. The JMP (jump)
instruction is like GOTO (a branch) in many languages; it simply
goes and begins executing code someplace else.
The CALL instruction, like GOSUB (a subroutine call), does
the same thing, but remembers where you were before, and will
return there when it finds a RET instruction.
Both JMP and CALL take an address (16-bit data value) for an
argument. This is almost always a label marking the beginning of
the code you want to execute. For example:
instr1
instr2
JMP LABEL
instr3
LABEL: instr4
instr5
actually will execute instructions 1, 2, 4, 5, skipping instr3.
Similarly,
instr1
CALL LABEL
instr2
RET
LABEL: instr3
instr4
RET
will execute the instructions in the order 1, 3, 4, 2.
As an exercise, you might want to go back to the byte mover
program of the last installment, and rewrite the instructions
that move a byte from source to destination as a subroutine,
which you could then CALL any number of times in sequence.
2. BDOS Calls
In Part I, I said that assembler differed from higher-level
languages in that there were no prepared subroutines for you to
call, you had to write every byte of code yourself.
Actually, this isn't strictly true.
CP/M has a number of routines in the BDOS (basic disk
operating system) which are designed to be called as subroutines,
and allow you to do useful things like send characters to and
from the terminal, read and write disk files, etc.
This is the only way to do these tasks without knowing a lot
of specific detail about (or duplicating a lot of the code in)
the BIOS (basic input/output system) that allows CP/M to run on
your particular computer.
In brief, the operating system has already been told how to
do these things -- all you have to do is ask it to do them.
There are several dozen "BDOS calls". At address 0005H in
memory, there is always a JMP instruction which leads into the
operating system.
Thus you make a BDOS call by setting up certain initial
values, and then CALLing address 0005H. First you need to decide
which BDOS function you want. Each has a code number, which has
to be loaded into the C register. Then, if necessary, you put
some additional information into the D-E registers. Then you CALL
0005H. The whole thing winds up looking like:
MVI C,xx ;put the BDOS function number here
LXI D,xxxx ;(may need some more data here)
CALL 0005H ;ask the BDOS to do it
Our immediate need is to communicate with a program. Here
are three BDOS calls to do the job.
Function 2 sends a character to the screen. You have to put
the desired ASCII code into the E register (the D register isn't
used here):
MVI C,2 ;console output function
MVI E,xx ;put the character you want here
CALL 0005H ;ask BDOS to do it
This could get tedious for a whole string. We could write a
subroutine to use this call repeatedly, but for now let's be
lazy.
Function 9 sends a whole string of characters to the screen.
You have to use the $ character to mark the end of the string,
and to put the address where the string starts into the D-E pair.
The following complete program, which you can assemble and run,
prints out "Hello world!":
ORG 0100H
MVI C,9 ;print string function
LXI D,STRING ;address of the string to print
CALL 0005H ;ask BDOS to do it
RET
STRING: DB 'Hello world!$'
END
Function 1 gets a character from the keyboard. It doesn't
require any other information; you simply:
MVI C,1 ;console input function
CALL 0005H ;ask BDOS to do it
and the BDOS will wait for a key to be pressed. The ASCII code of
the key will be returned to your program in the A register. The
following program will ask you a question and accept your
response:
ORG 0100H
MVI C,9 ;print string
LXI D,QUESTN ;(this one)
CALL 0005H
MVI C,1 ;get key
CALL 0005H
RET ;who cares?
QUESTN: DB 'Are you excited? (Y/N) $'
END
At the point where this program RETurns, the ASCII value of
the key you typed is in the A register. Sadly, we don't know how
to examine or test this yet, so there's no more to do for now.
3. It Talks, It . . .
Now we can rewrite our "byte mover" program from Part II, so
that it uses a neat subroutine to move each byte, and tells us
what it's doing. Let's call this BYTEMOV2.ASM:
BDOS EQU 0005H ;define the BDOS entry address
ORG 0100H ;code begins here
MVI C,9 ;BDOS print string fn
LXI D,HELLO ;i.d. message
CALL BDOS ;do it
MVI C,9
LXI D,DEST ;show the original string
CALL BDOS
LXI H,SOURCE ;point to source
LXI D,DEST ;point to destination
CALL MOVBYT ;move one byte
CALL MOVBYT ;move the second byte
MVI C,9
LXI D,DONE ;explain that it's changed
CALL BDOS
MVI C,9
LXI D,DEST ;show what it is now
CALL BDOS
RET ;all done, return.
;subroutine to move a byte
MOVBYT: MOV A,M ;fetch byte from memory
XCHG ;point to destination
MOV M,A ;store byte
XCHG ;point back to source
INX H ;increment source
INX D ;increment destination
RET ;all done
;data area... note "$" char so we can use BDOS 9
SOURCE: DB 'hi$' ;move bytes from here
DEST: DB '??$' ;to here
;messages
HELLO: DB 'Byte mover program 2-- string was: $'
DONE: DB ' and it is now: $'
END
When you assemble and run this program, you will know that
it actually did something; what you should of course see is:
A>bytemov2<cr>
Byte mover program 2 string was ?? and it is now hi. Note
how easy it would be to increase the string length -- just add in
the extra characters at SOURCE and DEST, and call MOVBYT a few
more times.
4. Coming Up . . .
In future installments we'll learn some more assembler
instructions, including arithmetic and conditional branches.