AVRの最近のブログ記事

先週基板ができたので、今日はジャンパを通しました。 およそ30本。 面倒ですが、全部手配線に比べたら圧倒的に楽です。 ちなみにUEWを使って半田面に配線したので、部品面は未だにスッカスカです。 ISP用に背の高いコネクタをつけたくらい。

P1000708.jpg

プログラミングはCPLD、tiny2313、mega88の3種類に必要です。 デバイスへの書き込みはこれだけのために用意してある古いノートパソコンを使います。 タダでもらったVAIOで、シリアル&パラレルポートを持っています。

なおこれ、液晶のヒンジが折れています。 ありあわせのネジで直しても直しても折れてしまうので、スピーカーに寄りかかった状態で使っています。

P1000706.jpg

CPLDは特に問題もなく終了。

tiny2313が、どうしてもDevice connection failedでうまくいかない...。 RS-232Cを疑ったり書き込みデバイスを変えたり、いろいろしてもなかなか見えず、仕方ないので試しに別のチップでやってみたら...できた。

見事にババ引いちゃったみたいです。 その間、電源取りに使っていたFT245Rのボードを壊してしまったみたいです(泣)。

mega88のほうは、まだプログラムのほうが古いままで、新しいアダプタに対応していません。

明日の引越し荷物到着の準備をしなきゃいけないし、今日はもう疲れたので、こちらは明日以降にしましょう...。

それでも、面倒なハードウェア部分はおおよそ終わったので一段落かな。


AVR Tiny2313をROMとして動作させられるかどうか、ここのところ試行しているわけですが。

どうも、20MHz動作でも必ずアクセスが成功するというわけではないようです。 ごく稀にですが、誤動作することがあります。

プログラムは以下のようになっています。

_loop:
	in	ZL, PIND	;1  7bit
	lpm			;3
	out	PORTB, r0  	;1
	rjmp	_loop		;2

ここからアクセス時間が最長になってしまう状況を考えると、こうなります。 すなわち、in命令で得られたデータはまだアドレス確定前で、その直後にアドレス確定、次のinでようやく正しいアドレスが取得できる状況です。 すると、正しいデータが出るまでの最長のクロック数は、以下のようになります。

3(lpm)+1(out)+2(rjmp)+ 1(in)+3(lpm)+1(out) = 11 (clk)

これを20MHzで考えると、1clkあたり50nsなので、550nsということになります。 これは最悪の場合で、通常はここまでかからないのですが。 ただ、プログラムを読んでいるので、一回でも読み間違えれば当然のように誤動作します。

PC-6001で使われているROMのアクセスタイムは、以下のようになっています。

  • BIOS(BASIC) ROM: uPD2364 Max. 450ns (2364-45)
  • Character ROM: uPD2332 Max. 450ns (2332-45)
  • UV-EPROM: uPD2732 Max. 450ns (互換品のHN462732のデータ)

やはりこの時代の製品は450nsあたりが最大のようで、550nsでは間に合わないことがあるようです。

上記のプログラムではrjmpが無駄にクロックを消費しています。 rjmpがなければ最悪でも9clk = 450nsで動作してくれます。 そのため、in~outをたくさん並べれば誤動作の可能性は低くなります。 でもメモリは有限ですからいつかはrjmpで元に戻る必要があるため、絶対誤動作しないとは言い切れません。

誤動作といっても、ごく稀のことなのでこのまま行くか、信頼性を上げるために別の手を考えるか、悩ましいところです。


先日のなんちゃってROMですが、Enable信号をモニタするのはスピード的に辛そうなので、バッファをつけてみました。

20090411-experiment.PNG

これだと、AVR側は入力されたアドレス線に対して出力するのみ。

_loop:
	in	ZL, PIND	;1  7bit
	lpm			;3
	out	PORTB, r0  	;1
	rjmp	_loop		;2

まさに「来たら打つ! 来たら打つ!」状態です。 実際の出力判断は244に任せていますので。

で、うまく動きます。 アドレスは7ビット分(A0-A6)なので、128バイトまでですが、ブートアップには十分な容量です。

ただ、ちょっと気になるのは、27C256(32kB UV-EPROM)を積むのとどちらがオトクかということです。 上記の回路はTiny2313(100円)+LS244(100円くらい)+XTAL+パスコン2個で、300円弱かかります。 一方、27C256は200円で買えます。

まぁ、244などジャンクを漁れば大量に出てくるので、実質買うことはないのですけど。 また、AVRはISPが使えるので、UV-EPROMよりは書き換えは楽ですね。

実際のSDカードインタフェース回路では、mega88も使うため、そこからのCLKO信号を入れることにより、XTALを省略することも可能かもしれません。

ただ、Tiny2313の仕様を見ると、外部クロックは16MHzまでとあります。 ためしにPC-6001の15.9744MHzでやってみたところ、誤動作しました。 微妙です。


こんなものを試作してみました。

20090407-experiment.PNG

ATtiny2313をROM代わりに使おうというものです。 アドレスバスのピン数の都合で、64バイトまでしかハンドルできません。 あとは全部イメージになりますが、起動時だけなので問題ないでしょう。

やはりスピードがかなり厳しく、(初代)本体からのクロック(15.9744MHz)では間に合いませんでした。

20MHzの水晶でやってみましたが、やはり連続アクセスには難があるようです。 暴走こそしないものの、読み込みデータが抜けたりして、安定しません。

AVRのプログラムは今のところ以下のようになっています。

.org	0x0000
	ldi	R_TMP, LOW(RAMEND)
	out	SPL, R_TMP
	eor	R_ZERO, R_ZERO
	ldi	R_FF, 0xff
	ldi	ZH, 0x02 	; data start from 0x0100
	out	DDRD, R_ZERO
	out	PORTD, R_FF
	out	PORTB, R_ZERO
_loop:
	out	DDRB, R_ZERO	;1 4-8clk until PortB output disabled
_wait1:
	;; wait for read request
	sbic	PIND, 6		;1/2
	rjmp	_wait1		;2
	in	ZL, PIND	;1 Z = 0x0200 + PIND (PIND < 0x3f)
	lpm			;3
	out	DDRB, R_FF	;1
	out	PORTB, r0  	;1  8-11clk until data appears on the bus
_wait2:
	;; wait until read request has been cleared
	sbic	PIND, 6		;1/2
	rjmp	_loop		;2
	rjmp	_wait2		;2

	.org	0x0100
	.db	0x41, 0x42, 0x04, 0x40, 0x3e, 0x40, 0xcd, 0x75, 0x10, 0xc9

割り込みを使うとビットが連続したピンが使えなくなるため、busy loopで待ったほうが速そうなので、そうしています。 なんとかAVR側のプログラムを高速にできないか考えているのですが、これ以上は考え付きませんでした。 プログラムエリアからバイトを読むlpm命令は3クロックもかかりますが、代わりにSRAMから読むにしても2クロック、しかも入力アドレスに0x60を加算する必要があるので速くなりません。

データバスに3-state bufferをつけてしまえば、ウェイトなしでアドレス入力から垂れ流しができるので十分高速になるとは思いますが。 しかもPD6をアドレス線に使えます。 まぁ、搭載チップがひとつ増えてしまうので、なるべく避けたいところではあります。

ちなみに、どうせCS2がイネーブルになるときはROM読み込みだろうということで、CS2を直接PD6に入れてみたりもしましたが、症状は改善しませんでした。


AVRで音声を出すことができたので、今度は映像信号に挑戦中です。

映像信号の詳細についてはこのへんに詳細な説明があるので、いろいろ調べながらトライしてますが、まだきちんと理解しているわけではありません。

基本的には、PWMを使って正確なタイミングで同期信号を出して、それに映像信号を載せればいいみたいです。 手元のジャンクには、PAL信号用のチップもある(イギリスはPALですしね)のですが、ここは勉強のためにも、まずは同期信号だけでもうまく出せればと思っています。

基本的には、水平ラインあたり63.555us(モノクロなら63.492us)を単位とし、水平ブランキング期間の10.9us中、4.7usの期間はLレベルにすればいいみたいです。 ただ、コンポジット信号にしたときのインタレース方法や等価パルス、映像信号の重畳方法などは良くわかっていません。 あちこち調べてると、重畳はワイヤードORのようですが。

ノンインタレース信号やRGB分離のほうがアルゴリズム的には簡単なのかも。

ちなみに、AVRで簡単におおよそのタイミングで同期信号を作って試してみていますが、いまのところうまく同期が取れているわけではありません。 ただ、ちらつくにせよ画面に何か出るので、思ったより映像信号の作成そのものは手の届かないほど遠いものではないということだけはわかりました。

ただ、電圧レベルが-40IRE(0V)から100IRE(1V)のアナログ信号なので、ちゃんと同期信号と映像信号で電圧をあわせてあげないといけないのかも。 特に、同期信号だけ出したら0V~3.3Vまでフルスイングしてしまうので、その辺の調整は必要でしょう。

いずれ、ちゃんと映像出力をして音声と両方あわせてみたいですね。 でも、AVRの処理能力ではカラーは難しいかもしれません。 色差信号を計算することを考えると、周波数さえ満たせれば、純粋にデジタルRGBが楽なのかしらん。といってもデジタルRGBのモニタなんて持ってないので、アナログRGBになるかな。こちらで一般的なSCARTはどうなっているんだろう?


ATtiny2313でのステレオサウンド再生テストです。

FT245RLモジュールと、ATtiny2313を使い、きわめて簡単な回路でやってみました。 データは単に垂れ流しているだけです。 これで少しだけBitBangモードの使い方がわかったような気がします。 回路はこんな感じ。 端子はすべてFT245RLのものです。

20090323-experiment.PNG

ポートが8ビットなので、LSBの1ビットをLRチャネルの選択に使い、実質7ビットですが、サンプリング周波数は結構高いので、それなりに聞こえます。 ただ、結構プチプチとDCっぽいノイズが乗りますね。 LPFのカットオフはデューティ比50%の時に約16kHzなのですが。

AVRを20MHzで使っているので、PWMの周波数は次のようになります。

20,000,000 / 256 = 78,125 (Hz)

そこで、ffmpegを使い、次のようにして8ビットステレオファイルを作ります。

ffmpeg -i ソースファイル -f rawvideo -acodec pcm_u8 -vn -ar 39062 出力ファイル

この出力ファイルを次のようなソースで吐き出せばokです。

#include <windows>
#include <stdio.h>
#include <sys/unistd.h>
#include "ftd2xx.h"

int main(int argc, char **argv) {
  FT_HANDLE ftHandle;
  FT_STATUS ftStatus;
  DWORD written;
  unsigned int count = 0, i;
  unsigned char val[4096];
  FILE *in;

  if (argc < 2) {
    fprintf(stderr, "Usage: %s \n", argv[0]);
    return 1;
  }
  if (!(in = fopen(argv[1], "rb"))) {
    fprintf(stderr, "Error: opening input: %s\n", argv[1]);
    return 1;
  }
  ftStatus = FT_Open(0, &ftHandle);
  if (ftStatus != FT_OK) {
    fprintf(stderr, "Error: opening USB device\n");
    return 1;
  }
  ftStatus = FT_SetBitMode(ftHandle, 0xff, 1);
  if (ftStatus != FT_OK) {
    fprintf(stderr, "Error: initializing USB device\n");
    return 1;
  }
  ftStatus = FT_SetUSBParameters(ftHandle, 4096, 0);
  if (ftStatus != FT_OK) {
    fprintf(stderr, "Error: setting USB buffer size\n");
    return 1;
  }
  ftStatus = FT_SetBaudRate(ftHandle, 78125 / 2 / 2);
  if (ftStatus != FT_OK) {
    fprintf(stderr, "Erro: setting baud rate\n");
    return 1;
  }
  while (1) {
    if (!fread(val, 4096, 1, in)) {
      break;
    }
    for (i = 0; i < 4096; i++) {
      if (i % 2) {
	val[i] |= 1; // right
      } else {
	val[i] &= 0xfe; // left
      }
    }
    FT_Write(ftHandle, val, 4096, &written);
    if (written == 0) {
      break;
    }
  }
  FT_Close(ftHandle);
  fclose(in);
  return 0;
}

AVR側はきわめて簡単で、次のようなソースになります。

#include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER0_OVF_vect) {
  unsigned char val = (PINB & 0xfb) | (PIND & 0x04);
  unsigned char channel = val & 0x01;
  val &= 0x0fe;
  if (channel) {
    OCR0A = val; // right
  } else {
    OCR0B = val; // left
  }
}
int main() {
  PORTB = 0xff;
  PORTD = _BV(PD2);
  DDRB = _BV(PB2);
  DDRD = _BV(PD5);
  TCCR0A = 0b10100011; // OSCA enable, OSCB enable, fast PWM
  TCCR0B = 0b00000001; // OSC=fCPU
  TIMSK = _BV(TOIE0);
  sei();
  while (1);
  return 0;
}

FT245RLのボーレートの設定が、サンプリング周波数の半分(78125/4)なのが謎。 これをサンプリング周波数と同じに設定してしまうと、再生が倍速になってしまいます。


CPLDも書けるようになったので、そちらはブレッドボードからユニバーサル基板へ実装を移したあといったん終了。

んで、秋月のFT245RLモジュールをテストしました。 とりあえず何をしたってわけでもないですが、秋月のサンプルプログラムを動かしてみました。

20090322-FT245RL.PNG

自分でUSBデバイスをいじれるってのはなかなか良さそうですね。 BitBangモードはターゲットとどうやって同期を取っているかなど、まだ良くわかってないことがあるので、その辺はこれから勉強です。

ATtiny2313でテストするときも、今まではPC-6001からデータを入れていましたが、USBを使えると一段手間が減って便利そうです。 ていうか、実験でPC-6001は既に電源とデータ入出力器としての役割しか果たしてなかったりします...。 結構便利なんですけどね。


パラレルポートからXilinxのCPLDに書き込めるようにJTAG用の回路を作ったのですが、これがうまく動いてくれません。 回路は次のようになっています。


20090321-CPLDwriter.PNG

WebPack 9.2iのiMPACTから通信させようとすると、デバイスIDがオール0またはオール1になってしまいます。 明らかに通信不良です。

AVRのISPケーブルの場合は何の問題もなく一発で動いていたので、なんともわかりません。 単にレベル変換をしながらD0~D2をTDI, TCK, TMDに接続し、TDOをSLCT入力に接続しているだけなんですけどね。

ちなみに今回は3.3Vへの変換にSHARPのPQ3RD23を使いました。 安かったので。 


(21 Mar. 2009 7:05pm更新)

理由がわかりました。D-sub25ピンのGNDとJTAGのGNDを接続し忘れていたのでした。 なんともみっともない理由です。 お恥ずかしい...。


ATtiny2313の実験回路を作ったので、ちょいと回路を変更してPWM出力をしてみました。

変更はきわめて簡単で、実験回路のPB2/OC0A(14番ピン)をD2から外し、RCAコネクタの信号線(内側)に入れます。 RCAコネクタのもう片方はGNDに接続します。

本体からの入力が1ビット使えなくなってデータが不正確になりますが、この際気にしないことにしましょう。

音質を気にするならローパスフィルタを設けるべきですが、とりあえずこれだけでも音は出ます。 ただ、出力電圧が高いので本当は抵抗を入れたほうがいいでしょうね。

とりあえず8bitカウンタを使ったPWMなのですが、この場合は以下のいずれかになるようです。

  • 周波数一定、デューティ比を変化できる...音質が変化
  • デューティ比一定(50:50)、周波数を変化できる...音程が変化

周波数とデューティ比の両方を可変にするには、16bitカウンタを使う必要があるようです。

なお、デューティ比50:50の出力は、まるでPSGそのものです。 ファミコンのあの独特の音も出せそうですね。

プログラムはこんな感じです(AVR Studioのアセンブラ使用)。

.include  "tn2313def.inc"

.def	regB		= r16
.def	regD		= r17
.def	Temp		= r18
	
.org  0
	rjmp	RESET
	rjmp	0
	in	regB, PINB	; input from PC (bit4, 3, 1, 0)
	in	regD, PIND	; input from PC (bit7, 6, 5)
	andi	regB, 0x1f
	andi	regD, 0x70
	lsl	regD
	or	regB, regD	; regB = input
	out	OCR0A, regB
	reti
RESET:
	;; port setup
	ldi	Temp, 0x1b ; 00011011
	out	PORTB, Temp
	ldi	Temp, 0x74 ; 01110100
	out	PORTD, Temp
	ldi	Temp, (1 << PB2)
	out	DDRB, Temp
	ldi	Temp, 0x00
	out	DDRD, Temp
	;; PWM setup (change here to make duty or frequency controllable)
	ldi	Temp, 0b01000011	; OC1A enable, 8bit PWM
	out	TCCR0A, Temp
	ldi	Temp, 0b00001100	; I/O clk/256, OCR0A=frequency control
	out	TCCR0B, Temp
	;; interrupt setup
	ldi	Temp, (1 << ISC11)
	out	MCUCR, Temp
	ldi	Temp, (1 << INT1)
	out	GIMSK, Temp
	sei
LOOP:
	rjmp	LOOP

ご参考まで。


私の手元にはATmega88は一個しかなく、やたら使い回ししまくって壊すのも怖いので、先日買ってきたATtiny2313で実験を始めました。

まず、ラッチを使わずにデータをハンドルできるかどうかです。 次のような試験回路を使いました。


20090318-experiment.PNG

使っているORゲートはHC32です。 この回路では3段入れているので、遅延は50nsに近くなります。 この数値は20MHzの1クロック分にもなるため、結構無視できません。 AHCなどの高速品も考慮に入れるべきでしょうね。

とりあえずPC-6001の15.7944MHzをクロックにして試した結果は次のようになりました。


  • 以前の実験でINT信号が活性化している時間はおよそ630nsである
  • 割り込みルーチンスタート後にINT信号が活性化している時間は2クロック分程度(割り込みベクタにあるrjmp命令の実行時間を除く)
  • よって、AVRの割り込み処理内ではinを2回使ってデータを読み込むのはぎりぎり可能
  • この時間ではAVRからのデータ出力は不可能。データを出力する際は、そのときだけデータバスを占有するための設定(出力設定)をしてからデータを出力しなければならないため
  • 同様の理由で、データ出力を有効にしている時間にも気を使う必要がある。短すぎると出力データを読まないし、長すぎると本体が次の命令を実行するときにコンフリクトしてハングアップする
  • 計算上は、20MHzにすると4~5クロック分になりそう
  • 処理自体は時間がかかってもそれほど問題はない。Z80が次のin/outを行うまではかなりの時間的余裕があるため


以上のことから、とりあえずAVRから見て入力側はラッチがなくてもいけそうです。 出力側はちょっと厳しそうですね。

今回はロジアナでの計測まではしていません。 いずれやるつもりです。


次に、WAIT信号(拡張スロットの7番ピン)についても調べました。 実はPC-6001のWAIT入力はちゃんと機能します。 実行時間がわかるほどのWAITを入れると、画面が乱れます。 これは、CPUが止まることによってVDG(MC6847)からのDMAも止まるためでしょう。

ただし、あまりに長時間止めると、本体がハングアップします。 これはおそらく、WAIT中はメモリリフレッシュを行わないため、DRAMの内容が消えてしまうのが原因だと思います。 まぁ、AVR内部のタイマ割り込みと組み合わせて使えば問題はないでしょう。

いずれにしても、ブートアップ時にきわめて有効に使える手段です。


と、言いたいのですが...。

ちょっとだけ追試したところ、どうもPC-6001mkIIでは有効に機能していないようです。 初代では回路上でもWAIT入力はちゃんとバッファリングするなど正式な入力端子としての扱いをしているのですが...。


一応同時再生ができるようになったので、例によりニコニコ動画からムービーを適当に拾ってきて録画してみました。

TVで見ると色が付くのですが、キャプチャで録画したので本来の白黒になってしまっています。

動作環境は以下のようになっています。

  • 割り込みを禁止
  • AVRのクロックはPC本体からの15.9744MHz
  • 画像3バイトに音声1バイトを1組としてデータを構成
  • 処理速度を平均化するため、一切の圧縮なし。音声も1バイト中下位4ビットしか使ってない
  • 256x192ドット/10fpsで、1画像は6144バイト
  • 音声は1fpsあたり6144/3=2048バイト

以上から、音声は20480Hz/4ビットとなるのですが、DMAの関係で、やはり今のところ音質はどうにもなりません。 画面出力を切ると、10kHz程度でもかなりの音質になるのですが。

上記の条件から、転送レートは以下のようになります。

(6144(video) + 2048(audio)) * 10(fps) = 81920 = 80kB/s

やはり拡張スロットからAVRを介して直接入力しているだけのことはあり、かなりの転送レートが出ます。 しかも、バッファを持たない完全ストリーミングなので、PC側のメモリをほとんど消費しません。 プレイヤーのプログラムも、0.5kB程度の容量で実現できています。

以下が、プレイヤーの中心部です。

_play_loop:
	ld	hl, #0xe200	;11 (hl=VRAM position)
 	ld	d, #16		;8 -> 19 (d=counter)
_read:
	ld	a, #AVRSD_READ	;8
	out	(#0x00), a	;13
	nop			;5 -> 26
_read_command_loop:
	in	a, (#0x00)	;13
	cp	e		;5 (e=BUSY)
	jp	z, _read_command_loop ;11 -> 29/loop
	or	a		;5
	jr	nz, _terminate	;8/13
	in	a, (#0x00)	;13 (size L)
	in	a, (#0x00)	;13 (size H)
	ld	b, #0x80	;8 -> 47 (b=loop counter=dec x 384)
_render_loop:
	in	a, (#0x00)	;13
	out	(#0xa1), a	;13 (audio output)
	ini			;19
	ini			;19
	ini			;19
	jp	nz, _render_loop;11 -> 94*128=12032
	dec	d		;5
	jp	nz, _read	;11
	jp	_play_loop	;11
_terminate:

ご覧のとおり、ほとんどinしているだけです。

このセッティングだと、画像:音声=3:1固定のため、fpsを上げると同時に音声周波数も上がってしまいます。 しかし、512バイトセクタ単位で読み込んでいる関係で、他の比率はなかなか難しいものがあります。

それにしても、DMA ON時の音質は何とかならないものでしょうかね...。


現在、AVRは内蔵の8MHzクロックで動作させています。 もともと手持ちのものが古い(2世代目)ATmega8Lで、2.7V~5.5Vでの駆動が可能な代わりに8MHzまでの動作となっているためです。 姉妹品のATmega8は最高16MHzですが、4.5V~5.5Vでないと動作しません。 3.3V動作のSDカードまでバッファを介したくなかったため、これはこれで悪くないチョイスではありました。

しかし、現行のAVRは3世代目に入っており、mega8系の後継にはATmega88(他に48/168/328)があります。 これは、2.7V~5.5Vで20MHz動作が可能となっています。 さらに低電圧(1.8V~)対応版として、ATmega88Vがあります。 型名の最後にVがつくものはみなこのバージョンで、こちらは10MHzまでの動作です。

電力消費を減らしたP/PVバージョンもあるのですが、そこまでは拘らないので別にいいです。

いずれにしても、ATmega88が後継としてはいいチョイスということです。 幸い、以前日本に行ったときにためしに一つ買っておきました。

ただ、8MHz以上の周波数で動作させる場合は、必ず外部にクリスタルが必要です。

というわけで、今月頭に日本に行ったときに20MHzのクリスタルとコンデンサを少々買っておきました。

現在のSDカードドライバは、データの連続読み込み部分をかなりチューンしてありますが、1バイト読み込むためにはPC本体側で29クロック必要です。 そのため、inir(24クロック)は使えません。

また、セクタをまたぐアクセスにはそこそこ時間がかかります。 とは言っても、

  • 大部分の命令を1クロックで実行する
  • avr-gccが賢く最適化する

という感じでZ80とは根本的に異なるので、体感するようなことはほとんどないのですが。

でも、やはりZ80側をフルパワーで動作させても追従させてあげたいので、88に差し替えてみようかと思っています。 幸いI/Oも結構空いていることですし。

でも、ATmega8L、5個も買っちゃったんだよなぁ...。


SDカードインタフェースはほぼ期待通りに動作しています。 ハードを設計して、作って、ソフトを設計して、作ってというのは結構長い道のりですが、動いたときはうれしいものですね。 いつもはソフトしか作らないので、またこれも格別なものがあります。 とはいっても、今回は西田さんにもかなり助けられていますが。 ありがとうございます。

さて、次の課題はブートアッププロセスです。 現在のインタフェースはメモリを積んでいないため、テストプログラムはカセットインタフェースから読んでいます。 ベルーガのカートリッジと一緒に挿せばROMからブートアップが可能ですが、拡張ユニットを作るのが(配線が)面倒なので、まだやっていません。

現在のインタフェースに使っている部品は以下のとおりです。 安価で手に入りやすい部品のみ使用しています。

  • ATmega8L (88でも動作可)
  • 74HC373
  • 74HC374
  • 74HC4078
  • 74HC32
  • レギュレータ 48M033F
  • コンデンサ 0.1u x 1, 10u x 1
  • 抵抗 1k x 1, LED x 1

これまでのアイデアは、CPLDを使ってロジックをまとめ、SRAMを同時搭載することによりブートアップ時にデータをSRAMに転送することです。 理論的には全く可能なのですが、意外に多ピンのCPLDは高いです(SRAMは安い)。 というか、もともとCPLDは(上記の部品に比べれば)結構高価です。 部品代もあるので、あまり高価な部品は使いたくありません。

AVRで扱えず、ロジックで必要になるのは以下の部分です。

  1. 入出力用のラッチ(8ビット x 2)
  2. I/Oポートのデコード
  3. SRAM初期化用アドレス発生
  4. アドレスバス・データバスのバッファリング

このうち、1, 2については現在既に行っています。 3, 4が問題で、入出力数が多いので小さなCPLDでは無理があります。 ATtiny2313 + 74HC244/245数個という組み合わせもありますが、DIPでは基板に乗り切らなさそうです。

ここは金を使わず頭を使って(?)、何とかうまい方法を編み出したいものです。


なかなか苦しんでいたAVR版SDカードインタフェースですが、ファイルのデータ読み込みまで動作させることに成功しました。

結局outの割り込み(INT1)の最後でペンディングしているin割り込み(INT0)を解除し、かつステータスデータをINT0割り込みで出すようにすることでタイミングが取れるようになりました。

データの入力はinを連続させて取得できるようにしているので、こちらも動作タイミングが重要です。 AVRによるSDカードからのオンデマンド1バイト読み込みには、現在のところ80クロック強かかっているので、Z80側はそれに合わせて読み込ませます。 現在使っているAVRは8MHz動作なので、約4MHzのZ80では1バイトあたり40クロック強かけることになります。

なお、現在の設計では、クロックポートを空けてあります。 そのため、ATmega88を使えば、外部クロックで20MHz駆動が可能なので、その場合は16クロック強/バイトということになり、INIRなどを使った連続読み込みも可能になるかと思います。

まだ読み込みデータの取りこぼしなど細かいところをチェックしていませんが、10kB/sec以上は出ています。 最適化はまだ行っていないため、今後ロジックを詰めることにより高速化が可能です。

ジョイスティックポート版がおよそ2kB/sec(CRT KILLで4kB/sec)なので、やはりかなり高速化しますね。

それと、FATの処理をAVR内部で行っているので、Z80側のプログラムは非常に小さくなります。 ルートディレクトリのファイル読み込みだけなら1kB以下の容量でプログラムできます。


AVRでは、割り込み処理中にも割り込み"要求"自体が禁止されていなければ、外部からの割り込みに対して要求が発生するようです。 コメントにもあるように、その割り込みは保留(pending)され、現在処理中の割り込み処理が終了した後で処理されるようなのです。 このあたりが情報源です。

概念として、「割り込み要求(ハードウェアで処理される)」と「割り込み許可」は独立して動作しているということのようです。

そのため、割り込み処理中の別の割り込み要求を完全に禁止したければ、割り込み要求ビットをクリアしておく必要があります。

具体的には、今回はINT1の処理中のINT0割り込みを禁止したいので、INT1の処理内でステータスデータを出力する部分で、

GIFR = _BV(INTF0);

とします。

ステータスデータを出力する前にクリアしてしまうと、データを取りこぼすことがあり、出力後にクリアするとステータスデータを2度読んでしまうことがあります。 なんとも厄介ですが、これで一応正常に動作しているように見えます。


追記: と思ったのですが、まだ時々ずれる(ステータスデータを2度読む)ことがあります。 一体どうせいっちゅうねん...。


さて次は、いよいよファイルデータのオープンと読み込みです。 SRAM容量の関係で、読み込み処理をオンデマンドで行う必要があります。 そのため、inの連続で処理するためには、処理時間を一定にすることが必要で、以前の実装(1セクタを一気に読む)では通用しません。


どうもうまく動いてくれないSDカードインタフェースですが、問題はクリアになりました。 わかりにくいので、シーケンス図にします。

sequence.png

この図で、PCからのリクエストがin/out命令で、OUT buffer/IN bufferがそれぞれフリップフロップやラッチです。

最初にコマンドをout命令で送出し、その後inを繰り返してデータを取得するという典型的な例です。 コマンド処理中はbusyをあらわすステータスがIN bufferに入っているので、PC側からはポーリングしながら待ちます。

問題は、赤い部分です。 ここは本来、INT1の処理の最後で書き込んだステータスが返されるはずなのですが、実際にはステータスは稀にしか返りません。 なぜか、ここでINT0の出力が返ります。

しかし、ときどきステータスも返ることがあるため、INT0の出力データの最初のバイトにステータスを埋め込んだとしても、データのずれが発生してしまいます。

なんでこんなことが発生するのか、まだ解明できていません。 busyが返るin命令でも割り込み信号自体はAVRに伝わっているので、それが何か影響しているのかもしれません。 プログラム上は、INT0/INT1の処理中(=割り込み処理中)は割り込みは禁止になっているはずなのですが。

なお、プログラムのメインループはsleepによりアイドル状態(スリープではない)の無限ループとしています。

while (1) {
  sleep_mode();
}

こんな感じです。 基本的に割り込みでしか動作しません。


SDカードインタフェースのハードウェアが出来上がったので、早速実際のSDカード入出力を作り始めています。 まずは、試しにディレクトリ情報を読み出しています。

今回は、AVR側にFATシステムを入れることにより、高速化を狙っていますが、1ポートのインタフェースで通信するのはなかなか難しいことがわかりました。

たとえば、BASICで入出力を組んでやると、特に問題なくSDカードからデータが読めます。 しかし、マシン語になると、処理によってはZ80からの要求がシビアで、AVR側の処理が追いつきません。


PCからのデータ出力時(コマンド)

PCからoutでデータを出力したら、それに反応してAVRが動作するようにしています。 処理中の場合はステータスとしてBUSYを示す信号を出すなどして本体側を待たせればいいわけですが、BUSYを出すまでの時間が問題です。 普通のAVR-GCCではレジスタ退避をするのでとても追いつきません。 そのため、割り込みベクタ部は一部アセンブリで組んでいます。

以前の調査で、割り込みルーチンの開始までの反応時間(ベクタテーブルからのrjmp含む)は8MHzでおよそ8クロックであることがわかっています。

BUSYシグナルを出すためには、アセンブリでも現状で12クロックほどかかるので、合計20クロック。4MHz相当で10クロックです。 out命令終了から次のin命令まで10クロックということは、間に1命令程度挟めば普通に間に合うことになります(in命令自体の入力動作が命令処理の後ろのほうであるため)。

これはほぼうまく動くようになりました。


PCへのデータ入力時(レスポンス)

こいつが問題です。

なるべく入力割り込みの処理を軽くして、連続するin命令に対応できるようにしようとしています。

ディレクトリ情報などは、SRAM内で保持してもたいしたことはないので、コマンド実行時に結果を用意しておいて、1バイトづつ返せば、割り込み処理自体もそれほど時間はかからないでしょう。

ところが実際は、けっこうwaitを入れながらでも、入力データのとりこぼし(というか、ずれ)が生じます。 PCからはINRQ信号で割り込みをかけ、AVRがそれに対し「次のデータ」を返す(でないと間に合わない)のですが、割り込み処理中の多重割り込みはしていないので、完全なハンドシェイクにはなりません。 AVRとZ80がうまく同期していないんですね。

特に、コマンド実行直後のステータス情報を取得する部分(連続するinの最初のバイトの受信)でうまく同期が取れていないようです。 busy状態からデータ入力に変わるところなので、タイミングが微妙なのです。 ステータス情報の確実な受け渡しのために、ステータスにPCからのackを返せばいいかもしれませんが、それに対してまたbusy状態になって...きりがありませんね。

また、セクタデータを読み出す際は、ATmega8では1セクタ分(512バイト)のバッファが取れないので(既にFATで512バイト使っているため)、オンデマンド読み込みをするしかなさそうです。


データもステータスも一つのポートでやり取りしなければならないのが根本原因のひとつなのですが、ここまできた以上なんとかするしかないですね。 完全に1バイトづつハンドシェイクする方法もあるのですが、なんかそれは負けた気がする...。


ROMレスシステムの回路を試しに描いてみました。 複雑度の雰囲気を掴むだけなので、正しい保証はありません。

まずは40ピン系(ATmega164/324/644)。

20090204-40pin.PNG

次に、28ピン系(ATmega8L/88/168/328)。 カウンタはATtiny2313を使ったほうが安くなりますが、わかりやすさのために2段のカウンタを使っています。

20090205-28pin.PNG

おっと、初期化時は本来A14をHにしなければいけませんね。 または本体からのA14にNOTを入れるか。

それにしても、かなり複雑ですね。 CPLDで実現するとなると、ゲート数やレジスタ数の割にI/Oが多いので、バッファを使わないともったいないことになるかもしれません。


西田さんからのコメントがあったので、ROMレスシステムについて考えてみました。

基本的な考えは、

  • SRAMをROM領域(0x4000-0x7fff)に確保しておく
    (実際には32kB分のSRAMを0x4000-0xbfffに配置してRAM拡張を兼ねる)
  • 起動時にSRAMにブートアップ用のプログラムを転送し、そこから起動する

以前、私もAVRのプログラム領域、またはSDカード内にブートアップ用の(Z80の)プログラムを入れておいて、そこから起動することも考えてはいたのですが、直接AVRからZ80に読むにはタイミングが間に合わないのでそれ以上考えていませんでした。

しかし、西田さんのアイデアのように、いったんSRAMに転送してしまえば問題はなさそうです。

ブートアッププログラムはAVRに焼きこんでもいいのですが、SDカードに入れておけば、カードの差し替えで起動方法を変えられるのでさらに便利そうです。

なんかSDカードからのブートアップなんて格好いいかも? :-)

課題は次のとおりです。

  • デコードおよび調停が複雑になる
    メモリ系のピン(MREQ, RAS, EXCAS等)のデコードが必要なほか、アドレスの上位も扱う必要があります。
    また、SRAMに書き込んでいるときには本体からの読み書きを停止する必要があります。 いよいよCPLDの出番ですかね。
  • PC-6001が起動してROMからブートアップするタイミングまでにSRAMへの書き込みが間に合わない可能性がある。 特にSDカードを使った場合。
    これは、電源を入れてブートアップを読み込んだ後、手動で本体をリセットすればいいのですが、格好悪いですね。
  • 28ピン系のAVRでは入出力が厳しい。
    仮に可能な限りI/Oを共用をしたとしても、28ピン系のAVRで使えるI/Oは23本です(リセット含む)。 データ線に8本、アドレス線(ROMの16kB分, 14本)、制御線(ブートアップ中であることを示す, 1本)を出したとすると23本。 う~んぎりぎり。 この場合SDカードからの読み込みはI/O不足でできません。 時間分割すれば不可能ではないでしょうが、なんか不毛かも。 SDカードから読み込みたければ、素直に40ピン系のAVRを使ったほうがいいでしょうね。

というわけで、けっこう変えなきゃいけません。 これまでの設計はなるべくシンプルにしてきましたが、実際BIOSが必要であることを考えると(BelugaやPC-6006のカートリッジと同時使用する場合を除き)、メモリの扱いも必要なんですね。

とりあえずシンプル版?とROMレス版、2本立てで考えを進めてみようかと思います。


前回の回路がまた間違ってました。

  1. アドレス線を使いたければ、データ線と同様ラッチしなければいけない
    これまでデータ線のみでアドレスのラッチを忘れていました。データのタイミングがAVRの割り込みからは間に合わないので、同然アドレスもラッチする必要があります。
  2. ラッチではなくてフリップフロップを使う必要がある(かも)
    出力リクエスト時の^WRとデータ線の確定タイミングは、8080ではデータが先に確定します。これに対し、8085や8086では^WRが先に確定するので、ラッチだと瞬間的にでたらめなデータがAVRに取り込まれる可能性があります。 Z80のドキュメントを調べてみたところ、CMOS版のZ84C004では"Data stable prior to /WR Fall"が-140ns~-10nsで、WRが先に確定することがあるようです。 実際にはAVRの反応はこれよりはるかに遅いことと、^IORQなど他の信号線とORをとっていることで実害はないと思いますが、念のためFlip Flop(374)を使うことにします。

先日日本に行った際に4075(3OR)は手に入らなかったので(代わりに4072(4OR)を買ったけど)、4078(8OR)を使って、占有I/Oアドレスをひとつだけで行くことにしました。 ソフトウェア的にはコマンドのための通信量が増えるのですが、まぁこれはトレードオフでしょう。 これ以上汎用ロジックICを増やすと、通常のDIPを使っている関係上、基板に乗り切らなくなってしまいますし、CPLDを使ったほうが簡単になるので。

というわけで、修正した回路が以下です。 I/Oアドレスは0x00に固定してしまいました。安直...。

20090127-AVR_SD-OR8.PNG

この修正で、ATmegaの6本ものI/Oが余ることになりました。 あと一つ減らすとATtiny861(20ピン)でも足りることになるのですが、実際にはRAM容量の関係(512Bytes)でちょっと厳しいですね。 FATでは512バイトのバッファを取らないとかなり性能的に厳しいので。

あ、XCKもXTAL1/2も空いているので、ATmega88(以上)なら外部クロックを使って高速に動作させることができますね。