Hardware Access/zh CN
概览
本页是关于在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 代表访问的第一个端口.
- num 是访问的端口数,因此ioperm($220, 8, 1)将为程序访问$220——$227之间(含)的所有端口。
在链接IOPerm后,可以使用port[<Address>]访问端口。
<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>
该代码在Mandriva Linux 2005和Damn Small Linux 1.5上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。
通用UNIX硬件访问
<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>
注意 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的串行鼠标上测试过。
<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>
下面的代码例子是上面例子的一个替代版本。上面的例子看起来在主要概念上有严重错误,准确地是“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”——像上面例子代码中声明。
<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>
另外,外部链接节有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头。
外部链接
通信协议速度比较:
- 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
串行通信链接:
- On UNIX: [1]
- 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数字示波器——一个硬件访问例子的全部源代码包含在: