.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
|