This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Update to my tutorial in C

Update to my tutorial in C
by on (#225005)
I haven't changed my website yet, but it should be very soon.

I did manage to finish the example code this past weekend, and posted everything on github.

https://github.com/nesdoug?tab=repositories

Feel free to comment, if you have any issues / see any problems. Thanks.


Here's the support libraries I wrote "nesdoug.s" and "nesdoug.h"

https://github.com/nesdoug/01_Hello/tree/master/LIB
Re: Update to my tutorial in C
by on (#225021)
Good to see an update!

I'm looking at the lib code to see if there is any method that could be used in C that I don't have yet ;) One thing I would like to know is that neslib has a history for update. Is the history from Shiru or from your own fix? Since I use some of the methods inside, if there was some important fix I will be more than happy to update the affected method.

I will check the example once I have some time.
Re: Update to my tutorial in C
by on (#225025)
Changes I made to neslib.h...

added the word "const" on many of the prototypes, to quiet some warnings.

split() now takes 1 argument (X) instead of 2.

in neslib.s

added ldx #0 to end of rand8, per calima's suggestion.

added "export _flush_vram_update_nmi" so I could use the flush function without needing to pass a pointer.

split adjusted to take 1 argument.

The version history, I don't know who added the last items, maybe "veg" whoever that is.
Re: Update to my tutorial in C
by on (#225035)
I see.

Since I'm using rand8 sparsely, could you kindly explain why the need to set x to #0? For now I do not see the impact of not setting it for an 8 bit value returned in A. Where does the value of X affect rand8? Maybe it's something related with cc65 internal on return value that I'm not very familiar yet.
Re: Update to my tutorial in C
by on (#225048)
Any assembly function that is called as a C function must return a 16 bit value in A/X, even if the function is defined as returning unsigned char. This is just a limitation of the compiler, it assumes that any 8-bit return value is automatically promoted to a 16-bit int, and relies on the assembly function to do this. (Otherwise a leftover value in X has the potential to generate incorrect results.)
http://cc65.github.io/doc/cc65-intern.html#ss1.3
Re: Update to my tutorial in C
by on (#225050)
Oh, I think I read something about that but since I usually do not return value I didn't put much attention and completely forgot about it. That was when I started to learn the innards of cc65 to know how to use methods.

I will review my code just in case I have some issues. I know that the metatitle attribute code return an 8 bit value and I didn't set the X. Good think I asked about that, I have now found another source of possible bugs!

Thanks!
Re: Update to my tutorial in C
by on (#225087)
Great tutorial Doug,

I'm new to NES Dev. I had been using NESASM3, but had been running into problems organizing my code with it. You have me really excited about using C and ca65/cc65

Thanks for the tutorials,
Rick
Re: Update to my tutorial in C
by on (#225094)
Well, on that note, I deleted the old tutorial, and am rewriting it (I have rough drafts done).

Give me a week or 2 to get it back in shape.
Re: Update to my tutorial in C
by on (#225104)
Thanks for your work.

About the split function, I have a rough version which somewhat works with the Y parameter. At least it fits my needs (vertical scrolling with top status bar using vertical mirroring). I'm sure it could be done better (it's a bit shaky), but my kung fu is not strong enough.

Code:
;;void __fastcall__ split(unsigned int x,unsigned int y);

_split:

   ; Extract SCROLL_Y1, SCROLL_X1, WRITE1 from parameters.

   sta <TEMP

   txa
   bne @1
   lda <TEMP
   cmp #240
   bcs @1
   sta <SCROLL_Y1
   lda #0
   sta <TEMP
   beq @2   ;bra

@1:
   sec
   lda <TEMP
   sbc #240
   sta <SCROLL_Y1
   lda #8               ;; Bit 3
   sta <TEMP
@2:

   jsr popax
   sta <SCROLL_X1
   txa
   and #$01
   asl a
   asl a                ;; Bit 2
   ora <TEMP               ;; From Y
   sta <WRITE1            ;; Store!

   ; Calculate WRITE2 = ((Y & $F8) << 2) | (X >> 3)

   lda <SCROLL_Y1
   and #$F8
   asl a
   asl a
   sta <TEMP             ;; TEMP = (Y & $F8) << 2
   lda <SCROLL_X1
   lsr a
   lsr a
   lsr a                ;; A = (X >> 3)
   ora <TEMP             ;; A = (X >> 3) | ((Y & $F8) << 2)
   sta <WRITE2            ;; Store!

   ; Wait for sprite 0 hit

@3:
   bit PPU_STATUS
   bvs @3
@4:
   bit PPU_STATUS
   bvc @4

   ; Set scroll value
   lda PPU_STATUS
   lda <WRITE1
   sta PPU_ADDR
   lda <SCROLL_Y1
   sta PPU_SCROLL
   lda <SCROLL_X1
   ldx <WRITE2
   sta PPU_SCROLL
   stx PPU_ADDR
   
   rts
Re: Update to my tutorial in C
by on (#225108)
look at my function _xy_split in nesdoug.s

It's similar.
Re: Update to my tutorial in C
by on (#225133)
I had issues with return value with X not set to 0 for pad too so this thread helped for that. thanks!

I don't want to sound rude but if it does I apologize in advance. I just want to know the motive behind such approach for labels in the above example. Maybe it should be a subject for another thread but this always scratch an itch everytime I see it.

It always puzzle me when peoples uses either anonymous label or numbered one inside logic. From my point of view, even though the code is simple, it just obfuscate it for the next user that will read it. It may be obvious the day you wrote it but it won't in the years later.

For example, instead of using : and just jmp with :-, usually I will write something based on the context. So if I'm processing metatile, I may write

Code:
whileMetatileLeft:
   xxxx   ; code that process metatile here   
...
   inx
   bne whileMetatileLeft


I could have just wrote @1 or : but you have no idea how much it helped me re-read my code I did a long time ago. I think named label are useful, however verbose they are.

Again, I apologize if it sounded rude.
Re: Update to my tutorial in C
by on (#225154)
Quote:
I had issues with return value with X not set to 0 for pad too


Hmm, I forgot to fix pad_poll() and others. Will maybe have to add some more ldx #0
Re: Update to my tutorial in C
by on (#225164)
Banshaku wrote:
I had issues with return value with X not set to 0 for pad too so this thread helped for that. thanks!

I don't want to sound rude but if it does I apologize in advance. I just want to know the motive behind such approach for labels in the above example. Maybe it should be a subject for another thread but this always scratch an itch everytime I see it.

It always puzzle me when peoples uses either anonymous label or numbered one inside logic. From my point of view, even though the code is simple, it just obfuscate it for the next user that will read it. It may be obvious the day you wrote it but it won't in the years later.

For example, instead of using : and just jmp with :-, usually I will write something based on the context. So if I'm processing metatile, I may write

Code:
whileMetatileLeft:
   xxxx   ; code that process metatile here   
...
   inx
   bne whileMetatileLeft


I could have just wrote @1 or : but you have no idea how much it helped me re-read my code I did a long time ago. I think named label are useful, however verbose they are.

Again, I apologize if it sounded rude.


No, what you are saying makes complete sense.

I was just completing an already existing function so I guess I adhered to the existing code style. The annonymous labels - I think they are used so you can cut and paste a routine to another project without having to care about identifier collisions. Other than that, I don't find any advantages. I'm not an assembly coder, tho' :)
Re: Update to my tutorial in C
by on (#225168)
One way to avoid collision in cc65 is to create scopes like .proc for procedures or even define local scopes to wraps things. For example, let say you have a function with some label like this:

Code:
.proc subMyFunction
   ; init code here
processData:
    ; xxx
    bne processData
    rts
.endproc


processData is scoped to subMyFunction and will not clash if you define it again in another procedure. You can even call that part is you require just to "process the data" from another procedure without the beginning code with:

Code:
   jmp subMyFunction::processData


and now you just jumped there. Another case, those zpTemp variable that means nothing when seen but you need to use them anyway? Scope them!

Code:
; void __fastcall __ function2(unsigned char counter)
;
; process data. receive value in A
;
.proc subFunction2
; ------- local scope begin -------
.scope local
      counter    = zpTemp   ; counter points on zpTemp
.endscope
; ------- local scope end -------
       ; save parameter for later use
       sta local::counter
       ....
loop:
      ; do stuff on it
      lda #$23
      cmp local::counter
      bne loop
 
      rts
.endproc


and now you zpTemp has more meaning as the "local counter". Easier to read the code. This is just vary basic stuff. There is more advanced scope that makes you code quite "interesting" but now I try to avoid that :lol:

Sorry to have hijacked the thread m(_ _)m
Re: Update to my tutorial in C
by on (#225617)
Ok, the new tutorial is up an 99% finished.

And all the code is on github.

https://github.com/nesdoug
Re: Update to my tutorial in C
by on (#226190)
Does anyone think I should increase the # of metatiles? Currently my code can use up to 51 (5 bytes each). But with a slight modification, that could be 102.

In my mind (before) you could swap metatile definitions for each type of level. I thought 51 should be enough.

Just wondering what other people think.
Re: Update to my tutorial in C
by on (#226191)
Since it's a tutorial for learning about the nes, yes, it should be fine. Once the user know enough to do things on their own, they can update the code and add those extra metatile they are missing. It should be part of the learning process anyway, to extend to make it work the way you want :)
Re: Update to my tutorial in C
by on (#226200)
Most of my games use 16 or 32 as per "section", I think it's more than enough.
Re: Update to my tutorial in C
by on (#226206)
Banshaku wrote:
Since it's a tutorial for learning about the nes, yes, it should be fine. Once the user know enough to do things on their own, they can update the code and add those extra metatile they are missing. It should be part of the learning process anyway, to extend to make it work the way you want :)


Agreed. It's a tutorial, not an all-purpose framework. Arbitrary limitations are fine. People following your tutorials are eventually going to have to learn to do things themselves.
Re: Update to my tutorial in C
by on (#226212)
True. Just trying to anticipate "how do I expand your code to do X" questions.
Re: Update to my tutorial in C
by on (#230227)
I wrote a function for changing song speed manually (a while back).

It occurred to me today, as I was working on music code, that you could have greater flexibility over song speed, AND not interfere with Fxx effects if you instead adjusted the FT_TEMPO_STEP_L and FT_TEMPO_STEP_H.

Bigger for faster.

I might edit my code... later (I'm busy).
Re: Update to my tutorial in C
by on (#242546)
Updated the neslib Sprite functions to remove the "sprid" parts. one sprite is 11% faster, metasprite is 5% faster. fewer passed arguments = faster.
Re: Update to my tutorial in C
by on (#242551)
Hi! Thanks for the update! :)

I've been trying to "load" it into 8bitworkshop, but without success yet.
No matter what I do, a reference to the "old" neslib is probably hardcoded somewhere in their IDE.
I'll keep trying, nevertheless.

@Doug, is neslib.h and neslib.s independent from nesdoug.h and nesdoug.s?
Re: Update to my tutorial in C
by on (#242552)
wonder wrote:
I've been trying to "load" it into 8bitworkshop, but without success yet.
No matter what I do, a reference to the "old" neslib is probably hardcoded somewhere in their IDE.


You could try the LIBARGS special command, put it in the comments like here (but add in crt0, that's for an assembly example):
viewtopic.php?f=2&t=19215#p242505

It implies that crt0 and neslib are linked by default.
Re: Update to my tutorial in C
by on (#242554)
Quote:
is neslib.h and neslib.s independent from nesdoug.h and nesdoug.s?


Correct. neslib was written by Shiru 6 years. The version in 8bitworkshop is a more recent fork. The version that I use is also an unrelated fork, which I modified yesterday.

nesdoug is code specific to my tutorial, and meant to be a companion library to neslib, which I felt was not complete.

But you can make a complete game without the nesdoug files, you will just not be able to follow my tutorial.
Re: Update to my tutorial in C
by on (#242557)
I've started to setup an NES development environment on my Ubuntu VM.

I can compile and run 01_Hello without problems.

Instructions:
Code:
# Install an emulator (eg. Nestopia)
sudo apt install nestopia

# Install CC55
cd $HOME
mkdir -p nes && cd nes
git clone https://github.com/cc65/cc65.git
make

# Add CC65 to your path environment variable
echo '# NesDev' >> $HOME/.bashrc
echo 'export CC65_HOME="$HOME/nes/cc65"' >> $HOME/.bashrc
echo 'export PATH="$PATH:$CC65_HOME/bin"' >> $HOME/.bashrc


Now you can git clone the 01_Hello repository.
The windows file 'compile.bat' won't work here,
so I created a new file named 'compile.sh':

Code:
#!/bin/bash

name="$1"

cc65 -Oirs "$name.c" --add-source
ca65 crt0.s
ca65 "$name.s" -g

ld65 -C nrom_32k_vert.cfg -o "$name.nes" crt0.o "$name.o" nes.lib -Ln labels.txt

rm *.o

mv labels.txt  BUILD/
mv "$name.s"   BUILD/
mv "$name.nes" BUILD/

nestopia "BUILD/$name.nes"


When done, don't forget to
Code:
chmod +x compile.sh


Now it's simple:
Code:
# Assuming 01_Hello exists in 'projects'
cd $HOME/nes/projects
cd 01_Hello
./compile.sh hello


I'll convert the bash script to a Makefile when I have some time. :)
Re: Update to my tutorial in C
by on (#242565)
dougeff wrote:
Updated the neslib Sprite functions to remove the "sprid" parts. one sprite is 11% faster, metasprite is 5% faster. fewer passed arguments = faster.


Where I download it? I've looked at your github but I don't see it.
Re: Update to my tutorial in C
by on (#242566)
Every tutorial, from 01_Hello to 29_Powerpad.

In main folders, crt0.s changed to include SPRID as an internal variable.

In LIB folders, neslib.h and neslib.s updated. oam_spr() and oam_meta_spr() specifically.
Re: Update to my tutorial in C
by on (#242570)
@Dougeff, in neslib.h you have the following declaration:
Code:
//set sprite in OAM buffer, chrnum is tile, attr is attribute, sprid is offset in OAM in bytes
//returns sprid+4, which is offset for a next sprite
// Note: sprid removed for speed
void __fastcall__ oam_spr(unsigned char x,unsigned char y,unsigned char chrnum,unsigned char attr);


The docstring says it returns something, but the return type is void. I would suggest:
Code:
//set sprite in OAM buffer, chrnum is tile, attr is attribute, sprid is offset in OAM in bytes
//increments the sprid by 4 bytes, which is the offset for the next sprite
// Note: sprid removed for speed
void __fastcall__ oam_spr(unsigned char x,unsigned char y,unsigned char chrnum,unsigned char attr);
Re: Update to my tutorial in C
by on (#242573)
Fixed. Removed old comments about return values.