2009年2月20日金曜日

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

水平1ライン分を描画するプログラムの仕上げとしてスコア表示を説明しましょう。

スコアは2桁の数字です。普通に考えれば1桁分の「数字フォント」を0から9まで10種類用意することになるのですが、ここではフォントの幅=ドット数も勘案して2桁で1つの数字として扱っています。2桁だと100種類のバリエーションができてフォントの格納領域が膨大になってしまいそうですが、そこはPONG。0点から15点まで16種類しか必要としません。その定義が以下の"fconst.asm"です。

fconst.asm ("1"を強調表示しています)

.setと.dbがあって一瞬なんだこりゃ?ですが、フォントをビジュアルで定義するための小技です。1のビットが白――文字部分――になります。1つの数字は横×縦=4×5ドットの枠の中に3×5ドットとして表現されています。これを横に2桁分並べて8×5ドットの「2桁数字」を1フォントにしています。

一方、画面デザイン上の問題として、フォントの1ドット=画面上の1ドットにしてしまうと数字があまりにも小さくなりすぎてバランスが良くありません。数字をある程度の大きさで見せるためにはサイズを拡大してあげないといけない。そこでフォントの1ドット=画面上の4ドットと縦横4倍になるようにしています。

また、スコアの数値自体はSRAM上の変数ScoAとScoBに入っているのですが、そこから上記のフォントデータに変換して、4倍にふくらませて…とやっていると水平ブランキング期間内に処理が終わりません。そこで、スコア→フォントデータへの変換部分だけはあらかじめ――垂直ブランキング期間内に――終わらせておくという措置をとります。その処理をするのが"font_cache.asm"です。ScoA/Bの値を表すフォントデータがSRAM上のバッファ変数Ftbufに格納されます。

さて、Ftbufに格納されたフォントデータを描画する部分が"draw.asm"の最後の部分です。まず、スコアが描かれる縦位置に来たかどうか判定します。
  1. ;; --- draw scores ---  
  2. ore:  
  3. mov     zl, nxline      ; +1=42 prepare for font load  
  4. subi    nxline, DGT_TOP ; +1=43  
  5. cpi     nxline, DGT_BOT+1-DGT_TOP; +1=44  
  6. brsh    eol             ; +1=45  

スコアが描かれる位置に来ました。次はフォントを縦4倍に広げるのですが、どうしたらよいのでしょう。答えは「4回足踏みして待つ」です。次にどこの行を走査するのかを示す変数nextlineは1ずつ増加するのに対し、4回同じデータを描画すれば縦が4倍に伸びることになります。nxline(のコピーのZL)を4で割っている(LSR命令が2回)のはそのためです。
  1. lsr     zl              ; +1=46  
  2. lsr     zl              ; +1=47  
  3. addi    zl, low(Ftbuf)-DGT_TOP/4; +1=48  
  4. clr     zh              ; +1=49  

そして横の4倍化。「11110000」と「00001111」の2つのビットパターンを用意しておいて、フォントデータの1になっているビットの位置によってこれら2つを使い分けて行のデータと論理和を取ります。分かりにくいと思うので実例で。

今、描画しようとしている先の行には下記のようにボールの一部が描画されているとします。R7-R10はスコアを表示する位置にあたります。


ここにたとえばフォントデータ"14"を重ねて描画しようとしたとして、
    11001010
    01001010
    01001110 ←この行を描画
    01000010
    11100010

今はそのフォントの3行目だとすると、その描画データは01001110です。これを上位ビットから判定していきます。ルールは0なら何もせず、1ならそれが偶数ビットであればパターン11110000を、奇数ビットであればパターン00001111を重ねるというものです。するとこうなります。
    ビット7は0 … 何もしない
    ビット6は1 … パターン00001111をR7に重ねる
    ビット5は0 … 何もしない
    ビット4は0 … 何もしない
    ビット3は1 … パターン11110000をR9に重ねる
    ビット2は1 … パターン00001111をR9に重ねる
    ビット1は1 … パターン11110000をR10に重ねる
    ビット0は0 … 何もしない

図示するとこうです。

この部分のソースはこれです。ScoA側だけ示します。SBRC命令を使って0のビットをスキップしています。ScoBについても同じ処理になります。
  1. .def    fontd   =r24  
  2. .def    pat10   =r27  
  3. .def    pat01   =r26  
  4. ldi     pat10, 0b11110000; +1=50  
  5. ldi     pat01, 0b00001111; +1=51  
  6. ;; for Score A  
  7. ld      fontd, z        ; +2=53 load a font row of A  
  8. sbrc    fontd, 7        ; +1=54  
  9. or      rast7, pat10    ; +1=55  
  10. sbrc    fontd, 6        ; +1=56  
  11. or      rast7, pat01    ; +1=57  
  12. sbrc    fontd, 5        ; +1=58  
  13. or      rast8, pat10    ; +1=59  
  14. sbrc    fontd, 3        ; +1=60  
  15. or      rast9, pat10    ; +1=61  
  16. sbrc    fontd, 2        ; +1=62  
  17. or      rast9, pat01    ; +1=63  
  18. sbrc    fontd, 1        ; +1=64  
  19. or      rast10, pat10   ; +1=65  

以上で映像出力と描画関連のプログラムの説明は終わりです。ゲームの進行については垂直ブランキング期間――1/60秒に1回かつ1.28msの間だけ――に行われますが、それはgame.asmを読んでいただくことにしましょう。

長々と説明をしてきましたが、とりあえずソフトウェアの説明はこれで終了です。お疲れ様でした!

2009年2月7日土曜日

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

TinyでPONG!(ソフトウェア2)では映像出力の最前線の部分の説明をしました。1水平ライン分の映像データは24個の汎用レジスタのR0~R23に用意されているということが前提でした。逆にいえばデータをレジスタに入れさえすればいいわけです。

全速力で1ラインの映像を出力した後は、次のライン出力の出番が来るまでのつかの間にデータを仕込みます。ソースは"draw.asm"にあります。最初に判定すべきは何のデータを仕込まなければならないのかです。つまり次に何行目を走るのかで書きだすものを変える。大くくりでは、
  • 1~10行目 → サイドライン(横一本ずっと白=「1」)
  • 11~226行目 → コート内部(センターライン、スコア、ボール、パドル)
  • 227~236行目 → サイドライン
となります。この判定をしているのが以下の部分。
  1.         ;; --- prepare next raster ---  
  2.         cpi     nxline, COURT_MIN; +1=2  
  3.         brlo    sideline        ; +1=3  
  4.         cpi     nxline, COURT_MAX; +1=4  
  5.         brlo    field           ; +2=6  
  6.   
  7.         ;; --- draw sidelines ---  
  8. sideline:                       ; draw sideline  
  9.         filrast                 ; (+24)  
  10.         rjmp    eol  

次に何行目を走るのかはnxline(ここではR27と同義)に入っています。それがコート内部にあるかどうかの比較があって、外部であればサイドラインを書くべくfilrastを実行して終わりです。filrastはマクロで、汎用レジスタR0~R23の全ビットを1に立てるだけです。これは簡単。

コート内部だったら前出の通りサイドラインとボールとスコアとパドルのデータを用意しなくてはなりません。これらは排他的ではなくて、すべて書かなければならない状態もあり得ますから1つ書いてサヨナラというわけにはいきません。全部説明するのも冗長なのでここではまずボールを書く部分を。
  1.         ;; --- draw ball ---  
  2.         .def    pos     =r24  
  3. drawball:  
  4.         lds     pos, Ballt      ; +2=10  
  5.         cp      nxline, pos     ; +1=11 skip if (line > Ball_top)  
  6.         brlo    drawpad         ; +1=12  
  7.         addi    pos, BAL_HEI    ; +1=13  
  8.         cp      pos, nxline     ; +1=14 skip if (line < Ball_bot)  
  9.         brlo    drawpad         ; +1=15 / +2=16  
  10.   
  11.         .def    pattern =r26  
  12.         clr     zh              ; +1=16  
  13.         lds     zl, Ballad      ; +2=18  
  14.         lds     pattern, Ballpt ; +2=20  
  15.         st      z, pattern      ; +2=22  
  16.         com     pattern         ; +1=23  
  17.         std     z+1, pattern    ; +2=25 (r24 can be written)  

次の行nxlineがボールの上辺の位置Balltと下辺の位置Ballt+BAL_HEIの間にあるかどうかの判定が冒頭にあります。なければそこにはボールがないので書く必要がないということになり、次のパドルを書きに行きます。

ボールを書く場合、ボールのバイトアドレスBalladでレジスタ番号を指定し、ボールのビットパターンBallptをそのレジスタに書き込みます。ここがAVRのアーキテクチャの特徴をうまく使った部分で、レジスタがメモリアドレスのゼロ側にマップされていることを利用しています。つまり、メモリアドレス0x0000はR0を、0x001fはR31を指し示し、この例のようにZレジスタ間接などでもあたかもSRAMをアクセスしているかのごとしです。このようにできることはデータシートには書いてなくて(探しが足りなかったのかも)、実験してみたらできてしまってラッキーでした。こいつは便利だ。AVRすごい!

ボールのビットパターンを書くと言いましたが、ボールの幅が8ドット=8ビット、レジスタが8ビット単位なので(ほとんどの場合)2つのレジスタにまたがって書かなければなりません。この例ではまず指定されたレジスタに左側を書き、次のレジスタに右側を書いています。右側のパターンはちょうど左側のパターンのビット反転になっているのが分かりますでしょうか。(だからcom命令で反転しています)

ボールの形を書く


今回はこのくらいで。次はスコアの表示です。

そうそう、ソースアーカイブにレジスタマップ(regmap.txt)を入れておいたのを忘れていました。今Excelで開いてもう少し見やすくしたイメージを下に貼っておきます。

レジスタマップ

2009年2月6日金曜日

TinyでPONG!(液晶テレビで映らない?)

このPONGの記事を参考に実際に作ってみてくださった方がいらっしゃるらしくてとても光栄な気持ちです。この場を借りて御礼します。

そちらの記事の中で液晶テレビには映らないというご報告がありました。本当に申し訳ないのですがそのことを説明するのを忘れていました。そうなんです。確かに私も確認していました。仕事で使っているNTSC入力付きのデルの液晶モニターには映りませんでした。これは液晶テレビ全般がだめかと思いきや、自宅で使っている三菱製の32インチの液晶テレビ(2005年製の地デジ・BSデジ対応機)では映ります。

実は、今回の作例ではNTSCの信号規格を完全には満たしていません。一番効いているのが、簡略化のためなのですが、インターレースにしていないところでしょう。本来は秒間60フレームではなくて2フィールド×30フレームにするために同期タイミングを小細工しなくてはならないのです。これが、シビアな同期判定の仕組みを持つ最近の液晶テレビだと認識してくれない(だまされてくれない)ケースがあるんじゃないかと。説明が不足していてすみません。

むか~し同じような簡略化をしていたやつ(ゲーム機だったか、コンピュータだったか)があったような。そういうのは液晶テレビには映らない(ことがある)んでしょうね。

(次回はソフトウェアの解説の続きをします。)

2009年2月2日月曜日

フライスにデジタルスケールがついた

プロクソンの小型フライスを使っています。プラケースにスライドスイッチ用の角穴を開ける時などに重宝しているのですが、X-Yテーブルの送りハンドルのバックラッシュ(遊び)に手を焼いていました。バックラッシュがあるのは当然のことだとは思うのですが、それを考慮に入れてもハンドルに刻んである目盛がなんか信用できない。ハンドルの目盛を頼りに四角くぐるっと一周しても元の位置に戻らないんです。
いらいらは募り、ついにデジタルスケールを装着することにしました。かなり高額なものなんだろうと思っていたら、探したら結構安いのがあったんです。

昨今はこの手のフライスを改造してコンピュータNC化するキットなども比較的安価(といっても20万円くらいはする)に買え、それを使えばNCデータを作成するだけで加工は機械任せにできます。しかし…そこまで一足飛びにやるべきか?とりあえず練習の意味を込めて手動でもきちんと図面通りに加工できるレベルに到達することが大切と考えました。

右の写真はX軸側、左下のはY軸側です。3×30mmのアルミ材に固定し、そのアルミ材をテーブルに固定しています。スライダー部の裏側には同じアルミ材やLアングル(今回は手元にあったアルミ製のブックエンドを切って利用)をねじ止めしています。困ったのはテーブルの壁面が垂直じゃないこと!微妙に下方向にすぼまってる。あと、ねじの頭が引っかからないように皿ネジにしたり、ハンドルのコラムが邪魔でタップ(ネジ切りの道具)のハンドルが回せないところにある穴はタップタイトねじを使ってねじ切りしたり…。まあいろいろと、現物合わせで苦労しましたが、なんとか形になりました。

このX-Yテーブルは左右に約140mm(=±70mm)、前後に110mm移動しますので、デジタルスケールはどちらも150mmまで計測可能なタイプにしました。ゼロ点合わせ、インチ/ミリ切り替えの機能がついて1本税込3,780円と安価でした。ちゃんとしたモノでしたよ。バックル付きの木箱に入って、合格検査証も入ってたし。でも…たぶん某国製なんでしょうね、取説の説明と現物が合わない…ボタンの数が違うし…。

あと、通常デジタルスケールにはリードアウト端子が出ていて外部表示装置をつなぐことができるようになっているのですが、今回購入したものは端子がありませんでした(涙)。取説上はあることになっているのに。諦めきれずに分解して中をのぞくと…ありました!それらしきものが。オシロで観測してみるときちんと信号が出ています。ラッキー!ケースの外には出てないけど、基板上にはあったのです。むふふ。そのうちLEDの表示器作ろっと。ケースに穴あけて。おっと、右の写真、ピンボケですな…。

ちなみにデジタルスケールの購入先はスリースカンパニーでした。きちんと使えるし、質感も悪くないし、値段考えると十分満足。他の特価商品としてデジタルノギス1,400円(!)などがありました。うぉっ、安すぎる…。