Neo TeAm Forum

The 9th. NEO Project for SNES => The 9th. NEO Project for SNES Dev kit. => SNES development section => Topic started by: spinal on March 02, 2010, 04:00:02 AM

Title: Lesson 01 - fail :(
Post by: spinal on March 02, 2010, 04:00:02 AM
OK, I tried following the tutorial here (http://en.wikibooks.org/wiki/Super_NES_Programming/Initialization_Tutorial), and finished with this (http://socoder.net/uploads/124/lesson_01.rar).

the problem is, even though the code is short and as far as I can tell I followed the instructions, but the stupid thing doesn't work.
According the site, when I run the rom, I should get a nice dark green screen, however, I get nothing. :(.

Can someone tell me what I did wrong?

-Thanks-
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 02, 2010, 04:06:40 AM
Change it from HIROM to LOROM.
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 02, 2010, 04:28:20 AM
In  the neo menu? tried that, didn't help. It doesn't work in emu either.
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 02, 2010, 04:34:56 AM
No, in your header code.
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 02, 2010, 04:41:55 AM
That worked, although I get a bunch of **** on the screen when running on hardware.

Also, when attempting to swap some gfx in an example program for my own, i'm not seeing anything on the screen, I assume im doing something wrong....

link to pastebin to save space in the thread (http://spinal.pastebin.com/A67rHVZB).

...like not knowing what im doing i guess.
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 02, 2010, 03:05:51 PM
Running the ROM in the BSNES debugger is very useful to find out what's wrong.

The Snes_Init routine sets all registers to 8-bit, but in LoadPalette and LoadVRAM you assume that X and Y are 16-bit. So you need to make them 16-bit by inserting a REP #$10 after you've called Snes_Init.
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 04, 2010, 03:49:38 AM
OK, I got the tiles loaded, but...
1. How to a load a map? where do I copy it to?
2. In the below copy, I am trying to scroll the screen by 8 pixels then jump back. However, it doesn't seem to be doing anything at all. Have I missed something stupid again?

3. This would be far easier in BASIC :P

Code: [Select]
;============================================================================
; Includes
;============================================================================

;== Include MemoryMap, Vector Table, and HeaderInfo ==
.INCLUDE "header.inc"

;=== Include Library Routines & Macros ===
.INCLUDE "InitSNES.asm"



;============================================================================
; Macros
;============================================================================
;============================================================================
;LoadPalette - Macro that loads palette information into CGRAM
;----------------------------------------------------------------------------
; In: SRC_ADDR -- 24 bit address of source data,
;     START -- Color # to start on,
;     SIZE -- # of COLORS to copy
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
; Modifies: A,X
; Requires: mem/A = 8 bit, X/Y = 16 bit
;----------------------------------------------------------------------------
.MACRO LoadPalette
    lda #\2
    sta $2121       ; Start at START color
    lda #:\1        ; Using : before the parameter gets its bank.
    ldx #\1         ; Not using : gets the offset address.
    ldy #(\3 * 2)   ; 2 bytes for every color
    jsr DMAPalette
.ENDM

;============================================================================
; LoadBlockToVRAM -- Macro that simplifies calling LoadVRAM to copy data to VRAM
;----------------------------------------------------------------------------
; In: SRC_ADDR -- 24 bit address of source data
;     DEST -- VRAM address to write to (WORD address!!)
;     SIZE -- number of BYTEs to copy
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
; Modifies: A, X, Y
;----------------------------------------------------------------------------

;LoadBlockToVRAM SRC_ADDRESS, DEST, SIZE
;   requires:  mem/A = 8 bit, X/Y = 16 bit
.MACRO LoadBlockToVRAM
    ldx #\2         ; DEST
    stx $2116       ; $2116: Word address for accessing VRAM.
    lda #:\1        ; SRCBANK
    ldx #\1         ; SRCOFFSET
    ldy #\3         ; SIZE
    jsr LoadVRAM
.ENDM



;============================================================================
; Main Code
;============================================================================

.BANK 0 SLOT 0
.ORG 0
.SECTION "MainCode"

Start:
    InitSNES    ; Clear registers, etc.

    ; Load Palette for our tiles
    LoadPalette BG_Palette, 0,15

    ; Load Tile data to VRAM
    LoadBlockToVRAM Tiles, $0000, $0800 ; 64 tiles, 4bpp, = 2048 bytes = $0800


    lda #$80
    sta $2115
    ldx #$0400
    stx $2116
    lda #$01
    sta $2118

   
    ; Setup Video modes and other stuff, then turn on the screen
    jsr SetupVideo




Infinity:

; Attempt to scroll screen by 8 pixels then jump back
lda $210D ; load screen Y location into a
inc a ; a = a + 1
sta $210D ; screen Y = a

jmp Infinity    ; loop forever








;============================================================================
; SetupVideo -- Sets up the video mode and tile-related registers
;----------------------------------------------------------------------------
; In: None
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
SetupVideo:
    lda #%00000000
    sta $2105           ; Set Video mode 0, 8x8 tiles, 4 color BG1/BG2/BG3/BG4

    lda #$04            ; Set BG1's Tile Map offset to $0400 (Word address)
    sta $2107           ; And the Tile Map size to 32x32

    stz $210B           ; Set BG1's Character VRAM offset to $0000 (word address)

    lda #$01            ; Enable BG1
    sta $212C



    lda #$0F
    sta $2100           ; Turn on screen, full Brightness

    rts
;============================================================================

;============================================================================
; LoadVRAM -- Load data into VRAM
;----------------------------------------------------------------------------
; In: A:X  -- points to the data
;     Y     -- Number of bytes to copy (0 to 65535)  (assumes 16-bit index)
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
; Modifies: none
;----------------------------------------------------------------------------
; Notes:  Assumes VRAM address has been previously set!!
;----------------------------------------------------------------------------
LoadVRAM:
    stx $4302   ; Store Data offset into DMA source offset
    sta $4304   ; Store data Bank into DMA source bank
    sty $4305   ; Store size of data block

    lda #$01
    sta $4300   ; Set DMA mode (word, normal increment)
    lda #$18    ; Set the destination register (VRAM write register)
    sta $4301
    lda #$01    ; Initiate DMA transfer (channel 1)
    sta $420B

    rts         ; return
;============================================================================

;============================================================================
; DMAPalette -- Load entire palette using DMA
;----------------------------------------------------------------------------
; In: A:X  -- points to the data
;      Y   -- Size of data
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
; Modifies: none
;----------------------------------------------------------------------------
DMAPalette:
    stx $4302   ; Store data offset into DMA source offset
    sta $4304   ; Store data bank into DMA source bank
    sty $4305   ; Store size of data block

    stz $4300  ; Set DMA Mode (byte, normal increment)
    lda #$22    ; Set destination register ($2122 - CGRAM Write)
    sta $4301
    lda #$01    ; Initiate DMA transfer
    sta $420B

    rts         ; return from subroutine


.ENDS

;============================================================================
; Character Data
;============================================================================
.BANK 1 SLOT 0
.ORG 0
.SECTION "CharacterData"

    .INCLUDE "tiles.inc"
    .INCLUDE "maps.inc"

.ENDS
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 04, 2010, 04:41:25 AM
I didn't look at all the code, but I did notice that you're not making X and Y 16-bit here either, unless you've modified the InitSNES macro to do that.
You should step through the code in a debugger to see if it actually does what you were expecting it to.
Title: Re: Lesson 01 - fail :(
Post by: ChillyWilly on March 05, 2010, 02:27:08 AM
3. This would be far easier in BASIC :P

Why don't you try the C compiler in the snes-sdk mentioned in the sticky thread? Most people usually don't have too much trouble using C, and it's WAY easier than 65816 assembly.
 ~sm-37.gif~
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 05, 2010, 02:31:50 AM
That's true.  But 65816 assembly isn't really that complicated, you just have to be very careful about the register sizes and the addressing modes.
For someone who hasn't programmed in any assembly language before, C would obviously help that person get started faster.
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 07, 2010, 01:17:52 AM
... Do tile maps need to be formatted any particular way?
My map is not loading properly and I cant find any info about how the map should be formatted. I assumed just a plain old array with the 1024 entries in it, but when I copy the map to [what I assume is] the correct location, it seems to be only half as wide with most of the tiles not showing. In fact, the only tile that seems to work is 0, not so useful for anything but clearing the map :(

test.rar (http://socoder.net/uploads/124/test.rar) <- source

So, do the map entries need more than just the tile number to be set, or have I set up the bg wrong or something?

Also, slightly unrelated, how do I go about using the joypads?
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 07, 2010, 02:13:27 AM
There you go (http://emu-docs.org/Super%20NES/General/snesdoc.html#BGBasics)

and another doc (http://emudocs.org/Super%20NES/General/snestech.txt)

Look up registers $4218 and $4016 in the docs to see how to read the controllers.
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 14, 2010, 06:08:00 AM
 I am trying to add a tile to my map, where each tile is 16x16px, making each of my gfx 2x2 tiles.

So I need to do the equivalent of

Code: [Select]
Void Drawblock(int t, int x, int y)
{
 MapBuffer(32*y + x) = t*2;
 MapBuffer(32*y + x+1) = t*2+1;
 ...etc.
}


 I came up with the following...

Code: [Select]
.MACRO DrawBlock
; \1 = tile, \2 = x, \3 = y

LDA #32*\3 + \2 + MapBuffer
STA Temp

LDA #\1 ; A = tile#
ASL A ; A << 1 (A = A * 2) (Tiles are 16x16px)

LDX Temp
STA MapBuffer,X ; Map(X) = A (Should shot top left of tile at location \2,\3)
.ENDM

...BUT, I get the following error -

INPUT_NUMBER: Out of 8bit range.
ERROR: Couldn't parse "LDA".

1. What am I doing wrong?
2. Is this the correct way to attempt what I want to do?
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 14, 2010, 04:58:51 PM
You're getting this error because at some place where you're using the macro the assembler thinks that A is 8-bit, so you obviously can't load a 16-bit value into it. You could fix this by inserting a REP #$20 as the first instruction in the macro. But there's no reason why you need A and Temp to "calculate" the address since it's a constant, so you could rather do something like this:

Code: [Select]
.MACRO DrawBlock
; \1 = tile, \2 = xpos, \3 = ypos
; Precondition: 8-bit A, 16-bit X,Y

   LDA #\1      ; A = tile#
   ASL A      ; A << 1 (A = A * 2) (Tiles are 16x16px)

   LDX #32*\3 + \2   ; Why add MapBuffer here as well if it's used in the STA below?
   STA MapBuffer,X   ; Map(X) = A (Should shot top left of tile at location \2,\3)
.ENDM

What's still unclear here is where is MapBuffer? If you're writing to it, it can't be in ROM. So is it a long address (like $7F0000)? If it's a word address then you'll have to set DBR to point to RAM ($7F) using the PHB/PLB instructions.

Title: Re: Lesson 01 - fail :(
Post by: spinal on March 14, 2010, 05:39:09 PM
... Of all the things to get wrong, adding MapBuffer twice, how stupid.
Thanks mic_. This learning ASM business is much slower than I first thought.
Title: Re: Lesson 01 - fail :(
Post by: ChillyWilly on March 15, 2010, 07:03:52 AM
This learning ASM business is much slower than I first thought.

You picked the wrong CPU to start with.  ~sm-73.gif~.gif   :D
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 15, 2010, 02:48:02 PM
You picked the wrong CPU to start with.  ~sm-73.gif~.gif   :D

It's not so much about picking a cpu, my only aim was to make a snes game, it just turns out that the only way to do that while being able to get even a small amount of support was asm. And as the snes processor is a 65816, I had little choice but to try to learn it.  ~sm-80.gif~.gif

It looks doubtful that I will have a snes entry in the next contest though.
Title: Re: Lesson 01 - fail :(
Post by: ~tak on March 15, 2010, 03:03:59 PM
Quote from: ChillyWilly
You picked the wrong CPU to start with.  ~sm-73.gif~.gif   :D

Confirm! ASM for GBA is much easier and has great documentation (official/unofficial). I'm stuck with simple stuff on SNES which should work however doesn't (yet) :(
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 15, 2010, 04:09:05 PM
Sure, the ARM is pretty much the best thing since sliced bread for an assembly programmer (except for Thumb which is **** from a coder's point of view. Thumb2 is a lot less awkward).

I'll reiterate my key points about 65816 programming:

1) Make sure that the registers have the widths you expect them to. The assembler may not always be able to figure out what the current registers widths are supposed to be, especially if you set them in function F and then branch to function G. I use REP/SEP frequently to make it clear both to the assembler and myself if I expect the registers to be 8-bit or 16-bit. They only cost 3 cycles so as long as you don't use them inside a loop you're fine. Or you can use the ".8bit"/".16bit" directives in WLA-DX if you prefer that.

2) Make sure that you use the correct addressing mode. If you want long addressing then specify that to the assembler. If an address fits in 16 bits the assembler will probably use word addressing by default and then you need to make sure that DBR points to the bank you expect it to.

Apart from these two points you're basically looking at a 6502 with some improvements.
Title: Re: Lesson 01 - fail :(
Post by: ChillyWilly on March 16, 2010, 01:48:30 AM
Sure, the ARM is pretty much the best thing since sliced bread for an assembly programmer (except for Thumb which is **** from a coder's point of view. Thumb2 is a lot less awkward).

I'll reiterate my key points about 65816 programming:

1) Make sure that the registers have the widths you expect them to. The assembler may not always be able to figure out what the current registers widths are supposed to be, especially if you set them in function F and then branch to function G. I use REP/SEP frequently to make it clear both to the assembler and myself if I expect the registers to be 8-bit or 16-bit. They only cost 3 cycles so as long as you don't use them inside a loop you're fine. Or you can use the ".8bit"/".16bit" directives in WLA-DX if you prefer that.

2) Make sure that you use the correct addressing mode. If you want long addressing then specify that to the assembler. If an address fits in 16 bits the assembler will probably use word addressing by default and then you need to make sure that DBR points to the bank you expect it to.

Apart from these two points you're basically looking at a 6502 with some improvements.

Yeppers! It's not that I think the 65816 is difficult, it just has more going on that the programmer has to keep track of. That makes it less suitable for a beginner just starting on assembly than another. All the bank selecting in the SNES also contributes to the difficulty. If you want to start on learning, I think a more straightforward CPU on a console with a nice flat map is probably easier. I'm not saying people CAN'T start with the SNES, just that it will require more effort.
 ~sm-78.gif~.gif ~sm-71.gif~.gif ::sm-17.gif::
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 16, 2010, 04:04:24 AM
Time to bug you guys again, I apologise if its this is getting a bit irritating, it seems like you guys have done more work on my game than I have :P

Anyway, I'm stuck again...
Code: [Select]
.MACRO LoadLevel

; BASIC
; For y = 0 to 15
;  For x = 0 to 15
;   A = LevelMaps + 16 * y + x
;   DrawBlock A,x,y
;  Next x
; Next y

   LDY #0 ; Y = 0
  Next_Y:
   LDX #0 ; X = 0
  Next_X:
   STX TX
   STY TY
   LDA LevelMaps + 16*TY + TX ; I hope A = LevelMaps(x + 16 * y)
   DrawBlock,A,TX,TY ; DrawBlock A,X,Y
   INX ; X = X + 1
   CPX #16
    BNE Next_X ; If X != 16 then goto Next_X
   INY ; Y = Y + 1
   CPY #16
    BNE Next_Y ; If Y != 16 then goto Next_Y

.ENDM

Where LevelMaps is -
Code: [Select]
LevelMaps:
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $02, $03, $00, $00, $02, $03, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $04, $05, $01, $01, $04, $05, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $01, $00, $00, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $01, $00, $00, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $02, $03, $01, $01, $08, $09, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $04, $05, $00, $00, $06, $07, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00

I get the following error -

COMPUTE_PENDING_CALCULATIONS: Result (38944/$9820) of a computation is out of range.

The line number is that of
   LDA LevelMaps + 16*TY + TX   ; I hope A = LevelMaps(x + 16 * y)

I assume I'm doing that wrong?
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 16, 2010, 04:47:05 AM
Quote
LDA LevelMaps + 16*TY + TX   ; I hope A = LevelMaps(x + 16 * y)
TX and TY in this expression will evaluate to whatever TX and TY are defined as. If they are labels then they will evaluate to the addresses of those labels. You can't get the values that those addresses point to in this way.

Assuming A is 8-bit, X and Y are 16-bit and DrawBlocks is working correctly, what you probably want to do is this:

Code: [Select]
.MACRO LoadLevel
  LDY #15 ; Reverse the loop order to simplify the loop
  LDX #255 ; Start with the last byte (16*16 - 1)
Next_Y:
  LDA #15
  STA TX  ; Use TX as a loop counter instead of the X-register
Next_X:
  LDA.L LevelMaps,X
  DrawBlock A,TX,TY
  DEX
  DEC TX
  BPL Next_X  ; Loop while TX >= 0
  DEY
  BPL Next_Y
.ENDM

I still suspect that you need to fix DrawBlock as well. Why would you need to pass A to a macro for example? That will literally pass 'A', not the value that the accumulator happens to contain at the moment.
Title: Re: Lesson 01 - fail :(
Post by: spinal on March 16, 2010, 06:46:30 AM
So, Am I doing this wrong then?

Code: [Select]
;=== Global Variables ===

.ENUM $00
TX db
TY db
TA db
.ENDE



.MACRO LoadLevel

; BASIC
; For y = 0 to 15
;  For x = 0 to 15
;   A = LevelMaps + 16 * y + x
;   DrawBlock A,x,y
;  Next x
; Next y

  LDY #15 ; Reverse the loop order to simplify the loop
  LDX #255 ; Start with the last byte (16*16 - 1)
Next_Y:
  STY TY
  LDA #15
  STA TX  ; Use TX as a loop counter instead of the X-register
Next_X:
  LDA.L LevelMaps,X
  DrawBlock 1,TX,TY ; 1 to draw tile #1 all over the screen
  DEX
  DEC TX
  BPL Next_X  ; Loop while TX >= 0
  DEY
  BPL Next_Y

.ENDM


.MACRO DrawBlock
; This macro works fine
; e.g. DrawBlock 1,2,3 will draw block#1 at 4,6 on the screen (blocks are 16x16)

LDA #\1 ; A = tile
ASL A ; A << 1 (A = A * 2) (Tiles are 16x16px)

LDX #64*\3 + \2*2 ; X = 32*y+x (tile location)
STA MapBuffer,X ; Map(X) = A
INC A ; A = A + 1
STA MapBuffer+1,X ; Map(X) = A

LDY #$1F ; Y = 32
jump: ; A Lable
INC A ; A = A + 1
DEY ; Y = Y - 1
CPY #$00 ; N 0 - Y
BNE jump ; If N != 0 goto loop
STA MapBuffer+32,X ; Map(X) = A
INC A ; A = A + 1
STA MapBuffer+33,X ; Map(X+1) = A
.ENDM
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 16, 2010, 02:25:20 PM
Yes, because DrawBlock 1,TX,TY is a macro expansion, which is not the same thing as a function call in a HLL. A macro is kind of like a #define in C. The arguments you pass to it aren't evaluated before being passed - it's simply text replacement.

So with the way you call DrawBlock, the line LDX #64*\3 + \2*2  will become  LDX #64*TY + TX*2 == 64*1 + 0*2 == 64. Remember, this isn't a HLL - complex expressions aren't available. The assembler allows expressions that are constant, i.e. ones that it can evaluate at compile-time. Everything else you'll have to calculate yourself using the correct instruction sequences.

Title: Re: Lesson 01 - fail :(
Post by: spinal on March 16, 2010, 02:42:40 PM
Time to give up I think.
Title: Re: Lesson 01 - fail :(
Post by: mic_ on March 16, 2010, 05:20:20 PM
I think it's a matter of properly understanding the fundamentals of assembly language programming in general, and the ISA of the specific CPU in particular, before trying to use it for a complex project.
The way I got started was by going to the local library and borrowing two books on 80386/486 assembly language programming, and then spending a lot of time trying out different things that I had learned from those books. But that was over 10 years ago and now you've got all kinds of information on the subject available on the internet. For example, "Nerdy Nights" (a NES programming tutorial) could be of help to you, since the 65816 ISA merely is a superset of the 6502 ISA. A few advantages of textbooks are that they often go into more detail than online tutorials; they have usually gone through some form of editing or at least proof-reading; and you won't have to worry that the book just stops abruptly halfway through because the author got tired of writing it.
Title: Re: Lesson 01 - fail :(
Post by: ChillyWilly on March 17, 2010, 12:11:41 AM
Iffen I were you, I'd avoid macros until you understand it better. Just write out ALL the code. That's longer and more work, but much easier to understand if you don't understand how the macros work.