Function 2 INT 21H - BIOS Routine to Print a Character to Display

Normally, the 8086 fetches instructions from sequential memory locations and places them into an instruction queue. Many instructions simply perform a specific operation on one of the processor's registers or on the data contained in a memory location. There are, however, many times when the results of an instruction need to be interpreted. Did the last instruction set the zero flag? Was the result of the last subtraction negative? Did a 0 rotate into bit 7 of register BL? There may be many different choices to make during execution, and these choices require a number of different portions of machine code.

The program needs to be able to change its execution path to respond appropriately. For this reason we need to be able to change the path of program execution by forcing the processor to fetch its next instruction from a new location. This is commonly referred to as a jump. A jump alters the contents of the processor’s instruction pointer (and sometimes its CS register as well).

There are two classes of jump instructions - the unconditional (JMP address which is jump always) and Jump on Condition to an address. In assembly language, the jump instruction is how we implement a loop.

Loop and Jump Instructions

A loop is a section of a program that is repeated more than once. In high level languages, this could be a For - Next loop, Repeat - Until loop, or a Do - While loop. When these loops are executed, the program will remain within this section of code while a condition exists, or until a condition exists. The test that is performed is Boolean (the result is true or false). The result is based on a test of a condition.

One way to implement a loop is to create a Count Controlled Loop. In this type of loop, we must have a loop control variable, which is decremented or incremented on each pass of the loop. With this type of loop, we have to initialize the loop control variable to a specific value. For example, we might initialize the counter to 10, decrement the counter on each pass, and remain in the loop until the count reaches zero. Likewise, we could clear the counter and increment it on each pass. Until the count reaches a desired value, we would remain in the loop.

A second method is to use a Sentinel Controlled Loop. This might be the case where a large amount of data is read from an array until a special piece of data, called the sentinel, is encountered. This is sometimes referred to as dummy data since it is data that might not normally be encountered in the data stream... For example, if we are printing names and addresses, we would expect to encounter alphabetic and numeric data. A good choice for the sentinel would be the tilde or ~. When the sentinel is encountered, the loop is exited.

The disadvantage to the Count Controlled Variable is the loop is always traversed a specific number of times. This means that we have no flexibility as to the amount of data to be accessed. With a Sentinel Control Variable we must configure a storage area for a maximum size, but placing the dummy data at the end of the data, we do not have to pad the array or traverse all elements of the array.

Whichever method we use, we need to use a conditional branch instruction to either say in the loop or exit the loop. These conditional branch instructions are Jump On Condition. It works as follows:

We enter the loop and do something. Then we perform a test (compare the data on to the sentinel), or increment, or decrement the loop control variable. Either way, we set or clear flags. The flags that we will use the most are:

Sign Flag 0 = positive and 1 = negative

Zero Flag 0 = not zero and 1 = zero

Carry Flag 0 = no overflow and 1 = overflow

Parity Flag0 = odd and 1 = even

Loop Count Method

MOVCX,10;load loop count into register

HERE:

;Do something to process data

DECCX;Subtract 1 from CX

JNZHERE;If CX did not reach 0, go to beginning of loop

In the previous example, when the count reaches zero, we fall out of the loop. Here is a different method.

MOVCX,10;load loop count into register

HERE:

DECCX;Subtract 1 from CX

JZOVER;If CX reached 0, go to OVER

;Do something to process data

JMP HERE;Jump always to beginning of the loop

OVER:

; Next Instruction here

In the above example, we have two branch instructions. The first JZ OVER will exit the loop when the count reaches 0. If it is not 0, we continue through the loop. When we reach the end of the loop, we encounter JMP HERE. This is an unconditional branch or JUMP Always.

We will see examples of this in the following section on BIOS Function 2.

BIOS Function 2 INT 21H - Console Output

Note: This is similar to Function 9 except Function 9 prints a string of text until the $ is encountered while Function 2 outputs only one character. Function 9 has a loop in the routine. To print more than one character using Function 2, the User must create the loop.

Function 2: Console Output
Entry Parameters:
Register AH: 02H
Register DL: ASCII Character
Returned Value:
Character from DL on Display
Uses INT 21H

If the ASCII character is 08H, the cursor moves back one space. If the ASCII character is 0AH, the display scrolls one line. Here is an example of Function 2. In this case, the tilde (~) is the exit character instead of the $.

Since the BIOS function only outputs a single character, we must use a loop to print a string.

.data

crequ0dh

lfequ0ah

AnyString db cr,lf,lf,”This is a text string.”,cr,lf,lf,”~”

LastChar db ‘~’

In the above data segment, AnyString is an array of characters, in this case a message that we wish to print on the display. You should already know about the CR, LF characters from previous handouts. This string contains a Sentinel, the ~, and LastChar identifies the loop exit character. Now we move to the Code Segment.

.code

.startup

mov ax,@data

mov ds,ax

After initializing the DS register, we identify an address pointer, and initialize it to the beginning of our array (storage buffer for the data). In our example, we are using the SI register as the address pointer to allow us to traverse (walk) the array. Since the data is ASCII (byte size), we must increment SI on each pass. SI is initialized either with the MOV or LEA instruction.

si <= offset AnyString;load address to use SI as string pointer

Here is the loop GetChar On entry, the address pointer is initialized to the offset address of GetChar in the Data Segment. Here is the Algorithm

We will read the character from memory location DS:SI into the DL register

If DL contains LastChar then Jump to fini.

Else move Function 2 into AH

Perform a BIOS Call using INT 21H

Increment the address pointer (SI)

Go back to the beginning of the loop

The IF -THEN -ELSE structure is a HLL description. We cannot implement it in assembly language that way. What we must do is:

Test for the exit by comparing DL with LastChar. If they are the same, the Zero Flag is set. If they are not the same, it is cleared. So Compare is similar to the IF

JZ fini is the THEN. JZ is Jump if Zero. If the zero flag is set (true) by the Compare instruction, then the next instruction is at the address indicated by the label fini.

ELSE the condition was False and we perform the next instruction. We put the BIOS function in register AH

Here is the PseudoCode for the loop.

GetChar:

dl <= [si] ;read a string character

compare dl,LastChar ;end of string?

jz fini ;jump if match

ah <= 2 ;display character function

int 21h ;dos call

si <= (si) +1 ;point to next string character

jmp GetChar;and repeat

fini: ;place normal MS-DOS program termination here

.exit

end

We examined how to print a string using Function 9. Let us look at the complete code for printing the same message using function 2. The Exit Character is the ~ instead of the $, and I used different labels than the previous discussion.

TITLE Function 2 Example by A. H. Andersen

page 60,80

; Author Andrew H. Andersen, Jr.

; BrookdaleCommunity College

; This program will print a string of

; ASCII characters from memory to the

; display using the MS-DOS BIOS

; Function 2 using INT 21h

; This routine sends one character at a time

; to the CRT, so we must write a loop.

; You must set up the string in a buffer

; (array) before the program is assembled,

; or the string may be created and placed

; in the buffer by some process during program

; execution. A loop exit character is placed

; at the end of the string.

; You should select a loop exit character that

; would not normally be used. Good choices are

; the $ or the ~. Remember they must be

; enclosed in quotes

.MODEL SMALL

.STACK 2000H

.DATA

CR EQU 0DH

LF EQU 0AH

PRNT EQU 9

DUMMY EQU “~”

OurString DB CR,LF,LF,”Hello there!”, \ ;,\ means continue on next line

CR,LF,LF,'~'

.CODE

.STARTUP

MOV AX,@DATA ;initialize DS register

MOV DS,AX ;indirectly

; In the following, we will use the SI

; (Source Index) register as an address pointer

; since we wish to read many charactersin a loop.

BEGIN:

;The following two instructions are for loop initialization

MOV AH,2 ;BIOS Function 2 display character function

LEA SI,OurString;load the address pointer

; SI will be used as an address pointer. Anytime we wish

; to use a register as an address pointer, it is enclosed

; in [ ]. We mist also initialize the pointer with

; base address of the data before we use it, and we

; must increment the pointer in the loop

GetChar:

MOV DL,[SI] ;read a string character

CMP DL,DUMMY ;IF character is the end of string

JZ Exit ;THEN jump to Exit if match

INT 21H ;ELSE perform BIOS call

INC SI ;point to next string character

JMP GetChar ;and repeat

Exit:

MOV AX,4C00h ;Normal MS-DOS Exit Routine

INT 21H

.EXIT

END

The previous example used a sentinel (dummy data) to exit the loop. We could also use a loop counter to perform a loop pass one time for each character.

TITLE Function 2 Example by A. H. Andersen

page 60,80

; Author Andrew H. Andersen, Jr.

; BrookdaleCommunity College

; This program will print a string of ASCII characters from

; memory to the display using the MS-DOS BIOS Function 2

; This example uses a loop counter.

.MODEL SMALL

.STACK 2000H

.DATA

CR EQU 0DH

LF EQU 0AH

PRNT EQU 9

OurString DB CR,LF,LF,”Hello there!”, \

CR,LF,LF,”~”

Times EQU $-OurString;Calaculate the number of characters

.CODE

.STARTUP

MOV AX,@DATA ;initialize DS register

MOV DS,AX ;indirectly

; In the following, we will use the SI

; (Source Index) register as an address pointer

; since we wish to read many characters in a loop.

BEGIN:

;The following three instructions are for loop initialization

MOV AH,2 ;BIOS Function 2 display character function

LEA SI,OurString ;load the address pointer

MOV CX,Times ;load loop counter with times

; SI will be used as an address pointer. Anytime we wish

; to use a register as an address pointer, it is enclosed

; in [ ]. We mist also initialize the pointer with

; base address of the data before we use it, and we

; must increment the pointer in the loop

GetChar:

MOV DL,[SI] ;REPEAT read a string character

INT 21H ;perform BIOS call

INC SI ;point to next string character

LOOP GetChar ;Decrement CX and loop to GetChar UNTIL CX is 0

Exit:

MOV AH,4Ch ;Normal MS-DOS Exit Routine

INT 21H ;End Process

.EXIT

END

Page 1