VBlank中に描け

VRAMのアクセスはVBlank中にしなくてはいけない。VBlank中かどうかを確認できたらいいと思うんだけど、そういうことはできるのだろうか。

renderLoop:
  ; VRAM に書き込むコード
  lda $2002
  bmi renderLoop  ; VBlank 中は $2002 の 7bit on

のようにしたいのだけど、$2002を read するとフラグがクリアされるので上のループは期待通り動かない。
クロック数を計るしかないのかな。29859 cycle/frame で VBlank が 22/262 scanline 分らしいので、VBlank中は2507cycle。またスプライトDMA転送は513cycleかかるらしい。
単純にバッファをネームテーブルに送る場合を考えると

  ldx size       ; 4
loop:
  lda buffer, x  ; 4
  sta $2007      ; 4
  dex            ; 2
  bne loop       ; 2

12cycle/byte なので、VBlank中に最大 200 byte 程度書き込める。1画面が 32*30 だから、それの 1/5 くらい。とこんな感じでしょうか。属性テーブルとの兼ね合いで1回に 32*4=128 を書こうとすると結構失敗する。

乱数生成

手軽に8bitの乱数っぽいものを作りたい。

N := (N xor $AA) + 73;

http://www.programmersheaven.com/mb/Console/100277/100277/nes-random-numbers/

試す。

irb(main):001:0> hash = {}; (0..0x100).inject(0) {|rnd, i| print rnd, ' '; print "\n#{i} / cycle\n" or break if hash[rnd]; hash[rnd] = ((rnd^0xaa) + 73) % 0x100 }
0 243 162 81 68 55 230 149 136 107 10 233 140 111 14 237 144 131 114 33 212 199 182 101 24 251 154 121 28 255 158 125 32 211 194 177 100 23 6 245 168 75 42 201 172 79 46 205 176 99 18 1 244 167 86 69 56 219 186 89 60 223 190 93 64 51 226 145 132 119 38 213 200 171 74 41 204 175 78 45 208 195 178 97 20 7 246 165 88 59 218 185 92 63 222 189 96 19 2 241 164 87 70 53 232 139 106 9 236 143 110 13 240 163 82 65 52 231 150 133 120 27 250 153 124 31 254 157 128 115 34 209 196 183 102 21 8 235 138 105 12 239 142 109 16 3 242 161 84 71 54 229 152 123 26 249 156 127 30 253 160 83 66 49 228 151 134 117 40 203 170 73 44 207 174 77 48 227 146 129 116 39 214 197 184 91 58 217 188 95 62 221 192 179 98 17 4 247 166 85 72 43 202 169 76 47 206 173 80 67 50 225 148 135 118 37 216 187 90 57 220 191 94 61 224 147 130 113 36 215 198 181 104 11 234 137 108 15 238 141 112 35 210 193 180 103 22 5 248 155 122 25 252 159 126 29 0
256 / cycle

おー1周した。いや偶奇があってればなんでも1周するっぽいな。LFSRっていうのを理解すればいいのかな。
線形帰還シフトレジスタ - Wikipedia
まあいいや。
http://codebase64.org/doku.php?id=base:6502_6510_maths#random_numbers
そんな感じのお手軽乱数を16bitなり32bitなりで作ってそのうちの8bitを使えば、という感じか。

MMC3(マッパー4)でラスタスクロール


ギコ猫でもわかるファミコンプログラミング - ラスタースクロール を MMC3 で書き換えたもの。画面走査のタイミングのプログラムだなんて、ファミコンはおっかない世界だねぇ。
参考文書

;;; MMC3 でラスタスクロールするテスト
;;; http://gikofami.fc2web.com/nes/nes015.html の改変
;;; giko3.pal, giko3.bkg は上のページから拾ってね

        .inesprg 1              ; プログラムバンク数
        .ineschr 1              ; CHR バンク数
        .inesmir 0              ; 水平ミラーリング
        .inesmap 4              ; mapper #4 (MMC3)

        .bank   0
        .org    $C000

SIZE:   .equ    3               ; スクロールする領域の数
lines:  .byte   120, 50         ; スクロールする領域の高さ。スキャンライン数
speeds: .byte   0, 1, 2         ; スクロールスピード

target: .equ    $00             ; 次の IRQ でこのインデックスのスクロール領域を設定する
scrolls: .equ   $01             ; 現在のスクロールX位置. ここから SIZE bytes

;;; スタート/リセット時に呼ばれる
start:
        ;; 初期化中は割り込み禁止
        sei                     ; IRQ
        lda #%00001000          ; NMI
        sta $2000
        
        cld                     ; デシマルモードクリア
        ldx     #$ff            ; スタック初期化
        txs

        jsr     vwait           ; VBlank待ち

        lda     #%00000110      ; 初期化中はスプライトとBGをOFF
        sta     $2001

        lda     #$40            ; frame counter off
        sta     $4017           ; (mapper のみ IRQ 発行)

        lda     #$01
        sta     $a000           ; MMC3 水平ミラーリング
        lda     #$00
        sta     $a001           ; これなに?

        ;; ゼロページ初期化
        lda     #$00
        ldx     #$00
.initZeroPage:
        sta     <$00, x
        inx
        bne     .initZeroPage


        ;; パレット初期化
        ldx     #$3F            ; PPU $3F00
        stx     $2006
        ldx     #$00
        stx     $2006
.initPalette
        lda     PALETTE_DATA, x
        sta     $2007
        inx
        cpx     #$20
        bne     .initPalette

        ;; ネームテーブル生成 (空と地面を480こずつ)
        lda     #$20            ; PPU $2000
        sta     $2006
        lda     #$00
        sta     $2006

        lda     #$00            ; 空
        ldx     #250
        jsr     writeVRAM
        ldx     #230
        jsr     writeVRAM

        lda     #$01            ; 地面
        ldx     #250
        jsr     writeVRAM
        ldx     #230
        jsr     writeVRAM


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

        lda     #%10001000      ; NMI割り込み許可
        sta     $2000

        cli                     ; IRQ割り込み許可
.loop
        jmp     .loop


;;; VBlank のタイミングで呼ばれる
nmi:
        lda     #$00
        sta     target

        jsr     scroll

        rti

;;; scroll でセットしたスキャンラインのタイミングで呼ばれる
irq:
        ;; レジスタをスタックに退避
        pha
        txa
        pha
        tya
        pha

        jsr     scroll

        ;; レジスタを IRQ 前の状態に復帰
        pla
        tay
        pla
        tax
        pla

        rti

;;; target 番目のスクロールをセット
;;; target のインクリメントと IRQ のセットもする
scroll:
        lda     $2002           ; スクロールクリア

        ldx     target

        lda     scrolls, x
        sta     $2005           ; set scroll X
        lda     #$00
        sta     $2005           ; Y スクロールは固定

        lda     scrolls, x        
        clc
        adc     speeds, x
        sta     scrolls, x      ; increment scroll

        ;; target をインクリメント
        inx
        cpx     #SIZE
        beq     .quit

        ;; 次の IRQ セット
        dex
        lda     lines, x        ; IRQ を呼ぶまでのスキャンライン
        sta     $c000
        sta     $c001
        lda     #$01
        sta     $e000           ; acknowledge the IRQ
        sta     $e001           ; enable the IRQ
        inx
        stx     target
        rts
        
.quit
        ;; 最後なら IRQ 終わり
        lda     #$01
        sta     $e000
        ldx     #$00
        stx     target
        rts


;;; VBlank 待ち
vwait:
.wait:
        lda     $2002
        bpl     .wait
        rts

;;; a を $2007 に x 回書き込む
writeVRAM:
.write:
        sta     $2007
        dex
        bne     .write
        rts

PALETTE_DATA:
        .incbin "giko3.pal"


        .bank   1
        .org $fffa
        .dw nmi, start, irq


        .bank 2
        .incbin "giko3.bkg"