Hardware Access

From Free Pascal wiki
Revision as of 07:15, 2 January 2010 by SunshineTech (talk | contribs)
Jump to navigationJump to search

Template:硬件访问

概览

本页是关于在Lazarus上访问硬件设备的教程的开始。这些设备包括,但不限于:ISA,PCI,USB,并行端口,串行端口。

RTL或LCL没有实现统一的多平台硬件设备的访问。因此本教程将基本覆盖不同平台上的硬件访问方法。在不同环境下可以使用条件编译来编译代码,像这样:

<delphi>

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

</delphi>

此时还不知道Mac OS X/x86是否会允许HW访问。它可能不允许,虽然那种情形下我假设像io.dll的驱动器将会及时出现。

并行和串行的比较

ISA卡,PCI卡和并行端口使用“并行”协议与计算机进行通信。串行端口和USB设备使用“串行”协议。因为处理器和编程语言都通过并行方式处理数据,在软件端比较容易实现对这类协议的访问。例如,访问一个整型变量时,可以仅用一个命令就能访问它的值。然而,使用串行协议,一次仅能知道一位(bit),需要将所有片断(piece)放在一起才能理解数据。

串行通信比较难于直接实现,如果使用预制(pre-made)组件,可能会稍微容易些。在硬件端也比较困难,因此许多设备使用专门的集成电路或甚至微控制器来实现。

现在来做一个硬件访问协议的简要比较:

速度 硬件实现难度
串行端口 非常慢 (< E5 bit/s) 中等
并行端口 慢 (~ E6 bit/s) 容易
ISA卡 中等 (~ E7 bit/s) 中等
USB 中等 (~ E7 bit/s) 困难
PCI卡 非常快 (> E9 bit/s) 非常难

并行通信

为Windows使用inpout32.dll

在9x系列和NT系列,Windows有不同的方法来访问硬件设备。在9x系列(95,98,Me),程序可以直接访问硬件,正如在DOS上一样。然而,NT系列(Windows NT和XP)不允许这种方式。在该架构上,所有与硬件端口的通信必须通过一个设备驱动器。这是一种安全机制,但是为一个小项目开发一个驱动器可能会花费太多的时间和金钱。

幸运地是有一个库解决了这个问题。如果检测到Windows NT,它解压HWInterface.sys内核设备驱动器并安装。如果检测到Windows 9x,它简单地使用汇编操作码访问硬件。

但是如何使用这个库呢?非常简单!它仅有2个函数:Inp32和Out32,它们的使用是比较直观的。

我们将动态加载该库,因此让我们首先定义这2个函数:

<delphi>

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

</delphi>

  • Address代表期望访问的端口地址
  • Out32发送数据到指定地址的端口
  • Inp32从指定地址的端口返回一字节

现在可以加载该库了。这可能在一个类似程序主form的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函数的简单例子:

<delphi>

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

</delphi>

该代码在Windows XP上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。

这是该库的主页:www.logix4u.net/inpout32.htm *查看讨论*

在Windows 9x上使用汇编

在Windows 9x上也可以使用汇编代码。假设希望把$CC发送到$320端口。下面代码可以实现:

<delphi>

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

</delphi>

在Windows上的疑难解答

在Windows上使用不支持即插即用并行硬件的一个可能问题来源是,Windows可能将该硬件使用的端口分配给了另外的设备。你可以在下面的URL里找到关于如何告诉Windows不要将你设备的地址分配给即插即用设备的指示:

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

在Linux上使用ioperm访问端口

在Linux上访问硬件的最好方法通过设备驱动器,但是考虑到创建一个驱动器任务的复杂性,有时候一个快速的方法是非常有用的。

为了使用Linux下的“ports”单元,程序必须以root身份运行,并且必须调用IOPerm设置端口访问的合适权限。你可以在这里找到关于“ports”单元的文档。

要做的第一件事是链接(g)libc和调用IOPerm。一个链接整个(g)libc的单元存在于free pascal,但是当应用直接使用时该单元出现了问题,并且静态链接整个(g)libc库不是一个非常好的主意,因为在不同版本间它以不兼容的方式改变。然而,像ioperm类的函数不大可能改变。

<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 fpIOPL and out-/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 compatibility. See libc unit

Serial Communication

It is very easy to build a serial communication software using the Synaser library. The example when used together with the 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

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 External Links section has UNIX and Windows serial port tutorials.


It is also worth noting the function of the TBlockSerial.LinuxLock parameter under linux. When set to default of True, a connect will try to create a lock file (eg. "LCK..ttyUSB0") under /var/lock and fail if a lock already exists for the requested port. The lock file will be left over if Free was not called. Setting LinuxLock to False will make Synaser ignore port locking.

Alternatives to Synaser:

There is also a Visual component 5dpo that is based in Synaser.

Another very simple fpc Serial unit is now part of freepascal (at least in version 2.2.2), just put Serial in your Uses list however there does not seem to be any documentation other than the Serial.pp source file.

USB

libusb

A cross platform possibility for Linux, BSDs and Mac OS X is libusb.

Headers are listed in http://www.freepascal.org/contrib/db.php3?category=Miscellaneous:

name author version date link remarks
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 http://www.johann-glaser.at/projects/libusb.pas
fpcusb Joe Jared 0.11-14 2006-02-02 http://relays.osirusoft.com/fpcusb.tgz download link broken

FTDI

If you use one of the chips from FTDI, you can use their pascal headers for their dll interface to the chips.

External Links

Communication Protocols speed comparison:

  1. http://en.wikipedia.org/wiki/Serial_port#Speed
  2. http://www.lvr.com/jansfaq.htm - Jan Axelson's Parallel Port FAQ
  3. http://en.wikipedia.org/wiki/USB#Transfer_Speed
  4. http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications

Serial Communication Links:

  1. On UNIX: [1]
  2. On Windows: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
  3. Synaser component: http://synapse.ararat.cz/
  4. Comport Delphi package: http://sourceforge.net/projects/comport/

ISA Digital Oscilloscope - A example of hardware access with full source included:

[2]


Networking