<戻る

ソフトウェア編
その1 パラレルポートに電気を流そう


 デスクトップタイプのパソコンの裏には、プリンタポート(パラレルポート)が1つはあります。これはその名の通りプリンタをつないでデータの送受信を行う端子です。しかしながら、最近はUSBなどのもっと高速な端子がメジャーになってしまい、大抵は開いている端子でもあります。今回の計画では、パソコン側から「何か」を出力しなければなりません。プリンタポートはDos/V機が出る前からあり、制御も比較的簡単だと聞きます。でも、基本的なところがぜんぜんわかりません。沢山あるピンは何なのか?パラレルポートから何が発せられているのか?何か特別なやり取りが必要なのか?そして、どんなプログラムを書けば良いのか?この章では、超初心者の目線で、これらを探っていきたいと思います。



@ プリンタポートの予備知識

 まず、プリンタポートってどれさ?っという所から始めます。デスクトップの裏を見ると、きっとこんな形状のコネクタがあるはずです。

 これがプリンタポートです。穴の数は全部で25個あって、右下から右上に1、2、3、...、13、そして左下に戻って左上に14、15、...、25と番号が付いています。それぞれの穴の役目は決まっています。詳細はこちらサイト様「なひたふ電子情報」(http://www.nahitech.com/nahitafu/mame/mame5/printer.html)に大変詳しく掲載されています。興味の無い方でもご覧になるととても勉強になります(私も勉強させて頂きました)。このポートがパラレルポートと呼ばれるのは、2〜9番までの8つの穴1つ1つに0もしくは1の信号を定義でき、それを接続先に同時平行で伝えることが出来るからです。正に「パラレル(並行)」です。

 このコネクタを通してプリンタに印刷等を行う時には、それなりのルールに従って送受信を行わなければならないのですが、そうでない場合はそれぞれの端子は信号を出すか入れるかだけの単純な回路に変貌します。これが、扱いやすい理由でしょうね。ただし、18〜25番は電気を逃がすための線(GND)なので、情報のやり取りは出来ません。また、番号によっては入力(パソコン側にデータを入れること)専用の箇所があります(10,11,12,13,15)。出力として扱えるのは1〜9,14,15の11箇所ということになりますが、普通は2-9番を使います。


A プリンタポートに割り当てられる物理アドレス

 プリンタポートはIO(Input/Output)ポートの1つで、パソコンを起動するとその制御のための物理メモリ空間が4バイト分自動的に確保されます。よほど変なことをしていない限り、その先頭アドレスは0x3BC、0x378、0x278のいずれかに決まっているそうです(どれになっているかは機種等で違うようです)。先頭アドレスから1バイトずつ、次のように名前が付けられています。

先頭アドレス データレジスタ ステータスレジスタ コントロールレジスタ その他
0x378 0x378 0x379 0x37a 0x37b
bit

 この中で、コントロールレジスタ・データレジスタがとっても大切です。コントロールレジスタでは信号の入出力を決めるビットがあります。これをちゃんと設定しないとうまく信号が出入りしてくれません。データレジスタには出力する信号を8bitで指定します。また、入力時にはここにデータが格納されます。このデータレジスタを詳細に見てみます。

0x378 データレジスタ
0x378
bit D7 D6 D5 D4 D3 D2 D1 D0
穴の番号 9 8 7 6 5 4 3 2

 各bitと穴の番号が1対1で対応していまして、出力時にはbitが立っている部分だけに信号が出ます。

 データレジスタやコントロールレジスタは共有メモリである物理アドレスにあります。普段プログラミングなどで使用しているメモリは「相対メモリ」で、OSから仮の住所として貰っている、言ってみれば私物です。相対メモリに間違って書き込んでも、大抵はアプリケーションが止まって終わるだけですが、共有の物理アドレスに間違って書き込むと、最悪OSそのものが動かなくなります。よってポートが存在しているか、先頭アドレスが何なのかをちゃんと調べないと物凄く危険です。
 プログラムから調べる方法は、色々と探したのですが非常に難しいようです。事前に調べてしまうのが簡単です。[コントロールパネル]→[システム]→[ハードウェア]→[デバイスマネージャ]の「ポート(COMとLPT)」を開くと「プリンタポート(LPT1)」が出てきます。これをダブルクリックすると「プリンタポートのプロパティ」が開きます。その[リソース]タグを見ると、IOの範囲というところに物理メモリのアドレスが表示されています。プリンタポートが無い場合は、デバイスマネージャにポートが出てきませんので気を付けて下さい。



B Windows2000、XPでのポートアクセス問題

 *これらOSを搭載している方は、ほぼ必ず以下の作業が必要になります。

 Windows2000とXPは保護機能が働いており、IOポートに直接触ることを禁じています。よって、何もせずに「書き変えろ」と命令すると「あんたには権限が無いからだめ!」っと怒られます。触るにはいわゆる「ドライバ」が必要なのです。

 Windows2000以降でIOポートを触る時には、「GIVEIO」というドライバをインストールする必要があります。GIVEIO自体は検索サイトでいくらでも見つかるほど有名なドライバです。GIVEIOはインストールがちょっと面倒らしいのですが、(http://homepage1.nifty.com/paraffin/software/)にありますINSTDRVはこれを自動的に行ってくれます。ありがたいフリーソフトです(作者様に感謝致します)。GIVEIOへのリンクもありますので、大変助かりました。 

 このドライバをインストールして再起動すると、Windows2000、XPでもIOポートに触れるようになります。ただ、当然のことながら、これによって不測の事態が起こる可能性が新たに生じます。くれぐれも慎重に・・・



C プリンタポートのチェック

 GIVEIOドライバを設定すると、このドライバが権限の壁を越えてポートへ誘ってくれます。ポートをチェックするには、次のようにプログラミングします。

int main()
{
   HANDLE hComm;

   // プリンタポートのチェック
      hComm = CreateFile( \\\\.\\giveio,          // ポートの名前
         GENERIC_READ | GENERIC_WRITE,    // アクセス権
         0,                                              // 共有方法
         NULL,                                        // ハンドルの子プロセスへの継承
         OPEN_EXISTING,                          // 作成時動作
         FILE_ATTRIBUTE_NORMAL,             // 属性
         NULL                                         // ファイル属性テンプレートファイルハンドルの指定
       );

    if(hComm == INVALID_HANDLE_VALUE){
      // 使えるプリンタポートがない
      return 0;
   }

   // ポートハンドルを閉じる
   CloseHandle(hComm);

   return 0;
}

Win32APIであるCreateFile関数は、普通ファイル作成に使用しますが、第1引数にポートの名前を指定すれば、ポートが存在すればそのハンドルを返してくれます。ポートの名前は第1引数の通り、第5引数はOPEN_EXISTING、第7引数はNULLにしてください。

 関数が成功すると、hCommに使用可能なポートのハンドルが返ります。失敗するとINVALID_HANDLE_VALUEが返ります。これで有無が判断できます。最後にハンドルを閉じれば、ポートの存在チェックの完了です。



Dプリンタポートの信号を目で見て確認する方法

 使えるポートが判明したら、そのポートから何か情報を発してみましょう。ただ、当然ながら発した情報の受け手が必要になります。でも、その為に他のパソコンを用意したり、受信側を作ったりというのは面倒。送受信をエミュレートできるソフトもきっとありますが、やっぱり目で見るのが一番確実なわけで。

 そこで、今回は「テスター」を用いることにしました。今後回路を組むはずなので、実際にどんな電気が出ているかをテスターで見るのが一番まともで確実な確認だと思います。挙動も目で判断できますしね。

 ただパソコンの裏のプリンタポートはメス型なので、このままだとテスターの端子が入りませんから、テストできるようにこれをオス型に変換しなければなりませんでした。家のあちこちを探し回って、PC9821で使用していた大変古いプリンタケーブルを見つけました。もう使用していませんので、このコードをばっさり切ってコードをむき出しにします。


ケーブル初めてばらしました・・・これ、全部結線していないケーブルでした(笑)

 手作り感いっぱいのテスト用コードの完成です。不恰好ですが、これでも十分いけます。

 では、ポートから情報を発してみましょう。そのためには、ちょっとした手順を踏みます。まず、コントロールレジスタを「出力モード」に切り替えます。コントロールレジスタの詳細を下に示します。

LPT1 コントロールレジスタ
0x37a
bit 8 7 6 5 4 3 2 1
穴の番号 DIRC(1入力:0出力)
*ポートアドレスが0x378の場合

 0x37aの6bit目が入出力を切り替えるフラグです。1にすると入力、0にすると出力モードになります。他のビットは今回関係ありませんので触りません。

出力モードに設定した後、データレジスタに値を書き込むと、即座にそれが出力されます(1MHzくらいの速さだそうです。つまり100万分の1秒くらいで変化を出力できると言う事)。これらポートアドレスへの情報の書き込みは_opt関数を用いるのが楽です。
 CreateFile関数でポートを確認し、そのアドレスをコンパネでチェックしたら、次のようにして出力します。

unsigned short data_address = 0x378;
unsigned short cont_address = 0x37a;

// 出力モードに設定
_opt(cont_address, 0);

// 「17」を出力
_opt(data_address, 17);
↑私の環境ではポートの先頭アドレスは0x378でした

 _opt関数は2つの引数を取ります。第1引数にはポートに割り当てられている物理アドレスを符号無し整数(0-65535の範囲)で指定します。ポートの先頭アドレスが0x378なので、データアドレスなら0x378、コントロールアドレスなら0x37aになります(それぞれdata_address、cont_addressに格納)。第2引数には変更したい値を0〜255の間で設定します。

 出力モードにするために、コントロールアドレスに0を入れています。細かく言えば6bit目を0にすれば良いのですが、フラグを全部降ろしても問題ありません。例として「17」という数字をデータアドレスに格納すると、それに見合うビットが立ちます。この時、プリンタポートから「何か」が出ているはずです。具体的には17(=00010001)ですから、

LPT1 データレジスタ
0x378
穴の番号 9 8 7 6 5 4 3 2
bit D7 D6 D5 D4 D3 D2 D1 D0
17 0 0 0 1 0 0 0 1

と2番と6番の穴から何かが出ているはずです。

 さて、では実際に2番と6番の穴から何が出ているのかを検証してみましょう。テスターを直流電圧測定モードにして、一方を2番、もう一方を18番(GND)に触れさせます。その状態でテスターを見ます。

ほうほう、177.3mVでした。そして、このままでプログラムを実行すると・・・

おお!表示が約4.3Vの電圧に変わりました!確かに、ここには電気が通っています。しかし、プログラムが終わっても、電気が流れたままです。低レベルアクセスであるプリンタポートは、プログラムが終わったからと言って後始末はしてくれないわけです。このままでは気持ちが悪いので、最後にはちゃんとすべての出力を終えてプログラムを終了させることにします。先ほどのプログラムの最後に次のコードを追加します。

unsigned short data_address = 0x3BC;
unsigned short cont_address = 0x3BE;

// 出力モードに設定
_opt(cont_address, 0);

// 「17」を出力
_opt(data_address, 17);

// 「0」を出力
_opt(data_address, 0);


 めでたく、本章の目的であるパラレルポートに電気を流すことができました。目で見て電圧の違いを確認できたのが大きいですね。私は工学畑出身ではないので、普通に感動でした。上のプログラムで指定の穴に電圧が生じる事は確認しましたから、今後しばらくはそれを前提とし仮想的にアプリケーションを作成できます。

 次からは、いよいよ自動操作を行うアプリケーションを先に作成していきます。