Contributor: JACK MOFFITT 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