July 2008 Archives

ちゃんとテストしていなかったのが原因です。
今までにコメントしようとしていただいた方々、ご迷惑をおかけしてすみません。

Movable Typeで認証のないコメントを受け付けるには、スパム防止のために画像認証をするのが普通です。いわゆるノイズにまみれた人間にしか読めない文字列を入力させる、というものです。これを実現しているのがCaptchaなのですが、いくつかやらなければいけないことがあります。
  • Image::Magickをインストールする
  • Movable Typeの設定→コメントで
    「コメントを受け付ける」にチェック
    CAPTCHAプロバイダを「MovableType規定」とする(この選択肢が出ない場合はImage::Magickのインストールができていません)
  • Movable Typeの設定→登録/認証設定で
    「認証なしコメント」にチェック
  • mt-config.cgiに以下の設定を追加または変更する。なお、絶対パスにする必要がある
    CaptchaSourceImageBase /(mt-staticのディレクトリ)/images/captcha-source
私の場合、この最後の設定ができていないのでした。
詳しくは公式サイトのリファレンスにもあります。

Z80のC言語クロスコンパイル(SDCC)(2)

| 1 Comment | No TrackBacks
sdccを使うことによりC言語でプログラムが可能になりますが、実際にはアセンブリ言語と一緒に使うことが多いと思います。理由は、
  • sdccの出力するコードは速度的にも容量的にも効率が悪い
  • 直接BIOSやI/Oポートにアクセスすることがある
 などです。そのため、ここではsdccが出力する関数の構造についてみてみます。

関数の構造

簡単な関数の実装から、例を見てみましょう。

char func(char *arg) {
  char *str;
  unsigned char i;

  // 関数本体

  return i;
}
  
という関数を例にとって見ましょう。sdccでは、この関数を次のような構造で出力します。

_func_start::
_func:
	; ixのバックアップ
	push	ix
	; ix = sp
	ld	ix, #0
	add	ix, sp
	; sp = sp - 3
	ld	hl, #-3
	add	hl, sp
	ld	sp, hl

	; 関数本体

	; sp = ix
	ld	sp, ix
	; ixを戻す
	pop	ix
	ret
_sdos_openFile_end::
  
まず、最初にixをバックアップします。このレジスタはスタックポインタをバックアップするために使います。そのため、ixにspを代入します。

次に、spを3減じています。これは、関数内で利用するローカル変数のためのヒープを確保するためです。この関数では、str(2バイト)、i(1バイト)をローカル変数として使っているので、その分の領域をスタックに確保しています。

 この関数が呼び出されたときにSPが0xA200だとすると、この時点でのメモリは次のようになっています。

sdcc1.PNG 関数の最後では、spを保存されていたixに戻してretします。

関数呼び出し

次に、関数呼び出しです。先ほどのfunc内に、次のようなfunc2の呼び出しがあるとしましょう。

unsigned char func2(char *, char);
  
呼び出すためのCコードは次のようなものだとします。

i = func2(str, 2);
  
この部分の出力の様子を見てみます。

	; 引数2をスタックに入れる
	ld	a,#0x02
	push	af
	inc	sp
	; 引数strをスタックに入れる
	ld	l, -2(ix)
	ld	h, -1(ix)
	push	hl
	; 呼び出し
	call	_func2
	; 結果の退避(この部分は呼び出しによって異なる)
	ld	c, l
	; スタックを戻す
	pop	af
	inc	sp
  
まず、Aに2を代入してスタックにpushします。この引数は1バイトなので、inc spして不要なスタックを削除します。次に、HLにstrを代入してpushします。

callする直前のメモリマップは次のようになります。

sdcc2.PNG
これを見て判るように、関数呼び出しの際に与える引数は、後ろから積んでいきます。これにより、呼び出し先では最初の引数がより低いアドレスに格納されています。

呼び出し結果ですが、この関数呼び出しは返り値が1バイトで返ってきます。その場合、結果はLレジスタに入っています。いったんそれをCレジスタに退避したあと、不要となった引数スタックを捨てています。pop afで引数str(2バイト)を捨て、次のinc spで引数2(1バイト)を捨てています。これでスタックの状態が元に戻ります。

アセンブリ言語による関数作成

アセンブリ言語でC言語から呼び出される関数を作成する場合、以下に注意します。
引数の格納位置

引数は前述のように最初の引数から順番に格納されています。実際にはcallされているので、スタックの先頭はリターンアドレスになっています。そのため、一般的なアクセス方法は次のようになります。
	ld	hl, #2
	add	hl, sp
	; (HL)に最初の引数が入っている
  ; 上記のfuncの呼び出しの例で言うとHL=0xa1fa
壊してはいけないレジスタ

上述のように、sdccの関数ではixレジスタをspの保存に使っています。そのため、呼び出し先でixを破壊してしまうと戻ってからの処理がおかしくなってしまいます。必要な場合は、作成する関数の最初でpush ixし、戻る直前でpop ixしましょう。

sdccは命令ごとにレジスタを初期化しなおすので、その他のレジスタは基本的に使っても問題ありません。これが、sdccのコンパイルコードの効率が悪い理由でもあります。

結果の格納

関数呼び出しの結果ですが、結果の受け渡しにはスタックポインタは使わず、レジスタを使います。結果の大きさに応じて次のようにレジスタが決まっています。

1バイト(char)
Lレジスタ
2バイト(int, short, ポインタ(char*など))
HLレジスタ
4バイト(long)
DEHLレジスタ、DEに上位16ビット、HLに下位16ビットを入れる

型に応じて上記レジスタに結果を格納してretします。

格安SSD...なんでEee PCだけなの?

| No Comments | No TrackBacks
Eee PC 901用に格安のSSDが発売だそうです。 残念ながらEee PC 901専用。なんで? 1.8インチや2.5インチでの重要はすごくあるでしょうに。

直販価格が32GBモデルが1万6000円、64GBが3万2000円なので、SSDが一気に身近に感じられます。

ソケットは別ですが、Eee PC のSSD1.8インチHDDに換装することも可能ということなので、通常のHDD互換品を作ることもそんなに難しいことじゃないと思うんですが...。どこかの人柱か、ニッチなメーカーがトライしないかな?

Z80のC言語クロスコンパイル(SDCC)(1)

| No Comments | No TrackBacks
UNIXやWindows環境で利用できるフリーのZ80対応クロスC言語コンパイラとして、現在利用可能なのはSDCCくらいしかありません。

このコンパイラに関する日本語のドキュメントはまだほとんどないと思われますので、私が知っている限りで説明しておこうと思います。

コンパイルとリンク

sdccの使い方は、gccなどのコンパイラとそれほど変わりませんが、典型的な使い方を挙げます。

(各プログラムをコンパイル、アセンブル)
% sdcc -mz80 -c myprog1.c -o myprog1.o
% sdcc -mz80 -c myprog2.c -o myprog2.o
% as-z80 -o mycrt0.o mycrt0.S
(リンク)
% sdcc -mz80 --out-fmt-ihx --code-loc 0x840f --data-loc 0 --no-std-crt0 -o myprog.ihx mycrt0.o myprog1.o myprog2.o

mycrt0.Sはこの後説明する独自のスタートアップコードです。リンク時はこのオブジェクトを最初に指定しないとリンクの順番がおかしくなるので注意してください。

なお、このリンク方法でうまくihxが生成されないことがしばしばあります。おそらくバグだと思いますが、その場合、lnkファイルが生成されているので、続けてlink-z80を実行するとうまくいきます。

(リンクはされていなくても、myprog.lnkは作成されている)
% sdcc -mz80 --out-fmt-ihx --code-loc 0x840f --data-loc 0 --no-std-crt0 -o myprog.ihx mycrt0.o myprog1.o myprog2.o
(リンクしてihx作成)
% link-z80 -nf myprog

link-z80にはコマンドモードからの入力オプション(-c)がありますが、これは標準入力からの行単位入力を要求するので注意してください。コマンドライン引数では指定できません。

なお、アセンブラ(as-z80)では、オプションをソースファイルの後ろに指定できませんので注意してください。

sdccのオプション

sdccの基本的なオプションは他のコンパイラと同じですが、特殊なオプションがいろいろあるので、ここではその中でよく使うもののみ挙げます。

コンパイルオプション

-c
コンパイルのみでリンクしない。
-o
出力ファイル指定
-I
インクルードディレクトリ
-D
define定義の指定
コンパイルオプション(sdcc固有)

-mz80
コンパイル対象をz80に指定します。他にも-mhc08などいろいろあるので、Z80プログラムのコンパイルには必須です。
--std-c99
C99標準に準拠します。sdcc 2.8.0では完全ではありません。関連するオプションとして他に、--std-c89, --std-sdcc89, --std-sdcc99があります。sdccがつくオプションは、独自拡張を使うことができます。
リンク時オプション

-l
ライブラリファイルの指定です。ライブラリファイルはsdcclibで作成することもできますし、対象ファイル名を並べただけでもokです。あとで実際の使い方を説明します。
-L
ライブラリファイルのディレクトリ指定です。-lにはディレクトリを指定できませんので、カレントディレクトリ以外にライブラリファイルがある場合は必要です。
リンク時オプション(sdcc固有)

--code-loc
コードセグメント(.area _CODEおよび_GSINIT)の開始番地を指定します。_GSINITは_CODEに続いて配置されます。
--data-log
データセグメント(.area _DATA)の開始番地を指定します。0を指定すると、コードセグメントの直後に配置されます。
--out-fmt-ihx
リンク後のオブジェクトの出力フォーマットをIntel hex形式にします。これを付属のmakebinや拙作のHexameterに与えることにより、バイナリファイルを作成します。
--no-std-crt0
標準のスタートアップコード(crt0.o)を使わない指定です。以降で詳しく説明します。

crt0のリンク

sdccをインストールしてデフォルトでコンパイルすると、付属しているcrt0.oをリンクします。これが0番地に必ずinitにジャンプするスタートアップを入れたり、PC-6001や他のプラットフォームで利用するにはそのままでは使いにくいので、変更する必要があります。
しかし、ソースコードのcrt0.sはいろいろ示唆的なので、構造を知っておくと役立ちます。

デフォルトのcrt0.s

デフォルトのcrt0.oのソースコード(crt0.s)を説明します。
	; モジュール名を定義します。
	; スタートアップコードでも、任意の名前が使えます。
	.module crt0
	; C言語のmain関数への参照です。
	; アセンブラでは、C言語の名前の頭に"_"をつけます。
	.globl	_main

	; エリア名を定義します。
	; エリア名は任意ですが、いくつか典型で使われるものがあります。
	; (ABS)は、絶対アドレス指定になります。
	.area	_HEADER (ABS)

	; .orgは次からの命令の格納アドレス指定です。
	; ここからRST 0~RST 0x38までのベクタを定義しています。
	; デフォルトではすべてretiになっています。

	.org	0
	jp	init
	.org	0x08
	reti
	.org	0x10
	reti
	.org	0x18
	reti
	.org	0x20
	reti
	.org	0x28
	reti
	.org	0x30
	reti
	.org	0x38
	reti

	; 初期化ルーチンが0x100からになります。0番地からここに飛んできます。
	.org	0x100
init:
	; スタックポインタの初期化。
	ld	sp,#0xffff
	; グローバル変数初期化ルーチンを呼び出します。
	call	gsinit
	; メインルーチンを実行します。
	call	_main
	; 終了処理へ飛びます。
	jp	_exit

	; 以下はリンカに対してエリアの順序を指示しています。
	; sdccではエリアごとに別のメモリ領域を指定することができます。
	; メモリアドレスは互いに重なってもいいので、64kBを越えるプログラムを扱うことも可能です。
	; ただし、バンク切り替えなどが必要な場合は自分で処理する必要があります。
	.area	_HOME
	.area	_CODE
	.area	_GSINIT
	.area	_GSFINAL
	.area	_DATA
	.area	_BSS
	.area	_HEAP

	; ここからはコード領域です。
	; リンカに対して--code-locで指定したアドレスから配置されます
	.area	_CODE
__clock::
	ld	a,#2
	rst	0x08
	ret
	; 終了処理。エミュレータ用のコードのようです。
_exit::
	;; Exit - special code to the emulator
	ld	a,#0
	rst	0x08
1$:
	halt
	jr	1$
	; ここからはグローバル変数の初期化です。
	; コード内に初期化が必要なグローバル変数があると、初期化コードが自動的にここに配置されます。
	.area	_GSINIT
gsinit::
	; 初期化コードは各ソースから出力されて連続した領域となります。
	; 自動的にリターンしないので、ここで別エリアを定義してretします。
	.area	_GSFINAL
	ret

なお、ラベルはコロンがひとつの場合は.globl定義がない限りローカル、コロンが二つの場合は常にグローバルになります。たとえば上記では、initはローカル、gsinitはグローバルです。ただし、アセンブラのas-z80で-aオプションをつけると、すべてのラベルがグローバルになります。

crt0の入れ替え

さて、標準のcrt0.oは使えないので、入れ替えます。
sdccには、「グローバル変数がデフォルトで0に初期化されない」というC言語の仕様に沿っていない部分があるので、そこも修正します。
	; モジュール名とグローバル宣言は一緒です。
	.module	crt0
	.globl	_main

	; リンカへのエリア指定ですが、使うものだけでも構いません。
	; コード(_CODE)、初期化ルーチン(_GSINIT)、データ(_DATA)はsdccで出力するので必須です。
	; _GSFINALは初期化ルーチンから戻るために必須です。_DATAFINALは別に必須ではないのですが、
; 大域変数の初期化時にデータ領域の終了アドレスが必要なので入れています。 .area _CODE .area _GSINIT .area _GSFINAL .area _DATA .area _DATAFINAL ; コードセグメントの最初の部分です。 ; 0番地から飛んできませんが、リンカの--code-loc指定でここから開始するので問題ありません。 .area _CODE init:: ; グローバル変数が入るデータエリア(_datastartから_dataend-1まで)を0で初期化します。 ld hl, #_datastart ld bc, #_dataend _clear_loop: ld a, h sub b jr nz, _clear_next ld a, l sub c jr z, _clear_exit _clear_next: ld (hl), #0 inc hl jr _clear_loop _clear_exit: ; グローバル変数初期化ルーチンを呼び出します。 call gsinit ; メインに飛びます。 ; メインがretするとプログラム自体の呼び出し元に返ります。 jp _main .area _GSINIT gsinit:: .area _GSFINAL ret .area _DATA _datastart:: .area _DATAFINAL _dataend::
上記のcrt0.Sでは、絶対アドレス指定を一切使っていないので、アドレスはすべてリンカで指定することになり、ihxファイルを作るまではリロケータブルです。

プログラムによっては、グローバル変数の初期化が不要な場合もあるでしょう。そのような時は、init~_clear_exitの間のコードとcall gsinit、そして_GSFINALのretを削ることができ、メモリ削減になります。C言語から出力された_GSINITはけっこう効率の悪いコード なので、グローバル変数の宣言時に値を代入するより、個別にプログラムしたほうが効率がいいこともよくあります。

このあたりのテクニックはいろいろとあるのですが、少しづつ説明していきたいと思います。

上記のcrt0.sをas-z80でアセンブルしておくことにより、複数のCプログラムをリンクして、PC-6001や他のZ80ベースのパソコンでも使えるようなバイナリを作成することが可能となります。

SDカードドライバの設計(3)

| No Comments | No TrackBacks
FATの構造(2)

前回に続き、FATの構造を理解します。

BIOS Parameter Block

前回説明したMaster Boot RecordでfirstSectorの位置にはBIOS Parameter Blockがあります。その構造体は、FAT12/FAT16の場合、以下のようになっています。なおFAT32だと、定義が異なります。

struct BIOSParameterBlock {
	BYTE jmpOpCode[3];
	BYTE OEMName[8];
	WORD bytesPerSector;	/* bytes/sector (512) */
	BYTE sectorsPerCluster;	/* sectors/cluster */
	WORD reservedSectors;	/* reserved sector for BPB */
	BYTE numberOfFATs;	/* the number of file allocation tables */
	WORD rootEntries;	/* the number of root entries (512) */
	WORD totalSectors;	/* the number of secters for this partition */
	BYTE mediaDescriptor;	/* 0xf8: Hard Disk */
	WORD sectorsPerFAT;	/* number of sectors for FAT */
	WORD sectorsPerTrack;	/* sector/track (not used) */
	WORD heads;		/* heads number (not used) */
	DWORD hiddenSectors;	/* hidden sector number */
	DWORD bigTotalSectors;	/* total sector number */
	BYTE driveNumber;
	BYTE unused;
	BYTE extBootSignature;
	DWORD serialNumber;
	BYTE volumeLabel[11];
	BYTE fileSystemType[8];	/* "FAT1?   " */
	BYTE loadProgramCode[448];
	WORD sig;		/* 0x55, 0xaa */
};


ここで重要なのは、以下の情報です。

bytesPerSector
セクタあたりのバイト数。SDカードなら通常512です。フロッピーや昔のハードディスクで256の場合があります。SD-OSではここが512でない場合は異常と判断しています。以降、この値は512として話を進めます。

reservedSectors
BPBに割り当てられているセクタ数。BPBのセクタ位置にこの数を足すと、FATのセクタ位置が求まります。

sectorsPerFAT
1つのFATに割り当てられているセクタ数。

numberOfFATs
FATの数。通常FATは2組あるので、この数値は2です。FATのセクタ位置にsectorsPerFAT * numberOfFATsを足すと、ルートディレクトリエントリのセクタ位置が求まります。

sectorsPerCluster
1クラスタに含まれるセクタ数。FATではデータの格納位置をクラスタ番号で管理するので、この数は重要です。

totalSectors, bigTotalSectors
総セクタ数。totalSectorsは16ビットしかないので、約32MBまでしか表せません。これが昔あった「32MBの壁」ですね。これより大きな場合はtotalSectorsは0で、bigTotalSectorsに総セクタ数が入っています。この数値は32ビットなので、2TBまで表せます。あくまでセクタ数の話で、他にクラスタ番号の制約があるので話はそんなに簡単ではないですが。

rootEntries
ルートディレクトリのエントリ数。すなわちルートディレクトリに入れることのできるファイル・ディレクトリの数です。FAT16まではこの数値は512固定となります。

FAT

BIOS Parameter Blockの次にはFATがあります。ここには、「あるクラスタの次に格納されているデータ位置を示すクラスタ番号」が入っています。そのクラスタ番号は12/16/32ビットで表され、これがFAT12/FAT16/FAT32と言われる所以です。

FAT12だとクラスタ番号は0x000~0xfff=4096個あり、各エントリは12ビットなので、FATの最大サイズは

4,096(エントリ) * 12(bit) / 8(bits/byte) = 6,144バイト = 6kB = 12セクタ

となります。FAT16なら、

65,536(エントリ) * 16(bit) / 8(bits/byte) = 131,072バイト = 128kB = 256セクタ

あります。実際のサイズはBIOS Parameter BlockのsectorsPerFAT * bytesPerSectorで求めます。FATが占めるディスク領域は、やはりBIOS Parameter BlockのsectorsPerFAT * numberOfFATsで求められます。

各エントリは以下のような意味を持っています(FAT12/FAT16)。

0x000 / 0x0000
未使用
0x001 / 0x0001
予約
0x002 - 0xff6 / 0x0002 - 0xfff6
現在のクラスタの次のデータが格納されているクラスタ番号
0xff7 / 0xfff7
不良クラスタ
0xff8 - 0xfff / 0xfff8 - 0xffff
現在のクラスタでデータが最終することを示す

与えられたクラスタ番号に相当するFATエントリを見つけるには、

FAT開始位置 + クラスタ番号 * クラスタエントリの大きさ

となります。16ビットFATなら、2バイトごとにリトルエンディアンで格納されているので話は簡単です。

00000000: F8 FF FF FF 12 34 56 78 ...
エントリ: 00 00 11 11 22 22 33 33 ...
上記の場合、クラスタ番号0のエントリは 0xfff8、クラスタ番号3のエントリは0x7856です。

12ビットFATでは、各エントリが1.5バイトと半端なので、ちょっと計算が必要となります。たとえば、FATが開始位置から以下のようになっているとします。

00000000: F8 FF FF 12 34 56 78 9A ...
エントリ: 00 10 11 22 32 33 44 54 ...
ここで、クラスタ番号0(偶数番号)のエントリは1バイト目と2バイト目の下位4ビットを合わせて F8 F -> 0xff8 となります。
クラスタ番号3(奇数番号)のエントリは5バイト目の上位4ビットと6バイト目を合わせて 3 56 -> 0x563 となります。
やはりリトルエンディアンなので注意してください。

Root Directory Entry

FATの次に格納されているのがルートディレクトリエントリです。
Root Directory Entryの開始セクタは、以下で求められます。

FATの開始セクタ + sectorsPerFAT * numberOfFATs
FAT12/FAT16ではエントリ数が512個固定(BIOS Parameter BlockのrootEntries)となっています。

ディレクトリの1エントリは32バイトなので、Root Directory Entryが占めるディスク領域は、512 * 32バイトで、32セクタです。エントリの詳細については以下で説明します。

Data Area

Root Directory Entryの次からはデータエリアとなります。
データエリアの開始セクタは、以下で求められます。
Root Directory Entryの開始セクタ + 32 * rootEntries / bytesPerSector
通常rootEntriesおよびbytesPerSectorはともに512なので、結局、

Root Directory Entryの開始セクタ + 32
ということになります。

FATファイルシステムでは、データ領域はディレクトリエントリまたはファイルデータそのものです。

データはクラスタ単位で記録されています。あるクラスタを使い切ったら、そのクラスタ番号に対応するFATのエントリを参照することで、次のクラスタ番号を求めることができます。次のクラスタ番号が0xff8(FAT12)/0xfff8(FAT16)以上なら、次のクラスタはなく、そこでファイルやディレクトリのデータが終わることを意味します。

クラスタ番号に対応するセクタ番号を求めるには、最初の2つのクラスタ番号が予約されているので、

 (クラスタ番号 - 2) / sectorsPerCluster + データエリア開始セクタ
となります。すなわち、データエリアの最初のクラスタ番号は2です。

Directory Entry

Root Directory Entryまたはデータエリア内でディレクトリを表す場合、各ディレクトリエントリは32バイト固定の定義でできています。その構造は以下のとおりです。

struct DirEntry {
	unsigned char name[8];		/* name */
	unsigned char extension[3];	/* extension */
	BYTE attribute;			/* attribute [arc dir vol hid sys ro] */
	BYTE reserved;
	BYTE createTimeMs;		/* for VFAT */
	WORD createTime;		/* for VFAT */
	WORD createDate;		/* for VFAT */
	WORD accessDate;		/* for VFAT */
	WORD clusterHighWord;		/* for FAT32 */
	WORD updateTime;
	WORD updateDate;
	WORD cluster;			/* start cluster number */
	DWORD fileSize;			/* file size */
};
重要なフィールドは以下のとおりです。

name, extension

ファイル名と拡張子です。
最初のバイト(name[0])が0であれば、それはディレクトリの終了を示し、0xe5であれば、削除されたエントリを示します。

nameには"."および".."のエントリが存在し、それぞれカレントディレクトリ、親ディレクトリを表します。これらのエントリにも、以下に出てくるクラスタ番号は正しく格納されているので、普通のディレクトリエントリとして扱うことが出来ます。

attribute

各ビットがこのファイルエントリの属性を格納します。MSB -> LSBの順で、

N/A N/A Archive Directory Volume Hidden System Readonly
となります。VFATのロングネームエントリの場合、ここが0x0fとなります。VFATのファイル名は32バイトでは表しきれない場合があるので、複数のディレクトリエントリを使って表現することが可能です。
ロングネームを持つファイルは、常にMS-DOS互換形式のエントリも持ちます。たとえば、

a long file name.long extension
というファイル名は、VFATのエントリ(attribute=0x0fのエントリ)を1個以上持ち、かつ

ALONGF~1.LON
というエントリも格納されているので、こちらのみでもファイルの識別は可能です。
実際、SD-OSではattributeが0x0fのものは無視します。

cluster

このファイルの最初のデータが格納されているクラスタ番号です。ファイルを読み込む場合は、このクラスタからデータを読み込みます。

fileSize

ファイルサイズがバイトで入っています。ここが32ビットなので、FATで格納できる最大ファイルサイズは2^32=4GBということになります。このエントリがディレクトリの場合は、常にサイズは0です。

大体このくらいで、FATは終わりです。FAT32やVFATはサポートしませんが、これでFAT対応のドライバを書くことができるでしょう。

なお、この記事はFAT FS フォーマットの実装についての覚え書きからかなりの情報をいただきました。ありがとうございます。

Computer Dummies!

| No Comments | No TrackBacks
「私は何もしてないのに、壊れたんです!」
私も数え切れないほどこの言葉を聞きましたが、恐らくRod Shelley氏の足元にも及ばないでしょう。彼はいったい何度聞いたことでしょうか。

「インターネットでお買い物しようと思ったから、パソコンにVISAカードを入れたんですが、買えないんです!このコンピュータ壊れてます!」

おそらく、身近な人たちのコンピュータのサポートをして数々のトラブルを体験して方は多いでしょうが、Rod Shelley氏の記録は貴重です。ぜひ技術サポートの悪夢(CNET Japan)をみて抱腹絶倒してください。

それにしても、数え切れないほどたくさん同じショートカットを作ってしまったり、数え切れないほどたくさん同じものをインストールしてしまったりしている例はよく見ますね。これってOS自身の欠陥じゃないでしょうかね。いい加減自動で検出してくれてもよさそうなものですが。

私自身も「電源が入らないんです!」と言われて見てみたらコンセントが挿さってなかったり、「インターネットができないんです!」と言われて見てみたらモデム(当時)がなかったり、楽しい経験がいろいろあります。写真を撮っておけばよかったですね。ビデオだったらもっと面白かったかも。
コアなゲーマーへの製品というのは常に一般人の常識を超えて宇宙のはるか彼方へすっ飛んでいますが、デス・スターまですっ飛んでしまったのはこれが初めてでしょうか?

emperor.pngその名も"Emperor Workstation"。この手のものに$39,950(Macなら$41,950)という金を惜しまず拠出する趣味人は世の中におそらくいるでしょうが、問題は「エンペラー」という名前にしてはちょっとコンピュータまわりのスペックが貧弱なことでしょうか。

プロセッサはFSB 800MHzのCore 2 Duo(Quadではない!) 2.6GHz、メモリは4GB、HDDは500GB2台と今の時代では標準的です。モニタがSXGAの19インチ3台というのは、今日び少し広い画面が好きな人なら普通に装備するでしょう。私もマルチモニタが好きで、以前仕事でUXGAの20インチ2台を使っていたことがあります。おそらく8台以上の画面を駆使する人はデイトレイダーだけではないでしょう。

そして、いつの時代もこの手の製品の宿命である「あっという間に時代遅れ」という危険性を孕んでいます。いつだったか、ハードオフでえらく仰々しく大型のドライブゲーム用コンソールが投げ売られていたのを見たことがあります。400万円の行く末があれではあまりに哀れです。

ちなみに開発元はカナダのNovelQuestという会社。他にもヘッドバンドをつけて脳波で操作するコントローラなど、ユニークなものを作っているようです。

パソコン用OSの意義

| No Comments | No TrackBacks
WebがプラットフォームとなったWeb 2.0の時代に、OSとはどのような意義を持つのでしょうか。MicrosoftはWindowsの次のメジャーアップグレード"Windows 7"を2010年にリリースする予定とのこと。

Tim O'Reillyが2004年に提唱した"Web 2.0"概念の中で、最初に登場するのが"The Web As Platform"です。プラットフォームとしてのWeb - すなわち、これまでのOSのような役割をWebが果たすというわけです。

既に世の中にはWebブラウザのみで利用できるサービスがあふれています。これらはWebブラウザとインターネット接続環境のみあれば利用できるため、ユーザが利用している機器の固有性はあまり重要ではなくなりました。たとえば、10年前にはお店でソフトウェアを買うときには「Windows 95対応」「MacOS 8対応」などという文字を確認していたものですが、今やブラウザにURLを入れるだけです。携帯電話などの流行もこの傾向を後押ししています。

相変わらずソフトウェアは販売されています。しかし人々は、ソフトウェアを買うのではなくサービスを買うということに既に気づいています。「なんだかよく判らないが流行ってるから買う」といったWindows 95が販売されたときのような馬鹿騒ぎはもう起きないでしょう。

そのような流れの中で、巨大な投資をして「パソコン用」OSを作る意義がどれだけあるのか、Microsoftは問われているのではないでしょうか。

SDカードでSSDとはいうものの...

| No Comments | No TrackBacks
私が使っているパソコンはHDDが1.8インチなので、SSDには並々ならぬ興味があります。

ただ、安価なものはMLC(Multi Level Cell)であることが多く、転送速度が遅かったり書き換え回数が少なかったりという難点があります。また、SDカードのものは概してCFよりも転送速度が遅く、HDDの置き換えには適しません。ただ、このアプローチは悪くないですね。SDHCを6枚挿してストライピングにより容量・速度を稼ごうというわけです。

問題は、私のPCがいわゆる「東芝型1.8インチPATA」という微妙な世代の1.8インチHDDを採用していることですね。一時期かなり採用があったこのインタフェースも、今ではすっかりZIFやSATAに取って代わられてしまいました。80GBを最後に新製品も出ておらず、その80GBのMK8007GAHも既に生産完了のようで、市場では高値安定しています。

この製品も2.5インチ、SATAインタフェースです。1.8インチバージョンを作ることは搭載SDカードの枚数のみの話で難しくないでしょうが、PATAへの対応、ましてや東芝タイプへの対応は望めそうにもありません...。

SDカードドライバの設計(2)

| No Comments | No TrackBacks
SDカードのファイルシステム

SDカードはファイルシステムにFATを採用しています。ですが実は、FATであることは必須ではありません。SDカードの低レベルインタフェース仕様のみを見れば、単なるセクタ単位の入出力なので、他のファイルシステムを入れることも可能です。
セクタサイズは512バイト(2GBのカードなどでたまに例外があります)なので、効率的な読み込みのためには最低512バイトのバッファが必要です。書き込みをする場合は、フラッシュメモリの特性である消去単位(数kB~数100kB)を考慮しないと性能や寿命の問題が出るのでさらに面倒ですが、ここではとりあえず読み込みだけを考えます。

ただ、現在販売されているSDカードはすべてFATフォーマット済みですし、パソコンを始めとしてデジタルカメラや携帯電話に至るまで、FATをサポートしているので、他のフォーマットを採用するのは相互運用性の面からは現実的ではありません。

ちなみに、デフォルトでMMC/SDカードは12ビットまたは16ビットFAT、SDHCカードは32ビットFATとなっています。通常はVFATとなっており、ロングファイルネームに対応しています。

さてそのFATですが、情報が少なく、実装はなかなか大変です。さらにZ80上での実装となるとほとんどWeb上には例がありません。AVRなんかだとMP3プレイヤーの実装例などがあるので比較的ソースを見つけることができるのですけどね。

そしてそのソースなのですが、バイト配列に対してオフセットを直接指定してアクセスしているものがほとんどで、非常に読みにくいです。そこで、今回は構造をきちんと定義した上で実装することからはじめました。ただこれも良し悪しで、gccなどの進化したCコンパイラならいいのですが、Z80で使えるsdccなどだと構造体の使い方によっては効率の悪いコードを吐き出します。実装ではこの辺も勘案してありますが、ここでは綺麗な世界(?)で説明します。

FATの構造

セクタ0: Master Boot Record

最初のセクタはいわゆるMBRです。SDカードに限らず、フロッピーディスクやハードディスクなど、ほとんどすべてのブロックデバイスは、初期化後に初めてカードを読み込むときは必ずセクタ0を読み込んで、基本情報を得ます。FATの場合、その構造は以下のようになっています。

struct MasterBootRecord {
BYTE checkRoutineOnX86[446];
struct {
BYTE bootDescriptor; /* 0x80: bootable device */
BYTE firstPartitionSector[3]; /* 1st sector number */
BYTE fileSystemDescriptor; /* FAT type */
BYTE lastPartitionSector[3];
DWORD firstSector; /* BPB sector number */
DWORD numberOfSectors;
} partitionTable[4]; /* up to four partitions per drive */
WORD sig; /* 0x55, 0xaa */
};


checkRoutineOnX86は、主にx86 PCのBIOS向けと考えていいでしょう。PCはフロッピーであろうがハードディスクであろうが、ブート可能なデバイスを読み込んだときにここにあるコードを実行して、初期化を進めるようです。SDカードは普通ブートデバイスではないですし、x86に限った話でもないのでここの情報にはあまり意味はありません。

この中で実装に重要な情報は以下です。

partitionTable
パーティションごとの情報を格納する。ひとつのディスクには最大4つのパーティションが格納できるので、この構造体も4つある。
firstSector
パーティションの最初のセクタ、BIOS Parameters Blockの位置を表す。通常SDカードはパーティションは1つしかないので、partitionTable[0].firstSectorを見ればよい。
sig
シグネチャで、0x55, 0xaaの順番で格納されている。これはレコードの正当性を判断するのに使う。

それから、fileSystemDescriptorというフィールドがあります。これはパーティションのFATの種類を以下のように表します(一部)。

0x01:FAT12
0x04または0x06:FAT16
0x0b:FAT32
0x0c:FAT32 (int32拡張)
0x0e:FAT16 (int32拡張)

FAT32は無視するとしても、この0x0eが曲者です。実は、Windows XPなどで容量の小さなSDカードをフォーマットすると、実際にはFAT12でフォーマットされているにもかかわらず、fileSystemDescriptorは0x0eになります。そのため、実際にはこのフィールドだけではFATの厳密な種類を判別できません。

あとで説明しますが、FAT12とFAT16ではデータアクセス時のFATの読み方が違うため、厳密に判定できないと問題です。そのためこのフィールドは使えません。市販されているSDカードをそのまま使う分には問題ないのですが、Windowsでフォーマットした途端に読めなくなってしまいます。

なんだか長くなったので、続きは次回...。

SDカードドライバの設計(1)

| No Comments | No TrackBacks
拙作ののSDカードアダプタは、ジョイスティックポート経由で接続し、データのやり取りをすることができます。これから少しづつその説明をしようと思います。

AY-3-8910

PC-6001のジョイスティックポートはPSGであるAY-3-8910が持つ、2つの汎用8ビットパラレルI/Oポートに接続されていて、I/Oポート0xa0~0xa2でアクセスすることができます。この仕組みはI/Oポートの番号も含めてMSXと同じです。

0xa0:  レジスタラッチ
0xa1:  レジスタへの書き込み
0xa2:  レジスタからの読み出し

I/Oポートアクセスのために利用するAY-3-8910のレジスタは以下のとおりです。

0x07:    I/O選択
0x0e:    ポートA
0x0f:     ポートB

PC-6001の回路

ここまではAY-3-8910の仕様なのですが、PC-6001の回路では、0x0e、0x0fの2つのレジスタが直接2つのジョイスティックポートにマッピングされているわけではないので、話は少し面倒です。
基本的に、レジスタ0x0eは読み出し専用、レジスタ0x0fは書き込み専用です。そして、実際のジョイスティックポートへの出力は各ポートあたり3本のみです。SDカードのSPIモードでは、出力3本、入力1本を使うため、ぎりぎり間に合います。

レジスタ0x0fのビットは以下のようになっています。

*OE  SEL  J1-8  J2-8  J1-7  J1-6  J2-7  J2-6

OE:    出力イネーブル(0で出力可能)
SEL:  入力するジョイスティックの選択(1=ジョイスティック1, 0=ジョイスティック2)
Jx-y:   ジョイスティックポートxのyピンに出力するデータ

SDカードアダプタ

前述のように、SDカードをSPIモードで使う場合、出力3本、入力1本が必要なので、SDカードアダプタはジョイスティックポートのピンを以下のように割り当てています。

ピン1: 入力データ(MISOまたはDO)
ピン6: クロック(CLK)
ピン7: セレクト(CS)
ピン8: 出力データ(MOSIまたはDI)

データの読み書き

AY-3-8910を使う場合、レジスタのラッチを行ってからそのレジスタに対する読み書きをする必要があるので、通常のバイト指向のI/Oポートよりも入出力が面倒です。また、SPIモードの仕様として、1ビットのデータ入出力にクロックをPC側から入れてやる必要があるので、さらに手順が煩雑になります。まぁ、これのおかげでスピードをPC側の都合で速くも遅くもできます。
実際の入出力は以下のような感じになります。

準備
(ポート0xa0)<-0x07   レジスタ0x07をラッチ
A<-(ポート0xa2)        レジスタ0x07の値を読み込む
A = A | 0x80        
(ポート0xa0)<-A        レジスタ0x0eを入力、レジスタ0x0fを出力に設定

出力
(ポート0xa0)<-0x0f                     レジスタ0x0fをラッチ
(ポート0xa1)<-0x10または0x00    ビットを書き込み
(ポート0xa1)<-0x11または0x01    クロックを反転

入力
(ポート0xa0)<-0x0f      レジスタ0x0fをラッチ
(ポート0xa1)<-0x10     DIを書き込み
(ポート0xa1)<-0x11     クロックを反転
(ポート0xa0)<-0x0e     レジスタ0x0eをラッチ
A<-(ポート0xa2)         データ読みこみ、ビット0にデータが入る

ふぅ。これで1ビットの入出力です。8回繰り返してようやく1バイトの入出力となります。なお、データはMSB→LSBの順で転送します。

ロンドン地下鉄駅で配布している無料の新聞"Metro"7/22版でも取り上げられていましたが、映画"BATMAN"の最新作「The Dark Knight」の主演男優Christian Bale氏ロンドンで逮捕されたそうです。

わかっている範囲内では突発的な家族内での暴力ということで、大犯罪を犯したというわけではないようですが、タブロイド紙にとっては格好のスキャンダルネタとして餌食にされているようです。

The Dark Knightについては、敵役であるThe Jokerを演じたHeath Ledger氏が今年1月に薬物の過剰摂取で死亡しており、関係者は頭が痛いことでしょうね。

Fedora 9サーバの設定(メール篇:TLS)

| No Comments | No TrackBacks
昨日の設定では不足の部分があるので、追加します。

Postfixの設定

秘密鍵の生成: 1024ビットの秘密鍵を作成
% openssl genrsa -out /etc/ssl/server.key 1024
自己証明書の作成
% openssl req -new -x509 -key server.key -out /etc/ssl/smtp.pem

main.cfに関連設定を追加

# TLS configuration
smtpd_tls_cert_file = /etc/ssl/smtp.pem
smtpd_tls_key_file = /etc/ssl/server.key
smtpd_tls_loglevel = 3
smtpd_use_tls = yes
smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_cache

最後の設定はsdbmだとログにエラーが出る。
postfix/tlsmgr: fatal: unsupported dictionary type:sdbm

% yum install sdbm.so
しているが、まだ何かが不足しているようなので、btreeを使うことにしている。

Courier-IMAPの設定

パッケージをデフォルトでrpmbuildしてインストールすると、
imapd-ssl:/usr/lib/courier-imap/etc/shared/index: No such file or directory
というエラーがログに出る。これは単にパッケージングの問題のようで、
% touch /usr/lib/courier-imap/etc/shared/index
しておけばいいだけ。

自己証明書作成時は、いくつかインタラクティブな入力があるが、
Common Name (eg, your name or your server's hostname) []:
この質問で、利用するメールサーバーのDNS名を正しく入れないと、メールクライアントが「証明情報が食い違っている」というWarningを出したり、mewなどはそもそも接続してくれない。注意。

利用ポート

SMTPでは通常通り25を開けておく。SSLを有効にするには465を開ける必要がある。また、TLSもMUAによっては465を開ける必要がある。
IMAPでSSLを有効にするには993を開け、143は開けなくてもよい。TLSを有効にするには143を開ける必要がある。




Fedora 9サーバの設定(メール篇)

| No Comments | No TrackBacks
サーバをアップグレードするたびにいつもいつも忘れるので、備忘録を書くのですが、
そのファイルもいつもどこかに行ってしまって、結局調べなおしになります。

ここに書いておけば最悪Googleのキャッシュにでも残るであろう。:-)

私のサーバコンフィグレーションは、

Postfix
Courier-IMAP
です。posfixのインストールはFedoraインストール時にすんでいるので省略。Courier-IMAPは配布パッケージには含まれていないので自分で作ります。

Postfix設定

いらないものを削除

sendmail、CFがインストールされていたら、使わないので削除

main.cf

/etc/main.cf は以下を変更

mydomain = ドメイン名
myorigin = $mydomain
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
        mail.$mydomain, www.$mydomain, ftp.$mydomain
mynetworks_style = subnet
mynetworks = ローカルサブネット/24, 127.0.0.0/8
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
home_mailbox = Maildir/
# これをしないと踏み台にされる
smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
smtpd_sasl_auth_enable = yes
# Outlook対応(無くてもよい)
broken_sasl_auth_clients = yes

Courier-IMAP設定

インストール

rpmbuildするために、ホームディレクトリに
rpm/{BUILD,RPMS,RPMS/i386,SOURCES,SPECS,SRPMS}
を作っておく。

Courier-authlib関連
% yum -y install mysql-devel
% yum -y install expect
% yum -y install /usr/include/ltdl.h
% yum -y install postgresql-devel
% rpmbuild -tb courier-authlib-0.61.0.tar.bz2
% rpm -Uvh courier-authlib-0.61.0-1.fc9.i386.rpm
% rpm -Uvh courier-authlib-devel-0.61.0-1.fc9.i386.rpm

Courier-IMAP関連
% yum -y install /usr/include/fam.h
% yum -y install openldap-servers
% rpmbuild -tb courier-imap-4.4.1.tar.bz2
% rpm -Uvh courier-imap-4.4.1-1.9.i386.rpm

起動サービス設定

  1. /usr/lib/courier-imap/etc/ にあるファイルのうち、起動しないものの設定を変える。
    たとえば、
    /usr/lib/courier-imap/etc/pop3 で、
    POPD3START=NO
    とする。
  2. 起動するサービスは証明書ファイルを使うようにする
    たとえば、
    /usr/lib/courier-imap/etc/imap-ssl で、
    TLS_CERTFILE=/etc/ssl/server.pem
    とする。
自己証明書作成

上記で指定した証明書を作成する。
% openssl req -new -x509 -nodes -out /etc/ssl/server.pem -keyout /etc/ssl/server.pem

authdaemondが起動していることを確認する。

以上では、IMAP over SSLで接続できますが、実はTLSは有効になっていません。
設定不足なんでしょうが、まぁ一応セキュアになったのであとはゆっくりやりましょう。







毎日新聞社が陥った「紺屋の白袴」

| No Comments | No TrackBacks
この騒ぎにはつい今しがた気づきました。
あー、もう言葉も見つかりませぬ...。

日本に住んでないせいもあり、日常的に英語のサイトは読んでいますが、
「日本の」英語サイトはわざわざ読まないですからね...。

雪印食品やミートホープ、船場吉兆などがまずかったのは、事を起こしたことのみならず、
その後の対応のまずさにあるというのは既に常識だと思うのですが、
それを声高に報道してきた新聞社が同じ轍を飽くことなく踏みましたね...。

参照:
http://gigazine.net/index.php?/news/comments/20080721_mdn_mainichi_jp/
http://www.mainichi.co.jp/home.html

PC-6001ムービー技術情報

| No Comments | No TrackBacks
ムービー再生の技術情報です。
前提として、SDカードの技術情報が必要なのですが、こちらはまた別に書きます。

PMVフォーマットのベースは、ベタのビットマップファイルです。 というかむしろ、VRAMのイメージそのものです。 スクリーンモード2のセミグラフィックモードの対応は、エンコーダ側で変換を行っています。

PMVフォーマットでは、ムービーファイルの容量の低減と再生時の負荷低減を目指して、 run-lengthとフレーム差分の2種類の方法で圧縮をかけています。 この2つの圧縮アルゴリズムはフレーム内に混在可能です。 現在のエンコーダは、どちらも同じ圧縮率になる場合は、 再生時の負荷が低い(後述)フレーム差分を優先します。

バッファはオプションの設定により、 21.5kB(0x6a00-0xbfff)または29.5kB(0x6a00-0xdfff)使います。

ファイルフォーマット


PMVファイルは、ヘッダとボディから構成されます。ボディは圧縮していますが、可能な限り軽量にするため、CRCなどのエラーチェックは一切使いません。 ファイルの全体構成を以下の図に示します。

PMV file format

ヘッダはPC-6001のテープフォーマットや拙作のMP6フォーマットにあわせ、16バイトとします。定義は以下のようになります。現在のプレイヤーではversion, compression, imagetype, reservedは使っていません。

struct PMVHeader {
    char magic[4]; // "PMVt"
    unsigned char version; // verion, 1
    unsigned char compression; // compression method, 1
    unsigned char imagetype; // 0: gray scale bmp, 1: pbm, // 2: pbm w/o header, 3: semi-graphic(6)
    unsigned char fps; // frames per second
    unsigned short width; // frame width (pixels)
    unsigned short height; // frame height (pixels)
    unsigned short framesize; // maximum compressed frame size (bytes)
    unsigned char reserved[2]; // for future use
} __attribute__((packed));

圧縮技術

考え方

SDカードからのロード時間を短縮するため、ボディには圧縮をかけています。

今回ちょっと悩んだのが圧縮方法です。Deflateアルゴリズムのように、 比較的展開側の負荷を低減する方法は確立していますが、 それでもPC-6001には荷が重過ぎます。 そもそも標準のDeflateアルゴリズムははハフマン符号テーブルと 32kBのバッファを要求します。 また、ハフマン符号がビット単位の処理になるため、Z80では効率的に処理できません。

そこで、「バイト単位での処理」「展開用のバッファを要求しない」 ということをを基本にしました。 すると、おのずから方策は限られてきます。

まずはRLE(Run-Length Encoding)による圧縮を行いました。 要は、連続する同じバイトはまとめるということです。 モノクロの画像データは連続する白または黒が結構あるため、 これでもそこそこ圧縮が可能となります。

次に、動画における「連続するフレームは似たような画像である」という性質に着目します。 直前のフレームと同じデータなら、その部分の情報は記録しなくていいわけです。

圧縮の実際

では具体的に見ていきましょう。

まずは、データ内に特別な値である「マジック」を用意します。

読み込んだバイトがマジックでないなら、それは純粋にデータです。 現状マジックは1種類しかないので、 256種類のデータのうち255種類は普通のデータとして処理します。

1バイト目にマジックを見つけたら、専用の処理に入ります。2バイト目が0xffならフレームエンド、0xfeならマジックそのものです。 それ以外の場合、0x00-0x03ならフレーム差分、0x04-0xfdならRLE圧縮と解釈します。フレーム差分、RLEの場合は3バイト目を読みこみます。

<magic> 00 <length>: same as the previous frame (0 <= length <= 255)
<magic> 01 <length>: same as the previous frame (256 <= length <= 511)
<magic> 02 <length>: same as the previous frame (512 <= length <= 767)
<magic> 03 <length>: same as the previous frame (768 <= length <= 1023)
<magic> 04-fd <byte>: run-length for a byte (4 <= length <= 253)
<magic> fe: <magic>
<magic> ff: end of a frame
other: byte

フレーム差分の場合は2バイト目と3バイト目を合わせて長さとします。これにより、最大1023バイトまでの前フレームと同じバイト列を3バイトで表現できます。RLEの場合は3バイト目は連続しているデータです。上記のように、253バイトまでの連続する同じバイト列を3バイトで表現できます。

マジックはいくつかのムービーソースから出現頻度の低い値を統計的に算出し、現在は0x55を使っています。

プレイヤー


プレイヤーはSDカードからPMVファイルのヘッダをロードし、ファイルの正当性(最初の4バイト, "PMVt")をチェックします。 その後画面サイズを元に画面モードを設定し、fps値から1フレームにかけられる時間を算出し、保存します。

バッファリング

pmvプレイヤーは、 最低でも1画面分の情報をメモリに読み込んでから画面に展開します。 そのためにヘッダのframesizeを使います。 フレームの描画実行中はロードしないため、 描画しようとしたときにframesize分をロードしていないときは、 ロードを実行します。 バーストモード時は、 framesizeにかかわらず常に読み込める限り読み込んでから描画します。

圧縮フォーマットから考えれば、 理論的には実際に必要な1フレーム分のデータ量は、 1画面のVRAMの量を超すことがありえる(実際はまずありえませんが)ので、 エンコード時に正しくframesizeを設定しておくことが必要です。 エンコーダは実行時に圧縮したすべてのフレームの中での最大値を検出し、 これをframesizeに設定します。 このため、プレイヤーはこのframesizeの値以上のバッファがあれば再生が可能です。 実際には、現在のSDカードドライバはセクタ単位のアクセスを行うので、 バッファ量は512バイト境界で切り上げが必要です。

バッファはラウンドロビンで使います。バッファを大きく取るほど、バースト再生時のパフォーマンスがあがりますが、バーストモードでない場合は意味がありません。

バーストモードの場合、 ファイルをすべて読み終えた後はバッファにデータが残っていますので、 終了する前にこれら読み込み済みのフレームをすべて描画してから終了します。

画面スイッチング

デフォルトでは、プレイヤーはフレームごとに、 PC-6001のスクリーン2(0xe000-0xf9ff)とスクリーン3(0xc000-0xd9ff)を交互に描画し、 描画し終わってから画面を切り替えることによってちらつきを抑えます (昔のゲームなどにもよく使われたテクニックですね)。 一方、画面スイッチオフの指定がある場合はスクリーン2のみに描画します。 128x96程度の解像度だと、これでもそれほどちらつきません。 64x48ではデフォルト(スイッチあり)のほうがむしろムダですね。

描画自体はいたってシンプルで、バッファに読んであるデータを展開して、 VRAMに転送するだけです。 その際、上述した2種類の圧縮アルゴリズムにより、マジックを読んだ際には 展開を行います。

画面描画


フレーム差分圧縮がかかっているデータは、 画面スイッチングを使わない場合は書き換えを行わず、 VRAMの描画対象メモリポインタを進めるのみです。 解像度が128x96だと書き換え量が最大1.5kBなので、 画面スイッチを行わなくてもそれほどスキャンラインは見えません。 そのため、似たようなフレームが続くと、 ほとんどメモリポインタを進めるだけで1フレームの描画が終わってしまうため、 圧縮効果と高速描画の両方が期待できます。

256x192(6kB)だとさすがに重いので、 ちらつきを抑えるために画面スイッチが必要です。 現在のフレーム差分圧縮は1フレーム前の情報しか使わないため、 画面スイッチによって2フレーム前の情報が残っている場合は利用できません。 そのため、メモリポインタを進めるだけではなく、 前フレームからのコピー(LDIR)を行っています。

これは、2フレーム前の情報を元に圧縮すればいいだけなのですが、 ひとつのムービーファイルで対応するのはデコード時の負荷が厳しいので、 今のところ対応していません。 まぁ、ムービーファイルそのものを別にしてしまえば、 対応はそれほど難しいものではありません。

fpsはフレーム間の描画にかけられる時間を算出するのに使います。 フレーム描画時にPC-6001の2msecタイマを読んで、 時間が余っていればbusy waitします。 wait終了時の時刻を元に次のフレームまでのタイミングを設定するので、 いったん遅れたら無理に取り戻すことはしません。 それをしてしまうと、SDカードからの読み込みで遅れた分を取り戻そうとして、 最高速で描画してしまうためです。

バッファ量


プレイヤーが使えるバッファ量は、以下のようになります。 バーストモード時はこの量を最大限取って実行します。 通常モード時はムービーヘッダのframesizeのバッファがあればいいので、 バッファを大きくしてもあまり意味はありません。

プログラム実行モード
シングルスクリーン デュアルスクリーン
mp6 (ROMあり)
29.5kB 21.5kB
p6 (ROMなし)
16.5kB 8.5kB

その他
あと何があったかな。続く...?


PC-6001で動画再生

| No Comments | No TrackBacks
SDカードの大容量とせっかく作ったSD-OSを生かしたアプリケーションとして、 動画再生を試してみました。名づけて、Pico Movie(ベタベタ)。
1. 現在のバージョン

とりあえずBELUGAのカートリッジを利用していますが、 RAMのみで動作させることも可能です。 取れるバッファの大きさが変わるので、後述のバーストモードで違いが出ます。 もちろん、SDカードアダプタは必須です。

準備ができ次第、ダウンロード可能にする予定ですので、少々お待ちください。
2. ツール

以下のツールから構成されます。

2.1. 専用エンコーダ(pmvenc)

Windows上でffmpegと一緒に動作させ、 ムービーの独自形式(pmv形式)へのエンコードを行います。

2.2. プレイヤー(pmv)

SD-OSのmp6フォーマット(SD-OSの実行バイナリ)の実行ファイルで、 pmv形式の再生を行います。

3. エンコード

3.1 エンコード方法


サポートしている解像度は以下のとおりで、すべて2色です。

  1. 64x48 (スクリーンモード2)
    % ffmpeg -i <ムービーファイル> -s 64x48 -f image2pipe -vcodec bmp [-ss <再生開始>] [-t <長さ>] [-r <fps>] tmp % pmvenc tmp -s [-r <fps>] [-t <カットオフ>] -o <pmvファイル>
  2. 128x96
    ffmpeg -i <ムービーファイル> -s 128x96 -f image2pipe -vcodec bmp [-ss <再生開始>] [-t <長さ>] [-r <fps>] tmp % pmvenc tmp [-r <fps>] [-t <カットオフ>] -o <pmvファイル>
  3. 256x192 (スクリーンモード4)
    % ffmpeg -i <ムービーファイル> -s 256x192 -f image2pipe -vcodec bmp [-ss <再生開始>] [-t <長さ>] [-r <fps>] tmp % pmvenc tmp [-r <fps>] [-t <カットオフ>] -o <pmvファイル>

pmvenc -hでヘルプが出ます。現在プレイヤーではサポートしていないグレイスケールやpbmの出力もおまけでつけてあります。

ポイントはカットオフで、入力されたピクセルの輝度(グレイスケール)がこの数値より大きなもののみビット出力します。 輝度は、入力映像の24ビットRGBの平均値で、0〜255です。 そのため、設定数値が高いほど暗い映像になります。

3.2. 使用例とレポート


エンコードの実際を以下に示します。

% ffmpeg -i source.mpg -s 128x192 -f image2pipe -vcodec bmp -r 15 tmp.bmp
% pmvenc tmp.bmp -r 15 -o movie.pmv
size= 128x 96, 522941 bytes( 27.02%), 1260 frames
bpf= 415.03(max: 1134), fps= 15 white= 37.42%

pmvencの出力は以下を表しています。

  • 解像度が128x96
  • ヘッダ部分(16バイト)を除く映像のエンコード後のサイズが522,941バイト
  • 圧縮率が27.02%(元ファイルに対して27.02%になった)
  • フレーム数が1260
  • 1フレームあたり平均415.03バイト
  • 最大サイズのフレームが1134バイト
  • ヘッダに15fpsを指定
  • white pixelsが全体の37.42%あった

モノクロエンコードによりどのくらいの映像になったかの参考にするため、エンコーダはホワイトレシオをレポートします。

4. 再生
4.1. プレイヤーの実行


あらかじめ、SDカードにpmvコマンドとエンコード済みムービーファイルを入れておきます。
PC-6001起動後、sdコマンドでSD-OSに入り、以下でムービーを再生します。 ちなみに、プログラム内で画面のアトリビュートや表示をムービーファイルや オプション指定に従って完全にコントロールするため、 "How Many Pages?"に対するページ数は何でもかまいません。

% pmv % ex pmv% <pmvファイル> [オプション]

サブディレクトリのpmvファイルを指定する場合は、ex実行前にcdでカレントディレクトリを移動してください。pmvファイル指定時に直接/相対ディレクトリを指定することはできません。

途中で止めるときにはSTOPキーが使えます。再度実行するときは、sdコマンド実行後、以下のようにします。

% ex 6431 pmv% <pmvファイル> [オプション]

4.2. プレイヤーのオプション

オプションは以下の3種類をサポートしています。 対応していないオプション文字を入力しても無視されます。

s (シングルスクリーン指定)
通常、これを指定しなければ2画面使って裏画面に描画するのでより滑らかですが、バッファが21.5kBに減ります.
b (バーストモード指定)
フルバッファモード指定で、指定しないとバッファが満杯になるのを待たず、 画面がロード出来次第再生します。 現在のSDカードアダプタのスピードが遅いので、 これだとロード時間が断続的に発生し、コマ送りのような映像になります。
c (CRT-OFF指定)
常にロード時はDMAを止めます。ロード実行中は画面が乱れますが、 bオプションと一緒に使うことにより、あとで結合編集する際に 「ムービー再生中ではない時間」がわかりやすくなります。
5. 今後の予定

5.1. SDカードドライバ

現在のバージョンのSDカードドライバがスタック領域を大きくとっているため、メモリの有効活用のため、pmvではそのスタックを塗りつぶしてしまいます。 そのため、いったん実行するとSD-OSに戻れなくなっています。 また、再度実行時に開始アドレス(6431)指定が必要です。 これらは、次のバージョンで改善予定です。

5.2. SDカードアダプタ

SDカードアダプタがジョイスティックポートを使っているため、 転送速度に限界があります。 現在、拡張カードにSDカードアダプタを実装するための開発を行っています。 拡張カード上にAVRを乗せ、FATの管理はこちらに任せるという方法をとる予定です。 こうすると、かなりの高速化が期待できます。

5.3. 手っ取り早く安く高速化?

AVRバージョンのアダプタは開発中ですが、拡張カードとして実装するために多少コストが上がります。まぁ、部品代は1000円くらいでしょうが。ただ、ROM/RAMカートリッジと共存させるためにPC-6011拡張ユニットが必要です。これはフロッピーインタフェースを無視すれば簡単に自作可能ですが、それでも面倒ですよね。実際私も持っていませんし...。

そのため、いくつか考え中です。
  • バイト単位I/Oによるストリーミング
  • プリンタポートを使う

まぁ、気長にやります。

6. その他

できれば音楽も一緒に再生したいのですが、 さすがにPSGでのPCM再生を並行して行うのはCPUパワー、 メモリの両方の面からつらいです。 音楽再生はAVRに任せてPC-6001本体と同期を取るようにするのが妥当でしょうかね。

技術情報は別に書きます。

SDカードアダプタの設計

| No Comments | No TrackBacks
PC-6001のジョイスティックポートに接続可能なSDカードアダプタを作成しました。

SDカードドライバ

ハード設計

手持ちの部品で作っているので抵抗とかがテキトーです。SDカードの仕様上は、DINとDATは50kohm〜100kohmでプルアップする必要がある ようですが、それだとLED電流が少なくなりすぎて光らなくなります。ちゃんとバッファを入れたりすればいいんでしょうが、面倒なのでこれでやっていま す。
Joystick-SDカードアダプタ回路図
サンハヤトのユニバーサル基板(ICB-86)上に実装しています。部品代は500円くらいでしょうか。

ATARI仕様のジョイスティックポートには通常のDSUB9ピンのシリアルケーブルだと、角が邪魔をして入りません。そのため、ケーブルはコネクタ部分の耳を切り落として挿入しています。


ソフト設計

ドライバは、Tiny-FatFsを参考にさせていただきましたが、以下のような事情から、ほとんど作り直しました。

  • Z80で利用できるSDCCコンパイラでコンパイルできるようにする。
  • PC-6001の容量の制限から、リードオンリーにする。
  • やはり容量の制限から、SDカードのみの対応とする。
  • やはり容量と、速度の制限から、シングルセクタ単位のアクセスとする。
転送速度

速度計測を行いました。32kBのファイルを用意し、読込み時間を計測したところ、およそ1.5kB/s程度です。手動計測なのであ まり正確に測れません。ドライバ内で割り込みを禁止する必要があるため、PC-6001のタイマ割り込みでは計測できないのです。

それでも、テープが1200bps=150bytes/sであることを考えれば、7倍の速度ということになります。PC- 6001mk2でフル実装の64kBいっぱい読んでも約40秒。PC-6001用の普通のゲームなら23kB以内なので15秒程度で読めます。 まぁ、我慢できる範囲ではないでしょうか。

ちなみに、よく知られているようにPC-6001の処理時間の大半はVDG MC6847(Video Display Generator)によるDMA転送に取られてしまいます。画面表示が乱れても良いなら、倍速読み込みも可能です。実際ムービープレイヤーではその手法をとっています。





ブログオープン

| No Comments | No TrackBacks
ようやくオンラインになりました。

HTML手書きが面倒なので、Movable Typeをインストールしました。
これまでの記事も少しづつこちらに移動させる予定です。
よろしくお願いします。