[Back to FAQ SWAG index] [Back to Main SWAG index] [Original]
From: JACK MOFFITT Refer#: NONE
To: ALL Recvd: NO
Subj: MODEM REFERENCE 1/ Conf: (1221) F-PASCAL
---------------------------------------------------------------------------
Pascal Programmer's Reference to Modem Communications
by
Jack Moffitt
___-------------------------------------------------------------------------
INTRODUCTION
~~~~~~~~~~~~
Direct UART programming is a subject that not many people are
familiar with. Since the advent of FOSSIL, many people advise that one
should use that for all communications, to make it more portable. But for
some instances, it is necessary to have internal modem routines to go on.
Because no one seems to know or understand this subject, and because I have
found no other texts on the subject, I have decided to put it all into one
text, and maybe round off the edges on this subject.
THE ASYNCRONOUS MODEM
~~~~~~~~~~~~~~~~~~~~~
The asyncronous modem uses one (or more) specific ports on a
computer, as well as an IRQ (Interrupt Request). Every time a character
of data is received in the device, an interrupt is processed. One must
make a interrupt service routine to handle this input, but where does it go?
Since the IRQs are tied into interrupts, knowing the IRQ the device is using,
we can replace that interrupt. The port addresses and IRQ vectors are as
follows:
Port Addresses: COM1 -- 03F8h IRQ Vectors : 0 -- 08h
COM2 -- 02F8h 1 -- 09h
COM3 -- 03E8h 2 -- 0Ah
COM4 -- 02E8h 3 -- 0Bh
4 -- 0Ch
5 -- 0Dh
Standard Port IRQs: COM1 -- 4 6 -- 0Eh
COM2 -- 3 7 -- 0Fh
COM3 -- 4 8 -- 70h
COM4 -- 3 9 -- 71h
10 -- 72h
11 -- 73h
12 -- 74h
13 -- 75h
14 -- 76h
15 -- 77h
For standard use, the IRQ for comm ports 1 and 3 is 4, and for 2 and 4 it's
3. The 8250 UART has 10 registers available for getting, receiving and
interperating data. They are all located at offsets from the base address
of the port. Here are the registers and their offsets:
Register Offsets: Transmitter Holding Register (THR) -- 00h
Receiver Data Register (RDR) -- 00h
Baud Rate Divisor Low Byte (BRDL) -- 00h
Baud Rate Divisor High Byte (BRDH) -- 01h
Interrupt Enable Register (IER) -- 01h
Interrupt Identification Register (IIR) -- 02h
Line Control Register (LCR) -- 03h
Modem Control Register (MCR) -- 04h
Line Status Register (LSR) -- 05h
Modem Status Register (MSR) -- 06h
With this information one can address any register by adding the offset to
the base address. Therefor, if one is using COM2 (base address 02F8h) they
would access the Modem Status Register with: port[$02F8 + $06].
TRANSMITTER HOLDING REGISTER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This register contains the data to be sent to the remote PC or modem.
When bit 5 (THR empty) of the LSR is set, one can write to this port, thus
sending data over the phone line or null modem cable.
RECEIVER DATA REGISTER
~~~~~~~~~~~~~~~~~~~~~~
This register contains the incoming data. Read this register only
if bit 0 (Data Received) of the LSR is set, otherwise one will get
unpredictable characters.
BAUD RATE DIVISOR
~~~~~~~~~~~~~~~~~
The Baud Rate Divisor is used to set the BPS rate. To calculate the
Baud Rate Divisor, one must use the formula: (UART Clock Speed)/(16*BPS).
The UART Clock Speed is 1843200. To set the BRD one must first set bit 7
(port toggle) of the Line Control Register to 1, and then write the low and
high bytes to the correct offsets. Always remember to reset LCR bit 7 to 0
after one is finished setting the BPS rate.
INTERRUPT ENABLE REGISTER
~~~~~~~~~~~~~~~~~~~~~~~~~
The IER is used to simulate real interrupt calls. Write a byte
containing to interrupt information to enable any interrupts, all interrupts
also have corresponding actions to clear the interrupts. Here's the list:
Info Byte:
bit 7-6-5-4 3 2 1 0
~~~~~~~ ~ ~ ~ ~
Always 0 MSR Change Data Error or Break THR empty Data Received
To Clear: Read MSR Read LSR Output to THR Read RDR
INTERRUPT IDENTIFICATION REGISTER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This register is used to determine what kind of interrupts have
occured. Read one byte from this register, and use AND masks to find out
what has happened. The information in the byte is:
Info Byte:
bit 7-6-5-4-3 2-1 0
~~~~~~~~~ ~~~ ~
Unused 0-0 = Change in MSR If this bit is set
0-1 = THR empty more than one
1-0 = Data Received interrupt has
1-1 = Data Error or Break occured.
LINE CONTROL REGISTER
~~~~~~~~~~~~~~~~~~~~~
The Line Control Register (LCR) is used for changing the settings
on the serial line. It is also used for initializing the modem settings.
Write a byte to the port, containing the following info:
LCR Byte.
Port Toggle Break Condition Parity Stop Bits Data Bits
bit 7 6 5-4-3 2 1-0
~ ~ ~~~~~ ~ ~~~
0 = Normal 0 = Off 0-0-0 = None 0 = 1 0-0 = 5
1 = Set BRD 1 = On 1-0-0 = Odd 1 = 2 0-1 = 6
1-1-0 = Even 1-0 = 7
1-0-1 = Mark 1-1 = 8
1-1-1 = Space
Everything is pretty clear except for the purpose of bits 6 and 7. Bit 6
controls the sending of the break signal. Bit 7 should always be 0, except
if one is changing the baud rate. Then one must set it to one, write to
the BRD and then set it back to zero. One can only write to the BRD if this
bit is set.
MODEM CONTROL REGISTER
~~~~~~~~~~~~~~~~~~~~~~
The MCR is used to control the modem and it's function. Write one
byte to the MCR containing the following info:
MCR Byte.
bit 0 = Set DTR Line
1 = Set RTS Line
2 = User Output #1
3 = User Output #2
4 = UART Loopback
7-6-5 = Unused (Set to 0)
Typically one will set bits 3 through 0 to 1. Bit 4 is used for testing
their routines without another modem, and the other bits are unused, but
should always be set to 0.
LINE STATUS REGISTER
~~~~~~~~~~~~~~~~~~~~
The LSR reports the current status of the RS232 serial line. The
information contained is obtained by reading one byte from the LSR. The
bits and the info associated with each are listed below.
LSR Byte.
bit 0 = Data Received
1 = Overrun Error
2 = Parity Error
3 = Framing Error
4 = Break Detect
5 = THR empty
6 = Transmit Shift Register (TSR) empty
7 = Time Out
The TSR takes the byte in the THR and transmits is one bit at a time. When
bit 0 is set one should read from the RDR, and when bit 5 is set one should
write to the THR. What actions are taken on various errors are left up to
the programmer.
MODEM STATUS REGISTER
~~~~~~~~~~~~~~~~~~~~~
Just like the LSR returns the status of the RS232 line, the MSR
returns the status of the modem. As with other registers, each bit in the
byte one reads from this port contains a certain piece of info.
MSR byte.
bit 0 = Change in CTS
1 = Change in DSR
2 = Change in RI
3 = Change in DCD
4 = CTS on
5 = DSR on
6 = RI on
7 = DCD on
Carrier Detect is achieved by testing bit 7, to see if the line is ringing
test bit 6.
PUTTING IT ALL TOGETHER
~~~~~~~~~~~~~~~~~~~~~~~
One can now use this information about the 8250 UART to start
programming their own modem routines. But before they can do that, they
must learn a little about interrupts and the 8259 PIC (Programmable
Interrupt Controller). This information is necessary to write modem
routines that are not dependant on a slow BIOS.
INTERRUPTS
~~~~~~~~~~
Interrupts are a broad subject, and this is not a reference for them.
For for information on interrupts, one should look at DOS Programmer's
Reference 4th Edition. Although there are two kinds of interrupts - Non-
Maskable and Maskable, maskable interrupts are the only ones that one should
be concerned with. When an interrupt generates, the processor finishes the
current command, and then saves a few variables (the address to return to)
on the stack and jumps to the vector of the interrupt. One can turn off
maskable interrupts with the STI, and back on with CLI. One can not turn
off non-maskable interrupts. Replacing interrupt routines in pascal is very
easy. Include the DOS unit in their program, and use the procedures GetIntVec
and SetIntVec. To replace the interrupt for COM2 (remember it's 0Bh) one
would do this:
GetIntVec($0B, OldInt0Bh);
SetIntVec($0B, NewInt0Bh);
At the end of the program, one MUST restore the interrupt using:
SetIntVec($0B, OldInt0Bh);
Failing to do this will most likely result in a system crash after the
program terminates. Because another interrupt may be called inside another
interrupt at any time, it is necessary to turn off interrupts, as mentioned
above, every once in a while. Remember all this, and programming for the
modem will be much easier ( :) ).
8259 PROGRAMMABLE INTERRUPT CONTROLLER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The 8259 PIC is used by the processor as a gateway for interrupts.
The 8259 decides which interrupts go first and which are currently active.
The order interrupts are processed in in the order of their IRQ number.
Thus, IRQ0 will always be processed before IRQ1 if both are generated at the
same time. Since asyncronous communication uses IRQs, we must instruct the
8259 PIC on when are interrupts should start interrupting, and when they
should stop. When initializing the modem, one must "turn on" the IRQ before
one can start to use it. Turning back off is identical, but don't turn it
off if one is writing door routines! To do either requires one assign the
value contained at the port the value AND the mask. The masks for turning
on and off the 8259 follows.
To Turn On:
mask = (1 shl (IRQ number)) xor $00FF
To Turn Off:
mask = 1 shl (IRQ number)
One must also reset the PIC in the custom interrupt handler after one is
finished with it. That will allow the PIC to process the next interrupt.
To reset the PIC, write 20h to it. This is also refered to as the End Of
Interrupt (EOI) signal. This must also be done after first initializing the
modem. There is another PIC on the 286, allowing the last 8 IRQs (7 - 15).
The second PIC is called the cascade PIC. The addresses for the PIC command
and mask ports are listed next.
8259 PIC command address = 20h
8259 PIC mask address = 21h
Cascade 8259 PIC command address = A0h
Cascade 8259 PIC mask address = A1h
To reset the PIC always write to the command, and for turning off with the
masks always write to the mask. The masks for the cascade PIC are the same
for the other PIC. So the mask for IRQ0 is equal to the mask for IRQ7.
Also, one should write 20h to the cascade PIC as the EOI signal.
INPUT/OUTPUT CONTROL
~~~~~~~~~~~~~~~~~~~~
To keep the text simple, only buffered input will be covered.
Buffered output is a subject of more depth than one can provide in a short
reference. Buffered input is relatively simple, but there are a few things
one must consider. The size of the buffer is very import, make the buffer
to big and one will eat up the datasegment, make the buffer to small and
one will get overruns. A good choice for a general buffer would be in the
range of 4 to 8k. This should allow plenty of room for all incoming data.
Another inportant factor is the type of buffer. For simplicity and ease of
use, a circular input buffer is recommended. A head and a tail point
to the start and end of the buffer, and they will both wrap around when
either go past the end of the buffer, thus making the buffer a kind of
circle. Getting data in the buffer is the primary job of the custom
interrupt routine. Clearing the buffer and reading characters from the
buffer is then as easy as reading a character from an array, and advancing
the head of the buffer. Sending characters over the phone can be
accomplished by waiting for the flow control and then sending the character
to the THR, repeating for every character.
THE INTERRUPT SERVICE ROUTINE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ISR (Interrupt Service Routine) is the backbone for asyncronous
communication. The interrupt is called for every character that comes
through the modem. So in the interrupt one must process these incoming
characters or else they will be lost. Since the the interrupt got called,
one must check the IIR (Interrupt Identification Register) to see what
actually cause the interrupt to be called. Since the interrupt is mainly
dealing with handling the incoming data, and for reasons of simplicity,
flow control will be ommited from the routine but will be discussed later
in this text. Since one is writing to the buffer, and since another
character is likely to come in during this time, one must disable interrupts
for the shortest time possible while writing to the buffer, and then reenable
them so no data is lost. (NOTE: If the ISR is to be contained in a unit, it
must be declared in the unit's interface section as an INTERRUPT procedure.)
After disabling interrupts, checking for data, discarding data if no buffer
space is available, putting the data in the buffer if there is room, and
clearing the RDR if any data error or break occured, one must turn on the
interrupts and issue the EOI signal to the 8259 PIC or both the 8259 PIC
and the cascade PIC if IRQ7 - IRQ15 is used. Here is a sample routine:
const
BaseAddr: array[1 .. 4] of word = ($03F8, $02F8, $03E8, $02E8);
{ Nice array to make finding the base address easy }
var
Buffer: array[1 .. 4096] of char; { A 4k buffer for input }
Temp, { Varible to hold various modem statuses }
CommPort: byte; { Comm Port in use }
Head, { Start of the buffer }
Tail, { End of the buffer }
Size: word; { Size of the buffer }
Cascade: boolean; { For IRQ7 - IRQ15 }
procedure Async_ISR; interrupt; { NOTE: must declare the procedure interrupt }
begin
inline($FB); { STI - Disable interrupts }
Temp := port[BaseAddr[CommPort] + $02]; { Read a byte from the IIR }
if Temp and $06 = $04 then { Character received }
begin
if Head <> Tail then { Make sure there is room in the buffer }
begin
Buffer[Tail] := Chr(port[BaseAddr[CommPort] + $00]); { Read char }
inc(Tail); { Position the Tail for the next char }
if Tail > 4096 then Tail := 0; { If Tail is greater, wrap the buffer }
end
else temp := port[BaseAddr[CommPort] + $00]; { Throw away overruns }
end
else if Temp and $06 = $06 then { Data error or break }
Temp := port[BaseAddr[CommPort] + $00]; { Clear RDR }
inline($FA); { CLI - Enable interrupts }
port[$20] := $20; { Reset the 8259 PIC }
if Cascade then port[$A0] := $20; { Reset the cascade PIC }
end;
First the procedure disables interrupts, then it reads the IIR to find out
what kind of interrupt needs processing. The procedure then masks out bits
2 and 1 and tests it to see if bit 4 is set. If data is received it checks
to make sure there is room in the buffer, and places the character at the
position marked by Tail, otherwise it disregards the character as overrun.
If a data error occured it clears the RDR to make sure no garbage is
received. Finally it enables interrupts and resets the 8259 (and the cascade
if necessary).
SENDING CHARACTERS
~~~~~~~~~~~~~~~~~~
Sending character over the modem is much simpler than getting them.
First one must wait for the flow control and for the UART and then write the
character to the THR. Here's an example:
procedure XmitChar(C: char); { Uses variable and constant declarations from
begin the previous example }
while ((port[BaseAddr[CommPort] + $05] and $20 <> $20) and { Wait for THR }
(port[BaseAddr[CommPort] + $06] and $10 <> $10)) { Wait for CTS }
do ; { Do nothing until CTS and THR empty }
port[BaseAddr[CommPort] + $00] := Ord(C); { Send character }
end;
This waits for the CTS signal and for the THR to be clear and then sends the
character. To send strings just use this in a repeat loop such as:
for x := 1 to length(s) do
XmitChar(s[x]);
READING CHARACTERS
~~~~~~~~~~~~~~~~~~
The actual reading of character takes place in the ISR, but one still
has to get them from the buffer. Just read the character at the head of
the buffer and pass it back. An example:
function RemoteReadKey: char; { Uses var and const from above }
begin
RemoteReadKey := Buffer[Head]; { Get the character }
inc(Head); { Move Head to the next character }
if Head > 4096 then Head := 0; { Wrap Head around if necessary }
dec(Size); { Remove the character }
end;
To find out if a character is waiting is even easier:
function RemoteKeyPressed: boolean; { Uses vars and consts from above }
begin
RemoteKeyPressed := Size > 0; { A key was pressed if there is data in
end; the buffer }
INITIALIZING MODEM PARAMETERS AND OTHER TOPICS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For most cases one can use interrupt 14h function 00h to initialize
modem parameters, but if the baud rate is over 9600, this function will
not work. One must change the BRD themselves. It is a simple matter of
accessing the BRD by setting the LCR bit 7 to 1 and writing to the BRD and
then reseting the LCR bit 7 back to 0. Everything else, clearing buffers,
flushing buffers, formatting input, is all up to the programmer. I have
provided one with enough information to grasp the basis of modem programming
and the I/O involved.
FLOW CONTROL
~~~~~~~~~~~~
Flow control is mainly used to prevent overflow error on today's
high speed modems. CTS/RTS was already covered earlier, but nothing has
been said for XOn/XOff. XOn/XOff will send a certain character (usually
a ^S) when the input buffer has reached a certain percentage of capacity.
This signal is XOff. When the buffer has gone down to another percentage of
capacity, XOn (usually a ^Q) will be sent. It is the programmer's job to
look for XOn/XOff codes and interperate them, as there are no standard ways
to do it as with CTS/RTS. It is also his job to make sure he or she sends
the signals at the appropriate time.
CONCLUSION
~~~~~~~~~~
This text is general, and won't satisfy the needs of advanced modem
programmers. It was written to help those just starting, or thinking about
starting, through the ordeal of finding a book, or read through source not
knowing what some of it does. If one finds any mistakes, please feel free
to contact me via the Pascal FIDONet echo, and he will gladly correct
them. Also, if one would like more information on other related topics,
contact me via the Pascal echo, and I will try to help.
_____________________________________________________----
I hope everyone will find this text useful, and please feel free to
comment or correct anything. I posted it once but it got choped off in
places, so i'm posting it again. Enjoy.
Jack
[Back to FAQ SWAG index] [Back to Main SWAG index] [Original]