
Linux I/O ポートプログラミング mini-HOWTO

Riku Saikkonen

Riku.Saikkonen@hut.fi

JF Project - 日本語訳

JF@linux.or.jp

v3.0, 2000-12-13

このハウツーでは、インテルの x86 プロセッサ上で走るプログラム上の、ハー
ドウェア I/O ポートのプログラミングと、Linux のユーザモードで短い時間待
ちをおこなうプログラム、などについて述べます。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Table of Contents
1. Introduction
2. C プログラムから I/O ポートを使う
    2.1. 普通の方法
        2.1.1. パーミッション
        2.1.2. ポートのアクセス
       
       
    2.2. I/O ポートアクセスをする別の方法：/dev/port
   
   
3. 割り込み (IRQ) と DMA アクセス
4. 高い精度のタイミング制御
    4.1. ディレイ
        4.1.1. スリープ：sleep() と usleep()
        4.1.2. nanosleep()
        4.1.3. ポートI/Oを使ったディレイ
        4.1.4. アセンブラ命令によるディレイ
        4.1.5. Pentiumのrdtscについて
       
       
    4.2. 時間の測定
   
   
5. その他のプログラミング言語について
6. よく使われるポート
    6.1. パラレルポート
    6.2. ゲーム(ジョイスティック)ポート
    6.3. シリアルポート
   
   
7. Hints
8. トラブルシュート
9. プログラムの例
10. 謝辞
11. 日本語訳について

1. Introduction

このハウツーでは、インテルの x86 プロセッサ上で走るプログラム上の、ハー
ドウェア I/O ポートのプログラミングと、Linux のユーザモードで短い時間待
ちをおこなうプログラム、などについて述べます。私は前にも同じような(とて
も小さな) IO-Port-mini-HOWTO を書きましたが、この文書はその続編です。

このドキュメントの著作権は 1995-2000 Riku Saikkonsen に属します。詳細に
関しては通常の Linux の HOWTO COPYRIGHT をご覧下さい。

もし、なにか間違いがあったり、付け加えることなどありましたらぜひ私にメ
イルしてください。(Riku.Saikkonen@hut.fi)

日本語訳版についてのご指摘、ご意見などございましたら JF Projectまでお願
いします。<JF@linux.or.jp>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2. C プログラムから I/O ポートを使う

2.1. 普通の方法

I/O ポートをアクセスするためのルーチンは /usr/include/asm/io.h (または
カーネルのソースパッケージの中の linux/include/asm-i386/io.h) に定義さ
れています。必要なルーチンは、この中でインラインマクロとして定義されて
いますので、#include <asm/io.h> とするだけで十分でしょう。特別なライブ
ラリなどは不要です。

gcc (私の知る限り全てのバージョン、egcs も含む) からの制限のため、これ
らのルーチンを使うソースをコンパイルするにあたり、最適化をオンにする(
gcc -O1 またはそれ以上)、あるいは #include <asm/io.h> の前に #define
extern static という宣言をおこなっておく必要があります。 (あとで #undef
extern しておくのを忘れないように。)

デバッグのために、(少なくとも最近の gcc では) gcc -g -Oを使うことができ
ますが、最適化されたコードではデバッガが変な挙動を示すこともあります。
このような場合には、I/O ポートアクセスを含んだコードを独立したファイル
にしておいて、そのファイルのコンパイルの時にだけ最適化をオンにするとい
う方法を使うこともできます。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2.1.1. パーミッション

ポートをアクセスする前に、そのポートをアクセスする許可をプログラムに与
えなければなりません。これには、ioperm() 関数 (unistd.h で宣言され、カ
ーネルの中で定義されている)を呼び出します。この関数はプログラムの最初の
方で( I/O ポートアクセスをする前に)呼び出す必要があります。この関数の書
式は ioperm(from, num, turn_on)です。 fromはアクセスを許すポートの最初
のアドレス、 numはfromからいくつの連続アドレスのアクセスを許すか、を指
定します。例えば、ioperm(0x300, 5, 1) は、0x300 から 0x304 (合計で 5 つ
のポート)に対する許可を指定し、最後のパラメータでは、そのポートに対する
アクセス許可を与えるのか禁止するのかを論理値(true : 1 = アクセスを許可
する, false : 0 = アクセスを禁止する)で指定します。連続していないポート
の場合には、複数回ioperm() を呼び出して、適切な許可を指定します。文法の
詳細に関しては ioperm(2) のマニュアルページを参照してください。

ioperm() を呼び出すには、そのプログラムが root 特権で走っていることが必
要です。つまり、そのプログラムをルートユーザで実行するか、setuid root
(訳注: 実行ファイルのオーナーを root にしておき、chmod 4755 hogehoge と
いうやつです。)しておく必要があります。必要なポートに対する許可を
ioperm() で与えたあとに、root 特権を落すこともできます。プログラムの最
後で、明示的に ioperm(..., 0) を呼び出して許可を落す必要はありません。
これはプログラムの終了時に自動的に行なわれますから。

root 以外のユーザに setuid() することで ioperm() の与えたポートアクセス
許可がなくなることはありませんが、一方で fork() すると許可がなくなりま
す。 (子プロセスはアクセス許可を持っていませんが、親プロセスは持ってい
ます。)

ioperm() では、0x000 から 0x3ff までのポートに対するアクセス許可しか与
えることはできません。これよりも上のポートに対しては、iopl() を使う必要
があります。 (iopl() を使うと一度にすべてのポートに対するアクセス許可を
与えることになります。) すべてのポートに対してアクセス許可を与えるには
、レベルパラメータとして「3」を使います。つまり、iopl(3) という関数呼び
出しをします。 (間違ったポートにアクセスするととんでもないことが起きる
かも知れませんから、使うときには注意してください。) もちろん、iopl() を
呼び出す時には root 特権が必要です。詳細に関しては iopl(2) のマニュアル
ページを参照してください。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2.1.2. ポートのアクセス

ポートからバイトデータ(8bit)を入力する場合には inb(port) を呼び出します
。この関数はバイトデータを返します。ポートにバイトデータを出力するには
、outb(value, port) を呼び出します。 (パラメータの順番に注意して下さい
。) (訳注:アセンブラや、MS-DOS のコンパイラライブラリなどの場合とは逆で
す。) ポートアドレス x と x+1 からワードデータを入力する場合には inw(x)
を呼び出します。(アセンブラのinw とまったく同じようにそれぞれのポートか
ら 1 バイトずつ読んできて、ワードデータを構成するわけですが。) ふたつの
ポートにワード( 2 バイト)を出力するには outw(value, x) を使います。どち
らの命令(バイトデータかワードデータか)を使えばいいのか分からないときは
、多分、inb() と outb() を使えばいいでしょう。大抵のデバイスはバイト単
位のポートアクセスをするよう設計されています。ポートアクセスの命令はす
べて実行に少なくとも約 1 マイクロ秒かかるので注意してください。

inb_p(), outb_p(), inw_p(), outw_p() といったマクロは、上に述べたのと同
じ動作をしますが、ポートにアクセスした後に少しだけ(約 1 マイクロ秒) ウ
エイトします。 #include <asm/io.h> の前に #define REALLY_SLOW_IO を付け
加えるとこのウエイト時間を 4 マイクロ秒に変えることができます。これらの
マクロは通常、0x80 番地の I/O ポートに出力することでウエイトを実現して
います。 (#define SLOW_IO_BY_JUMPING としている場合には別の方法が使われ
ますが、これはあまり正確な遅延をおこなうことができないはずです。) この
ような理由から、0x80 番地のポートに対する ioperm() を先に実行しておく必
要があります。(0x80 番地のポートに対する出力はシステムに対してなんら影
響を与えることはないはずです。) さらにさまざまな遅延を行なう方法につい
ては、以下を読み進めて下さい。

そこそこ最近のリリースの Linux マニュアルページには、 ioperm(2) や iopl
(2)、上述のマクロに関する説明があります。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2.2. I/O ポートアクセスをする別の方法：/dev/port

I/O ポートにアクセスするもう一つの方法は、open() を使って /dev/port (キ
ャラクタデバイス、メジャー番号 1、マイナー番号 4)をオープンして、read/
write をおこなうという方法です。 (stdio の f*() 関数(訳注: fwrite() と
か fread() とか...)は内部でバッファリングをしているので、使えませんね
。) オープンした後に、入出力したいポートのアドレスまで lseek() します。
(file position 0 がポート 0x00 に、file position 1 がポート 0x01 ... と
いった具合に相当します。) その後に read(), write() を使ってバイト/ワー
ドを読み書きします。

この方法を使うには、もちろんそのプログラムが /dev/port に対する read/
write 許可を持っていなければなりません。この方法は上に述べた通常の方法
よりもおそらく遅いでしょうが、コンパイル時の最適化や ioperm() が不要で
あるという利点があります。 /dev/port に対してルート以外のユーザやグルー
プのアクセス許可を与えれば root 特権も必要ありません。でもこれはシステ
ムセキュリティという意味ではとても良くないことです。というのは、/dev/
port を使ってハードディスク、ネットワークカード、その他に直接アクセスす
ることで、システムに障害を与えたり、さらには root 特権も与えてしまう可
能性があるからです。

/dev/port からの読み込みにおいて select(2) や poll(2) を使うことはでき
ません。なぜなら入力ポートの値が変化したときに、ハードウェアにはそれを
CPUに伝える機能がないからです。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

3. 割り込み (IRQ) と DMA アクセス

ユーザモードのプログラムから直接に割り込みや DMA を使うことはできません
。これには、カーネルドライバを作成する必要があります。その詳細について
は「Linux カーネルハッカーズガイド」を、またその例としては、カーネルの
ソースコードを読んで下さい。

ユーザモードのプログラムから割り込みを禁止することは、危険を伴いますが
、可能です。(カーネルドライバですらそれを実行するときはできる限り短い時
間でするものです。) iopl(3) を呼び出したあと、asm("cli"); を呼び出すだ
けで割り込みを禁止することができます。再び割り込み可能にするには asm
("sti") を呼びます。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4. 高い精度のタイミング制御

4.1. ディレイ

まず最初に断っておきますが、ユーザプログラムにおいて正確なタイミング保
証をすることはできません。これは、Linux がマルチタスク・プリエンプティ
ブなシステムだからです。あなたのプロセスが、約 10 ミリ秒から数秒(非常に
負荷の高いシステムなどで)に渡ってなんらかの理由でスケジューリング対象か
ら外されることは、いつでもあり得ます。しかし、I/O ポートを使うほとんど
のアプリケーションでは、これは実際には問題にはならないでしょう。この時
間をできるだけ小さくするためには、nice (マニュアルページ nice(2)を参照
のこと) を使ってプロセスの優先順位を高くしたり、またはリアルタイムスケ
ジュールのシステム(下記を参照のこと)を使うということもできます。 (訳注
：nice を有効に使うことで、プロセスの持ち時間の再計算を頻繁に行い、それ
によりある程度、優先的にスケジュールされるという効果があります。)

通常のユーザモードプロセスで扱える範囲よりもっと精細なタイミング制御を
求めるのなら、ユーザモードで「リアルタイム」をサポートするための対策も
あります。 Linux 2.x カーネルはソフトリアルタイムをサポートしています。
詳細は sched_setscheduler(2) のマニュアルページを参照してください。ハー
ドリアルタイムをサポートする特別なカーネルもあります。これについてのも
っと詳しい情報は http://luz.cs.nmt.edu/ をご覧ください。 (訳注：上記以
外にもハードリアルタイムをサポートする実装が存在します。詳細はhttp://
www.linux.or.jp/link/kernel.html#RTを参照してください。)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.1.1. スリープ：sleep() と usleep()

さて、もっと簡単なタイミングについてお話しましょう。数秒のディレイが欲
しい場合には、おそらく sleep() がいいでしょう。数十ミリ秒以上のディレイ
(最小のディレイは約 10 ミリ秒のようです。)の場合には usleep() がお薦め
です。これらの関数を呼び出すと、CPU は他のプロセスに割り当てられます(つ
まりプロセスが「眠る」)ので、CPU タイムが無駄になることはありません。詳
細については sleep(3) と usleep(3) のマニュアルページをご覧ください。

約 50 ミリ秒よりも小さいディレイに関しては(おそらくプロセッサやマシンの
速度、システムの負荷にも依存しますが)、上に述べたような CPU を放してし
まうというアプローチではうまくいきません。通常、Linux のスケジューラが
あなたのプロセスに対して制御を戻す前に(x86 アーキテクチャでは)少なくと
も 10-30 ミリ秒はかかるからです。そのようなわけで、usleep(3) に小さいデ
ィレイを指定すると通常は指定した値をちょっとだけ越えるディレイが発生し
ます。また最小の値は約 10 ミリ秒ということになります。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.1.2. nanosleep()

2.0.x シリーズの Linux カーネルには nanosleep() という新たなシステムコ
ールが付け加わりました。(詳細は nanosleep(2) のマニュアルページを参照し
てください。) このシステムコールを使えば(数マイクロ秒といった)短い時間
のスリープまたはディレイが可能となります。

プロセスが (sched_setscheduler()を用いて)ソフトリアルタイムスケジューリ
ングされている場合、2 ミリ秒以下のディレイに対しては、nanosleep() はビ
ジーループを使います。それ以上のディレイに対しては usleep() と同様スリ
ープします。 (訳注：ソフトリアルタイムでなければ、usleep() と同程度の時
間分解能になるようです。)

このビジーループは udelay() (多くのカーネルドライバが使うカーネルの内部
ファンクション)を使っていて、ループの長さは BogoMips 値を使って計算され
ます。 (この手のビジーループの速さなどは BogoMips 値が正確に反映される
ものの例です。) どのように動作するかについて詳細は /usr/include/asm/
delay.h を参照してください。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.1.3. ポートI/Oを使ったディレイ

ポート I/O を使えば数マイクロ秒のディレイがまた違った方法で作れます。ポ
ート 0x80 に何かバイトデータを出力または入力すると、プロセッサの種類や
速度に関係なく、ほぼ正確に 1 マイクロ秒待つことができます。 (入出力の方
法については、前の方を読んで下さい。) これを必要な回数繰り返すことで数
マイクロ秒待つこともできます。標準的なマシンでは、このポート出力によっ
てなにか変な副作用があったりしないはずです。 (カーネルドライバでこれを
使っているものもありますから。) この方法は {in|out}[bw]_p() でもディレ
イするために通常に使われている方法です。 (asm/io.h を見て下さい。)

ポートアドレス 0-0x3ff の範囲にある大抵のポートへの I/O 命令は実際のと
ころほぼ正確に 1 マイクロ秒かかります。だから例えばパラレルポートを直接
扱っている場合なら、ディレイを作るためにはそのポートに対して inb() をい
くつか追加するだけでいいわけです。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.1.4. アセンブラ命令によるディレイ

そのプログラムが走るマシンのプロセッサの種類とクロック速度がわかってい
る場合には、特定のアセンブラ命令をハードコードすることで、もっと短いデ
ィレイを実現することもできます。 (しかし、プロセスがスケジューリングか
ら外され、ディレイが長くなってしまうことがあり得ることを忘れないで下さ
い。) 以下の表では、それぞれの命令で何クロック(内部クロック)消費される
かを示しています。これによってどれくらいの時間を消費するかを知ることが
できます。例えば 50MHz のプロセッサ (486DX-50 とか、486DX2-50) の場合に
は 1 クロックは 1/50000000 秒(＝ 200 ナノ秒)です。

 
Instruction   i386 clock cycles   i486 clock cycles                    
xchg %bx,%bx          3                   3                            
nop                   3                   1                            
or %ax,%ax            2                   1                            
mov %ax,%ax           2                   1                            
add %ax,0             2                   1                            
 

Pentium のクロック数は i486 と同じになるはずです。ただし Pentium Pro/II
では違います。また add %ax, 0 は半クロックしか消費しません。この命令は
時々他の命令とペアで実行されるからです。 (out-of-order 実行のため、相方
は命令実行ストリームのすぐお隣の命令である必要はないのです。)

上の表で、nop と xchg は何も副作用はないはずです。その他の命令はフラグ
レジスタを変更する可能性があります。でも、gcc はそれを検出してくれます
から、問題にはならないはずです。 xchg %bx, %bx がディレイ用の命令として
は無難な選択と言えるでしょう。

これらを使うには、プログラムの中で asm("命令") という書式を使って、上の
表の命令を埋め込みます。ひとつの asm() 文で複数の命令を埋め込むにはセミ
コロンで各命令をつなげます。例えば asm("nop ; nop ; nop ; nop") は nop
命令を4回実行して、i486 または Pentium プロセッサでは 4 クロックのディ
レイ(i386 では 12 クロック)になります。

asm() 文は、gcc によって、インラインアセンブラコードに変換されるので関
数呼び出しのオーバーヘッドはありません。

Intel x86 アーキテクチャでは 1 クロックより短いディレイを作ることは不可
能です。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.1.5. Pentiumのrdtscについて

Pentium プロセッサでは、リブートから現在までの経過クロックサイクル数を
知ることができます。以下のコードです(このコードは RDTSC という CPU 命令
を実行しています。):

 
   extern __inline__ unsigned long long int rdtsc()                    
   {                                                                   
     unsigned long long int x;                                         
     __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));                 
     return x;                                                         
   }                                                                   
 

この値がディレイとして必要な数だけ変化するのをビジーループ中で監視すれ
ばよいでしょう。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.2. 時間の測定

1 秒くらいの精度の時間ならば、time() を使うのが一番簡単でしょう。もっと
正確な時間が必要な場合には、gettimeofday() を使うと大体、マイクロ秒の精
度があります。 (でも、前の方で述べた、スケジューリングのことは忘れない
でください。) Pentium の場合には、上のコードを使うと 1 クロックサイクル
の精度が出ます。

プロセスが、ある時間経過した後にシグナルを受け取るようにしたい場合には
、 setitimer() か alarm() を使います。この関数の詳細についてはマニュア
ルページをご覧ください。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

5. その他のプログラミング言語について

以上の説明では C 言語に特化して述べましたが、C++ や Objective C でも同
じようにできるはずです。アセンブラの場合、C の場合と同様に ioperm() や
iopl() を呼び出さなければなりませんが、その後には直接 I/O ポートに対す
るリード/ライト命令(訳注: in, out 命令) を使うことができます。

その他の言語で、もしインラインアセンブラや C のコードを埋め込むことがで
きない、または前に述べたシステムコールが使えない場合にもっとも簡単な方
法は、必要な I/O ポートアクセスのためのソースコードを C で記述してコン
パイルし、プログラムの他の部分とリンクしてしまうという方法でしょう。あ
るいは前で触れた /dev/port を使うとよいでしょう。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

6. よく使われるポート

以下は、よく使われる TTL (または CMOS)の汎用論理 I/O ポートについてのプ
ログラミング情報です。

もし以下に述べるポートやその他の標準的なポートで本来の使い方(例えば普通
のプリンタやモデムを制御するといったような)をしたいのなら、この HOWTO
で述べるようにポートを直接操作するよりも、既存のドライバ(大抵はカーネル
にくっついてきます。)を使った方がいいでしょう。このセクションは PC の標
準ポートにLCDディスプレイ(訳注：と言っても近頃はやりの 14 インチとかの
ディスプレイのことじゃないですよ。)とかステッピングモーターとか特製の電
子機器をつなぎたいという人を意図しています。

もしスキャナとかいったお店に並べて売ってあるような機器(しかもお店に出回
り始めてからをちょっと経ったようなもの)を制御したいのなら、既にある
Linux ドライバを探してみましょう。 Hardware-HOWTO (訳注：日本語訳は
Hardware-HOWTO(日本語訳)です。) などが最初の手がかりとしてはいいと思い
ます。

コンピュータに接続する機器(とか、他にも一般の電子機器)についてのより詳
しい情報はhttp://www.hut.fi/Misc/Electronics/がお薦めです。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

6.1. パラレルポート

パラレルポートのベースアドレス(以下では ``BASE'' と呼ぶことにします。)
は /dev/lp0 では 0x3bc、 /dev/lp1 では 0x378、/dev/lp2 では 0x278 にな
ります。 (訳注：Linux カーネル2.2.x以降の parport ドライバをサポートし
たシステムでは lp デバイスファイルとベースアドレスが必ずしも対応してい
るわけではありません。詳しくはカーネル付属文書 parport.txt を参照してく
ださい。) 通常のプリンタポートとして動作させたいだけでしたら、
Printing-HOWTO(訳注：日本語訳は Printing-HOWTO(日本語訳)です。)を読むの
がいいでしょう。

ほとんどのパラレルポートには以下に述べる標準的な出力専用モードに加えて
、「拡張」双方向モードがあります。これに関すること、また最近の ECP/EPP
モード(さらには汎用の IEEE1284 規格)については、 http://www.fapo.com/や
http://www.senet.com.au/~cpeacock/parallel.htmlを参照してください。ユー
ザモードのプログラムでは IRQ や DMA が使えませんので、ECP/EPP を使うた
めにはおそらくカーネルドライバを書かなければならないでしょう。誰かがそ
ういったドライバを書いているだろうとは思いますが、私は詳しいことは知り
ません。

BASE+0のポート(データポート) は、ポートのデータ信号を制御します。 (D0
〜 D7 はビット 0 〜 7 に対応します。状態 : 0 = low (0V)、1 = high (5V))
。このポートに対するライトはデータをラッチして外部のピンに出力します。
標準または拡張ライトモードの場合には、リードすると、最後にライトされた
データが読み出されます。拡張リードモードの場合には、外部デバイスからの
データが読み出されます。

BASE+1のポート(ステータスポート) はリードオンリーで、以下のような入力信
号のステータスを返します:

 ・ Bit 0 と 1 は予約済
   
 ・ Bit 2 IRQ ステータス (外部ピンの値ではない。どういうふうに動作す
    るか知らない)
   
 ・ Bit 3 ERROR (1=high)
   
 ・ Bit 4 SLCT (1=high)
   
 ・ Bit 5 PE (1=high)
   
 ・ Bit 6 ACK (1=high)
   
 ・ Bit 7 -BUSY (0=high)
   

 

BASE+2のポート(制御ポート)はライトオンリー (リードした場合、最後にライ
トしたデータを返す。)で、以下のステータス信号を制御します:

 ・ Bit 0 -STROBE (0=high)
   
 ・ Bit 1 -AUTO_FD_XT (0=high)
   
 ・ Bit 2 INIT (1=high)
   
 ・ Bit 3 -SLCT_IN (0=high)
   
 ・ Bit 4 '1' にすると、パラレルポートの割り込みを許可する(ACK の low
    -> high の遷移で割り込み発生。)
   
 ・ Bit 5 拡張モードでの方向(0 = 出力, 1 = 入力) を制御する。このビッ
    トは完全にライトオンリーで、リード時には不定の値が返る。
   
 ・ Bit 6 と 7 は予約済
   

 

ピン接続 (25 ピンの D-sub メスコネクタ) (i=input, o=output):
1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6,   
9io D7, 10i ACK, 11i -BUSY, 12i PE, 13i SLCT, 14o -AUTO_FD_XT,         
15i ERROR, 16o INIT, 17o -SLCT_IN, 18-25 Ground                        
 

IBM の規格によれば、1, 14, 16, 17 番ピン(制御出力)は、4.7kオームで 5V
にプルアップされたオープンコレクタドライバ (シンク(吸い込み) 20mA, ソー
ス (吐き出し) 0.55mA, ただし、ハイレベルの出力はプルアップの 5V) その他
のピンについては、シンク(吸い込み) 24mA, ソース(吐き出し) 15mA, ハイレ
ベル出力は最小 2.4V, ローレベルの出力は最大 0.5V。 IBM 以外のパラレルポ
ートではおそらくこの標準とは少々違う場合もあるでしょう。これに関するも
っと詳しい情報は http://www.hut.fi/Misc/Electronics/circuits/
lptpower.html をご覧ください。

最後に注意: グランド(接地)には気をつけましょう。私はコンピュータが稼働
中にコネクタを接続したせいで、パラレルポートを何個も壊してしまった経験
があります。これを考えると、マザーボードに乗っているのではない(拡張カー
ド上の)パラレルポートを使うのがいいかもしれませんね。 (標準的な安い「マ
ルチI/O」カードを使えばあなたのコンピュータに二つめのパラレルポートが作
れます。必要のない(パラレルポート以外の：訳注)ポートは disable してしま
えばいいです。拡張カードのパラレルポートのI/Oアドレスを空いているところ
に設定しましょう。 IRQ は使う予定がなければ気にする必要はありません。)
(訳注: 各機器のフレームグランドは、ちゃんととりましょうね。)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

6.2. ゲーム(ジョイスティック)ポート

ゲームポートはポートアドレス 0x200-0x207 です。普通のジョイスティックを
制御するなら、多分、Linux カーネルにくっついてくるドライバを使った方が
いいですよ。

ピン接続 (15 ピンの D-sub メスコネクタ):

 ・ 1,8,9,15: +5 V (power)
   
 ・ 4,5,12: Ground
   
 ・ 2,7,10,14: デジタル入力 (順番に) BA1, BA2, BB1, BB2
   
 ・ 3,6,11,13: アナログ入力 (順番に) AX, AY, BX, BY
   

 

+5 V ピンはマザーボードの電源に直接接続されているようですので、 (マザー
ボードの作りや、電源、ゲームポートにもよりますが)非常に大きな電力を供給
することができるはずです。

このポートには二つのジョイスティックを接続できますが、デジタル入力は、
これらのボタンからの入力です。 (ジョイスティック A とジョイスティック B
それぞれに二つずつボタンがあります。) これらは通常の TTL レベルの入力で
、以下で述べるステータスポート(以下に記述)から現在の状態を読み出すこと
ができます。実際のジョイスティックではボタンが押されていると low (0V)
を、そうでなければ high (1K オームの抵抗を通じて 5V 電源にプルアップ)
が読み出されます。

いわゆるアナログ入力というのは、実際には抵抗値を計っているのです。以下
でその仕組みを述べます。ゲームポートには4本の入力それぞれにワンショット
マルチバイブレータが接続されています。(全体(4本)で 558 チップ一つ) それ
ぞれの入力について、入力ピンとマルチバイブレータの出力の間に 2.2K オー
ムの抵抗が、マルチバイブレータの出力とグランドの間に 0.01uF のタイミン
グ用コンデンサが接続されています。実際のジョイスティックには、X と Yの
両方の軸にそれぞれポテンショメータがあり、+5V とそれぞれの入力ピンに接
続されています。 (AX, AY がジョイスティック A に、BX, BY がジョイスティ
ック B に対応します。) (訳注: 回路図はこんな感じかな:
+5V                                                                    
 |       ^                                                             
 |      /          2.2Kオーム                                          
 +---/\/\/\--IN----/\/\/\------+                                       
       /                       |                                       
 ポテンショメータ              |                                       
                  +---------+  |                                       
                  | Multi   |  |                                       
                  | vib.   Q|--+                                       
                  |         |  |                                       
                  |         |  |                                       
                  |         |  |                                       
                  |         | === 0.01uF                               
                  |         |  |                                       
                  +---------+  |                                       
                               |                                       
                              GND                                      
)

マルチバイブレータが作動すると、その出力を high (5V) にドライブし、それ
ぞれに接続されているタイミングコンデンサの電位が 3.3V になるまで待ち、
その後に出力を low にします。この結果、マルチバイブレータの出力が high
の時間はジョイスティック中のポテンショメータの抵抗値に比例します。 (つ
まり、それぞれの軸のジョイスティックの位置を表します。) 抵抗値は以下の
式のとおりです:

R = (t - 24.2) / 0.011,                                                

ここで、R はポテンショメータの抵抗値(オーム)、t は high の時間(マイクロ
秒)です。

とまあ、こういうわけですので、アナログ入力を読み出すためには、以下で述
べるようにまずポートにライトして、マルチバイブレータを作動させる必要が
あります。その後に4つの軸の状態をポーリング(ポートを繰り返し読み出す)し
、それぞれのポートが high から low になるまでの時間を測定します。このポ
ーリングには非常に CPU タイムを消費しますし、(通常のユーザモードの)
Linux のような非リアルタイム・マルチタスクシステムではその結果はあまり
正確ではありません。ポートを絶え間なく読み出すことはできないからです。
(カーネルレベルのドライバを使うか、ポーリングの間、割り込みを禁止すれば
いいのですが、これは更に多くの CPU タイムを食います。) 信号が low にな
るまで長い時間(数十ミリ秒)かかるのが分かっているなら、ポーリング前に
CPU タイムを他のプロセスに明け渡すよう usleep() を呼ぶこともできます。

さて、アクセスする必要のあるポートはただ一つ、0x201 (他のポートアドレス
はこのポートと同じようにふるまうか、なにもしないか、のどちらか)です。こ
のポートに対するライト(書き込むデータはなんでも良いです。)によってマル
チバイブレータが作動します。リードすると入力信号の状態が返ります:

 ・ Bit 0: AX (マルチバイブレータ出力の状態 (1=high))
   
 ・ Bit 1: AY (マルチバイブレータ出力の状態 (1=high))
   
 ・ Bit 2: BX (マルチバイブレータ出力の状態 (1=high))
   
 ・ Bit 3: BY (マルチバイブレータ出力の状態 (1=high))
   
 ・ Bit 4: BA1 (デジタル入力, 1=high)
   
 ・ Bit 5: BA2 (デジタル入力, 1=high)
   
 ・ Bit 6: BB1 (デジタル入力, 1=high)
   
 ・ Bit 7: BB2 (デジタル入力, 1=high)
   

 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

6.3. シリアルポート

コンピュータと接続させたい機器が RS-232 みたいな仕組みを持っているなら
、その機器との接続にシリアルポートを使うといいでしょう。 Linux のシリア
ルドライバはほとんどありとあらゆる応用が効きます。 (シリアルポートを直
接操作するなんてしない方がいいです。そんなことするなら多分カーネルドラ
イバを書かなければならないハメになりますよ。) このドライバとっても汎用
性が高いので標準でない bps レートを使うといったこととかも問題ではありま
せん。

Unix システムでシリアルポートを使うプログラムのより詳しい情報については
termios(3) マニュアルページやシリアルドライバのソースコード(linux/
drivers/char/serial.c) 、またhttp://www.easysw.com/~mike/serial/を参考
にしてください。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

7. Hints

まともなアナログ I/O が必要な場合には、ADC (アナログ -> デジタルコンバ
ータ)や DAC (デジタル -> アナログコンバータ)チップをパラレルポートに接
続するという方法もあります。 (ヒント: 電源には気をつけて下さい。ゲーム
ポートからの電源を使うか、ディスクドライブ用の余っている電源コネクタを
コンピュータケースから引っ張り出すなどがいいでしょう。パラレルポートか
ら電源をとるならば、低消費電力のデバイスを使う必要があります。) (訳注:
いずれにせよ、これらはデジタル系の電源ですから、アナログ系に使う場合に
はなんらかの方法でデカップリングをお忘れなく。) または、AD/DA カードを
購入するという手もあります。 (昔のトロいものなら大抵 I/O ポートで制御で
きます。) もし、1〜2 チャネルで十分であり、あまり正確でなくてもよく、
(おそらく) 不正確な中点補正(zeroing)でもよければ、Linux のサウンドドラ
イバでサポートされている安いサウンドカードを買ってきて使う、という方法
もあります。 (それに、結構高速ですよね。)

精密なアナログ機器では不適当なグランド設定によりアナログ入出力の誤差が
生ずることになります。何か変だなぁ、と思ったら、その機器をフォトカップ
ラを用いてコンピュータから電気的に(コンピュータと機器の間のすべての信号
線を) アイソレートしてみるといいでしょう。より良いアイソレーションを得
るためにフォトカップラの電源をコンピュータからとるようにしてみましょう
。 (ポートの予備の信号線から十分なくらいの電源がとれます。)

Linux で使えるプリント基板設計のソフトウェアをお探しでしたら、 Pcb とい
う名前のフリーな X11 アプリケーションがあります。すごく難しいことをしよ
うというのでない限り、これで十分役にたちます。多くの Linux ディストリビ
ューションに収録されていますが、 ftp://sunsite.unc.edu/pub/Linux/apps/
circuits/ (ファイル名 pcb-* ) からも手にいれることができます。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

8. トラブルシュート

Q1.
    ポートをアクセスした時に segmentation faults が起きてしまいます。
   
A1.
    プログラムが root 特権を持っていないか、なんらかの理由で ioperm()
    を呼び出した時に失敗しているのでしょう。 ioperm() の返り値を調べて
    みてください。それと本当に ioperm() で許可を得たポートにアクセスし
    ているかどうかも調べてみてください。 (Q3も参考にしてください。) デ
    ィレイ付きマクロ (inb_p(), outb_p() などなど)を使っているなら、
    ioperm() でポート 0x80 のアクセス許可も忘れずに取得してください。
   
Q2.
    in*() とか out*() とかの関数がどこにもみつかりません。gcc が
    undefined references とか文句を言ってきます。
   
A2.
    コンパイルの時に最適化をオン (-O) にしなかったのでは? その結果、gcc
    は asm/io.h の中にあるマクロを解決することができなかったのです。そ
    れとも、#include <asm/io.h> を忘れてませんか?
   
Q3.
    out*() を実行してもなにもおこらない、またはなんか変なのですが。
   
A3.
    パラメータの順番をチェックして下さい。outb(value, port) という順番
    です。 MS-DOS でおなじみの outportb(port,value) とは逆です。
   
Q4.
    標準の RS-232 機器、パラレルプリンター、ジョイスティックといったも
    のを制御したいのですが...
   
A4.
    たぶん既にあるドライバ( Linux のカーネルとか X サーバとかそういった
    ものにくっついてくるやつのことです。)を使った方がいいです。そういう
    ドライバはすごく汎用性が高くて、ちょっと規格外の機器なんかも大抵動
    きます。そこらへんの文書へのポインターとして、上で書いた標準ポート
    の情報を参考にしてください。
   

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

9. プログラムの例

これは、I/O ポートアクセスの単純なコーディング例です:

 
/*                                                                       
 * example.c: とっても簡単なポートI/Oの例                                
 *                                                                       
 * なにも役にたつことはしていません。ポートに書き込み、一時停止して、    
 * ポートから読み出すだけです。「gcc -O2 -o example example.c」で        
 * コンパイルして、ルートユーザになって「./example」で実行してください。 
 */                                                                      
                                                                         
#include <stdio.h>                                                       
#include <unistd.h>                                                      
#include <asm/io.h>                                                      
                                                                         
#define BASEPORT 0x378 /* lp1 */                                         
                                                                         
int main()                                                               
{                                                                        
  /* ポートへのアクセス許可を得る */                                     
  if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}               
                                                                         
  /* ポートのデータ (D0-D7)を全て low (0) にする */                      
  outb(0, BASEPORT);                                                     
                                                                         
  /* しばらくの間 (100 ms) スリープ */                                   
  usleep(100000);                                                        
                                                                         
  /* ステータスポート (BASE+1) から読み出して、表示する */               
  printf("status: %d\n", inb(BASEPORT + 1));                             
                                                                         
  /* もうポートを使わないので後始末 */                                   
  if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}               
                                                                         
  exit(0);                                                               
}                                                                        
                                                                         
/* example.c おわり */                                                   
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

10. 謝辞

とてもたくさんの方から助け (contribute) を受けましたので、ここには記し
きれません。皆さん、とてもありがとう。すべての方にリプライすることはで
きませんでしたがそういう方々にはごめんなさい、そしてもう一度、助けてく
れてありがとう。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

11. 日本語訳について

訳者          : 川島 浩       <kei@sm.sony.co.jp>
訳の吟味/校正 : 山崎 康弘さん <hiro@koneeko.linux.or.jp>
(1996/12/26)

更新：幸田あきひろ <coda@post.kek.jp> (2001/03/24)
