Hardware Access/ko

From Free Pascal wiki

Deutsch (de) English (en) español (es) français (fr) magyar (hu) 日本語 (ja) 한국어 (ko) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

개관

이 페이지는 라자루스상에서 하드웨어 장치(디바이스)에 접근하기위한 튜토리얼의 시작입니다. 이 장치들은 ISA, PCI, USB, parallel port, serial port 등을 포함하지만 이것들에 제한된것은 아닙니다. 완전한 멀티 플랫폼상에서 하드웨어에 접근하는 것은 프리파스칼 라이브러리나 LCL에의해 구현되지는 않았으므로 이 튜토리얼은 다른 플랫폼상에서의 기초적인 하드웨어 접근 방법을 다룰 것 입니다. 조건적인 컴파일에 의해 다른 플랫폼에서 컴파일된 코드는 다음과 같을 것이다:

 uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
 {$IFDEF WIN32}
   Windows;
 {$ENDIF}
 {$IFDEF Unix}
   ports;
 {$ENDIF}

이 시점에서 Mac OX/x86이 HW 접근을 허용하는지는 아직 확실치는 않다. 이 경우에서는 내가 가정하듯이 위의 방법이 허용되지 않을 수 있지만 곧 io.dll같은 드라이버가 나타날 것이다.

패러랠과 시리얼 의 비교

ISA카드, PCI 카드 및 패러랠 포트는 parallel 프로토콜에 의해 컴퓨터와 통신을 한다. 시리얼 포트와 USB장치는 serial 프로토콜로 통신한다. 프로세서와 프로그래밍 언어는 모두 데이터에 패러랠한 접근을 통해 작업을 수행하기 때문에 이런 종류의 프로토콜에 대한 접근은 소프트웨어 측면에서 보면 구현하기가 더 쉽다.예를 들어, Integer 변수에 접근할 때, 단일 명령으로 그 값에 접근할 수가 있다. 그러나 시리얼 포트 프로토콜에서는 동시에 한개의 비트만을 알 수 밖에 없으며 데이터를 이해하기 위해서는 그 비트 조각들을 붙여 놓을 필요가 있다.

시리얼 툥신은 직접적으로 구현하기가 어렵지만, 미리 만들어 놓은(pre-made) 컴포넌트를 사용한다면 매우 쉽게 구현할 수가 있다.이는 하드웨어 측면에서도 역시 어려운건 마찬가지이기 때문에 많은 장치들은 그것을 구현하기 위해 특별한 통합 써킷이나 마이크로 컨트롤러를 사용한다.

이제 다음에 하드웨어 접근 프로토콜에 대한 간략한 비교가 있다:

속도 하드웨어 구현의 난이도
시리얼 포트 매우 느림 (< E5 bit/s) 중간
패러랠 포트 느림 (~ E6 bit/s) 쉬움
ISA 카드 중간(~ E7 bit/s) 중간
USB 중간(~ E7 bit/s) 어려움
PCI 카드 매우 빠름 (> E9 bit/s) 매우 어려움

패러랠 통신

윈도우에서 inpout32.dll의 이용

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

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

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

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

 type
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
  • Address는 접근하려는 포트의 어드레스를 표시한다
  • Out32는 지정한 Address의 포트로 Data를 전송한다
  • Inp32는 지정한 포트로부터 한개의 byte를 되돌려 준다

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

 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;

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

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

다음에 Inp32 함수를 이용하는 방법에 관한 단순한 예제가 있다:

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

이 코드는 Windows XP상의 포트 $0220에 있는 커스텀 ISA카드에서 Lazarus 0.9.10으로 터스트하였다. 물론 이코드를 실행하기 위해서는 uses 절에 Windows를 넣어야 한다. 개발을 위해 할 일은 작성하는 응용프로그램과 같은 디렉토리에 있는"inpout32.dll"를 인클루드(include)하기만 하면 된다.

이 라이브러리에 관한 홈페이지는 다음과 같다: www.logix4u.net/inpout32.htm *토론을 보세요*

Windows 9x에서 어셈블러 사용

Windows 9x에서 역시 어셈블러 코드를 사용할 수 있다. $CC를 $320 포트에 쓰고싶다고 가정해보자. 다음 코드가 그렇게 해줄 것이다:

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

Windows상에서 문제 해결

Windows상에서 플럭앤플레이를 지원하지 않는 패러랠 하드웨어를 사용할 때 문제를 일으키는 가능한 한가지는 당신의 하드웨어에 의해 사용되고 있는 포트를 다른 하드웨어에 할당할 때 일어난다. Windows가 당신의 디바이스 주소에 플럭앤플레이 디바이스를 할당하지 못하도록 하는 방법을 아래 URL에서 그에 대해 설명된 것을 찾을 수 있을 것이다:

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

리눅스상에서 포트 접근을 위해서는 ioperm을 사용

리눅스에서 하드웨어에 접근하는 가장 좋은 방법은 디바이스 드라이버를 통해 하는 것이지만 드라이버를 만드는 작업이 너무 복잡함으로 인해 가끔은 빠른 방법이 매우 유용하다.

리눅스에서 "ports" unit를 이용하기 위해서는 프로그램은 root로 실행해야만 하며 포트접근을 위한 적절한 퍼미션 설정을 위해 IOPerm을 콜해야 한다. "ports" 유닛에 관한 문서는 다음에서 찾을 수 있다. 이곳.

첫번째 할 일은 (g)libc를 링크하고 IOPerm을 콜하는 것이다. 유닛과 연결된 완전한 (g)libc가 프리파스칼에 존재하지만 이 유닛은 어플리케이션이 직접 사용할 때 문제를 야기하고, 또한 이것을 완전한 (g)libc 라이브러리에 정적으로 연결하는 것은 호환되지 않은 방법으로 버전이 변화하기 때문에 좋은 생각은 아니다. 그러나 ioperm같은 함수는 변하지 않을 것 같다.

 {$IFDEF Linux}
 function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
 {$ENDIF}
  • "from" 은 접근할 수 있는 첫번째 포트를 표시한다.
  • "num" 은 첫번째 접근한 다음의 포트 수이므로 ioperm(&220, 8, 1)은 프로그램에게 $220과 $227를 포함한 그 사이의 모든 포트를 접근하게 할 것이다.

IOPerm에 링크 한 후 포트에 접근하기 위해 port[<Address>]를 사용할 수 있다.

 {$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}

이 코드는 커스텀 ISA 카드의 포트 $0220에서 테스트 하였으며, Mandriva Linux 2005 와 Damn Small Linux 1.5상에서 Lazarus 0.9.10을 사용하였다.

일반적인 유닉스 하드웨어 접근

{$IFDEF Unix}
Uses Clib;   // libc 라이브러리 이름을 가져옴.
{$ENDIF}

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


Note 1 :FPC는 "fpioperm"이라 불리는 ioperm에 대한 추상화를 unit x86에서 제공하며 또한 out과 inport 함수를 제공한다. 이 함수들은 현재 Linux/x86 과 FreeBSD/x86에서도 구현되어 있다.

배포와 이동성 기능 때문에 반드시 필요한 것이 아니라면 libc에 링크하는 것은 추천하지 않는다. 또한 위에서 한 것처럼 수동으로 libc에 링크(다른 곳에 필요한 함수 때문에 특별히 libc 임포트를 선언하여)하는 것도 추천되지 않는다.(예, 위의 libc 임포트(import) 라인은 표준(standard) C lib가 BeOS상의 libroot나 비표준 C심볼을 가진 플랫폼같이 libc에 의해 콜되지 않으면 실패하게 된다.

Note 2 _unit_ libc를 사용하는 것 역시 Kylix 호환을 제외한 어떤 환경하에서도 추천되지 않는다. 이것은 유닛은 상대적으로 이동성이 떨어지며(구조체와 다른 private 심볼의 과도한 노출(excessive exposure)) Kylix와 호환이 되지 않는 것에서는 가능한 적은 수정만이 필요하기 때문이다.

시리얼 통신

시리얼 통신 소프트웨어를 빌드하는 것은 Synaser library를 이용하면 매우 쉽다. Synaser 문서와 함께 사용한 예는 이해하기가 쉬워야 한다. 가장 중요한 부분은 속도(초당 bits)와 데이터 비트, 패리티 비트, 스톱 비트 및 핸드쉐이크 프로토콜 등을 조정하는 TBlockSerial.Config이다. 다음의 코드는 COM 1에 연결된 시리얼 마우스에서 테스트 하였다.

program comm;

{$apptype console}

uses
  Classes, SysUtils, Synaser;

var
  ser: TBlockSerial;
begin
  ser:=TBlockSerial.Create;
  try
    ser.Connect('COM1');
    ser.config(1200, 7, 'N', SB1, False, False);
    while True do
      Write(IntToHex(ser.RecvByte(10000), 2), ' ');
  finally
    ser.free;
  end;
end.

다음의 코드-에제는 상기에 있는 예제의 또 다른 버전이다. 위에 보인 예는, 정확히 말하자면 메인 컨셉에서 결정적으로 잘못된 것이 있어 보인다. 이 부분은 "while true do..."이다. 테스트에서 시스템(Asus A6T 랩탑으로 Digitus USB가 RS232 어댑터에 연결되어 있으며 Ubuntu 8.04.1를 사용한다)에서 이 부분은 다음과 같은 에러를 일으켰다: 어플리케이션은 세션당 단 한번 만 성공적으로 실행이 되었고, 어플리케이션을 다시 시작하면, 어프리케이션은 시리얼 포트에 연결할 수가 없었다. 그러므로 사용자가 어플리케이션을 다시 시작할 때마다 리부트가 필요했으므로, 이는 정말 불쾌한 버그인 것이다.

이유를 이해하는 것은 어렵지 않다: 어플리케이션은 while true do 루프에 있게 되면, 그것은 정확히 말하면 무한 루프이다. 중단할 방법이 없기 때문에 어플리케이션을 중단시킬 유일한 방법은 터미널을 닫거나 CTRL-C를 누르는 것 뿐이다. 그러나 어플리케이션을 이런 방법으로 종료한다면, 시리얼 포트를 해제하는 "ser.free"의 중요한 부분이 콜되지 않는다. 이 문제는 다음의 독일의 라자루스 포럼http://www.lazarusforum.de/viewtopic.php?f=10&t=2082에 설명되어 있다.

각 사용자가 CTRL-C를 누르지 않고 클리어하기 위한 메인 어플리케이션에서의 코드는 약간 만 필요하다. /dev/ttyUSB0를 com-포트를 위해 사용하는 것에 대해 걱정하는 사람은 테스트 시스템상의 USB-시리얼 어댑터(Digitus) 때문이다.만약 ㅅ스템에 내장된 시리얼 포트를 가지고 있다면 'Com0'를 사용하는 것이 좋다.- 위의 예와 같이 코드와 같이 선언해서.

program serialtest;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,SysUtils,Synaser,Crt
  { you can add units after this };

  var l:boolean;

  function check_affirmation():boolean;
  var k:string;
  begin
       Writeln('To quit the application please do NOT use CTRL-C! Instead, please press any key to quit the application! '+
       'Please confirm this notification before the application continues! '+
       '[0]=Quit, [1]=Confirm, please continue! ');
       Writeln('Your decision: ');
       Read(k);
       if StrtoInt(k) = 1 then
       begin
            check_affirmation:=true;
            Writeln('OK, application continues ...');
       end
       else
       begin
            check_affirmation:=false;
            Writeln('Abort');
       end
  end;

  procedure RS232_connect;
  var
     ser: TBlockSerial;
  begin
       ser:=TBlockSerial.Create;
       try
          ser.Connect('/dev/ttyUSB0'); //ComPort
          Sleep(1000);
          ser.config(1200, 7, 'N', SB1, False, False);
          Write('Device: ' + ser.Device + '   Status: ' + ser.LastErrorDesc +' '+
          Inttostr(ser.LastError));
          Sleep(1000);
          repeat
                Write(IntToHex(ser.RecvByte(10000), 2), ' ');
          until keypressed; //Important!!!
       finally
              Writeln('Serial Port will be freed...');
              ser.free;
              Writeln('Serial Port was freed successfully!');
       end;
  end;

  begin
     l:=check_affirmation();
     if l=true then
     RS232_connect()
     else
     Writeln('Program quit! ');
  end.

또한, 외부 링크 섹션에 유닉스와 윈도우즈 시리얼 포트 튜토리얼이 있다.

USB

libusb

libusb 는 Linux, BSDs 및 Mac OS X 등의 크로스 플랫폼을 지원합니다.

헤더를 구할 수 있는 링크는 다음과 같습니다. http://www.freepascal.org/contrib/db.php3?category=Miscellaneous :

이름 제작자 버전 일자 링크 비고
libusb.pp Uwe Zimmermann 0.1.12 2006-06-29 http://www.sciencetronics.com/download/fpc_libusb.tgz
libusb.pas Johann Glaser 2005-01-14 https://github.com/hansiglaser/pas-libusb OOP 래퍼를 포함하며, LibUSB 1.0는 곧 나온다
fpcusb Joe Jared 0.11-14 2006-02-02 http://relays.osirusoft.com/fpcusb.tgz 다운로드 링크 깨짐
libusb.pp Marko Medic 1.0 2010-12-14 http://www.lazarus.freepascal.org/index.php/topic,11435.0.html

FTDI

만약 FTDI의 칩 중의 하나를 사용한다면, 칩을 위한 dll 인터페이스용 파스칼 헤더를 사용할 수 있다.

추가적으로 볼 것

외부 링크

통신 프로토콜 속도 비교:

  1. http://en.wikipedia.org/wiki/Serial_port#Speed
  2. http://www.lvr.com/jansfaq.htm - Jan Axelson의 패러랠 포트 FAQ
  3. http://en.wikipedia.org/wiki/USB#Transfer_Speed
  4. http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications

시리얼 통신 링크:

  1. UNIX에서: [1]
  2. Windows에서: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
  3. Synaser 콤포넌트: http://synapse.ararat.cz/
  4. Comport 델파이 패키지e: http://sourceforge.net/projects/comport/

ISA 디지탈 오실로 스코프- 하드웨어 접근에 대한 완전 소스가 포함된 예제:

[2]


네트워킹