https://happysoul.github.io/nes/nesdoug/

 

 

所有NES汇编程序都是命令行程序。那是什么意思?它没有图形用户界面。您不需要在其中键入代码。您必须在单独的程序(Notepad ++)中编写代码并保存。然后,打开命令提示符,并通过在命令提示符下键入来运行汇编程序。

 

Cc65要复杂得多,它需要输入几个指令,有多个步骤,所以我们不打算使用命令提示符。但不要担心!我将为你简化它...我们将编写一个批处理文件(compile.bat)来自动化该过程。一旦编写完成,你所要做的就是双击compile.bat,如果一切顺利,它将生成你的NES文件。(用Notepad ++编写.bat文件。)

 

如何cc65编译..首先,您将用C语言编写源代码(使用Notepad ++)。cc65将其编译为6502汇编代码。然后ca65将它组装成一个目标文件。然后ld65将您的目标文件(使用配置文件.cfg)链接到一个完整的.nes文件中。所有这些步骤都将写入批处理文件中,因此最后,您将双击.bat文件以执行所有这些步骤。

 

我将所有源文件“#include”到一个.c文件中,并将“.include”或“.incbin”文件放在crt0.s.中。您唯一需要在.bat文件中更改的内容是主.c文件的“名称”。如果它们发生变化,你只需要更改(name).c和crt0.s中包含文件的名称。

 

更多关于cc65 ......

 

运行NES的6502处理器是一个8位系统。它没有简单的方法来访问大于8位的变量,因此对大多数变量首选'unsigned char'。地址是16位,但几乎所有其他地方都处理8位。并且,它所知道的唯一数学是加法,减法和位移乘法/除法(乘法的 x2 等同于 << 1)。

 

这意味着,由于系统的限制,您可能需要以非常规方式编写C代码。

 

 

1.变量应定义为 unsigned char(8位,范围值0-255)。

2.使用全局变量(或本地静态变量)

3.使用无参函数......

造成C运行慢的原因是堆栈的存取操作。

局部变量和传递的变量使用C堆栈,速度可能慢5倍。

函数参数的替代方法是在函数调用之前将值存储到临时全局变量。

这种情况很容易引起冲突,因此您可能希望立即将它们放入函数顶部的静态局部变量(也很快)。

4.理想情况下,数组最多使用256 bytes

5.像数组一样使用指针。用ptr[1] 代替 *(ptr+1)

6.每行代码只放一个数组,解释......

正确用法

temp = array2[y];
array1[x] = temp;

错误用法

array1[x] = array2[y];

如果这些数组是小于256的全局字符数组,并且如果一行中只有一个,则可以将其转换为非常快的代码。

如果你把2放在一行上,它会将右数组的地址传递给一个临时指针,

用y做一些16位数学运算,然后它必须对第二个数组执行相同的操作,这需要大约5倍的时间。

7.使用++g而不是g++

cc65使用 “inc g” 来编译 ++g,g++ 使用的是 “lda g, clc, adc #1, sta g” 会多花费4倍的时间

正确用法

z = g;

++g;

错误用法

z = g++;

8. cc65不能按值传递结构,也不能返回结构。

9.不要使用结构数组,使用带有数组的结构。

 

 

 

编译时,使用-O指令优化代码。还有i,r,s指令,它们有时组合为-Oirs,可以增加优化效果。但是,可能会导致异常(不过我没有遇到错误,所以我仍然使用-Oirs这个命令)。

以下是cc65编码的更多建议......

http://www.cc65.org/doc/coding.html

 

为什么我们如此关注优化?因为时序非常重要,而且旧的8位处理器的资源非常薄。而且,干净的未更改的C代码将编译成非常慢的代码,占用了太多有限的内存空间。

 

在文件之间共享变量... asm模块(.s文件)可以使用import,export,importzp,exportzp定义彼此共享变量/标签。cc65可以通过声明变量“extern unsigned char foo;”来访问asm模块中的变量和数组(如果它是一个zeropage符号,则添加行#pragma zpsym(“foo”);.当它编译C代码时,它将会为它添加一个导入定义。我几乎不使用“extern”。这可能是我提到它的唯一一次,除了可能......如果你有一个大的二进制文件...最容易在asm代码中“加入”它像这样......

.export _foo
_foo:
.incbin “foo.bin”

 

然后从C代码访问它,执行“extern unsigned char foo [];”。请注意下划线。出于某种原因,当cc65编译时,它会在每个符号前添加下划线。因此,在asm方面,您必须为每个导出的标签/变量添加下划线。

您可以使用__fastcall__调用在ASM中编写的cc65中的函数。这将在A,X寄存器中存储最右侧传递的变量,而不是C堆栈。如果函数在C中,这没有用,因为它会立即将它传递给C堆栈以将其用作局部变量。

 

当您传递变量时,会发生什么?

(注意,测试是全局的)

 

 

 

Test(Foo);
void Test (char A) {
test = A;
}

 

 

//一个传递的变量...编译成19行代码

// 20,如果你在我们跳到这里之前计算'lda Foo'

 

 

 

_Test:
 
jsr pusha
ldy #$00
lda (sp),y
sta _test ; test = A;
 
jmp incsp1
 
pusha: ldy sp
beq @L1
dec sp
ldy #0
sta (sp),y
rts
 
@L1: dec sp+1
dec sp
sta (sp),y
rts
 
incsp1:
 
inc sp
bne @L1
inc sp+1
@L1: rts

 

没有传递变量,编译成3行代码

 

void Test(void){
test = A;
}

 

 

_Test:
lda _A
sta _test
rts

 

也可以将asm代码内联到C代码中。它看起来像这样......

 

asm (“Z: bit $2002”) ;
asm (“bpl Z”) ;

 

另一个说明。我正在使用cc65附带的标准nes.lib文件。

其他人一直在使用runtime.lib,如果你使用其他版本的cc65,它会无法使用。

我增加了命令参数

 

--add-source

 

到命令行,编译代码时将生成一个包含c代码的.s(asm)文件,可以查看asm的代码。还会生成一个labels.txt文件,用于调试。