パソファミを 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 です。
パラレルポートっていうのは……,今更説明することもないですね。アノ長いポートです。ピンアサインは以下のようになっています。
pin | I/O | Name | Description |
---|---|---|---|
1 | O | /STROBE | ストローブ |
2 | I/O | DATA0 | データビット0 |
3 | I/O | DATA1 | データビット1 |
4 | I/O | DATA2 | データビット2 |
5 | I/O | DATA3 | データビット3 |
6 | I/O | DATA4 | データビット4 |
7 | I/O | DATA5 | データビット5 |
8 | I/O | DATA6 | データビット6 |
9 | I/O | DATA7 | データビット7 |
10 | I | /ACK | 受信可能 |
11 | I | BUSY | 受信不可 |
12 | I | PE | 用紙切れ |
13 | I | SLCT | セレクト状態 |
14 | O | /AUTOFEEDXT | オートフィード指示 |
15 | I | /ERROR | エラー |
16 | O | /INIT | 初期化 |
17 | O | /SLCTIN | セレクト入力 |
18-25 | --- | GND | グラウンド |
2pin の DATA0 から 9pin の DATA7 までの合計 8bit はデータポートで,パラレルにデータ転送できます。これがパラレルポートの本体と言ってもいいでしょう。このポートは Input/Output のどちらにも使えます。「用紙切れ」とか,「オートフィード」とかがあるけれど,これはセントロニクス社が規格化した当時,プリンタ用のインターフェースとして策定されたからです。
初めに,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(void)
{
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;
}
要するに,
fopen(3) でファイルを開く
fprintf(3) とか fscanf(3) でいぢる
fclose(3) で後始末
といった手順を踏んでアクセスするわけです。デバイスをいぢるときも,これと基本的に同じです。もっとも,Manpage の番号を見ても分かるとおり(3),ファイルの入出力は標準ライブラリとして stdio.h にまとまってるけれど,デバイスをいぢる場合は,システムコールを使う必要があります(パラレルポートをいぢる標準ライブラリはないのです)。つまり,
open(2) でデバイスを開く
ioctl(2) なんかでいろいろいぢる
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(void)
{
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(void)
{
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(void)
{
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 の使い方などなど,実際にどういう動きをしているのかがまだよく分かりません。これは,ソフト側の制御そのものに関わるので,もう少し詳しく解析してみることにします。