Serial Communication

  1. DELPHI 2/3
  2. Port command and win95, a summary.
  3. Hardware port access in DELPHI 2

DELPHI 2/3

From: "Ed Lagerburg" <lagerbrg@euronet.nl>


//{$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.

Port command and win95, a summary.

From: Martin Larsson <martin.larsson@delfi-data.msmail.telemax.no>

APPOLOGY

This was supposed to be a quick summary. It ended up being quite long. Hope it's not too boring...

THE PROBLEM

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 DOCUMENTED WAY

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 UNDOCUMENTED WAY

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

BUT WHAT ABOUT NT?

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.

RESOURCES

(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.

Hardware port access in DELPHI 2


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.