serial string driver
As writing a serial driver seems to be a problem, the following code is a sample
string driver.
{
StrDrv - uart driver 16550
----------------------------------------------------
It is optimized for a 16550 UART in the PC, but
should work also with a 8250 UART. - not yet tested
Just below IMPLEMENTATION there is <$DEFINE U16550>,
which enables codegeneration for the 16550, which is
strongly recommended.
There is a VAR Is16550 locally to this unit, that is
set at PROC UARTInit, it reflects the UART detected.
The detection is off when conditionally disabled.
----------------------------------------------------
It is set for COM1, IRQ4, 8bit, 1stop, no parity
----------------------------------------------------
Furthermore it supports RTS/CTS handshake ,
that the other side may stop the PC from sending.
It then activates the CTS-line(-12V).
Upon a trailing edge (-12V to +12V), the Driver resumes
transmitting. The levels are assumed after the drivers,
that means on the cable.
----------------------------------------------------
Benchmarks : on a 386/16MHz
At 9600 full speed tx and rx : ( shorted )
real mode :5% CPU time
protected mode :8% CPU time
----------------------------------------------------
The interface is PROC PutBuffer,GetBuffer, which
assumes a STRING.
The string is as follows:
[0]:stringlength
Note:
MMUDRV.InitDrv; < only once >
rep
MMUDRV.PutBuffer(@p);
Wait(RxSema);
MMUDRV.GetBuffer(q); < either >
end rep
q: reply conforms the above structure
----------------------------------------------------
----------------------------------------------------
New from mmudrv2.pas :
Tx is timed, this reduces interrupts, as the tx is
always on. further the txsema is not required anymore.
Now putbuffer includes a timed wait until the new message
fits into the buffer. This allows the senders to fill
the buffer, where the tx has maximum performance, as
the txirq then moves 16 bytes at a time.
The txbuffer has 256 bytes
----------------------------------------------------
Created :27/sept/93
Last Update :6/feb/96
Is updated version of :mmudriv.pas,mmudrv2.pas,mmudrv.pas
----------------------------------------------------
TestFile is :DRVTEST.PAS
----------------------------------------------------
}
UNIT StrDrv;
INTERFACE
USES RTKernel,OPString;
TYPE StrPtr=^STRING;
PROCEDURE InitDrv(baud:LONGINT;Fifosize:BYTE);
PROCEDURE PutBuffer(p:StrPtr); { p is @String }
PROCEDURE GetBuffer(VAR p:STRING); { p is a string }
FUNCTION IsLineOpen:BOOLEAN;
{ the rest is for debug }
PROCEDURE WriteTx;
PROCEDURE WriteRx;
PROCEDURE WriteCStr(p:StrPtr); { write string passed to/from driver }
PROCEDURE WriteUARTState;
VAR
RxSema1 :RTKernel.Semaphore;
IMPLEMENTATION
{===========================================================}
{ The following defines are disabled by a dot : .$DEFINE }
{$DEFINE U16550 } { enables 16550 UART }
{-------------------------------------------------------}
CONST
{ UARTAdress for COM1 }
RBR1=$3F8;THR1=$3F8;IER1=$3F9;IIR1=$3FA;FCR1=$3FA;LCR1=$3FB;
MCR1=$3FC;LSR1=$3FD;MSR1=$3FE;DLL1=$3F8;DLM1=$3F9;
{ UARTAdress for COM2 }
RBR2=$2F8;THR2=$2F8;IER2=$2F9;IIR2=$2FA;FCR2=$2FA;LCR2=$2FB;
MCR2=$2FC;LSR2=$2FD;MSR2=$2FE;DLL2=$2F8;DLM2=$2F9;
CONST
RxBufferSize=256; { must be, as byte wraps }
TxBufferSize=256; { must be, as byte wraps }
{ TxDepth=16; } { nr of bytes being filled at TxIRQ <=16 }
VAR
ExitSave :POINTER;
RxBuffer1 :ARRAY[0..RxBufferSize-1]OF BYTE;
ErrorBuffer1:ARRAY[0..RxBufferSize-1]OF BYTE;
RxWrite1,RxRead1,RxFree1 :BYTE;
TxBuffer1 :ARRAY[0..TxBufferSize-1]OF BYTE;
TxWrite1,TxRead1,TxFree1 :BYTE;
TxOnIRQ1,TxStop1,RxStop1 :BOOLEAN; { set during message }
Is16550 :BOOLEAN;
TxDepth:BYTE; { nr of bytes being filled at TxIRQ <=16 }
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE EnableInterrupts; INLINE($FB);
PROCEDURE DisableInterrupts; INLINE($FA);
{$F+}
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
{$F+,S-,R-} { far, not stack chk, no range chk }
PROCEDURE UARTHandler1;INTERRUPT;
VAR td,iirc,msrc :BYTE;
BEGIN
iirc:=Port[IIR1] AND $0F; { get interrupt source }
Port[MCR1]:=Port[MCR1] AND $07; { switch interrupts of UART off }
msrc:=Port[MSR1]; { get modem status reg }
CASE IIRC OF
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
$06: { line IRQ }
BEGIN
td:=port[LSR1]AND $71; { accept only OV,PA,FR,FI }
ErrorBuffer1[RxWrite1]:=td;
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
{ Note :for a 16550 UART, the FIFO is emptied.
}
$04,$0C: { rx data avail IRQ }
BEGIN
{$IFDEF U16550}
WHILE ((port[LSR1] AND $01)<>0) DO { read 16550 FIFO }
BEGIN
{$ENDIF}
td:=port[RBR1];
RxBuffer1[RxWrite1]:=td;
Inc(RxWrite1);
Dec(RxFree1);
IF (RxFree1<16) THEN RxStop1:=TRUE ELSE RxStop1:=FALSE;
{$IFDEF U16550}
END; { while data }
{$ENDIF}
IF RxStop1 THEN
port[MCR1]:=port[MCR1] and $FC; { DTR/RTS low }
{ ELSE
port[MCR1]:=port[MCR1] or $03;} { DTR/RTS high }
END;
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
{ as long as the txbuffer is not empty, another byte is put to the
transmitter. The 16550 UART is fed with up to 16 bytes.
should the CTS signal stop, nothing is done
}
$02: { tx empty IRQ }
BEGIN
IF (TxRead1=TxWrite1) THEN TxOnIRQ1:=FALSE { last byte sent }
ELSE { TxRead<>TxWrite }
BEGIN
TxOnIRQ1:=TRUE;
IF (((msrc AND $10)=0)) THEN TxStop1:=TRUE; { cts signals stop }
IF (not TxStop1) THEN
BEGIN
{$IFDEF U16550}
td:=0;
REPEAT
{$ENDIF}
inc(td);
Port[THR1]:=TxBuffer1[TxRead1];
Inc(TxRead1); Inc(TxFree1);
{$IFDEF U16550}
UNTIL (Not Is16550)OR(TxRead1=TxWrite1)OR(td=TxDepth);
{$ENDIF}
END;
END; { txonirq and not stop }
END; { txempty }
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
{ The CTS stops transmitting (TxStop:=TRUE). When the CTS disappears
the transmitter resumes action
}
$00: { modem status IRQ }
BEGIN
IF (((msrc and $11)=$01)) THEN { cts signals stop }
BEGIN
TxStop1:=TRUE;
END
ELSE
BEGIN
IF (((msrc AND $11)=$11)) THEN { cts signals ready again }
BEGIN
TxStop1:=FALSE;
IF (TxRead1<>TxWrite1) THEN
BEGIN
TxOnIRQ1:=TRUE;
IF ((Port[LSR1] and $20)<>0) THEN { THR1e empty }
BEGIN
{$IFDEF U16550}
td:=0;
REPEAT
{$ENDIF}
inc(td);
Port[THR1]:=TxBuffer1[TxRead1];
Inc(TxRead1); Inc(TxFree1); { includes wrap }
{$IFDEF U16550}
UNTIL (Not Is16550)OR(TxRead1=TxWrite1)OR(td=TxDepth);
{$ENDIF}
END;
END; { txread<>txwrite }
END; { cts signal }
END;
END; { modem line }
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
ELSE {b:=iirc};
END; { case }
RTKernel.EndOfInterrupt;
Port[MCR1]:=Port[MCR1] OR $08; { switch interrupts of UART on again }
END; { proc UARTHandler }
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
PROCEDURE IRQInit;
VAR i :WORD;
BEGIN
FOR i:=0 TO RxBufferSize-1 DO RxBuffer1[i]:=0;
FOR i:=0 TO TxBufferSize-1 DO TxBuffer1[i]:=0;
FOR i:=0 TO RxBufferSize-1 DO ErrorBuffer1[i]:=0;
TxWrite1:=0;TxRead1:=0;
RxFree1:=RxBufferSize-16;
TxFree1:=TxBufferSize-4;
TxOnIRQ1:=FALSE;
TxStop1:=FALSE; { tx stops on CTS }
RxStop1:=FALSE; { signal stop on DTR }
RxRead1:=0;RxWrite1:=0;
END; { proc IRQInit }
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
PROCEDURE PICInit;
BEGIN
SetIRQHandler(4,@UARTHandler1);
IRQTopPriority(4,8);
EnableIRQ(4);
END;
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
PROCEDURE PICDone;
BEGIN
DisableIRQ(4);
END; { proc PICDone }
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
{ initializes 16550 or 16450 or 8250 UART }
PROCEDURE UARTInit(baud:WORD;Fifosize:BYTE);
VAR Bd :WORD;
Tb :BYTE;
BEGIN
Port[IER1]:=$00; { no interrupts }
Port[LCR1]:=$80;
CASE baud OF
115 :BEGIN Port[DLL1]:=1; Port[DLM1]:=0; END;
56000:BEGIN Port[DLL1]:=2; Port[DLM1]:=0; END;
38400:BEGIN Port[DLL1]:=3; Port[DLM1]:=0; END;
19200:BEGIN Port[DLL1]:=6; Port[DLM1]:=0; END;
9600 :BEGIN Port[DLL1]:=12; Port[DLM1]:=0; END;
4800 :BEGIN Port[DLL1]:=24; Port[DLM1]:=0; END;
2400 :BEGIN Port[DLL1]:=48; Port[DLM1]:=0; END;
1200 :BEGIN Port[DLL1]:=96; Port[DLM1]:=0; END;
600 :BEGIN Port[DLL1]:=lo(192); Port[DLM1]:=hi(192); END;
300 :BEGIN Port[DLL1]:=lo(384); Port[DLM1]:=hi(384); END;
150 :BEGIN Port[DLL1]:=lo(768); Port[DLM1]:=hi(768); END;
110 :BEGIN Port[DLL1]:=lo(1047);Port[DLM1]:=hi(1047); END;
75 :BEGIN Port[DLL1]:=lo(1536);Port[DLM1]:=hi(1536); END;
50 :BEGIN Port[DLL1]:=lo(2304);Port[DLM1]:=hi(2304); END;
ELSE BEGIN Port[DLL1]:=96;Port[DLM1]:=0; END; { 1200 }
END;
Port[LCR1]:=$03; { 8 bit 1 stop no parity }
{$IFDEF U16550}
CASE Fifosize OF { set FIFO incl clear }
1 :Port[FCR1]:=$07;
4 :Port[FCR1]:=$47;
8 :Port[FCR1]:=$87;
14 :Port[FCR1]:=$C7;
ELSE Port[FCR1]:=$47;
END;
TxDepth:=FifoSize;
{$ENDIF}
TB:=Port[RBR1]; { empty receiver }
TB:=Port[MSR1]; { clear MSR irq }
TB:=Port[LSR1]; { clear LSR IRQ }
TB:=Port[IIR1]; { clear THR1 irq }
Port[MCR1]:=$0C; { RTS off,DTR off, out1+2 on }
Port[IER1]:=$0F; { enable Rx, Tx ,line and modemstatus irq }
{$IFDEF U16550}
Is16550:=((Port[IIR1] AND $C0)=$C0); { detect 16550 }
{$ELSE}
Is16550:=FALSE;
{$ENDIF}
END; { proc UARTInit }
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
PROCEDURE UARTDone;
VAR tb :BYTE;
BEGIN
Port[MCR1]:=0;
PORT[IER1]:=0;{ disable interrupts }
TB:=Port[RBR1];
{$IFDEF U16550}
Port[FCR1]:=0;
{$ENDIF}
END; { proc UARTDone }
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
FUNCTION IsLineOpen:BOOLEAN;
VAR b:BYTE;
BEGIN
b:=Port[MSR1];
IsLineOpen:=(((Port[MSR1] AND $10)=$10));
END;
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
{ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
GetBuffer this is the usual accessproc to the Rxbuffer
it copies the Ringbuffer to the specified region
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ}
PROCEDURE GetBuffer(VAR p:STRING);
VAR XStart,XEnd,XSize,ASize,BSize:BYTE;
BEGIN
XStart:=RxRead1;XEnd:=RxWrite1;
IF (XEnd>XStart) THEN
BEGIN
XSize:=XEnd-XStart;
Move(RxBuffer1[XStart],p[1],XSize);
END
ELSE
BEGIN
XSize:=RxBufferSize-XStart+XEnd;
ASize:=RxBufferSize-XStart;
Move(RxBuffer1[XStart],p[1],ASize);
BSize:=XSize-ASize;
Move(RxBuffer1[0],p[ASize+1],BSize);
END;
Inc(RxRead1,XSize);
Inc(RxFree1,XSize);
IF (RxFree1>16)AND RXStop1 THEN
BEGIN
RxStop1:=FALSE;
port[MCR1]:=port[MCR1] or $03; { DTR/RTS high }
END;
p[0]:=chr(XSize);
END; { proc GetBuffer }
{ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ}
PROCEDURE PutBuffer(p:StrPtr);
VAR s,t:BYTE;
d:WORD;
BEGIN
s:=ord(p^[0]); { length of block }
WHILE (s>TxFree1) DO { packet too big to fit into buffer }
BEGIN
RTKERNEL.Delay(1);
IF (Not TxStop1) THEN { transmission enabled }
BEGIN
IF ((Port[LSR1]AND $20)<>0)THEN { thre empty }
BEGIN
TxOnIRQ1:=TRUE;
Port[THR1]:=TxBuffer1[TxRead1];
Inc(TxRead1); Inc(TxFree1); { includes wrap }
END;
END;
END;
d:=TxBufferSize-TxWrite1; { space to the end of the buffer }
IF (d>=s) THEN { it fits }
BEGIN
move(p^[1],TxBuffer1[TxWrite1],s);
inc(TxWrite1,s);
END
ELSE { it doesn't fit }
BEGIN
move(p^[1],TxBuffer1[TxWrite1],d);
TxWrite1:=0;
move(p^[d+1],TxBuffer1[0],s-d);
TxWrite1:=s-d;
END;
Dec(TxFree1,s);
{ block is now in buffer }
IF (Not TxStop1)AND(not TxOnIRQ1) THEN { cts is false }
BEGIN
IF ((Port[LSR1]AND $20)<>0)THEN { thre empty }
BEGIN
Port[THR1]:=TxBuffer1[TxRead1];
Inc(TxRead1); Inc(TxFree1); { includes wrap }
END;
END;
TxOnIRQ1:=TRUE;
END;
{-------------------------------------------------------}
PROCEDURE InitDrv(baud:LONGINT;Fifosize:BYTE);
BEGIN
IRQInit;
UARTInit(baud,Fifosize);
PICInit;
Port[MCR1]:=$0F; { RTS on,DTR on, out1+2 on }
{$IFDEF U16550}
IF (not IS16550) THEN
BEGIN
Writeln('WARNING : There is no 16550 UART on COM1');
Writeln(' Due to continous 9600, it reduces CPU load');
Writeln(' by buffering. The program may not work properly');
RTKernel.Delay(100);
END;
{$ENDIF}
END; { const NetworkDriver.Init }
{-------------------------------------------------------}
PROCEDURE WriteTx;
VAR i,j:WORD;
BEGIN
WriteLn('w: ',OPSTRING.hexw(txwrite1),' r: ',OPSTRING.hexw(txread1));
i:=0;
REPEAT
j:=0;
REPEAT
inc(j);
Write(OPSTRING.HexB(TxBuffer1[i]),' ');
inc(i);
UNTIL (j=16) or (i=TxBufferSize);
Writeln;
UNTIL (i=TxBufferSize);
Writeln;
END;
PROCEDURE WriteRx;
VAR i,j:WORD;
BEGIN
WriteLn('w: ',OPSTRING.hexw(Rxwrite1),' r: ',hexw(rxread1));
i:=0;
REPEAT
j:=0;
REPEAT
inc(j);
Write(OPSTRING.HexB(RxBuffer1[i]),' ');
inc(i);
UNTIL (j=16) or (i=RxBufferSize);
Writeln;
UNTIL (i=RxBufferSize);
Writeln;
END;
PROCEDURE WriteCStr(p:StrPtr);
VAR i :BYTE;
BEGIN
FOR i:=1 TO length(p^) DO Write(hexb(ord(p^[i])));
END;
PROCEDURE WriteUARTState;
BEGIN
Writeln;
Write('RBR1',hexb(port[RBR1]),' ');
Write('IER1',hexb(port[IER1]),' ');
Write('IIR1',hexb(port[IIR1]),' ');
Write('LCR1',hexb(port[LCR1]),' ');
Write('MCR1',hexb(port[MCR1]),' ');
Write('LSR1',hexb(port[LSR1]),' ');
Write('MSR1',hexb(port[MSR1]),' ');
END;
{-------------------------------------------------------}
{$F+,S-}
PROCEDURE MyExitProc;
BEGIN
ExitProc:=ExitSave;
Uartdone;
PICdone;
END;
{-------------------------------------------------------}
BEGIN { autoinit of this unit}
ExitSave:=ExitProc;
ExitProc:=@MyExitProc;
InitSemaphore(RxSema1,Counting,0,'Rxsema');
END. { unit }
home
last updated: 21.nov.99
Copyright (99,2000) Ing.Büro R.Tschaggelar