Serials.Mod 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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. TimeoutExpired* = -3; (** Timeout expired *)
  44. (** Errors for Port.Open procedure *)
  45. PortInUse* = 1; NoSuchPort* = 2; WrongBPS* = 3; WrongData* = 4; WrongParity* = 5; WrongStop* = 6;
  46. TYPE
  47. Port* = OBJECT (Streams.Connection);
  48. VAR
  49. name- : ARRAY 6 OF CHAR;
  50. description- : ARRAY 128 OF CHAR;
  51. (** Characters sent/read since port has been opened. Consider these fields read-only! *)
  52. charactersSent*, charactersReceived* : LONGINT;
  53. PROCEDURE Open* (bps, data, parity, stop : LONGINT; VAR res: LONGINT);
  54. END Open;
  55. PROCEDURE Close* ;
  56. END Close;
  57. PROCEDURE SendChar* (ch: CHAR; VAR res : LONGINT);
  58. END SendChar;
  59. (** Send len characters from buf to output, starting at ofs. res is non-zero on error. *)
  60. PROCEDURE Send*(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
  61. VAR i : LONGINT;
  62. BEGIN
  63. i := 0;
  64. WHILE i < len DO
  65. SendChar(buf[ofs + i], res);
  66. IF res # Ok THEN RETURN END;
  67. INC(i)
  68. END
  69. END Send;
  70. PROCEDURE ReceiveChar* (VAR ch: CHAR; VAR res: LONGINT);
  71. END ReceiveChar;
  72. (** Receive size characters into buf, starting at ofs and return the effective number of bytes read in len.
  73. Wait until at least min bytes (possibly zero) are available. res is non-zero on error. *)
  74. PROCEDURE Receive*(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
  75. VAR ch: CHAR;
  76. BEGIN
  77. len := 0;
  78. res := Ok;
  79. WHILE (len < min) DO
  80. ReceiveChar(ch, res);
  81. IF res # Ok THEN RETURN END;
  82. buf[ofs + len] := ch;
  83. INC(len);
  84. END;
  85. WHILE (Available() > 0) & (len < size) DO
  86. ReceiveChar(ch, res);
  87. IF res # Ok THEN RETURN END;
  88. buf[ofs + len] := ch;
  89. INC(len)
  90. END;
  91. END Receive;
  92. PROCEDURE Available*(): LONGINT;
  93. END Available;
  94. (** Get the port state: state (open, closed), speed in bps, no. of data bits, parity, stop bit length. *)
  95. PROCEDURE GetPortState*(VAR openstat : BOOLEAN; VAR bps, data, parity, stop : LONGINT);
  96. END GetPortState;
  97. (** Clear the specified modem control lines. s may contain DTR, RTS & Break. *)
  98. PROCEDURE ClearMC*(s: SET);
  99. END ClearMC;
  100. (** Set the specified modem control lines. s may contain DTR, RTS & Break. *)
  101. PROCEDURE SetMC*(s: SET);
  102. END SetMC;
  103. (** Return the state of the specified modem control lines. s contains
  104. the current state of DSR, CTS, RI, DCD & Break Interrupt. *)
  105. PROCEDURE GetMC*(VAR s: SET);
  106. END GetMC;
  107. (** Setup receive timeout (maximum time allowed to elapse before arrival of the next data byte) in ms.
  108. Use timeout <= 0 to disable receive timeout handling *)
  109. PROCEDURE SetReceiveTimeout*(timeout: LONGINT);
  110. END SetReceiveTimeout;
  111. PROCEDURE Show*;
  112. BEGIN
  113. KernelLog.String(name); KernelLog.String(" ("); KernelLog.String(description); KernelLog.String(")");
  114. END Show;
  115. END Port;
  116. VAR
  117. (* In this array the RS232-type COM ports are registered in the first 8 array elements.
  118. USB ports equipped with a USB to serial adapter are registered in the next 8 array elements. *)
  119. ports : ARRAY MaxPorts OF Port;
  120. tracePort: Port;
  121. traceChar0: Trace.CharProc;
  122. (** At the disposal of the USB driver modules for hot plug-in of a device. *)
  123. PROCEDURE RegisterPort* (port : Port; CONST description : ARRAY OF CHAR);
  124. VAR name : ARRAY 6 OF CHAR; portNumber : LONGINT;
  125. BEGIN {EXCLUSIVE}
  126. ASSERT(port # NIL);
  127. portNumber := 9;
  128. WHILE (portNumber < LEN(ports)) & (ports[portNumber-1] # NIL) DO INC(portNumber); END;
  129. IF portNumber < LEN(ports) THEN
  130. ports[portNumber-1] := port;
  131. name := "COM";
  132. IF portNumber < 10 THEN
  133. name[3] := CHR(ORD("0") + portNumber);
  134. ELSE
  135. name[3] := CHR(ORD("0") + portNumber DIV 10);
  136. name[4] := CHR(ORD("0") + portNumber MOD 10);
  137. END;
  138. COPY(name, port.name);
  139. COPY(description, port.description);
  140. IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" is now available."); KernelLog.Ln; END;
  141. ELSE
  142. KernelLog.String("Serials: Could not register port: No free slots."); KernelLog.Ln;
  143. END;
  144. END RegisterPort;
  145. (** At the disposal of the USB driver modules for hot plug-in of a device. *)
  146. PROCEDURE UnRegisterPort* (port : Port);
  147. VAR i : LONGINT;
  148. BEGIN {EXCLUSIVE}
  149. i := 0; WHILE (i < LEN(ports)) & (ports[i] # port) DO INC(i); END;
  150. IF i < LEN(ports) THEN
  151. ports[i].Close;
  152. ports[i] := NIL;
  153. IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" has been removed."); KernelLog.Ln; END;
  154. ELSE
  155. KernelLog.String("Serials: Warning: UnRegisterPort: Port not found."); KernelLog.Ln;
  156. END;
  157. END UnRegisterPort;
  158. (** COM1 to COM8 are reserved for RS-232 / V24 communication ports. Other ports will be named COM9, COM9 and so on as needed.
  159. The idea is that the onboard COM ports get the same port numbers as in the BIOS *)
  160. PROCEDURE RegisterOnboardPort*(portNumber : LONGINT; port : Port; CONST name, description : ARRAY OF CHAR);
  161. BEGIN {EXCLUSIVE}
  162. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] = NIL) THEN
  163. ports[portNumber-1] := port;
  164. COPY(name, port.name);
  165. COPY(description, port.description);
  166. IF Verbose THEN KernelLog.String("Serials: "); port.Show; KernelLog.String(" is now available."); KernelLog.Ln; END;
  167. ELSE
  168. KernelLog.String("Serials: Warning; Could not register onboard port."); KernelLog.Ln;
  169. END;
  170. END RegisterOnboardPort;
  171. PROCEDURE GetPort* (portNumber : LONGINT) : Port;
  172. VAR port : Port;
  173. BEGIN {EXCLUSIVE}
  174. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
  175. port := ports[portNumber-1];
  176. END;
  177. RETURN port;
  178. END GetPort;
  179. PROCEDURE TraceChar(ch: CHAR);
  180. VAR res: LONGINT;
  181. BEGIN
  182. IF tracePort # NIL THEN
  183. tracePort.SendChar(ch, res);
  184. END;
  185. END TraceChar;
  186. (**
  187. Setup serial port for kernel trace output
  188. portNumber: serial port number, or 0 to disable trace output, or a negative value for rolling back to the initial trace output configuration
  189. bps, data, parity, stop: serial port settings
  190. *)
  191. PROCEDURE SetTracePort*(portNumber: LONGINT; bps, data, parity, stop: LONGINT; VAR res: LONGINT);
  192. VAR
  193. isOpen0: BOOLEAN;
  194. bps0, data0, parity0, stop0 : LONGINT;
  195. res1: LONGINT;
  196. port: Port;
  197. BEGIN{EXCLUSIVE}
  198. res := 0;
  199. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
  200. port := ports[portNumber-1];
  201. port.GetPortState(isOpen0, bps0, data0, parity0, stop0);
  202. (* do not close the port if the current port settings match *)
  203. IF ~isOpen0 OR (bps0 # bps) OR (data0 # data) OR (parity0 # parity) OR (stop0 # stop) THEN
  204. port.Close;
  205. port.Open(bps, data, parity, stop, res);
  206. IF res # 0 THEN
  207. IF isOpen0 THEN port.Open(bps0, data0, parity0, stop0, res1); END;
  208. RETURN;
  209. END;
  210. END;
  211. IF Trace.Char # TraceChar THEN
  212. traceChar0 := Trace.Char;
  213. END;
  214. TRACE(traceChar0);
  215. Machine.Acquire(Machine.TraceOutput);
  216. tracePort := port;
  217. Trace.Char := TraceChar;
  218. Machine.Release(Machine.TraceOutput);
  219. TRACE(Trace.Char);
  220. ELSIF portNumber = 0 THEN
  221. tracePort := NIL;
  222. IF Trace.Char # TraceChar THEN
  223. traceChar0 := Trace.Char;
  224. END;
  225. Trace.Char := TraceChar;
  226. ELSIF portNumber < 0 THEN
  227. IF traceChar0 # NIL THEN
  228. Machine.Acquire(Machine.TraceOutput);
  229. Trace.Char := traceChar0;
  230. Machine.Release(Machine.TraceOutput);
  231. tracePort := NIL;
  232. traceChar0 := NIL;
  233. END;
  234. ELSE
  235. res := NoSuchPort;
  236. END;
  237. END SetTracePort;
  238. (** Returns TRUE if a given port is currently used for kernel trace output *)
  239. PROCEDURE IsTracePort*(port: Port): BOOLEAN;
  240. BEGIN
  241. RETURN (tracePort # NIL) & (port = tracePort);
  242. END IsTracePort;
  243. (**
  244. Get serial port parameters from an input stream
  245. The format of the stream is expected to be as [portNumber bps data parity stop] exactly in this predefined order;
  246. portNumber is obligatory parameter, the othe parameters are optional; if not specified they will be assigned to
  247. default values (DefaultBPS, DefaultDataBits, DefaultParity, DefaultStop)
  248. The format of parity and stop parameters is as follows:
  249. parity = "odd"|"even"|"mark"|"space"|"no"
  250. stop = "1"|"1.5"|"2"
  251. params: set of parameters specified by the user (0-bps, 1-data, 2-parity, 3-stop)
  252. res: error code, 0 in case of success
  253. *)
  254. PROCEDURE GetPortParameters*(r: Streams.Reader; VAR portNumber, bps, data, parity, stop: LONGINT; VAR params: SET; VAR res: LONGINT);
  255. VAR
  256. str : ARRAY 32 OF CHAR;
  257. d: LONGINT;
  258. BEGIN
  259. res := 0;
  260. IF ~r.GetInteger(portNumber,FALSE) OR (GetPort(portNumber) = NIL) THEN
  261. res := NoSuchPort;
  262. RETURN;
  263. END;
  264. params := {};
  265. bps := DefaultBPS; data := DefaultDataBits; parity := DefaultParity; stop := DefaultStop;
  266. IF r.GetInteger(d, FALSE) THEN
  267. bps := d; INCL(params,0);
  268. ELSE RETURN;
  269. END;
  270. IF r.GetInteger(d, FALSE) THEN
  271. data := d; INCL(params,1);
  272. ELSE RETURN;
  273. END;
  274. IF ~r.GetString(str) THEN
  275. RETURN;
  276. END;
  277. IF str = "odd" THEN
  278. parity := ParOdd
  279. ELSIF str = "even" THEN
  280. parity := ParEven
  281. ELSIF str = "mark" THEN
  282. parity := ParMark
  283. ELSIF str = "space" THEN
  284. parity := ParSpace
  285. ELSIF str # "no" THEN
  286. res := WrongParity;
  287. RETURN;
  288. END;
  289. INCL(params,2);
  290. IF ~r.GetString(str) THEN
  291. RETURN;
  292. END;
  293. IF str = "1.5" THEN
  294. stop := Stop1dot5
  295. ELSIF str = "2" THEN
  296. stop := Stop2
  297. ELSIF str # "1" THEN
  298. res := WrongStop;
  299. RETURN;
  300. END;
  301. INCL(params,3);
  302. END GetPortParameters;
  303. (**
  304. Setup trace over a given serial port
  305. SetTrace portNumber bps data parity stop~
  306. portNumber: serial port number, or 0 to disable trace output, or a negative value for rolling back to the initial trace output configuration
  307. *)
  308. PROCEDURE SetTrace*(context: Commands.Context);
  309. VAR
  310. portNumber, bps, data, parity, stop, res: LONGINT; params: SET;
  311. BEGIN
  312. GetPortParameters(context.arg, portNumber, bps, data, parity, stop, params, res);
  313. IF (portNumber > 0) & (res # 0) THEN
  314. context.result := 1;
  315. context.error.String("Invalid port settings, res="); context.error.Int(res,0); context.error.Ln;
  316. RETURN;
  317. END;
  318. SetTracePort(portNumber, bps, data, parity, stop, res);
  319. IF res # 0 THEN
  320. context.result := 2;
  321. context.error.String("Failed to setup trace port, res="); context.error.Int(res,0); context.error.Ln;
  322. END;
  323. END SetTrace;
  324. PROCEDURE Show*(context : Commands.Context);
  325. VAR port : Port; noPortsAvailable : BOOLEAN; i : LONGINT;
  326. BEGIN {EXCLUSIVE}
  327. noPortsAvailable := TRUE;
  328. context.out.String("Serials: "); context.out.Ln;
  329. FOR i := 0 TO LEN(ports)-1 DO
  330. port := ports[i];
  331. IF port # NIL THEN
  332. noPortsAvailable := FALSE;
  333. context.out.String(port.name); context.out.Char(9X); context.out.String(port.description); context.out.Ln;
  334. END;
  335. END;
  336. IF noPortsAvailable THEN context.out.String("No serial ports found."); END;
  337. context.out.Ln;
  338. END Show;
  339. (** Test serial ports COM1 and if present COM2 with the generic driver *)
  340. PROCEDURE Test*(context : Commands.Context);
  341. VAR
  342. result : LONGINT;
  343. portgotten : Port;
  344. BEGIN
  345. context.out.String ("Testing availability of COM1 and COM2."); context.out.Ln;
  346. context.out.String ("Testing COM1: ");
  347. portgotten := GetPort (1);
  348. IF portgotten # NIL THEN
  349. portgotten.Open (4800, 8, 2, 2, result);
  350. portgotten.Close ();
  351. context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln;
  352. context.out.String ("Testing COM2: ");
  353. portgotten := GetPort (2);
  354. IF portgotten # NIL THEN
  355. portgotten.Open (9600, 8, 2, 2, result);
  356. portgotten.Close ();
  357. context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln
  358. ELSE
  359. context.out.String ("Could not get port 2"); context.out.Ln
  360. END
  361. ELSE
  362. context.out.String ("Could not get port 1"); context.out.Ln;
  363. context.out.String ("Not testing COM2 as it is likely not to work either."); context.out.Ln;
  364. END;
  365. END Test;
  366. (** Close all serial ports *)
  367. PROCEDURE CloseAllPorts*(context : Commands.Context);
  368. VAR portNbr : LONGINT;
  369. BEGIN {EXCLUSIVE}
  370. FOR portNbr := 0 TO LEN(ports)-1 DO
  371. IF ports[portNbr] # NIL THEN
  372. ports[portNbr].Close;
  373. END;
  374. END;
  375. IF (context # NIL) THEN context.out.String("All serial ports closed"); context.out.Ln; END;
  376. END CloseAllPorts;
  377. PROCEDURE Cleanup;
  378. BEGIN
  379. CloseAllPorts(NIL);
  380. END Cleanup;
  381. BEGIN
  382. Modules.InstallTermHandler(Cleanup);
  383. END Serials.
  384. SystemTools.Free V24 Serials ~
  385. V24.Install ~
  386. Serials.Test ~
  387. Serials.Show ~