Lesson I: Overview of the
80x86 Family
The 80x86 family was first started in 1981 with the 8086 and the
newest member is the Pentium which was released thirteen years later in 1994.
They are all backwards compatible with each other but each new generation has
added features and more speed than the previous chip. Today there are very few
computers in use that have the 8088 and 8086 chips in them as they are very
outdated and slow. The number of 286 or 386 based computers around is declining
as today's software becomes more and more demanding. Even 486's are being
replaced by Pentiums. With the Pentium PRO and the MMX based CPUs Intel keeps
increasing performance and features.
Representation of numbers in binary
Before we begin to understand how to program in assembly it is
best to try to understand how numbers are represented in computers. Numbers are
stored in binary, base two. There are several terms which are used to describe
different size numbers and I will describe what these mean.
1 BIT: 0
One bit is the
simplest piece of data that exists. Its either a one or a zero.
1 NIBBLE: 0000
4 BITS
The nibble is
four bits or half a byte. Note that it has a maximum value of 15 (1111 = 15).
This is the basis for the hexadecimal (base 16) number system which is used as
it is far easier to understand. Hexadecimal numbers go from 1 to F and are
followed by a h to state that the are in hex. i.e. Fh = 15 decimal. Hexadecimal
numbers that begin with a letter are prefixed with a 0 (zero).
1 BYTE
00000000
2 NIBBLES
8 BITS
A byte is 8
bits or 2 nibbles. A byte has a maximum value of FFh (255 decimal). Because a
byte is 2 nibbles the hexadecimal representation is two hex digits in a row
i.e. 3Dh. The byte is also that size of the 8-bit registers which we will be
covering later.
1 WORD
0000000000000000
2 BYTES
4 NIBBLES
16 BITS
A word is two
bytes that are stuck together. A word has a maximum value of FFFFh (65,536).
Since a word is four nibbles, it is represented by four hex digits. This is the
size of the 16-bit registers.
Registers
Registers are
a place in the CPU where a number can be stored and manipulated. There are
three sizes of registers: 8-bit, 16-bit and on 386 and above 32-bit. There are
four different types of registers; general purpose registers, segment
registers, index registers and stack registers. Firstly here are descriptions
of the main registers. Stack registers and segment registers will be covered
later.

General Purpose Registers
General Purpose Registers
These are 16-bit registers. There are four general purpose
registers; AX, BX, CX and DX. They are split up into 8-bit registers. AX is
split up into AH which contains the high byte and AL which contains the low
byte. On 386's and above there are also 32-bit registers, these have the same names
as the 16-bit registers but with an 'E' in front i.e. EAX. You can use AL, AH,
AX and EAX separately and treat them as separate registers for some tasks.
If AX
contained 24689 decimal:
|
AH |
AL |
|
01100000 |
01110001 |
AH would be 96
and AL would be 113. If you added one to AL it would be 114 and AH would be
unchanged. SI, DI, SP and BP can also be used as general purpose registers but
have more specific uses. They are not split into two halves.
Index Registers
These are sometimes called pointer registers and they are 16-bit
registers. They are mainly used for string instructions. There are three index
registers SI (source index), DI (destination index) and IP (instruction
pointer). On 386's and above there are also 32-bit index registers: EDI and
ESI. You can also use BX to index strings.
IP is a index
register but it can't be manipulated directly as it stores the address of the
next instruction.
Stack registers
BP and SP are stack registers and are used when dealing with the
stack. They will be covered when we talk about the stack later on.
Segments and offsets
The original designers of the 8088 decided that nobody will ever
need to use more that one megabyte of memory so they built the chip so it
couldn't access above that. The problem is to access a whole megabyte 20 bits
are needed. Registers only have 16 bits and they didn't want to use two because
that would be 32 bits and they thought that this would be too much for anyone.
They came up with what they thought was a clever way to solve this problem: segments
and offsets. This is a way to do the addressing with two registers but not 32
bits.
OFFSET =
SEGMENT * 16
SEGMENT = OFFSET / 16 (the lower 4 bits are lost)
One register
contains the segment and another register contains the offset. If you put the
two registers together you get a 20-bit address.
|
SEGMENT |
0010010000010000---- |
|
OFFSET |
----0100100000100010 |
|
20-bit Address |
00101000100100100010 |
DS stores the
Segment and SI stores the offset. As they are both 16 bits long the addresses
overlap. This is how DS:SI is used to make a 20 bit address. The segment is in
DS and the offset is in SI. The standard notation for a Segment/Offset pair is:
SEGMENT:OFFSET
Segment
registers are: CS, DS, ES, SS. On the 386+ there are also FS and GS.
Offset
registers are: BX, DI, SI, BP, SP, IP. In 386+ protected mode, ANY general
register (not a segment register) can be used as an Offset register. (Except
IP, which you can't manipulate directly).
If you are now
thinking that assembly must be really hard and you don't understand segments
and offsets at all then don't worry. I didn't understand them at first but I
struggled on and found out that they were not so hard to use in practice.
The Stack
As there are only six registers that are used for most operations,
you're probably wondering how do you get around that. It's easy. There is
something called a stack which is an area of memory which you can save and
restore values to.
This is an
area of memory that is like a stack of plates. The last one you put on is the
first one that you take off. This is sometimes referred to as Last On First Off
(LOFO) or Last In First Out (LIFO).

How the stack is organised
If another
piece of data is put on the stack it grows downwards. As you can see the stack
starts at a high address and grows downwards. You have to make sure that you
don't put too much data in the stack or it will overflow.
There are a lot of instructions in assembly but there are only
about twenty that you have to know and will use very often. Most instructions
are made up of three characters and have an operand then a comma then another
operand. For example to put a data into a register you use the MOV instruction.
mov
ax,10 ; put 10 into ax
mov
bx,20 ; put 20 into bx
mov
cx,30 ; put 30 into cx
mov
dx,40 ; put 40 into dx
Notice that in
assembler anything after a ; (semicolon) is ignored. This is very useful for
commenting your code.
Push and Pop: Two Instructions to use the
Stack
You know about the stack but not how to put data in an out of it.
There are two simple instructions that you need to know: push and pop. Here is
the syntax for
their use:
PUSH: Puts a piece of data onto the top of the
stack
Syntax:
push
data
POP: Puts the piece of data from the top of the
stack into a specified register or variable.
Syntax:
pop
register (or variable)
This example
of code demonstrates how to use the push and pop instructions
push cx ; put cx on the stack
push ax ; put ax on the stack
pop cx ; put value from stack
into cx
pop ax ; put value from
stack into ax
Notice that
the values of CX and AX will be exchanged. There is an instruction to exchange
two registers: XCHG, which would reduce the previous fragment to "xchg
ax,cx".
Types of Operand
There are three types of operands in assembler: immediate,
register and memory. Immediate is a number which will be known at compilation
and will always be the same for example '20' or 'A'. A register operand is any
general purpose or index register for example AX or SI. A memory operand is a
variable which is stored in memory which will be covered later.
Some Instructions that you will need to
know
This is a list of some important instructions that you need to
know before you can understand or write assembly programs.
MOV: moves a value from one place to another.
Syntax:
MOV
destination, source
for example:
mov
ax,10 ; moves an
immediate value into ax
mov
bx,cx ; moves value
from cx into bx
mov
dx,Number ; moves the value of
Number into dx
INT: calls a DOS or BIOS function which are
subroutines to do things that we would rather not write a function for e.g.
change video mode, open a file etc.
Syntax:
INT
interrupt number
For example:
int 21h ; Calls DOS service
int 10h ; Calls the Video BIOS interrupt
Most interrupts
have more than one function, this means that you have to pass a number to the
function you want. This is usually put in AH. To print a message on the screen
all you need to do is this:
mov ah,9
; subroutine number 9
int 21h ; call the interrupt
But first you
have to specify what to print. This function needs
DS:DX to be a
far pointer to where the string is. The string has to be terminated with a
dollar sign ($). This would be easy if DS could be manipulated directly, to get
round this we have to use AX.
This example
shows how it works:
mov
dx,OFFSET Message ; DX contains
offset of message
mov
ax,SEG Message ; AX
contains segment of message
mov
ds,ax ;
DS:DX points to message
mov ah,9
; function 9 -
display string
int 21h ; call dos service
The words
OFFSET and SEG tell the compiler that you want the segment or the offset of the
message put in the register not the contents of the message. Now we know how to
set up the code to display the message we need to declare the message. In the
data segment we declare the message like this:
Message
DB "Hello World!$"
Notice that
the string is terminated with an dollar sign. What does 'DB' mean? DB is short
for declare byte and the message is an array of bytes (an ASCII character takes
up one byte). Data can be declared in a number of sizes: bytes (DB), words (DW)
and double words (DD). You don't have to worry about double words at the moment
as you need a 32-bit register, such as EAX, to fit them in.
Here are some
examples of declaring data:
Number1
db ?
Number2
dw ?
The question
mark (?) on the end means that the data isn't initialised i.e. it has no value
in to start with. That could as easily be written as:
Number1
db 0
Number2
dw 1
This time
Number1 is equal to 0 and Number2 is equal to 1 when you program loads. Your
program will also be three bytes longer.
If you declare
a variable as a word you cannot move the value of this variable into a 8-bit
register and you can't declare a variable as a byte and move the value into a
16-bit register. For examples:
mov
al,Number1 ; ok
mov
ax,Number1 ;
error
mov
bx,Number2 ; ok
mov
bl,Number2 ;
error
All you have
to remember is that you can only put bytes into 8-bit registers and words into
16-bit registers.
Now that you
know some basic instructions and a little about data it is time that we looked
at a full assembly program which can be compiled.
Listing
1: 1STPROG.ASM
; This
is a simple program which displays "Hello World!"
; on the
screen.
.model
small
.stack
.data
Message
db "Hello World!$" ; message
to be display
.code
start:
mov
dx,OFFSET Message ; offset of
Message is in DX
mov
ax,SEG Message ; segment
of Message is in AX
mov
ds,ax ;
DS:DX points to string
mov ah,9
; function 9 -
display string
int 21h ; call dos service
mov
ax,4c00h ;
return to dos DOS
int 21h
END
start ;end
here
These are some instructions to compile and link programs. If you
have a compiler other than TASM or A86 then see your instruction manual.
Turbo Assembler
tasm
file.asm
tlink
file [/t]
The /t switch makes a .COM file. This will only work if the memory
model is declared as tiny in the source file.
A86
a86
file.asm
This will
compile your program to a .COM file. It doesn't matter what the memory model
is.
Lesson 3: Making things easier
The way we entered the address of the message we wanted to print
was a bit cumbersome. It took three lines and it isn't the easiest thing to
remember.
mov
dx,OFFSET MyMessage
mov
ax,SEG MyMessage
mov
ds,ax
We can replace all this with just one line. This makes the code
easier to read and it easier to remember.
mov
dx,OFFSET MyMessage
To make this work at the beginning of your code add these lines:
mov
ax,@data
mov
ds,ax
Note: for A86 you need to change the first line to:
mov
ax,data
This is because all the data in the segment has the same SEG
value. Putting this in DS saves us reloading this every time we want to use
another thing in the same segment.
Keyboard Input
We are going to use interrupt 16h, function 00h to read the
keyboard. this gets a key from the keyboard buffer. If there isn't one, it
waits until there is. It returns the SCAN code in AH and the ASCII translation
in AL.
xor
ah,ah ; function 00h
- get character
int 16h ; interrupt 16h
All we need to worry about for now is the ascii value which is in
AL.
Note: XOR
performs a Boolean Exclusive OR. It is commonly used to erase a register or
variable.
Printing a Character
The problem is that we have the key that has been pressed in ah.
How do we display it? We can't use function 9h because for that we need to have
already defined the string which has to end with a dollar sign. this is what we
do instead:
; after
calling function 00h of interrupt 16h
mov
dl,al ; move al (ascii code) into dl
mov
ah,02h ; function 02h of
interrupt 21h
int 21h ; call interrupt 21h
If you want to save the value of AH then push AX before and pop it
afterwards.
Control Flow
In assembly there is a set of commands for control flow like in
any other language. Firstly the most basic command:
jmp
label
All this does it to move to the label specified and start
executing the code there. For example:
jmp
ALabel
.
.
.
ALabel:
What do we do if we want to compare something? We have just got a
key from the user but we want to do something with it. Lets print something out
if it is equal to something else. How do we do that? It is easy. We use the
jump on condition commands.
Firstly you
compare the operand with the data and then use the correct command.
cmp ax,3 ; is AX = 3?
je
correct ; yes
Jump on Condition Instructions
|
JAJumps if the first number was above the second number |
|
|
JAE |
same as above, but will also jump if they are equal |
|
JB |
jumps if the first number was below the second |
|
JBE |
Same as above, but will also jump if they are equal |
|
JNA |
jumps if the first number was NOT above (JBE) |
|
JNAE |
jumps if TDe first number was NOT above or TDe same as (JNB) |
|
JNB |
jumps if the first number was NOT below (JAE) |
|
JNBE |
jumps if the first number was NOT below or the same as (JA) |
|
JZ |
jumps if the two numbers were equal |
|
JE |
same as JZ, just a different name |
|
JNZ |
jumps if the two numbers are NOT equal |
|
JNE |
same as above |
|
JC |
jump if carry flag is set |
Note: the jump
can only be a maximum of 127 bytes in either direction.
CMP: compare a value
Syntax:
CMP
register or variable, value
jxx
destination
An example of this is:
cmp
al,'Y' ; compare the value in al with Y
je
ItsYES ; if it is equal then jump to
ItsYES
Every instruction takes up a certain amount of code space. You
will get a warning if you try and jump over 127 bytes in either direction from
the compiler. You can solve this by changing a sequence like this:
cmp
ax,10 ; is AX 10?
je done ; yes, lets finish
to something like this:
cmp
ax,10 ; is AX 10?
jne
notdone ; no it is not
jmp done
; we are now done
notdone:
This solves the problem but you may want to think about reordering
your code or using procedures if this happens often.
Now we are
going to look at a program which demonstrates input, output and control flow.
Listing
2: PROGFLOW.ASM
; a
program to demonstrate program flow and input/output
.model
tiny
.code
org 100h
start:
mov
dx,OFFSET Message ; display a
message on the screen
mov ah,9
; using function 09h
int 21h ; of interrupt 21h
mov
dx,OFFSET Prompt ; display a
message on the screen
mov ah,9
; using function 09h
int 21h ; of interrupt 21h
jmp
First_Time
Prompt_Again:
mov
dx,OFFSET Another ; display a
message on the screen
mov ah,9
; using function 09h
int 21h ; of interrupt 21h
First_Time:
mov
dx,OFFSET Again ; display
a message on the screen
mov ah,9
; using function 09h
int 21h ; of interrupt 21h
xor
ah,ah ;
function 00h of
int 16h ; interrupt 16h gets a
character
mov
bl,al ; save to bl
mov
dl,al ; move al to dl
mov
ah,02h ;
function 02h - display character
int 21h ; call DOS service
cmp
bl,'Y' ;
is al=Y?
je
Prompt_Again ; if yes then display it
again
cmp
bl,'y' ;
is al=y?
je
Prompt_Again ; if yes then display it
again
theEnd:
mov
dx,OFFSET GoodBye ; print goodbye
message
mov ah,9
; using function 9
int 21h ; of interrupt 21h
mov
ah,4Ch ;
terminate program
int 21h
.DATA
CR equ
13 ;
enter
LF equ
10 ;
line-feed
Message
DB "A Simple Input/Output Program$"
Prompt DB CR,LF,"Here is your first
prompt.$"
Again DB CR,LF,"Do you want to be prompted
again? $"
Another
DB CR,LF,"Here is another prompt!$"
GoodBye
DB CR,LF,"Goodbye then."
end
start
This is just a list of some basic assembly instructions that are
very important and are used often.
ADD: Add the contents of one number to another
Syntax:
ADD
operand1,operand2
This adds operand2 to operand1. The answer is stored in operand1. Immediate
data cannot be used as operand1 but can be used as operand2.
SUB: Subtract one number from another
Syntax:
SUB
operand1,operand2
This subtracts operand2 from operand1. Immediate data cannot be
used as operand1 but can be used as operand2.
MUL: Multiplies two unsigned integers (always
positive)
IMUL: Multiplies two signed integers (either positive or negitive)
Syntax:
MUL
register or variable
IMUL
register or variable
This multiples AL or AX by the register or variable given. AL is
multiplied if a byte sized operand is given and the result is stored in AX. If
the operand is word sized AX is multiplied and the result is placed in DX:AX.
On a 386, 486
or Pentium the EAX register can be used and the answer is stored in EDX:EAX.
DIV: Divides two unsigned integers (always
positive)
IDIV: Divides two signed integers (either positive or negitive)
Syntax:
DIV
register or variable
IDIV
register or variable
This works in the same way as MUL and IMUL by dividing the number
in AX by the register or variable given. The answer is stored in two places. AL
stores the answer and the remainder is in AH. If the operand is a 16 bit
register than the number in DX:AX is divided by the operand and the answer is
stored in AX and remainder in DX.
Introduction to Procedures
In assembly a procedure is the equivalent to a function in C or
Pascal. A procedure provides a easy way to encapsulate some calculation which
can then be used without worrying how it works. With procedures that are
properly designed you can ignore how a job is done.
This is how a
procedure is defined:
PROC
AProcedure
.
. ; some
code to do something
.
ret ; if this is
not here then your computer will crash
ENDP
AProcedure
It is equally easy to run a procedure all you need to do is this:
call
AProcedure
This next program is an example of how to use a procedure. It is
like the first example we looked at, all it does is print "Hello
World!" on the screen.
Listing
3: SIMPPROC.ASM
; This
is a simple program to demonstrate procedures. It should
; print
Hello World! on the screen when ran.
.model
tiny
.code
org 100h
Start:
call
Display_Hi ; Call the procedure
mov
ax,4C00h ;
return to DOS
int 21h
Display_Hi
PROC
mov
dx,OFFSET HI
mov ah,9
int 21h
ret
Display_Hi
ENDP
HI DB
"Hello World!" ;
define a message
end
Start
Procedures that pass parameters
Procedures wouldn't be so useful unless you could pass parameters
to modify or use inside the procedure. There are three ways of doing this and I
will cover all three methods: in registers, in memory and in the stack.
There are
three example programs which all accomplish the same task. They print a square
block (ASCII value 254) in a specified place.
The sizes of
the files when compiled are: 38 for register, 69 for memory and 52 for stack
(in bytes).
In registers
The advantages of this is that it is easy to do and is fast. All
you have to do is to is move the parameters into registers before calling the
procedure.
Listing
4: PROC1.ASM
; this a
procedure to print a block on the screen using
;
registers to pass parameters (cursor position of where to
; print
it and colour).
.model
tiny
.code
org 100h
Start:
mov dh,4
; row to print
character on
mov dl,5
; column to print character on
mov
al,254 ; ascii value of
block to display
mov bl,4
; colour to display character
call
PrintChar ; print our
character
mov
ax,4C00h ; terminate program
int 21h
PrintChar
PROC NEAR
push bx ; save registers to be destroyed
push cx
xor
bh,bh ; clear bh -
video page 0
mov ah,2
; function 2 - move cursor
int 10h ; row and col are already in dx
pop bx ; restore bx
xor
bh,bh ; display page -
0
mov ah,9
; function 09h write char & attrib
mov cx,1
; display it once
int 10h ; call bios service
pop cx ; restore registers
ret ; return to
where it was called
PrintChar
ENDP
end
Start
Passing through memory
The advantages of this method is that it is easy to do but it
makes your program larger and can be slower.
To pass
parameters through memory all you need to do is copy them to a variable which
is stored in memory. You can use a variable in the same way that you can use a
register but commands with registers are a lot faster.
Listing
5: PROC2.ASM
; this a
procedure to print a block on the screen using memory
; to
pass parameters (cursor position of where to print it and
;
colour).
.model
tiny
.code
org 100h
Start:
mov
Row,4 ; row to print
character
mov
Col,5 ; column to
print character on
mov
Char,254 ; ascii value of
block to display
mov
Colour,4 ; colour to display
character
call
PrintChar ; print our
character
mov
ax,4C00h ; terminate program
int 21h
PrintChar
PROC NEAR
push ax
cx bx ; save registers to be
destroyed
xor
bh,bh ; clear bh -
video page 0
mov ah,2
; function 2 - move cursor
mov
dh,Row
mov
dl,Col
int 10h ; call Bios service
mov
al,Char
mov
bl,Colour
xor
bh,bh ; display page -
0
mov ah,9
; function 09h write char & attrib
mov cx,1
; display it once
int 10h ; call bios service
pop bx
cx ax ; restore registers
ret ; return to
where it was called
PrintChar
ENDP
;
variables to store data
Row db ?
Col db ?
Colour
db ?
Char db ?
end
Start
Passing through Stack
This is the most powerful and flexible method of passing
parameters the problem is that it is more complicated.
Listing
6: PROC3.ASM
; this a
procedure to print a block on the screen using the
; stack
to pass parameters (cursor position of where to print it
; and
colour).
.model
tiny
.code
org 100h
Start:
mov dh,4
; row to print string
on
mov dl,5
; column to print string on
mov
al,254 ; ascii value of
block to display
mov bl,4
; colour to display character
push dx
ax bx ; put parameters onto
the stack
call
PrintString ; print our string
pop bx
ax dx ;restore registers
mov
ax,4C00h ;terminate program
int 21h
PrintString
PROC NEAR
push bp ; save bp
mov
bp,sp ; put sp into bp
push cx ; save registers to be destroyed
xor
bh,bh ; clear bh -
video page 0
mov ah,2
; function 2 - move cursor
mov
dx,[bp+8] ; restore dx
int 10h ; call bios service
mov
ax,[bp+6] ; character
mov
bx,[bp+4] ; attribute
xor
bh,bh ; display page -
0
mov ah,9
; function 09h write char & attrib
mov cx,1
; display it once
int 10h ; call bios service
pop cx ; restore registers
pop bp
ret ; return to
where it was called
PrintString
ENDP
end
Start

This shows the stack for a procedure with two parameters
To get a
parameter from the stack all you need to do is work out where it is. The last
parameter is at BP+2 and then the next and BP+4.
What are "Memory Models"?
We have been using the .MODEL directive to specify what type of
memory model we use, but what does this mean?
Syntax:
.MODEL MemoryModel
Where
MemoryModel can be SMALL, COMPACT, MEDIUM, LARGE, HUGE, TINY or FLAT.
Tiny
This means that there is only one segment for
both code and data. This type of program can be a .COM file.
Small
This means that by default all code is
place in one segment and all data declared in the data segment is also placed
in one segment. This means that all procedures and variables are addressed as
NEAR by pointing at offsets only.
Compact
This means that by default all elements of
code are placed in one segment but each element of data can be placed in its
own physical segment. This means that data elements are addressed by pointing
at both at the segment and offset addresses. Code elements (procedures) are
NEAR and variables are FAR.
Medium
This is the opposite to compact. Data
elements are NEAR and procedures are FAR.
Large
This means that both procedures and
variables are FAR. You have to point at both the segment and offset addresses.
Flat
This isn't used much as it is for 32 bit
unsegmented memory space. For this you need a DOS extender. This is what you
would have to use if you were writing a program to interface with a C/C++
program that used a DOS extender such as DOS4GW or PharLap.
Note: All
code examples given are for macros in Turbo Assembler.
Macros are
very useful for doing something that is done often but for which a procedure
can't be used. Macros are substituted when the program is compiled to the code
which they contain.
This is the
syntax for defining a macro:
Name_of_macro
macro
;
; a sequence of instructions
;
endm
These two examples are for macros that take away the boring job of
pushing and popping certain registers:
SaveRegs
macro
push ax
push bx
push cx
push dx
endm
RestoreRegs
macro
pop dx
pop cx
pop bx
pop ax
endm
Notice that the registers are popped in the reverse order to they
were pushed. To use a macro in you program you just use the name of the macro
as an ordinary instruction:
SaveRegs
; some
other instructions
RestoreRegs
This example shows how you can use a macro to save typing in. This
macro simply prints out a message to the screen.
OutMsg
macro SomeText
local
PrintMe,SkipData
jmp
SkipData
PrintMe
db SomeText,'$'
SkipData:
push ax
dx cs
mov
dx,OFFSET cs:PrintMe
mov ah,9
int 21h
pop cs
dx ax
endm
The only
problems with macros is that if you overuse them it leads to you program
getting bigger and bigger and that you have problems with multiple definition
of labels and variables. The correct way to solve this problem is to use the
LOCAL directive for declaring names inside macros.
Syntax:
LOCAL
name
Where name is the name of a local variable or label.
Macros with parameters
Another useful property of macros is that they can have
parameters. The number of parameters is only restricted by the length of the
line. Syntax:
Name_of_Macro
macro par1,par2,par3
;
;
commands go here
;
endm
This is an example that adds the first and second parameters and
puts the result in the third:
AddMacro
macro num1,num2,result
push ax ; save ax from being destroyed
mov
ax,num1 ; put num1 into ax
add
ax,num2 ; add num2 to it
mov
result,ax ; move answer into
result
pop ax ; restore ax
endm
Files can be opened, read and written to. DOS has some ways of
doing this which save us the trouble of writing our own routines. Yes, more
interrupts. Here is a list of helpful functions of interrupt 21h that deal with
files.
Note: Bits are
numbered from right to left.
Function 3Dh:
open file
Opens an existing file for reading, writing or appending on the specified drive
and filename.
INPUT:
AH = 3Dh
AL = bits 0-2 Access mode
000 = read only
001 = write only
010 = read/write
bits 4-6 Sharing mode (DOS 3+)
000 = compatibility mode
001 = deny all
010 = deny write
011 = deny read
100 = deny none
DS:DX = segment:offset of ASCIIZ pathname
OUTPUT:
CF = 0
function is succesful
AX = handle
CF = 1 error has occured
AX = error code
01h missing file sharing software
02h file not found
03h path not found or file does not exist
04h no handle available
05h access denied
0Ch access mode not permitted
What does
ASCIIZ mean? An ASCIIZ string like a ASCII string with a zero on the end
instead of a dollar sign.
Important:
Remember to save the file handle it is needed for later.
How to save the file handle
It is important to save the file handle because this is needed to
do anything with the file. Well how is this done? There are two methods we
could use: copy the file handle into another register and don't use that
register or copy it to a variable in memory.
The
disadvantages with the first method is that you will have to remember not to
use the register you saved it in and it wastes a register that can be used for
something more useful. We are going to use the second. This is how it is done:
FileHandle
DW 0 ; use this for saving the file
handle
.
.
.
mov
FileHandle,ax ; save the file handle
Function 3Eh: close file
Closes a file that has been opened.
INPUT:
AX = 3Eh
BX = file handle
OUTPUT:
CF = 0
function is successful
AX = destroyed
CF = 1 function not successful
AX = error code - 06h file not opened or unauthorised handle.
Important:
Don't call this function with a zero handle because that will close the
standard input (the keyboard) and you won't be able to enter anything.
Function 3Fh:
read file/device
Reads bytes
from a file or device to a buffer.
INPUT:
AH = 3Fh
BX = handle
CX = number of bytes to be read
DS:DX = segment:offset of a buffer
OUTPUT:
CF = 0
function is successful
AX = number of bytes read
CF = 1 an error has occurred
05h access denied
06h illegal handle or file not opened
If CF = 0 and AX = 0 then the file pointer was already at the end
of the file and no more can be read. If CF = 0 and AX is smaller than CX then
only part was read because the end of the file was reached or an error
occurred.
This function
can also be used to get input from the keyboard. Use a handle of 0, and it
stops reading after the first carriage return, or once a specified number of
characters have been read. This is a good and easy method to use to only let
the user enter a certain amount of characters.
Listing
7: READFILE.ASM
; a
program to demonstrate creating a file and then reading from
; it
.model
small
.stack
.code
mov
ax,@data ;
base address of data segment
mov
ds,ax ;
put this in ds
mov
dx,OFFSET FileName ; put address
of filename in dx
mov al,2
; access mode -
read and write
mov
ah,3Dh ;
function 3Dh -open a file
int 21h ; call DOS service
mov
Handle,ax ;
save file handle for later
jc
ErrorOpening ; jump if carry flag
set - error!
mov
dx,offset Buffer ;
address of buffer in dx
mov
bx,Handle ;
handle in bx
mov
cx,100 ;
amount of bytes to be read
mov
ah,3Fh ;
function 3Fh - read from file
int 21h ; call dos service
jc
ErrorReading ; jump if carry flag
set - error!
mov
bx,Handle ;
put file handle in bx
mov
ah,3Eh ;
function 3Eh - close a file
int 21h ; call DOS service
mov
cx,100 ;
length of string
mov
si,OFFSET Buffer ; DS:SI
- address of string
xor
bh,bh ;
video page - 0
mov
ah,0Eh ;
function 0Eh - write character
NextChar:
lodsb ;
AL = next character in string
int 10h ; call BIOS service
loop
NextChar
mov
ax,4C00h ;
terminate program
int 21h
ErrorOpening:
mov
dx,offset OpenError ; display an error
mov
ah,09h ;
using function 09h
int 21h ; call DOS service
mov
ax,4C01h ;
end program with an errorlevel =1
int 21h
ErrorReading:
mov
dx,offset ReadError ; display an error
mov
ah,09h ;
using function 09h
int 21h ; call DOS service
mov
ax,4C02h ;
end program with an errorlevel =2
int 21h
.data
Handle
DW ? ;
to store file handle
FileName
DB "C:\test.txt",0 ; file to
be opened
OpenError
DB "An error has occured(opening)!$"
ReadError
DB "An error has occured(reading)!$"
Buffer
DB 100 dup (?) ; buffer
to store data
END
Function 3Ch:
Create file
Creates a new
empty file on a specified drive with a specified pathname.
INPUT:
AH = 3Ch
CX = file attribute
bit 0 = 1 read-only file
bit 1 = 1 hidden file
bit 2 = 1 system file
bit 3 = 1 volume (ignored)
bit 4 = 1 reserved (0) - directory
bit 5 = 1 archive bit
bits 6-15 reserved (0)
DS:DX = segment:offset of ASCIIZ pathname
OUTPUT:
CF = 0
function is successful
AX = handle
CF = 1 an error has occurred
03h path not found
04h no available handle
05h access denied
Important: If
a file of the same name exists then it will be lost. Make sure that there is no
file of the same name. This can be done with the function below.
Function 4Eh:
find first matching file
Searches for
the first file that matches the filename given.
INPUT:
AH = 4Eh CX =
file attribute mask (bits can be combined)
bit 0 = 1 read only
bit 1 = 1 hidden
bit 2 = 1 system
bit 3 = 1 volume label
bit 4 = 1 directory
bit 5 = 1 archive
bit 6-15 reserved
DS:DX = segment:offset of ASCIIZ pathname
OUTPUT:
CF = 0
function is successful
[DTA] Disk Transfer Area = FindFirst data block
The DTA block:
Offset Size in bytes Meaning
0 21 Reserved
21 1 File attributes
22 2 Time last modified
24 2 Date last modified
26 4 Size of file (in bytes)
30 13 File name (ASCIIZ)
An example of checking if file exists:
File DB
"C:\file.txt",0 ; name of file that we want
mov
dx,OFFSET File ; address of filename
mov
cx,3Fh ; file mask 3Fh - any file
mov
ah,4Eh ; function 4Eh - find first file
int 21h ; call DOS service
jc
NoFile
; print
message saying file exists
NoFile:
;
continue with creating file
This is an example of creating a file and then writing to it.
Listing
8: CREATE.ASM
; This
example program creates a file and then writes to it.
.model
small
.stack
.code
mov
ax,@data ; base address of data
segment
mov
ds,ax ; put it in ds
mov
dx,offset StartMessage
mov
ah,09h
int 21h
mov
dx,offset FileName ; put offset
of filename in dx
xor cx,cx
; clear cx - make
ordinary file
mov
ah,3Ch ;
function 3Ch - create a file
int 21h ; call DOS service
jc
CreateError ;
jump if there is an error
mov
dx,offset FileName ; put offset
of filename in dx
mov al,2
; access mode -read
and write
mov
ah,3Dh ;
function 3Dh - open the file
int 21h ; call dos service
jc
OpenError ;
jump if there is an error
mov
Handle,ax ;
save value of handle
mov
dx,offset WriteMe ; address of
information to write
mov
bx,Handle ;
file handle for file
mov
cx,38 ;
38 bytes to be written
mov
ah,40h ;
function 40h - write to file
int 21h ; call dos service
jc
WriteError ;
jump if there is an error
cmp
ax,cx ;
was all the data written?
jne
WriteError ;
no it wasn't - error!
mov
bx,Handle ;
put file handle in bx
mov
ah,3Eh ;
function 3Eh - close a file
int 21h ; call dos service
mov
dx,offset EndMessage
mov
ah,09h
int 21h
ReturnToDOS:
mov
ax,4C00h ;
terminate program
int 21h
WriteError:
mov
dx,offset WriteMessage
jmp
EndError
OpenError:
mov
dx,offset OpenMessage
jmp
EndError
CreateError:
mov
dx,offset CreateMessage
EndError:
mov
ah,09h
int 21h
mov
ax,4C01h
int 21h
.data
CR equ
13
LF equ
10
StartMessage
DB "This program creates a file called NEW.TXT"
DB ,"on the C drive.$"
EndMessage
DB CR,LF,"File create OK, look at file to"
DB ,"be sure.$"
WriteMessage DB "An error has occurred
(WRITING)$"
OpenMessage DB "An error has occurred
(OPENING)$"
CreateMessage
DB "An error has occurred (CREATING)$"
WriteMe DB "HELLO, THIS IS A TEST, HAS IT
WORKED?",0
FileName
DB "C:\new.txt",0 ; name of file to open
Handle DW ? ;
to store file handle
END
This is an example of how to delete a file after checking it
exists:
Listing
9: DELFILE.ASM
; a
demonstration of how to delete a file. The file new.txt on
; c: is
deleted (this file is created by create.exe). We also
; check
if the file exits before trying to delete it
.model
small
.stack
.data
CR equ
13
LF equ
10
File db "C:\new.txt",0
Deleted
db "Deleted file c:\new.txt$"
NoFile db "c:\new.txt doesn't exits -
exiting$"
ErrDel db "Can't delete file - probably
write protected$"
.code
mov
ax,@data
mov
ds,ax
mov
dx,OFFSET File ;
address of filename to look for
mov
cx,3Fh ;
file mask 3Fh - any file
mov ah,4Eh
;
function 4Eh - find first file
int 21h ; call dos service
jc
FileDontExist
mov
dx,OFFSET File ;
DS:DX points to file to be killed
mov
ah,41h ;
function 41h - delete file
int 21h ; call DOS service
jc
ErrorDeleting ; jump if there was
an error
mov
dx,OFFSET Deleted ; display
message
jmp
Endit
ErrorDeleting:
mov
dx,OFFSET ErrDel
jmp
Endit
FileDontExist:
mov
dx,OFFSET NoFile
EndIt:
mov ah,9
int 21h
mov
ax,4C00h ;
terminate program and exit to DOS
int 21h ; call DOS service
end
Using the FindFirst and FindNext Functions
Listing
10: DIRC.ASM
; this
program demonstrates how to look for files. It prints
; out
the names of all the files in the c:\drive and names of
; the
sub-directories
.model
small
.stack
.data
FileName
db "c:\*.*",0 ;file name
DTA db 128 dup(?) ;buffer to store the DTA
ErrorMsg
db "An Error has occurred - exiting.$"
.code
mov
ax,@data ; set up ds to be equal
to the
mov ds,a
; data segment
mov
es,ax ; also es
mov
dx,OFFSET DTA ; DS:DX
points to DTA
mov ah,1AH
;
function 1Ah - set DTA
int 21h ; call DOS service
mov
cx,3Fh ;
attribute mask - all files
mov
dx,OFFSET FileName ; DS:DX points
ASCIZ filename
mov
ah,4Eh ;
function 4Eh - find first
int 21h ; call DOS service
jc error
; jump if carry flag is set
LoopCycle:
mov
dx,OFFSET FileName ; DS:DX points
to file name
mov
ah,4Fh ;
function 4fh - find next
int 21h ; call DOS service
jc exit ; exit if carry flag is set
mov
cx,13 ;
length of filename
mov
si,OFFSET DTA+30 ; DS:SI
points to filename in DTA
xor
bh,bh ;
video page - 0
mov
ah,0Eh ;
function 0Eh - write character
NextChar:
lodsb ; AL = next
character in string
int 10h ; call BIOS service
loop
NextChar
mov
di,OFFSET DTA+30 ; ES:DI
points to DTA
mov
cx,13 ;
length of filename
xor
al,al ; fill with
zeros
rep
stosb ;
erase DTA
jmp
LoopCycle ; continue searching
error:
mov
dx,OFFSET ErrorMsg ; display
error message
mov ah,9
int 21h
exit:
mov
ax,4C00h ; exit to DOS
int 21h
end
In assembly there are some very useful instructions for dealing
with strings. Here is a list of the instructions and the syntax for using them:
MOV* Move String: moves byte, word or double
word at DS:SI to ES:DI
Syntax:
movsb ; move byte
movsw ; move word
movsd ; move double word
CMPS*
Compare string: compares byte, word or double word at DS:SI to ES:DI
Syntax:
cmpsb ; compare byte
cmpsw ; compare word
cmpsd ; compare double word
Note: This instruction is normally used with the REP prefix.
SCAS* Search string: search for AL, AX, or EAX
in string at ES:DI
Syntax:
scasb ; search for AL
scasw ; search for AX
scasd ; search for EAX
Note: This instruction is usually used with the REPZ or REPNZ
prefix.
REP Prefix for string instruction repeats instruction
CX times
Syntax:
rep
StringInstruction
STOS* Move
byte, word or double word from AL, AX or EAX to ES:DI
Syntax:
stosb ; move AL into ES:DI
stosw ; move AX into ES:DI
stosd ; move EAX into ES:DI
LODS* Move
byte, word or double word from DS:SI to AL, AX or EAX
Syntax:
lodsb ; move ES:DI into AL
lodsw ; move ES:DI into AX
lodsd ; move ES:DI into EAX
The next example demonstrates how to use these instructions.
Listing
11: STRINGS.ASM
.model
small
.stack
.code
mov
ax,@data ;
ax points to of data segment
mov
ds,ax ;
put it into ds
mov
es,ax ;
put it in es too
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Message1 ; ds:dx points
to message
int 21h ; call dos function
cld ;
clear direction flag
mov
si,OFFSET String1 ; make
ds:si point to String1
mov
di,OFFSET String2 ; make
es:di point to String2
mov
cx,18 ;
length of strings
rep
movsb ;
copy string1 into string2
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Message2 ; ds:dx points
to message
int 21h ; call dos function
mov
dx,OFFSET String1 ; display
String1
int 21h ; call DOS service
mov
dx,OFFSET Message3 ; ds:dx points
to message
int 21h ; call dos function
mov
dx,OFFSET String2 ; display
String2
int 21h ; call DOS service
mov
si,OFFSET Diff1 ; make
ds:si point to Diff1
mov
di,OFFSET Diff2 ; make
es:di point to Diff2
mov
cx,39 ;
length of strings
repz
cmpsb ;
compare strings
jnz
Not_Equal ;
jump if they are not the same
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Message4 ; ds:dx points
to message
int 21h ; call dos function
jmp
Next_Operation
Not_Equal:
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Message5 ; ds:dx points to
message
int 21h ; call dos function
Next_Operation:
mov
di,OFFSET SearchString ; make es:di
point to string
mov
cx,36 ;
length of string
mov
al,'H' ;
character to search for
repne
scasb ;
find first match
jnz
Not_Found
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Message6 ; ds:dx points
to message
int 21h ; call dos function
jmp
Lodsb_Example
Not_Found:
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Message7 ; ds:dx points
to message
int 21h ; call dos function
Lodsb_Example:
mov ah,9
; function 9 -
display string
mov
dx,OFFSET NewLine ; ds:dx points
to message
int 21h ; call dos function
mov
cx,17 ;
length of string
mov
si,OFFSET Message ; DS:SI -
address of string
xor
bh,bh ;
video page - 0
mov
ah,0Eh ;
function 0Eh - write character
NextChar:
lodsb ;
AL = next character in string
int 10h ; call BIOS service
loop
NextChar
mov
ax,4C00h ;
return to DOS
int 21h
.data
CR equ
13
LF equ
10
NewLine
db CR,LF,"$"
String1 db "This is a string!$"
String2 db 18 dup(0)
Diff1 db "This string is nearly the same as
Diff2$"
Diff2 db
"This string is nearly the same as Diff1$"
Equal1 db "The strings are equal$"
Equal2 db "The strings are not equal$"
Message db "This is a message"
SearchString
db "1293ijdkfjiu938uHello983fjkfjsi98934$"
Message1
db "Using String instructions example program.$"
Message2
db CR,LF,"String1 is now: $"
Message3
db CR,LF,"String2 is now: $"
Message4
db CR,LF,"Strings are equal!$"
Message5
db CR,LF,"Strings are not equal!$"
Message6
db CR,LF,"Character was found.$"
Message7
db CR,LF,"Character was not found.$"
end
How to find out the DOS Version
In many programs it is necessary to find out what the DOS version
is. This could be because you are using a DOS function that needs the revision
to be over a certain level.
Firstly this
method simply finds out what the version is.
mov
ah,30h ; function 30h -
get MS-DOS version
int 21h ; call DOS function
This function returns the major version number in AL and the minor
version number in AH. For example if it was version 4.01, AL would be 4 and AH
would be 01. The problem is that if on DOS 5 and higher
SETVER can
change the version that is returned. The way to get round this is to use this
method.
mov
ah,33h ; function 33h -
actual DOS version
mov
al,06h ; subfunction 06h
int 21h ; call interrupt 21h
This will only work on DOS version 5 and above so you need to
check using the former method. This will return the actual version of DOS even
if SETVER has changed the version. This returns the major version in BL and the
minor version in BH.
Multiple Pushes and Pops
You can push and pop more than one register on a line in TASM and
A86. This makes your code easier to understand.
push ax
bx cx dx ; save registers
pop dx
cx bx ax ; restore registers
When TASM (or A86) compiles these lines it translates it into
separate pushes an pops. This way just saves you time typing and makes it
easier to understand.
Note: To make
these lines compile in A86 you need to put commas (,) in between the registers.
The PUSHA/PUSHAD and POPA/POPAD
Instructions
PUSHA is a useful instruction that pushes all general purpose
registers onto the stack. It accomplishes the same as the following:
temp =
SP
push ax
push cx
push dx
push bx
push
temp
push bp
push si
push di
The main advantage is that it is less typing, a smaller instruction
and it is a lot faster. POPA does the reverse and pops these registers off the
stack. PUSHAD and POPAD do the same but with the 32-bit registers ESP, EAX,
ECX, EDX, EBX, EBP, ESI and EDI.
Using Shifts for faster Multiplication and
Division
Using MUL's and DIV's is very slow and should be only used when
speed is not needed. For faster multiplication and division you can shift
numbers left or right one or more binary positions. Each shift is to a power of
2. This is the same as the << and >> operators in C.
There are four
different ways of shifting numbers either left or right one binary position.
SHL Unsigned multiple by two
SHR Unsigned divide by two
SAR Signed divide by two
SAL same as SHL
The syntax for
all four is the same:
SHL
operand1,operand2
Note: The 8086 cannot have the value of opperand2 other than 1.
The 286/386 cannot have operand2 higher than 31.
Loops
Using Loop is a better way of making a loop then using JMP's. You
place the amount of times you want it to loop in the CX register and every time
it reaches the loop statement it decrements CX (CX-1) and then does a short
jump to the label indicated. A short jump means that it can only 128 bytes
before or 127 bytes after the LOOP instruction.
Syntax:
mov
cx,100 ; 100 times to
loop
Label:
.
.
.
Loop
Label: ; decrement CX and
loop to Label
This is exactly the same as the following piece of code without
using loop:
mov
cx,100 ; 100 times to
loop
Label:
dec cx ; CX = CX-1
jnz
Label ; continue until done
Which do you think is easier to understand? Using DEC/JNZ is
faster on 486's and above and it is useful as you don't have to use CX.
This works
because JNZ will jump if the zero flag has not been set. Setting CX to 0 will
set this flag.
How to use a debugger
This is a good time to use a debugger to find out what your
program is actually doing. I am going to demonstrate how to use Turbo Debugger
to check what the program is actually doing. First we need a program which we
can look at.
Listing
12: DEBUG.ASM
;
example program to demonstrate how to use a debugger
.model
tiny
.code
org 100h
start:
push ax ; save value of ax
push bx ; save value of bx
push cx ; save value of cx
mov
ax,10 ; first parameter
is 10
mov
bx,20 ; second
parameter is 20
mov cx,3
; third parameter is 3
Call
ChangeNumbers
pop cx ; restore cx
pop bx ; restore bx
pop ax ; restore dx
mov
ax,4C00h ; exit to dos
int 21h
ChangeNumbers
PROC
add
ax,bx ; adds number in
bx to ax
mul cx ; multiply ax by cx
mov
dx,ax ; return answer
in dx
ret
ChangeNumbers
ENDP
end
start
Now compile it to a .COM file and then type:
td name
of file
Turbo Debugger then loads. You can see the instructions that make
up your program.

For example
the first few lines of this program is shown as:
cs:0000
50 push ax
cs:0001
53 push bx
cs:0002
51 push cx
Some useful keys for Turbo Debugger:
F5 Size Window
F7 Next Instruction
F9 Run
ALT F4 Step Backwards
The numbers that are moved into the registers are different that
the ones that in the source code because they are represented in their hex form
(base 16) as this is the easiest base to convert to and from binary and that it
is easier to understand than binary also.
At the left of
this display there is a box showing the contents of the registers. At this time
all the main registers are empty. Now press F7 this means that the first line
of the program is run. As the first line pushed the AX register into the stack,
you can see that the stack pointer (SP) has changed. Press F7 until the line
which contains mov ax,000A is highlighted. Now press it again.
Now if you
look at the box which contains the contents of the registers you can see that
AX contains A. Press it again and BX now contains 14, press it again and CX
contains 3. Now if you press F7 again you can see that AX now contains 1E which
is A+14. Press it again and now AX contains 5A, 1E multiplied by 3. Press F7
again and you will see that DX now also contains 5A. Press it three more times
and you can see that CX, BX and AX are all set back to their original values of
zero.
I am going to cover some more ways of outputting text in text
modes. This first program is an example of how to move the cursor to display a
string of text where you want it to go.
Listing
12: TEXT1.ASM
.model
tiny
.code
org 100h
start:
mov
dh,12 ;
cursor col
mov
dl,32 ;
cursor row
mov
ah,02h ;
move cursor to the right place
xor
bh,bh ;
video page 0
int 10h ;
call bios service
mov
dx,OFFSET Text ;
DS:DX points to message
mov ah,9
;
function 9 - display string
int 21h ;
all dos service
mov
ax,4C00h ; exit to dos
int 21h
Text DB
"This is some text$"
end
start
This next example demonstrates how to write to the screen using
the file function 40h of interrupt 21h.
Listing
13: TEXT2.ASM
.model
small
.stack
.code
mov
ax,@data ;
set up ds as the segment for data
mov
ds,ax
mov
ah,40h ;
function 40h - write file
mov bx,1
; handle = 1 (screen)
mov
cx,17 ;
length of string
mov
dx,OFFSET Text ; DS:DX
points to string
int 21h ; call DOS service
mov
ax,4C00h ;
terminate program
int 21h
.data
Text DB
"This is some text"
end
The next program shows how to set up and call function 13h of
interrupt 10h - write string. This has the advantages of being able to write a
string anywhere on the screen in a specified colour but it is hard to set up.
Listing
14: TEXT3.ASM
.model
small
.stack
.code
mov
ax,@data ;
set up ds as the segment for data
mov
es,ax ;
put this in es
mov
bp,OFFSET Text ; ES:BP
points to message
mov
ah,13h ;
function 13 - write string
mov
al,01h ;
attrib in bl,move cursor
xor
bh,bh ;
video page 0
mov bl,5
; attribute -
magenta
mov
cx,17 ;
length of string
mov dh,5
;
row to put string
mov dl,5
; column to put
string
int 10h ; call BIOS service
mov
ax,4C00h ;
return to DOS
int 21h
.data
Text DB
"This is some text"
end
The next program demonstrates how to write to the screen using rep
stosw to put the writing in video memory.
Listing
15: TEXT4.ASM
.model
small
.stack
.code
mov
ax,0B800h ; segment of video
buffer
mov
es,ax ; put this into
es
xor
di,di ; clear di, ES:DI points to video
memory
mov ah,4
; attribute - red
mov
al,"G" ;
character to put there
mov
cx,4000 ; amount of times
to put it there
cld ; direction -
forwards
rep
stosw ; output
character at ES:[DI]
mov
ax,4C00h ; return to DOS
int 21h
end
The next program demonstrates how to write a string to video
memory.
Listing
15: DIRECTWR.ASM
; write
a string direct to video memory
.model
small
.stack
.code
mov
ax,@data
mov
ds,ax
mov
ax,0B800h ;
segment of video buffer
mov
es,ax ;
put this into es
mov ah,3
; attribute - cyan
mov
cx,17 ;
length of string to print
mov
si,OFFSET Text ;
DX:SI points to string
xor
di,di
Wr_Char:
lodsb ;
put next character into al
mov
es:[di],al ;
output character to video memory
inc di ;
move along to next column
mov
es:[di],ah ;
output attribute to video memory
inc di
loop
Wr_Char ;
loop until done
mov
ax,4C00h ;
return to DOS
int 21h
.data
Text DB
"This is some text"
end
It is left as an exercise to the reader to modify it so that only
one write is made to video memory.
Mode 13h
Mode 13h is only available on VGA, MCGA cards and above. The
reason that I am talking about this card is that it is very easy to use for
graphics because of how the memory is arranged.
First check that mode 13h is possible
It would be polite to tell the user if their computer cannot
support mode 13h instead of just crashing it computer without warning. This is
how it is done.
Listing
16: CHECK13.ASM
.model
small
.stack
.data
NoSupport
db "Mode 13h is not supported on this computer."
db ,"You need either a MCGA or VGA
video"
db ,"card/monitor.$"
Supported
db "Mode 13h is supported on this computer.$"
.code
mov
ax,@data ;
set up DS to point to data segment
mov
ds,ax ;
use ax
call
Check_Mode_13h ; check if
mode 13h is possible
jc Error
; if cf=1 there
is an error
mov ah,9
; function 9 -
display string
mov
dx,OFFSET Supported ; DS:DX points to message
int 21h ; call DOS service
jmp
To_DOS ;
exit to DOS
Error:
mov ah,9
; function 9 -
display string
mov
dx,OFFSET NoSupport ; DS:DX points to message
int 21h ; call DOS service
To_DOS:
mov
ax,4C00h ;
exit to DOS
int 21h
Check_Mode_13h
PROC ; Returns: CF = 1 Mode 13h
not possible
mov
ax,1A00h ;
Request video info for VGA
int 10h ; Get Display
Combination Code
cmp
al,1Ah ;
Is VGA or MCGA present?
je
Mode_13h_OK ; mode
13h is supported
stc ;
mode 13h isn't supported CF=1
Mode_13h_OK:
ret
Check_Mode_13h
ENDP
end
Just use this to check if mode 13h is supported at the beginning
of your program to make sure that you can go into that mode.
Setting the Video Mode
It is very simple to set the mode. This is how it is done.
mov
ax,13h ; set mode 13h
int 10h ; call BIOS service
Once we are in mode 13h and have finished what we are doing we
need to we need to set it to the video mode that it was in previously.
This is done
in two stages. Firstly we need to save the video mode and then reset it to that
mode.
VideoMode
db ?
.
.
mov
ah,0Fh ; function 0Fh - get current mode
int 10h ;
Bios video service call
mov
VideoMode,al ; save current mode
;
program code here
mov
al,VideoMode ; set previous video mode
xor
ah,ah ; clear ah - set mode
int 10h ;
call bios service
mov
ax,4C00h ; exit to dos
int 21h ;
call dos function
Now that we can get into mode 13h lets do something. Firstly lets
put some pixels on the screen.
Function 0Ch: Write Graphics Pixel
This makes a
colour dot on the screen at the specified graphics coordinates.
INPUT:
AH = 0Ch
AL = Color of the dot
CX = Screen column (x coordinate)
DX = Screen row (y coordinate)
OUTPUT:
Nothing except
pixel on screen.
Note: This
function performs exclusive OR (XOR) with the new colour value and the current
context of the pixel of bit 7 of AL is set.
This program
demonstrates how to plot pixels. It should plot four red pixels into the middle
of the screen.
Listing
17: PIXINT.ASM
;
example of plotting pixels in mode 13 using bios services -
; INT
10h
.model
tiny
.code
org 100h
start:
mov
ax,13 ; mode = 13h
int 10h ; call bios service
mov
ah,0Ch ; function 0Ch
mov al,4
; color 4 - red
mov
cx,160 ; x position = 160
mov
dx,100 ; y position = 100
int 10h ; call BIOS service
inc dx ; plot pixel
downwards
int 10h ; call BIOS service
inc cx ; plot pixel to
right
int 10h ; call BIOS service
dec dx ; plot pixel up
int 10h ; call BIOS service
xor
ax,ax ; function 00h - get a key
int 16h ; call BIOS service
mov ax,3
; mode = 3
int 10h ; call BIOS service
mov
ax,4C00h ; exit to DOS
int 21h
end
start
This method isn't too fast and we could make it a lot faster. How?
By writing direct to video memory. This is done quite easily.
The VGA segment
is 0A000h. To work out where each pixel goes you use this simple formula to get
the offset.
Offset = X + (
Y * 320 )
All we do is
to put a number at this location and there is now a pixel on the screen. The
number is what colour it is. There are two instructions that we can use to put
a pixel on the screen, firstly we could use stosb to put the value in AL to
ES:DI or we can use a new form of the MOV instruction like this:
mov
es:[di], color
Which should we use? When we are going to write pixels to the
screen we need to do so as fast as it is possible.
Instruction
Pentium
486 386 286
86
STOSB 3 5
4 3 11
MOV AL
to SEG:OFF 1 1
4 3 10
If you use the MOV method you may need to increment DI (which
STOSB does).
If we had a
program which used sprites which need to be continuously draw, erased and then
redraw you will have problems with flicker. To avoid this you can use a 'double
buffer'. This is another part of memory which you write to and then copy all
the information onto the screen.
Lesson 8 : Computer
Hardware Interfacing using Assembly Language
Parallel Ports
(0278H 0378H)
The original
IBM-PC's Parallel Printer Port had a total of 12 digital outputs
and 5 digital inputs accessed via 3 consecutive 8-bit ports in the
processor's I/O space.

25-way Female D-Type Connector
Below is a table of the "Pin Outs" of the
D-Type 25 Pin connector and the Centronics 34 Pin connector. The D-Type 25 pin
connector is the most common connector found on the Parallel Port of the
computer, while the Centronics Connector is commonly found on printers. The
IEEE 1284 standard however specifies 3 different connectors for use with the
Parallel Port. The first one, 1284 Type A is the D-Type 25 connector found on
the back of most computers. The 2nd is the 1284 Type B which is the 36 pin
Centronics Connector found on most printers.
IEEE 1284 Type C however, is a 36 conductor connector
like the Centronics, but smaller. This connector is claimed to have a better
clip latch, better electrical properties and is easier to assemble. It also
contains two more pins for signals which can be used to see whether the other
device connected, has power. 1284 Type C connectors are recommended for new
designs, so we can look forward on seeing these new connectors in the near
future.
|
Pin No (D-Type 25) |
Pin No (Centronics) |
SPP Signal |
Direction In/out |
Register |
Hardware Inverted |
|
1 |
1 |
nStrobe |
In/Out |
Control |
Yes |
|
2 |
2 |
Data 0 |
Out |
Data |
|
|
3 |
3 |
Data 1 |
Out |
Data |
|
|
4 |
4 |
Data 2 |
Out |
Data |
|
|
5 |
5 |
Data 3 |
Out |
Data |
|
|
6 |
6 |
Data 4 |
Out |
Data |
|
|
7 |
7 |
Data 5 |
Out |
Data |
|
|
8 |
8 |
Data 6 |
Out |
Data |
|
|
9 |
9 |
Data 7 |
Out |
Data |
|
|
10 |
10 |
nAck |
In |
Status |
|
|
11 |
11 |
Busy |
In |
Status |
Yes |
|
12 |
12 |
Paper-Out / Paper-End |
In |
Status |
|
|
13 |
13 |
Select |
In |
Status |
|
|
14 |
14 |
nAuto-Linefeed |
In/Out |
Control |
Yes |
|
15 |
32 |
nError / nFault |
In |
Status |
|
|
16 |
31 |
nInitialize |
In/Out |
Control |
|
|
17 |
36 |
nSelect-Printer / nSelect-In |
In/Out |
Control |
Yes |
|
18 - 25 |
19-30 |
Ground |
Gnd |
|
|
Table 1. Pin Assignments
of the D-Type 25 pin Parallel Port Connector.
The
above table uses "n" in front of the signal name to denote that the
signal is active low. e.g. nError. If the printer has occurred an error then
this line is low. This line normally is high, should the printer be functioning
correctly. The "Hardware Inverted" means the signal is inverted by
the Parallel card's hardware. Such an example is the Busy line. If +5v (Logic
1) was applied to this pin and the status register read, it would return back a
0 in Bit 7 of the Status Register.
The
output of the Parallel Port is normally TTL logic levels. The voltage levels
are the easy part. The current you can sink and source varies from port to
port. Most Parallel Ports implemented in ASIC, can sink and source around 12mA.
However these are just some of the figures taken from Data sheets, Sink/Source
6mA, Source 12mA/Sink 20mA, Sink 16mA/Source 4mA, Sink/Source 12mA. As you can
see they vary quite a bit. The best bet is to use a buffer, so the least
current is drawn from the Parallel Port.
PC parallel port
can be very useful I/O channel for connecting your own circuits to PC. The port
is very easy to use when you first understand some basic tricks. This document
tries to show those tricks in easy to understand way.
WARNING: PC
parallel port can be damaged quite easily if you make mistakes in the circuits
you connect to it. If the parallel port is integrated to the motherboard (like
in many new computers) repairing damaged parallel port may be expensive (in
many cases it it is cheaper to replace the whole motherborard than repair that
port). Safest bet is to buy an inexpensive I/O card which has an extra parallel
port and use it for your experiment. If you manage to damage the parallel port
on that card, replacing it is easy and inexpensive.
PC parallel port is
25 pin D-shaped female connector in the back of the computer. It is normally
used for connecting computer to printer, but many other types of hardware for
that port is available today.
Not all 25 are
needed always. Usually you can easily do with only 8 output pins (data lines)
and signal ground. I have presented those pins in the table below. Those output
pins are adequate for many purposes.
pin function 2 D0 3 D1 4 D2 5 D3 6 D4 7 D5 8 D6 9 D7
Pins
18,19,20,21,22,23,24 and 25 are all ground pins.
Those datapins are
TTL level output pins. This means that they put out ideally 0V when they are in
low logic level (0) and +5V when they are in high logic level (1). In real
world the voltages can be something different from ideal when the circuit is
loaded. The output current capacity of the parallel port is limited to only few
milliamperes.
Dn Out ------+ |+ Sourcing Load (up to 2.6 mA @ 2.4 v) |- Ground ------+
You can make simple
circuit for driving a small led through PC parallel port. The only components
needed are one LED and one 470 ohm resistors. You simply connect the diode and
resistor in series. The resistors is needed to limit the current taken from
parallel port to a value which light up acceptably normal LEDs and is still
safe value (not overloading the parallel port chip). In practical case the
output current will be few milliampres for the LED, which will cause a typical
LED to somewhat light up visibly, but not get the full brigtness.
Then you connect
the circuit to the parallel port so that one end of the circuit goes to one
data pin (that one you with to use for controlling that LED) and another one
goes to any of the ground pins. Be sure to fit the circuit so that the LED
positive lead (the longer one) goes to the datapin. If you put the led in the
wrong way, it will not light in any condition. You can connect one circuit to
each of the parallel port data pins. In this way you get eight software
controllable LEDs.
The software
controlling is easy. When you send out 1 to the datapin where the LED is
connected, that LED will light. When you send 0 to that same pin, the LED will
no longer light.
outp(0x378,n);
or
outportb(0x378,n);
Where
N is the data you want to output. The actual I/O port controlling command
varies from compiler to compiler because it is not part of standardized C
libraries.
# include<stdio.h>
main()
{
int n;
clrscr();
printf( Enter
a value:);
scanf(%d,&n);
outportb(0x378,n);
printf(The
equivalent binary no. is display in the led.);
getch();
}
You have to think
the value you give to the program as a binary number. Every bit of the binary
number control one output bit. The following table describes the relation of
the bits, parallel port output pins and the value of those bits.
Pin 2 3 4 5 6 7 8 9Bit D0 D1 D2 D3 D4 D5 D6 D7Value 1 2 4 8 16 32 64 128
For
example if you want to set pins 2 and 3 to logic 1 (led on) then you have to
output value 1+2=3. If you want to set on pins 3,5 and 6 then you need to
output value 2+8+16=26. In this way you can calculate the value for any bit
combination you want to output.