Sei sulla pagina 1di 13

A Brief x86 Assembler Tutorial

Assembly language programming for the Intel x86 chips is not necessarily a difficult task. However, it can be made more difficult than it need
be, depending upon a number of factors. One of the most critical of these factors is the programmer's choice of assembler. There are three
main assemblers in use today (I stand open to correction on this as I have only ever heard of these three). These are MASM, (MicroSoft's
Assembler), TASM (Borland's Turbo Assembler) and A86 Assembler. (It has been recently pointed out to me that, as well as these, there is
the GNU assembler which is released under the GNU public licence and is freely available for use with a number of operating systems. It comes
as part of the binutils package on linux distributions.) Of all these assemblers, I use A86, which is an excellent assembler and, though not free
software, it is shareware and so can be downloaded from the internet. It is written by Eric Isaacson, and comes with a hefty manual in the
shape of 19 text-files. I am including the entire package on my web-site, zipped, as well as the complete manual.

A86.ZIP - The assembler package.


The Manual - All 19 Text documents.

For these tutorials I will assume that you are using A86, as with it there is no need to use the initial heading details required by other
assemblers. You just simply type up your assembler instructions into a file and assemble it and see how your program runs. So prepare a
directory and put A86.com and A86.lib into it, and you are ready to go!

The example programs included in these tutorials and have been tested by me before posting them on my site. However, this does not mean
that they are bug free and so feel free to email me with bugs you have identified in the code. I would like to thank those few people who have
already pointed out a number of bugs to me.

Select a Tutorial or Other Link


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
SP
Instruction Pointer
BP Stack Pointer
Data Transfer Registers Base Pointer
SI
DI Source Index
Set of flags Destination Index

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.

With that basic information known, we can now start writing programs.

To continue click HERE


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". A86 assembler should start up ( I am assuming you are already in the directory
where A86.com, A86.lib and prog1.asm are) adn assemble your program. If you then type "dir" you will see that the file prog1.com has been generated. Type "prog1"
to run the program, and volia!
To continue clickHERE
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:

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


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.

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: ; sub-function 8 - read a character


mov ah,08 ; call interrupt
int 21h ; save the key read in bl.
mov bl,al ; a jump instruction causes the program to start running now from the
JMP output ; output label, skipping out the next two lines.
; These never get executed...
mov ah,01
int 21h ; execution continues here
output: ; output a "("
mov dl,"("
mov ah,02
int 21h ; Then output the character read still held in bl
mov dl,bl
int 21h
; Last output a ")"
mov dl,")"
int 21h
exit:
mov ah,4ch ; Terminate the program
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:
mov ah,08 ; again sub-function 8
int 21h ; read the character
CMP al,27 ; compare it to the escape character
JNZ not_escape ; if it is not equal (difference is not zero (NZ)) then go to not_escape
is_escape: ; otherwise this code gets executed.
mov ah,02 ; subfunction 2 - output a character.
mov dl,"E" ; output letter "E"
int 21h
mov dl,"S" ; then output "S". (NOTE: 02 remains in ah register so no need 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

To continue click HERE


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

;=======================
s t a r t : ; 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.

To continue click HERE


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)
m u l 7 ; a x = 7 * a l
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.

s h l a x , 2 ; m u l t i p l y a x b y 4 ( 2 ^ 2 )
s h r b l , 1 ; d i v i d e b l b y 2 ( 2 ^ 1 )
s h l c h , 3 ; m u l t i p l y c h b y 8 ( 2 ^ 3 )
s hr dx,cl ;divide dx by 2^value-in-cl

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


s h l d l , 4 ; . . . o f t h e d l r e g i s t e r

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:
i n c b x ; g e t n e x t d i g i t
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
a d d a x , b x ; . . . a n d a d d
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
d i v d l ; d i v i d e b y 1 0
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
i n t 2 1 h ; o u t p u t t h e s t r i n g
close:
mov ah,4ch
mov al,00
i n t 2 1 h ; e n d p r o g r a m

To continue click HERE


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 Res Graphics Res Colours Mode Type Text Res Graphics Res Colours

0 text 25 x 40 320 x 200 16 13 graphics 25 x 40 320 x 200 16

1 text 25 x 40 320 x 200 16 14 graphics 25 x 80 640 x 200 16

2 text 25 x 80 640 x 200 16 15 graphics 25 x 80 640 x 350 mono

3 text 25 x 80 640 x 200 16 16 graphics 25 x 80 640 x 350 16

4 graphics 25 x 40 320 x 200 4 17 graphics 30 x 80 640 x 480 mono

5 graphics 25 x 40 320 x 200 4 18 graphics 30 x 80 640 x 480 16

6 graphics 25 x 80 640 x 200 mono 19 graphics 25 x 40 320 x 200 256

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
;=========================================
m o v a h , 0 0 ; s u b f u n c t i o n 0
mov al,18 ;select mode 18 (or 12h if prefer)
int 10h ;call graphics interrupt
;==== Graphics code here ====
m o v a h , 0 0 ; a g a i n s u b f u n c 0
m o v a l , 0 3 ; t e x t m o d e 3
i n t 1 0 h ; c a l l i n t
mov ah,04ch
m o v a l , 0 0 ; e n d p r o g r a m n o r m a l l y
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
;=========================================
m o d e d b 1 8 ; 6 4 0 x 4 8 0
x _ s t a r t d w 1 0 0
y _ s t a r t d w 1 0 0
x _ e n d d w 5 4 0
y _ e n d d w 3 8 0
c o l o u r d b 1 ; 1 = b l u e
;=========================================
start:
m o v a h , 0 0 ; s u b f u n c t i o n 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
i n c c x ; m o v e t o n e x t p o i n t
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
i n t 1 6 h ; w a i t f o r k e y p r e s s
;==========================
end:
mov ah,00 ;again subfunc 0
m o v a l , 0 3 ; t e x t m o d e 3
i n t 1 0 h ; c a l l i n t
mov ah,04ch
mov al,00 ;end program normally
int 21h

For more graphics click HERE


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
c o l o u r d b 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)
a d d d i , 7 5 ; c o l u m n 7 6
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
i n t 1 6 h ; a w a i t k e y p r e s s
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;

To continue click HERE

Potrebbero piacerti anche