Difference between revisions of "Hardware Access"

From Free Pascal wiki
Jump to navigationJump to search
m
Line 1: Line 1:
{{Hardware Access}}
+
{{硬件访问}}
 
__TOC__
 
__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.
+
本页是关于在Lazarus上访问硬件设备的教程的开始。这些设备包括,但不限于:ISA,PCI,USB,并行端口,串行端口。
  
Uniform multi-platform access to hardware devices 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:
+
RTL或LCL没有实现统一的多平台硬件设备的访问。因此本教程将基本覆盖不同平台上的硬件访问方法。在不同环境下可以使用条件编译来编译代码,像这样:
  
 
<delphi>
 
<delphi>
Line 17: Line 17:
 
</delphi>
 
</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.
+
此时还不知道Mac OS X/x86是否会允许HW访问。它可能不允许,虽然那种情形下我假设像io.dll的驱动器将会及时出现。
  
==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.
+
ISA卡,PCI卡和并行端口使用“并行”协议与计算机进行通信。串行端口和USB设备使用“串行”协议。因为处理器和编程语言都通过并行方式处理数据,在软件端比较容易实现对这类协议的访问。例如,访问一个整型变量时,可以仅用一个命令就能访问它的值。然而,使用串行协议,一次仅能知道一位(bit),需要将所有片断(piece)放在一起才能理解数据。
  
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.
+
串行通信比较难于直接实现,如果使用预制(pre-made)组件,可能会稍微容易些。在硬件端也比较困难,因此许多设备使用专门的集成电路或甚至微控制器来实现。
  
Now a brief comparison of hardware access protocols will be given:
+
现在来做一个硬件访问协议的简要比较:
  
 
{| border=2 width="100%"
 
{| border=2 width="100%"
Line 30: Line 30:
 
|-
 
|-
 
!
 
!
! Speed
+
! 速度
! Hardware implementation difficulty
+
! 硬件实现难度
  
 
|-
 
|-
! Serial Port
+
! 串行端口
| align="center" | Very slow (< E5 bit/s)
+
| align="center" | 非常慢 (< E5 bit/s)
| align="center" | Medium
+
| align="center" | 中等
  
 
|-
 
|-
! Parallel Port
+
! 并行端口
| align="center" | Slow (~ E6 bit/s)
+
| align="center" | (~ E6 bit/s)
| align="center" | Easy
+
| align="center" | 容易
  
 
|-
 
|-
  
! ISA Card
+
! ISA卡
| align="center" | Medium (~ E7 bit/s)
+
| align="center" | 中等 (~ E7 bit/s)
| align="center" | Medium
+
| align="center" | 中等
  
 
|-
 
|-
 
! USB
 
! USB
| align="center" | Medium (~ E7 bit/s)
+
| align="center" | 中等 (~ E7 bit/s)
| align="center" | Hard
+
| align="center" | 困难
  
 
|-
 
|-
! PCI Card
+
! PCI卡
| align="center" | Very Fast (> E9 bit/s)
+
| align="center" | 非常快 (> E9 bit/s)
| align="center" | Very Hard
+
| align="center" | 非常难
  
 
|}
 
|}
  
==Parallel Communication==
+
==并行通信==
  
===Using inpout32.dll for Windows===
+
===为Windows使用inpout32.dll===
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.
+
在9x系列和NT系列,Windows有不同的方法来访问硬件设备。在9x系列(95,98,Me),程序可以直接访问硬件,正如在DOS上一样。然而,NT系列(Windows NT和XP)不允许这种方式。在该架构上,所有与硬件端口的通信必须通过一个设备驱动器。这是一种安全机制,但是为一个小项目开发一个驱动器可能会花费太多的时间和金钱。
  
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.
+
幸运地是有一个库解决了这个问题。如果检测到Windows NT,它解压HWInterface.sys内核设备驱动器并安装。如果检测到Windows 9x,它简单地使用汇编操作码访问硬件。
  
But how do I use the library? Simple! It has only two functions, Inp32 and Out32, and their use is quite intuitive.
+
但是如何使用这个库呢?非常简单!它仅有2个函数:Inp32和Out32,它们的使用是比较直观的。
  
We will load the library dynamically, so let's define both functions first:
+
我们将动态加载该库,因此让我们首先定义这2个函数:
  
 
<delphi>
 
<delphi>
Line 78: Line 78:
 
</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发送数据到指定地址的端口
* Inp32 returns a byte from the port you specify by Address
+
* Inp32从指定地址的端口返回一字节
  
Now we can load the library. This can be implemented in a place like the OnCreate method of your program's main form:
+
现在可以加载该库了。这可能在一个类似程序主form的OnCreate方法的地方来实现:
  
 
<delphi>
 
<delphi>
Line 113: Line 113:
 
</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 124:
 
</delphi>
 
</delphi>
  
Here is a simple example of how to use Inp32 function:
+
这里是一个如何使用Inp32函数的简单例子:
  
 
<delphi>
 
<delphi>
Line 132: Line 132:
 
</delphi>
 
</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.
+
该代码在Windows XP上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。
  
This is the homepage for the library: [http://www.logix4u.net/inpout32.htm www.logix4u.net/inpout32.htm]  *see discussion*
+
这是该库的主页:[http://www.logix4u.net/inpout32.htm www.logix4u.net/inpout32.htm]  *查看讨论*
  
===Using assembler on Windows 9x===
+
===在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:
+
在Windows 9x上也可以使用汇编代码。假设希望把$CC发送到$320端口。下面代码可以实现:
  
 
<delphi>
 
<delphi>
Line 150: Line 150:
 
</delphi>
 
</delphi>
  
===Troubleshooting on Windows===
+
===在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:
+
在Windows上使用不支持即插即用并行硬件的一个可能问题来源是,Windows可能将该硬件使用的端口分配给了另外的设备。你可以在下面的URL里找到关于如何告诉Windows不要将你设备的地址分配给即插即用设备的指示:
  
 
http://support.microsoft.com/kb/135168
 
http://support.microsoft.com/kb/135168
  
===Using ioperm to access ports on Linux===
+
===在Linux上使用ioperm访问端口===
  
The best way to access the hardware on Linux is through device drivers, but, due to the complexity of the task of creating a driver, sometimes a quick method is very useful.
+
在Linux上访问硬件的最好方法通过设备驱动器,但是考虑到创建一个驱动器任务的复杂性,有时候一个快速的方法是非常有用的。
  
In order to use the "[[doc:rtl/ports|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 "[[doc:rtl/ports|ports]]" unit [http://www.freepascal.org/docs-html/rtl/ports/index.html here].
+
为了使用Linux下的“[[doc:rtl/ports|ports]]”单元,程序必须以root身份运行,并且必须调用IOPerm设置端口访问的合适权限。你可以在[http://www.freepascal.org/docs-html/rtl/ports/index.html 这里]找到关于“[[doc:rtl/ports|ports]]”单元的文档。
  
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.
+
要做的第一件事是链接(g)libc和调用IOPerm。一个链接整个(g)libc的单元存在于free pascal,但是当应用直接使用时该单元出现了问题,并且静态链接整个(g)libc库不是一个非常好的主意,因为在不同版本间它以不兼容的方式改变。然而,像ioperm类的函数不大可能改变。
  
 
<delphi>
 
<delphi>

Revision as of 07:15, 2 January 2010

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