たまりば

  パソコン・インターネット パソコン・インターネット  三鷹市 三鷹市

ゲームボーイの吸い出し機を作った (後編)
2017年01月16日 22:44

前編の続き。プログラム側について。

まずは単純に読むことを試みる。
手持ちの中でバンク切り替えなし(32kB)のソフトとしてDr.マリオを選択。

バンク切り替えが無ければアドレスを出してデータを読むだけ。
…とはいえCLKとかCSとかRDとかどう制御すればいいのか。
GBのカートリッジの仕様くらいいくらでも見つかると思っていたのだが、ROMの読み出し方法は常識なのか、細かい解説が意外と見つからない。
結局、分かってみると単純で、
/RDがLにアサートされている間、アドレスピンで指定されたアドレスのデータが、データピンに出る
というだけのことだった。
つまり読み出すには、/RDをLにアサートしたままアドレスを順次変え、データピンを読むだけでよい。
これが分かるまでに/RDをH/L切り替えてしていた。
あと/CSはSRAMを読む時アサートするものだった。

プログラミング自体もなんだかんだで苦労した。
やはり合っているか分からない操作を正しく組めているか分からない機械にプログラムするのは疑心暗鬼に陥ってだいぶ精神を消耗する。
まずシリアル通信するところからしてうまくいかない。
シリアルポートが全く反応しなくて焦った挙句Windows10へアップグレードした時にケーブル類全部抜いてたのを戻してなかっただけだったりもした。
何か出るようになったと思えば文字化けしている。これはどうもTeraTermのバグかWindows10との相性のようで、最新版を使ったら正常であった。
新しいPICを使う際には毎回のようにGPIO以外の機能を切っていなくて問題が起こるのだが、今回もまんまとその罠にはまった。
ADコンバータとコンパレータを切ったまでは良かったのだが、それで読もうとしても何も読めない(FFが読める)。
ポートのレジスタを直接インクリメントしてたのがまずかったかと思い、別の変数をインクリメントしてそれを出力するようにしたところ何かは読めるようになったが、全体にわたりほぼ確実に8バイトづつ同じ値が取れる。
つまりアドレスの下3bitが何かおかしい。そのピンを調べると、LCDドライバの電圧生成機能がデフォルトでONであった。
ということはポートのレジスタを直接インクリメントすること自体は問題なく、最下位bitの書き込みが無視されていたせいでインクリメントできていなかったんだな。

というわけでついにDr.マリオが読めた。
Dr.マリオ

次はバンク切り替え…の前に色々なソフトをバンク0だけ読んでみることにした。
するとポケモンYellowやポケモンカードなど読めるものもあるが、ポケモンSilverが読めない。
読めないというのは、ほぼ全てFFが返ってくる。ごく稀にFF以外のものが返ってくることもあるのがまた不可解である。
FFでない箇所のパターンは規則的で、なにかありそうである。2進数で「xxxx xxx1 0000 000x」と「xx00 0110 0000 0001」、つまり0100, 0101, 0300, 0301,…と0601, 4601がFFでない。
不要なはずのクロックだがMBCを積んでいることもあり何か変わるのではないかと入れてみる。当然変わらず。
…散々悩んだ挙句、電圧不足であった。
いつもPICを動かす時はEneloop2本(2.6V程度)を使っていて、使いやすい5V電源を持っていないこともあり、とりあえずそれでやっていたのだが、GBの電圧は5Vなので動かなくてなんの不思議も無いのであった。
Dr.マリオを始めいくつかのソフトで(バンク0は)読めていたので発覚が遅れた。

改めてバンク切り換えだ。
バンクの少ない(最少の4バンク)ものとしてQIXを選択。
バンク切り換えの手順はMBCによって少々違うが、基本的に特定のアドレスにデータを書き込むだけである。
書き込むべきデータを入れていなかったり、PICのIOを入力のまま出力したつもりになっていたり、書き込むアドレスを間違ったりして手間取ったが、まあまあすんなりと読めた。
QIX

さて続いて本命のポケモンSilverだ。
128バンクあるが、バンク切り換えのやり方は同じなので、単純に数が多いだけ。難しいことは何もない。
しかしなぜか途中でデータが飛ぶ。読み取ったデータを見るとファイルサイズが想定より小さい。
今までこのような大量のデータをシリアルで受信したことは無かった。シリアル通信の信頼性はこの程度なのだろうか。
だがそれは想定の内。1バンクごとに目印を入れてあるのでどこで抜けたかは分かる。何度か読んで正常な部分を切り貼りすればよいだろう。
…と思っていたのだが、不思議な事に常にエラーが出ている場所がある。
バッファ切れを疑ってバッファ量を変えたりウェイトを入れたりしてもなんとなく変化はあるものの直らず。
データの問題かと思いXOR 0x55したデータを送ってみると抜けの量はほぼ変わらず、抜けの位置が変わった。特定のデータが来ると問題が起こるのだろうか。
速度を落としてみるとだいぶ改善した。抜けが6バンクまで減ったので試しに起動してみると、一応起動はした。
ポケモンSilver_データ抜け1ポケモンSilver_データ抜け2
このような分かりやすいバグった表示になるものなんだなあ。
なお部屋に出口がないので進めなかった。その後もう1バンク正常に取れたのでそれと合わせると部屋から出られたがBGMが異常になったりする。
しかしここで何度とってもほぼ同じ場所でエラーを起こす。
やはり速度を落とすだけでは解決しない。特定のデータが問題という線で考えてみよう。
改行の処理に時間が掛かっている可能性を考えてCRの後にウェイトを入れてみる。むしろ悪化。
あとは…エスケープシーケンス。何らかのエスケープシーケンスが来るとそれの処理に時間がかかってデータを取りこぼすのではないか。
ここでTeraTermを調べて、受信した文字をすべて表示するデバッグモードがあることを知る。
デバッグモードで受信するようにしたところ、一切取りこぼさなくなった!
ポケモンSilver
後で調べたところ、制御シーケンスに「1B 63: 端末リセット」というものがあるらしい。
つまりこのバイト列が来るとTeraTermがリセット動作を起こし、その間に来たデータを取りこぼしていたようだ。
調べてみると「1B 63」は最後まで読めなかった5バンク中の3ヶ所にあった。残り2ヶ所やそれ以前のエラー箇所は分からないままだがたぶん他のエスケープシーケンスだろう。
デバッグモード以外にエスケープシーケンスを無視する方法がないか調べたのだが、見つからなかった。
人が読む文字列を出す時は改行は使いたいのだが、致し方ない。

次に困ったのがX(エックス)だ。バンク切り替えができない。
調べてみるとこれに使われているMBC2はバンク切り替え時に書き込むアドレスに制限があり、今まで使っていた0x2000では駄目だった。
と0x2100に変えてみたが、やはりバンクは切り替わらない。
そこで読み取れたバンク0のコードを見てみることにした。この中にバンク切り替えのコードがあるはずである。
するとやはり0x2100に書き込んでいる。
合っているのにおかしいなと思い更に調べると、バンク1に変更する時は0x2100だったのだが、バンク2では0x2101、バンク3では0x2102…と、バンクNに変更する時0x2100+(N-1)に書き込むようになっていた。
1少ないのは書き込み後のインクリメントの関係だろう。ということでバンクNに変更する時は0x2100+Nに書き込むようにコードを書き換えてみると、見事読み取りに成功した。
なんだろう。バスコンフリクトだろうか。ファミコンのMMCでバスコンフリクトを起こすものがあるという情報はあるが、GBで起こるというのは見たことがなかった。
X(エックス)

さて次はニンテンドウパワーのGBメモリの読み取りを試みている。
これは複数のMBCの動作を再現する特殊なコントローラを積んでおり、普通のバンク切り換えとは異なるコマンドを入れる必要があるらしい。
色々試しているのだがまだ一切反応がない。一番つらい時期だ。
読み取りができたら、どうも書き込みも出来るらしいのでやりたい。自作ソフトを実機で動かすのは夢である。

ただその前に、どうも読み取りが安定しないのでどうにかしたい。
今まで読めていたソフトでも読めなくなったりしている。
断線しかかっているとかだろうか…。  

  • ファミコンで全画面に任意の画像(ただしモノクロ)を表示
    2017年01月14日 00:00

    ファミコンは普通には全画面に自由に画像を表示できないことはよく知られている。
    でもモノクロ2値ならできることに気づいたのでやってみた。
    ファミコン全画面_猫耳とりあえず猫耳。
    ファミコン全画面_羽根っ娘酉年らしく羽根っ娘。
    ファミコン全画面_漢字全画面任意なのが分かりやすい図柄も。

    まず前提として、ファミコンのBG(背景)面は32×30=960個のキャラクタで構成されるが、BGの(スプライトも)キャラクタパターンの指定は1バイトなので、一度に256種類からしか選べない。
    よって普通にはBGだけでは256キャラ、画面の1/4強しか埋められず、スプライトを8×16ドットモードで最大の64個使って128キャラ用意してやっと384/960=40%にしかならない。
    何らかのマッパーを使えば描画中にバンク切り替えで全画面を別のキャラクタで埋められるが、マッパーを使ってできるのは面白くないので今回考えないことにする。

    そこで今回の手法だが、パレットの色分けとキャラクタテーブルの描画中切り替えで実質キャラ数を4倍に増やしている。
    まずパレット。
    ファミコンのキャラごとの色数は4色だが、これを2色しか使わなければ例えば「白黒白黒」と「白白黒黒」の2種類のパレットを左右で使い分けることで1つのキャラで2つの絵を表示することができる。
    00011011

    0111111111111100
    1111111111111111
    1111000000001111
    1111010101011110
    1111111111111111
    1111101010101111
    1111010101011111
    1111010101011110
    00011011

    0111111111111100
    1111111111111111
    1111000000001111
    1111010101011110
    1111111111111111
    1111101010101111
    1111010101011111
    1111010101011110
    00011011

    0111111111111100
    1111111111111111
    1111000000001111
    1111010101011110
    1111111111111111
    1111101010101111
    1111010101011111
    1111010101011110
    比較のために単一のパレットで表示したものがこちらだ。
    単一パレット表示

    次にキャラクタテーブル。
    「一度に256種類からしか選べない」と書いたが、この「一度に」の部分がポイントだ。
    仕組みを詳しく言うと、定義できるキャラは256個の領域が2つあり、BGと8×8のスプライトはそれぞれその2つのどちらの領域からキャラを選ぶかを設定できる。同じ領域をBGとスプライトの両方で使うこともできる。
    (なお今回関係ないが8×16のスプライトは一度に2キャラを使うので、どちらかの領域から選ぶのではなく、2つの領域を合わせた512キャラを2キャラづつの256組と見たものから選択する)
    ここでこの「どちらの領域から選ぶか」の設定は画面の描画中にも切り替えることができる。これにより上半分と下半分で別のテーブルからキャラを選ぶことが可能なのだ。
    比較のためにこの切り換えを行わなかったものがこちら。
    切り換えを行わない表示

    基本的なアルゴリズムは以上だ。
    実際のコードは下に置いたが、少々細かい解説を加えておく。

    今回CHR-ROMでなくCHR-RAMを使っている。
    そのためまず最初にPRG-ROMに持っている画像データを元にCHR-RAM上にパターンテーブルのデータを生成している。
    これは事前に適切な並びにしたデータをCHR-ROMに持っておけば必要ない操作だ。
    ただ、この並べ替えのプログラムは、ファミコン自体で行うか外部で事前に行うかの違いでどうせ書かなければならない。
    ならば1つにまとめた方が扱いが楽なのでこうした。
    下に書いたコードと、任意のモノクロビットマップを用意するだけで、NESASMでビルドできる。

    このパターンテーブル書き込みのコードは少々複雑だが、やっていることは単純に、256×240ドットの1bitビットマップを適切な順に並び替えているだけだ。
    パターンテーブル前半0x0000~0x0FFFが画像の上128ドット、後半のうち0x1000~0x0DFFが画像の残り。0x1F00~0x1FFFには後述の通り0x0F00~0x0FFFと同じものを重複して配置している。

    「どちらの領域から選ぶか」の設定、つまりパターンテーブルのベースの切り換えだが、このような描画中のタイミング合わせはファミコンではふつう0番スプライトヒットフラグをポーリングすることで行う。
    だが今回別の方法をとった。
    0番スプライトヒットはBGとスプライトが共に不透明な点でしか起こらない。つまり画像の当該箇所が透明なとき使えない。
    今回任意の画像を表示できるようにしようとしているのでこれは困る。
    代わりにあまり知られていないスプライトオーバーフローフラグを使った。
    これはライン上にスプライトが9個以上並んで横並び数制限を越えたときに立つはずだったフラグだ。
    このフラグはスプライトが透明でも変わらず立つので、BGの内容に関係なく使えて便利だ。
    ただ重大な欠点があって、このフラグ、アドレスの桁上がりの誤りで別のデータを参照するせいでまともに動かないという、致命的かつしょうもないバグが存在する。
    使い物にならないと思うかもしれないが、バグの条件は厳密に判明している。
    詳しくはNesDevの「PPU sprite evaluation」のページを読むとよいが、
    スプライト番号順にライン内に存在するかをチェックし、8つめが見つかった次のチェックまでは正しく、その次から誤ったアドレスを参照しだす。
    ここから、
    ・スプライト数が7つ以下なら、必ずフラグは立たない
    ・スプライト数が9つ以上の時、8つめと9つめのスプライト番号が隣り合っていれば、必ずフラグが立つ
    ということが言える。
    なので今回はスプライト番号順に連続した9つを目的の場所に置くことで確実にスプライトオーバーフローフラグを立てることにした。
    というか残りも面倒なので同じ位置に置いた。

    切り換えタイミングだが、画像を単純に上下に分割した場合、きっちりHBlank中に切り換えを起こすようにしないとこのように画面が乱れてしまう。
    ファミコン全画面_画面乱れ
    そのレベルで合わせるには描画中のスプライトの判定処理の流れをきちんと理解してコードを書かねばならないが、今回面倒なのでそこまではしなかった。
    その代わり、1列分(8ライン)のキャラをパターンテーブル前半と後半で重複させておいた。これでこの8ラインの中で切り替えれば画面を乱さないようにできる。

    画像データのインクルード部分は「画像データ(生)」と「画像データ(BMP)」の2種類を用意した。(生)は256×240bitの生データを使う時用で、(BMP)はWindowsのMSペイントで出力したBMPファイルをそのまま使えるようにヘッダ分だけずらした位置に配置するものだ。
    これを使う時は、まずペイントで適当に描いた絵を上下反転
    ペイントで描いて上下反転
    モノクロビットマップで保存して
    保存
    できたファイル名をソースに書き込んでビルドするとできあがり。
    ビルドした表示
    ぜひ試してみてほしい。

    ; 全画面に任意のモノクロ2値のビットマップを表示する
    ; NESASM v3.1でビルド、NNNesterJとNintendulatorで動作確認している。

        ; INESヘッダー
        .inesprg 1 ; PRG-ROM容量(0x4000=16KB単位)
        .ineschr 0 ; CHR-ROM容量、CHR-RAMでは0
        .inesmir 0 ; Don'tCare(水平ミラーリング)
        .inesmap 0 ; マッパーなし

        .bank 0

    ; レジスタ名定義(してたりしてなかったり、してるのに使ってなかったり)
    PPUMASK = $2001
    PPUADDR = $2006
    PPUDATA = $2007

    ;(グローバル)変数定義
    ;RAMの連続したアドレスにラベルを付けたいだけなのだがどうもROMに値をとっている。
    ;どうせ上書きされる場所で実害ないからいいけど、もっとまともな方法があるのだろうか…。
    ;まあとりあえず今回はこのままでいいや。
        .org $0000
    chrptr .dw 0 ;CHR-RAM書き込み時のポインタ
    cnt1 .db 0 ;カウンタ変数
    sprpos .db 0 ;スプライト書き込み時のポインタ
    posx2 .db 0 ;何だっけ

    ;割り込みベクタ
        .bank 1     ; 0x2000(8KB)単位。ファイルが全16KBなのでbank3でなく1。
        .org $FFFA  ; BFFAだけど。

        .dw intvb   ; VBlank割り込み
        .dw init    ; リセット割り込み。起動時とリセット時
        .dw 0       ; ハードウェア割り込みとソフトウェア割り込み

        .bank 0
        .org $8000

    init:
    ; PPUレジスタに書き込みできるようになるまで時間がかかるらしいので待つ
        ldy #25
    .loop:
        dex
        bne .loop ;5*256=1280 ;30000 30000/1280=23.4375
        dey
        bne .loop

    .loop2:
        lda $2002  ; VBlank待ち
        bpl .loop2

        ; VBlankのNMIを無効
        lda #%00110000
        ;     ^||||||| VBlank開始時にNMI発生
        ;      ^|||||| PPUマスター/スレーブ
        ;       ^||||| スプライトサイズ 0:8x8 1:8x16
        ;        ^|||| BGパターンテーブル
        ;         ^||| スプライトパターンテーブルアドレス
        ;          ^|| VRAMアドレス増分 0:1 1:32
        ;           ^^ ベースネームテーブルアドレス 0:2000 1:2400 2:2800 3:2C00
        sta $2000
        lda #%00000110 ; 初期化中はスプライトとBGを表示OFFにする
        sta $2001

        ; パレットRAM=$3F00
        lda #$3F
        sta $2006
        lda #$00
        sta $2006

        ldx #$00
    loadPal:
        lda pallet, x
        sta $2007
        inx
        cpx #32
        bne loadPal

        lda    #50
        sta    posx2

    setBg: ;BGのネームテーブルを適宜セット
    ; 00,01,...0F,00,01,...0F
    ; 10,11,...1F,10,11,...1F
    ; ...
    ; F0,
    ; 00,
    ; ...
    ; D0,
        ; $2000: BG1 name table
        lda #$20
        sta $2006 ;PPU_ADDR
        lda #$00
        sta $2006
        ldx $00
        lda #30
        sta cnt1
        ldy #0
    bgLoop0:
        ldx #16
    bgLoop1:
        sty PPUDATA
        iny
        dex
        bne bgLoop1
        tya
        sec
        sbc #$10
        tay
        ldx #16
    bgLoop2:
        sty PPUDATA
        iny
        dex
        bne bgLoop2
        dec cnt1
        bne bgLoop0

    ;CHR-RAM書き込み
    setChrRom:
        lda #0
        sta chrptr
        lda #high(chr)
        sta chrptr+1
        ldy #$00
        sty PPUADDR
        sty PPUADDR
        ldx #30

    .loop
        lda [chrptr],Y
        sta PPUDATA

    ; 8: タイル縦px数 000yxxxx ~ 111yxxxx
    ;Y+=20
        tya
        clc
        adc #$20
        tay
        bcc .loop ; if no carry loop
    ; 2: 色bit 0000xxxx, 0001xxxx
    ;Y+=10
        tya
        eor #$10
        tay
        and #$10
        bne .loop ;if !y&10 loop
    ; 16: 横タイル数 00000000 ~ 00001111
        iny
        tya
        and #$0F
        bne .loop; if neq loop
    ; 30: 縦タイル数
        ldy #$00
        inc (chrptr+1)
        dex
        bne .loop

    setChrRomOverlap: ;BGのベースを変える境目でオーバーラップさせておく
        ldy #$1F
        sty PPUADDR
        ldy #$00
        sty PPUADDR

        lda #high(chr)+$0F
        sta chrptr+1
        ldy #0
    .loop
        lda [chrptr],Y
        sta PPUDATA

    ; 8:タイル縦 000yxxxx ~ 111yxxxx
    ;Y+=20
        tya
        clc
        adc #$20
        tay
        bcc .loop ; if no carry loop
    ; 2:パレットbit 0000xxxx, 0001xxxx
    ;Y+=10
        tya
        eor #$10
        tay
        and #$10
        bne .loop ;if !y&10 loop
    ; 16: 横タイル数 00000000 ~ 00001111
        iny
        tya
        and #$0F
        bne .loop; if neq loop


    ;属性テーブル (BGパレット)
        ldy #$23
        sty PPUADDR
        ldy #$C0
        sty PPUADDR
        ldx #8
    attrloop:
        lda #$00 ; 左半分 00 00 00 00
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        lda #$55 ; 右半分 01 01 01 01
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        dex
        bne attrloop

    ;scroll
        lda #0
        sta $2005
        sta $2005

        ; スプライト配置
        lda #$00
        sta $2003 ; OAMADDR

    ; Byte 0-3: Y,TileNo,Attr,X
    ; Attr:
    ;76543210
    ;||||||||
    ;||||||++- Palette (4 to 7) of sprite
    ;|||+++--- Unimplemented
    ;||+------ Priority (0: in front of background; 1: behind background)
    ;|+------- Flip sprite horizontally
    ;+-------- Flip sprite vertically

        ldx #64
    sprLoop:
        lda #$7B ; BGのベースの切り替わりあたり
        sta $2004
        lda #$E1 ; 1:E0 空スプライト
        sta $2004
        lda #$00
        sta $2004
        lda #$B0 ; 特に意味はない
        sta $2004

        dex
        bne sprLoop

        ; 表示開始
        lda #%00011110    ; スプライトとBGの表示をONにする
        sta $2001

    intvb:
        lda #%11100000 ;BGパターンテーブルベース=0
        sta $2000

    waitVBlankEnd: ;VBlank終了時点でスプライトオーバーフローフラグが消えるのを待つ
        lda $2002
        and #$20
        bne waitVBlankEnd
       
    waitSprOvr: ;スプライトオーバーフローフラグが立つのを待ってBGパターンを切り替える
        lda $2002
        and #$20
        beq waitSprOvr

        lda #%11110000 ;BGパターンテーブルベース=1
        sta $2000

    waitInt:
        jmp waitInt

    ;色0・色1
    C0 = $0F
    C1 = $30

    pallet:
        .db C0, C1, C0, C1, C0, C0, C1, C1, C0, C0, C0, C0, C0, C0, C0, C0
        .db C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0

    ; 画像データ(生)
        .bank 1
        .org $A000
    chr:
        .incbin "rawfile.bin" ; 画像。256×240/8=0x1E00バイト

    ; 画像データ(BMP)
    ;    .bank 1
    ;    .org $A0C2
    ;    .incbin "bitmap.bmp" ; 画像。MSペイントなどで作成したモノクロビットマップ
    ;    .org $A100
    ;chr:
      

  • Atari2600詳細
    2015年04月13日 01:20

    アタリショックで有名なAtari2600。
    フェアチャイルドチャンネルFに続くカートリッジ交換式ゲーム機であり、ファミコンの前世代機としてアメリカで流行していたらしい。
    日本では流行らなかったため日本語での情報が少ないのが残念だ。とりあえず日本語で一番詳しくなるくらいに仕様をまとめてみる。
    性能が分かりやすいよう、日本人に馴染みの深いファミコンとの比較を各所に入れた。

    スペック

    ・CPU
    6507、1.19MHz
    この6507というCPUは6502の機能削減版で、割り込みが存在しないことと、メモリ領域が6502の64KBに対し8KBしかない点が違いである。
    ファミコンのCPUもBCD演算が無い他は機能は6502と同一なので単純に比較すると、ファミコンは1.79MHzなので、演算性能はファミコンの2/3と言えそうだ。
    が、Atari2600はファミコンと違い画面の描画にCPUを使うため、実質的に使用できるCPU時間は非表示期間中のみである。
    Atari2600の一般的な解像度は縦192pxなので、262-192=70と、1/4程度の時間しか演算に使えない。
    ファミコンは単純な(ラスタスクロールなどしない)静止画を表示する分にはCPUを使わないので、これと比較すると演算性能はファミコンの1/6程度とも言える。
    なおAtari2600も画面の描画を止めればCPU時間を演算に割くことができる。
    例えば「Vedeo Chess」ではAIの思考中は画面がチカチカする単色になる。
    https://www.youtube.com/watch?v=wdpRDYQgm84

    ・メモリ
    ワーキングRAM128バイト、VRAM数バイト
    ファミコンはワーキングRAMが2KB、VRAMが2KBである。
    ワーキングRAMが6502の高速なゼロページ領域に収まっているのでちょっと嬉しい。
    なおワーキングRAMは6502のスタック用の1ページにも重複してマッピングされているという面白い構造。
    128バイトでは少ないということでカートリッジ側に128バイトや256バイトのRAMを積むこともあったようだ。

    画面表示

    ・解像度
    横解像度は回路で決まっており、160ドットである。
    問題は縦で、(NTSCでは)192pxという値がよく見られるが、これはこの値が多くのゲームで使われているとか、この値が推奨されている程度の意味だろう。
    Atari2600の画面描画の特徴として、メモリには常に走査線1ライン分の情報しか持っていない。
    そこで、1ラインごとに、HBLANKの間にメモリ上の情報を書き換えて画面を作るのが基本の操作となる。描画中に何もしなければ縦線しか描けない。
    つまり、縦方向にどれだけの長さ描画するかはプログラムによって決まる。
    これより、NTSCの規格上許されている最大値をAtari2600の縦解像度の値とするのが妥当だろう。
    (規格外の走査線数を出力することすら可能だが、さすがにそれは考えないことにする)
    NTSCの規格上、映像信号を入れられるのは1フィールドあたり242.5ラインだが、NTSCの規格は2フィールドを半ラインずらすことになっているのをAtari2600などのゲーム機はずらしていないので、242pxとなる。

    なおこの192pxというのは242の約79%にあたる。
    横方向の160pxは44.7μ秒ほどで、描画期間約53.4μ秒の83.5%ほど。
    当時のTVで画面内に表示できる範囲はこの程度だったのであろう。

    さてではこの160×242ドットに自由に絵が描けるかというとそうではない。
    画面構成は後述するが、1ドット単位で絵を作れる構成要素は「オブジェクト」という、今で言うスプライトのみである。
    ファミコンのスプライトに横8枚制限があるのと同様、Atari2600もオブジェクトだけで画面全体を覆うことはまず不可能である。(たぶん本当に不可能だと思うが自信がない。後述)
    もう1つの構成要素としてPlayFieldという今で言う背景面のようなものがあるが、こちらは横解像度が40px、つまり160pxの画面に対し4pxごとにしか絵を描けない。
    これにより、Atari2600の画面はやけに解像度の低い背景の上にキャラクターがぽつぽつと置かれている独特な見た目になる。
    ただし、横は4ドット単位だが縦方向には1ドットごとに絵を書き換えられるため、上手いプログラムでは巧妙に横方向の解像度の低さを感じさせない絵柄を作っている。
    例えば「Pitfall!」の穴や木の表現が上手い。
    Atari2600 Pitfall!

    ・画面構成
    BackGround:
    以降の全てが無い部分の色

    PlayField:
    今で言う背景面
    単色。(Scoreモードでない場合)
    横解像度40px。
    拡大縮小やスクロールなどの機能は無い。
    「Scoreモード」では左右それぞれがPlayer0/1と同色で表示される。

    オブジェクト:
    今で言うスプライト。3種類5個のオブジェクトがある。
    Player0 / Player1
    横8px、それぞれ単色。
    一定間隔の2個または3個に増殖したり横幅2倍・4倍で表示する機能あり。
    具体的には、以下の8通り。2倍幅2個などといった自由な組み合わせはできない。
    Atari2600 Playerの表示パターン
    4倍幅モードでは横32pxと画面横幅の1/5。2つのPlayerで計2/5。ファミコンではスプライト上限横8個で画面の1/4なので、巨大キャラを動かすことについてはファミコンに優っていると言えなくもない。
    (まあファミコンなら背景面を使うところだが)

    Missile0 / Missile1
    横1px
    横幅2・4・8倍表示が可能
    対応するPlayerと同色
    個数・間隔もそれぞれのPlayerと同じ

    Ball
    横1px
    横幅2・4・8倍表示が可能
    色はPlayFieldと同色
    増殖はしない

    ・発色数
    扱える色は、オリジナルのNTSC版では
    (15色相+無彩色)×明度(?)8段階 = 128色
    PAL版では色信号の周波数が約15/12倍である関係か、
    (12+無)×8=104色
    と少々少ないが、どちらもファミコンの54色と比較すると多い。
    SECAMでは仕組みが全く違い明度に対応したデジタル8色のみ。

    なお「明度」と書いたが、彩度も連動して変わるかもしれない。(ファミコンがそうであるように)

    同時発色数は、1ライン中に同時に出せる色は基本的には以下の4色である。
    ・BackGround
    ・PlayField・Ball
    ・Player0・Missile0
    ・Player1・Missile1
    ただしライン中にレジスタを書き換えればこの限りではない。
    例えばAcidDropというゲームがあり、横1列に色違いの7区画+両端の色を表示している。
    https://www.youtube.com/watch?v=5Po47y0ALpU
    また、頑張れば下図のように横15色も可能である。
    Atari2600 1列に15色

    なお縦方向には制限はないので、ファミコンと違い画面中同時発色数は扱える全ての色128色が出力可能。縦方向のグラデーションはAtari2600の特徴である。

    ・オブジェクトの横位置の指定
    現代の感覚で考えれば、オブジェクトのX座標をレジスタに指定するとその位置に書いてくれる仕組みがあると思うだろう。
    否、そんな軟弱な仕組みではない。
    走査線を描画中にここぞというタイミングで各オブジェクトのリセットレジスタに書き込むことで、その地点のX座標に(次ラインから)オブジェクトが表示されるようになるのである。

    とはいえCPUの1クロックは画面上の3ドットに相当する。また時間調整のためのループは最短で1周に5クロックが必要であり、都合15ドットごとにしか書込みができない。
    そこで微調整機能が用意されている。
    5種のオブジェクト(Player0, Player1, Missile0, Missile1, Ball)それぞれについて横位置の差分を登録し、HMOVEレジスタに書き込むことで次ラインでの表示位置がその値だけずらせる。
    これが+7~-8まで設定できるので、任意の位置にオブジェクトを表示することが可能となる。
    またBallやMissileを表示したままラインごとにHMOVEをすることで斜めの線が表示できる。上のPitfall!の画像にもある。ファミコンにはできない芸当だ。
    なおこのHMOVEへの書込みを行うと、次ラインの左端8ドットが黒になるという妙な仕様があり、これによる櫛状の横線がAtari2600の画像の特徴となっている。

    高等技術

    プログラミング技術が成熟するにつれ、走査線の描画中にレジスタ内容を書き換えることで同時表示内容の限界を超えるテクニックが使われるようになる。

    ・PlayField内容の書き換え
    実は上で横40pxと言ったもののデータは20px分しか無く、1ライン中何もしなければ右半分が左半分と同じか、あるいは左半分を左右反転させたもので表示される。
    横40pxをフルに使うにはライン中の書き換えが必須である。
    なおこれは最初から想定されていた操作だろう。PlayFieldのScoreモードはライン中書き換えを前提としている。

    ・Player内容の書き換え
    Playerを複数表示モードにした状態で、1つ表示された後に内容を書き換えると、2つ目に別の内容を表示できる。
    これを最大限に駆使すると、Player0/1を3つづつ交互に並ぶようにして横48pxに自由に絵が描ける。
    ただし、このためにはメモリ(レジスタ)へ4回の書込みが必要になる。(CPUのレジスタとメモリにマップされたレジスタが紛らわしいな…)
    6502では(CPUの)レジスタからメモリへの書込みが最速で3クロック掛かる。画面のドット数で言うと9ドット分である。
    [--P0--][--P1--][--P0--][--P1--][--P0--][--P1--]
            ^P0      ^P1      ^P0      ^P1
    このように、猶予は4ドット分しか無く、書込み以外の操作を入れる余裕は無い。
    かつ、6502での最速の書込みはレジスタが3つしか無いため3連続が限度である。
    よって普通にはこの表示はできないことになる。

    この状況を打破するためには裏レジスタを使う必要がある。
    裏レジスタを理解するには、Vertical Delay(垂直遅延)機能の理解が必要である。
    この機能を有効にすると、Player0/1の書き込みはそれぞれの裏レジスタになされ、他方への書き込みを契機に表レジスタに移される。
    つまり、Player0/1の内容の書込みを他方への書込みまで遅延させるという動作をする。
    「Vertical」Delayの名は、1ラインごとにPlayer0と1へ交互に書き込む場合に1ライン分縦に遅延することになることによる。
    通常の使い道としては、Player0と1を両方1ラインごとに書き換えるのは処理が重いため、1ライン置きで交互に書き換えるようにしたとき、縦位置がずれないように使うものだろう。

    これを利用して、P0,P1両方の遅延を有効にし、非表示期間中にPlayer0,1に加えPlayer0の裏レジスタに表示内容を入れておく。
    すると、
    非表示期間:
    lda A
    sta P0 ;      P0裏=A
    lda B
    sta P1 ;P0=A, P1裏=B
    lda C
    sta P0 ;P1=B, P0裏=C
    lda D
    ldx E
    ldy F
    描画中:
    sta P1 ;P0=C, P1裏=D
    stx P0 ;P1=D, P0裏=E
    sty P1 ;P0=E, P1裏=F
    sta P0 ;P1=F, P0裏=D(表示されないのでstxでもstyでもよい)
    このように最速での4連続書込みが可能となる。

    ・オブジェクト増殖
    通常はリセットレジスタに書き込んだ次のラインからオブジェクトが表示されるのだが、Playerの2個・3個表示モードでは頭の1つを除いて同ラインに表示できる。またBallも同ラインから表示される。
    これを利用して、ライン中に複数回リセットをすることで1ライン中に複数個のPlayerを表示できる…らしい。
    これを使えばひょっとして画面を完全にオブジェクトで覆うこともできたりするのだろうか。恐らくは処理時間が足りず不可能だと思うが、この動作についてはいまいち仕様が理解できていないため自信がない。仮にできても意味のある絵を表示するのは困難だろう。
    この動作については下記「TIA Hardware Notes」が詳しいので気になる人はこれにあたってほしい。

    その他機能

    ・音源
    数種類のノイズと中途半端な音程の矩形波を出せるチャンネルが2チャンネル。
    あまり興味が無いので説明は割愛する。
    「Tone Toy」というHomebrewなソフトがあるので試してみると楽しいだろう。
    https://www.youtube.com/watch?v=Zco1hnyDiVI

    ・衝突検知
    ファミコンにも0番スプライトと背景面の衝突判定があるが、Atari2600ではPlayField, Ball, Player0/1, Missile0/1の6つの物体の全ての組合せ15通りについて衝突判定ができる。
    6502の「BIT」命令で読みやすいよう6,7bit目が立つ。

    ・タイマー
    ファミコンにもあれば良かったのに…。

    参考文献

    ネット上でいろいろと探した気がするが、結果的に以下の2つにだいたい全てまとまっていた。
    Atari 2600 Specifications http://problemkaputt.de/2k6specs.htm
    Atari 2600 TIA Hardware Notes http://www.atarihq.com/danb/files/TIA_HW_Notes.txt  

  • ゲームボーイ始めました。
    2015年03月28日 13:04

    ゲームボーイで猫耳
    ファミコンAtari2600GBAに続いて、ゲームボーイにも手を出してみた。

    やっと最近の話になった。冒頭の画像ができたのが今年の2/11である。

    GBは実機で動作させるのが簡単そうな気がするのが嬉しい点。
    たぶんFlashROMを1個用意してカートリッジの線につなげばできると思う。
    これがファミコンだと2個要るし、GBAだとなんだか特殊な回路が入ってるようなことを見た記憶がある。

    CPUは80系ということで初めて触るCPUだった。
    8080→8086→80x86なのでx86と同系統なのだが、x86は触ったことがあったがレジスタ名に多少面影を感じる程度であった。
    新しいCPUのアセンブラを触るのは楽しいのでこれはこれでよい。

    開発環境はググって見つけたGBDKというものを。
    ルートに展開せよと書いてあるがどうせパスの日本語に非対応という事だろうといつもの場所に置いてみたが、どうもパスの指定の関係でルート直下でないと動かないようだ。
    C言語コンパイラもついてるけど、やっぱりアセンブラだよねー。
    アセンブラのサンプルコードも(1個だけ)ついてるので安心。

    アセンブラの起動方法はなんだかんだで手間取ったが分かってみれば簡単だった。
    ..\bin\lcc -Wa-l -Wl-m -o temp.gb temp.s

    ちょっと困ったことに、この開発環境でアセンブルすると先頭のヘッダあたりを自動で生成してくれるのだが、その中の割り込みハンドラ部分が使い方がよく分からない。
    このようなことはよくある。
    CPU自体の割り込みの扱いはそのCPUを使おうとしている時点で大抵理解しているし、分からなかったらいくらでも資料があるので調べればよいだけなのだが、ある特定の開発環境でだけ使われるコードなど知らないし、調べようにもその付属のドキュメントが不足していたらそれまでである。だいたい、CPUが使いたいのであって開発環境を使いたいわけでもないのに、それを調べなくてはいけないというのはなかなかやる気を殺がれるものである。
    まあコードを追って一応使える程度には理解した。

    GBの面白いところとして、全画面に任意の画像が「頑張れば」表示できるという点がある。
    ファミコンのようにキャラクタ数が足りず明らかに不可能でもなく、
    GBAのようにビットマップモードがあって容易に可能でもない。

    GBの全画面を覆うには20*18=360キャラクタ必要。
    しかしGBで背景面に一度に表示できるキャラクタ数は256個。普通には不可能。
    だが、表示する256個のキャラクタは384個の内から2通りの選び方ができる。
    これを、画面の中程で切り替えることで、全画面に任意の表示ができるのである。

    それで全画面表示をしたのが冒頭の画像なのだが、ユニークタイル数を数えてみたら181。普通にも表示できた画像であった。
    単純に減色した画像では芸がないと思って綺麗にベタ塗りにしたのが災いした。

    なおGBカラーになると同時表示キャラクタ数が512個になるので全画面に別のキャラクタを表示することが容易に可能になる。その代わり色数の面で全画面任意とはいかなくなるのだが。

    あと画像表示の前に試したハローワールド
    ゲームボーイ_ハローワールド
    と、7×7(8×8)ドットでは芸がないので5×7ドットのテキストを表示…してみようとしたが2文字で力尽きた。
    ゲームボーイで5×7ドットフォント

    あとエミュレータはGBAでも使っていたので何も考えずVisualBoyAdvanceを使ったが、どうもGBエミュレータではBGBなるものが再現度が高いようだ。  

  • Atari2600はじめました。
    2015年03月24日 01:19

    Atari2600で猫耳1
    ファミコンGBAに続き、Atari2600の開発を始めてみた。
    …と思っていたのだが、Twitterのログやファイルの日付を見ると2011年5月あたり、GBAより前だった。やってすぐに書かないとこういうことになる。

    Atari2600と聞いてもピンと来ない人もいるかもしれない、というか自分自身始める前はおぼろげに名前を聞いたことがあるかという程度だった。
    日本では流行らなかったけれどアメリカの方ではファミコンの前世代機として一時代を築いた名機らしいのだが。

    このAtari2600、当然(総合的な)性能はファミコンを大きく下回るわけだが、画面の描画方法が他のゲーム機には無い独特なもので、挑戦しがいのある機械である。
    詳細な仕様については別に書こうかと思うが、ごく簡単にまとめると以下のようになる。 (4/13追記: 書いた→Atari2600詳細)
    ・画面解像度は横160ドット
    ・背景面は横4ドット単位でしか描けない
    ・スプライトは計5個
     横8ドットの絵が描けるものが2個
     横1ドットが3個
     (スプライトは複製or拡大できるが、詳細は割愛)

    縦は? と思うだろうが、ここがAtari2600最大のポイント。なんとVRAMを走査線1本分しか持っていないため、何もしなければ縦縞しか描けない。絵を作るには1ラインごとにVRAMを書き換える必要がある。
    さらに走査線の描画中でもお構い無しにVRAMを書き換えることができて、スプライトの3倍複製モードで描画するそばから書き換えることで6キャラクタ分48ドットの任意の画像を表示する高等テクニックがある。(実際にはそれだけでは書き換え速度が足りないので裏レジスタを使うというさらにひとひねり必要。詳細は割愛)

    そのテクニックを使って描いたのが冒頭の画像。
    Atari2600のCPUはファミコンと同じ6502(正確にはその機能削減版の6507)なのでプログラムは楽であった。
    なお開発環境はdasm、エミュレータはz26。
    dasmでアセンブルするとなぜか出力ファイルの頭に謎の2バイトが付いてそれを消さないとz26で動かなかった。なんだかよく分からないがとりあえず備忘録的に書いておく。

    あとつい最近またAtari2600をやりたくなって描いた絵を2枚。
    Atari2600で猫耳2
    Atari2600ではちゅねミク
    プログラムもちょこちょこ変えているが外見はただ絵が変わっただけ。下の数字はエミュレータの機能。
    はちゅねミクの絵で分かる人には分かると思うが、次はアニメーションさせたい。  

  • GBAはじめました。
    2015年03月23日 02:04

    GBA_HelloWorld

    ファミコンに続き、ゲームボーイアドバンス(以下GBA)の開発を始めてみた。3年前に。
    Twitterのログやファイルの更新日時を見るに、2012年5/30~6/6くらいにやっていたようである。
    なぜ今頃書くのかというと、そろそろネタを書きためているテキストファイルの見通しが悪くなってきたので整理するためだ。前回のCボタンユニットもそれである。

    開発環境はdevkitProというもの。これが環境を整えるのがすごく楽だった。インストーラの指示に従ってあとは環境変数を書くだけ。
    言語はとりあえずC言語。まあARMくらいの性能になると敢えてアセンブラを使う理由も薄いだろう。
    ARMの命令セットは条件実行機能が面白そうだから使ってみたいんだけどね。

    GBAではフルスクリーンの1枚絵を何の制限もなく表示できる。GBカラーやファミコンではできなかった芸当だ。
    GBA_15bitカラー1枚絵
    GBAの画面モードは色々あるが、これはGBAの最大色数である15bitカラー表示。背景数は1枚のみである。
    使用した画像データはこちら。
    GBA用画像_青肌
    GBAの色データはxBBBBBGGGGGRRRRRであるが、GIMPで出力する15bitカラーはxRRRRRGGGGGBBBBBしか出せず、RとBを入れ替えて書き出しているため、不気味な色になってしまった。上下反転はBMP画像の特徴。

    もう1枚、こちらは256色インデックスカラー。このモードでは背景面が2枚持てる。
    GBA_インデックスカラー1枚絵

    次はGIF画像を表示したり簡単な落ち物ゲームを作ろうかと思っていたようだが力尽きたようだ。
    その後最近になって(14年8月~11月あたり)、1カートリッジプレイで無改造で実機で動作させることを目指してPICで通信実験などしていた。
    そのうち再開したい。  

  • Cボタンユニット七変化
    2015年03月19日 01:56

    「ニンテンドウ64のコントローラの特徴は?」と問えば十人が十人「3Dスティック」と答えるであろう。
    アナログスティックを標準搭載したことでまさに「ゲームが変わる、ロクヨンが変える」を実感したものだ。

    だが、3Dスティックの陰に隠れて重要な脇役がある。「Cボタンユニット」だ。
    今日はCボタンユニットへの愛を語ろうと思う。

    Cボタンユニットはただの4つのボタンでしかないのだが、配置の妙によってさまざまな使い方ができる。

    単純4ボタン

    まず第一に、Cボタンユニットは実体としてあくまで4つの別々のボタンである。
    よって4つのモノを選択するのに使用できる。
    同様のことは十字キーでもできるだろうが、十字キーと違い斜め入力をしてしまう危険がないという利点がある。
    またゲームキューブのABXYボタンでも可能だが、4つが整然と並んでいること、主となるABボタンが残せることが優っている。
    例: ポケモンスタジアムのバトル中の技選択
    Cボタンユニット七変化の1
    ポケモンの4つの技を上下左右に対応付けることで、すべての技を1動作で選択することができる。
    これにより、本編での十字キーでカーソルを動かしAで決定する形と異なり、画面上に選択肢を表示する必要がなくなる。1画面でポケモン対戦をするために相手の技選択を見せないことは必須事項である。
    ゲームキューブのABXYボタンでも4つの選択は可能だが、並びが整然としていないため画面上の表示が不恰好になってしまうし、技の使用以外の動作がRやLなどの不自然なボタンに飛ばされてしまうだろう。

    4方向指示

    4つのボタンが正方形に並んでいるため、3Dスティックに次ぐ第2の方向入力として使える。その用途を示すように上下左右のマークも付いている。
    例: パイロットウィングスのカメラ
    Cボタンユニット七変化の2
    3Dスティックで自機を動かし、Cボタンユニットでカメラを動かせる。
    十字キーでも同様の操作は可能だが、コントローラー左側には十字キーとLボタンしかないのに対し、右側にはCボタンユニットとRボタンに加えA・Bボタンがあるため、上位互換と言える。
    コントローラーの左と真ん中を持つレフトポジションを使うソフトがほとんど存在しないことからその優位性は明白だ。


    そして、必要な分だけ切り売りして自由な組み合わせで使える。

    2方向指示×2

    例: マリオ64のカメラ
    Cボタンユニット七変化の3
    左右で回り込み
    上下で寄り・引き
    これは十字キーでも可能だが、斜めを押してしまう危険が無い分、上下と左右に別の動作を割り振るにはCボタンユニットの方が向いている。

    2方向指示+1+1

    例: F-Zeroの操作
    Cボタンユニット七変化の4
    上は視点変更
    左右はカメラ操作
    下はブレーキ

    3ボタン+1ボタン

    例: ゼルダの伝説のフィールド上操作
    Cボタンユニット七変化の5
    左下右で3つのアイテムを使用
    上は視点変更および妖精の呼び出し

    このような細かい分割もお手の物。


    さらには、絶妙な配置によりA・Bボタンとも組み合わせることすら可能である。

    4ボタン+A

    例: ゼルダの伝説のオカリナ(など楽器)操作
    Cボタンユニット七変化の6
    A、下、右、左、上の5ボタンがこの順で音高に対応している。
    Cボタンユニットが無ければこのオカリナ操作は生まれなかっただろう。
    実際、のちのシリーズでも音楽を演奏するギミックは毎回のように入っているのだが、
    風のタクトの風のタクトも、トワイライトプリンセスの遠吠えも、スカイウォードソードの琴も、皆オカリナのような自由演奏は不可能である。
    (夢幻の砂時計の笛はまあ頑張れば演奏できなくもないかな…)
    また64版ゼルダの移植版も、ゲームキューブは単純にA+Cスティックに対応させただけで操作しづらい上に音高の並び順が直感に反しており、3DS版では順にLRYXAと広範囲に散らばってしまっている

    4ボタン+A+B

    例: ポケモンスタジアムのポケモン選択
    Cボタンユニット七変化の7
    この作品では当時主流であった「6匹を見せ合い、相手を見た上でバトルに出す3匹を選ぶ」という対戦形式を公式に取り入れた。
    このために、Cボタンユニットに加えA・Bボタンを合わせた6つのボタンを6匹のポケモンに割り当て、3匹の選択を行う。
    ここでABとCボタンユニットがほぼ2×3の長方形に並んでいるため、順番が分かりやすい。
    技の選択と同じく、ここでも相手に見せずに確実に選択することが必要になる。これもCボタンユニットにしかできない芸当だろう。
    十字キーやアナログスティックで6方向を選ぶのはきちんと押せているか不安になる。GCのABXYLRでも可能だが並びが不自然である。


    その他

    4つ使い切る必要もない。
    ポケモンスタジアムではオプションやメニューを開くのにC右や上が使われたり、
    GBの操作でスタート・セレクトボタンをC左・下に割り振ることができる。
    スマッシュブラザーズではCボタンはすべてジャンプに割り当てられている。

    余談

    ・ストリートファイターが64に出てたら弱中強×パンチ・キックの6ボタンがAB+Cボタンユニットに綺麗に収まったろうに。
    ・6点で点字を打つのに便利そうだ。  

  • ファミコンの縦解像度224px説の考察
    2015年02月22日 04:49

    ファミコンの解像度は横256px×縦240pxだが、縦を224pxとする説が根強い。
    中には「内部的には240px」の但し書きをつけたり、「約」224pxとするものもあるが、224という値を書いている時点で五十歩百歩だ。

    「内部的」の解釈も曖昧だが、たぶん次のような認識なのだろう。
    「メモリ上には240px分の領域が用意されているが、出力されるのはそのうちの224px分のみ」
    そのようなことがないのは、Google画像検索で適当なファミコンソフト名を「640×480」や「720×480」の解像度指定で検索すれば分かるだろう。

    ただ似たような現象はあって、240px分が出力されても、普通のアナログTVでは実際に画面に表示されるのはその内の85%や90%程度である。
    (詳しくは「タイトルセーフ・アクションセーフ」「オーバースキャン」あたりで検索するとよい)
    ではそのことを言っているのかというと、2つの点からおかしい。
    第1に、どの程度の範囲が表示されるかはTVによって異なるので、224pxという値は出てこない。「約」であっても(10進法では)切りの悪い値にするのは不自然だ。
    第2に、TVに映らないという理由で解像度を224pxなどの値にするなら、同様にアナログTVに出力するスーパーファミコンやプレイステーションも240pxや480pxを出力できないことになろうが、これらの解像度には239pxや480pxという値が平気で書かれている。

    ではこの224という数字の出処はどこかだが、スーパーファミコンにはどうやら224pxモードがある。
    これこれを見ると、$2133のbit2で縦224pxと239px(448pxと478px)の切り換えができるようだ。
    またプレイステーションについても、興味が無いのであまり調べていないが、224pxや448pxのモードがあるような記述が見られる。

    もう1つの可能性として、エミュレータのオプションから来ているのかもしれない。
    エミュレータには縦240pxのうち224pxのみを表示するモードが付いている物がある。
    エミュレータ240pxモード1エミュレータ240pxモード2
    (というよりどちらもデフォルトが224pxで、240pxモードがある)

    まとめると、
    ・そもそもアナログTVの仕様として映像信号のうち画面には表示されない部分がある
    ・スーパーファミコンには縦224px出力のモードがある
    ・ファミコンのエミュレータには出力のうち縦224pxのみしか表示しないモードがあるものがある
    このあたりを混同した結果、ファミコンの縦224px説が生まれたのではないだろうか。


    さて本題の考察は以上だが、なぜエミュレータには224px設定が付いているのかの説明をしよう。
    まずこちらの画像をご覧頂きたい。
    ファミコン縦スクロール回り込み
    ドラゴンクエスト3で縦スクロール中に撮った画像だ。これは互換機だが、ファミコン実機でも同様の表示になるはずである。
    一番下の数ドットが、本来下に続くマップではなく、最上段からはみ出した分が下に回りこんでいるのが見て取れるだろう。

    これはファミコンの仕様で、内部的に2画面分を縦または横につなげた仮想画面をスクロールしているため、縦か横どちらか、この場合縦は1画面分の幅しかなく、スクロール時に上下にまたがるキャラクタは表示が乱れるのである。
    この乱れは、上記の通り通常のアナログTVでは画面外で表示されない部分であり、ユーザーの目にはいることはまずない。
    しかしPCのキャプチャカードで全ての出力を取り込んだり、エミュレータで全てを表示すると見えてしまうわけだ。
    そこでエミュレータでは、この本来表示されない領域を隠す設定が付けられた。
    どれだけ隠せばいいかといえば、背景面を構成するキャラクタは16×16単位で設定できる(正確には色が16×16、キャラが8×8)ので、うまく作られたプログラムなら半分の8pxづつだけ隠せばよいはずである。
    (うまく作られたプログラムばかりでもないと思うが)

    また、横についても同様の乱れが起こる。さらに縦と違う点として、ファミコンは横方向はもともと映像表示期間の90%強しか使っていないため、少し広めのTVではこの乱れが画面内に見えてしまう場合がありうる。
    おそらくそのために、ファミコンには左のみ8pxを表示しない設定がある(両側でないのはなぜだろう)。上のドラクエ3の画像もそうなっている。左右の黒帯の幅が違うことから分かるだろう。
    これを考えると、ファミコンの解像度は248×240と256×240の2モードあるとすべきだろう。

    2016/09/04追記
    左8pxを表示しないモードについて、スクロール時の乱れを見せないためだろうと推測したが、おそらく違った。
    ファミコンのスプライトは横8px幅であり、そのX座標は0-255の値をとり、0で左端、248で右端、249-255では右端からはみ出す位置になる。つまり画面の右端からスプライトの一部分が見える表現はできるが、左端で同じことはできない非対称性がある。
    ここで左端8pxを背景・スプライトともに隠すことで、左端からスプライトの一部分が見える表現が可能となる。
    両側でない理由もつくのでこれがこの機能を付けた理由で間違いないだろう。乱れを見せないためにも使えたかもしれないが、少なくともメインの目的ではないだろう。
    なおこちらによればMSXやSG-1000ではスプライトの表示位置を32pxずらす機能があったらしい。