Difference between revisions of "Hardware Access/zh CN"

From Lazarus wiki
Jump to navigationJump to search
m
m (Text replace - "delphi>" to "syntaxhighlight>")
Line 7: Line 7:
 
RTL或LCL没有实现统一的多平台硬件设备的访问。因此本教程将基本覆盖不同平台上的硬件访问方法。在不同环境下可以使用条件编译来编译代码,像这样:
 
RTL或LCL没有实现统一的多平台硬件设备的访问。因此本教程将基本覆盖不同平台上的硬件访问方法。在不同环境下可以使用条件编译来编译代码,像这样:
  
<delphi>
+
<syntaxhighlight>
 
  uses
 
  uses
 
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
 
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
Line 16: Line 16:
 
   ports;
 
   ports;
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
 
此时还不知道Mac OS X/x86是否会允许HW访问。它可能不允许,虽然那种情形下我假设像io.dll的驱动器将会及时出现。
 
此时还不知道Mac OS X/x86是否会允许HW访问。它可能不允许,虽然那种情形下我假设像io.dll的驱动器将会及时出现。
Line 73: Line 73:
 
我们将动态加载该库,因此让我们首先定义这2个函数:
 
我们将动态加载该库,因此让我们首先定义这2个函数:
  
<delphi>
+
<syntaxhighlight>
 
  type
 
  type
 
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
 
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
 
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
 
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
</delphi>
+
</syntaxhighlight>
  
 
* Address代表期望访问的端口地址
 
* Address代表期望访问的端口地址
Line 85: Line 85:
 
现在可以加载该库了。这可能在一个类似程序主form的OnCreate方法的地方来实现:
 
现在可以加载该库了。这可能在一个类似程序主form的OnCreate方法的地方来实现:
  
<delphi>
+
<syntaxhighlight>
 
  type
 
  type
 
   TMyForm = class(TForm)
 
   TMyForm = class(TForm)
Line 112: Line 112:
 
  {$ENDIF}
 
  {$ENDIF}
 
  end;
 
  end;
</delphi>
+
</syntaxhighlight>
  
 
如果在OnCreate加载了该库,那么不要忘记在OnDestroy卸载它:
 
如果在OnCreate加载了该库,那么不要忘记在OnDestroy卸载它:
  
<delphi>
+
<syntaxhighlight>
 
  procedure TMyForm.FormDestroy(Sender: TObject);
 
  procedure TMyForm.FormDestroy(Sender: TObject);
 
  begin
 
  begin
Line 123: Line 123:
 
  {$ENDIF}
 
  {$ENDIF}
 
  end;
 
  end;
</delphi>
+
</syntaxhighlight>
  
 
这里是一个如何使用Inp32函数的简单例子:
 
这里是一个如何使用Inp32函数的简单例子:
  
<delphi>
+
<syntaxhighlight>
 
  {$IFDEF WIN32}
 
  {$IFDEF WIN32}
 
   myLabel.Caption := IntToStr(Inp32($0220));
 
   myLabel.Caption := IntToStr(Inp32($0220));
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
 
该代码在Windows XP上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。
 
该代码在Windows XP上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。
Line 141: Line 141:
 
在Windows 9x上也可以使用汇编代码。假设希望把$CC发送到$320端口。下面代码可以实现:
 
在Windows 9x上也可以使用汇编代码。假设希望把$CC发送到$320端口。下面代码可以实现:
  
<delphi>
+
<syntaxhighlight>
 
  {$ASMMODE ATT}
 
  {$ASMMODE ATT}
 
  ...
 
  ...
Line 149: Line 149:
 
         outb %al, %dx
 
         outb %al, %dx
 
     end ['EAX','EDX'];
 
     end ['EAX','EDX'];
</delphi>
+
</syntaxhighlight>
  
 
===在Windows上的疑难解答===
 
===在Windows上的疑难解答===
Line 165: Line 165:
 
要做的第一件事是链接(g)libc和调用IOPerm。一个链接整个(g)libc的单元存在于free pascal,但是当应用直接使用时该单元出现了问题,并且静态链接整个(g)libc库不是一个非常好的主意,因为在不同版本间它以不兼容的方式改变。然而,像ioperm类的函数不大可能改变。
 
要做的第一件事是链接(g)libc和调用IOPerm。一个链接整个(g)libc的单元存在于free pascal,但是当应用直接使用时该单元出现了问题,并且静态链接整个(g)libc库不是一个非常好的主意,因为在不同版本间它以不兼容的方式改变。然而,像ioperm类的函数不大可能改变。
  
<delphi>
+
<syntaxhighlight>
 
  {$IFDEF Linux}
 
  {$IFDEF Linux}
 
  function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
 
  function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
 
* from 代表访问的第一个端口.
 
* from 代表访问的第一个端口.
Line 176: Line 176:
 
在链接IOPerm后,可以使用port[<Address>]访问端口。
 
在链接IOPerm后,可以使用port[<Address>]访问端口。
  
<delphi>
+
<syntaxhighlight>
 
  {$IFDEF Linux}
 
  {$IFDEF Linux}
 
   i := ioperm($220, 8, 1);
 
   i := ioperm($220, 8, 1);
Line 184: Line 184:
 
   myOtherLabel.Caption := 'response: ' + IntToStr(i);
 
   myOtherLabel.Caption := 'response: ' + IntToStr(i);
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
 
该代码在Mandriva Linux 2005和Damn Small Linux 1.5上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。
 
该代码在Mandriva Linux 2005和Damn Small Linux 1.5上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。
Line 190: Line 190:
 
===通用UNIX硬件访问===
 
===通用UNIX硬件访问===
  
<delphi>{$IFDEF Unix}
+
<syntaxhighlight>{$IFDEF Unix}
 
Uses Clib;  // retrieve libc library name.
 
Uses Clib;  // retrieve libc library name.
 
{$ENDIF}
 
{$ENDIF}
Line 196: Line 196:
 
{$IFDEF Unix}
 
{$IFDEF Unix}
 
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
 
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
{$ENDIF}</delphi>
+
{$ENDIF}</syntaxhighlight>
  
  
Line 210: Line 210:
 
使用[http://synapse.ararat.cz/doku.php Synaser库]开发一个串行通信软件是非常容易的。当与[http://synapse.ararat.cz/doc/help/synaser.html Synaser文档]一起使用时,例子应该很好理解。最主要的部分是TBlockSerial.Config设置速度(位/秒),数据位,奇偶校验位,停止位和握手协议,如果有的话。下面代码在一个连接到COM 1的串行鼠标上测试过。
 
使用[http://synapse.ararat.cz/doku.php Synaser库]开发一个串行通信软件是非常容易的。当与[http://synapse.ararat.cz/doc/help/synaser.html Synaser文档]一起使用时,例子应该很好理解。最主要的部分是TBlockSerial.Config设置速度(位/秒),数据位,奇偶校验位,停止位和握手协议,如果有的话。下面代码在一个连接到COM 1的串行鼠标上测试过。
  
<delphi>
+
<syntaxhighlight>
 
program comm;
 
program comm;
  
Line 231: Line 231:
 
   end;
 
   end;
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
下面的代码例子是上面例子的一个替代版本。上面的例子看起来在主要概念上有严重错误,准确地是“while true do...”部分。在测试系统(Asus A6T Laptop with Digitus USB to RS232 Adapter, Ubuntu 8.04.1)上,该部分导致了下面错误:每个会话只能成功运行一次应用,当再次起动应用时,应用不能连接到串行端口。因此,每次用户试图重启应用时,需要重启(计算机),这是一个非常恼人的bug。  
 
下面的代码例子是上面例子的一个替代版本。上面的例子看起来在主要概念上有严重错误,准确地是“while true do...”部分。在测试系统(Asus A6T Laptop with Digitus USB to RS232 Adapter, Ubuntu 8.04.1)上,该部分导致了下面错误:每个会话只能成功运行一次应用,当再次起动应用时,应用不能连接到串行端口。因此,每次用户试图重启应用时,需要重启(计算机),这是一个非常恼人的bug。  
Line 239: Line 239:
 
为了让每个用户明白,而不是按CTRL-C,在主要应用周围有一些代码。也许有人担心,为什么/dev/ttyUSB0被用作com端口:这是由于在测试系统上使用的USB串行适配器。如果有内置串行端口,请使用“Com0”——像上面例子代码中声明。  
 
为了让每个用户明白,而不是按CTRL-C,在主要应用周围有一些代码。也许有人担心,为什么/dev/ttyUSB0被用作com端口:这是由于在测试系统上使用的USB串行适配器。如果有内置串行端口,请使用“Com0”——像上面例子代码中声明。  
  
<delphi>
+
<syntaxhighlight>
  
 
program serialtest;
 
program serialtest;
Line 304: Line 304:
 
   end.
 
   end.
  
</delphi>
+
</syntaxhighlight>
  
 
另外,[[Hardware Access#External Links |外部链接]]节有UNIX和Windows串行端口教程。
 
另外,[[Hardware Access#External Links |外部链接]]节有UNIX和Windows串行端口教程。

Revision as of 15:27, 24 March 2012

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)

概览

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

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

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

此时还不知道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个函数:

 type
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
  • Address代表期望访问的端口地址
  • Out32发送数据到指定地址的端口
  • Inp32从指定地址的端口返回一字节

现在可以加载该库了。这可能在一个类似程序主form的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上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。

这是该库的主页: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里找到关于如何告诉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类的函数不大可能改变。

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

该代码在Mandriva Linux 2005和Damn Small Linux 1.5上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。

通用UNIX硬件访问

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


注意 FPC在unit x86里为ioperm提供了一个叫做“fpioperm”的抽象,也定义了fpIOPL和输出/输入函数。这些函数当前是为Linux/x86和FreeBSD/x86实现的。

不建议链接到的libc,除非绝对必要的,因为可能的部署和移植功能。 像上面那样(通过为别处有效的函数声明特设导入)手工链接libc也是不建议的(例如,如果标准C库不叫作libc,那么上面的libc导入行将不必要(unnecessarily)地失败,比如BeOS或非标准C符号扩展(symbol mangling)平台上的libroot)。

注意 2 不建议在除了Kylix兼容性的任何环境下使用unit libc。参见libc unit

串行通信

使用Synaser库开发一个串行通信软件是非常容易的。当与Synaser文档一起使用时,例子应该很好理解。最主要的部分是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 Laptop with Digitus USB to RS232 Adapter, Ubuntu 8.04.1)上,该部分导致了下面错误:每个会话只能成功运行一次应用,当再次起动应用时,应用不能连接到串行端口。因此,每次用户试图重启应用时,需要重启(计算机),这是一个非常恼人的bug。

原因不难理解:应用处于while true do循环,更准确地说是无限循环。没有退出条件,因此关闭应用的唯一办法是关闭终端或按CTRL-C。但是如果通过种办法退出应用,释放串行端口的重要部分“ser.free”从来不被调用。德语Lazarus论坛的下面贴子里描述了这个问题http://www.lazarusforum.de/viewtopic.php?f=10&t=2082

为了让每个用户明白,而不是按CTRL-C,在主要应用周围有一些代码。也许有人担心,为什么/dev/ttyUSB0被用作com端口:这是由于在测试系统上使用的USB串行适配器。如果有内置串行端口,请使用“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.

另外,外部链接节有UNIX和Windows串行端口教程。


值得一提的是linux下TBlockSerial.LinuxLock参数的功能。当设置默认为True时,连接将试图在/var/lock下创建一个锁文件(例如,“LCK..ttyUSB0”),如果所请求的端口存在一个锁就会失败。如果没有调用释放,锁文件将一直存在。设置LinuxLock为False将使Synaser忽略端口锁定。

替代Synaser:

基于Synaser的可视组件5dpo

另外一个非常简单的fpc单元现在是freepascal(至少版本2.2.2)的部分了,只要将Serial放到你的Uses清单里,然而除了Serial.pp源文件外还没有其它文档。

USB

libusb

一个跨Linux,BSDs和Mac OS X平台的可能性是libusb

标题在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

如果你使用FTDI的芯片,你可以使用他们芯片dll接口的pascal头。

外部链接

通信协议速度比较:

  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

串行通信链接:

  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数字示波器——一个硬件访问例子的全部源代码包含在:

[2]


Networking