Mais um blog inútil.

Abril 19, 2009

Arvorezinha – 6502 Assembly (NES)

Filed under: Arvorezinha — falso @ 20:44

Olá jovens garotos!

Desde sexta-feira que andei com ideias de fazer uma arvorezinha para a clássica NES, comecei então a minha jornada de ver como funcionava esta maravilha da minha juventude!
Achei o site nesdev que tem montes de informação sobre a consola, e tambem tem alguns sores para algumas roms, mas não achei nada que printasse texto para o ecrã, que é o pretendido. Num dos textos que li vi uma referência ao canal #nesdev da efnet, então lá fui eu, e não é que existe uma grande comunidade de pessoal que percebe e ama isto, e pelos vistos são todos amigaveis, falei la com uns deles, e um deles (não me recordo do nick...) enviou me um zipzinho com 4 tutoriais, e o ultimo deles escreve "Hello World" pro ecrã, copiei descaradamente do codigo desse.

Aqui segue o sores:

.include "nes.h"

POS1 = $0300 ; Endereço de memoria onde vai guardar o primeiro byte da posição de print inicial 0x21cd
POS2 = $0301 ; segundo byte

COUNTER1 = $0310 ; primeiro contador
COUNTER2 = $0311 ; segundo contador

TEMP1 = $0320 ; variavel temporaria usada para comparar com 5

CURBG = $0325 ; utilizado pra guardar o indice actual da palete para a cor do background

; The iNES header tells the emulator which circuit board to emulate.
; The emulator can see it, but the emulated NES cannot.
.segment "INESHDR"
  .byt "NES", $1A  ; these four bytes identify a file as an NES ROM
  .byt 1  ; size of PRG, in 16384 byte units
  .byt 1  ; size of CHR, in 8192 byte units
  .byt 0, 0  ; mapper, mirroring, etc.  You'll learn these later.

.segment "ZEROPAGE"
; Reserve 1 byte for the variable 'retraces', used to detect
; the vertical blanking interrupt
retraces: .res 1

; Now the NMI routine actually does something!
.segment "VECTORS"
  ; NMI vector is at $FFFA, reset at $FFFC, IRQ at $FFFE
  .addr nmi, reset, irq

.segment "CODE"
.proc reset
; inicializar variaveis!!!
; variaveis pelos vistos começam em $c000
;posicao: .byte $21, $CD
  lda #$21
  sta POS1
  lda #$cd
  sta POS2
;counter1:	.byte $00
  lda #$00
  sta COUNTER1
;counter2:	.byte $00
  lda #$00
  sta COUNTER2
; define o currbg
  lda #$05
  STA CURBG

  ; Turn off PPU
  lda #0
  sta PPUCTRL  ; turn off NMI
  sta PPUMASK  ; turn off display

  ; Set up stack pointer
  ldx #$FF
  txs

  ; Wait for PPU to stabilize
  warmup1:
    lda PPUSTATUS
    bpl warmup1
  warmup2:
    lda PPUSTATUS
    bpl warmup2

  ; define palete
  lda #$3F
  sta PPUADDR
  lda #$00
  sta PPUADDR

  lda #$3A ; VERDE posicao 0
  sta PPUDATA

  lda #$3B ;
  sta PPUDATA

  lda #$3C;
  sta PPUDATA

  lda #$30
  sta PPUDATA

  lda #$16
  sta PPUDATA

  ; If you're going to turn on rendering, you don't need to reset the
  ; VRAM address.  Setting the scroll position does this for you.
  ; Turn on vblank notification
  lda #VBLANK_NMI
  sta PPUCTRL

  ; Wait for a vblank before turning the screen on
  jsr wait_vblank

  ; PPUMASK controls whether sprites are displayed and whether the
  ; background is displayed.  Here, display only the background.
  lda #BG_ON
  sta PPUMASK

  nop
  nop

  lda #CH_ALL ; ligar o chip de som
  sta SND_CHN

  lda #15 | SQ_1_2
  sta SQ1_VOL  ; for pulse 1
  lda #SWEEP_OFF
  sta SQ1_SWEEP  ; for pulse 1  

_ciclo1:
  ldx #$05 ; le o valor 5 para X - $c063
  stx TEMP1 ; grava X em TEMP1
  lda COUNTER1 ; le o contador 1 para A
  cmp TEMP1 ; compara A com TEMP1 (5)
  beq _fim ; se for igual -> _fim
  lda #$00 ; le 0 para A
  sta COUNTER2 ; define counter2 a 0

_ciclo2:
  lda COUNTER1 ; le coutner1 para A
  cmp COUNTER2 ; compara c1 com c2
  bcs _estrela ; se for menor -> estrela

  jsr proxima_linha ; manda mudar de linha

  lda COUNTER1 ; le o c1 para A
  clc ; limpa a carry
  adc #$01 ; adiciona 1 a A
  sta COUNTER1 ; guarda A em c1
  jmp _ciclo1 ; salta para o _ciclo1

_estrela:
  jsr imprime ; imprime *
  jsr proximo_char ; move a posição para o prox char

  lda COUNTER2 ; le o c2 para A
  clc ; limpa a carry
  adc #$01 ; adiciona 1 a A
  STA COUNTER2 ; guarda A em c2
  jmp _ciclo2 ; salta para _ciclo2

  _fim:

  jsr barulhinho1

  ; Wait for a few vblanks.
  jsr wait_vblank
  jsr wait_vblank
  jsr wait_vblank
  jsr wait_vblank
  jsr wait_vblank

  ; Under most circumstances, we can only update the VRAM through
  ; PPUADDR and PPUDATA at one of two times: when rendering is turned
  ; off, and in the first 2200 or so cycles after vblank starts.
  ; The PPU is continuously accessing VRAM at all other times,
  ; so the CPU has to keep its hands off.
  ; But a vblank has just started, so we're safe.
  jsr barulhinho2

  ; Spin until power off
  forever:
  jsr mudabackground
  jsr wait_vblank ; aguarda um pouco senao nem se nota
  jsr wait_vblank
  jsr wait_vblank
  jsr wait_vblank

  jmp forever

.endproc

.proc mudabackground
  ; define o endereço da cor do bg
  lda #$3F
  sta PPUADDR
  lda #$00
  sta PPUADDR

  lda CURBG ; le o curbg inicial 0x03
  sta PPUDATA
  EOR #$01
  STA CURBG

  jsr actualiza_ppu

  rts
.endproc

.proc imprime

  lda POS1
  sta PPUADDR

  lda POS2
  sta PPUADDR

  lda #'*'
  sta PPUDATA

  jsr mudabackground

  jsr actualiza_ppu
  jsr wait_vblank ; aguarda um pouco
  jsr wait_vblank
  jsr wait_vblank

  rts
.endproc

.proc proxima_linha

  ; MUDAR DE LINHA
  clc ; limpa o carry
  lda POS2 ; le o segundo byte da posição2
  adc #$20 ; incrementa 0x20 (32) , se der a volta mete o carry a 1
  sta POS2 ; guarda

  lda POS1 ; le o primeiro byte da posicao
  adc #$0 ; incrementa 0 + carry
  sta POS1 ; guarda

  ; REMOVER O NUMERO DE ESTRELAS IMPRESSAS, PARA IR PRO X INICIAL
  sed
  lda POS2 ; le o segundo byte da posição
  sec ; define o carry
  sbc COUNTER2 ; remove-lhe o numero de estrelas printadas
  sta POS2 ; guarda

  lda POS1 ; le o primeiro byte da posicao
  sbc #$0 ; remove 0 - carry
  sta POS1 ; guarda

  jsr barulhinho2 

  rts
.endproc

.proc proximo_char
  clc ; limpa a carry
  lda POS2 ; le o segundo byte da posicao para A
  adc #$01 ; incrementa 1 a A, se der a volta mete o carry a 1
  sta POS2 ; guarda em POS2 o valor de A

  lda POS1 ; le a primeira posição para A
  adc #$0 ; incrementa 0 + carry (se houver)
  STA POS1 ; guarda POS1

  jsr barulhinho1

  rts ; volta a onde foi chamado
.endproc

.proc actualiza_ppu
  ; Set the scroll position of the background by writing (x, y)
  ; pixel coordinates to PPUSCROLL.   This should always be done
  ; as the last thing before turning on rendering.
  ; The earliest programs will always scroll to (0, 0), so write
  ; 0 twice.

  ; Again, reset the VRAM address by writing to PPUCTRL and
  ; PPUSCROLL.  We need to do this every time we use PPUADDR and
  ; PPUDATA to load something into VRAM.
  lda #VBLANK_NMI
  sta PPUCTRL
  lda #0
  sta PPUSCROLL
  sta PPUSCROLL
  rts
.endproc

.proc barulhinho1
  ; The E above that B is 1318.5 Hz.
  lda #84  ; (111860.8 / 1318.5) - 1
  sta SQ1_LO
  lda #%00001000
  sta SQ1_HI
  rts
.endproc

.proc barulhinho2
  ; The B nearly two octaves above middle C is 987.77 Hz.
  ;lda #112  ; (111860.8 / 987.77) - 1
  lda #50  ; (111860.8 / 987.77) - 1
  sta SQ1_LO
  lda #%00001000
  sta SQ1_HI
  rts
.endproc

; When the PPU is told to generate interrupts on vertical blank,
; it sends a signal to the CPU that sends it here.
.proc nmi
  ; Set the 'retraces' variable up by 1 so that the main program
  ; can see that a vblank has happened.
  inc retraces

  rti
.endproc

; We don't use IRQ so don't do anything
.proc irq
  rti
.endproc

; This subroutine waits for the NMI handler to change retraces.
.proc wait_vblank
  lda retraces

  ; The 'cmp' (compare) instruction reads a value from a memory
  ; location and then performs a subtraction: register A minus the
  ; value from memory.  Then it discards the value, but it sets the
  ; minus flag based on bit 7 of the result, and it sets the equal
  ; flag if the result was zero.  So this loop will spin until
  ; the new value of retraces has become different from the old
  ; value, which indicates that the NMI handler has run.
  loop:
    cmp retraces
    beq loop

  ; To jump to the instruction after the 'jsr' that called a
  ; subroutine, use the 'rts' (return from subroutine, or return
  ; to saved) instruction.
  rts
.endproc

; Include a font.  Without a font, the NES isn't going to display
; anything.
.segment "CHR"
.incbin "ascii.chr"

Podem fazer download do sores completo, licença WTFPL. É preciso ter instalado o cc65 para assemblar.
Download da rom em ficheiro .nes para correr nos emuladores arvorezinha.nes. Utilizei os emuladores fceux e o nintendulator porque têm debuggers fixes.

Segue aqui um videozinho do que esta arvorezinha é capaz!

[hana-flv-player video='/wp-content/uploads/2009/04/arvorezinha.flv'
width="256"
height="240"
autorewind="true" /]

E já que pus suporte para videos no blol, aqui vai mais um da arvorezinha a correr num emulador de NES da Nintendo DS.

[hana-flv-player video='/wp-content/uploads/2009/04/arvorezinha_nds.flv'
width="176"
height="144" /]

Se alguém se julgar com suficientes pelos no peito, capaz de queimar isto num cartucho e correr numa nes de verdade e filmar só pelo lulz, ofereco-lhe duas internets.

Bem hajas, e até à proxima, talvez agora para a Super Nintendo!

3 comentários a “Arvorezinha – 6502 Assembly (NES)”

  1. fuck diz:

    why don't you fuck more and and leave the trees grow

  2. mirage diz:

    BEST ARVOREZINHA EVAH!

  3. madinfo diz:

    Amei a melhor de sempre mesmo...
    tenho a NES e o cartuxo... que se pode matar... se tiveres o know how para tal podemos fazelo numa tarde de caverna...

Leave a Reply for mirage

widgeon
widgeon
widgeon
widgeon