Serials.Mod 13 KB

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