Hardware Access/ja

From Lazarus wiki
Jump to navigationJump to search

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 Documentation日本語版 - 翻訳ノート - 日本語障害情報

概要

このページはLazarusでハードウェアデバイスにアクセスを行うためのチュートリアルです。ここにはISA、PCI、USB、パラレルポート、シリアルポートの各デバイスについて書かれていますが、完全ではありません。

ハードウェアデバイスへのアクセスはFree PascalランタイムライブラリやLCLをもってしても完全な移植性をもつことはできません。このチュートリアルは多くのプラットホームに適用できるハードウェアへアクセスの手法の基本的な部分をカバーします。コードを違った環境でコンパイルするためには以下のような条件付きコンパイルが必要となるでしょう。

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

x86プロセッサ上で動作するMac OS Xでのハードウェアアクセスが可能かどうかは現時点では不明です。

パラレルおよびシリアル接続

ISAカード、PCIカードおよびパラレルポートでの通信ではコンピュータはパラレルプロトコルを使用します。シリアルポートやUSBデバイスではシリアルプロトコルを使用します。このようにプロトコルを使い分けるのは、プロセッサやプログラミング言語はすべてデータにパラレルでアプローチするため、より簡単にするためにプロトコルはソフトウェア側で実装してやる必要があるのです。 例えばあなたがInteger変数にアクセスする時、それは一つのコマンドで行う事ができます。しかしそれは連続した手順によって一度に一ビットずつデータを取得しているだけなのです。ですから、その断片化したデータをくっつけてやる必要があります。

シリアル通信を直接行う事は難しいですが、仮実装されたコンポーネントを使用する事により簡単に行う事もできます。また、ハードウェア側ではそれすらできないので、多くの場合それを実行するのに特化した装置が必要であり、ICやマイクロコントローラを使用する必要があります。

ハードウェアアクセスプロトコルの簡潔な比較を以下に提示します。

速度 実装の難易度
シリアルポート 非常に遅い (< E5 bit/s) 普通
パラレルポート 遅い (~ E6 bit/s) 簡単
ISAカード 普通 (~ E7 bit/s) 普通
USB 普通 (~ E7 bit/s) 難しい
PCIカード 非常に速い (> E9 bit/s) 非常に難しい

パラレル通信

Windows上でinpout32.dllを使用する

Windowsではハードウェアデバイスへのアクセスを行う手順に9x系とNT系では違いがあります。9x系(95,98,Me)ではプログラムはDOSのように直接ハードウェアに対してアクセスすることができます。NT系(Windows NT,2000,XP)ではそのような方法は使えません。すべてのハードウェアポートはデバイスドライバによって通信する事ができます。これはセキュリティ上の問題からそうなっていますが、ドライバを作るためには時間とお金が必要で小さいプロジェクトは難しいでしょう。

しかし、幸せな事にこの問題を解決するライブラリがあります。WIndowsNTが検出されるとそれはHWInterface.sysカーネルデバイスドライバを解凍して、インストールします。Window9xが検出されると、それはハードウェアデバイスにアクセスするのに単にアセンブラオペコードを使用します。

でもそのライブラリをどうやって使ったらいいのでしょうか?簡単です!このライブラリはInp32とOut32という2つの関数しか持っていませんし、その使用は非常に直感的です。

ライブラリを動的にロードすると、関数は次のように定義されます

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 you specify by Address
  • Inp32 returns a byte from the port you specify by Address

わたしたちはこれでライブラリをロードする事ができます。あなたのプログラムのメインフォームの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}

このコードはカスタムISAカードのポート$0200と、WindowsXP上のLazarus 0.9.10でテストされました。もちろんこのコードを実行するためにはuses節にWindowsが必要です。また、自分のアプリケーションと同じディレクトリに"input32.dll"も必要です。

このホームページのライブラリはこちら: www.logix4u.net/inpout32.htm


Using assembler on Windows 9x

On Windows 9x assembler code can be used. Suppose one wants to write $CC to the $320 port. The code to do that is:

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

Troubleshooting on 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 the hardware to another device.


Linuxでiopermを使用する

Linuxでハードウェアにアクセスする最適な方法はThroughtデバイスドライバを使用することですが、ドライバを作成するタスクは複雑なので整理されたメソッドを使えば楽にそれを行うことができます。

あなたの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を使って、自作のISAカードで$0220にアクセスしてテストしました。

一般的な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はin unit x86で"fpioperm"よ呼ばれる抽象的な機構を提供しています。そして、入出力機能を定義しています。 これらの関数はx86で動作するLinuxとFreeBSDで実装されています。

配布と関数のポータビリティのため、必要な抽象化なしにlibcをリンクすることは推奨されていません。libcの手動リンク(つまり、どこからでも使えるように、libcをad hoc宣言をする方法)をおこなうことは推奨されません。

(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). 訳注:うまいこと訳せないので、和訳せず、そのままのこしておきます。)


注意2 _unit_ libcを使うことはどんな状況下でもKylixの互換性の点では推奨されません。このユニットは相対的にポータブルではないからです。(due to excessive exposure of structures and other private symbols)

そして、Kylixの互換性の問題からは、なるべく少なく変更すべきです。

Status and control

Besides data lines, the parallel port also has status and control lines which are accessed using the status and control registers. While the base address accesses the data lines and reads or writes data bytes from/to them, the status register is accessed on the address offset by +1 and the control register is accessed on the offset +2. For example, LPT1 (first parallel port on a PC) has the base address $378, so its status register is at $379 and control register at $380. To get individual status line states, read a byte from its address and its bits represent those lines. Setting control lines is similarly done by writing a byte with accordingly set bits to the control register.

Newer bidirectional parallel port versions have more registers on higher offsets. More details about them, together with information which bits map to which lines can be found here.

Most directly accessed hardware devices other than PC parallel ports are controlled in a similar way. Depending on the device in question, it is necessary to find out what registers are available (above mentioned control and status, but also address and other registers) and which bits represent which hardware functions.

シリアル通信

デバイス設定

開始する前に、Linux 上でデバイスを認証してください。各デバイスに対して永続的な UDEV ルールを作成する方法があります。たとえば、次の方法では、「serial」、「idVendor」、「idProduct」属性で指定されたデバイスにモード「0664」を適用します。

create a new dev rule based on some constants
echo 'SUBSYSTEM=="tty", ATTRS{serial}=="A1004chl", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0664"' | sudo tee /etc/udev/rules.d/50-usb-serial.rules > /dev/null 

# reload rules
sudo udevadm control --reload-rules

# re-add all devices
sudo udevadm trigger

# test
ls -al /dev/ttyUSB0

# you may want to reboot your system
sudo reboot

Synaser

Synaser ライブラリを使用すれば、シリアル通信ソフトウェアを非常に簡単に作成できます。Synaser のドキュメントと併せて参照すれば、サンプルコードの内容は容易に理解できるはずです。最も重要なのは、通信速度(ビット/秒)、データビット数、パリティビット数、ストップビット数、およびハンドシェイクプロトコル(ある場合)を設定する`TBlockSerial.Config`です。以下のコードは、COM1に接続されたシリアルマウスを使用してテストされています。

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ノートPC、Digitus製USB-RS232アダプタ、Ubuntu 8.04.1)において、この部分が次のようなエラーを引き起こしました:
 ・アプリケーションは1セッションにつき1回だけ正常に実行され、再起動するとシリアルポートに接続できなくなりました。
 ・そのため、ユーザーがアプリケーションを再起動しようとするたびに、システムの再起動が必要となっていました。

その理由は容易に理解できます。アプリケーションは while true do ループ、より正確には無限ループの中にあります。終了条件がないため、アプリケーションを終了させる唯一の方法は、ターミナルを閉じるか、CTRL-Cを押すことだけです。しかし、この方法でアプリケーションが中断されると、シリアルポートを解放する重要な部分である「ser.free」が決して呼び出されなくなります。この問題については、ドイツのLazarusフォーラムの以下のスレッドで説明されています:http://www.lazarusforum.de/viewtopic.php?f=10&t=2082

メインアプリケーションの周囲に、ユーザーにCTRL-Cを押さないよう注意を促すためのコードが少しあります。テストシステムでは、Digitus製のUSB-シリアルアダプタを使用しているため、COMポートとして/dev/ttyUSB0が使用されています。内蔵シリアルポートを使用する場合は、Windowsの場合は上記のコード例のように「COM1」、Linuxの場合は「/dev/ttyS0 や /dev/ttyS1」という宣言を使用してください。

program serialtest;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,SysUtils,Synaser,Crt
  { add other units after this };

  var l:boolean;

  function check_affirmation():boolean;
  var k:string;
  begin
    Writeln('アプリケーションを終了するには、Ctrl+C を使用しないでください。代わりに、いずれかのキーを押してアプリケーションを終了してください。'+
    'この案内を承知の上で、アプリケーションを続行してください!'+
    '[0]=終了、[1]=確認、続行!');
    Writeln('Your choice: ');
    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('シリアルポートを開放します...');
      ser.free;
      Writeln('シリアルポートが正常に解放されました!');
    end;
  end;

  begin
    l:=check_affirmation();
    if l=true then
    RS232_connect()
    else
    Writeln('プログラムを終了します! ');
  end.

また、 外部リンクセクションには、UNIXおよびWindowsのシリアルポートに関するチュートリアルが掲載されています。

Linux環境におけるTBlockSerial.LinuxLockパラメータの機能に注意してください。デフォルトのTrueに設定されている場合、接続時に/var/lockディレクトリ下にロックファイル(例:「LCK..ttyUSB0」)を作成しようとします。要求されたポートに対して既にロックファイルが存在する場合、接続は失敗します。Freeが呼び出されなかった場合、ロックファイルは残ります。LinuxLockをFalseに設定すると、Synaserはポートのロックを無視します。

Synaserの代替ツールもあります。詳細は以下をご覧ください。

Visual Synapse

Visual Synapseには、Synapseのシリアルおよびネットワークライブラリの多くの部分に対応したコンポーネントラッパーが多数用意されています。TvsComPortは、Synaserをラップしたビジュアルコンポーネントです。

5dpo

また、Synaserに基づく5dpoのビジュアルコンポーネントもあります。

LazSerial (LazSerialPort)

5dpo(およびSynapse)をベースにしています:http://forum.lazarus.freepascal.org/index.php/topic,20481.0.html

Windows、Linux、macOSで動作することが期待されます。

バージョン0.7以降、このパッケージにはTLazSerial、TSerialWatcher、TSerialSelectorの3つのコンポーネントが含まれています。

Serial components.png

バージョン 0.6 以下のものは https://github.com/JurassicPork/TLazSerial で入手可能です。

バージョン 0.7 以上のものは https://github.com/CM630/TLazSerial で入手可能です。

TLazSerial

通信を実行する基本コンポーネントです。 以下の専用プロパティがあります。

  • BaudRate(カスタムボーレートが0未満の場合に適用される)
  • CustomBaudrate(v.07以降)
  • DataBits
  • Device - シリアルデバイスの名前
  • FlowControl - 3つのオプションに限定されていますが、内部的にさらに多くの設定が可能です。
  • Parity
  • RcvLineCRLF
  • StopBits
  • SynSer - 微調整のための設定項目

以下の専用イベントがあります。

  • OnRemoved(v.07以降) - 現在アクティブなデバイスが取り外された、マウント解除されたなどの場合に通知します。
  • OnRxDate - 受信データの処理に使用するイベントです。aString := LazSerial1.ReadData; でデータを受信すると、バッファから削除されます。次の aString := LazSerial1.ReadData; で次のデータを取得します。
  • OnStatus
  • SynaSer.OnStatus

メソッド:

  • ShowSetupDialog は、シリアル接続を設定するためのダイアログを表示します。

Serial ShowSetupDialog.png


TSerialWatcher

シリアルポートの接続または切断(接続/取り外し)が行われた際にイベントを発生させます。

この機能には、「OnComConnected」と「OnComDisconnected」の2つのイベントがあります。

TSerialSelector

これは、コンピュータで使用可能なシリアルデバイスの一覧が表示されるコンボボックスです。

serial selector.png

FPC built in Serial unit

バージョン2.2.2以降、FreePascalには別のシリアルユニットが組み込まれています。Usesリストに「Serial」を追加するだけで利用できます。ただし、Serial.ppのソースファイルといくつかの議論以外には、ドキュメントは存在しないようです。

サンプルはこちらにあります。

TDataPort

DataPortは、データ交換のためのスレッドセーフな抽象ポートです。ネットワーク(TCP/UDP/HTTP)、シリアルポート(UART、COMポート、FTDI)、デバイスファイル(ioctl対応)、および従来のファイル名付きパイプを介した通信に使用されます。シリアル通信用には、TDataPortSerialおよびTDataPortFtdiという派生クラスが用意されています。簡単なサンプルは、デモおよびこちらでご覧いただけます。

PXL

PXL (Platform eXtended Library)は、シリアルポート、GPIO、I²C、SPI、PWM、V4L2、ディスプレイ、センサーへの低レベルのネイティブアクセスを実現します。

Windowsにおけるシリアルポート名

Windows 9x系OS(95、98、ME)では、COMポートは番号で命名されます(例:COM1、COM30)。

Windows NT系システム(NT、2000、XP、Vista、Windows 7、Windows 8)でもCOMポートは番号で命名されますが、これはDOS/Win9xとの互換性のためだけです。

実際の名前を取得するには、以下のコードを使用してください。

// ComNr is obviously the number of the COM port
if ComNr > 9 then
  Result := Format('\\\\.\\COM%d', [ComNr])
else
  Result := Format('COM%d', [ComNr]);

接続可能なデバイスの一覧は、レジストリの \HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM に保存されています。

なお、ごくまれに、シリアルデバイスがレジストリから削除されない場合があります。これは、Prolific 製デバイスの一部のドライバで確認されています。

Linuxにおけるシリアルポート名

Linux のシリアルデバイスは /dev ディレクトリに一覧表示されます。その名前は ttyS*、ttyUSB*、ttyAM*、または rfcomm* といった形式になっている場合があります。Linux では通常通り、シリアルデバイスの名前は大文字と小文字が区別されます。

ttyS* は組み込みデバイスであることに注意してください。デフォルトでは /dev ディレクトリに多数の一覧が表示されますが、それらが実際に存在していることを意味するわけではありません。

シリアルポートの一覧の取得

TLazSerial/lazsynaser.pas に含まれる GetSerialPortNames 関数は、利用可能なシリアルデバイスのリストを取得するマルチプラットフォームルーチンの良い例です。

https://github.com/JurassicPork/TLazSerial/blob/master/lazsynaser.pas

シリアルデバイスに関する追加情報の取得

Windowsの場合

シリアルデバイスに関する追加情報は、レジストリの HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\ に格納されています。

次の例は、デバイスのフレンドリーネームを取得するものです:

{$ifdef windows}
function GetComFriendlyName (PortName : string) : String;
var
  Registry : TRegistry;
  PortType: String = '';
  VID_PID: String = '';
  ID: string = '';
  Contents : array of String;
  KeyName: String = '';
begin
  Result := '';
  Registry := TRegistry.Create(KEY_READ);
  try
    Registry.RootKey := HKEY_LOCAL_MACHINE;
    if Registry.OpenKey('\SYSTEM\ControlSet001\Control\COM Name Arbiter\Devices',False) then
    begin
      Contents := Registry.ReadString(PortName).Split(['#']);
      if (Length(Contents) > 2) then
      begin
        PortType := Copy(Contents[0],5,MaxInt) ;
        VID_PID := Contents[1];
        ID := Contents[2];
        KeyName := '\SYSTEM\ControlSet001\Enum\' + PortType + '\' + VID_PID + '\' + ID;
        if Registry.OpenKey(KeyName,False) then
        begin
          Result := Registry.ReadString('FriendlyName');
          //Usually friendly names in Windows duplicate the name of the port at the end
          if Result.EndsWith(' (' + PortName + ')',False) then
            Result := LeftStr(Result, max(0,length(Result)-length(' (' + PortName + ')')));  //Remove the name of the port
        end;//if registry
      end; //Length
    end; //if Registry.OpenKey
  finally
    Registry.Free;
  end; //try
end;
{$endif}

GetComFriendlyName('COM5'); のように使用します。戻り値は String型 です。

Linuxの場合

Linux では、シリアルデバイスのプロパティを `udevadm info -q property /dev/ttyxxx`(「xxxx」はデバイスの名前、例えば ttyUSB0 に置き換えてください)で取得できます。次の例は、デバイスのフレンドリーネームを取得するものです:

{$IfDef Linux}
Function GetComFriendlyName (PortName: String) : String;
var
  RunOutput: string = '';
  StartPos: integer = 0;
  EndPos: integer = 0;
begin
  Result := '';
  if not RunCommand('udevadm info -q property ' + PortName,RunOutput) then exit;
  StartPos := pos('ID_MODEL_FROM_DATABASE=',RunOutput);
  if (StartPos = 0) then exit;
  StartPos := StartPos + Length('ID_MODEL_FROM_DATABASE=');
  EndPos:= pos(#10,RunOutput,StartPos+1);
  Result := copy(RunOutput,StartPos, EndPos - StartPos);
end;
{$EndIf}

GetComFriendlyName('/dev/ttyUSB0'); のように使用します。戻り値は String型 です。

USB

libusb

A cross platform possibility for Windows, Linux, BSDs and macOS is libusb.

name author version date link remarks
libusb.pp Uwe Zimmermann 0.1.12 2006-06-29
libusb.pas Johann Glaser 2012-09-23 https://github.com/hansiglaser/pas-libusb includes OOP wrapper, see branch "libusb-1.0" for libusb 1.0
fpcusb Joe Jared 0.11-14 2006-02-02
libusb.pp Marko Medic 1.0 2010-12-14 http://www.lazarus.freepascal.org/index.php/topic,11435.0.html

FTDI

If one of the chips from FTDI is used, their pascal headers for their dll interface to the chips can be used.

一般的なデバイス

On Windows, devices can be managed from code, see here: Windows Programming Tips#Enabling and disabling devices

関連項目

外部リンク

通信プロトコルスピードの比較:

  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/

ISA デジタルオシロスコープ - 全ソースが含まれるハードウエアのアクセス例:

[2]