source: PinConnection/SerialPort.pas

Last change on this file was 575, checked in by chronos, 5 months ago
  • Modified: Remove U prefix from unit names of PinConnection package.
File size: 12.0 KB
Line 
1unit SerialPort;
2
3interface
4
5uses
6 {$IFDEF Windows}Windows, {$ENDIF}Registry,
7 Classes, SysUtils, SynaSer, Dialogs, Common, Threading, Syncobjs,
8 DateUtils, FileUtil, SpecializedList;
9
10const
11 MaxPort = 255;
12
13type
14 TBaudRate = (br110, br300, br600, br1200, br2400, br4800,
15 br9600, br14400, br19200, br38400, br56000,
16 br57600, br115200, br128000, br256000);
17 TParity = (paNone, paOdd, paEven, paMark, paSpace);
18 TStopbits = (sb1_0, sb1_5, sb2_0);
19 TDatabits = Integer;
20 TFlowControl = (fcNone, fcSoftware, fcHardware);
21
22 TSerialPort = class;
23 TReceiveDataEvent = procedure(Stream: TListByte) of object;
24
25 { TSerialPortReceiveThread }
26
27 TSerialPortReceiveThread = class(TListedThread)
28 public
29 Parent: TSerialPort;
30 procedure Execute; override;
31 destructor Destroy; override;
32 end;
33
34 { TSerialPort }
35
36 TSerialPort = class(TBlockSerial)
37 private
38 FRTS: Boolean;
39 FDTR: Boolean;
40 FActive: Boolean;
41 FBaudRate: Integer;
42 FDataBits: TDataBits;
43 FFlowControl: TFlowControl;
44 FName: string;
45 FOnReceiveData: TReceiveDataEvent;
46 FParity: TParity;
47 FStopBits: TStopBits;
48 FReceiveThread: TSerialPortReceiveThread;
49 FReceiveBuffer: TListByte;
50 function FindFriendlyName(Key: string; Port: string): string;
51 function GetName: string;
52 procedure GetSerialPortNamesExt(Strings: TStrings);
53 procedure SetBaudRate(const AValue: Integer);
54 procedure SetDataBits(const AValue: TDataBits);
55 procedure SetDTR(const AValue: Boolean);
56 procedure SetFlowControl(const AValue: TFlowControl);
57 procedure SetActive(const AValue: Boolean);
58 procedure SetName(const AValue: string);
59 procedure SetParity(const AValue: TParity);
60 procedure SetRTS(const AValue: Boolean);
61 procedure SetStopBits(const AValue: TStopBits);
62 procedure Open;
63 procedure Close;
64 public
65 Lock: TCriticalSection;
66 property Name: string read GetName write SetName;
67 property FlowControl: TFlowControl read FFlowControl write SetFlowControl;
68 property DataBits: TDataBits read FDataBits write SetDataBits;
69 property StopBits: TStopBits read FStopBits write SetStopBits;
70 property Parity: TParity read FParity write SetParity;
71 property BaudRate: Integer read FBaudRate write SetBaudRate;
72 property Active: Boolean read FActive write SetActive;
73 property RTS: Boolean read FRTS write SetRTS;
74 property DTR: Boolean read FDTR write SetDTR;
75 property ReceiveBuffer: TListByte read FReceiveBuffer;
76
77 property OnReceiveData: TReceiveDataEvent read FOnReceiveData write FOnReceiveData;
78 procedure LoadAvailableToStrings(Strings: TStrings; Check: Boolean = False);
79 constructor Create;
80 destructor Destroy; override;
81 procedure Flush;
82 procedure Purge;
83 procedure Assign(Source: TObject);
84 end;
85
86const
87 BaudRateNumericTable: array[TBaudRate] of Integer = (
88 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000,
89 57600, 115200, 128000, 256000);
90 INVALID_HANDLE_VALUE = DWORD(-1);
91
92
93implementation
94
95resourcestring
96 SAssignmentError = 'Assignment error';
97 SWrongDataBitsNumber = 'Wrong data bits number %s';
98
99{ TSerialPort }
100
101procedure TSerialPort.SetActive(const AValue: Boolean);
102begin
103 if not FActive and AValue then begin
104 FActive := True;
105 Open;
106 FActive := FHandle <> THandle(INVALID_HANDLE_VALUE);
107 if not FActive then FreeAndNil(FReceiveThread);
108 end else
109 if FActive and not AValue then begin
110 FActive := False;
111 Close;
112 end;
113end;
114
115procedure TSerialPort.SetName(const AValue: string);
116var
117 LastState: Boolean;
118begin
119 if FName = AValue then Exit;
120 LastState := FActive;
121 Active := False;
122 FName := AValue;
123 Active := LastState;
124end;
125
126procedure TSerialPort.SetParity(const AValue: TParity);
127begin
128 FParity := AValue;
129 if FActive then begin
130 GetCommState;
131 DCB.Parity := Integer(AValue);
132 SetCommState;
133 end;
134end;
135
136procedure TSerialPort.SetRTS(const AValue: Boolean);
137begin
138 FRTS := AValue;
139 if FActive then SetRTSF(FRTS);
140end;
141
142procedure TSerialPort.SetStopBits(const AValue: TStopBits);
143begin
144 FStopBits := AValue;
145 if FActive then begin
146 GetCommState;
147 DCB.StopBits := Integer(AValue);
148 SetCommState;
149 end;
150end;
151
152procedure TSerialPort.Open;
153begin
154 Connect(FName);
155 if FHandle <> INVALID_HANDLE_VALUE then begin
156 //set_fDtrControl(DCB, 1);
157 //DCB.flags := ;
158 {$IFDEF DEBUG}
159 RaiseExcept := True;
160 {$ENDIF}
161 SetBaudRate(FBaudRate);
162 SetParity(FParity);
163 SetStopBits(FStopBits);
164 SetDataBits(FDataBits);
165 SetFlowControl(FFlowControl);
166 SetDTR(FDTR);
167 SetRTS(FRTS);
168
169 FReceiveThread := TSerialPortReceiveThread.Create(True);
170 FReceiveThread.FreeOnTerminate := False;
171 FReceiveThread.Parent := Self;
172 FReceiveThread.Name := 'SerialPort';
173 FReceiveThread.Start;
174 end;
175end;
176
177procedure TSerialPort.Close;
178begin
179 if FHandle <> INVALID_HANDLE_VALUE then begin
180 FreeAndNil(FReceiveThread);
181 CloseSocket;
182 end;
183end;
184
185function TSerialPort.FindFriendlyName(Key: string; Port: string): string;
186var
187 r: TRegistry;
188 k: TStringList;
189 i: Integer;
190 ck: string;
191 rs: string;
192begin
193 r := TRegistry.Create;
194 k := TStringList.Create;
195
196 r.RootKey := HKEY_LOCAL_MACHINE;
197 r.OpenKeyReadOnly(key);
198 r.GetKeyNames(k);
199 r.CloseKey;
200
201 try
202 for i := 0 to k.Count - 1 do
203 begin
204 ck := key + k[i] + '\'; // current key
205 // looking for "PortName" stringvalue in "Device Parameters" subkey
206 if r.OpenKeyReadOnly(ck + 'Device Parameters') then
207 begin
208 if r.ReadString('PortName') = port then
209 begin
210 r.CloseKey;
211 r.OpenKeyReadOnly(ck);
212 rs := UTF8Encode(r.ReadString('FriendlyName'));
213 Break;
214 end
215 end
216 // keep looking on subkeys for "PortName"
217 else
218 begin
219 if r.OpenKeyReadOnly(ck) and r.HasSubKeys then
220 begin
221 rs := FindFriendlyName(ck, port);
222 if rs <> '' then Break;
223 end;
224 end;
225 end;
226 result := rs;
227 finally
228 r.Free;
229 k.Free;
230 end;
231end;
232
233procedure TSerialPort.GetSerialPortNamesExt(Strings: TStrings);
234var
235 Reg: TRegistry;
236 l: TStringList;
237 n: integer;
238 pn, fn: string;
239begin
240 l := TStringList.Create;
241 Reg := TRegistry.Create;
242 try
243 Reg.RootKey := HKEY_LOCAL_MACHINE;
244 if reg.OpenKeyReadOnly('HARDWARE\DEVICEMAP\SERIALCOMM') then
245 begin
246 Reg.GetValueNames(l);
247 for n := 0 to l.Count - 1 do
248 begin
249 pn := Reg.ReadString(l[n]);
250 fn := FindFriendlyName('\System\CurrentControlSet\Enum\', pn);
251 if fn <> '' then
252 Strings.Add(pn + Strings.NameValueSeparator + fn)
253 else Strings.Add(pn + Strings.NameValueSeparator + pn)
254 end;
255 end;
256 finally
257 L.Free;
258 Reg.Free;
259 end;
260end;
261
262procedure TSerialPort.LoadAvailableToStrings(Strings: TStrings; Check: Boolean = False);
263var
264 I: Integer;
265 {$IFDEF Linux}Files: TStringList;{$ENDIF}
266 TestPort: TSerialPort;
267begin
268 Strings.Clear;
269 Strings.NameValueSeparator := '|';
270 {$IFDEF Windows}
271 if Check then begin
272 GetSerialPortNamesExt(Strings);
273 // If no ports with friendly names detected try open all ports (compatibility with Win98)
274 if Strings.Count = 0 then
275 try
276 TestPort := TSerialPort.Create;
277 for I := 0 to MaxPort - 1 do
278 with TestPort do begin
279 Name := 'COM' + IntToStr(I);
280 Active := True;
281 if (LastError = ERROR_SUCCESS) or (LastError = ERROR_ACCESS_DENIED) then
282 begin
283 Strings.AddObject(Name, nil);
284 end;
285 Active := False;
286 end;
287 finally
288 TestPort.Free;
289 end;
290 end else begin
291 for I := 1 to MaxPort do
292 Strings.AddObject('COM' + IntToStr(I) + Strings.NameValueSeparator +
293 'COM' + IntToStr(I), nil);
294 end;
295 {$ENDIF}
296 {$IFDEF Linux}
297 if Check then
298 try
299 Files := FindAllFiles('/dev', 'tty*', False);
300 Strings.Assign(Files);
301 for I := 0 to Strings.Count - 1 do
302 Strings.Strings[I] := Strings.Strings[I] + Strings.NameValueSeparator +
303 Strings.Strings[I];
304 finally
305 Files.Free;
306 end else begin
307 for I := 1 to 63 do
308 Strings.AddObject('/dev/ttyS' + IntToStr(I) + Strings.NameValueSeparator +
309 '/dev/ttyS' + IntToStr(I), nil);
310 end;
311 {$ENDIF}
312end;
313
314constructor TSerialPort.Create;
315begin
316 inherited Create;
317 Lock := TCriticalSection.Create;
318 FReceiveBuffer := TListByte.Create;
319 FBaudRate := 9600;
320 FName := 'COM1';
321 FDataBits := 8;
322 FStopBits := sb1_0;
323 FParity := paNone;
324 FlowControl := fcNone;
325 FDTR := False;
326 FRTS := False;
327 Active := False;
328end;
329
330destructor TSerialPort.Destroy;
331begin
332 Active := False;
333 FreeAndNil(FReceiveThread);
334 FreeAndNil(FReceiveBuffer);
335 FreeAndNil(Lock);
336 inherited;
337end;
338
339procedure TSerialPort.Flush;
340begin
341 if FActive then begin
342 inherited Flush;
343 end;
344end;
345
346procedure TSerialPort.Purge;
347begin
348 if FActive then
349 inherited Purge;
350end;
351
352procedure TSerialPort.Assign(Source:TObject);
353begin
354 if Source is TSerialPort then begin
355 Name := TSerialPort(Source).Name;
356 BaudRate := TSerialPort(Source).BaudRate;
357 Parity := TSerialPort(Source).Parity;
358 StopBits := TSerialPort(Source).StopBits;
359 DataBits := TSerialPort(Source).DataBits;
360 FlowControl := TSerialPort(Source).FlowControl;
361 DTR := TSerialPort(Source).DTR;
362 RTS := TSerialPort(Source).RTS;
363 end else raise Exception.Create(SAssignmentError);
364end;
365
366procedure TSerialPort.SetBaudRate(const AValue: Integer);
367begin
368 FBaudRate := AValue;
369 if FActive then begin
370 GetCommState;
371 DCB.BaudRate := AValue;
372 SetCommState;
373 end;
374end;
375
376function TSerialPort.GetName: string;
377begin
378 Result := FName;
379end;
380
381procedure TSerialPort.SetDataBits(const AValue: TDataBits);
382begin
383 if (AValue >= 5) and (AValue <= 8) then FDataBits := AValue
384 else raise Exception.CreateFmt(SWrongDataBitsNumber, [IntToStr(AValue)]);
385 if FActive then begin
386 GetCommState;
387 DCB.ByteSize := AValue;
388 SetCommState;
389 end;
390end;
391
392procedure TSerialPort.SetDTR(const AValue: Boolean);
393begin
394 FDTR := AValue;
395 if FFlowControl = fcNone then
396 DCB.flags := DCB.flags and (not (dcb_DtrControlEnable * 3)) or
397 (dcb_DtrControlEnable * Byte(AValue));
398 if FActive then begin
399 if FFlowControl = fcNone then SetCommState
400 else SetDTRF(FDTR);
401 end;
402end;
403
404procedure TSerialPort.SetFlowControl(const AValue: TFlowControl);
405begin
406 FFlowControl := AValue;
407 if FActive then begin
408 GetCommState;
409 case AValue of
410 fcNone: DCB.flags := 0;
411 fcSoftware: DCB.flags := dcb_OutX or dcb_InX or
412 dcb_DtrControlEnable or dcb_RtsControlEnable;
413 fcHardware: DCB.flags := dcb_OutxCtsFlow or dcb_OutxDsrFlow
414 or dcb_DtrControlHandshake or dcb_RtsControlHandshake;
415 end;
416 SetCommState;
417 end;
418end;
419
420{ TSerialPortReceiveThread }
421
422procedure TSerialPortReceiveThread.Execute;
423var
424 InBufferUsed: Integer;
425 Buffer: array of Byte;
426 Read: Integer;
427begin
428 InBufferUsed := 0;
429 with Parent do repeat
430 if InBufferUsed = 0 then Sleep(1);
431 //else Yield;
432 if Active then
433 try
434 Parent.Lock.Acquire;
435 InBufferUsed := WaitingData;
436 if InBufferUsed > 0 then begin
437 SetLength(Buffer, InBufferUsed);
438 Read := RecvBuffer(Buffer, Length(Buffer));
439 SetLength(Buffer, Read);
440
441 Parent.FReceiveBuffer.Count := Length(Buffer);
442 Parent.FReceiveBuffer.ReplaceBuffer(0, PByte(Buffer)^, Length(Buffer));
443 if Assigned(Parent.FOnReceiveData) then begin
444 try
445 Parent.Lock.Release;
446 Parent.FOnReceiveData(Parent.FReceiveBuffer);
447 finally
448 Parent.Lock.Acquire;
449 end;
450 end;
451 end else InBufferUsed := 0;
452 finally
453 Parent.Lock.Release;
454 end else InBufferUsed := 0;
455 until Terminated;
456end;
457
458destructor TSerialPortReceiveThread.Destroy;
459begin
460 inherited;
461end;
462
463end.
464
Note: See TracBrowser for help on using the repository browser.