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

Beginner's questions about CC65

Beginner's questions about CC65
by on (#117690)
Hello,

Recently, I got into NES programming. After working through a general book about 6502 Assembly, I did the Nerdy Nights tutorial. Then, I converted my custom sample program to CC65 with the help of the website https://bitbucket.org/ddribin/nerdy-nights/src.
I did that because I'm planning to do my first game in C with the whole NES-specific low-level stuff written in Assembly and the general game logic written in C. So, I will need to write everything in CC65 syntax.

But of course, some questions came up. And I want to ask the first few in this thread. It would be great if you could help me with them.


1.
I've read that CC65 can be a hassle to set up while NESASM is quite easy to start because it doesn't require a cfg file and putting together the header.
But in the examples, the CC65 code doesn't look any more complicated than the NESASM code.

In NESASM, we have
Code:
  .inesprg 1
  .ineschr 1
  .inesmap 0
  .inesmir 1

In CC65, we have:
Code:
.segment "HEADER"

  .byte "NES", $1A
  .byte 2
  .byte 1
  .byte $01, $00

Doesn't look that much more complicated to me. So, in which situation does CC65 become complicated to set up?


2.
What's the deal with the "nes.cfg" file?

The guy from that CC65 example site said:
Quote:
Here's the nes.cfg as shipping with cc65 version 2.13.0.
and then showed a source code.
But I neither found an "nes.cfg" in "cc65-win32-2.13.3-1.zip", nor in "cc65-nes-2.13.3-1.zip". (O.k., it's version 2.13.3 instead of 2.13.0, but I didn't find 2.13.0 to check it.)

And when I compile my code with
Code:
cl65 -t nes -L cc65\lib -o test test.asm
it doesn't ask for such a file. So, what do I need it for?

By the way, why is there a file called "neschar.inc" in the folder "06-backgrounds2" on that site? This doesn't seem to be used either.


3.
My first game shall look like one of those first generation games. You know, like "Donkey Kong". (I don't mean that I want to re-program a specific one of those games, I just want my game to have the same technical limitations.) So, I would for example expect my nes file to be 24592 bytes long.
That's the case when I compile with NESASM. But with CC65, it's 40976 bytes.

I assume that's because the NESASM code (see above) declares
Code:
  .ineschr 1
while the CC65 code declares 2 x 16KB PRG:
Code:
  .byte 2
However, when I replace the 2 with a 1, the NES program doesn't work anymore and only shows a gray screen.

What do I have to do to tell CC65 that my game shall have the same header data as games like "Donkey Kong"?
I guess this is where the "nes.cfg" comes into play somehow, right? But I'm still a bit confused since the CC65 zip files don't include one and I'm not sure if the one from that site is identical to the one that the current version of CC65 would use by default.
(The basis for my sample code was the background2 example from week 6 from Nerdy Nights. The CC65 version can be found on the website that I linked above.)
Re: Beginner's questions about CC65
by on (#117703)
Don't use the NES libraries or config stuff that comes with CC65. If you neglected to download them, that's fine, they're not really useful for making games. Generally I would recommend just it without giving it a platform specific target, and it will emit generic 6502 code that is perfectly fine for the NES as long as you set up your .cfg correctly.

There's a good primer on how to use cfg files in this thread: http://forums.nesdev.com/viewtopic.php?f=2&t=10435

This is my generic iNES header snippet for CA65:

Code:
; iNES header
.segment "HEADER"

INES_MAPPER = 0
INES_MIRROR = 0 ; 0 = horizontal mirroring, 1 = vertical mirroring
INES_SRAM   = 0 ; 1 = battery backed SRAM at $6000-7FFF

.byte 'N', 'E', 'S', $1A ; ID
.byte $02 ; 16k PRG bank count
.byte $01 ; 8k CHR bank count
.byte INES_MIRROR | (INES_SRAM << 1) | ((INES_MAPPER & $f) << 4)
.byte (INES_MAPPER & %11110000)
.byte $0, $0, $0, $0, $0, $0, $0, $0 ; padding
Re: Beginner's questions about CC65
by on (#117712)
rainwarrior wrote:
Code:
.byte $01 ; 4k CHR bank count

4K?
Re: Beginner's questions about CC65
by on (#117721)
LOL, I dunno. Fixed.
Re: Beginner's questions about CC65
by on (#117732)
rainwarrior wrote:
Don't use the NES libraries or config stuff that comes with CC65. If you neglected to download them, that's fine, they're not really useful for making games. Generally I would recommend just it without giving it a platform specific target, and it will emit generic 6502 code that is perfectly fine for the NES as long as you set up your .cfg correctly.

Yeah, I also prefer to do this all by myself without using any libraries. So, it will just be the compiler and assembler (and linker), but I guess I won't include any external source code files for my project.

rainwarrior wrote:
There's a good primer on how to use cfg files in this thread: http://forums.nesdev.com/viewtopic.php?f=2&t=10435

Thanks for the link. I already got a bit more insight. I will read all the stuff (one of the links even includes the manual for cfg files) and then see if I can edit and condense my configuration file accordingly.
By the way, the default "nes.cfg" was in the CC65 source code download package. That's why I didn't find it at first because I downloaded the binaries.

rainwarrior wrote:
This is my generic iNES header snippet for CA65:

Content-wise, that's actually identical to the one from the tutorial. Only that the other one uses vertical mirroring. But I switched it to horizontal now because "Donkey Kong", "Mario Bros.", "Pac-Man" etc. have it too.
By the way, if a game does not scroll, is there even any difference between horizontal and vertical mirroring?
And do you know of any (real, licensed) non-scrolling game that switches between the two name tables during gameplay?

P.S.: You can leave the last eight zeroes (the padding bytes) out. In the config file, there is the option
Code:
fill = yes
anyway.
Re: Beginner's questions about CC65
by on (#117734)
DRW wrote:
rainwarrior wrote:
This is my generic iNES header snippet for CA65:

Content-wise, that's actually identical to the one from the tutorial.


Not really. You can see what they are using in the tutorial by passing ld65 the dump config command: ld65 --dump-config nes
There is a lot of extra information in there releated to cc65's default config for developing with C that is not needed for 6502 assembly.
Re: Beginner's questions about CC65
by on (#117735)
Movax12 wrote:
Not really. You can see what they are using in the tutorial by passing ld65 the dump config command: ld65 --dump-config nes

I was just talking about the header, i.e. the first 16 bytes of the file: Apart from the mirroring, the one that rainwarrior posted is basically identical to the one from the tutorial. He just wrote some things a little different.

But thanks for the command. I was already wondering if it is possible to get the default "nes.cfg" from within the program.
Re: Beginner's questions about CC65
by on (#117736)
Oh. Sorry for the misunderstanding. rainwarrior did say header snippet.
Re: Beginner's questions about CC65
by on (#117737)
DRW wrote:
And do you know of any (real, licensed) non-scrolling game that switches between the two name tables during gameplay?

Rampart by Jaleco doesn't actually scroll, but it does use the scrolling registers to draw a notice that goes down the screen to cover slow map updates.
Re: Beginner's questions about CC65
by on (#117739)
I was thinking more of a game where one level consists of two rooms or whatever that the player can switch between through a door and each room is represented by one nametable.
After all, since the NES has two nametables available anyway even in the most basic games, I wonder if that was ever used somehow in a non-scrolling way. Wouldn't this have been a good method to double the size of a level in a plain first generation game without having to use any advanced techniques and without having to reload the background graphics?
Re: Beginner's questions about CC65
by on (#117740)
To "double the size of a level", you'd probably want to scroll, like in Lode Runner.
Re: Beginner's questions about CC65
by on (#117741)
Yes, if the level is one connected location. But imagine an inside view of a house and when you leave the door, you are on the outside, on the street. (Where you see the house in a miniature format where you can go in again.)

Besides, does it take long to build up a completely new background from scratch? Imagine I have a game with a bunch of rooms, all looking different. The player can go through, let's say four doors. And in the next room, he can go through several doors again. So, you can never know in advance which room he will take. Whenever the scene changes, you have to put new graphics into the PPU. How long would this take? Would there be some kind of delay in the gameplay or can the switch be done within the time of one frame where everything else is also processed?
Re: Beginner's questions about CC65
by on (#117743)
Quote:
Whenever the scene changes, you have to put new graphics into the PPU. How long would this take?
If you're only updating the nametable, that's 1024 bytes. At most, you can upload the sprites and about 230 bytes per vertical sync, so that'll take at least 5 frames (or 1/12th of a second) if you don't turn off the display. That's pretty small, and can be done without really changing anything else.
Quote:
Would there be some kind of delay in the gameplay or can the switch be done within the time of one frame where everything else is also processed?
It's probably not actually kind to suddenly change everything without the player getting a chance to react; a small delay may actually be kinder than immediate page flips. If you turn the screen off entirely, you can definitely draw everything in 1/60th of a second, but then the player will see a (usually) black flicker, which may be annoying.
DRW wrote:
So, you can never know in advance which room he will take.
To speed things up, you actually can start drawing off-screen as the player approaches one of the exits. Since you only need to know 5 frames before you change the view, you probably actually already know if the player is going out one of the doors.
Re: Beginner's questions about CC65
by on (#117791)
I don't know if the O.P. knows, but, there is a fairly robust IDE called NESICIDE that needs active use and feedback :)
http://www.nesicide.com/
Re: Beginner's questions about CC65
by on (#117793)
Are you referring to a specific post of me or is this just a general suggestion?
I guess I will have a look, but in the moment, I'm still learning the basics of NES programming. I won't start developing an actual game until I understand every single line in my sample code. So, yes, I might actually use an IDE as soon as I start a real project. But in the moment, a text editor and the assembler is all that I need. I don't want anything to be abstracted away from me now. (Well, except for the actual conversion of assembly code to binary code of course.)
Re: Beginner's questions about CC65
by on (#117822)
DRW wrote:
Are you referring to a specific post of me or is this just a general suggestion?
I guess I will have a look, but in the moment, I'm still learning the basics of NES programming. I won't start developing an actual game until I understand every single line in my sample code. So, yes, I might actually use an IDE as soon as I start a real project. But in the moment, a text editor and the assembler is all that I need. I don't want anything to be abstracted away from me now. (Well, except for the actual conversion of assembly code to binary code of course.)


I think he just meant that it comes with C/asm example projects [New Project->Templates] and config files that [should] compile [at least last I checked...]. :beer:
Re: Beginner's questions about CC65
by on (#117849)
cpow wrote:
DRW wrote:
Are you referring to a specific post of me or is this just a general suggestion?
I guess I will have a look, but in the moment, I'm still learning the basics of NES programming. I won't start developing an actual game until I understand every single line in my sample code. So, yes, I might actually use an IDE as soon as I start a real project. But in the moment, a text editor and the assembler is all that I need. I don't want anything to be abstracted away from me now. (Well, except for the actual conversion of assembly code to binary code of course.)


I think he just meant that it comes with C/asm example projects [New Project->Templates] and config files that [should] compile [at least last I checked...]. :beer:



What cpow said I said! NESICIDE is a great way to start programming for the NES. A beginner should focus on coding - save twiddling with the toolchain and utilities for later.
Re: Beginner's questions about CC65
by on (#117866)
cpow wrote:
I think he just meant that it comes with C/asm example projects [New Project->Templates] and config files that [should] compile [at least last I checked...]. :beer:

Well, in this case I guess I will have a look. To see what the example programs are like.

slobu wrote:
What cpow said I said! NESICIDE is a great way to start programming for the NES. A beginner should focus on coding - save twiddling with the toolchain and utilities for later.

Generally, you're right. If someone is eager to start a project right now. But since the NES is so low-level, I'd rather understand everything before I start a real game. After all, I'm not in a hurry. I have a sample program that doesn't really do anything useful, but that already includes the basics (building backgrounds, letting a sprites move etc.). And as soon as I'm able to explain every single line in that code, I will move on.
Re: Beginner's questions about CC65
by on (#117927)
A little question about the CC65 assembler: What are the commands to declare variables?

IN NESASM, you write this:
Code:
  .rsset $0000
xlocation .rs 1
ylocation .rs 1
score .rs 1

What's the CC65 equivalent? I didn't find it.
Re: Beginner's questions about CC65
by on (#117935)
It's ".res". From the ca65 manual:

Code:
11.84 .RES

Reserve storage. The command is followed by one or two constant expressions. The first one is mandatory and defines, how many bytes of storage should be defined. The second, optional expression must by a constant byte value that will be used as value of the data. If there is no fill value given, the linker will use the value defined in the linker configuration file (default: zero).

Example:

            ; Reserve 12 bytes of memory with value $AA
            .res    12, $AA


Obviously, the fill value would be ignored for RAM.
Re: Beginner's questions about CC65
by on (#117948)
Memblers wrote:
Obviously, the fill value would be ignored for RAM.

If I remember correctly, CC65 will give a warning if a fill value other than 0 (the default) is used for BSS segments.

Anyway, for DRW, also remember to specify the correct segment:
Code:
.segment "ZEROPAGE" ; Or whatever other segment.
foo: .res 2
bar: .word 0 ; This also works. Note that you need to clear the memory in your initialization code to make sure the value is actually 0.
xyzzy: .dword 12345 ; This will give a warning if/when ZEROPAGE segment has been defined as type=bss.
Re: Beginner's questions about CC65
by on (#117978)
Thanks a lot. It works very well.
Re: Beginner's questions about CC65
by on (#118324)
thefox wrote:
Code:
bar: .word 0 ; This also works. Note that you need to clear the memory in your initialization code to make sure the value is actually 0.

Hm... why clear anything? ".word 0" places two zero-bytes at the label.
Re: Beginner's questions about CC65
by on (#118326)
A quirk? The value is actually how many of the proceeding to reserve space for (or maybe not in this context.. it may place zeros there, but that matches what should be in RAM, so it's okay). If you prefer to use .byte,.word or .addr, etc, in a RAM segment, you need to specify a zero. It's a bit odd, since inside a .struct, you don't need to specify a zero size, and you can use numbers greater than zero.

Code:
.struct foo
    bar    .byte 10    ; bar is 10 bytes
    baz    .byte        ; baz is 1 byte
.endstruct


A struct in ca65 doesn't reserve anything though, it just creates a data structure.
Re: Beginner's questions about CC65
by on (#118329)
Lazycow wrote:
thefox wrote:
Code:
bar: .word 0 ; This also works. Note that you need to clear the memory in your initialization code to make sure the value is actually 0.

Hm... why clear anything? ".word 0" places two zero-bytes at the label.

It doesn't place anything if that label is in RAM, you have to initialize all variables manually.

Some people might be used to initializing variables like that (.db, .dw, .byte, .word, etc.), but this will only work in computers/architectures that copy the programs from the storage media to RAM before running them (so the variables are copied over as well). On the NES, however, programs run directly from ROM, so RAM is completely untouched when the program starts running.
Re: Beginner's questions about CC65
by on (#118331)
That's a better explanation. ca65 will let you define .word/.bytes or whatever in a RAM segment, but it won't actually place anything there for you.

I assume (untested) you could define your ZP segment load as:
Code:
MEMORY
{
   ZP:  start =    $0, size =   $100,  type = rw,  define = yes, fillval = $AA;
}

And then:
Code:
.segment "ZEROPAGE"
    bar: .word $AA     ; matches what the fillval for ZP is suppose to be.


If the value doesn't match, you'll get a linker error.
Re: Beginner's questions about CC65
by on (#118333)
Ok, didn't realize that you are talking about structs. Without .struct, .byte 10 reserves 1 byte, filled with value 10
Re: Beginner's questions about CC65
by on (#118335)
I brought structs into this. They are a different thing, that use the same keywords to define space. Not the same as reserving RAM.
Re: Beginner's questions about CC65
by on (#118338)
Lazycow wrote:
thefox wrote:
Code:
bar: .word 0 ; This also works. Note that you need to clear the memory in your initialization code to make sure the value is actually 0.

Hm... why clear anything? ".word 0" places two zero-bytes at the label.

It depends on what is the type of the segment that you're placing the data in. If the segment is in the NES CPU RAM, it's your responsibility to initialize it (either by clearing it, if it's a BSS segment, or by copying the correct values over from ROM, if it's a read-write segment). If the segment is in ROM, the value will of course be written to the ROM file, so no need to "manually" initialize anything.

Movax12 wrote:
That's a better explanation. ca65 will let you define .word/.bytes or whatever in a RAM segment, but it won't actually place anything there for you.

I assume (untested) you could define your ZP segment load as:
Code:
MEMORY
{
   ZP:  start =    $0, size =   $100,  type = rw,  define = yes, fillval = $AA;
}

And then:
Code:
.segment "ZEROPAGE"
    bar: .word $AA     ; matches what the fillval for ZP is suppose to be.


If the value doesn't match, you'll get a linker error.

Not really true, or I'm misunderstanding you.

It depends on the type of the segment again. If it's "bss", you have to use 0 (.word 0, or .byte 0, or whatever -- in this context, CA65 regards 0 as "uninitialized"), regardless of the fill value of the corresponding memory area definition. If it's "rw" or "ro", it doesn't matter if the value matches the fillval, the data will be written to the output file.
Re: Beginner's questions about CC65
by on (#118383)
The CC65 CRT initializes the BSS segment to 0 on startup, which is why it makes that assumption. If you make a custom CRT library you should probably initialize BSS to 0 to avoid breaking the assembler's assumption.

It also copies the DATA segment from its ROM (load position) to RAM (run position), which is how your global C variables get initialized.
Re: Beginner's questions about CC65
by on (#118955)
I've got some new questions:


1. I created some functions to initialize graphics data: Background, palette, attributes.
At first, I set the corresponding high and low byte at address 0x2006. Then, I assign a global pointer variable to a constant that contains the data. (This way, I can load new data for new levels.) Until now, everything is fine. Here is a code snippet:
Code:
/* Pointer to the constant that is written. */
const unsigned char *Data;

/* The loop that writes certain data to the PPU.
   (The kind of data is specified by setting high
   and low value to 0x2006 before calling this function. */
void DoLoop()
{
   /* 0xFF is simply defined as the end-of-data character since the
      constant arrays have an unknown length. */
   while (*Data != 0xFF)
   {
      *(unsigned char *)0x2007 = *Data;
      
      ++Data;
   }
}

But as soon as the assignment isn't absolutely constant, it doesn't work anymore:
Code:
const unsigned char *Data;

unsigned char i;

void DoLoop()
{
   i = 0;

   while (*Data != 0xFF)
   {
      *(unsigned char *)(0x2007 + i) = *Data;
      /* Since i = 0, 0x2007 + i should still be 0x2007,
         only that it isn't known at compile time. */
      
      ++Data;
   }
}

So, from a logical point of view, both functions should do the same. The only difference is that in the first function, the address is known at compile time.

If you're asking yourself why I even need this: Well, when writing sprites, the PPU access isn't to a constant address anymore. While you can write background, palettes and attributes to 0x2007 and the PPU increments its internal address automatically, writing sprites requires writing to address (0x0200 + numberOfAlreadyWrittenBytes).

So, how does this function work even with a variable address?


2. How do I put variables into the zero page?

I tried this here:
Code:
extern unsigned char variable;
#pragma zpsym ("variable");

And then, in crt0.s, I declared:
Code:
.segment "ZEROPAGE"
_variable: .res 1

But this gives me a compiler error: "Error: Symbol `_variable' is already defined".
That's because my generated assembly file declares:
Code:
  .importzp   _variable


So, how do I properly declare variables in the zero page that I want to use in the C code and in my crt0.s?
Re: Beginner's questions about CC65
by on (#118959)
Don't you want .exportzp? You're exporting that symbol to other files, not importing it from another file.
Re: Beginner's questions about CC65
by on (#118962)
CC65 doesn't know that $2007 is a volatile address, and in some situations it will use instructions the indirect indexed STA which generates an extra write that will interfere with your attempt to load data into the PPU.

You may want to read this thread if you want some more details about it: http://forums.nesdev.com/viewtopic.php?f=2&t=9407

If you want the short answer, it's that you have to be very careful what syntax you use for writing to volatile registers (e.g. $2003, $2004, $2005, $2006, $2007) in CC65. Specifically, this syntax seems to generate correct code:
Code:
*((unsigned char*)0x2007) = a


Don't mess around with arrays or adding an index, etc. for this purpose, you need to make sure the compiler generates code that only produces one write when the number of writes matters. The problem arises because in all other situations the extra write is a harmless side effect, and CC65 was not written with this in mind, as it is likely a problem unique to the NES. (There is a "volatile" keyword in C which could potentially solve this problem, but CC65 ignores it.)
Re: Beginner's questions about CC65
by on (#118969)
blargg wrote:
Don't you want .exportzp? You're exporting that symbol to other files, not importing it from another file.

Yeah, but the problem is: The importzp isn't written by me. That's what the compiler makes from my C source code. That's why I wanted to know: What do I have to do to declare a zero page variable that can be used in my C code and in my hand-written assembly code?

@rainwarrior: So, I guess it's back to writing the function in Assembly again.
Re: Beginner's questions about CC65
by on (#118970)
You can put variables declared in C code into zero page using pragmas. Not sure which ones, as it has been changes between recent CC65 versions, in the version that I use it is this:

Code:
#pragma bssseg (push,"ZEROPAGE")
#pragma dataseg(push,"ZEROPAGE")

static unsigned char i; //after the pragmas global vars are going to ZP


There is no .export directives in the generated assembly code, though, but I guess that if you declare the same var as an extern first, it may work for your purposes, in a hacky way.
Re: Beginner's questions about CC65
by on (#118971)
DRW wrote:
2. How do I put variables into the zero page?

I tried this here:
Code:
extern unsigned char variable;
#pragma zpsym ("variable");

And then, in crt0.s, I declared:
Code:
.segment "ZEROPAGE"
_variable: .res 1

But this gives me a compiler error: "Error: Symbol `_variable' is already defined".
That's because my generated assembly file declares:
Code:
  .importzp   _variable


So, how do I properly declare variables in the zero page that I want to use in the C code and in my crt0.s?

If that is indeed the error you get, you do have multiple definitions of _variable somewhere. The code you gave should work just fine, as long as you .export the symbol from your assembly file (crt0.s).
Re: Beginner's questions about CC65
by on (#118972)
@Shiru:

Thanks. This method works now.

I don't understand this whole export and import stuff anyway. My code starts in "crt0.s", right? In this file, I simply include the other files. So, when I have a file called "functions.c", then I know that the compiler will transform it into "functions.s". So, I just write .include "Functions.s" into "crt0.s" and that's it. Isn't this the correct way to combine the source files?

However, there's still a warning in my source code: When I declare a global zeropage variable in my C code:
Code:
#pragma bssseg (push,"ZEROPAGE")
byte variable;

and I use it in the "crt0.s" like this:
Code:
  LDA #$00
  STA _variable

then the compiler says: "Warning: Didn't use zeropage addressing for `_variable'".
The error message refers to the line with the STA command. What do I have to change here?

thefox wrote:
If that is indeed the error you get, you do have a multiple definitions of _variable somewhere. The code you gave should work just fine, as long as you .export the symbol from your assembly file (crt0.s).

As I said: My non-crt0.s code is not an assembly file, it's a C file. And I cannot control what .exports and .imports the compiler writes into the .s file that it creates out of the .c file.
According to that link you have to declare the variable in your assembly code (i.e. in "crt0.s") and then declare it as extern in your C file and do a #pragma zpsym for this variable.
So, I myself never used any .import or .export. That's all done by the compiler. Does that mean the solution in the link is incorrect or did I misunderstand something there?
Re: Beginner's questions about CC65
by on (#118974)
DRW wrote:
In this file, I simply include the other files. So, when I have a file called "functions.c", then I know that the compiler will transform it into "functions.s". So, I just write .include "Functions.s" into "crt0.s" and that's it. Isn't this the correct way to combine the source files?
Oh. No, that's not what crt0 does. crt0 isn't your makefile. Like with greater C compiler suites, you either manually link (ld65 here) all the objects together after all the compilation stages, or just use cl65 to run it all automatically.
crt0 should: clear uninitialized memory
maybe wait for the PPU to warm up
any other generic setup, such as disabling interrupts
copy things from ROM to RAM (for any line in your foo.cfg that specifies a separate load and run segment)
and then jump to main

By including the generated assembly file, you effectively have "import" and "export" in the same file, hence the errors.
Re: Beginner's questions about CC65
by on (#118978)
DRW wrote:
As I said: My non-crt0.s code is not an assembly file, it's a C file. And I cannot control what .exports and .imports the compiler writes into the .s file that it creates out of the .c file.

In fact you can control it.

If you do:
Code:
extern unsigned char variable;

Then you will get an .import statement in the generated .s file.

If you do:
Code:
unsigned char variable2;

Then you will get an .export statement in the generated .s file.

If you do:
Code:
static unsigned char variable3;

Then you will get neither an .export or an .import statement in the generated .s file.

Quote:
According to that link you have to declare the variable in your assembly code (i.e. in "crt0.s") and then declare it as extern in your C file and do a #pragma zpsym for this variable.
So, I myself never used any .import or .export. That's all done by the compiler. Does that mean the solution in the link is incorrect or did I misunderstand something there?

You misunderstood something there. :)
Re: Beginner's questions about CC65
by on (#119021)
O.k., I corrected everything. The code files are now compiled into separate *.o files and then linked together. Now, everything works.