Entry

パラレルポートを叩いてみる

2004年02月13日

パソファミを FreeBSD からいぢろうかと思ってたけれど,考えてみると,FreeBSD からパラレルポートを叩いたことが無かったんでした。気づかないのも何ですが,まったくもって誤算です。

そんなわけで,テスト回路を作っていろいろやってみました。UN*X で周辺デバイスを扱うのは,思いの他簡単です。

テスト回路

テスト回路の回路図は次のようなものです。

パラレルポートのテスト回路

やっつけで描いたので抵抗値が抜けてます。これは,普通のプルアップに使うくらいの抵抗(1〜10kΩ)だと思ってください。ここでは,あまりもんの 10kΩ を付けておきました。

また,トランシーバ IC として 74645 を指定してるけれど,実際作った回路には 74640 を使いました。どっちでも大した違いはないけれど,74640 は出力が反転します。あ……えらい違いですね。すいません。74640 は "H" と "L" の違いが分かりづらいので,74645 をおすすめします。

パソファミは,カウンタをクリアするのに /STROBE を使うので,パラレルポートの 1pin にもLED を付けました。これはアクティヴロー("H" で "0","L" で "1")です。今回は Input を試さないから,データ受信用の BUSY ピンは使いません。電源周りの回路を描いてないけれど,これはいいですよね。どんなものでもいいので IC 用に 5V を供給してください。適当にコンデンサを挟むと電源が安定します。

簡単な回路なので,チャッチャカ作っちゃいましょう。できあがると,こんな感じになりました。

テスト回路

8つ並んでいる緑の LED はデータポート(後述)で,5φ の LED(青色!)は /STROBE です。

パラレルポートの構造

パラレルポートっていうのは……,今更説明することもないですね。アノ長いポートです。ピンアサインは以下のようになっています。

pinI/ONameDescription
1O/STROBEストローブ
2I/ODATA0データビット0
3I/ODATA1データビット1
4I/ODATA2データビット2
5I/ODATA3データビット3
6I/ODATA4データビット4
7I/ODATA5データビット5
8I/ODATA6データビット6
9I/ODATA7データビット7
10I/ACK受信可能
11IBUSY受信不可
12IPE用紙切れ
13ISLCTセレクト状態
14O/AUTOFEEDXTオートフィード指示
15I/ERRORエラー
16O/INIT初期化
17O/SLCTINセレクト入力
18-25---GNDグラウンド

2pin の DATA0 から 9pin の DATA7 までの合計 8bit はデータポートで,パラレルにデータ転送できます。これがパラレルポートの本体と言ってもいいでしょう。このポートは Input/Output のどちらにも使えます。「用紙切れ」とか,「オートフィード」とかがあるけれど,これはセントロニクス社が規格化した当時,プリンタ用のインターフェースとして策定されたからです。

プログラム

UN*X のお作法

初めに,UN*X で周辺デバイスをいぢるお作法を確認しておきます。FreeBSD に限らず,「UN*X はデバイスをファイルのように扱うことができる」ということは聞いたことがあるかと思います。このことは,例えばシェルスクリプトなんかで,

% nkf -S -e -d  sjis.txt > euc.txt

なんてするのと同じように,デバイスにアクセスするときも,

# cat hoge.txt > /dev/lpt0

みたいな処理をすることができる,ということです。書式は同じだけど,内部でやっていることが全然違うことに注目してください。

これはシェルでの扱い方ですけど,他のプログラムからデバイスを扱うときも同様に,ファイルを扱うようにすることができます(もちろん,その言語がシステムコールを実装している必要はある)。ここでは,C を使うけれど,C でファイルを扱うと言えば fopen() をはじめとした f*() 系の関数ですね。入門書にさんざっぱら出てくるから見飽きてるでしょうけど,一応,比較のために「ファイル foo.txt に "hoge" という文字列を書き込む」というプログラムを見ておきましょう。

#include <stdio.h>

int main()
{
    char str[] = "hoge";
    FILE *fp;

    /* ファイルを開く */
    if ((fp = fopen("foo.txt", "w")) == NULL) {
        fprintf(stderr, "ERROR: file can't open!\n");
        return 1;
    }

    /* "hoge" を書く */
    fprintf(fp, str);

    /* 後始末 */
    fclose(fp);

    return 0;
}

要するに,

  1. fopen(3) でファイルを開く
  2. fprintf(3) とか fscanf(3) でいぢる
  3. fclose(3) で後始末

といった手順を踏んでアクセスするわけです。デバイスをいぢるときも,これと基本的に同じです。もっとも,Manpage の番号を見ても分かるとおり(3),ファイルの入出力は標準ライブラリとして stdio.h にまとまってるけれど,デバイスをいぢる場合は,システムコールを使う必要があります(パラレルポートをいぢる標準ライブラリはないのです)。つまり,

  1. open(2) でデバイスを開く
  2. ioctl(2) なんかでいろいろいぢる
  3. close(2) で後始末

といった手順で扱えばいいわけです。ファイルの扱い方と似てますよね。そうでもないかな。

いぢるときは,専ら ioctl(2) を使います。これは,I/O 周りを扱うときのナンデモ関数です。書式は,

#include <sys/ioctl.h>
int ioctl(int d, int request, ...);   

になっていて,第1引数にファイルディスクリプタ(ここでは,/dev/ppi0 のファイルディスクリプタ),第2に I/O への命令,それ以降に命令が取る引数を取ります。肝心のファイルディスクリプタを作るには open(2),後片づけするときは close(2) を使います。ここら辺は,fopen(3) fclose(3) と同じですね(詳しくは Manpage を見てください)。

データポートをいぢってみる

まずは,データポートからいぢってみます。「パラレル」というだけあって,このピンは 8bit 分をまとめていぢることができます。ioctl(2) に与えられる命令は,Output 用に PPISDATA,Input 用に PPIGDATA が用意されてるけれど,この回路では受け取るデータがないので PPISDATA だけを使ってみます。

#include <fcntl.h>
#include <sys/ioctl.h>
#include </sys/dev/ppbus/ppi.h>     
#include </sys/dev/ppbus/ppbconf.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int fd;
    u_int8_t val;
    
    if ((fd=open("/dev/ppi0", O_WRONLY)) < 0) {
        fprintf(stderr, "Error!\n");
        exit(-1);
    }

    for(val=0x00; val < 0xff; val++) {
        ioctl(fd, PPISDATA, &val);
        usleep(100000);             /* wait 100ms */
    }
    val = 0x00;
    ioctl(fd, PPISDATA, &val);

    close(fd);

    return 0;
}

ここでは,val を 0xFF までインクリメントして,その都度パラレルポートに出力しています。何もしないと早すぎて分からないので,出力した後に 100ms 眠らせました。

コントロールポートをいぢってみる

次に,コントロールポートをいぢってみます。コントロールポートはアクティヴローなので,ビットを立てると LED が消えるはずです。実験回路には /STROBE しか設けていないから,これだけいぢってみます。

#include <fcntl.h>
#include <sys/ioctl.h>
#include </sys/dev/ppbus/ppi.h>     
#include </sys/dev/ppbus/ppbconf.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int fd;
    u_int8_t con = 0x00;
    
    if ((fd=open("/dev/ppi0", O_WRONLY)) < 0) {
        fprintf(stderr, "Error!\n");
        exit(-1);
    }

    con |= STROBE;
    ioctl(fd, PPISCTRL, &con);

    sleep(5);  /* wait 5s */
    
    con &= ~STROBE;
    ioctl(fd, PPISCTRL, &con);

    close(fd);

    return 0;
}

コントロール用のビットを立てたり落としたりするために,STROBE と ~STROBE という定数(?)があって,次のように使います。

con |= STROBE     /* ビットを立てる */
con &= ~STROBE    /* ビットを落とす */

コンパイルして実行すると,/STROBE の LED が5秒消えて,また点くはずです。あ,なんも面白くないですか。そうですね。

これだけじゃつまらないので,さっきのデータポートいぢりで作ったプログラムと合体させて,まとまったプログラムにしてみます。やることは,「起動後,/STROBE を "H" にして,データポートの値を 0x00 から 0xFF までインクリメントする。終わったら, /STROBE を "L" に戻す。」といったことにしましょう。パソファミでもこんな感じの制御になりそうですから……。

ただ切り貼りするだけなんで,余計な説明は要らないと思います。こんな感じでいきます。

#include <fcntl.h>
#include <sys/ioctl.h>
#include </sys/dev/ppbus/ppi.h>     
#include </sys/dev/ppbus/ppbconf.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int fd;
    u_int8_t val;
    u_int8_t con = 0x00;
    
    if ((fd=open("/dev/ppi0", O_WRONLY)) < 0) {
        fprintf(stderr, "Error!\n");
        exit(-1);
    }

    con |= STROBE;
    ioctl(fd, PPISCTRL, &con);

    
    for(val=0x00; val < 0xff; val++){
        ioctl(fd, PPISDATA, &val);
        usleep(100000);             /* wait 100ms */
    }    
    val = 0x00;
    ioctl(fd, PPISDATA, &val);

    con &= ~STROBE;
    ioctl(fd, PPISCTRL, &con);

    close(fd);

    return 0;
}

実行してみる

めでたくプログラムもできたので,コンパイルして実行してみます。変なライブラリは使ってないので,コンパイルは問題なく通ると思います。

作ったテスト回路をパラレルポートに挿して,電源を入れると次のようになります。まだ制御してないので,ここでは適当な LED が点くだけかもしれません。

パラレルポートの初期状態

ここでは /STROBE もデータポートも全部 "L" になってますね。え?「データポートは点いてるんだから "H" だろうが」ですって?これは,74640 を使ってるからデータポートの真偽がが反転してるんです。74645 を使うと,全部消えてるはずです(紛らわしくて申し訳ない)。/STROBE (青)はアクティヴローなので,これで "L" です。

早速実行してみます。点滅するのを表現するのは難しいけど,実行すると,まず /STROBE が消えて("H" になる),データポートが以下のように2進で消えていきます(繰り返すけど,ここでは消えると "H" なのです)。下の写真だと,val の値は 01100100 (0x64)でしょうか。

パラレルポートコントロール中

今回はここまで

どうでしょう。ハードウェアの制御といっても,いわゆるレガシーポートなら結構簡単に扱えたと思います。USB やら IEEE1394 だと,こうはいかないかもしれないけれど(ここらへんはよく分かってない),デバイスをファイルのように扱えるというのはとても便利ですね。これだけ制御できるだけでも,例えば,マイコン用によく使われてる16×2行の液晶モジュールを制御するなんてアイデアが湧いてきます。

パソファミに話を戻すと,これで懸案のパソファミ制御に 1/4 歩くらい近づいた感じでしょうか。回路図を見る限り,パソファミはカウンタをインクリメントしてアドレスバスをコントロールつつ,データバスからシフトレジスタ経由(シリアルに変換)でデータをいただいてくるもののようです。ただ,CHR ROM や PRG ROM の使い方などなど,実際にどういう動きをしているのかがまだよく分かりません。これは,ソフト側の制御そのものに関わるので,もう少し詳しく解析してみることにします。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN