| Name | Inputs | Outputs | Description |
|---|---|---|---|
| ComBaud | AX: bps (baud rate) = 110, 150, 300, 600, 1200, 2400, 4800, 9600, or 19200 | - | Sets
the communication rate for the serial port. ComBaud only supports the specified
speeds. If ax contains some other value on entry, ComBaud ignores
the value. |
| ComStop | AX: 1 or 2 | - | Sets the number of stop
bits. The ax register contains the number of stop bits to use
(1 or 2). |
| ComSize | AX: word size (5, 6, 7, or 8) | - | Sets
the number of data bits. The ax register contains the number
of bits to transmit for each byte on the serial line. |
| ComParity | AX:
Parity selector. If bit zero is zero, parity off, if bit zero is one, bits
one and two are: 00 - odd parity 01 - even parity 10 - parity stuck at 0 11 - parity stuck at 1 | - | Sets the parity (if any) for the serial communications. |
| ComRead | - | AL- Character read from port. | Waits until a character is available from in the data register and returns that character. Used for polled I/O on the serial port. Do not use if you've activated the serial interrupts (see ComInitIntr). |
| ComWrite | AL- Character to write. | - | Waits until the transmitter holding register is empty, then writes the character in al to the output register. Used for polled I/O on the serial port. Do not use with interrupts activated. |
| ComTstIn | - | AL=0 if no character, AL=1 if char avail. | Test to see if a character is available at the serial port. Use only for polling I/O, do not use with interrupts activated. |
| ComTstOut | - | AL=0 if transmitter busy, AL=1 if not busy. | Test to see if it is okay to write a character to the output register. Use with polled I/O only, do not use with interrupts active. |
| ComGetLSR | - | AL= Current LSR value. | Returns the current LSR value in the al
register. See the section on the LSR for more details. |
| ComGetMSR | - | AL= Current MSR Value. | Returns the current MSR value in the al
register. See the section on the MSR for more details. |
| ComGetMCR | - | AL= Current MCR Value. | Returns the current MCR value in the al
register. See the section on the MCR for more details. |
| ComSetMCR | AL = new MCR Value | - | Stores the value in al into the MCR
register. See the section on the MCR for more details. |
| ComGetLCR | - | AL= Current LCR Value. | Returns the current LCR value in the al
register. See the section on the LCR for more details. |
| ComSetLCR | AL = new LCR Value | - | Stores the value in al into the LCR
register. See the section on the LCR for more details. |
| ComGetIIR | - | AL= Current IIR Value. | Returns the current IIR value in the al
register. See the section on the IIR for more details. |
| ComGetIER | - | AL= Current IER Value. | Returns the current IER value in the al
register. See the section on the IER for more details. |
| ComSetIER | AL = new IER Value | - | Stores the value in al into the IER
register. See the section on the IER for more details. |
| ComInitIntr | - | - | Initializes the system to support interrupt driven serial I/O. See details below. |
| ComDisIntr | - | - | Resets the system back to polled serial I/O |
| ComIn | - | - | Reads a character from the serial port when operating with interrupt driven I/O. |
| ComOut | - | - | Writes a character to the serial port using interrupt driven I/O. |
; Useful equates: BIOSvars = 40h ;BIOS segment address. Com1Adrs = 0 ;Offset in BIOS vars to COM1: address. Com2Adrs = 2 ;Offset in BIOS vars to COM2: address. BufSize = 256 ;# of bytes in buffers. ; Serial port equates. If you want to support COM2: rather than COM1:, simply ; change the following equates to 2F8h, 2F9h, ... ComPort = 3F8h ComIER = 3F9h ComIIR = 3FAh ComLCR = 3FBh ComMCR = 3FCh ComLSR = 3FDh ComMSR = 3FEh ; Variables, etc. This code assumes that DS=CS. That is, all the variables ; are in the code segment. ; ; Pointer to interrupt vector for int 0Ch in the interrupt vector table. ; Note: change these values to 0Bh*4 and 0Bh*4 + 2 if you want to support ; the COM2: pot. int0Cofs equ es:[0Ch*4] int0Cseg equ es:[0Ch*4 + 2] OldInt0c dword ? ; Input buffer for incoming character (interrupt operation only). See the ; chapter on data structures and the description of circular queus for ; details on how this buffer works. It operates in a fashion not unlike ; the keyboard's type ahead buffer. InHead word InpBuf InTail word InpBuf InpBuf byte Bufsize dup (?) InpBufEnd equ this byte ; Output buffer for characters waiting to transmit. OutHead word OutBuf OutTail word OutBuf OutBuf byte BufSize dup (?) OutBufEnd equ this byte ; The i8259a variable holds a copy of the PIC's IER so we can restore it ; upon removing our interrupt service routines from memory. i8259a byte 0 ;8259a interrupt enable register. ; The TestBuffer variable tells us whether we have to buffer up characters ; or if we can store the next character directly into the 8250's output ; register (See the ComOut routine for details). TestBuffer db 0The first set of routines provided by the Standard Library let you initialize the 8250 SCC. These routines provide "programmer friendly" interfaces to the baud rate divisor and line control registers. They let you set the baud rate, data size, number of stop bits, and parity options on the SCC.
ComBaud routine sets the 8250's transfer rate (in bits
per second). This routine provides a nice "programmer's interface"
to the 8250 SCC. Rather than having to compute the baud rate divisor value
yourself, you can simply load ax with the bps value you want
and simply call this routine. Of course, one problem is that you must choose
a bps value that this routine supports or it will ignore the baud rate change
request. Fortunately, this routine supports all the common bps rates; if
you need some other value, it is easy to modify this code to allow those
other rates.ax
with the corresponding 16 bit divisor constant. The second part of this
code switches on the baud rate divisor registers and stores the value in
ax into these registers. Finally, it switches the first two 8250 I/O registers
back to the data and interrupt enable registers.ComSetLCR
and ComGetLCR, that we will define a little later. These routines
do the obvious functions, they read and write the LCR register (preserving
registers, as appropriate).
ComBaud proc
push ax
push dx
cmp ax, 9600
ja Set19200
je Set9600
cmp ax, 2400
ja Set4800
je Set2400
cmp ax, 600
ja Set1200
je Set600
cmp ax, 150
ja Set300
je Set150
mov ax, 1047 ;Default to 110 bps.
jmp SetPort
Set150: mov ax, 768 ;Divisor value for 150 bps.
jmp SetPort
Set300: mov ax, 384 ;Divisor value for 300 bps.
jmp SetPort
Set600: mov ax, 192 ;Divisor value for 600 bps.
jmp SetPort
Set1200: mov ax, 96 ;Divisor value for 1200 bps.
jmp SetPort
Set2400: mov ax, 48 ;Divisor value for 2400 bps.
jmp SetPort
Set4800: mov ax, 24 ;Divisor value for 4800 bps.
jmp SetPort
Set9600: mov ax, 12 ;Divisor value for 9600 bps.
jmp short SetPort
Set19200: mov ax, 6 ;Divisor value for 19.2 kbps.
SetPort: mov dx, ax ;Save baud value.
call GetLCRCom ;Fetch LCR value.
push ax ;Save old divisor bit value.
or al, 80h ;Set divisor select bit.
call SetLCRCom ;Write LCR value back.
mov ax, dx ;Get baud rate divisor value.
mov dx, ComPort ;Point at L.O. byte of divisor reg.
out dx, al ;Output L.O. byte of divisor.
inc dx ;Point at the H.O. byte.
mov al, ah ;Put H.O. byte in AL.
out dx, al ;Output H.O. byte of divisor.
pop ax ;Retrieve old LCR value.
call SetLCRCom1 ;Restore divisor bit value.
pop dx
pop ax
ret
ComBaud endp
The ComStop routine programs the LCR to provide the specified
number of stop bits. On entry, ax should contain either one
or two (the number of stop bits you desire). This code converts that to
zero or one and writes the resulting L.O. bit to the stop bit field of the
LCR. Note that this code ignores the other bits in the ax register.
This code reads the LCR, masks out the stop bit field, and then inserts
the value the caller specifies into that field. Note the usage of the shl
ax, 2 instruction; this requires an 80286 or later processor.
comStop proc
push ax
push dx
dec ax ;Convert 1 or 2 to 0 or 1.
and al, 1 ;Strip other bits.
shl ax, 2 ;position into bit #2.
mov ah, al ;Save our output value.
call ComGetLCR ;Read LCR value.
and al, 11111011b ;Mask out Stop Bits bit.
or al, ah ;Merge in new # of stop bits.
call ComSetLCR ;Write result back to LCR.
pop dx
pop ax
ret
comStop endp
The ComSize routine sets the word size for data transmission.
As usual, this code provides a "programmer friendly" interface
to the 8250 SCC. On enter, you specify the number of bits (5, 6, 7, or 8)
in the ax register, you do not have to worry an appropriate
bit pattern for the 8250's LCR register. This routine will compute the appropriate
bit pattern for you. If the value in the ax register is not appropriate,
this code defaults to an eight bit word size.
ComSize proc
push ax
push dx
sub al, 5 ;Map 5..8 -> 00b, 01b, 10b, 11b
cmp al, 3
jbe Okay
mov al, 3 ;Default to eight bits.
Okay: mov ah, al ;Save new bit size.
call ComGetLCR ;Read current LCR value.
and al, 11111100b ;Mask out old word size.
or al, ah ;Merge in new word size.
call ComSetLCR ;Write new LCR value back.
pop dx
pop ax
ret
comsize endp
The ComParity routine initializes the parity options on the 8250. Unfortunately,
there is little possibility of a "programmer friendly" interface
to this routine, So this code requires that you pass one of the following
values in the ax register:| Value in AX | Description |
|---|---|
| 0 | Disable parity. |
| 1 | Enable odd parity checking. |
| 3 | Enable even parity checking. |
| 5 | Enable stuck parity bit with value one. |
| 7 | Enable stuck parity bit with value zero. |
comparity proc
push ax
push dx
shl al, 3 ;Move to final position in LCR.
and al, 00111000b ;Mask out other data.
mov ah, al ;Save for later.
call ComGetLCR ;Get current LCR value.
and al, 11000111b ;Mask out existing parity bits.
or al, ah ;Merge in new bits.
call ComSetLCR ;Write results back to the LCR.
pop dx
pop ax
ret
comparity endp
The next set of serial communication routines provide polled I/O support.
These routines let you easily read characters from the serial port, write
characters to the serial port, and check to see if there is data available
at the input port or see if it is okay to write data to the output port.
Under no circumstances should you use these routines when you've activated
the serial interrupt system. Doing so may confuse the system and produce
incorrect data or loss of data.ComRead routine is comparable to getc - it
waits until data is available at the serial port, reads that data, and returns
it in the al register. This routine begins by making sure we
can access the Receive Data register (by clearing the baud rate divisor
latch bit in the LCR).
ComRead proc
push dx
call GetLCRCom
push ax ;Save divisor latch access bit.
and al, 7fh ;Select normal ports.
call SetLCRCom ;Write LCR to turn off divisor reg.
WaitForChar: call GetLSRCom ;Get data available bit from LSR.
test al, 1 ;Data Available?
jz WaitForChar ;Loop until data available.
mov dx, comPort ;Read the data from the input port.
in al, dx
mov dl, al ;Save character
pop ax ;Restore divisor access bit.
call SetLCRCom ;Write it back to LCR.
mov al, dl ;Restore output character.
pop dx
ret
ComRead endp
The ComWrite routine outputs the character in al to the serial
port. It first waits until the transmitter holding register is empty, then
it writes the output data to the output register.
ComWrite proc
push dx
push ax
mov dl, al ;Save character to output
call GetLCRCom ;Switch to output register.
push ax ;Save divisor latch access bit.
and al, 7fh ;Select normal input/output ports
call SetLCRCom ; rather than divisor register.
WaitForXmtr: call GetLSRCom ;Read LSR for xmit empty bit.
test al, 00100000b ;Xmtr buffer empty?
jz WaitForXmtr ;Loop until empty.
mov al, dl ;Get output character.
mov dx, ComPort ;Store it in the ouput port to
out dx, al ; get it on its way.
pop ax ;Restore divisor access bit.
call SetLCRCom
pop ax
pop dx
ret
ComWrite endp
The ComTstIn and ComTstOut routines let you check
to see if a character is available at the input port (ComTstIn)
or if it is okay to send a character to the output port (ComTstOut).
ComTstIn returns zero or one in al if data is
not available or is available, respectively. ComTstOut returns
zero or one in al if the transmitter register is full or empty,
respectively.
ComTstIn proc
call GetComLSR
and ax, 1 ;Keep only data available bit.
ret
ComTstIn endp
ComTstOut proc
push dx
call ComGetLSR ;Get the line status.
test al, 00100000b ;Mask Xmitr empty bit.
mov al, 0 ;Assume not empty.
jz toc1 ;Branch if not empty.
inc ax ;Set to one if it is empty.
toc1: ret
ComTstOut endp
The next set of routines the Standard Library supplies load and store the
various registers on the 8250 SCC. Although these are all trivial routines,
they allow the programmer to access these register by name without having
to know the address. Furthermore, these routines all preserve the value
in the dx register, saving some code in the calling program
if the dx register is already in use.al
register. They let you write ("Set") the value in al
to any of the LCR, MCR, and IER registers. Since these routines are so simple
and straight-forward, there is no need to discuss each routine individually.
Note that you should avoid calling these routines outside an SCC ISR while
in interrupt mode, since doing so can affect the interrupt system on the
8250 SCC.
ComGetLSR proc ;Returns the LSR value in the AL reg.
push dx
mov dx, comLSR ;Select LSR register.
in al, dx ;Read and return the LSR value.
pop dx
ret
ComGetLSR endp
ComGetMSR proc ;Returns the MSR value in the AL reg.
push dx
mov dx, comMSR ;Select MSR register.
in al, dx ;Read and return MSR value.
pop dx
ret
ComGetMSR endp
ComSetMCR proc ;Stores AL's value to the MCR reg.
push dx
mov dx, comMCR ;Point at MCR register.
out dx, al ;Output value in AL to MCR.
pop dx
ret
ComSetMCR endp
ComGetMCR proc ;Stores value in AL into MCR reg.
push dx
mov dx, comMCR ;Select MCR register.
in al, dx ;Read value from MCR register into AL.
pop dx
ret
ComGetMCR endp
ComGetLCR proc ;Return the LCR value in the AL reg.
push dx
mov dx, comLCR ;Point at LCR register.
in al, dx ;Read and return LCR value.
pop dx
ret
ComGetLCR endp
ComSetLCR proc ;Write a new value to the LCR.
push dx
mov dx, comLCR ;Point at LCR register.
out dx, al ;Write value in AL to the LCR.
pop dx
ret
ComSetLCR endp
ComGetIIR proc ;Return the value in the IIR.
push dx
mov dx, comIIR ;Select IIR register.
in al, dx ;Read IIR value into AL and return.
pop dx
ret
ComGetIIR endp
ComGetIER proc ;Return IER value in AL.
push dx
call ComGetLCR ;Need to select IER register by saving
push ax ; the LCR value and then clearing the
and al, 7fh ; baud rate divisor latch bit.
call ComSetLCR
mov dx, comIER ;Address the IER.
in al, dx ;Read current IER value.
mov dl, al ;Save for now
pop ax ;Retrieve old LCR value (divisor latch).
call ComSetLCR ;Restore divisor latch
mov al, dl ;Restore IER value
pop dx
ret
ComGetIER endp
ComSetIER proc ;Writes value in AL to the IER.
push dx
push ax ;Save AX's value.
mov ah, al ;Save IER value to output.
call ComGetLCR ;Get and save divsor access
push ax ; bit.
and al, 7fh ;Clear divisor access bit.
call ComSetLCR
mov al, ah ;Retrieve new IER value.
mov dx, comIER ;Select IER register
out dx, al ;Output IER value.
pop ax ;Restore divisor latch bit.
call ComSetLCR
pop ax
pop dx
ret
ComSetIER endp
The last set of serial support routines appearing in the Standard Library
provide support for interrupt driven I/O. There are five routines in this
section of the code: ComInitIntr, ComDisIntr,
ComIntISR, ComIn, and ComOut. The
ComInitIntr initializes the serial port interrupt system. It
saves the old int 0Ch interrupt vector, initializes the vector to point
at the ComIntISR interrupt service routine, and properly initializes
the 8259A PIC and 8250 SCC for interrupt based operation. ComDisIntr
undoes everything the ComDisIntr routine sets up; you need
to call this routine to disable interrupts before your program quits. ComOut
and ComIn transfer data to and from the buffers described in
the variables section; the ComIntISR routine is responsible
for removing data from the transmit queue and sending over the serial line
as well as buffering up incoming data from the serial line.ComInitIntr routine initializes the 8250 SCC and 8259A
PIC for interrupt based serial I/O. It also initializes the int 0Ch vector
to point at the ComIntISR routine. One thing this code does
not do is to provide break and critical error exception handlers. Remember,
if the user hits ctrl-C (or ctrl-Break) or selects abort on an I/O error,
the default exception handlers simply return to DOS without restoring the
int 0Ch vector. It is important that your program provide exception handlers
that will call ComDisIntr before allowing the system to return
control to DOS. Otherwise the system may crash when DOS loads the next program
into memory.
ComInitIntr proc
pushf ;Save interrupt disable flag.
push es
push ax
push dx
; Turn off the interrupts while we're doing this.
cli
; Save old interrupt vector. Obviously, you must change the following code
; to save and set up the int 0Bh vector if you want to access COM2: rather
; than the COM1: port.
xor ax, ax ;Point at interrupt vectors
mov es, ax
mov ax, Int0Cofs
mov word ptr OldIInt0C, ax
mov ax, Int0Cseg
mov word ptr OldInt0C+2, ax
; Point int 0ch vector at our interrupt service routine (see note above
; concerning switching to COM2:).
mov ax, cs
mov Int0Cseg, ax
mov ax, offset ComIntISR
mov Int0Cofs, ax
; Clear any pending interrupts:
call ComGetLSR ;Clear Receiver line status
call ComGetMSR ;Clear CTS/DSR/RI Interrupts
call ComGetIIR ;Clear xmtr empty interrupt
mov dx, ComPort
in al, dx ;Clear data available intr.
; Clear divisor latch access bit. WHILE OPERATING IN INTERRUPT MODE, THE
; DIVISOR ACCESS LATCH BIT MUST ALWAYS BE ZERO. If for some horrible reason
; you need to change the baud rate in the middle of a transmission (or while
; the interrupts are enabled) clear the interrupt flag, do your dirty work,
; clear the divisor latch bit, and finally restore interrupts.
call ComGetLCR ;Get LCR.
and al, 7fh ;Clear divisor latch bit.
call ComSetLCR ;Write new LCR value back.
; Enable the receiver and transmitter interrupts. Note that this code
; ignores error and modem status change interrupts.
mov al, 3 ;Enable rcv/xmit interrupts
call SetIERCom
; Must set the OUT2 line for interrupts to work.
; Also sets DTR and RTS active.
mov al, 00001011b
call ComSetMCR
; Activate the COM1 (int 0ch) bit in the 8259A interrupt controller chip.
; Note: you must change the following code to clear bit three (rather than
; four) to use this code with the COM2: port.
in al, 21h ;Get 8259A interrupt enable value.
mov i8259a, al ;Save interrupt enable bits.
and al, 0efh ;Bit 4=IRQ 4 = INT 0Ch
out 21h, al ;Enable interrupts.
pop dx
pop ax
pop es
popf ;Restore interrupt disable flag.
ret
ComInitIntr endp
The ComDisIntr routine disables serial interrupts. It restores
the original value of the 8259A interrupt enable register, it restores the
int 0Ch interrupt vector, and it masks interrupts on the 8250 SCC. Note
that this code assumes that you have not changed the interrupt enable bits
in the 8259 PIC since calling ComInitIntr. It restores the
8259A's interrupt enable register with the value from the 8259A interrupt
enable register when you originally called ComInitIntr. ComInitIntr. Doing so would patch the int 0Ch vector with garbage
and, likewise, restore the 8259A interrupt enable register with a garbage
value. Make sure you've called ComInitIntr before calling this
routine. Generally, you should call ComInitIntr once, at the
beginning of your program, and call ComDisIntr once, either
at the end of your program or within the break or critical error exception
routines.
ComDisIntr proc
pushf
push es
push dx
push ax
cli ;Don't allow interrupts while messing
xor ax, ax ; with the interrupt vectors.
mov es, ax ;Point ES at interrupt vector table.
; First, turn off the interrupt source at the 8250 chip:
call ComGetMCR ;Get the OUT 2 (interrupt enable) bit.
and al, 3 ;Mask out OUT 2 bit (masks ints)
call ComSetMCR ;Write result to MCR.
; Now restore the IRQ 4 bit in the 8259A PIC. Note that you must modify this
; code to restore the IRQ 3 bit if you want to support COM2: instead of COM1:
in al, 21h ;Get current 8259a IER value
and al, 0efh ;Clear IRQ 4 bit (change for COM2:!)
mov ah, i8259a ;Get our saved value
and ah, 1000b ;Mask out com1: bit (IRQ 4).
or al, ah ;Put bit back in.
out 21h, al
; Restore the interrupt vector:
mov ax, word ptr OldInt0C
mov Int0Cofs, ax
mov ax, word ptr OldInt0C+2
mov Int0Cseg, ax
pop ax
pop dx
pop es
popf
ret
ComDisIntr endp
The following code implements the interrupt service routine for the 8250
SCC. When an interrupt occurs, this code reads the 8250 IIR to determine
the source of the interrupt. The Standard Library routines only provide
direct support for data available interrupts and transmitter holding register
empty interrupts. If this code detects an error or status change interrupt,
it clears the interrupt status but takes no other action. If it detects
a receive or transmit interrupt, it transfers control to the appropriate
handler.ComIn routine that removes data
from the input buffer.ComOut) must
coordinate their activities. If the buffer is empty and the transmitter
is not currently transmitting anything, the ComOut routine
must write its data directly to the 8250. If the 8250 is currently transmitting
data, ComOut must append its data to the end of the output
buffer. The ComIntISR and ComOut use a flag, TestBuffer,
to determine whether ComOut should write directly to the serial
port or append its data to the output buffer. See the following code and
the code for ComOut for all the details.
ComIntISR proc far
push ax
push bx
push dx
TryAnother: mov dx, ComIIR
in al, dx ;Get interrupt id value.
test al, 1 ;Any interrupts left?
jnz IntRtn ;Quit if no interrupt pending.
cmp al, 100b ;Since only xmit/rcv ints are
jnz ReadCom1 ; active, this checks for rcv int.
cmp al, 10b ;This checks for xmit empty intr.
jnz WriteCom1
; Bogus interrupt? We shouldn't ever fall into this code because we have
; not enabled the error or status change interrupts. However, it is possible
; that the application code has gone in and tweakd the IER on the 8250.
; Therefore, we need to supply a default interrupt handler for these conditions.
; The following code just reads all the appropriate registers to clear any
; pending interrupts.
call ComGetLSR ;Clear receiver line status
call ComGetMSR ;Clear modem status.
jmp TryAnother ;Check for lower priority intr.
; When there are no more pending interrupts on the 8250, drop down and
; and return from this ISR.
IntRtn: mov al, 20h ;Acknowledge interrupt to the
out 20h, al ; 8259A interrupt controller.
pop dx
pop bx
pop ax
iret
; Handle incoming data here:
; (Warning: This is a critical region. Interrupts MUST BE OFF while executing
; this code. By default, interrupts are off in an ISR. DO NOT TURN THEM ON
; if you modify this code).
ReadCom1: mov dx, ComPort ;Point at data input register.
in al, dx ;Get the input char
mov bx, InHead ;Insert the character into the
mov [bx], al ; serial input buffer.
inc bx ;Increment buffer ptr.
cmp bx, offset InpBufEnd
jb NoInpWrap
mov bx, offset InpBuf
NoInpWrap: cmp bx, InTail ;If the buffer is full, ignore this
je TryAnother ; input character.
mov InHead, bx
jmp TryAnother ;Go handle other 8250 interrupts.
; Handle outgoing data here (This is also a critical region):
WriteCom1: mov bx, OutTail ;See if the buffer is empty.
cmp bx, OutHead
jne OutputChar ;If not, output the next char.
; If head and tail are equal, simply set the TestBuffer variable to zero
; and quit. If they are not equal, then there is data in the buffer and
; we should output the next character.
mov TestBuffer, 0
jmp TryAnother ;Handle other pending interrupts.
; The buffer pointers are not equal, output the next character down here.
OutputChar: mov al, [bx] ;Get the next char from the buffer.
mov dx, ComPort ;Select output port.
out dx, al ;Output the character
; Okay, bump the output pointer.
inc bx
cmp bx, offset OutBufEnd
jb NoOutWrap
mov bx, offset OutBuf
NoOutWrap: mov OutTail, bx
jmp TryAnother
ComIntISR endp
These last two routines read data from the serial input buffer and write
data to the serial output buffer. The ComIn routine, that handles
the input chore, waits until the input buffer is not empty. Then it removes
the first available byte from the input buffer and returns this value to
the caller.
ComIn proc
pushf ;Save interrupt flag
push bx
sti ;Make sure interrupts are on.
TstInLoop: mov bx, InTail ;Wait until there is at least one
cmp bx, InHead ; character in the input buffer.
je TstInLoop
mov al, [bx] ;Get next char.
cli ;Turn off ints while adjusting
inc bx ; buffer pointers.
cmp bx, offset InpBufEnd
jne NoWrap2
mov bx, offset InpBuf
NoWrap2: mov InTail, bx
pop bx
popf ;Restore interrupt flag.
ret
ComIn endp
The ComOut must check the TestBuffer variable
to see if the 8250 is currently busy. If not (TestBuffer equals
zero) then this code must write the character directly to the serial port
and set TestBuffer to one (since the chip is now busy). If
the TestBuffer contains a non-zero value, this code simply
appends the character in al to the end of the output buffer.
ComOut proc far
pushf
cli ;No interrupts now!
cmp TestBuffer, 0 ;Write directly to serial chip?
jnz BufferItUp ;If not, go put it in the buffer.
; The following code writes the current character directly to the serial port
; because the 8250 is not transmitting anything now and we will never again
; get a transmit holding register empty interrupt (at least, not until we
; write data directly to the port).
push dx
mov dx, ComPort ;Select output register.
out dx, al ;Write character to port.
mov TestBuffer, 1 ;Must buffer up next char.
pop dx
popf ;Restore interrupt flag.
ret
; If the 8250 is busy, buffer up the character here:
BufferItUp: push bx
mov bx, OutHead ;Pointer to next buffer position.
mov [bx], al ;Add the char to the buffer.
; Bump the output pointer.
inc bx
cmp bx, offset OutBufEnd
jne NoWrap3
mov bx, offset OutBuf
NoWrap3: cmp bx, OutTail ;See if the buffer is full.
je NoSetTail ;Don't add char if buffer is full.
mov OutHead, bx ;Else, update buffer ptr.
NoSetTail: pop bx
popf ;Restore interrupt flag
ret
ComOut endp
Note that the Standard Library does not provide any routines to see if there
is data available in the input buffer or to see if the output buffer is
full (comparable to the ComTstIn and ComTstOut routines). However, these
are very easy routines to write; all you need do is compare the head and
tail pointers of the two buffers. The buffers are empty if the head and
tail pointers are equal. The buffers are full if the head pointer is one
byte before the tail pointer (keep in mind, the pointers wrap around at
the end of the buffer, so the buffer is also full if the head pointer is
at the last position in the buffer and the tail pointer is at the first
position in the buffer).