2
0

Serials.Mod 13 KB

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