MS-DOS support for serial ports (often referred to as the auxiliary device in MS-DOS manuals) is weak compared with its keyboard, video-display, and printer support. This is one area where the application programmer is justified in making programs hardware dependent to extract adequate performance.
Programs that restrict themselves to MS-DOS functions to ensure portability can use the handle read and write functions (Int 21H Functions 3FH and 40H), with the predefined standard auxiliary handle (3) to access the serial port. For example, the following code writes the string hello to the serial port that is currently defined as the AUX device:
msg db 'hello' ; message for serial port
msg_len equ $-msg ; length of message
.
.
.
mov ah,40h ; function 40h = write file or device
mov bx,3 ; BX = standard aux handle
mov cx,msg_len ; CX = string length
mov dx,seg msg ; DS:DX = string address
mov ds,dx
mov dx,offset msg
int 21h ; transfer to MS-DOS
jc error ; jump if error
.
.
.
The standard auxiliary handle gives access to only the first serial port (COM1). If you want to read or write COM2 and COM3 using the handle calls, you must issue an open request (Int 21H Function 3DH) for the desired serial port and use the handle returned by that function with Int 21H Functions 3FH and 40H.
Some versions of MS-DOS have a bug in character-device handling that manifests itself as follows: If you issue a read request with Int 21H Function 3FH for the exact number of characters that are waiting in the driver's buffer, the length returned in the AX register is the number of characters transferred minus one. You can circumvent this problem by always requesting more characters than you expect to receive or by placing the device handle into binary mode using Int 21H Function 44H.
MS-DOS also supports two traditional functions for serial-port I/O. Int 21H Function 03H inputs a character from COM1 and returns it in the AL register; Int 21H Function 04H transmits the character in the DL register to COM1. Like the other traditional calls, these two are direct descendants of the CP/M auxiliary-device functions.
For example, the following code sends the string hello to COM1 using the traditional Int 21H Function 04H:
msg db 'hello' ; message for serial port
msg_len equ $-msg ; length of message
.
.
.
mov bx,seg msg ; DS:BX = string address
mov ds,bx
mov bx,offset msg
mov cx,msg_len ; CX = length of string
mov dl,[bx] ; get next character
mov ah,4 ; function 04h = aux output
int 21h ; transfer to MS-DOS
inc bx ; bump pointer to string
loop next ; loop until string done
.
.
.
MS-DOS translates the traditional auxiliary-device functions into calls on the same device driver used by the handle calls. Therefore, it is generally preferable to use the handle functions in the first place, because they allow very long strings to be read or written in one operation, they give access to serial ports other than COM1, and they are symmetrical with the handle video-display, keyboard, printer, and file I/O methods described elsewhere in this book.
Although the handle or traditional serial-port functions allow you to write programs that are portable to any machine running MS-DOS, they have a number of disadvantages:
The built-in MS-DOS serial-port driver is slow and is not interrupt driven.
MS-DOS serial-port I/O is not buffered.
Determining the status of the auxiliary device requires a separate call to the IOCTL function (Int 21H Function 44H)——if you request input and no characters are ready, your program will simply hang.
MS-DOS offers no standardized function to configure the serial port from within a program.
For programs that are going to run on the IBM PC or compatibles, a more flexible technique for serial-port I/O is to call the IBM ROM BIOS serial-port driver by means of Int 14H. You can use this driver to initialize the serial port to a desired configuration and baud rate, examine the status of the controller, and read or write characters. Section III of this book, "IBM ROM BIOS and Mouse Functions Reference," documents the functions available from the ROM BIOS serial-port driver.
For example, the following sequence sends the character X to the first serial port (COM1):
.
.
.
mov ah,1 ; function 01h = send character
mov al,'X' ; AL = character to transmit
mov dx,0 ; DX = serial-port number
int 14h ; transfer to ROM BIOS
and ah,80h ; did transmit fail?
jnz error ; jump if transmit error
.
.
.
As with the ROM BIOS printer driver, the serial-port numbers used by the ROM BIOS are zero-based, whereas the serial-port numbers in MS-DOS logical-device names are one-based. In this example, serial port 0 corresponds to COM1.
Unfortunately, like the MS-DOS auxiliary-device driver, the ROM BIOS serial-port driver is not interrupt driven. Although it will support higher transfer speeds than the MS-DOS functions, at rates greater than 2400 baud it may still lose characters. Consequently, most programmers writing high-performance applications that use a serial port (such as telecommunications programs) take complete control of the serial-port controller and provide their own interrupt driver. The built-in functions provided by MS-DOS, and by the ROM BIOS in the case of the IBM PC, are simply not adequate.
Writing such programs requires a good understanding of the hardware. In the case of the IBM PC, the chips to study are the INS8250 Asynchronous Communications Controller and the Intel 8259A Programmable Interrupt Controller. The IBM technical reference documentation for these chips is a bit disorganized, but most of the necessary information is there if you look for it.