Serials.Mod 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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, or 0 to disable trace output, or a negative value for rolling back to the initial trace output configuration
  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. port: Port;
  192. BEGIN{EXCLUSIVE}
  193. res := 0;
  194. IF (portNumber >= 1) & (portNumber <= LEN(ports)) & (ports[portNumber-1] # NIL) THEN
  195. port := ports[portNumber-1];
  196. port.GetPortState(isOpen0, bps0, data0, parity0, stop0);
  197. (* do not close the port if the current port settings match *)
  198. IF ~isOpen0 OR (bps0 # bps) OR (data0 # data) OR (parity0 # parity) OR (stop0 # stop) THEN
  199. port.Close;
  200. port.Open(bps, data, parity, stop, res);
  201. IF res # 0 THEN
  202. IF isOpen0 THEN port.Open(bps0, data0, parity0, stop0, res1); END;
  203. RETURN;
  204. END;
  205. END;
  206. IF Trace.Char # TraceChar THEN
  207. traceChar0 := Trace.Char;
  208. END;
  209. TRACE(traceChar0);
  210. Machine.Acquire(Machine.TraceOutput);
  211. tracePort := port;
  212. Trace.Char := TraceChar;
  213. Machine.Release(Machine.TraceOutput);
  214. TRACE(Trace.Char);
  215. ELSIF portNumber = 0 THEN
  216. tracePort := NIL;
  217. IF Trace.Char # TraceChar THEN
  218. traceChar0 := Trace.Char;
  219. END;
  220. Trace.Char := TraceChar;
  221. ELSIF portNumber < 0 THEN
  222. IF traceChar0 # NIL THEN
  223. Machine.Acquire(Machine.TraceOutput);
  224. Trace.Char := traceChar0;
  225. Machine.Release(Machine.TraceOutput);
  226. tracePort := NIL;
  227. traceChar0 := NIL;
  228. END;
  229. ELSE
  230. res := NoSuchPort;
  231. END;
  232. END SetTracePort;
  233. (** Returns TRUE if a given port is currently used for kernel trace output *)
  234. PROCEDURE IsTracePort*(port: Port): BOOLEAN;
  235. BEGIN
  236. RETURN (tracePort # NIL) & (port = tracePort);
  237. END IsTracePort;
  238. (**
  239. Get serial port parameters from an input stream
  240. The format of the stream is expected to be as [portNumber bps data parity stop] exactly in this predefined order;
  241. portNumber is obligatory parameter, the othe parameters are optional; if not specified they will be assigned to
  242. default values (DefaultBPS, DefaultDataBits, DefaultParity, DefaultStop)
  243. The format of parity and stop parameters is as follows:
  244. parity = "odd"|"even"|"mark"|"space"|"no"
  245. stop = "1"|"1.5"|"2"
  246. params: set of parameters specified by the user (0-bps, 1-data, 2-parity, 3-stop)
  247. res: error code, 0 in case of success
  248. *)
  249. PROCEDURE GetPortParameters*(r: Streams.Reader; VAR portNumber, bps, data, parity, stop: LONGINT; VAR params: SET; VAR res: LONGINT);
  250. VAR
  251. str : ARRAY 32 OF CHAR;
  252. d: LONGINT;
  253. BEGIN
  254. res := 0;
  255. IF ~r.GetInteger(portNumber,FALSE) OR (GetPort(portNumber) = NIL) THEN
  256. res := NoSuchPort;
  257. RETURN;
  258. END;
  259. params := {};
  260. bps := DefaultBPS; data := DefaultDataBits; parity := DefaultParity; stop := DefaultStop;
  261. IF r.GetInteger(d, FALSE) THEN
  262. bps := d; INCL(params,0);
  263. ELSE RETURN;
  264. END;
  265. IF r.GetInteger(d, FALSE) THEN
  266. data := d; INCL(params,1);
  267. ELSE RETURN;
  268. END;
  269. IF ~r.GetString(str) THEN
  270. RETURN;
  271. END;
  272. IF str = "odd" THEN
  273. parity := ParOdd
  274. ELSIF str = "even" THEN
  275. parity := ParEven
  276. ELSIF str = "mark" THEN
  277. parity := ParMark
  278. ELSIF str = "space" THEN
  279. parity := ParSpace
  280. ELSIF str # "no" THEN
  281. res := WrongParity;
  282. RETURN;
  283. END;
  284. INCL(params,2);
  285. IF ~r.GetString(str) THEN
  286. RETURN;
  287. END;
  288. IF str = "1.5" THEN
  289. stop := Stop1dot5
  290. ELSIF str = "2" THEN
  291. stop := Stop2
  292. ELSIF str # "1" THEN
  293. res := WrongStop;
  294. RETURN;
  295. END;
  296. INCL(params,3);
  297. END GetPortParameters;
  298. (**
  299. Setup trace over a given serial port
  300. SetTrace portNumber bps data parity stop~
  301. portNumber: serial port number, or 0 to disable trace output, or a negative value for rolling back to the initial trace output configuration
  302. *)
  303. PROCEDURE SetTrace*(context: Commands.Context);
  304. VAR
  305. portNumber, bps, data, parity, stop, res: LONGINT; params: SET;
  306. BEGIN
  307. GetPortParameters(context.arg, portNumber, bps, data, parity, stop, params, res);
  308. IF (portNumber > 0) & (res # 0) THEN
  309. context.result := 1;
  310. context.error.String("Invalid port settings, res="); context.error.Int(res,0); context.error.Ln;
  311. RETURN;
  312. END;
  313. SetTracePort(portNumber, bps, data, parity, stop, res);
  314. IF res # 0 THEN
  315. context.result := 2;
  316. context.error.String("Failed to setup trace port, res="); context.error.Int(res,0); context.error.Ln;
  317. END;
  318. END SetTrace;
  319. PROCEDURE Show*(context : Commands.Context);
  320. VAR port : Port; noPortsAvailable : BOOLEAN; i : LONGINT;
  321. BEGIN {EXCLUSIVE}
  322. noPortsAvailable := TRUE;
  323. context.out.String("Serials: "); context.out.Ln;
  324. FOR i := 0 TO LEN(ports)-1 DO
  325. port := ports[i];
  326. IF port # NIL THEN
  327. noPortsAvailable := FALSE;
  328. context.out.String(port.name); context.out.Char(9X); context.out.String(port.description); context.out.Ln;
  329. END;
  330. END;
  331. IF noPortsAvailable THEN context.out.String("No serial ports found."); END;
  332. context.out.Ln;
  333. END Show;
  334. (** Test serial ports COM1 and if present COM2 with the generic driver *)
  335. PROCEDURE Test*(context : Commands.Context);
  336. VAR
  337. result : LONGINT;
  338. portgotten : Port;
  339. BEGIN
  340. context.out.String ("Testing availability of COM1 and COM2."); context.out.Ln;
  341. context.out.String ("Testing COM1: ");
  342. portgotten := GetPort (1);
  343. IF portgotten # NIL THEN
  344. portgotten.Open (4800, 8, 2, 2, result);
  345. portgotten.Close ();
  346. context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln;
  347. context.out.String ("Testing COM2: ");
  348. portgotten := GetPort (2);
  349. IF portgotten # NIL THEN
  350. portgotten.Open (9600, 8, 2, 2, result);
  351. portgotten.Close ();
  352. context.out.String ("available, result="); context.out.Int(result, 0); context.out.Ln
  353. ELSE
  354. context.out.String ("Could not get port 2"); context.out.Ln
  355. END
  356. ELSE
  357. context.out.String ("Could not get port 1"); context.out.Ln;
  358. context.out.String ("Not testing COM2 as it is likely not to work either."); context.out.Ln;
  359. END;
  360. END Test;
  361. (** Close all serial ports *)
  362. PROCEDURE CloseAllPorts*(context : Commands.Context);
  363. VAR portNbr : LONGINT;
  364. BEGIN {EXCLUSIVE}
  365. FOR portNbr := 0 TO LEN(ports)-1 DO
  366. IF ports[portNbr] # NIL THEN
  367. ports[portNbr].Close;
  368. END;
  369. END;
  370. IF (context # NIL) THEN context.out.String("All serial ports closed"); context.out.Ln; END;
  371. END CloseAllPorts;
  372. PROCEDURE Cleanup;
  373. BEGIN
  374. CloseAllPorts(NIL);
  375. END Cleanup;
  376. BEGIN
  377. Modules.InstallTermHandler(Cleanup);
  378. END Serials.
  379. SystemTools.Free V24 Serials ~
  380. V24.Install ~
  381. Serials.Test ~
  382. Serials.Show ~