HomePage Delphi Library
Serial Communication
From: "Ed Lagerburg" <[email protected]>
//{$DEFINE COMM_UNIT}
//Simple_comm door E.L. Lagerburg voor Delphi 2.01 Maart 1997
//Nog niet getest
//Compiler maakt Simple_Comm.Dll of Simple_Com.Dcu afhankelijk van 1e Regel
(COMM_UNIT)
{$IFNDEF COMM_UNIT}
library Simple_Comm;
{$ELSE}
Unit Simple_Comm;
Interface
{$ENDIF}
Uses Windows,Messages;
Const M_BaudRate =1;
Const M_ByteSize =2;
Const M_Parity =4;
Const M_Stopbits =8;
{$IFNDEF COMM_UNIT}
{$R Script2.Res} //versie informatie
{$ENDIF}
{$IFDEF COMM_UNIT}
Function Simple_Comm_Info:PChar;StdCall;
Function
Simple_Comm_Open(Port:PChar;BaudRate:DWORD;ByteSize,Parity,StopBits:Byte;Mas
k:Integer;WndHandle:HWND;WndCommand:UINT;Var Id:Integer):Integer;StdCall;
Function Simple_Comm_Close(Id:Integer):Integer;StdCall;
Function
Simple_Comm_Write(Id:Integer;Buffer:PChar;Count:DWORD):Integer;StdCall;
Function Simple_Comm_PortCount:DWORD;StdCall;
Const M_None = 0;
Const M_All = 15;
Implementation
{$ENDIF}
Const InfoString = 'Simple_Comm.Dll (c) by E.L. Lagerburg 1997';
const MaxPorts = 5;
Const bDoRun : Array[0..MaxPorts-1] of boolean
=(False,False,False,False,False);
Const hCommPort: Array[0..MaxPorts-1] of Integer =(0,0,0,0,0);
Const hThread: Array[0..MaxPorts-1] of Integer =(0,0,0,0,0);
Const dwThread: Array[0..MaxPorts-1] of Integer =(0,0,0,0,0);
Const hWndHandle: Array[0..MaxPorts-1] of Hwnd =(0,0,0,0,0);
Const hWndCommand:Array[0..MaxPorts-1] of UINT =(0,0,0,0,0);
Const PortCount:Integer = 0;
Function Simple_Comm_Info:PChar;StdCall;
Begin
Result:=InfoString;
End;
//Thread functie voor lezen compoort
Function Simple_Comm_Read(Param:Pointer):Longint;StdCall;
Var Count:Integer;
id:Integer;
ReadBuffer:Array[0..127] of byte;
Begin
Id:=Integer(Param);
While bDoRun[id] do
Begin
ReadFile(hCommPort[id],ReadBuffer,1,Count,nil);
if (Count > 0) then
Begin
if ((hWndHandle[id]<> 0) and
(hWndCommand[id] >> WM_USER)) then
SendMessage(hWndHandle[id],hWndCommand[id],Count,LPARAM(@ReadBuffer));
End;
End;
Result:=0;
End;
//Export functie voor sluiten compoort
Function Simple_Comm_Close(Id:Integer):Integer;StdCall;
Begin
if (ID < 0) or (id > MaxPorts-1) or (not bDoRun[Id]) then
Begin
Result:=ERROR_INVALID_FUNCTION;
Exit;
End;
bDoRun[Id]:=False;
Dec(PortCount);
FlushFileBuffers(hCommPort[Id]);
if not
PurgeComm(hCommPort[Id],PURGE_TXABORT+PURGE_RXABORT+PURGE_TXCLEAR+PURGE_RXCL
EAR) then
Begin
Result:=GetLastError;
Exit;
End;
if WaitForSingleObject(hThread[Id],10000) = WAIT_TIMEOUT then
if not TerminateThread(hThread[Id],1) then
Begin
Result:=GetLastError;
Exit;
End;
CloseHandle(hThread[Id]);
hWndHandle[Id]:=0;
hWndCommand[Id]:=0;
if not CloseHandle(hCommPort[Id]) then
Begin
Result:=GetLastError;
Exit;
End;
hCommPort[Id]:=0;
Result:=NO_ERROR;
End;
Procedure Simple_Comm_CloseAll;StdCall;
Var Teller:Integer;
Begin
For Teller:=0 to MaxPorts-1 do
Begin
if bDoRun[Teller] then Simple_Comm_Close(Teller);
End;
End;
Function GetFirstFreeId:Integer;StdCall;
Var Teller:Integer;
Begin
For Teller:=0 to MaxPorts-1 do
Begin
If not bDoRun[Teller] then
Begin
Result:=Teller;
Exit;
End;
End;
Result:=-1;
End;
//Export functie voor openen compoort
Function
Simple_Comm_Open(Port:PChar;BaudRate:DWORD;ByteSize,Parity,StopBits:Byte;Mas
k:Integer;WndHandle:HWND;WndCommand:UINT;Var Id:Integer):Integer;StdCall;
Var PrevId:Integer;
ctmoCommPort:TCOMMTIMEOUTS; //Lees specificaties voor de compoort
dcbCommPort:TDCB;
Begin
if (PortCount >= MaxPorts) or (PortCount < 0) then
begin
result:=error_invalid_function;
exit;
end;
result:=0;
previd:=id;
id:=getfirstfreeid;
if id = -1 then
begin
id:=previd;
result:=error_invalid_function;
exit;
end;
hcommport[id]:=createfile(port,generic_read or
generic_write,0,nil,open_existing,file_attribute_normal,0);
if hcommport[id]= invalid_handle_value then
begin
bdorun[id]:=false;
id:=previd;
result:=getlasterror;
exit;
end;
//lees specificaties voor het comm bestand
ctmocommport.readintervaltimeout:=maxdword;
ctmocommport.readtotaltimeoutmultiplier:=maxdword;
ctmocommport.readtotaltimeoutconstant:=maxdword;
ctmocommport.writetotaltimeoutmultiplier:=0;
ctmocommport.writetotaltimeoutconstant:=0;
//instellen specificaties voor het comm bestand
if not setcommtimeouts(hcommport[id],ctmocommport) then
begin
bdorun[id]:=false;
closehandle(hcommport[id]);
id:=previd;
result:=getlasterror;
exit;
end;
//instellen communicatie
dcbcommport.dcblength:=sizeof(tdcb);
if not getcommstate(hcommport[id],dcbcommport) then
begin
bdorun[id]:=false;
closehandle(hcommport[id]);
id:=previd;
result:=getlasterror;
exit;
end;
if (mask and m_baudrate <> 0) then dcbCommPort.BaudRate:=BaudRate;
if (Mask and M_ByteSize <> 0) then dcbCommPort.ByteSize:=ByteSize;
if (Mask and M_Parity <> 0) then dcbCommPort.Parity:=Parity;
if (Mask and M_Stopbits <> 0) then dcbCommPort.StopBits:=StopBits;
if not SetCommState(hCommPort[Id],dcbCommPort) then
Begin
bDoRun[Id]:=FALSE;
CloseHandle(hCommPort[Id]);
Id:=PrevId;
Result:=GetLastError;
Exit;
End;
//Thread voor lezen compoort
bDoRun[Id]:=TRUE;
hThread[Id]:=CreateThread(nil,0,@Simple_Comm_Read,Pointer(Id),0,dwThread[Id]
);
if hThread[Id] = 0 then
Begin
bDoRun[Id]:=FALSE;
CloseHandle(hCommPort[Id]);
Id:=PrevId;
Result:=GetLastError;
Exit;
End else
Begin
SetThreadPriority(hThread[Id],THREAD_PRIORITY_HIGHEST);
hWndHandle[Id]:=WndHandle;
hWndCommand[Id]:=WndCommand;
Inc(PortCount);
Result:=NO_ERROR;
End;
End;
//Export functie voor schrijven naar compoort;
Function
Simple_Comm_Write(Id:Integer;Buffer:PChar;Count:DWORD):Integer;StdCall;
Var Written:DWORD;
Begin
if (Id < 0) or (id > Maxports-1) or (not bDoRun[Id]) then
Begin
Result:=ERROR_INVALID_FUNCTION;
Exit;
End;
if not WriteFile(hCommPort[Id],Buffer,Count,Written,nil) then
Begin
Result:=GetLastError();
Exit;
End;
if (Count <> Written) Then Result:=ERROR_WRITE_FAULT Else
Result:=NO_ERROR;
End;
//Aantal geopende poorten voor aanroepende applicatie
Function Simple_Comm_PortCount:DWORD;StdCall;
Begin
Result:=PortCount;
End;
{$IFNDEF COMM_UNIT}
Exports
Simple_Comm_Info Index 1,
Simple_Comm_Open Index 2,
Simple_Comm_Close Index 3,
Simple_Comm_Write Index 4,
Simple_Comm_PortCount index 5;
Procedure DLLMain(dwReason:DWORD);
Begin
If dwReason = DLL_PROCESS_DETACH then Simple_Comm_CloseAll;
End;
Begin
DLLProc:=@DLLMain;
DLLMain(DLL_PROCESS_ATTACH);//geen nut in dit geval
End.
{$ELSE}
Initialization
Finalization
Simple_Comm_CloseAll;
end.
{$ENDIF}
From: "Lennart" Just wrote a I/O unit for Windows 95 /NT. Here it is :)
(with TDCB in SetCommStatus you can control DTR etc.)
(Att: XonLim and XoffLim not higher then 600 or else NT doesn't work properly ?)
unit My_IO;
interface
function OpenComm(InQueue, OutQueue, Baud: LongInt): Boolean;
function SetCommTiming: Boolean;
function SetCommBuffer(InQueue, OutQueue: LongInt): Boolean;
function SetCommStatus(Baud: Integer): Boolean;
function SendCommStr(S: String): Integer;
function ReadCommStr(var S: String): Integer;
procedure CloseComm;
var
ComPort: Word;
implementation
uses Windows, SysUtils;
const
CPort: array [1..4] of String =('COM1','COM2','COM3','COM4');
var
Com: THandle = 0;
function OpenComm(InQueue, OutQueue, Baud : LongInt): Boolean;
begin
if Com > 0 then CloseComm;
Com := CreateFile(PChar(CPort[ComPort]),
GENERIC_READ or GENERIC_WRITE,
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Result := (Com > 0) and SetCommTiming and
SetCommBuffer(InQueue,OutQueue) and
SetCommStatus(Baud) ;
end;
function SetCommTiming: Boolean;
var
Timeouts: TCommTimeOuts;
begin
with TimeOuts do
begin
ReadIntervalTimeout := 1;
ReadTotalTimeoutMultiplier := 0;
ReadTotalTimeoutConstant := 1;
WriteTotalTimeoutMultiplier := 2;
WriteTotalTimeoutConstant := 2;
end;
Result := SetCommTimeouts(Com,Timeouts);
end;
function SetCommBuffer(InQueue, OutQueue: LongInt): Boolean;
begin
Result := SetupComm(Com, InQueue, OutQueue);
end;
function SetCommStatus(Baud: Integer): Boolean;
var
DCB: TDCB;
begin
with DCB do
begin
DCBlength:=SizeOf(Tdcb);
BaudRate := Baud;
Flags:=12305;
wReserved:=0;
XonLim:=600;
XoffLim:=150;
ByteSize:=8;
Parity:=0;
StopBits:=0;
XonChar:=#17;
XoffChar:=#19;
ErrorChar:=#0;
EofChar:=#0;
EvtChar:=#0;
wReserved1:=65;
end;
Result := SetCommState(Com, DCB);
end;
function SendCommStr(S: String): Integer;
var
TempArray : array[1..255] of Byte;
Count, TX_Count : Integer;
begin
for Count := 1 to Length(S) do TempArray[Count] := Ord(S[Count]);
WriteFile(Com, TempArray, Length(S), TX_Count, nil);
Result := TX_Count;
end;
function ReadCommStr(var S: String) : Integer;
var
TempArray : array[1..255] of Byte;
Count, RX_Count : Integer;
begin
S := '';
ReadFile(Com, TempArray, 255, RX_Count, nil);
for Count := 1 to RX_Count do S := S + Chr(TempArray[Count]);
Result := RX_Count;
end;
procedure CloseComm;
begin
CloseHandle(Com);
Com := -1;
end;
end.
From: Martin Larsson <[email protected]>
This was supposed to be a quick summary. It ended up being quite long. Hope it's not too boring...
Under MS-DOS, an application has control of the entire machine. This gives the programmer a lot of freedom. To maximize speed, you can access the hardware directly if necessary.
Under Windows 3.x, this freedom was somewhat limited. You were no longer allowed to write directly to the screen, among other things. The problem is obvious: since the user could have any number of applications running, there was no guarantee that they were not accessing the same hardware simultaneously.
Another problem that showed up was that you had to be nice to the other applications running at the same time. Win 3.x is co-operatively multitasked, meaning that each application determines when it's done and other applications can run. Hogging the CPU for longer periods of time was not considered nice.
But the fact that no applications would run unless we as programmers said so, could be worked to our advantage when accessing the hardware. Since the application were guaranteed full control over the machine for as long as it wished, it could, when it got the CPU, muck with the I/O ports or memory, but not give up control until it was done.
Unfortunately, progress caught up with us; now there's Win32 (Windows NT and Windows 95). T hese are true operating systems, with true pre-emptive multi-tasking. Each thread (the execution unit) gets a certain amount of time with the processor. When the time is up, or a thread with higher priority comes along, the system will switch to the next thread, even though the first thread is not done. This switching can occur between any two assembly instructions; there's no guarantee that a thread will be able to complete any number of instructions before it's pre-empted, and there might be a long time 'till the next timeslot.
This brings up a real problem with direct hardware access. A typical I/O read, for instance, is composed of several assembly instructions:
mov dx, AddressPort
mov al, Address
out dx, al
jmp Wait
Wait:
mov dx, DataPort
in al, dx
While the state of all registers are preserved on a thread-switch, the state of the I/O ports are not. So, it is very possible that three applications have their way with 'your' I/O port between the 'out' and the 'in' instructions above.
The solution to this problem is to somehow tell all other applications that "Currently MyProg is using port 546, and everybody else better stay in line." What's needed is a mutex. Unfortunately, to use a mutex, all applications have to agree on a name for that mutex. But even if that was possible, you'd easily get into some thorny problems. Consider two applications App1 and App2. Both wants to execute the above code. Unfortunately, they're created by different people with different views, so App1 asks for the AddressPortMutex first, while App2 asks for the DataPortMutex first. And, by a sad coincidence, App1 gets the AddressPortMutex, then the system swithes to App2, which aquires the DataPortMutex, and we're deadlocked. App2 can't get the address port, 'cause App1 has that. App1 can't get the data port, 'cause App2 has that. And we're still waiting...
The correct way to solve this problem is to create a device driver that owns the port/memory area. Access to the hardware is supported through an API. A typical function would be
GetIOPortData(AddressPort, DataPort : word) : Byte;
GetIOPortData would aquire a mutex that protects both (possibly all) ports, then access the ports, and finally releasing the mutex before returning to the caller. If different threads are calling this function at the same time, one will get there first, the others must wait.
Writing a device driver is not easy. It must be done in assembler or C, and they are really hard to debug. And just to be safe, a device driver for Windows 95 (a VxD) isn't compatible with a device driver for Windows NT (a VDD, for virtual device driver). They are said to converge, and Windows NT 6.0 and Windows 2000 might have compatible device drivers, but until then, we're stuck with writing two separate pieces of code.
For more info see (for instance):
Microsoft's Windows 95 Device Driver Kit
Microsoft's Windows NT Device Driver Kit
Microsoft Press' "Systems Programming for Windows 95" by Walter Oney
Also, check out Vireo's VtoolsD library for writing VxD's in C.
http://www.vireo.com/.The above problem isn't too real. An application that accesses the hardware directly, is usually using some specialized hardware. A machine-configuration like that tend to run one application only, who's sole purpose is to access that hardware. In such a scenario, writing a device driver seems too much trouble. After all, the reason the thing is running on Windows, is just to get the nice GUI for (almost) free, not that 10 applications can be running simultaneously.
Fortunately, Windows 95 is built to be compatible with Windows 3.x. This means that direct I/O must be allowed, simply because a lot of Win 3.x programs uses it. To access the I/O ports, simply step down to assembly. The following code was supplied by Arthur Hoornweg ([email protected]):
function getport(p:word):byte; stdcall;
begin
asm
push edx
push eax
mov dx,p
in al,dx
mov @result,al
pop eax
pop edx
end;
end;
Procedure Setport(p:word;b:byte);Stdcall;
begin
asm
push edx
push eax
mov dx,p
mov al,b
out dx,al
pop eax
pop edx
end;
end;
François Piette also has some direct I/O access functions at
http://rtfm.netline.be/fpiette/portiofr.htmThe above will not work on Windows NT. NT is a much more robust operating system, and allowing all and everybody access to the hardware anytime they wanted, would seriously endager the stability. In addition, NT is cross platform, and access to I/O ports might be wildly different on different processors.
Even so, it is possible to access the I/O ports directly under NT on x86 processors. This is highly undocumented, and will probably disappear in future versions of the operating system.
I have not much information on the process, but an article by D. Roberts in the May, 1996 issue of Dr. Dobb's Journal looks promising: "Direct Port I/O and Windows NT." This seems to be the only DDJ I'm missing, so I can't verify it. See
http://www.ddj.com for ordring of back-issues.Windows Developer's Journal does have an article on "Port I/O under Windows." It's written by Karen Hazzah, and appeared in the June 1996 issue. See
http://www.wdj.com for ordering of back-issues.(Note, I know very little about these resources, check them out yourself.)
There are newsgroups dedicated to the topic of writing VxD's and VDD's:
comp.os.ms-windows.programmer.nt.kernel-mode (VDD)
comp.os.ms-windows.programmer.vxd (VxD)
Dejanews (
http://www.dejanews.com) turned up quite a few hits on 'device driver direct I/O access 95'.BlueWater Systems have developed OCX's for direct I/O, memory access and interrupt handling under all Win32 platforms. They also seem to offer custom built device drivers. See their page at
http://www.bluewatersystems.com/.I know some other company has been advertising here for their ability to write custom VxD's. But I can't find that reference.
function InPort(PortAddr: word): byte;
{$IFDEF VER90}
assembler; stdcall;
asm
mov dx,PortAddr
in al,dx
end;
{$ELSE}
begin
Result := Port[PortAddr];
end;
{$ENDIF}
procedure OutPort(PortAddr: word; Databyte: byte);
{$IFDEF VER90}
assembler; stdcall;
asm
mov al,Databyte
mov dx,PortAddr
out dx,al
end;
{$ELSE}
begin
Port[PortAddr] := DataByte;
end;
{$ENDIF}
Please email me and tell me if you liked this page.
Last modified 04/09/1998 11:26:32