Hardware Access

From Free Pascal wiki
Revision as of 02:59, 27 November 2008 by Heennavi (talk | contribs)
Jump to navigationJump to search

패러랠 통신

윈도우에서 inpout32.dll의 이용

윈도우즈는 9x시리즈와 NT시리즈에서 하드웨어 접근에 있어서 다른 방법을 취하고 있다. 9x 시리즈(95, 98, Me)에서 프로그램은 DOS상에서 했듯이 하드웨어에 직접 접근 할 수가 있었다. 그러나 NT 시리즈(Windows NT and XP)에서는 이런 접근방법을 허용하고 있지가 않다. 이 구조에서 하드웨어 포트와의 모든 통신은 디바이스 드라이버를 통해 이루어진다. 이것은 보안 메커니즘이지만, 드라이버 개발에 작은 프로젝트에서도 시간과 비용등 많은 비용을 추가하게 된다.

행복하게도 이 문제를 해결할 수 있는 라이브러리가 있다. 만약 Windows NT가 탐지 된다면, HWInterface.sys 커널 디바이스 드라이버의 압축을 해제하고 설치를 한다. 만약 Windows 9x가 탐지된다면 하드웨어 접근은 단순히 어셈블러 opcode를 이용한다.

그렇지만 이 라이브러리를 어떻게 사용할까요? 간단합니다! 이것은 Inp32 와 Out32 등의 단지 두개의 함수만이 있으므로 사용법은 매우 직관적이다

라이브러리를 동적으로 로드하므로, 먼저 두개의 함수를 정의해 보자:

<delphi>

type
  TInp32 = function(Address: SmallInt): SmallInt; stdcall;
  TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;

</delphi>

  • Address는 접근하려는 포트의 어드레스를 표시한다
  • Out32는 지정한 Address의 포트로 Data를 전송한다
  • Inp32는 지정한 포트로부터 한개의 byte를 되돌린다

이제 라이브러리를 로드한다. 이것은 작성한 프로그램의 메인폼의 OnCreate 메소드같은곳에서 구현할 수가 있다:

<delphi>

type
  TMyForm = class(TForm)
  .........
  private
    { private declarations }
    Inpout32: THandle;
    Inp32: TInp32;
    Out32: TOut32;
  .........
implementation
  .........
procedure TMyForm.FormCreate(Sender: TObject);
begin
{$IFDEF WIN32}
  Inpout32 := LoadLibrary('inpout32.dll');
  if (Inpout32 <> 0) then
  begin
    // needs overtyping, plain Delphi's @Inp32 = GetProc... leads to compile errors
    Inp32 := TInp32(GetProcAddress(Inpout32, 'Inp32'));
    if (@Inp32 = nil) then Caption := 'Error';
    Out32 := TOut32(GetProcAddress(Inpout32, 'Out32'));
    if (@Out32 = nil) then Caption := 'Error';
  end
  else Caption := 'Error';
{$ENDIF}
end;

</delphi>

OnCreate 에서 라이브러리를 로드했으면 OnDestroy에서 언로드하는 것을 있으면 안된다:

<delphi>

procedure TMyForm.FormDestroy(Sender: TObject);
begin
{$IFDEF WIN32}
  FreeLibrary(Inpout32);
{$ENDIF}
end;

</delphi>

다음에 Inp32 함수를 이용하는 방법에 관한 단 Here is a simple example of how to use Inp32 function:

<delphi>

{$IFDEF WIN32}
  myLabel.Caption := IntToStr(Inp32($0220));
{$ENDIF}

</delphi>

This code was tested with a custom ISA card on port $0220, using Lazarus 0.9.10 on Windows XP. Of course you will need to have Windows on your uses clause in order for this code to run. For deployment you only need to include "inpout32.dll" in the same directory of our application.

This is the homepage for the library: www.logix4u.net/inpout32.htm *see discussion*

Using assembler on Windows 9x

On Windows 9x you can also use assembler code. Suppose you wish to write $CC to the $320 port. The following code will do it:

<delphi>

{$ASMMODE ATT}
...
   asm
       movl $0x320, %edx
       movb $0xCC, %al
       outb %al, %dx
   end ['EAX','EDX'];

</delphi>

Troubleshooting on Windows

One possible source of trouble using parallel hardware that does not support Plug And Play on Windows is that Windows may assign the port utilized by your hardware to another device. You can find instructions on the URL below about how to tell Windows not to assign the address of your device to Plug And Play devices:

http://support.microsoft.com/kb/135168

Using ioperm to access ports on Linux

The best way to access the hardware on Linux is throught device drivers, but, due to the complexity of the task of creating a driver, sometimes a quick method is very useful.

In order to use the "ports" unit under Linux your program must be run as root, and IOPerm must be called to set appropriate permissions on the port access. You can find documentation about the "ports" unit here.

The first thing to do is link to (g)libc and call IOPerm. A unit that links to the entire (g)libc exists on free pascal, but this unit gives problems when used directly by application and linking statically to the entire (g)libc library is not a very good idea because it changes often between version in an incompatible manner. Functions like ioperm, however, are unlikely to change.

<delphi>

{$IFDEF Linux}
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
{$ENDIF}

</delphi>

  • "from" represents the first port to be accessed.
  • "num" is the number of ports after the first to be accessed, so ioperm($220, 8, 1) will give access for the program for all ports between and including $220 and $227.

After linking to IOPerm you can port[<Address>] to access the ports.

<delphi>

{$IFDEF Linux}
  i := ioperm($220, 8, 1);
  port[$220] := $00;
  myLabel.Caption := 'ioperm: ' + IntToStr(i);
  i := Integer(port[$220]);
  myOtherLabel.Caption := 'response: ' + IntToStr(i);
{$ENDIF}

</delphi>

This code was tested with a custom ISA card on port $0220, using Lazarus 0.9.10 on Mandriva Linux 2005 and Damn Small Linux 1.5

General UNIX Hardware Access

<delphi> {$IFDEF Unix} Uses Clib; // retrieve libc library name. {$ENDIF}

{$IFDEF Unix} function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib; {$ENDIF} </delphi>


Note that FPC provides an abstraction for ioperm called "fpioperm" in unit x86, and also defines out and inport functions. These functions are currently implemented for Linux/x86 and FreeBSD/x86.

It is not recommended to link to libc unless absolutely necessary due to possible deployment and portability functions. Also manual linking to libc (by declaring ad hoc libc imports for functions that are available elsewhere) like done above is not recommended (e.g. the above libc import line will unnecessarily fail if the standard C lib is not called libc, like e.g. libroot on BeOS, or on platforms with a non standard C symbol mangling).

Note 2 Using _unit_ libc is not recommended under any circumstances other than Kylix compability. This because the unit is relatively unportable (due to excessive exposure of structures and other private symbols) and must only be modified as little as possible out of Kylix compability issues.