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=repositoriesFeel 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
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.
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.
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.
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
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!
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
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.
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
look at my function _xy_split in nesdoug.s
It's similar.
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.
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
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'
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
Sorry to have hijacked the thread m(_ _)m
Ok, the new tutorial is up an 99% finished.
And all the code is on github.
https://github.com/nesdoug
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.
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
Most of my games use 16 or 32 as per "section", I think it's more than enough.
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.
True. Just trying to anticipate "how do I expand your code to do X" questions.
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).
Updated the neslib Sprite functions to remove the "sprid" parts. one sprite is 11% faster, metasprite is 5% faster. fewer passed arguments = faster.
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?
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#p242505It implies that crt0 and neslib are linked by default.
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.
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.
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.
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.
@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);
Fixed. Removed old comments about return values.