2009年1月19日月曜日

TinyでPONG!(ソフトウェア1)

さあ、ここからが本番、ソフトウェアの説明に入ります。早速ソースコードをアップロードしておきました。


トップレベルのエントリーは"pong.asm"です。拡張子を見ただけで分かると思いますが、8MHzという低速のCPUでビデオ信号のタイミングにシビアに合わせるためアセンブラを使っています。AVRのような簡易なマイコンはパイプライニングなどの高級な高速化技術を一切使っていないため、命令表の消費クロック数の値がそのままタイミングカウントとして簡単に計算できますので、アセンブラで記述すればタイミングを正確に合わせることができます。

時代はデジタルハイビジョンに向かっているのに今更ながらアナログTV信号(NTSC方式)を扱いますが、アナログ放送の停波まで公式にはまだ2年ちょっとありますし、全世帯からアナログTVがなくなるのはまだまだ時間がかかると思います。逆にデジタルTVへの切り替えで不要になるアナログTV(壊れても泣かなくて済む!)を実験などに存分に使えるようになる絶好の機会が到来すると前向きに考えましょう。

NTSC信号を使ってTVに絵を出すためにどんな信号をどのようなタイミングで出さなければならないかは、こちらなどのサイトに素晴らしい説明がされていますので参照していただくことにして、本プロジェクトではそのタイミングをどのようにして作り出しているのかを説明します。なお、本プロジェクトで扱う映像信号は白黒で、カラーではありません。また、グレースケールはなく2値の白黒ーーすなわち1画素=1ビットです。

信号出力のタイミングは唯一、16ビットタイマーのTimer1からの割り込みによります。Timer1は原クロック8MHzでカウントアップし、水平同期信号の周期(63.555μs≒64μs)に達したところでゼロクリアして自動的に循環するモードに設定。このカウンタの2つある比較レジスタ一致の1つOCR1Bでの一致により発生する割り込みで映像信号出力を開始します。同時に比較レジスタOCR1Aの値によるPWM動作を設定し、OC1Aピン(=PB1ピン)から水平同期信号を自動的に出力しています。


これをこのように作ります。無理やりテキストで書いて、見にくくてすみません。のこぎり状の波形はTimer1カウンタの内容です。


水平方向にいくつカウントするかは64μs×8MHz=512カウントですのでTimer1は0から511まで変化します。カウンタがOCR1Bレジスタと一致したところで事前に準備してあった水平1行分の映像データをタイミングをきちんと計算されたプログラムで1画素=1ビットずつ出力します(図中の「映像送」)。1行出力した直後には次の行のデータを準備しておきます。図中「備」と書かれた部分がそれに相当します。準備してから次の割り込みが来るまでの間は、たった数クロック程度の余裕しかありませんが、スリープ(図中の「Z」)して待ちます。

割り込みハンドラ(ソースtim1oc1b.asm)は垂直方向のカウント、垂直同期の生成、パドル=簡易A/Dコンバータの状態遷移の管理、音声出力など軽い処理をするのみです。

割り込みハンドラから戻ってくるとメインプログラム(main.asm)のスリープ命令が解除されて、映像送出(draw.asm)と次の行の準備(同)を行います。

OC1A出力はOCR1Aレジスタでの一致で"L"を出力、カウントトップすなわちICR1レジスタとの一致で"H"を自動的に出力するような高速PWMモードの設定にしてあります。これが同期信号出力になります。

水平方向の表示期間が終わり、垂直ブランキング期間に入ったかどうかはメインプログラムの冒頭で判断しています。本プログラムでは水平20行分がその期間となります。その中の3行分の期間は水平同期信号の極性反転を行います。これが垂直同期信号となります。極性反転は前述のPWMの条件を反転するだけで実現しています(つまり、OCR1A一致で"H"、トップで"L"になるように)。

垂直ブランキング期間では、映像出力が必要なくなるのでこの時とばかりにゲーム本来の動作を実行します(game.asm)。表示期間とブランキング期間合わせて全部で256行あるうちたった20行がゲーム進行に使われますので、本来の7%のパフォーマンスしか得られません。8MHzで公称8MIPSのAVRでも約0.6MIPSとなります。それでもこの程度のゲームであれば全く問題なくスムーズに動作しています。

0 件のコメント: