Serial Communication
//{$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}
(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 <martin.larsson@delfi-data.msmail.telemax.no>
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 (hoornweg@hannover.sgh-net.de):
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.htm
The 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}