Gå til innhold
🎄🎅❄️God Jul og Godt Nyttår fra alle oss i Diskusjon.no ×

Guide: Nintendo NES-programmering


Anbefalte innlegg

Videoannonse
Annonse

Hei, nå sitter jeg litt fast igjen..

Kode

Kompilert kode

 

Jeg har satt PPUen til å generere et NMI ved hvert V-blank. Ved NMI-rutinen (linje 166-209) har jeg satt at den skal tegne to sprites på skjermen. Posisjonene henter den fra RAM.

 

Hovedløkka til programmet (linje 66-146) går i en evig loop og oppdaterer posisjonene. Posisjonen til den første spriten forandrer seg hele tiden (en ball som spretter rundt på skjermen). Den andre spriten er kontrollert av brukeren og står stille hvis man ikke trykker på noe.

 

Problemet er at med en gang jeg har mer enn én sprite på skjermen, begynner de å blinke veldig fort hele tiden. Merkelig nok blinker det ikke når jeg holder inne ned-pilen og flytter den andre spriten nedover. Men når jeg flytter den oppover vises ingen av spritene før jeg slipper knappen... Tror du problemet ligger i at oppdatering av posisjonene ikke skjer i interrupt-rutinen?

 

Et annet antakeligvis relatert problem: Der jeg oppdaterer posisjonene venter jeg på V-blank først. (linje 66-68) Dette gir ikke helt mening i mitt hode da ingenting sendes til PPU her, kun i interrupt-rutinen som alltid skjer ved V-blank. Men hvis jeg ikke venter på V-blank vises ikke spritene i det hele tatt...

 

 

Og 3 små btw hvis du har tid:

  • Kan jeg få kontrollerne til å generere interrupts når man trykker ned en knapp?
  • Er det vanlig/mulig å dele opp kildefilene, hhv. kode-segmentet spredt over flere filer? Tenker hovedsaklig på at det kanskje kan bli problematisk etterhvert å finne unike label-navn til hver minste lille ting.
  • Jeg tar vare på registerverdiene når jeg går inn i interrupt-rutinen (burde kanskje hatt det på stacken istedet). Er dette nødvendig, eller gjør CPUen det for meg?

Lenke til kommentar

Koden din fungerer hos meg (på emulatoren Nestopia, har ikke testet på NES-en min) etter at jeg la sprite-dataene i henholdsvis byte 8 - 11 og 12 - 15. i stedet for i de to første spritene. Jeg kan ikke her og nå komme med en forklaring på hvorfor i alle dager det skal utgjøre noen forskjell, men jeg vet at det er frarådet å skrive direkte til OAM/SPR-RAM med $2003 og $2004. Det man bør gjøre er å benytte DMA-kontrolleren i CPU-en. Da må du sette av én page i RAM til å lagre en kopi av OAM. I hovedloopen tar du så å skriver de nye koordinatene osv. til RAM i stedet. I NMI kan du da bytte ut hele remsen med skriving til $2003 og $2004 med

 

LDA #0
STA $2003	  ; sett OAM-pekeren til 0
LDA #$02	   ; page 2
STA $4014	  ; sett i gang DMA fra page 2

 

Dette vil kopiere hele pagen fra $0200 til $02FF over til OAM (og det skjer mye raskere enn vi kan gjøre med LDA- og STA-instruksjoner)

 

Jeg har vært så frekk å endre koden din for å vise hvordan du kan gjøre endringene: http://pastebin.com/Qs7Jt3Ni

 

Du er ikke nødt til å vente på vblank når du bruker NMI, men det du gjøre er å lese fra $2002. Det resetter et internt vblank-flagg i PPU-en. Du bør også la hovedloopen (Sprite) vente på vblank, så den ikke kjører alt for fort. Det kan du enklest gjøre ved å lage deg en variabel som heter f.eks. vblank. Denne setter du til 0, og i NMI-rutinen setter du den til 1 (for å signalisere at vi er i vblank). I toppen av Sprite-loopen gjør du så følgende:

 

Sprite:
   LDA vblank
   BEQ Sprite        ; hvis 0, hopp opp igjen
   LDA #0              ; hvis vi har kommet her er vblank 1, sett den til 0 igjen
   STA vblank

 

Til de tre spørsmålene dine:

 

- Nei, du kan ikke få kontrollerne til å generere en interrupt når de trykkes. Du må sjekke input hver gang Sprite-loopen kjører.

 

- Ja, det er en god idé. For å se hvordan kan du f.eks. se på kildekoden til Chip's Challenge. Her er "chip.asm" hovedfilen, og denne inkluderer alle de andre filene der det er nødvendig. Filene med .inc-endelse inneholder symboldefinisjoner osv.

 

- Du må ta vare på registerverdiene selv, CPU-en tar ikke vare på noe som helst når den hopper til vanlige subrutiner. Når den hopper til interrupt-rutiner tar den bare vare på P-registeret. Det er dette stacken først og fremst er ment å brukes til. Etter hvert som du kanskje får bruk for å kalle subrutiner inne i andre subrutiner blir det ganske håpløst å holde styr på flere forskjellige midlertidige variable.

Lenke til kommentar

Har skrevet noen eksempler som tar for seg bl.a. sprites, input fra kontrollene og hvordan man lager en enkel bakgrunn (dvs. fylle nametable og attribute table med data).

 

Hvert eksempel tas opp i en egen seksjon i guiden. Selve koden er å finne her.

Lenke til kommentar

Vil prosessoren se at et tall er negativt hvis den henter det fra minnet? Jeg lagrer et tall i minne som kan være negativt i en subrutine og henter det senere. Kan jeg isåfall tvinge prosessoren til å se på det som et negativt tall dersom det er det? Det vil aldri være høyere enn $F0 (eller hva som var grensa). Har prøvd å ta vare på p-registeret, men det hjalp ikke.

Lenke til kommentar

Vil prosessoren se at et tall er negativt hvis den henter det fra minnet? Jeg lagrer et tall i minne som kan være negativt i en subrutine og henter det senere. Kan jeg isåfall tvinge prosessoren til å se på det som et negativt tall dersom det er det? Det vil aldri være høyere enn $F0 (eller hva som var grensa). Har prøvd å ta vare på p-registeret, men det hjalp ikke.

 

Prosessoren ser ikke på tallet som noe spesielt, det er koden vår som evt. må behandle tall der bit nr. 7 er satt som negative. Prosessoren tilrettelegger for dette ved å ha et flagg i P-registeret som kan fortelle oss om et tall er positivt eller negativt. Det kan være jeg misforsto spørsmålet. Hva er det koden din gjør?

Lenke til kommentar

Og kan DMA brukes litt mer generelt enn kun til OAM? Jeg vil prøve å gjøre større endringer på bakgrunnen ved hver V-blank, og det tar litt vel lang tid å skrive én og én byte..

 

Kun OAM, dessverre. Det er relativt små forandringer som kan gjøres i bakgrunnen i hver vblank-periode. Man rekker typisk ikke mer enn å fylle inn rundt 64 tiles (to rekker eller to kolonner av name table). Hvor mye man rekker avhenger av hvor effektiv koden er, selvfølgelig.

 

Hvilke forandringer er det du har lyst til å gjøre? Du kan jo slå av rendering i én frame og få gjort ganske mye på den tiden, men det vil jo merkes når spillet kjører.

Lenke til kommentar

Vil prosessoren se at et tall er negativt hvis den henter det fra minnet? Jeg lagrer et tall i minne som kan være negativt i en subrutine og henter det senere. Kan jeg isåfall tvinge prosessoren til å se på det som et negativt tall dersom det er det? Det vil aldri være høyere enn $F0 (eller hva som var grensa). Har prøvd å ta vare på p-registeret, men det hjalp ikke.

 

Prosessoren ser ikke på tallet som noe spesielt, det er koden vår som evt. må behandle tall der bit nr. 7 er satt som negative. Prosessoren tilrettelegger for dette ved å ha et flagg i P-registeret som kan fortelle oss om et tall er positivt eller negativt. Det kan være jeg misforsto spørsmålet. Hva er det koden din gjør?

 

Jeg lagrer et negativt tall i minnet, f.eks. -1. Det lagres som $FF og P-registeret antyder at det er et negativt tall. Senere i koden hentes denne verdien til A-registeret og vil da være $FF, men P-registeret sier ikke lenger noe om dette tallet som ble lagret for lenge siden. Hvordan kan jeg få prosessoren til å tolke tallet som -1 i stedet for 255? Kan jeg endre på P-registeret selv?

Lenke til kommentar

Vil prosessoren se at et tall er negativt hvis den henter det fra minnet? Jeg lagrer et tall i minne som kan være negativt i en subrutine og henter det senere. Kan jeg isåfall tvinge prosessoren til å se på det som et negativt tall dersom det er det? Det vil aldri være høyere enn $F0 (eller hva som var grensa). Har prøvd å ta vare på p-registeret, men det hjalp ikke.

 

Prosessoren ser ikke på tallet som noe spesielt, det er koden vår som evt. må behandle tall der bit nr. 7 er satt som negative. Prosessoren tilrettelegger for dette ved å ha et flagg i P-registeret som kan fortelle oss om et tall er positivt eller negativt. Det kan være jeg misforsto spørsmålet. Hva er det koden din gjør?

 

Jeg lagrer et negativt tall i minnet, f.eks. -1. Det lagres som $FF og P-registeret antyder at det er et negativt tall. Senere i koden hentes denne verdien til A-registeret og vil da være $FF, men P-registeret sier ikke lenger noe om dette tallet som ble lagret for lenge siden. Hvordan kan jeg få prosessoren til å tolke tallet som -1 i stedet for 255? Kan jeg endre på P-registeret selv?

 

Jeg tror kanskje du misforstår litt. Tallet $FF er 255, men vi kan velge å se på det som -1, siden $FF + 2 blir 1 og så videre. Det er vi som må tolke $FF som et negativt tall når vi skriver koden. Prosessoren skiller ikke mellom negative tall og positive tall når den f.eks. utfører addisjon eller subtraksjon. Den bruker ikke selv informasjonen i P-registeret til å behandle f.eks. negative tall annerledes enn positive tall. Flaggene er der for å gi oss informasjon om resultatet av den forrige instruksjonen. Når en instruksjon resulterer i et tall der den høyeste biten (bit 7) er 1, setter den N-flagget til 1 for å indikere at tallet er negativt (hvis vi bryr oss). Prosesoren bruker ikke selv N-flagget for å behandle noen tall som positive og noen tall som negative, så det har ingenting for seg å manuelt sette N-flagget til 1.

 

Dette er sikkert litt komplisert. Jeg kan prøve å beskrive det litt bedre i guiden!

Lenke til kommentar

Ah, ja jeg har misforstått. Jeg bruker vektorer for å flytte en ball rundt på skjermen. Når ballen går oppover eller mot venstre på skjermen er vektoren "negativ". Så hvis x-retning er -1 (255), og posisjon er 5, så blir neste posisjon 5 + (-1) => 5+255 som overflower og blir 4. Jeg trodde prosessoren brukte statusregisteret til å utføre negative addisjoner, men ser nå at det ikke ville vært nødvendig..

 

Jeg skulle bruke negative tall til noe annet, bare via minnet istedet, men ser nå at det ikke var det som var problemet. Jeg har valgt en annen løsning uansett så det ordna seg :)

Lenke til kommentar

Jeg skjønner absolutt ingenting av det her: Den siste linja i koden under gir range error (-689 not in [-128..127]). Jeg tok bort linjene som hadde med PLAYER2 å gjøre, og da forandra nummeret seg til -681. Hva betyr dette?

 

LDA #1
STA PLAYER1
STA PLAYER2
LDA #0
STA PLAYER1
STA PLAYER2

LDA PLAYER1 ; A
AND #1
BEQ Start

Endret av 89erik
Lenke til kommentar

Det var rart. Hvordan er PLAYER1 og PLAYER2 definert? Jeg antar de representerer tallene $4016 og $4017?

 

edit: Bra du forsto hvordan det hang sammen med negative tall. Jeg skal som sagt få gjort det litt klarere i guiden. :)

Endret av Jaffe
Lenke til kommentar

Hvordan har du definert dem? Jeg pleier ofte å navngi diverse registre slik:

 

PLAYER1 = $4016

PLAYER2 = $4017

PPUDATA = $2007

...

 

Edit: Vent litt ,nå tror jeg kanskje jeg ser problemet. Klager den på BEQ-instruksjonen? Husk at argumentet til en slik betinget hoppeinstruksjon (branch) er en såkalt relativ adresse; et tall som sier hvor langt fremover eller bakover den skal hoppe. Dette tallet kan maks. bruke 8 bits, så det må være mellom -128 og 127. Her er det antagelig ganske stor avstand (681 bytes) mellom labelen Start og instruksjonen.

 

Dette er jo ganske vesentlig, og er vel absolutt noe jeg også bør nevne i guiden :p

Endret av Jaffe
Lenke til kommentar

Nå glemte jeg å si noe om løsningen på problemet. Det er å gjøre noe slikt:

 

...

AND #1

BNE noe_mer

JMP Start

noe_mer:

 

Dette fungerer fordi JMP tar en absolutt adresse som argument, og BNE-instruksjonen her bare vil få tallet 3 som argument (den etterfølgende instruksjonen JMP Start tar 3 bytes).

Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
×
×
  • Opprett ny...