上次简单介绍了使用cc65开发的信息,这次把cc65的环境准备详细说明一下
上一篇中我使用的是cc65 2.13.3
现在最新版本已经是2.18了,所以我打算从下载开始讲起,并讲解编译,nmi的配置等内容,将开发环境基本部署到位,以后不再赘述。
首先,官网地址 https://www.cc65.org/
可以看到老的版本和资源还有,不过最新的在github上https://github.com/cc65/cc65
打开ftserver,看到只有2.13的版本提供下载,那就转去github
当然,用2.13也足够用,嫌麻烦就直接用2.13就可以了。
在github看到windows下载,https://sourceforge.net/projects/cc65/files/cc65-snapshot-win32.zip
下载下来,放到d:\cc65-2.18
当然,要记得在全局系统参数里加上设置
现在拿上一篇的代码试一下 0.c文件
#include
void main()
{
while(1){
gotoxy(10,10);
cprintf("string:%s ","123456");
}
}
编译
cl65 -t nes -o 0.nes 0.c
然后用模拟器运行
在这里,基本环境就搭建好了, 可以参考手册进行编程开发,
使用CHR查看编辑工具YY-CHR查看编译好的0.nes文件
可以看到cc65使用的CHR文件里都是文本内容,基本就是ascii码的对应内容,如何替换和使用图形以后再说
现在说说nmi,也是以后的重点内容,
现在来看没有开启nmi的代码演示
1.c文件
#include
void main()
{
while(1){
clrscr();//清屏
gotoxy(10,10);
cprintf("string:%s ","123456");
clrscr();//清屏
gotoxy(20,20);
cprintf("string:%s ","123456");
}
}
编译好后执行效果
可以看到在闪啊闪,
扩展开, 在nes开发过程中, 如果不做任何处理, 在全屏背景图, 几十个spring,配合手柄,音乐和各种判定代码的情况下,
屏幕会闪花眼,这个时候,就要引入nmi概念。
nmi的解释:
NMI的意思是 Non-Maskable Interrupt (不可屏蔽中断--译注),发生在每次刷新时
(VBlank). 这些刷新的间隔依赖于所用的系统 (PAL/NTSC).
NMI在NTSC控制下刷新次数为 60次/秒, PAL为50次/秒. 6502的中断潜伏期为七 (7) 个周
期; 这也就是说需要需要七 (7) 个周期来移入和移出一个中断.
其实说白了就是屏幕刷新,而一般的建议是在nmi中断过程中处理和图形显示相关的代码,在这些代码里更新VRAM,移动spring,更改调色盘,或其它任何与显示有关的操作,这样就可以避免产生不必要的闪烁。
而cc65默认情况下是没有开启nmi支持的,看过我第一篇文章的,会注意到代码里有mynmi函数,那就是我进行修改重新编译后增加的nmi中断函数(在编译的时候名称随意定义,但在代码中一定要严格使用定义的函数名字),也就是系统每次产生nmi中断,就会自动调用mynmi函数
#include
//mynmi申明
void mynmi(void);
//mynmi实现
void mynmi(void)
{
}
void main()
{
while(1){
waitvblank();
gotoxy(10,10);
cprintf("string:%s ","123456");
}
}
mynmi函数是没有参数传递的,也没有返回值,而这就设计到另一个概念, 在fc/nes开发过程中,尽量使用全局变量,在定义的各个函数或其它操作中尽量使用全局变量, 可以加快代码速度5倍左右,而直接使用全局变量,不如使用指针操作,会更快。在我以前的学习过程中,指针操作感觉比变量操作要快几倍,我操作精灵移动,使用变量和指针的速度差异非常大。第三点就是使用无符号变量类型,更可以加快速度,这些记住即可,不进坑就好。
(fc是8位机,所以尽量所有数据用8位数据)
下面我来修改一下代码,在之前编译好的2.13环境下测试一下效果
#include
short xx=10;
short zz=0;
//mynmi申明
void mynmi(void);
//mynmi实现
void mynmi(void)
{
++zz;
if(zz==10){
//clrscr();//清屏
if(xx == 10){
xx=15;
gotoxy(10,10);
}else{
xx=10;
gotoxy(15,15);
}
cprintf("%s "," ");
gotoxy(xx,xx);
cprintf("string:%s ","123456");
zz = 0;
}
}
void main()
{
while(1){
}
}
使用打开nmi支持的2.13编译后效果
可以看到不再闪烁。
而应用全屏背景和spring之后,意义更大,不会闪瞎眼,
以上两张图是以前用cc65 2.13 nmi环境下写的迷宫,里边有背景、音乐、碰撞判断、倒计时显示数字等
吃个饭去,回来讲编译打开nmi
=====================================================================================
下边开始cc65 nmi的修改和编译
下载源代码
https://github.com/cc65/cc65
并下载好了windows版本的可执行版本
cc65-master.zip
cc65-snapshot-win32.zip
两个全部解压, 并分开两个目录
在源代码“ libsrc\nes”文件夹找到 crt0.s 文件,
默认 nes 库怎么指明 nmi 中断入口的,找到一段代码:
.segment "VECTORS"
.word nmi ; $fffa vblank nmi
.word start ; $fffc reset
.word irq ; $fffe irq / brk
这里就是知道的 nmi、 start、 irq 三个中断,只看nmi相关的
然后看到$fffa 位置使用了标签(或者叫地址) nmi 去填充,这样最后生成的 nes 中 nmi 中断向量就为 nmi 了,
看看它在 nmi 里面写了写什么,找到 nmi 标签:
nmi: pha
tya
pha
txa
pha
......
省略号后边自己看
我们想让程序在nmi 中断的时候调用我们自己用 C写的中断服务程序,假设我们的中服务程序的声明为 void mynmi(void);
因为 mynmi 是之后自己写的,所以我们在 crt0.s 中的开头需要用.import_mynmi 导入,之所以有下划线是因为 C 函数编译之后编译器会在函数前面加上下划线,所以通过汇编方式访问的时候需要加下划线。这样就把mynmi()导入了。
然后调用,为了不破坏它原来的库,我们在它完成原来 nmi 中断的工作之后增加一条去执行我们外部用 C 写的中断服务程序的语句吧。一条 jsr19_mynmi 句足够了。(本段摘录自Trbbadboy的cc65高级玩法(1-7))
在nmi: 这一段找到
sta PPU_VRAM_ADDR1
sta PPU_VRAM_ADDR1
在下边添加一行
jsr _mynmi;
这个是在系统自己的代码执行完后,去执行自定义函数
mynmi这个函数需要在crt0.s文件的最前部分添加
.import _mynmi;
.import __CODE_LOAD__,__CODE_RUN__, __CODE_SIZE__
.import __RODATA_LOAD__,__RODATA_RUN__, __RODATA_SIZE__
.import _mynmi;
.include "zeropage.inc"
.include "nes.inc"
以上完成了源代码的修改,下边开始编译和替换文件里的部分
del *.o
ar65 d nes.lib crt0.o
ca65 -t nes crt0.s
ar65 a nes.lib crt0.o
pause
可以看到,使用cc65自己的编译工具,不用其它编译环境即可
nes.lib是cc65环境中lib目录里nes.lib文件
过程就是先删除所有编译好的 .o 文件,然后从nes.lib里删除crt0.o部分
然后编译crt0.s ,再然后向nes.lib文件里添加crt0.o部分,
这样编译和替换完成后,就实现了mynmi函数,也就是实现了nmi中断。
(在编译的时候需要把neschar.inc也拷贝过来)
编译过程
然后把修改好的nes.lib剪切回lib目录,即可进行相关编程开发操作,要记得,把刚才拷贝过去的inc文件, lib文件和.s文件删掉,
下边是2.18版本nmi支持示例代码和模拟器效果
使用上边的例子,也就是2.13环境nmi的那个代码
至此,cc65 2.18 就实现了对nmi的支持。
下一次,将继续进行cc65环境的准备,会进行自定义库的使用