Вы находитесь на странице: 1из 16

Tutorial 1 - The 8086 Chip

Before beginning to write programs in assembler, you need to know a few things about the chip
for which you are writing the program. This tutorial will assume that all programs are being run
on the 8086 chip and as such, they will all run on any IBM compatible PC, as the 80386, 80486
and Pentium (I, II and III) chips all are designed to run 8086 code.

The 8086 chip uses registers for performing operations. It has The following registers

General Registers

 AX
 BX
 CX
 DX
Segment Registers
 CS
Points to start of the Code Segment
 DS
Points to start of the Data Segment
 ES
Extra Segment pointer
 SS
Points to start fo Stack Segment
Pointer Registers
 IP
Instruction Pointer
 SP
Stack Pointer
 BP
Base Pointer
Data Transfer
Registers
Source Index
 SI
Destination Index
 DI
 Set of flags

When writing long or complicated programs in assembler one would need to use all these, but
when using A86 for the most part one can forget about all but the first four. These 4 general
purpose registers can be used alone to perform most of the tasks one would want. A86 by default
assembles programs into .com files instead of .exe. Com programs are small programs, less than
64 kilobytes in size, the size of one segment in memory so in .com programs the data, code and
stack all fit into one segment. Hence there is no need for the four segment pointers CS, DS, ES
and SS as they are automatically set by the assembler to point to the only used segment - so
forget about them for a few years!

As for the last two sets of pointers, you can forget about them for a few years as well as they will
not be required when doing only simple assembler. The exception being possibly the flags
register, as we will be using the zero flag in some programs.

Now that we have limited ourselves to just four registers we can look at these more closely. The
four registers have names ending with X and starting with the first four letters of the alphebet, so
their names should not be hard to remember. Each of the register is 16 bits wide ( as the 8086
was a 16 bit processor ). However, as one will frequently be working with bytes, each of the
registers can be accessed one half at a time. To access the top half of a register simply replace the
X in the name by a H, for high, and to access the lower half replace the X by an L for low. So
CH is the top eight bits of the CX register and AL is the lower eight bits of the AX register.

Tutorial 2 - The MOV and INT Instructions


In this tutorial we will write our very first program in assembler and compile it. We will also
meet the two most basic instructions in assembler, from our point of view. These are the MOV
instruction which is used to transfer data, and the INT instruction which calls a DOS interrupt.

The MOV Instruction

The MOV instruction is the instruction which will appear more than any other in an assembler
program. All that is does is it copies a piece of data from one location to another. It is similar in
concept to the MOVE operator in the COBOL language, but it is used far more frequently. Here
are a few examples of MOV instructions.

MOV BX,AX ; This copies the contents of the AX register into the BX register.
MOV CH,DH ; This copies the top byte of the DX register into the top byte of the CX register.
MOV BH,DL ; This copies the bottom byte of the DX register into the top byte of BX.
MOV AH,12 ; This puts the value 12 decimal into the top half of the AX register.
MOV AH,0Ch ; This does the same as the above except that the number is given in hexadecimal.
; Hexadecimal numbers MUST begin with a digit and end with a "h".
MOV DL,"*" ; This puts the character "*" into DL (lower half of DX).
MOV DL,42 ; This does the same thing, as characters are stored as numbers. ( ASCII char 42 = "*" )

The above are all prefectly legal assembler statements ( notice that comments are preceded by a
";". This is the same as "//" in C++ or "*" in COBOL). In fact you could type the above
statements into a text file and it would assemble with A86. (If you do try to do this, do NOT run
the .com file generated as it will not terminate!). There are a number of things that you cannot do
with the MOV instruction:

MOV AX,BH ; Invalid operation, as you cannot move an 8 bit quantity to a 16 bit one.
MOV CH,BX ; Similar to above, you cannot put a 16 bit quantity into an 8 bit one.
MOV 12,DL ; You cannot put a value into the number 12. If you see this is a program what
; is probably meant is MOV DL,12 - put 12 into DL.
MOV DL,AL,CL ; You cannot have 3 operands!
MOV AH ; Neither can you have only 1! You must have exactly 2: destination and source
The INT Instruction

The INT instruction is the instruction which does the most work in any assembler program. What
it does is it calls a DOS interrupt (like a function) to perform a special task. When one wants to
read from the keyboard or disk or mouse, or write to the screen, one uses an interrupt. When
using DOS, there are a over 50 different interrupts available. Of these the programmer will only
use a few. Each interrupt though, has a number of sub-functions which select the individual task
that the function has to do. For example, there is just one interrupt for accessing the mouse INT
33h, but there are separate subfunctions available to see if a BUTTON has been clicked, to see
how far the mouse has moved, to display or hide a mouse pointer etc. An assembler
programmer's best friend is an list of interrupts and their subfunctions, as whenever you want to
do some input or output you can simply go down the list until you find the interrupt subfunction
which does what you want, and use it. I, being the helpful chap that I am, have provided a brief
interrupt list here which should be sufficient for most of your needs.

By now I'm sure you are asking, how do I use these wonderful interrupts? Thankfully, it is not
difficult. One goes down the list until one find the appropriate interrupt subfunction and moves
the subfunction number to AH. One then looks at the input required by the function and moves
the appropriate values to the registers stated.

Example: I you go down the list you will see that interrupt 21h (The DOS interrupt),
subfunction 2, outputs a character. So let us write a code extract which will output the character
"!".

MOV AH,02 ; To select subfunction 2, move the appropriate number, 2, to AH.


MOV DL,"!" ; In the interrupt list, it says that the character to output should be
; in register DL. So we move the character to DL.
INT 21h ; Finally when all the registers are set as required, we call the interrupt.

Perhaps the most inportant of all the interrupt subfunctions is INT 21h, subfunction 4Ch. This is
the function which terminates the program and returns the user to the operating system. Every
assembler program you write should end with the following lines.

MOV AH,4Ch ; Select the subfunction


MOV AL,00 ; Select a return value (optional but recommended)
INT 21h ; Call the DOS interrupt.

Now we can write compile and run our first assembler program. Using a text editor, MS-DOS
Edit, or Windows Notepad, type in the following lines ( same as above ) and save it as prog1.asm
in the same directory as A86:

MOV AH,02 ; Function to output a char


MOV DL,"!" ; Character to output
INT 21h ; Call the interrupt to output "!"
MOV AH,04Ch ; Select exit function
MOV AL,00 ; Return 0
INT 21h ; Call the interrupt to exit
At the DOS command line then type in the following command: "A86 prog1.asm".

Tutorial 3 - Labels and Jumps.


In this, third tutorial we will meet the idea of labels in assembler and how to use them for
conditional execution. For this section we will meet 4 new instructions, and encounter the zero
flag for the first time.

Labels

Labels are names which are used to identify various pieces of code. In effect they give a name to
a particular location in an assembler program. In assembler, a label consists of a name followed
immediately by a colon. Any letter or number both upper and lower case as well as the
underscore, may be used in label names. Names are not case sensitive, like the rest of assembler
(mov Ah,dL is the same as MOV AH,DL). The following are all valid label names:

start:

loop1:

read_a_key:

ANY_Label_3:

L1:

To put a label in your code is simple, just put in it the middle of your code with instructions
either side of it. Remember, though, no two labels can have the same name, and reserved words
cannot be used as label names eg. you can't have a label "mov:", use "move:" instead. Below is
our program from Tut2 with labels inserted:

; This point in the code is now called "Output_char".


Output_char: ; To select subfunction 2, move the appropriate number, 2, to AH.
MOV AH,02 ; In the interrupt list, it says that the character to output
MOV DL,"!" should be
; in register DL. So we move the character to DL.
INT 21h ; Finally when all the registers are set as required, we call the
interrupt.

Exit: ; Labels should be relevant to the code after them.


MOV AH,4Ch ; Select the subfunction.
MOV AL,00 ; Select a return value (optional but recommended).
INT 21h ; Call the DOS interrupt.

Jumps and Conditional Execution

The principle usage of labels in assembler is to perform conditional execution, the equivalent of
IF statements in most high level languages. In assembler one has to have two instructions to have
conditional execution. The first instruction is nearly always the CMP instruction which compares
two values. If they are equal one of the CPU's flags, known as the zero flag is set (Basically the
CMP instruction gets the difference between two quantities and see's if it is zero or not). The
second instruction necessary for conditional execution is the JMP instruction or a derivitive
thereof. These instructions shall now be examined individually.

JMP

The JMP instruction in assembler causes the program execution to continue from a certain point.
The JMP instruction has just one operand which is either the address of the point in the program
where execution is to start (very very rare) or a label. Consider the following piece of code:

start:
mov ah,08
; sub-function 8 - read a character
int 21h
; call interrupt
mov bl,al
; save the key read in bl.
JMP output
; a jump instruction causes the program to start running now
from the
mov ah,01
; output label, skipping out the next two lines.
int 21h
; These never get executed...
output:
mov
; execution continues here
dl,"("
; output a "("
mov ah,02
int 21h
mov dl,bl
; Then output the character read still held in bl
int 21h
mov
; Last output a ")"
dl,")"
int 21h
exit:
; Terminate the program
mov
ah,4ch
mov al,00
int 21h

The code executes in a linear fashion until it gets to the jmp command from which it execution
continues with the statement "mov dl,bl". The intermediate two lines never get executed.
JZ

The JZ instruction is a form of the JMP instruction except that the jump occurs only when the
zero flag is set. The instruction is read as "Jump if Zero".

JNZ

The JNZ instruction is the opposite of the JZ instruction in that the jump occurs when the zero
flag is NOT set. It is read as "Jump if Not Zero".

CMP

The CMP(compare) instruction is used two compare two values and to act upon the result of that
comparison. For now, we shall concern ourselves with the two most basic results of the
comparison, whether the quantities are equal or not. The compare instruction essentially
subtracts the two values passed to it and sets the zero flag if the difference is zero i.e. the two
quantities are equal. A combination of the CMP and the JMP instructions can be used to
implement the assembler equaivalent of a basic if statement.

Consider the following example which will read in a key and tell the user if he pressed escape
(ASCII code 27):

start:
; again sub-function 8
mov ah,08
; read the character
int 21h
; compare it to the escape character
CMP al,27
; if it is not equal (difference is not zero (NZ)) then go to
JNZ
not_escape
not_escape
; otherwise this code gets executed.
is_escape:
; subfunction 2 - output a character.
mov ah,02
; output letter "E"
mov dl,"E"
int 21h
; then output "S". (NOTE: 02 remains in ah register so no need
mov dl,"S"
to keep moving it)
int 21h
mov dl,"C"
; finally output "C"
int 21h
not_escape:
; if any other key is pressed execution continues here
mov ah,4ch
; exit the program.
mov al,00
int 21h

Tutorial 4 - Variables and Strings.


In this the fourth tutorial we will cover how do allocate space in memory for variables in out
programs and also how output messages on the screen using strings. This whole topic is very
basic and very simple, as well as being essential to do anything useful in assembler
Unlike other assemblers, a86 does not specifically require a special area in the program where
variables are declared, filled with keywords to remember. However it is good practice to have all
your variables grouped together at the start of your program, so as to have a clear separation
between variables and code. In assembler there are two "keywords" of sorts to remember: DB
and DW. DB tells the compiler to allocate a byte space (8 bits) for a variable, while DW
allocates two bytes (16 bits) for the variable. Characters and small numbers are allocated with db
while most numbers for arithmetic are dw.

Once variables are declared, they can then be used like registers, with the mov instruction to
transfer data. Here is an example of the program from tutorial 3 with a variable to hold each
bracket:

jmp start ; First instruction should be a command


;=======================
; Data declarations clearly separated from code
leftbr db "(" ; The variables declared to hold the brackets
rightbr db ")" ; are given them as initial values.
key db ; Variable declared to hold the key pressed

;=======================
start: ; Program execution starts here...
mov ah,08
int 21h ; Read a keypress
mov key,al ; Store the key in the variable
output:
mov dl,leftbr ; Move the variable to dl for output
mov ah,02
int 21h ; Output "("
mov dl,key
int 21h ; Ouput key
mov dl,rightbr
int 21h ; Output ")"
exit:
mov ah,4ch
mov al,00 ; Exit code 0
int 21h ; Terminate program

Strings and arrays can be declared in assembler too, by a number of methods. The easiest method
involves simply assigning more than one character to a data item, and voila - a string. This
allows us to output messages easily, like in a basic "hello world" program:

jmp start ; Start program...


;============================
msg db "Hello World.$" ; A string variable with a value.
;============================
start:
mov ah,09 ; subfunction 9 output a string
mov dx,offset msg ; DX points to (holds the address of)
the string
int 21h ; Output the message
exit:
mov ah,4ch
mov al,00 ; Exit code 0
int 21h ; Terminate program

There are a couple of things to notice about the above program, and about outputing strings in
general. Firstly, when using interrupt 21h, sub-fn 9 to output strings, one must finish the string
with a "$". Otherwise the computer will continue outputing charaters from memory past the end
of the string until a "$" is reached. The second thing to notice is that you do not move the string
to DX, but you move the address of the string to the register. In the actual specification, it says
that DS:DX must point to the address. This means that DS must contain the segment in which the
string is, and DX holds the offset, or the address within that segment. However, as I mentionned
in previous tutorials, when writing basic programs using a86, all the data and code is in one
segment so you can forget about the DS requirement. (If you don't follow this don't worry, you
can still write simple assembler code without knowing it!)

When one outputs a string in assembler, the computer does not automatically move onto the next
line before the next output. It will continue outputting on the same line until that line is full,
which means until 80 characters have been output. To force a line break, insert the line feed and
carriage return characters in your string - characters 10 and 13. To display "Hello World" on
screen and move onto the next line declare the variable msg as follows:

msg db "Hello World.",10,13,"$"


Non-printing characters like the carriage return, have to be added to a string by way of their character
numbers. Character numbers are not placed in inverted commas in the definition, and must be
separated by commas.

Tutorial 5 - Mathematical Operators.


In assembler, more so than in high level programming languages, mathematical operations are
essential. Even to perform the simplest things, like reading in or printing out a decimal number
requires a surprisingly large number of mathematical operators.

This is not a long or difficult tutorial because each of the mathematical operators is contained
within one instruction and all one has to do is learn the appropriate instruction mnemonics. The
instructions I have divided into four categories, and I include a nice sample program at the end.

Increment and Decrement

These are two of the most basic and useful instructions in the instruction set. The instruction
"inc" adds one to the parameter, while the instruction "dec" subtracts one from the parameter.
These operations are generally faster than using an add instruction to add one to the value.

inc ax ;add one to contents of ax register


dec b[bx] ;subtract one from byte pointed to by bx register
inc var_name ;increment the variable var_name

Basic Arithmetic Operators

There are assembler instructions for all of four basic arithmetic operators: addition, subtraction,
multiplication, and division. The important thing about these instructions is that the latter two,
multiplication and division are slow to carry out in comparison to other operations, particularly
compared to bit operations such as the left and right shift given below. For this reason, when
multiplying by a constant value, it can be quicker to perform the operation using a sequence of
shifts and adds rather than a multiplication.

Below are some example operations using adds and subtracts. Note, however, that a register or
numeric literal must be one argument of the instruction - memory to memory adds are not
allowed in one instruction.

add ax,bx ;add value in bx to value in ax (result in ax)


sub bx,1 ;subtract 1 from the bx value
add [bx],ax ;add value in ax to memory _word_ pointed to by
bx
add [bx],al ;add value in al to memory _byte_ pointed to by
bx
sub num,cx ;subtract cx value from variable "num"
add cx,num ;add num value to cx value
sub num,5 ;subtract 5 from variable num

The multiplication and division operators are much more limited in their parameters. Each
instruction takes only one parameter and the other is always the AX (and/or AL and AH)
register. Here are some very simple example instructions (Only covering 8 bit multiply and
divides to avoid using multiple registers):

mul bl ;multiply bl * al giving result in ax.


mul ch ;multiply ch * al giving result in ax.
mul num ;multiply variable "num" by al giving ax (num is byte)
mul 7 ;ax = 7 * al
mul b[bx] ;ax = value-pointed-to-by-bx * al. ("b" specifies 8 bit
(byte value) mul)

div bl ;divide ax by value in bl. result in al, remainder in


ah
div ch ;al = ax / ch, ah = ax % ch (% = modulus operator in C
= "mod" in Pascal)
div num ;al = ax / num, ah = ax % num
div 7 ;al = ax / 7, ah = ax % 7
div b[bx] ;al = ax / [bx], ah = ax % [bx]

Bit Shifting Operators

The bit shifting operators are operators which take the binary representation of a value and move
the bits either left or right. With a left shift, a zero is added onto the right of the number and the
leftmost bit is removed. This effectively multiplies the number by two, and it is very fast. The
right shift is performed in the oposite way, and divides the number by two. The shift operators
take two parameters, the data to be shifted and the amount it is to be shifted by. The second
parameter is either a literal number, or the cl register.

shl ax,2 ;multiply ax by 4 (2^2)


shr bl,1 ;divide bl by 2 (2^1)
shl ch,3 ;multiply ch by 8 (2^3)
shr dx,cl ;divide dx by 2^value-in-cl

shr dl,4 ;clear the lower 4 bits...


shl dl,4 ;...of the dl register

Logical Operators

As well as shifting bits left and right, the x86 instruction set also contains instructions for
performing logical operations on the bits in numbers: and, or, not, and xor. Each of these except
the not operator take two parameters (not takes one). The two parameter operators are used in the
same way and accept the same parameter types as add and subtract (as in, one must have a literal
or register as at least one parameter - no memory memory operations allowed). The not operator,
takes one parameter of any non-literal type, memory or register.

For a complete list of the operators and all possible legal parameters to them, consult the A86
Manual Chapter 6.

Sample Program

This is a simple sample program which reads in two numbers and outputs their sum. Simple, one
would think, but not in assembler, as the inputs and outputs have to be converted to and from
character values into their numeric equivalents, i.e. we read in the characters '1' and '2' but we
have to convert this to the number 12. This makes the program longer.

jmp start
;****************************
;* Program to read in two *
;* numbers and add them *
;* and print out the result *
;****************************
number db 7 dup 0 ; string which will store input and
output
n1 dw 0 ; two input variables
n2 dw 0
res dw 0 ; one output variable
cr dw 13,10,"$" ; carriage return, line feed
start:
mov dx,offset number
mov bx,dx
mov b[bx],5 ; maximum 5 characters to read
mov ah,0ah
int 21h ; read in a string from keyboard
mov bx,offset number +1
mov cx,00
mov cl,[bx] ; cl now contains number of digits
mov ax,00 ; ax will contain the number input
usedigit:
inc bx ; get next digit
shl ax,1 ; multiply by 10 using 2 shift ops and
an add...
mov dx,ax ; ... x*8 + x*2 = x*10 is the
principle.
shl ax,2
add ax,dx ; ax is now multiplied by 10
mov dx,00
mov dl,[bx] ; dl has new character
sub dx,48 ; subtract 48 = ascii('0') to get
number value
add ax,dx ; add to ax
loop usedigit ; loop statement= jmp if cx > 0
cmp n1,00 ; see if this is first or second number
read
jnz second
mov n1,ax ; assign it to the first variable
jmp start ; read in another number
second:
mov n2,ax ; or assign to second variable and
continue
print_cr:
mov ah,09
mov dx,offset cr ; print out a carriage return character
int 21h
addnos:
mov ax,n1 ; move numbers to registers ...
mov bx,n2
add ax,bx ; ...and add
mov res,ax ; store the result
mov cx,00
setup_string:
mov bx,offset number+7 ; put a $ at end of buffer.
mov b[bx],'$' ; we will fill buffer from back
forwards
dec bx
mov ax,res
convert_decimal:
mov dx,10
div dl ; divide by 10
add ah,48 ; convert remainder to character
mov [bx],ah ; and move to buffer for output
dec bx
mov ah,00 ; quotient becomes new value
cmp ax,00 ; if we haven't got all digits divide
again
jnz convert_decimal
printout:
mov dx,bx
inc dx ; we decremented once too many, go
forward one.
mov ah,09
int 21h ; output the string
close:
mov ah,4ch
mov al,00
int 21h ; end program

Tutorial 6 - Some Basic Graphics


Not many application programs are written in assembler entirely these days. It is most usual to
find the assembler code embedded in code in a high level language, such as C++ or Pascal. (if I
use assembler at all these days, I use it embedded inside programs for Turbo Pascal). The
principal reason for using assembler is because of the increased speed of execution which it
gives, and one area where this speed is most appreciated is in the area of computer graphics.

This single tutorial will cover how to set up a graphics mode, how to return to text mode, and
also how to place a single pixel on the screen. This can all be done using various interupts, but
for faster pixel plotting, it is best to use direct memory accesses. The trouble with this is that
each different screen resolution requires a different method of plotting pixels.

Switching Screen Modes

The interrupt used for switching between screen modes, and for all graphics work is interrupt
10h. Subfunction 0 of this interrupt sets the screen mode, depending upon the value of the
number in the AL register. A list of the basic graphics modes are given below.

Mode Type Text Graphics Colours Mode Type Text Graphics Colours
Res Res Res Res
0 25 x 13 25 x
text 320 x 200 16 graphics 320 x 200 16
40 40
1 25 x 16 25 x
text 320 x 200 14 graphics 640 x 200 16
40 80
2 25 x 25 x
text 640 x 200 16 15 graphics 640 x 350 mono
80 80
3 25 x 25 x
text 640 x 200 16 16 graphics 640 x 350 16
80 80
4 25 x 30 x
graphics 320 x 200 4 17 graphics 640 x 480 mono
40 80
5 25 x 30 x
graphics 320 x 200 4 18 graphics 640 x 480 16
40 80
6 25 x 25 x
graphics 640 x 200 mono 19 graphics 320 x 200 256
80 40

When writing programs which use graphics, one should remember to return the display to text
mode just before the program finishes. Mode 3 is a standard mode, which is appropriate for most
programs to switch to before ending. The following is a stub of code which switches the display
to graphics modes (640 x 480 x 16) and then back to text mode again before ending.

;=========================================
; Basic program to change graphics modes
;=========================================
mov ah,00 ;subfunction 0
mov al,18 ;select mode 18 (or 12h if prefer)
int 10h ;call graphics interrupt
;==== Graphics code here ====
mov ah,00 ;again subfunc 0
mov al,03 ;text mode 3
int 10h ;call int
mov ah,04ch
mov al,00 ;end program normally
int 21h

Displaying and Reading Back Pixels - Simply

The displaying and reading back of pixels on the screen is again simply done using interrupts.
The interrupt in question is again int 10h, this time subfunctions 0Ch and 0Dh or decimal 12 and
13. The first of these displays a pixel on the screen at any resolution (provided a graphics mode)
at the co-ordinates specified by the values in the cx and dx registers. The colour value is
specified in the al register. The second function reads the value of the pixel in memory again
given by cx and dx, except this time it returns the colour in al. Below is a sample program which
will display a square in blue in the middle of the screen.

jmp start
;=========================================
; Basic program to draw a rectangle
;=========================================
mode db 18 ;640 x 480
x_start dw 100
y_start dw 100
x_end dw 540
y_end dw 380
colour db 1 ;1=blue
;=========================================
start:
mov ah,00 ;subfunction 0
mov al,mode ;select mode 18 (or 12h if prefer)
int 10h ;call graphics interrupt
;==========================
mov al,colour ;colour goes in al
mov ah,0ch
mov cx, x_start ;start drawing lines along x
drawhoriz:
mov dx, y_end ;put point at bottom
int 10h
mov dx, y_start ;put point on top
int 10h
inc cx ;move to next point
cmp cx, x_end ;but check to see if its end
jnz drawhoriz
drawvert: ;(y value is already y_start)
mov cx, x_start ;plot on left side
int 10h
mov cx, x_end ;plot on right side
int 10h
inc dx ;move down to next point
cmp dx, y_end ;check for end
jnz drawvert
;==========================
readkey:
mov ah,00
int 16h ;wait for keypress
;==========================
end:
mov ah,00 ;again subfunc 0
mov al,03 ;text mode 3
int 10h ;call int
mov ah,04ch
mov al,00 ;end program normally
int 21h

Tutorial 7 - Graphics with Direct Memory Access.


Drawing pictures on the screen using the bios interrupts is all very easy, but when push comes to
shove, its also very, very slow as the bios routines are built to cope with every graphics mode. A
faster way of plotting pixels is to directly place the bits in video memory, using, for example, a
move instruction. This is very, very fast. but it does limit you to the resolution for which the
routine was written.

The simplest video modes for demostrating this principle, is that of mode 19, (13h) which has
320 x 200 pixels and uses 256 colours. 256 is 2^8, meaning that the colour value takes up exactly
one byte per pixel. This makes the actual setting of pixels very easy, move the colour value into
the appropriate memory byte. The video memory for this mode begins at memory address
A000h, and the pixels are then linearly in memory row by row. The memory location to write to
for pixel (x,y) is A000h + (y * 320) + x.

Unfortunately, this involves a previously unencountered complication. With the programs we


have written so far, the data and program have all been placed in the one segment. However, the
graphics memory is not within that segment, so we need a segment offset. This offset is the start
of video memory. To access an address we now use two parts, the segment (stored in the es
register) and the offset (stored in the di register). The whole address is referenced es:[di].

Drawing horizontal and vertical lines in this mode is easy: to draw a horizontal line, simply fill
all memory addresses from the starting point to the end point; to draw a vertical line, add 320 to
the current pixel position and this gives the next point. Below is a sample program demostrating
this.
jmp start
;==============================
; Draws a horiz and vert line
;==============================
startaddr dw 0a000h ;start of video memory
colour db 1
;==============================
start:
mov ah,00
mov al,19
int 10h ;switch to 320x200 mode
;=============================
horiz:
mov es, startaddr ;put segment address in es
mov di, 32000 ;row 101 (320 * 100)
add di, 75 ;column 76
mov al,colour ;cannot do mem-mem copy so use reg
mov cx, 160 ;loop counter
hplot:
mov es:[di],al ;set pixel to colour
inc di ;move to next pixel
loop hplot
vert:
mov di, 16000 ;row 51 (320 * 50)
add di, 160 ;column 161
mov cx, 100 ;loop counter
vplot:
mov es:[di],al
add di, 320 ;mov down a pixel
loop vplot
;=============================
keypress:
mov ah,00
int 16h ;await keypress
end:
mov ah,00
mov al,03
int 10h
mov ah,4ch
mov al,00 ;terminate program
int 21h

That, basically is all there is to it. Note how for switching to and from graphics mode we still use
the int calls. This is because the change only occurs generally once per program and so, unlike
pixel plotting is not a bottle-neck.

The primary use of assembler for graphics is frequently to embed the code in a higher level
language. Given below, then is an implementation of a few basic graphics primatives created in
assembler but embedded withing pascal functions (these will work with Borland/Inprise's Turbo
Pascal Compilers or can be easily converted to equivalent C/C++ functions).

const
vga : word = $A000;
var
oldmode : byte;

Procedure setMCGA; assembler;


{Sets the graphics mode, including saving
the previous graphics mode}
asm
mov ax,0F00h
int 10h
mov oldmode,al
mov ax,0013h
int 10h
end;

Procedure settext; assembler;


{Returns the program to the graphics
mode it was previous in}
asm
mov ah,00h
mov al,oldmode
int 10h
end;

procedure putpixel( x,y : word; colour : byte);


{sets the pixel at (x,y) to the
colour given by colour.
Calculations are done using shifts
and additions not multiplications}
begin
if (x>319) or (y>199) then exit;
asm
push ds {save these two registers...}
push di {...by putting values on stack}

mov ax,y
shl ax,1 {ax=y*2}
mov bx,ax {bx=y*2}
shl ax,2 {ax=y*8}
add ax,bx {ax=(y*8)+(y*2)=y*10}
shl ax,5 {ax=(y*10)*2^5=y*320}

mov bx,x {ax has y offset, bx has x}


add ax,bx {add the offsets}

mov di,ax {di now has currect offset}


mov ah,colour
mov ds,vga {es now has segment}
mov ds:[di],ah {plot the pixel}

pop di {restore reg values}


pop ds
end;
end;

Вам также может понравиться