123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- MODULE Serials; (** AUTHOR "afi"; PURPOSE "Generic serial communication ports driver"; *)
- (**
- * Designed to support serial communication via the conventional RS232-type serial communication ports COM1 to COM8 and
- * via a USB port with a USB to serial adapter. Hot plug-in of the device is possible. For that reason, devices are registered/unregistered
- * by procedures located in this module.
- *
- * Usage:
- *
- * Serials.Show ~ displays a list of all available serial ports
- *
- * Serials.CloseAllPorts ~ forces closing all serials ports
- *
- * History:
- *
- * 20.01.2006 First Release (afi)
- * 14.06.2006 Fixed Port.Send, introduced result value for Port.SendChar, implemented termination handler, cleanup (staubesv)
- * 26.06.2006 Added charactersSent, characterReceived for tracking (staubesv)
- * 04.08.2006 Removed SetPortState from Port interface (staubesv)
- *)
- IMPORT Streams, Modules, KernelLog, Commands, Machine, Trace;
- CONST
- Verbose = TRUE;
- MaxPorts* = 64;
- (** Parity *)
- ParNo* = 0; ParOdd* = 1; ParEven* = 2; ParMark* = 3; ParSpace* = 4;
- (** Stop bits *)
- Stop1* = 1; Stop2* = 2; Stop1dot5* = 3;
- (* Serial port default settings *)
- DefaultBPS = 115200; DefaultDataBits = 8; DefaultParity = ParNo; DefaultStop = Stop1;
- (** Modem control lines *)
- DTR* = 0; RTS* = 1; (** output *)
- Break* = 2; (** input/output - Bit 6 in LCR *)
- DSR* = 3; CTS* = 4; RI* = 5; DCD* = 6; (** input *)
- (** Receive error diagnostic *)
- OverrunError* = 10;
- ParityError* = 11;
- FramingError* = 12;
- BreakInterrupt* = 13;
- (** Error conditions *)
- Ok* = 0;
- Closed* = -1;
- TransportError* = -2; (** Error on transport layer, e.g. USB error in RS-232 over USB *)
- (** Errors for Port.Open procedure *)
- PortInUse* = 1; NoSuchPort* = 2; WrongBPS* = 3; WrongData* = 4; WrongParity* = 5; WrongStop* = 6;
- TYPE
- Port* = OBJECT (Streams.Connection);
- VAR
- name- : ARRAY 6 OF CHAR;
- description- : ARRAY 128 OF CHAR;
- (** Characters sent/read since port has been opened. Consider these fields read-only! *)
- charactersSent*, charactersReceived* : LONGINT;
- PROCEDURE Open* (bps, data, parity, stop : LONGINT; VAR res: LONGINT);
- END Open;
- PROCEDURE Close* ;
- END Close;
- PROCEDURE SendChar* (ch: CHAR; VAR res : LONGINT);
- END SendChar;
- (** Send len characters from buf to output, starting at ofs. res is non-zero on error. *)
- PROCEDURE Send*(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
- VAR i : LONGINT;
- BEGIN
- i := 0;
- WHILE i < len DO
- SendChar(buf[ofs + i], res);
- IF res # Ok THEN RETURN END;
- INC(i)
- END
- END Send;
- PROCEDURE ReceiveChar* (VAR ch: CHAR; VAR res: LONGINT);
- END ReceiveChar;
- (** Receive size characters into buf, starting at ofs and return the effective number of bytes read in len.
- Wait until at least min bytes (possibly zero) are available. res is non-zero on error. *)
- PROCEDURE Receive*(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
- VAR ch: CHAR;
- BEGIN
- len := 0;
- res := Ok;
- WHILE (len < min) DO
- ReceiveChar(ch, res);
- IF res # Ok THEN RETURN END;
- buf[ofs + len] := ch;
- INC(len);
- END;
- WHILE (Available() > 0) & (len < size) DO
- ReceiveChar(ch, res);
- IF res # Ok THEN RETURN END;
- buf[ofs + len] := ch;
- INC(len)
- END;
- END Receive;
- PROCEDURE Available*(): LONGINT;
- END Available;
- (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
- PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
- END GetPortState;
- (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
- PROCEDURE ClearMC*(s: SET);
- END ClearMC;
- (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
- PROCEDURE SetMC*(s: SET);
- END SetMC;
- (** Return the state of the specified modem control lines. s contains
- the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
- PROCEDURE GetMC*(VAR s: SET);
- END GetMC;
- PROCEDURE Show*;
- BEGIN
- KernelLog.String(name); KernelLog.String(" ("); KernelLog.String(description); KernelLog.String(")");
- END Show;
- END Port;
- VAR
- (* In this array the RS232-type COM ports are registered in the first 8 array elements.
- USB ports equipped with a USB to serial adapter are registered in the next 8 array elements. *)
- ports : ARRAY MaxPorts OF Port;
- tracePort: Port;
- traceChar0: Trace.CharProc;
- (** At the disposal of the USB driver modules for hot plug-in of a device. *)
- PROCEDURE RegisterPort* (port : Port; CONST description : ARRAY OF CHAR);
- VAR name : ARRAY 6 OF CHAR; portNumber : LONGINT;
- BEGIN {EXCLUSIVE}
- ASSERT(port # NIL);
- portNumber := 9;
- WHILE (portNumber < LEN(ports)) & (ports[portNumber-1] # NIL) DO INC(portNumber); END;
- IF portNumber < LEN(ports) THEN
- ports[portNumber-1] := port;
- name := "COM";
- IF portNumber < 10 THEN
- name[3] := CHR(ORD("0") + portNumber);
- ELSE
- name[3] := CHR(ORD("0") + portNumber DIV 10);
- name[4] := CHR(ORD("0") + portNumber MOD 10);
- END;
- COPY(name, port.name);
- COPY(description, port.description);
- IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" is now available."); KernelLog.Ln; END;
- ELSE
- KernelLog.String("Serials: Could not register port: No free slots."); KernelLog.Ln;
- END;
- END RegisterPort;
- (** At the disposal of the USB driver modules for hot plug-in of a device. *)
- PROCEDURE UnRegisterPort* (port : Port);
- VAR i : LONGINT;
- BEGIN {EXCLUSIVE}
- i := 0; WHILE (i < LEN(ports)) & (ports[i] # port) DO INC(i); END;
- IF i < LEN(ports) THEN
- ports[i].Close;
- ports[i] := NIL;
- IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" has been removed."); KernelLog.Ln; END;
- ELSE
- KernelLog.String("Serials: Warning: UnRegisterPort: Port not found."); KernelLog.Ln;
- END;
- END UnRegisterPort;
- (** COM1 to COM8 are reserved for RS-232 / V24 communication ports. Other ports will be named COM9, COM9 and so on as needed.
- The idea is that the onboard COM ports get the same port numbers as in the BIOS *)
- PROCEDURE RegisterOnboardPort*(portNumber : LONGINT; port : Port; CONST name, description : ARRAY OF CHAR);
- BEGIN {EXCLUSIVE}
- IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] = NIL) THEN
- ports[portNumber-1] := port;
- COPY(name, port.name);
- COPY(description, port.description);
- IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" is now available."); KernelLog.Ln; END;
- ELSE
- KernelLog.String("Serials: Warning; Could not register onboard port."); KernelLog.Ln;
- END;
- END RegisterOnboardPort;
- PROCEDURE GetPort* (portNumber : LONGINT) : Port;
- VAR port : Port;
- BEGIN {EXCLUSIVE}
- IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
- port := ports[portNumber-1];
- END;
- RETURN port;
- END GetPort;
- PROCEDURE TraceChar(ch: CHAR);
- VAR res: LONGINT;
- BEGIN
- IF tracePort # NIL THEN
- tracePort.SendChar(ch, res);
- END;
- END TraceChar;
- (**
- Setup serial port for kernel trace output
- portNumber: serial port number, or 0 to disable trace output, or a negative value for rolling back to the initial trace output configuration
- bps, data, parity, stop: serial port settings
- *)
- PROCEDURE SetTracePort*(portNumber: LONGINT; bps, data, parity, stop: LONGINT; VAR res: LONGINT);
- VAR
- isOpen0: BOOLEAN;
- bps0, data0, parity0, stop0 : LONGINT;
- res1: LONGINT;
- port: Port;
- BEGIN{EXCLUSIVE}
- IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
- port := ports[portNumber-1];
- port.GetPortState(isOpen0, bps0, data0, parity0, stop0);
- (* do not close the port if the current port settings match *)
- IF ~isOpen0 OR (bps0 # bps) OR (data0 # data) OR (parity0 # parity) OR (stop0 # stop) THEN
- port.Close;
- port.Open(bps, data, parity, stop, res);
- IF res # 0 THEN
- IF isOpen0 THEN port.Open(bps0, data0, parity0, stop0, res1); END;
- RETURN;
- END;
- END;
- IF Trace.Char # TraceChar THEN
- traceChar0 := Trace.Char;
- END;
- TRACE(traceChar0);
- Machine.Acquire(Machine.TraceOutput);
- tracePort := port;
- Trace.Char := TraceChar;
- Machine.Release(Machine.TraceOutput);
- TRACE(Trace.Char);
- ELSIF portNumber = 0 THEN
- tracePort := NIL;
- IF Trace.Char # TraceChar THEN
- traceChar0 := Trace.Char;
- END;
- Trace.Char := TraceChar;
- ELSIF portNumber < 0 THEN
- IF traceChar0 # NIL THEN
- Machine.Acquire(Machine.TraceOutput);
- Trace.Char := traceChar0;
- Machine.Release(Machine.TraceOutput);
- tracePort := NIL;
- traceChar0 := NIL;
- END;
- ELSE
- res := NoSuchPort;
- END;
- END SetTracePort;
- (** Returns TRUE if a given port is currently used for kernel trace output *)
- PROCEDURE IsTracePort*(port: Port): BOOLEAN;
- BEGIN
- RETURN (tracePort # NIL) & (port = tracePort);
- END IsTracePort;
- (**
- Get serial port parameter from an input stream
- The format of the stream is expected to be as [portNumber bps data parity stop]
- where
- parity = "odd"|"even"|"mark"|"space"|"no"
- stop = "1"|"1.5"|"2"
- *)
- PROCEDURE GetPortParameters*(r: Streams.Reader; VAR portNumber, bps, data, parity, stop: LONGINT; VAR res: LONGINT);
- VAR str : ARRAY 32 OF CHAR;
- BEGIN
- res := 0;
- IF ~r.GetInteger(portNumber,FALSE) OR (GetPort(portNumber) = NIL) THEN
- res := NoSuchPort; RETURN;
- END;
- data := DefaultDataBits; parity := DefaultParity; stop := DefaultStop;
- IF ~r.GetInteger(bps, FALSE) THEN
- bps := DefaultBPS; RETURN;
- END;
- IF ~r.GetInteger(data, FALSE) THEN
- data := DefaultDataBits; RETURN;
- END;
- IF ~r.GetString(str) THEN
- parity := DefaultParity; RETURN;
- END;
- IF str = "odd" THEN
- parity := ParOdd
- ELSIF str = "even" THEN
- parity := ParEven
- ELSIF str = "mark" THEN
- parity := ParMark
- ELSIF str = "space" THEN
- parity := ParSpace
- ELSIF str # "no" THEN
- res := WrongParity;
- RETURN
- END;
- IF ~r.GetString(str) THEN
- stop := DefaultStop; RETURN;
- END;
- IF str = "1.5" THEN
- stop := Stop1dot5
- ELSIF str = "2" THEN
- stop := Stop2
- ELSIF str # "1" THEN
- res := WrongStop;
- END;
- END GetPortParameters;
- (**
- Setup trace over a given serial port
- SetTrace portNumber bps data parity stop~
- Set portNumber to a value < 0 for disabling trace output over an already set up serial port
- *)
- PROCEDURE SetTrace*(context: Commands.Context);
- VAR
- portNumber, bps, data, parity, stop, res: LONGINT;
- BEGIN
- GetPortParameters(context.arg, portNumber, bps, data, parity, stop, res);
- IF (portNumber >= 0) & (res # 0) THEN
- context.result := 1;
- context.error.String("Invalid port settings, res="); context.error.Int(res,0); context.error.Ln;
- RETURN;
- END;
- SetTracePort(portNumber, bps, data, parity, stop, res);
- IF res # 0 THEN
- context.result := 2;
- context.error.String("Failed to setup trace port, res="); context.error.Int(res,0); context.error.Ln;
- END;
- END SetTrace;
- PROCEDURE Show*(context : Commands.Context);
- VAR port : Port; noPortsAvailable : BOOLEAN; i : LONGINT;
- BEGIN {EXCLUSIVE}
- noPortsAvailable := TRUE;
- context.out.String("Serials: "); context.out.Ln;
- FOR i := 0 TO LEN(ports)-1 DO
- port := ports[i];
- IF port # NIL THEN
- noPortsAvailable := FALSE;
- context.out.String(port.name); context.out.Char(9X); context.out.String(port.description); context.out.Ln;
- END;
- END;
- IF noPortsAvailable THEN context.out.String("No serial ports found."); END;
- context.out.Ln;
- END Show;
- (** Test serial ports COM1 and if present COM2 with the generic driver *)
- PROCEDURE Test*(context : Commands.Context);
- VAR
- result : LONGINT;
- portgotten : Port;
- BEGIN
- context.out.String ("Testing availability of COM1 and COM2."); context.out.Ln;
- context.out.String ("Testing COM1: ");
- portgotten := GetPort (1);
- IF portgotten # NIL THEN
- portgotten.Open (4800, 8, 2, 2, result);
- portgotten.Close ();
- context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln;
- context.out.String ("Testing COM2: ");
- portgotten := GetPort (2);
- IF portgotten # NIL THEN
- portgotten.Open (9600, 8, 2, 2, result);
- portgotten.Close ();
- context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln
- ELSE
- context.out.String ("Could not get port 2"); context.out.Ln
- END
- ELSE
- context.out.String ("Could not get port 1"); context.out.Ln;
- context.out.String ("Not testing COM2 as it is likely not to work either."); context.out.Ln;
- END;
- END Test;
- (** Close all serial ports *)
- PROCEDURE CloseAllPorts*(context : Commands.Context);
- VAR portNbr : LONGINT;
- BEGIN {EXCLUSIVE}
- FOR portNbr := 0 TO LEN(ports)-1 DO
- IF ports[portNbr] # NIL THEN
- ports[portNbr].Close;
- END;
- END;
- IF (context # NIL) THEN context.out.String("All serial ports closed"); context.out.Ln; END;
- END CloseAllPorts;
- PROCEDURE Cleanup;
- BEGIN
- CloseAllPorts(NIL);
- END Cleanup;
- BEGIN
- Modules.InstallTermHandler(Cleanup);
- END Serials.
- SystemTools.Free V24 Serials ~
- V24.Install ~
- Serials.Test ~
- Serials.Show ~
|