Serials.Mod 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. MODULE Serials; (** AUTHOR "afi"; PURPOSE "Generic serial communication ports driver"; *)
  2. (**
  3. * Designed to support serial communication via the conventional RS232-type serial communication ports COM1 to COM8 and
  4. * 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
  5. * by procedures located in this module.
  6. *
  7. * Usage:
  8. *
  9. * Serials.Show ~ displays a list of all available serial ports
  10. *
  11. * Serials.CloseAllPorts ~ forces closing all serials ports
  12. *
  13. * History:
  14. *
  15. * 20.01.2006 First Release (afi)
  16. * 14.06.2006 Fixed Port.Send, introduced result value for Port.SendChar, implemented termination handler, cleanup (staubesv)
  17. * 26.06.2006 Added charactersSent, characterReceived for tracking (staubesv)
  18. * 04.08.2006 Removed SetPortState from Port interface (staubesv)
  19. *)
  20. IMPORT Streams, Modules, KernelLog, Commands, Machine, Trace;
  21. CONST
  22. Verbose = TRUE;
  23. MaxPorts* = 64;
  24. (** Parity *)
  25. ParNo* = 0; ParOdd* = 1; ParEven* = 2; ParMark* = 3; ParSpace* = 4;
  26. (** Stop bits *)
  27. Stop1* = 1; Stop2* = 2; Stop1dot5* = 3;
  28. (* Serial port default settings *)
  29. DefaultBPS = 115200; DefaultDataBits = 8; DefaultParity = ParNo; DefaultStop = Stop1;
  30. (** Modem control lines *)
  31. DTR* = 0; RTS* = 1; (** output *)
  32. Break* = 2; (** input/output - Bit 6 in LCR *)
  33. DSR* = 3; CTS* = 4; RI* = 5; DCD* = 6; (** input *)
  34. (** Receive error diagnostic *)
  35. OverrunError* = 10;
  36. ParityError* = 11;
  37. FramingError* = 12;
  38. BreakInterrupt* = 13;
  39. (** Error conditions *)
  40. Ok* = 0;
  41. Closed* = -1;
  42. TransportError* = -2; (** Error on transport layer, e.g. USB error in RS-232 over USB *)
  43. (** Errors for Port.Open procedure *)
  44. PortInUse* = 1; NoSuchPort* = 2; WrongBPS* = 3; WrongData* = 4; WrongParity* = 5; WrongStop* = 6;
  45. TYPE
  46. Port* = OBJECT (Streams.Connection);
  47. VAR
  48. name- : ARRAY 6 OF CHAR;
  49. description- : ARRAY 128 OF CHAR;
  50. (** Characters sent/read since port has been opened. Consider these fields read-only! *)
  51. charactersSent*, charactersReceived* : LONGINT;
  52. PROCEDURE Open* (bps, data, parity, stop : LONGINT; VAR res: LONGINT);
  53. END Open;
  54. PROCEDURE Close* ;
  55. END Close;
  56. PROCEDURE SendChar* (ch: CHAR; VAR res : LONGINT);
  57. END SendChar;
  58. (** Send len characters from buf to output, starting at ofs. res is non-zero on error. *)
  59. PROCEDURE Send*(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
  60. VAR i : LONGINT;
  61. BEGIN
  62. i := 0;
  63. WHILE i < len DO
  64. SendChar(buf[ofs + i], res);
  65. IF res # Ok THEN RETURN END;
  66. INC(i)
  67. END
  68. END Send;
  69. PROCEDURE ReceiveChar* (VAR ch: CHAR; VAR res: LONGINT);
  70. END ReceiveChar;
  71. (** Receive size characters into buf, starting at ofs and return the effective number of bytes read in len.
  72. Wait until at least min bytes (possibly zero) are available. res is non-zero on error. *)
  73. PROCEDURE Receive*(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
  74. VAR ch: CHAR;
  75. BEGIN
  76. len := 0;
  77. res := Ok;
  78. WHILE (len < min) DO
  79. ReceiveChar(ch, res);
  80. IF res # Ok THEN RETURN END;
  81. buf[ofs + len] := ch;
  82. INC(len);
  83. END;
  84. WHILE (Available() > 0) & (len < size) DO
  85. ReceiveChar(ch, res);
  86. IF res # Ok THEN RETURN END;
  87. buf[ofs + len] := ch;
  88. INC(len)
  89. END;
  90. END Receive;
  91. PROCEDURE Available*(): LONGINT;
  92. END Available;
  93. (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
  94. PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
  95. END GetPortState;
  96. (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
  97. PROCEDURE ClearMC*(s: SET);
  98. END ClearMC;
  99. (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
  100. PROCEDURE SetMC*(s: SET);
  101. END SetMC;
  102. (** Return the state of the specified modem control lines. s contains
  103. the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
  104. PROCEDURE GetMC*(VAR s: SET);
  105. END GetMC;
  106. PROCEDURE Show*;
  107. BEGIN
  108. KernelLog.String(name); KernelLog.String(" ("); KernelLog.String(description); KernelLog.String(")");
  109. END Show;
  110. END Port;
  111. VAR
  112. (* In this array the RS232-type COM ports are registered in the first 8 array elements.
  113. USB ports equipped with a USB to serial adapter are registered in the next 8 array elements. *)
  114. ports : ARRAY MaxPorts OF Port;
  115. tracePort: Port;
  116. traceChar0: Trace.CharProc;
  117. (** At the disposal of the USB driver modules for hot plug-in of a device. *)
  118. PROCEDURE RegisterPort* (port : Port; CONST description : ARRAY OF CHAR);
  119. VAR name : ARRAY 6 OF CHAR; portNumber : LONGINT;
  120. BEGIN {EXCLUSIVE}
  121. ASSERT(port # NIL);
  122. portNumber := 9;
  123. WHILE (portNumber < LEN(ports)) & (ports[portNumber-1] # NIL) DO INC(portNumber); END;
  124. IF portNumber < LEN(ports) THEN
  125. ports[portNumber-1] := port;
  126. name := "COM";
  127. IF portNumber < 10 THEN
  128. name[3] := CHR(ORD("0") + portNumber);
  129. ELSE
  130. name[3] := CHR(ORD("0") + portNumber DIV 10);
  131. name[4] := CHR(ORD("0") + portNumber MOD 10);
  132. END;
  133. COPY(name, port.name);
  134. COPY(description, port.description);
  135. IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" is now available."); KernelLog.Ln; END;
  136. ELSE
  137. KernelLog.String("Serials: Could not register port: No free slots."); KernelLog.Ln;
  138. END;
  139. END RegisterPort;
  140. (** At the disposal of the USB driver modules for hot plug-in of a device. *)
  141. PROCEDURE UnRegisterPort* (port : Port);
  142. VAR i : LONGINT;
  143. BEGIN {EXCLUSIVE}
  144. i := 0; WHILE (i < LEN(ports)) & (ports[i] # port) DO INC(i); END;
  145. IF i < LEN(ports) THEN
  146. ports[i].Close;
  147. ports[i] := NIL;
  148. IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" has been removed."); KernelLog.Ln; END;
  149. ELSE
  150. KernelLog.String("Serials: Warning: UnRegisterPort: Port not found."); KernelLog.Ln;
  151. END;
  152. END UnRegisterPort;
  153. (** COM1 to COM8 are reserved for RS-232 / V24 communication ports. Other ports will be named COM9, COM9 and so on as needed.
  154. The idea is that the onboard COM ports get the same port numbers as in the BIOS *)
  155. PROCEDURE RegisterOnboardPort*(portNumber : LONGINT; port : Port; CONST name, description : ARRAY OF CHAR);
  156. BEGIN {EXCLUSIVE}
  157. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] = NIL) THEN
  158. ports[portNumber-1] := port;
  159. COPY(name, port.name);
  160. COPY(description, port.description);
  161. IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" is now available."); KernelLog.Ln; END;
  162. ELSE
  163. KernelLog.String("Serials: Warning; Could not register onboard port."); KernelLog.Ln;
  164. END;
  165. END RegisterOnboardPort;
  166. PROCEDURE GetPort* (portNumber : LONGINT) : Port;
  167. VAR port : Port;
  168. BEGIN {EXCLUSIVE}
  169. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
  170. port := ports[portNumber-1];
  171. END;
  172. RETURN port;
  173. END GetPort;
  174. PROCEDURE TraceChar(ch: CHAR);
  175. VAR res: LONGINT;
  176. BEGIN
  177. IF tracePort # NIL THEN
  178. tracePort.SendChar(ch, res);
  179. END;
  180. END TraceChar;
  181. (**
  182. Setup serial port for kernel trace output
  183. portNumber: serial port number; a value < 0 for disabling trace output over an already set up serial port
  184. bps, data, parity, stop: serial port settings
  185. *)
  186. PROCEDURE SetTracePort*(portNumber: LONGINT; bps, data, parity, stop: LONGINT; VAR res: LONGINT);
  187. VAR
  188. isOpen0: BOOLEAN;
  189. bps0, data0, parity0, stop0 : LONGINT;
  190. res1: LONGINT;
  191. BEGIN{EXCLUSIVE}
  192. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
  193. tracePort := ports[portNumber-1];
  194. tracePort.GetPortState(isOpen0, bps0, data0, parity0, stop0);
  195. IF ~isOpen0 OR (bps0 # bps) OR (data0 # data) OR (parity0 # parity) OR (stop0 # stop) THEN
  196. tracePort.Close;
  197. END;
  198. tracePort.Open(bps, data, parity, stop, res);
  199. IF res # 0 THEN
  200. IF isOpen0 THEN tracePort.Open(bps0, data0, parity0, stop0, res1); END;
  201. RETURN;
  202. END;
  203. IF traceChar0 # TraceChar THEN
  204. traceChar0 := Trace.Char;
  205. END;
  206. Machine.Acquire(Machine.TraceOutput);
  207. Trace.Char := TraceChar;
  208. Machine.Release(Machine.TraceOutput);
  209. ELSIF portNumber < 0 THEN
  210. IF tracePort # NIL THEN
  211. Machine.Acquire(Machine.TraceOutput);
  212. Trace.Char := traceChar0;
  213. Machine.Release(Machine.TraceOutput);
  214. tracePort := NIL;
  215. traceChar0 := NIL;
  216. END;
  217. ELSE
  218. res := NoSuchPort;
  219. END;
  220. END SetTracePort;
  221. (** Returns TRUE if a given port is currently used for kernel trace output *)
  222. PROCEDURE IsTracePort*(port: Port): BOOLEAN;
  223. BEGIN
  224. RETURN (tracePort # NIL) & (port = tracePort);
  225. END IsTracePort;
  226. (**
  227. Get serial port parameter from an input stream
  228. The format of the stream is expected to be as [portNumber bps data parity stop]
  229. where
  230. parity = "odd"|"even"|"mark"|"space"|"no"
  231. stop = "1"|"1.5"|"2"
  232. *)
  233. PROCEDURE GetPortParameters*(r: Streams.Reader; VAR portNumber, bps, data, parity, stop: LONGINT; VAR res: LONGINT);
  234. VAR str : ARRAY 32 OF CHAR;
  235. BEGIN
  236. res := 0;
  237. IF ~r.GetInteger(portNumber,FALSE) OR (GetPort(portNumber) = NIL) THEN
  238. res := NoSuchPort; RETURN;
  239. END;
  240. data := DefaultDataBits; parity := DefaultParity; stop := DefaultStop;
  241. IF ~r.GetInteger(bps, FALSE) THEN
  242. bps := DefaultBPS; RETURN;
  243. END;
  244. IF ~r.GetInteger(data, FALSE) THEN
  245. data := DefaultDataBits; RETURN;
  246. END;
  247. IF ~r.GetString(str) THEN
  248. parity := DefaultParity; RETURN;
  249. END;
  250. IF str = "odd" THEN
  251. parity := ParOdd
  252. ELSIF str = "even" THEN
  253. parity := ParEven
  254. ELSIF str = "mark" THEN
  255. parity := ParMark
  256. ELSIF str = "space" THEN
  257. parity := ParSpace
  258. ELSIF str # "no" THEN
  259. res := WrongParity;
  260. RETURN
  261. END;
  262. IF ~r.GetString(str) THEN
  263. stop := DefaultStop; RETURN;
  264. END;
  265. IF str = "1.5" THEN
  266. stop := Stop1dot5
  267. ELSIF str = "2" THEN
  268. stop := Stop2
  269. ELSIF str # "1" THEN
  270. res := WrongStop;
  271. END;
  272. END GetPortParameters;
  273. (**
  274. Setup trace over a given serial port
  275. SetTrace portNumber bps data parity stop~
  276. Set portNumber to a value < 0 for disabling trace output over an already set up serial port
  277. *)
  278. PROCEDURE SetTrace*(context: Commands.Context);
  279. VAR
  280. portNumber, bps, data, parity, stop, res: LONGINT;
  281. BEGIN
  282. GetPortParameters(context.arg, portNumber, bps, data, parity, stop, res);
  283. IF (portNumber >= 0) & (res # 0) THEN
  284. context.result := 1;
  285. context.error.String("Invalid port settings, res="); context.error.Int(res,0); context.error.Ln;
  286. RETURN;
  287. END;
  288. SetTracePort(portNumber, bps, data, parity, stop, res);
  289. IF res # 0 THEN
  290. context.result := 2;
  291. context.error.String("Failed to setup trace port, res="); context.error.Int(res,0); context.error.Ln;
  292. END;
  293. END SetTrace;
  294. PROCEDURE Show*(context : Commands.Context);
  295. VAR port : Port; noPortsAvailable : BOOLEAN; i : LONGINT;
  296. BEGIN {EXCLUSIVE}
  297. noPortsAvailable := TRUE;
  298. context.out.String("Serials: "); context.out.Ln;
  299. FOR i := 0 TO LEN(ports)-1 DO
  300. port := ports[i];
  301. IF port # NIL THEN
  302. noPortsAvailable := FALSE;
  303. context.out.String(port.name); context.out.Char(9X); context.out.String(port.description); context.out.Ln;
  304. END;
  305. END;
  306. IF noPortsAvailable THEN context.out.String("No serial ports found."); END;
  307. context.out.Ln;
  308. END Show;
  309. (** Test serial ports COM1 and if present COM2 with the generic driver *)
  310. PROCEDURE Test*(context : Commands.Context);
  311. VAR
  312. result : LONGINT;
  313. portgotten : Port;
  314. BEGIN
  315. context.out.String ("Testing availability of COM1 and COM2."); context.out.Ln;
  316. context.out.String ("Testing COM1: ");
  317. portgotten := GetPort (1);
  318. IF portgotten # NIL THEN
  319. portgotten.Open (4800, 8, 2, 2, result);
  320. portgotten.Close ();
  321. context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln;
  322. context.out.String ("Testing COM2: ");
  323. portgotten := GetPort (2);
  324. IF portgotten # NIL THEN
  325. portgotten.Open (9600, 8, 2, 2, result);
  326. portgotten.Close ();
  327. context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln
  328. ELSE
  329. context.out.String ("Could not get port 2"); context.out.Ln
  330. END
  331. ELSE
  332. context.out.String ("Could not get port 1"); context.out.Ln;
  333. context.out.String ("Not testing COM2 as it is likely not to work either."); context.out.Ln;
  334. END;
  335. END Test;
  336. (** Close all serial ports *)
  337. PROCEDURE CloseAllPorts*(context : Commands.Context);
  338. VAR portNbr : LONGINT;
  339. BEGIN {EXCLUSIVE}
  340. FOR portNbr := 0 TO LEN(ports)-1 DO
  341. IF ports[portNbr] # NIL THEN
  342. ports[portNbr].Close;
  343. END;
  344. END;
  345. IF (context # NIL) THEN context.out.String("All serial ports closed"); context.out.Ln; END;
  346. END CloseAllPorts;
  347. PROCEDURE Cleanup;
  348. BEGIN
  349. CloseAllPorts(NIL);
  350. END Cleanup;
  351. BEGIN
  352. Modules.InstallTermHandler(Cleanup);
  353. END Serials.
  354. SystemTools.Free V24 Serials ~
  355. V24.Install ~
  356. Serials.Test ~
  357. Serials.Show ~