Difference between revisions of "Hardware Access"

From Free Pascal wiki
Jump to navigationJump to search
Line 1: Line 1:
{{Hardware Access}}
+
==패러랠 통신==
__TOC__
 
==Overview==
 
This page is the start of a tutorial about accessing hardware devices on Lazarus. These devices include, but are not limited to: ISA, PCI, USB, parallel port, serial port.
 
  
Access to hardware devices on a completely multi-platform way is not implemented by Free Pascal Runtime Library or by the LCL, so this tutorial will basically cover hardware access methods on different platforms. The code can be compiled on different environments using conditional compiles, like this:
+
===윈도우에서 inpout32.dll의 이용===
 +
윈도우즈는 9x시리즈와 NT시리즈에서 하드웨어 접근에 있어서 다른 방법을 취하고 있다. 9x 시리즈(95, 98, Me)에서 프로그램은 DOS상에서 했듯이 하드웨어에 직접 접근 할 수가 있었다. 그러나 NT 시리즈(Windows NT and XP)에서는 이런 접근방법을 허용하고 있지가 않다. 이 구조에서 하드웨어 포트와의 모든 통신은 디바이스 드라이버를 통해 이루어진다. 이것은 보안 메커니즘이지만, 드라이버 개발에 작은 프로젝트에서도 시간과 비용등 많은 비용을 추가하게 된다.
  
<delphi>
+
행복하게도 이 문제를 해결할 수 있는 라이브러리가 있다. 만약 Windows NT가 탐지 된다면, HWInterface.sys 커널 디바이스 드라이버의 압축을 해제하고 설치를 한다. 만약 Windows 9x가 탐지된다면 하드웨어 접근은 단순히 어셈블러 opcode를 이용한다.
uses
 
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
 
{$IFDEF WIN32}
 
  Windows;
 
{$ENDIF}
 
{$IFDEF Unix}
 
  ports;
 
{$ENDIF}
 
</delphi>
 
 
 
It is not known yet, at this time, if Mac OS X/x86 will allow HW access. It can disallow it, though I assume in that case, in time, drivers like io.dll will appear.
 
 
 
==Parallel and Serial Comparison==
 
ISA Cards, PCI Cards and the Parallel Port communicate with the computer using a '''parallel''' protocol. The Serial Port and USB devices work with a '''serial''' protocol. Because the processor and thus programming languages all work on a parallel approach to data, access to this kinds of protocols is easier to be implemented on the software side. When you access an Integer variable, for example, you can access it's value with a single command. With a serial protocol, however, you can only know one bit at a time, and you need to glue the pieces together to understand the data.
 
 
 
Serial communication is difficult to be implemented directly, but it can be pretty easy if you use a pre-made component. It is also harder on the hardware side, so many devices use specialised Integrated Circuits or even Microcontrolers to implement it.
 
 
 
Now a brief comparison of hardware access protocols will be given:
 
 
 
{| border=2 width="100%"
 
 
 
|-
 
!
 
! Speed
 
! Hardware implementation difficulty
 
 
 
|-
 
! Serial Port
 
| align="center" | Very slow (< E5 bit/s)
 
| align="center" | Medium
 
 
 
|-
 
! Parallel Port
 
| align="center" | Slow (~ E6 bit/s)
 
| align="center" | Easy
 
 
 
|-
 
 
 
! ISA Card
 
| align="center" | Medium (~ E7 bit/s)
 
| align="center" | Medium
 
 
 
|-
 
! USB
 
| align="center" | Medium (~ E7 bit/s)
 
| align="center" | Hard
 
 
 
|-
 
! PCI Card
 
| align="center" | Very Fast (> E9 bit/s)
 
| align="center" | Very Hard
 
 
 
|}
 
 
 
==Parallel Communication==
 
 
 
===Using inpout32.dll for Windows===
 
Windows has different ways to access hardware devices on the 9x series and on the NT series. On the 9x series (95, 98, Me) programs can access the hardware directly, just like they did on DOS. The NT series (Windows NT and XP), however, don't allow this approach. On this architecture, all communication with hardware ports must be throught a device driver. This is a security mechanism, but developing a driver can cost too much in terms of time and money for small projects.
 
 
 
Happily there is a library that solves this problem. If Windows NT is detected, it decompresses HWInterface.sys kernel device driver and installs it. If Windows 9x is detected, it simply uses assembler opcodes to access the hardware.
 
  
But how do I use the library? Simple! It has only two functions, Inp32 and Out32, and their use is quite intuitive.
+
그렇지만 이 라이브러리를 어떻게 사용할까요? 간단합니다! 이것은 Inp32 Out32 등의 단지 두개의 함수만이 있으므로 사용법은 매우 직관적이다
  
We will load the library dynamically, so let's define both functions first:
+
라이브러리를 동적으로 로드하므로, 먼저 두개의 함수를 정의해 보자:
  
 
<delphi>
 
<delphi>
Line 78: Line 16:
 
</delphi>
 
</delphi>
  
* Address represents the address of the port you desire to access
+
* Address는 접근하려는 포트의 어드레스를 표시한다
* Out32 sends Data to the port you specify by Address
+
* Out32는 지정한 Address의 포트로 Data를 전송한다
* Inp32 returns a byte from the port you specify by Address
+
* Inp32는 지정한 포트로부터 한개의 byte를 되돌린다
  
Now we can load the library. This can be implemented in a place like the OnCreate method of your program's main form:
+
이제 라이브러리를 로드한다. 이것은 작성한 프로그램의 메인폼의 OnCreate 메소드같은곳에서 구현할 수가 있다:
  
 
<delphi>
 
<delphi>
Line 113: Line 51:
 
</delphi>
 
</delphi>
  
If you load the library on OnCreate just don't forget to unload it in OnDestroy:
+
OnCreate 에서 라이브러리를 로드했으면 OnDestroy에서 언로드하는 것을 있으면 안된다:
  
 
<delphi>
 
<delphi>
Line 124: Line 62:
 
</delphi>
 
</delphi>
  
 +
다음에 Inp32 함수를 이용하는 방법에 관한 단
 
Here is a simple example of how to use Inp32 function:
 
Here is a simple example of how to use Inp32 function:
  
Line 206: Line 145:
  
 
'''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.
 
'''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.
 
==Serial Communication==
 
 
It is very easy to build a serial communication software using the [http://synapse.ararat.cz/doku.php Synaser library]. The example when used together with the [http://synapse.ararat.cz/doc/help/synaser.html Synaser documentation] should be trivial to understand. The most important part is TBlockSerial.Config to configure the speed (in bits per second), data bits, parity bits, stop bits and handshake protocol, if any. The following code was tested with a serial mouse connected to COM 1.
 
 
<delphi>
 
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.
 
</delphi>
 
 
The following code-example is an alternative version of the example above. The example above seems to have a critically fault in its main concept, to be exactly, it is the part with "while true do...". On the Test - System (Asus A6T Laptop with Digitus USB to RS232 Adapter, Ubuntu 8.04.1), this part caused the following error: The application ran only one time successfully per session, when the application was started again, the application was unable to connect to the serial port. Thus, a reboot was necessary everytime the user tried to relaunch the application, which is/was a really annoying bug.
 
 
The reason is not difficult to understand: The application is in the while true do - loop, which is, to be more precisely, an endless loop. There is no abort-condition, so the only way to close the application is to close the terminal or to press CTRL-C. But if you quit the application this way, the important part with "ser.free", which frees the serial port, will never be called. This problem is described in the following thread in the German Lazarus-Forum [http://www.lazarusforum.de/viewtopic.php?f=10&t=2082 http://www.lazarusforum.de/viewtopic.php?f=10&t=2082]
 
 
There is a bit code around the main application to make every user clear, not to press CTRL-C. If anyone is worrying, why /dev/ttyUSB0 is used for com-port: this is due to the USB to Serial Adapter (from Digitus) on the test-system. If you have an built-in serial port, please use the 'Com0' - declaration like in the code - example above.
 
 
<delphi>
 
 
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.
 
 
</delphi>
 
 
Also, the [[Hardware Access#External Links | External Links]] section has UNIX and Windows serial port tutorials.
 
 
==USB==
 
 
===libusb===
 
 
A cross platform possibility for Linux, BSDs and Mac OS X is [http://libusb.sourceforge.net/ libusb].
 
 
Headers are listed in http://www.freepascal.org/contrib/db.php3?category=Miscellaneous:
 
 
{|cellpadding="4"
 
|-
 
! name !! author !! version !! date !! link !! remarks
 
|-
 
| libusb.pp || nowrap | Uwe Zimmermann || 0.1.12 || nowrap | 2006-06-29 || http://www.sciencetronics.com/download/fpc_libusb.tgz ||
 
|-
 
| libusb.pas || Johann Glaser ||  || nowrap | 2005-01-14 || http://www.johann-glaser.at/projects/libusb.pas ||
 
|-
 
| fpcusb || Joe Jared || 0.11-14 || nowrap | 2006-02-02 || http://relays.osirusoft.com/fpcusb.tgz || nowrap | download link broken
 
|}
 
 
===FTDI===
 
 
If you use one of the chips from [http://www.ftdichip.com/ FTDI], you can use their pascal headers for their dll interface to the chips.
 
 
==External Links==
 
 
Communication Protocols speed comparison:
 
 
# http://en.wikipedia.org/wiki/Serial_port#Speed
 
# http://www.lvr.com/jansfaq.htm - Jan Axelson's Parallel Port FAQ
 
# http://en.wikipedia.org/wiki/USB#Transfer_Speed
 
# http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications
 
 
Serial Communication Links:
 
 
# On UNIX: [http://www.easysw.com/~mike/serial/serial.html]
 
# On Windows: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
 
# Synaser component: http://synapse.ararat.cz/
 
# Comport Delphi package: http://sourceforge.net/projects/comport/
 
 
ISA Digital Oscilloscope - A example of hardware access with full source included:
 
 
[http://eletronicalivre.incubadora.fapesp.br/portal/english/oscilloscope/]
 
 
----
 
[[Networking]]
 

Revision as of 03:59, 27 November 2008

패러랠 통신

윈도우에서 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.