Hardware Access

From Free Pascal wiki
Revision as of 02:59, 16 October 2005 by Sekelsenmat (talk | contribs)
Jump to navigationJump to search

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 in a multi-platform way is not implemented by Free Pascal Runtime Library or by the LCL, so this will basically cover hardware access methods on different platforms. The code can be compiled on different environments using conditional compiles, like this:

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

Using inpout32.dll for Windows

Windows has different ways to access hardware devices on the 9x series and on the NT series, and this can be really problematic. The 9x series (95, 98, Me) allow the programs to access ports directly, just like they did on DOS. Windows NT, however, completely prevents this, and only allows access to the ports throught device drivers. This is a secutiry mechanism, but can be anoying as a device driver may be too complex for a small project that need to access a parallel port.

Happily there is a library that carryes a device driver inside it. If Windows NT is detected, it decompresses the device and install the Hwinterface.sys kernel device driver. If Windows 9x is detected it simply uses assembler opcodes to access the hardware.

But how to use the library? Simple! It only has two functions, Inp32 and Out32, and their use is quite intuitive.

We will be loading the library dinamically, so let's define both functions first:

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

  • Address represents the address of the port you desire to access. Out32 sends Data to the port on Addess. Inp32 returns a byte from the port on Address.

Now we can load the library. This can be implemented on a place like the OnCreate method of your program's main form:

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
    @Inp32 := GetProcAddress(Inpout32, 'Inp32');
    if (@Inp32 = nil) then Caption := 'Error';
    @Out32 := GetProcAddress(Inpout32, 'Out32');
    if (@Out32 = nil) then Caption := 'Error';
  end
  else Caption := 'Error';
{$ENDIF}
end;

If you load the library on OnCreate just don't forget to unload it on OnDestroy:

procedure TMyForm.FormDestroy(Sender: TObject);
begin
{$IFDEF WIN32}
  FreeLibrary(Inpout32);
{$ENDIF}
end;

Here is a simple example of how to use Inp32 function:

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

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

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 glibc and call IOPerm. A unit that links to the entire glibc exists on free pascal, but this unit gives problems when used directly by application and linking statically to the entire glibc library is not a very good idea for a multi-platform application. The use of platform-specific functions should be kept to a minimum.

{$IFDEF Linux}
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
{$ENDIF}

  • "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 OIPerm you can port[<Address>] to access the ports.

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

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