学习汇编语言

来源:互联网 发布:手机升降调软件 编辑:程序博客网 时间:2024/04/30 14:15

 6502编程大奥秘

原文件作者:xuhonghai
EMAIL:syj22@163.net
网站:http://wqxmcode.8u8.com
(本文件由SAILOR整理完成)

***序一***
很荣幸能为xuhonghai先生写的书作序.
xuhonghai先生是鲜有的文曲星高手之一,在WINIDEFORNC-1020推出之前,他利用了文曲星NC-1020内置的GVBASIC来研究NC-1020系统软、硬件.并且成功的以GVBASIC为跳板,成功地编出NC-1020上首个非官方BIN程序--DEBUG系列.这听起来好像非常不可思议:他竟然是透过BUG多多的GVBASIC来编出BIN,而不是用什么秘密武器!!但事实上真正的高手是不需要什么强大武器,因为他所掌握的知识、技能已经足够他利用最简陋的工具来做出非常厉害的作品.一直以来很多星迷在很多文曲星论坛上问"怎么成为文曲星高手?"怎么才能编出像XUHONGHAI,LEE那样的作品?xuhonghai,SUN等高手跟LEE的性质不太一样.LEE更像是属于熟练利用现有的系统函数的应用软件程序员,而xuhonghai、SUN等人则属于文曲星研究高手,把研究出来的结果公布,让广大星迷可以利用编写软件,无论是想成为LEE那一类型的高手还是程序xuhonghai那一类型的高手,你都必须掌握6502汇编.而xuhonghai写的这本书更像是高手成长之路.当然,有了这本书并不说明你就一定能成为高手,因为,路是要自己走的,你不走,又怎能到达终点呢?   
                                                                        点虫虫
                                                                    2003-10

 


***前言***
**献给亲爱的读者朋友**
    自从金远见公司推出其具有战略意义的文曲星电子产品-CC800后,由于其内置了GVBASIC语言,开放的开发平台,促使在"文曲星世界"掀起了一股轰轰烈烈的编程热潮.我很幸运,也赶上了这股末潮.亲身经历了当年那令人永远难于忘却的"文曲星生活",我体会到了文曲星编程的极大乐趣.至今为止,我开发了CC800,PC1000,NC1020上的一系列DEBUG,例如XASM,XDEBUG,NCTOOLS.这些产品得到了广大网友的热情支持,我在这里向各位表达我最诚挚的感谢,我要特别感谢香港网友SUN,他无数次无私的帮助我,没有他的友情帮助,我必定是一事无成,向SUN敬礼!!!
    辗转于各大文曲星论坛,经常看到热心的网友渴望学习6502汇编,但又苦于没有相关资料,即使有,也是非常零散,不系统的一点点,这极大的打击了他们的学习信心.这种情况,严重阻碍了文曲星编程的发展.使汇编仅掌握在少数人手里.我们必须赶快看见文曲星世界日渐衰落的现象,我又不禁想起当年那场轰轰烈烈"革命","昔日杨柳,今日残枝",此情此景,如何叫人不感慨万端.诚然,汇编高手不少,例如SUN,LEE等,但这些年来,没有任何高手编写过一本关于汇编的书,我自认为自己没有资格做这件事,但是我愿意把我所知道的知识整理成一本书,以飨读者.由于水平有限,错误一定甚多,还请广大读者批评.你如果对本书有什么疑问,可以和我联系
mail:syj22@163.net
qq:    81874797
tel:13627006374
                                                            xuhonghai
                                                            2003.10.15

 

***为什么要学习汇编语言?***
编者注:该文章非编者原创,是台湾宏基公司"仓颉输入法"的作者朱邦复所写.

一、结构基础
    物质文明之有今天的成就,是因为人类掌握了物质的基本结构.物质的种类无穷,但是却都由基本元素交互组成,只要根据一定的法则,就能得到一定的结果.
    计算机技术虽然日新月异,应用软件的变化也无止无尽,而其基本因子却非常有限.各种微处理器的汇编语言,正是计算机软件的基础结构,任何要通过软件以完成的动作,都是经由汇编语言的指令群,逐步执行的.
    因为计算机结构复杂,各种任务分工极精,即使是一位资深的高级程序员,终其生也不过局限在若干固定的程序中钻研,很难以宏观的立场认知全貌.再加上市场需求的压力,局外人莫名其奥妙,局中人又忙得不可开交,所以还没有任何人能作出全盘的评估.
    汇编语言首先成为被误解的牺牲者,包括应用它的系统工程师在内,都一致认为它「难学难用」,(中文也是一种组合形式的应用,其所组合者是人的概念.无独有偶,人们在不求甚解之余,都视之为畏途.)事实上大谬不然,现在是科学挂帅,而科学的精义就在于系统的分类和应用.问题是我们能不能归纳出一些学习、应用的法则,将组合的过程化繁为简,以符合各种应用范畴.

二、个人体验
    我个人对此感受极为深切,我原是个十足的外行,1978年第一次接触计算机,曾以不到两周的时间,就学会计算机操作,并应用「培基语言」设计完成"仓颉输入"程序.当时我认为培基语言易学易用,是计算机上最好的工具.
    后来,我开始用培基语言设计"仓颉向量组字"程序,每秒可生成两个字,当时与我合作的宏碁公司建议我采用汇编语言,他们说组字程序速度要快,培基语言不能胜任.如改用汇编语言,效率可提高十倍,由此开始了我与汇编语言的不解之缘.1979年9月我们正式推出了由国人自行设计、具有完整的计算机功能、可运用数万中文字的"天龙中文计算机".
    宏碁公司动用了三位资深工程师,采用Z80MCZ系统,以六个月的时间完成了向量组字及系统程序,记忆空间占60KB,处理速度每秒约组成30字.
    这是我首次发现到汇编语言的威力,深究之下,才理解到计算机的全部工作原理.简单说来,汇编语言就是组合计算机所有功能的控制指令,利用它,就可以直接控制计算机.
    其它高级语言,只是让人省事,用一些格式化的手续,把人的想法化为过程的指令,这种情形就相当于为了迁就开车的人,建了密如蛛网的高速公路.本来走路只要几分钟就可到达的地方,以车代步的结果,反而需要耗费半个小时.
    1980年,我决定自己动手,又重新设计了一套字数较多,字形较美观的组字程序.只用了三个月的时间,结果不仅记忆空间缩小了三分之一,速度也快了十倍,达到每秒300字.这个产品,就是1苹果机上用的「汉卡」.
    1983年,再经分析,我发现以往写的程序很不精简,技术也不成熟.我坚信中文字形在计算机上的应用,将是中国文化存亡兴衰的根本因素,不仅值得投注自己的时间及精力,且也有此必要.所以我又拋掉了一切,重头设计,加入更多的变化参数,并根据人的辨识原理,设计成第三代至第五代等多种字形产生器.每一代之间,速度都明显地提高,功能也不断加强.在这样一再重复的摸索中,尝试了各种可行的途径,充分认识了汇编语言的特性及长处.
    由于汇编语言灵活无比的特性,我发现它就如同画家的画笔一般,只为了牟利,可以用它画成各种廉价速成的商品;一旦投入自己的理想与心智,画笔就不再只是一枝笔,而成为人心与外界的界面,画出的作品立时升华成为艺术,进入一个更高的境界!
    1985年,我再次重新设计规划,采用人的智能原则,把人写字、认字的观念化为数据结构,程序只是用来阐释数据、控制计算机的界面.该字库的字形可做到无级次放大缩小,字体、字型皆能任意变化(每字可以产生数亿种变形).而且除了现今各种字典已收的六万余字外,还可以组成完全符合中文规则的新字六百万个,足敷未来新时代新观念的发挥应用.
    不仅如此,组字速度又提高了,每秒可以组成30*30的字形两千个!当然现在用的是15MHZ80286,比以往的4.75MHZ的Z80已经快了近六倍.但是,改良后的新程序,其功能的增加,处理过程的繁杂性已远非当年可比.
    这些成果,用了很多特殊的数据结构技巧,不可能经由高级语言来完成.既然用汇编语言所制作的程序能一再大幅度地改进,这就说明了汇编语言的弹性极大,效率相去千里.如不痛下苦功钻研,程序写完,能执行就算了事,又怎能领悟其中奥妙?
    所以,我并不认为汇编语言只是一种程序语言而已,它是一种创造艺术品的工具,它能赋与无知无觉的电子机器一种「生命」,由无知进而有知,由有知而生智能.通过对汇编语言的研究探索,我整理出一些规律,写成这本书,以便于理解及应用.但是,要真正将汇编语言发展成为艺术,尚有待青年朋友们继续努力,在这个信息时代,开拓出一片崭新的天地.
    无意义的音符能编成美妙的音乐,无规律的色彩可幻化为缤纷的世界,为什么计算机的机器指令,不能架构出信息的理性天地?
    这就是艺术,作为艺术家,就必须奉献出自己的心血,以真、善、美为最高境界.
    要达到这种目的,就要认真的作好准备动作,再一步一步地追求下去.

三、利人与利己
    任何一种商业产品,当然是以利益为先,利己后而利人.如果是艺术品创造,则刚刚相反,唯有能忽视己利,沥血泣心地探索,虔诚狂热地奉献,才会迸发出人性的光辉,创造不朽的杰作.
    艺术家之伟大,在于此,人性之可贵,在于此.
    对组合程序语言,有人视为商品,将写作技巧当作专利,轻不示人.相信这也是迄今尚无一本象样的参考书籍之根本原因,我买了不少这类书,但书中除了指令介绍以及编程、侦错的手续外,完全没有技巧的说明,好象懂得指令就可以把程序写好一般.当我自己下了不少功夫,得到了一些心得,再回过头来看那些参考书,才发现连作者本人所举的例子,都是平铺直叙,毫无技巧可言.
    (更正,在序言中我曾提到有本最近出版的"禅-汇编语言",是唯一的例外,希望读者不要错过.)
    多年来,我一直想写本有关汇编语言写作技巧的书,可惜都得不到机会.这次,为了实现「整合系统」革命性的计划,所有招收的工程师,一概从头训练.由于没有可用的教材,只好自己动手,于是初步有了讲义,再经修改,便成此书.
    我认为,既然汇编语言是种艺术,我们不仅不应该藏私自珍,而且要相互探讨,交流切磋,以期发扬光大.
    不过,技术本身与利用该技术所创造的产品却不能混为一谈,产品是借以谋生的工具,能够生存,大家才有研究发展的机会,也才能把成果贡献给社会.如果国人不尊重别人的产品权利,只是互相抄袭盗用,或能受惠于一时,但影响所及,人人贪图现成,不事发展,则观念停顿,技术落伍,其后果不堪设想.

 

***第一章 基础知识***
    本章讲述的是汇编的一些基础知识,例如寄存器,堆栈,进制等,如果你以前学过汇编,那么你只要看寄存器,寻址方式就可以了.对于初学者,可能看了之后,有些不懂,但是不要紧.你可以继续看下一章,然后再回来看这章,以前不懂的可能就豁然开朗了.希望大家能坚持学下去,不要因为遇到一些困难就退缩了,当然"兴趣是最好的老师",如果你对6502汇编不感兴趣,那么就不要勉强自己,因为每个人都有自己的专长.
    衷心的祝福大家!

 **进制的概念**
  *16进制,2进制*
1.16进制
六进制数的每一位有16个不同的数码,分别为0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F.别表示10进制中的0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
2.2进制
在2进制中,每一位仅有0和1两个可能的数码,所以基数为2.低位和相邻高位的关系是"逢2进1",所以称为2进制.
3.2进制转化为10进制
例如把1101转为10进制=1*2*2*2+1*2*2+0*2+1=8+4+1=13
可见,转化很简单,只是基数是2
4.10进制转化为2进制
例如将173转化为2进制
173/2=86 余数=1
86/2=43  余数=0
43/2=21  余数=1
21/2=10  余数=1
10/2=5   余数=0
5/2=2    余数=1
2/2=1    余数=0
1/2=0    余数=1
所以结果为10110101
5.16进制转化为10进制
例如A2转化为10进制=10*16+2=162
BD转化为10进制=11*16+13=189
6.10进制转化为16进制
例如162转化为16进制
62/16=10,余数=2
所以结果为A2
说明,文曲星上的科学计算支持数值之间的转换,大家可以很方便的使用
例如要把十进制188转化为2进制,那么我们先进入科学计算
输入188,按按=号,然后按中英数,然后按7,就转化为2进制了
2进制    中英数+7
16进制    中英数+9
8进制    中英数+8
10进制    中英数+0
切换到16进制后,是如何输入A,B,C,D,E,F呢?
A中英数+1
B中英数+2
C中英数+3
D中英数+4
E中英数+5
F中英数+6


**认识寄存器**
6502寄存器
1.累加寄存器A
    这是个8位寄存器,既然是8位,那么说明该寄存器中只能存储一个(00-FF)之间的立即数.它与算术逻辑运算单元一起完成各种算术逻辑运算,它既可存放操作前的初始数据,也可存放操作结果,所以称为累加器.
    在6502汇编中,这个寄存器应该算是用的最多的
    大家也不要管那么多,只要知道有这个寄存器,该寄存器可以存放一个00-FF之间的立即数就可以了.
2.变址寄存器X
    也是8位寄存器,它在编程中常被当作一个计数器来使用.它可以由指令控制而被置成一个常数,并能方便的用加1,减1,比较操作来修改和测试其内容,以使得程序能够方便灵活的处理数据块,表格等问题.
3.变址寄存器Y
    用法和变址寄存器X一样,只不过在有些情况下,比如程序中要同时处理两个以上的数据块时,一个变址寄存器显得不够,所以6502中有两个用于变址的寄存器X和Y.
4.程序计数器PC
    它是6502中唯一的16位寄存器,PC是用来存放指令地址码的寄存器,由于程序的执行一般为顺序执行方式,每取出一个指令字节后PC即自动加1,为取下一个指令字节做好准备,所以程序计数器PC中的内容往往是指向下一个指令字节地址,但在执行转移指令时,PC中将被放进要转移的目标地址.
5.堆栈指针S
    它是用来指示堆栈栈顶位置的寄存器,由于6502规定堆栈设在第1页存储器中,所以堆栈指针S也是8位寄存器只用来指出堆栈位置的低8位地址.S具有数据进栈时自动减1,出栈时自动加1的功能.
6.标志寄存器P
    这也是8位的寄存器,但是只用了其中的7位,第5位空着不用.
    每条指令在执行之后往往会发生进位溢出,结果为0,或是结果为负数(大于7F的数叫负数)的情况.指令执行完后常常要保留这些情况作为条件分支的依据,标志寄存器P就是为了适应这需要而设计的,在寄存器P中有以下7个标志位,不过我这里只介绍其中的5位
76543210
NV    BDIZC
C--进位标志.指令执行完毕后的最高进位状态,若最高位有进位则使C=1,若最高位无进位则使C=0
N--零标志.指令执行完毕后结果为0,那么Z=1;否则Z=0.
I--中断标志.此位置0表示允许中断,置1表示禁止中断,但非屏蔽中断不受次约束
V--溢出标志.指令执行后若产生溢出,则次标志位被置1
N--负数标志.指令执行完毕后,若结果最高位为1,则该位置1
    下面我们举例来说明:
例:两个正数61,4A相加
01100001
+01001010
----------
=10101011
    两个正数相加,为什么结果变为负数呢?这是也61+4A=AB,超过了八位寄存器所能表示的最大正数7F,而产生了溢出,那么这时V=1,结果不是0,那么Z=0,结果最高位为1,那么N=1,结果最高位没有进位,那么C=0
    标志位常常在执行条件转移指令时做为条件判断的依据.这在后面的指令系统中会讲到.

 

 

**全面接触NCTOOLS**
    NCTOOLS是我为NC1020写的一个DEBUG软件,大家可以到我的网站http://wqxmcode.8u8.com下载
    因为在我的6502教程中,大量使用了NCTOOLS这个工具,所以你必须拥有这个软件.
    下面我讲讲这个软件的用法:
1.界面
    进入NCTOOLS后,你看到下面的界面,这是最新版本的界面,你可以到我的网站下载
(左上角的数字区为00)
2000-280029002A002B00
2008-2C002D001003FFFF
2010-2020790000FFFFFF
2018-0060000400FFFFFF
2020-3F8DF80368AA20C4
2028-43AD3510C900F003
    最左上面的00代表当前页码是00
    左边的2000代表地址当前地址是2000,后面的280029002A002B00代表地址的内容,但一行显示8个地址的内容.比如第1行,显示的是地址2000,2001,2002,2003.......2007的内容
    所以一个屏幕最大显示48个地址的内容,大家可以看到的
2.按键的用法
(1)I,K键
    按I键,当前页码减1,比如当前页码是02,那么按了I,当前页码变为01
    按K键,当前页码加1,比如当前页码是02,那么按了K,当前页码变为03了
(2)中英数键,切换显示模式,在十六进制和字符模式下切换
    比如当前显示模式是十六进制,那么按中英数,就切换到了字符显示模式
(3)输入法键
    按输入法,以汉字,数字,字符,符号等修改当前内存的内容
(4)0键
    在查找时,若查找的结果不只一个,查找到了一个后,按0键可以继续查找
(5)求助键
    显示本程序附加功能,附加功能将在后面介绍
(6)Z+H键
    同时按住Z,H两个键,可以使地址2000-2FFF全部为FF,并且当前地址转到地址2000
    当你要写程序时,一般先用这个命令,使得地址2000-2FFF全部为FF,比较干净
(7)双上箭头键
    按双上箭头键,当前地址减1,比如当前地址是2000,按了双上箭头,当前地址变为1FFF
单左箭头键
    按单左箭头键,当前地址加1,比如当前地址是2000,按了左箭头,当前地址变为2001
单上箭头键
    按单上箭头键,当前地址减#$10
单下箭头键
    按单下箭头键,当前地址加#$10
双下箭头键
    按双下箭头键,当前地址减#$30
单右箭头键
    按单右箭头键,当前地址加#$30
按Z,同时按双下箭头或单右箭头,可以快速查看地址内容
注:由于一个屏幕显示的地址有限,为了看全部的地址的内容,我们必须按这些键来显示其他地址的内容
(8) R键
    当执行G命令后,可以用该命令查看寄存器的状态
3.命令的使用
说明:①每个命令是以=号,作为确认键
     ②字符B用.代替

(1)内存查看命令V
格式:V XX
     V XXXX
     V XXXXYY
这里XX代表零页地址(00-FF)
    XXXX代表直接地址(0000-FFFF)
    YY代表页码
例如V 30,就是查看当前地址为30的48个地址的内容,如图:
0030-0000 0000 0000 0000
0038-0000 8000 80F8 049B
0040-3000 0000 00F4 4038
0048-D030 4000 F400 0256
0050-0011 0020 AA00 0006
0058-0005 0020 0020 0080
例如 V 4000 03,就是查看03页码的当前地址为4000的48个地址内容,如图:
4028-744F 3650 3A50 2673
(2)内存修改命令 E C
该命令功能非常强大,可以修改 FLASH ROM,RAM
格式:E C XX
     E C XXXX
     E C XXXXYY
例如 输入E C F0,出现下图:
4000-60EA 4040 6F40 9740
4008-BA40 C740 4742 3F40
4010-5271 CB71 3772 AA72
4018-FD72 E472 5A5B 594E
4020-704E 804E 904E A64E
-E c F0
然后按 "=",这时我们就可以输入字节了,注意这里输入的是十六进制,如果输入错了,可以按 单左箭头删除 最近输入的一个字节.
(3)插入字节命令 E I
该命令对于用机器码编写程序的人来说很有用
格式  E I XXXX
这里可能有些人搞不清楚这个命令的用法,我做详细的说明:
XXXX:代表结束地址,由于 插入了字节,那么当前地址后的内容就必然要往后偏移,那么是不是当前地址后的
所以地址都要往后偏移呢?这显然不是,所以这里有个参数 XXXX,这样就规定了当前地址到 地址XX往后偏移.
例如 假设当前地址是 2000
我输入  E I 2007,那么说明地址2000-2007的内容往后偏移,那么往后偏移多少字节呢?这是由你插入的字节数来决定的.
这里 我输入 E I 2007,然后按 = ,然后输入 0102030405,然后按 =,我们看下图:
使用 E I命令前:
2000-A2FF E8BD 0000 9DBF
2008-FFFF FFFF FFFF FFFF
2010-FFFF FFFF FFFF FFFF
2018-FFFF FFFF FFFF FFFF
2020-FFFF FFFF FFFF FFFF
2028-FFFF FFFF FFFF FFFF
使用 E I命令后:
2000-0102 0304 05A2 FFE8
2008-BD00 009D BFFF FFFF
2010-FFFF FFFF FFFF FFFF
2018-FFFF FFFF FFFF FFFF
2020-FFFF FFFF FFFF FFFF
2028-FFFF FFFF FFFF FFFF
    为什么需要这个命令呢,有时候,我们需要往某地址增加几个字节的内容,但如果用E C命令,势必会破坏别的字节,这时候  E I命令就发挥很大作用了.
(4)执行 程序命令G
格式: G XXXX
      G XXXXYY
例如 G 4040,就是执行当前页码的4040开始的程序
例如 G 4040 03,就是执行 03 页码的4040开始的程序
注:执行该命令后,可以 按 R,显示寄存器的状态
(5)4K闪存数据装入地址2000-2FFF 命令 L
格式:L XXXXYY
注意这里的XXXX,只能是 4000,5000,6000,7000,8000....B000.因为是将整整 4K 闪存的数据装入RAM
例如 L 5000 08,就是将08页码的地址 5000-5FFF的内容送地址 2000-2FFF
该命令的作用是修改闪存,例如我们要修改 08 页码的5000-5FFF的内容,我们可以先 L 5000 08,然后用 E C 命令修改地址 2000-2FFF的内容,修改完后,用 W 5000 08命令就可以将地址  2000-2FFF的内容送 08页码的 5000-5FFF.
    这里大家可能很纳闷,E C 命令不是可以直接修改闪存吗,为什么还要这个命令,大家知道闪存的擦写次数是一定的.因为每次修改闪存就需要擦写闪存一次,如果用 E C命令频繁的修改闪存,是不是会加快闪存的老化呢?这我不得而知.不过我建议大家修改大量闪存数据时,还是使用 L 命令先将闪存数据送RAM,然后修改RAM,然后用W 命令将数据送回.
(6)地址 2000-2FFF的内容送闪存 命令 W
格式: W XXXXYY
该命令可以将地址2000-2FFF的内容送4K闪存,这里XXXX同样只能是 4000,5000,6000.....B000
例如  W 5000 08,就是将地址 2000-2FFF的内容送08页码的地址 5000-5FFF
(7)擦除闪存命令 F R
格式 F R XXXXNNNNYY
这里XXXX是开始地址,NNNN是结束地址,YY是页码
例如 F R 4000 4010 08,就是擦除08页码地址 4000-4010的内容为 FF
注意:NC1020擦除的基本单位是 4K,但是我这里可以擦除任意大小的闪存,这并没有否定NC1020以4K为一个单位
我因为做过程序处理,所以有擦除任意大小闪存的功能.
(8)擦除指定字节的某个范围的地址内容  F D
格式: F D XXXXNN
这里 XXXX是结束地址,NN是擦除的字节数
例如我 V 4000 08,那么当前地址是4000,页码是08
我输入 F D 400703,那么功能是地址 4003-4007的内容送地址4000-4004,地址4005-4007的内容擦除为 FF
我举个例子,当前地址是 4000,我输入 F D 4007 02,按 =,看结果如图:
使用 F D 命令前:
4000-0102 0304 0506 0708
4008-FFFF FFFF FFFF FFFF
4010-FFFF FFFF FFFF FFFF
4018-FFFF FFFF FFFF FFFF
4020-FFFF FFFF FFFF FFFF
4028-FFFF FFFF FFFF FFFF
使用 F D 命令后:
4000-0304 0506 0708 FFFF
4008-FFFF FFFF FFFF FFFF
4010-FFFF FFFF FFFF FFFF
4018-FFFF FFFF FFFF FFFF
4020-FFFF FFFF FFFF FFFF
4028-FFFF FFFF FFFF FFFF
可见后面的字节往前移了.
(9)反汇编命令 U
格式: U XX
     U XXXX
     U XXXXYY
这里XX是零页地址,XXXX是直接地址,YY是页码
该命令可以反汇编,例如 U 4040 03,就是反汇编03页码的地址4040开始的内容,如图:
4040- A9 3F    LDA #$3F
4042- 8D C2 17 STA $17C2
4045- A9 01    LDA #$01
4047- 8D C7 17 STA $17C7
404A- A9 80    LDA #$80
404C- 8D 00 17 STA $1700
    这里 "-"前面的4个字节是地址,后面的十六进制是 机器码,再后面的就是反汇编汇编结果了.不懂机器码的朋友只有用这个命令了,可以看程序.
  反汇编状态下,按 跳出,返回
  反汇编状态下,可以使用 A 命令写汇编
  按其他键,反汇编下一地址内容 
(10)写汇编命令 A
    格式:A XXXX
注意:该命令不可以在闪存地址写汇编,那么XXXX的范围是 00页码的 4000-5FFF,0000-3FFF
符号定义:
'#':双上箭头
'$' : 单上箭头
'(' : 双下箭头
')' : 单左箭头
',' : 单下箭头
'Y' : 单右箭头  
'X' : 就是原来的 X 键  
'B' : 这里用 '.' 代替
错误提示:
1.语法出错,请重试!
    说明您写错了指令
2.偏移量发生溢出
    您可以能使用的条件转移指令的跳转步长过大了
3.寻址越位,请重试
    不支持这种寻址方式

按 K 键可以删除最近输入的一个字符
(11)查找十六进制命令 S H
格式: S H XXXXNNNN
     S H XXXXNNNNYY
     S H XXXXNNNNYYKK
这里有三种格式
   S H XXXXNNNN  在当前页码地址范围XXXX - NNNN 查找
   S H XXXXNNNNYY 在YY页码地址范围XXXX - NNNN 查找
   S H XXXXNNNNYYKK 在YY-KK多个连续的页码地址范围 XXXX - NNNN查找,实现多页码的数据的查找
例如: 在当前页码 地址范围4000-BFFF查找 121314.输入  S H 4000BFFF,按 =,然后输入 查找的数据 121314,按 =,如果查找到了,当前地址自动转到该地址,这时,还可以按 0 键继续查找下一个
例如: 在页码 01- 03的地址 4000-7FFF 查找 10111213.输入 S H 40007FFF0103,按 =,输入 10111213,按 = ,开始查找
(12)查找 汉字,字符,符号,数字 命令  S C
和上面一样,不过查找的是 汉字,字符,符号,数字
很有用的命令哦.
(12)中断地址 获取命令 X
格式: X NNNN
例如有中断指令 INT $8A15,那么该指令是执行哪里的程序呢,使用该命令就使当前地址自动转到程序入口点.输入 X 8A15,按 =
3.附加功能
  附加功能有下面几样
(1)屏幕RAM
显示小字模式的屏幕RAM的地址
显示大字模式的屏幕RAM的地址
(2)键盘扫描
若你不知道某个键的扫描码,按 该键就显示了
(3)字符ASCⅡ码
常见的ASCii码
(4)出厂信息
你的机器的出厂信息
(5)目录列表
(6)文件列表
(7)闪存利用表
(8)保存代码
保存地址2000-2FFF的内容,我们写程序的时候,写好了,最好先保存下
(9)导入代码
保存的数据还原到地址2000-2FFF
(10)制作程序
制作BIN程序
你先在地址2010后写好程序,注意地址2000-200F这里不要放东西,因为这里是文件头
然后保证程序的入口地址是 2010
然后制作程序,输入文件名,就可以了
(11)GMUD存档
GMUD存档备份和还原
(12)密码清除
(13)系统重新启动
(14)版本信息

 **6502的寻址**
    6502的寻址方式比较多,有13种之多,所以大家必须掌握他们.我一直都非常重视寻址方式,因为如果你不懂这个,那么你又如何能掌握指令的用法呢?只要你掌握了寻址方式,那么只要告诉你一个指令的寻址方式是什么,那么你就应该能使用该指令了.希望大家重视!!
1.立即寻址
    2字节指令.
  指令的操作数部分给出的不是操作数地址而是操作数本身,我们称为立即数(00-FF之间的任意数)
  寻址方式的指令格式:
   操作码     第一字节
   操作数     第二字节
例如指令 LDA #$30,这里"#"表示后面的是立即数, "$"表示是十六进制表示.这条指令就是 立即寻址,这条指令的功能是将立即数30送寄存器A.
例如指令 ADC #$30      //寄存器A的内容与立即数30和进位C相加,这里操作数30直接给出,所以是立即寻址
        SUB #$30    
        LDX #$30     //把立即数30送寄存器X
        LDY #$30     //把立即数30送寄存器Y
        AND #$30     //寄存器A的内容和立即数30进行逻辑与运算
说明:
(1)立即寻址一般用来设置初始数据
(2)立即寻址的指令,执行速度很快
2.直接寻址
三字节指令.
  指令的操作数给出的是操作数在存储器中的有效地址,所以称为直接寻址.指令格式:
第一字节      操作码
第二字节      操作数地址低字节
第三字节      操作数地址高字节
    由于操作数地址是两个字节,所以它可以是整个内存中的任何一个地址,这种指令表示成机器码时操作数地址是低字节在前,高字节在后.例如指令 LDA $3000,表示成机器码为:AD 00 30,而不是AD 30 00,初学者比较容易混淆这一点.
例如指令 LDA $3000,该指令的功能是将 地址3000中的内容送寄存器A
例如指令 STA $3001,该指令的功能是将寄存器A的内容送地址3001
    我们可在NCTOOLS做这样一个实验,证明一下
输入 A 2000,然后输入以下代码:
2000:LDA $3000
2003:STA $3001
2006:RTS
然后 E C 3000,输入一个数据,比如40,然后 G 2000,然后D 3001,看看地址3001的内容是不是40呢?
3.零页寻址
2字节指令.
先说说什么是零页,地址00-地址FF 就叫做零页地址
零页寻址和直接寻址的区别在于零页寻址方式中操作数的地址仅限于存储器的零页范围(00-FF)
指令格式如下:
操作码 第一字节
操作数 第二字节
例如指令 LDA $F0,功能是将地址 F0的内容送寄存器A,这里F0属于零页范围
有一点需要说清楚,可以用零页寻址的指令,一般就可用直接寻址.
例如指令 LDA $F0 和指令 LDA $00F0,功能是完全一样的,不过我们不应该用直接寻址,为什么呢?因为直接寻址占3个字节,而零页寻址仅仅2个字节,而且零页寻址执行速度快些,所以可以用零页寻址的指令就不应该用直接寻址.
4.累加器寻址
单字节指令.
指令操作所需要的操作数就存在于寄存器A中,所以无须操作数,该指令仅仅占一个字节.
例如指令 LSR,默认就是将寄存器A的内容逻辑右移一位,而无须在操作数中指出操作数,默认操作数就是寄存器A的内容.
指令 PHA, 默认就是将寄存器A的内容压入堆栈.所以也是累加器寻址.
5.隐含寻址
单字节指令
隐含寻址和累加器寻址的区别在于隐含寻址的操作数地址是除寄存器A外的其他寄存器,例如寄存器X,Y,S或P
所以说,累加器寻址其实也可以叫隐含寻址.
例如指令 INX  ;寄存器X的内容加1,这里隐含规定操作数就是寄存器X的内容,所以叫隐含寻址
        INY  ;寄存器Y的内容加1,这里隐含规定操作数就是寄存器Y的内容,所以叫隐含寻址
        DEX  ;......
        DEY  ;......
6.使用寄存器X的直接变址
为了方便起见,我们称该寻址方式为 直接X变址
三字节指令
这种寻址方式是将一个16位的直接地址作为基地址,然后和寄存器X的内容相加,结果就是真正的有效地址,指令格式:
操作码     第一字节
基地址低字节     第二字节
基地址高字节     第三字节
例如指令 LDA $3000,X 它的寻址过程是这样的:
假使此时寄存器X的内容为03,即(X) = 03,地址3003的内容为40,即(3003) = 40,先确定基地址  3000,把基地址3000 + (X) = 3000 + 03 = 3003,计算出有效地址为3003,然后把地址3003的内容送寄存器A
这里我们可以发现,有效地址是随寄存器X的内容发生变化的,所以叫直接X变址.
7.使用寄存器Y的直接变址
该寻址方式和上面是一样的,只不过把寄存器X换成寄存器Y而已.
为了方便起见,我们称该寻址方式为 直接Y变址
三字节指令
这种寻址方式是将一个16位的直接地址作为基地址,然后和寄存器Y的内容相加,结果就是真正的有效地址,指令格式:
操作码     第一字节
基地址低字节     第二字节
基地址高字节     第三字节
例如指令 LDA $3000,Y 它的寻址过程是这样的:
假使此时寄存器Y的内容为03,即(Y)=03,地址3003的内容为40,即(3003)=40,先确定基地址  3000,把基地址3000 + (Y) = 3000 + 03 = 3003,计算出有效地址为3003,然后把地址3003的内容送寄存器A
这里我们可以发现,有效地址是随寄存器Y的内容发生变化的,所以叫直接Y变址.
8.使用寄存器X的零页变址
现在我们应该不看我的下面的说明就能知道这种寻址方式的用法了吧,其实和上面的寻址方式几乎是一样的
只不过这里的基地址仅仅限于零页地址罢了,指令格式如下:
操作码 第一字节
零页基地址 第二字节
例如指令 LDA $F0,X 寻址过程如下:
设(X) = 03,(F3) = 40,基地址F0 + (X) = F0 + 03 = F3
这条指令的功能就是将地址F3的内容送寄存器A
9.使用寄存器Y的零页变址
和上面的"使用寄存器X的零页寻址"不同之处仅仅在于把寄存器X换为寄存器Y.
操作码 第一字节
零页基地址 第二字节
例如指令 LDX $F0,Y 寻址过程如下:
设(Y)=03,(F3)=40,基地址F0+(Y)=F0+03=F3
这条指令的功能就是将地址F3的内容送寄存器X
10.间接寻址
在 6502中,仅仅用于无条件跳转指令 JMP这条指令
三字节指令.
该寻址方式中,操作数给出的是间接地址,间接地址是指存放操作数有效地址的地址,指令格式:
操作码     第一字节
间接地址低字节     第二字节
间接地址高字节     第三字节
由于操作数有效地址是16位的,而每一存储单元内容仅仅8位,所以要通过两次间接寻址才能得到有效地址
我们还是举例子说明吧
这里我们设 (3000) = 23,(3001) = 30
指令 JMP ($3000)的寻址过程是这样的:
先对地址3000间接寻址得到有效地址低8位23,再对地址3001间接寻址得到有效地址高8位30,这样,再把两次结果合在一起就得到有效地址=3023执行该指令后,程序就无条件跳转到地址3023
11.先变址X后间接寻址
两字节指令
指令格式:
操作码 第一字节
零页基地址 第二字节
这种寻址方式是先以X作为变址寄存器和零页基地址IND相加 IND+X,不过这个变址计算得到的只是一个间接地址,还必须
经过两次间接寻址才得到有效地址
第一次对 IND + X 间址得到有效地址低 8 位
第二次对 IND + X + 1 间址得到有效地址高 8 位
然后把两次的结果合起来,就得到有效地址.
我们看一个例子:
指令 LDA ($F0,X) 的寻址过程如下:
这里设 (X) = 02,(F2) = 30,(F3) = 40,那么先得到间接地址 = F0 + (X) = F0 + 02 = F2,第一次对地址F0 + (X) = F2间址得到有效地址低8位 = 30,第二次对地址F0 + (X) + 1 = F3间址得到有效地址高8位 = 40,那么有效地址就是地址4030了,该指令功能就是将地址4030的内容送寄存器A,大家可以在NCTOOLS中试一下
12.后变址Y间接寻址
两字节指令
指令格式:
操作码 第一字节
零页间接地址 第二字节
这种寻址方式是对IND部分所指出的零页地址先做一次间接寻址,得到一个低8位地址
再对IND + 1 作一次间接寻址,得到一个高8位地址
最后把这高,低两部分地址合起来作为16的基地址,和寄存器Y进行变址计算得到操作数的有效地址,注意的是这里IND是零页地址
看一个例子:
例如指令 LDA ($F0),Y,我们看看寻址过程:
设 (F0)=20,(F1)=30,(Y)=03
先对地址F0间址得到低8位地址 20,再对地址F0+1间址得到高8位地址30,把两次结果合起来得到16位的基地址 3020,然后再把地址3020和寄存器Y进行变址,得到有效地址3020+(Y)=3020+03=3023,所以该指令的功能是将地址3023的内容送寄存器A
13.相对寻址
该寻址仅用于条件转移指令,指令长度为2个字节.第1字节为操作码,第2字节为条件转移指令的跳转步长.又叫偏移量D.偏移量可正可负,D若为负用补码表示.
指令格式:
操作码    第1字节
偏移量D   第2字节
相对寻址是用本条指令的第1个字节所在地址和偏移量D相加得到有效地址.
由于在实际中,你是用汇编写程序,所以没有必要搞懂其寻址方式,如果你想用机器码写程序.那么你必须搞懂,下面的东西你就必须看.
(1)负偏移的计算
例如下面的程序
2000:A2 9C         LDA #$9C
2002:BD 00 00      LDA $000,X
2005:9D BF 02      STA $02BF,X
2008:CA            DEX
2009:D0 ??         BNE $2002
200B:60            RTS
这里在地址2009那里的??就是偏移量,这里我们要跳到地址2002,那么怎么计算出偏移量呢?
方法是: A = (2009 + 1) - 2002 + 1 = 2009 - 2002 + 2 =9,然后 B = 256 - 9 = 247,然后化为16进制形式 B = F7.
所以这里 ?? = F7
(2)正偏移的计算
例如下面的程序
2000: A2 00     LDX #$00
2002: BD 00 00  LDA $0000,X
2005: 9D C0 02  STA $02C0,X
2008: E0 9B     CPX #$9B
200A: F0 ??     BEQ $2010
200C: E8        INX
200D: 4C 02 20  JMP $2002
2010: 60        RTS
这里??的计算方法是
A = 2010 - 200A -2 = 4
所以?? = 04

***第 二 章 6502指令系统***
    现在我们就进入6502汇编语言程序设计了,在学习这一章前,我希望大家已经看了6502寻址方式并且对寻址方式已经有了初步的认识,在这一章中,我对每类指令都举了例子,大家可以在 XASM 或NCTOOLS里面实现学习程序设计,就必须多多练习,光看我的例子是远远不够的.
    大家也可以试着反汇编系统的一些程序来练习.
注:addr:代表8位地址    addr16:代表16位地址     data:立即数

`**数据传送指令**

``*LDA--由存储器取数送入累加器 M→A*
符号码格式       指令操作码   寻址方式
LDA ($addr,X)    A1           先变址X后间址
LDA $addr        A5           零页寻址
LDA #$data       A9           立即寻址
LDA $addr16      AD           绝对寻址
LDA ($addr),Y    B1           后变址Y间址
LDA $addr,X      B5           零页X变址
LDA $addr16,Y    B9           绝对Y变址
LDA $addr16,X    BD           绝对X变址
``*LDX--由存储器取数送入累加器 M→X*
符号码格式       指令操作码   寻址方式
LDX #$data       A2           立即寻址
LDX $addr        A6           零页寻址
LDX $addr16      AE           绝对寻址
LDX $addr,Y      B6           零页Y变址
LDX $addr16,Y    BE           绝对Y变址
``*LDY--由存储器取数送入累加器 M→Y*
符号码格式       指令操作码   寻址方式
LDY #$data       A0           立即寻址
LDY $addr        A4           零页寻址
LDY $addr16      AC           绝对寻址
LDY $addr,X      B4           零页X变址
LDY $addr16,X    BC           绝对X变址
``*STA--将累加器的内容送入存储器 A--M*
符号码格式       指令操作码   寻址方式
STA ($addr,X)    81           先变址X后间址
STA $addr        85           零页寻址
STA $addr16      8D           绝对寻址
STA ($addr),Y    91           后变址Y间址
STA $addr,X      95           零页X变址
STA $addr16,Y    99           绝对Y变址
STA $addr16,X    9D           绝对X变址

``*STX--将寄存器X的内容送入存储器 X--M*
符号码格式       指令操作码   寻址方式
STX $addr        86           零页寻址
STX $addr16      8E           绝对寻址
STX $addr,Y      96           零页Y变址
``*STY--将寄存器Y的内容送入存储器 Y--M*
符号码格式       指令操作码   寻址方式
STY $addr        84           零页寻址
STY $addr16      8C           绝对寻址
STY $addr,X      94           零页X变址
``*寄存器和寄存器之间的传送*
符号码格式 指令操作码 寻址方式     指令作用
TAX        AA         寄存器寻址   将累加器A的内容送入变址寄存器X
TXA        8A         寄存器寻址   将变址寄存器X的内容送入累加器A
TAY        A8         寄存器寻址   将累加器A的内容送入变址寄存器Y
TYA        98         寄存器寻址   将变址寄存器Y的内容送入累加器A
TSX        BA         寄存器寻址   将堆栈指针S的内容送入变址寄存器X
TXS        9A         寄存器寻址   将变址寄存器X的内容送入堆栈指针S

[算术运算指令]

1. ADC--累加器,存储器,进位标志C相加,结果送累加器A  A+M+C→A

符号码格式      指令操作码  寻址方式
ADC ($addr,X)   61          先变址X后间址         
ADC $addr       65          零页寻址
ADC #$data      69          立即寻址
ADC $addr16     6D          绝对寻址
ADC ($addr),Y   71          后变址Y间址
ADC $addr,X     75          零页X变址
ADC $addr16,Y   79          绝对Y变址
ADC $addr16,X   7D          绝对X变址
注意:由于进位标志C也会参加运算,所以在做加法运算时,一般要在前面加指令 CLC,清除进位标志
例1:两个8位数加法运算演示,目的是将寄存器A的内容加上地址2100的内容,结果送寄存器A
2000:LDA #$20       //立即数21送寄存器A
2002:STA $2100      //地址2100的内容为20
2005:LDA #$21       //寄存器A的内容为21
2007:CLC            //清除进位标志C,注意:在做加法运算前一定要加该指令
2008:ADC $2100      //寄存器A的内容和地址2100的内容相加
200B:RTS
大家可以进入NCTOOLS,输入 A 2000,然后输入上面代码,然后 输入 G 2000,然后按R看寄存器A的值,是不是等于41呢?
例2:两个16位数加法运算,这里是将0194+01BA,大家注意看看程序与上面有什么不同.
    在这个程序里,先将要相加的数分别放地址2100,2101,2102,2103,具体是这样的:2100:94 01 BA 01,然后先将地址2100的内容+地址2102的内容,结果送地址2104,再将地址2101的内+地址2103的内容,结果送地址2105,那么地址2104,2105的结果,就是两个16位数相加的结果,这里是034E
2000:LDA #$94        //立即数94送寄存器A
2002:STA $2100       //寄存器A的内容送地址2100
2005:LDA #$01        //立即数01送寄存器A
2007:STA $2101       //寄存器A的内容送地址2101
200A:LDA #$BA        //立即数BA送寄存器A
200C:STA $2102       //寄存器A的内容送地址2102
200F:LDA #$01        //立即数01送寄存器A
2011:STA $2103       //寄存器A的内容送地址2103
2014:CLC             //清除进位标志,注意,开始做加法程序前,一定要清除进位标志
2015:LDA $2100      //地址2100的值送寄存器A
2018:ADC $2102      //寄存器A的内容 + 地址2102的内容 + C → A,这里C=0
201B:STA $2104      //寄存器A的内容送地址2104
201E:LDA $2101      //地址2101的内容送寄存器A
2021:ADC $2103      //寄存器A的内容 + 地址2103的内容 + C → A,这里C=1,请注意,这里没有用CLC指令
2024:STA $2105      //寄存器A的内容送地址2105
2027:RTS            //程序结束
在这个程序里,最重要的就是为什么后面的加法指令前面没有用CLC指令,不是说在做加法前要用CLC指令吗?但这里就不一样,由于在前面已经用了CLC指令,将C=0,做了一次加法运算后,会影响标志位C.如果做完第一次加法运算后,C=0,那么说明没有产生进位,C还是等于0,所以没有必要再用CLC指令.如果做完第一次加法运算后,C=1,那么说明产生了进位,如果再做第二次加法运算前,还使用CLC指令,那么计算的结果就是错误的,我们来看 第一次运算后,(2104)=4E,如果第二次运算前使用CLC指令,那么01+01+00=02,最后的结果是这样的:(2104)=4E,(2105)=02,那么说明 0194+01BA=024E,我们口算都能知道这个结果是错误的,实际上由于第一次运算后,产生了进位所以我们要把进位保留即01+01+(C)=01+01+01=03,所以实际的结果是(2104)=4E,(2105)=03

2. SBC--从累加器减去存储器和进位标志C,结果送累加器  A-M-C→A

符号码格式       指令操作码  寻址方式
SBC ($addr,X)    E1          先变址X后间址
SBC $addr        E5          零页寻址
SBC #$data       E9          立即寻址
SBC $addr16      ED          绝对寻址
SBC ($addr),Y    F1          后变址Y间址
SBC $addr,X      F5          零页X变址
SBC $addr16,Y    F9          绝对Y变址
SBC $addr16,X    FD          绝对X变址
注意:由于在做减法运算时,进位标志C会参与运算,所以在做减法前要先加指令 SEC,置进位标志
例:减法指令演示,目的是将寄存器A的内容减去地址2100的内容,结果送寄存器A
2000: LDA #$21                    //立即数21送寄存器A
2002:STA $2100                   //寄存器A的内容送地址2100
2005: LDA #$22                    //立即数22送寄存器A
2007:SEC                         //置位标志位C,注意:在做减法运算前一定要加该指令
2008:SBC $2100                   //寄存器A的内容减去地址2100的内容
200B: RTS

大家可以进入NCTOOLS,输入 A 2000,然后输入上面代码,然后 输入 G 2000,然后按R看寄存器A的值,是不是等于01呢?
例2:求二进制数的平方根
程序目的:将地址2100的内容求得其平方根后,结果送地址2101,为方便计算,这里假设地址2100的内容为整数,方根也取整数
例如,
若(2100)=19       (十进制的25),结果(2101)=05
若(2101)=65       (十进制的101),结果(2101)=0A
求方根方法:我们知道任何整数都有如下性质:
1 * 1 = 1
2 * 2 = 1 + 3
3 * 3 = 1 + 3 + 5
4 * 4 = 1 + 3 + 5 + 7
......
N * N=1 + 3 + 5 + ...... + (2N-1)
那么,我们可以把一个正整数X = N * N连续减去奇数 1, 3, 5, 7......(2N-1)直到结果为0或者不够减为止,所减去奇数的个数就是这个正整数的整数平方根.
在下面的程序里,我们将目标数连续减去地址F0的内容
2000:LDA#$00       //初始化地址F0的内容为00
2002:STA $F0
2004:LDA $2100      //取目标数到寄存器A中
2007:SEC
2008:SBC $F0        //目标数减去地址F0的内容
200A:BCC $2016      //不够减转结束,结果在地址F0中
200C:INC $F0        //地址F0的内容加1,(06)为已减的奇数个数
200E:SBC $F0        //和前面的减法指令合起来正好减去了奇整数
2010:BEQ $2016      //减结果为0,转结束,结果在地址F0中
2012:BCS $2008      //减结果>0,继续减
2014:DEC $F0        //减结果<0,则从结果中扣除1
2016:LDA $F0
2018:STA $2101      //把最后结果送地址2101
201B:RTS

3. INC--存储器单元内容增1  M+1→M

符号码格式     指令操作码 寻址方式
INC $addr      E6         零页寻址
INC $addr16    EE         绝对寻址
INC $addr,X    F6         零页X变址
INC $addr16,X  FE         绝对X变址
这个指令应该很好理解吧,就是把某个地址的内容加1,当然我们也可以用加法指令把某个地址加1,但大家可以看到,这里的指令所占用字节少执行速度也较快.请看下面的比较:
用加法指令把地址2100的内容加1
2000:LDA $2100
2003:CLC
2004:ADC #$01
2006:STA $2100
2009:RTS
所占字节数:10个
用上面的指令把地址2100的内容加1
2000:INC $2100
2003:RTS
所占字节数:4个
一比较,大家就能发现谁占了上风吧!在对某个地址进行加1运算时,应该用INC指令.

4. DEC--存储器单元内容减1  M-1→M

符号码格式      指令操作码  寻址方式
DEC $addr       C6          零页寻址
DEC $addr16     CE          绝对寻址
DEC $addr,X     D6          零页X变址
DEC $addr16,X   DE          绝对X变址

5. 寄存器X,Y加1减1

符号码格式  指令操作码  寻址方式
INX         E8          隐含寻址
DEX         CA          隐含寻址
INY         C8          隐含寻址
DEY         88          隐含寻址

[逻辑运算指令]

1.AND--寄存器与累加器相与,结果送累加器  A∧M→A

符号码格式     指令操作码  寻址方式
AND ($addr,X)  21          先变址X后间址
AND $addr      25          零页寻址
AND #$data     29          立即寻址
AND $addr16    2D          绝对寻址
AND ($addr),Y  31          后变址Y间址
AND $addr,X    35          零页X变址
AND $addr16,Y  39          绝对Y变址
AND $addr16,X  3D          绝对X变址
逻辑与的主要功能是对目的操作数的某些位置0,或测试某位的状态
例1: 屏蔽地址2100的高四位,即将地址2100的高四位清零
2000:LDA $2100
2003:AND #$7F    (7F转化为二进制:0000 1111)
2005:STA $2100
2008:RTS
所以我们可以知道,我们要使地址2100的高四为为0,只要使操作数的高四位为0,低四位为1,即操作数为00001111
然后再把地址2100的内容送寄存器A,把A的内容和0000 1111进行逻辑与运算,那么寄存器A的高四位自然就为0,由于操作数低四位为1,所以寄存器A的低四位不变.
例2: 测试地址2100的第2位的状态,如果第2位=0,那么把00送寄存器X,否则把01送寄存器X
2000:LDA $2100
2003:AND #$04    (04转化为二进制:0000 0100)
2005:CMP #$04
2007:BNE $200C
2009:LDX #$01
200B:RTS
200C:LDX #$00
200E:RTS
在这个程序中,我们要测试第2位的状态,只要让它与 0000 0100 进行逻辑与运算,若第2位为0,那么结果一定为0

如果第二位为1,那么由于操作数的第2位也为1,所以结果为0000 0100,即十六进制的04,所以我们可以判断结果是不是04.如果是04,那么说明第2位为1,否则为0.
随便说个笑话,我们数字电路的老师告诉我们这样的口诀记住逻辑与的用法,那就是 "见0出0,全1出1"

2.ORA--寄存器与累加器相或,结果送累加器  A∨M→A

符号码格式      指令操作码  寻址方式
ORA ($addr,X)   01          先变址X后间址
ORA $addr       05          零页寻址
ORA #$data      09          立即寻址
ORA $addr16     0D          绝对寻址
ORA ($addr),Y   11          后变址Y间址
ORA $addr,X     15          零页X变址
ORA $addr16,Y   19          绝对Y变址
ORA $addr16,X   1D          绝对X变址
先告诉大家那个不登大雅之堂的口诀 "见1出1,全0出0"
逻辑或的功能主要是对目的操作数的某些位置1
例1: 使地址2100的高4位置1
2000:LDA $2100
2003:ORA #$F0   (F0的二进制:1111 0000)
2005:STA $2100
2008:RTS
这里我就不解释为什么这样做了,大家可以想一下.

3.EOR--寄存器与累加器相异或,结果送累加器  A≮M→A

符号码格式      指令操作码  寻址方式
EOR ($addr,X)   41          先变址X后间址
EOR $addr       45          零页寻址
EOR #$data      49          立即寻址
EOR $addr16     4D          绝对寻址
EOR ($addr),Y   51          后变址Y间址
EOR $addr,X     55          零页X变址
EOR $addr16,Y   59          绝对Y变址
EOR $addr16,X   5D          绝对X变址
先告诉大家那个不登大雅之堂的口诀 "相同出0,不同出1"
异或的功能主要就是求补码,加密等.
例1: 求地址2100的内容的反码
2000:LDA $2100
2003:EOR #$FF
2005:STA $2100
2008:RTS
先说明下异或是怎么回事,什么是 "相同出0,不同出1"
这里我们要把立即数7F,40进行异或运算,过程是这样的
(1)先把7F,40转化为二进制形式
(2)然后如果相同的位的值都不相同,那么该位为1,否则为0
(HEX) 7F   (BIN) 0 1 1 1 1 1 1 1
(HEX) 40   (BIN) 0 1 0 0 0 0 0 0    (EOR)
-----------------------------------------
(HEX) 3F   (BIN) 0 0 1 1 1 1 1 1
所以我们要求反码,只要将该数和FF进行异或运算就可以了.
例2: 把地址3000-30FF的数据加密与解密
先说明下为什么异或运算可以对数据进行加密,异或运算有一个特性:
一个操作数(这里设为D1),和另外一个操作数(这里设为D2)进行异或运算,结果为D3,表达式是这样的 D1 EOR D2 = D3,然后如果我们将D3 再和D2进行异或运算,结果一定为D1,这就是加密的依据.
    所以我们要对某段数据进行加密时,只要使改段数据均和某个数进行异或运算,解密时再把加密的数据再和该数进行异或运算即可,这里的某个数我们成为密匙,也就是说,一个人要解密,他必须得到密匙才能解密.
    当然实际运用时,我们可以搞的稍微复杂些,就比如下面的例子,以寄存器X的内容作为对象进行异或.
加密程序如下:
2000:LDX #$50              //这里50就是密匙,不过这里的密匙不是不变的,每异或一次就加1
2002:LDY #$00              //计数器Y初始化为00
2004:INX                   //寄存器X的内容加1,即每异或一次,寄存器X的内容就加1
2005:TXA                   //寄存器X的内容发送给寄存器A
2007:EOR $3000,Y           //寄存器A的内容和地址[3000+Y]的内容进行异或运算
200A:STA $3000,Y           //结果仍然送回原地址
200D:INY                   //计数器Y的值加1
200E:BNE $2004             //当全部加密完,程序结束,否则继续
2010:RTS
解密程序如下:
2100:LDX #$50              //解密时,必须知道加密时X寄存器的初值,否则无法解密
2102:LDY #$00              //计数器Y初始化为00
2104:INX                   //寄存器X的内容加1,即每异或一次,寄存器X的内容就加1
2105:LDA $3000,Y           //地址[3000+Y]的内容送寄存器A
2108:STX $F0               //X寄存器的内容送地址F0,为下面的指令做好准备,因为没有和寄存器X进行异或运算的指令
210A:EOR $F0               //寄存器A的内容和地址F0即寄存器X的内容进行异或
210C:STA $3000,Y           //结果送回原地址
210F:INY                   //计数器Y的值加1
2110:BNE $2104             //当全部解密完,程序结束,否则继续
2112:RTS
    由上面的程序可以知道,加密程序的密匙是可变的,但是变化规律很明显,就是一直加1,解密程序和加密程序几乎是一样的.当然,这样的加密程序是不管用的,讲这个程序的目的只是抛砖引玉,为了说明EOR在加密中的运用.

[置标志位指令]

1. CLC--清除进位标志         0→C   机器码 18     √
2. SEC--置进位标志C          1→C   机器码 38     √
3. CLD--清除十进制运算标志D  0→D   机器码 D8     ×
4. SED--置十进制运算标志D    1→D   机器码 F8     ×
5. CLV--清除溢出标志V        0→V   机器码 B8
6. CLI--清除中断禁止指令I    0→I   机器码 58     √
7. SEI--置位中断禁止标志I    1→I   机器码 78     √
说明:上面的指令中,用的比较多的是指令 CLC,SEC,CLI,SEI,这些指令也没有什么好讲的,只是有两点要注意:
1.如果你在一个程序中用了 SEI 指令,那么程序结束前一定要用 CLI 指令,否则会死机.
2.指令 SED 在文曲星中似乎不能用,我每次用都会死机.也不知道是怎么回事,大家暂时也别用,CLD倒是可以用,不过好象没有用的必要,所以这两条指令我看可以去掉.

[比较指令]

1. CMP--累加器和存储器比较

符号码格式     指令操作码  寻址方式
CMP ($addr,X)  C1          先变址X后间址
CMP $addr      C5          零页寻址
CMP #$data     C9          立即寻址
CMP $addr16    CD          绝对寻址
CMP ($addr),Y  D1          后变址Y间址
CMP $addr,X    D5          零页X变址
CMP $addr16,Y  D9          绝对Y变址
CMP $addr16,X  DD          绝对X变址
    该指令也是做减法操作,将寄存器的内容减去存储器的内容,但它和减法指令有2点区别:一是借位标志C不参加运算,所以在用CMP指令不必加指令SEC,二是减法的结果不送入寄存器A
    该指令运行后,会影响标志位 C,Z,N.我们在实际中尤其要注意它是如何影响标志位C和标志位Z
    若执行指令CMP后,C=1表示无借位,即A》M
    若执行指令CMP后,C=0表示有借位,即A<M
    若执行指令CMP后,Z=1表示A=M
    从上面我们可以判断出A和M谁大谁小,或者A和M是不是相等
例:比较指令演示,演示如何判断A和M的大小
2000:LDA $2100   //地址2100的内容送寄存器A
2003:CMP $2101   //寄存器A的内容和地址2101的内容相比较
2006:BEQ $2016   //若标志位Z=1,那么程序就跳转到地址2016
2008:BCC $2010   //若标志位C=0,那么程序就跳转到地址2010
200A:LDA #$02    //若进位标志C=1,程序不跳转,顺序执行
200C:STA $2102   //若程序执行到这里,说明C=1,那么将立即数02送地址2102,作为地址2100的值>地址2101的值的标志
200F:RTS
2010:LDA #$01    //若标志位C=0,那么将立即数01送地址2102,作为地址2100的值<地址2101的值的标志
2012:STA $2102
2015:RTS
2016:LDA #$00    //若标志位Z=1,那么将立即数00送地址2102,作为地址2100的值=地址2101的值的标志
2018:STA $2102
201B:RTS
进入NCTOOLS,输入 A 2000,然后输入上面的程序,先用E命令 E 2100,输入0102,然后G 2000,然后看地址2102的值
这里因为(2100)=01,(2101)=02,显然01>02那么程序执行后,(2102)=02,你可以用D 2102,看看是不是这样.
你也可以输入别的数值,看看地址2102的内容是不是和预期的一样.

2. CPX--寄存器X的内容和存储器比较

符号码格式   指令操作码  寻址方式
CPX #$data   E0          立即寻址
CPX $addr    E4          零页寻址
CPX $addr16  EC          绝对寻址
这些指令和CMP指令相似,不过前者是寄存器A,后者是寄存器X,另外寻址方式也比较少.
这条指令用的比较多,特别是在循环时
例:CPX在循环程序中的运用,该程序实现了将地址3000-30FF的内容发送到地址3100-31FF
2000:LDX #$00       //初始化寄存器X的值,一开始(X)=0
2002:LDA $3000,X    //地址[3000+X]的内容送寄存器A
2005:STA $3100,X    //寄存器A的内容送地址[3100+X]
2008:INX            //寄存器X的内容加1
2009:CPX #$00       //如果寄存器X的内容=00,那么说明数据已经发送完了.注意:FF+01=00
200B:BNE 2002       //程序跳转到地址2002继续发送直到寄存器X的内容=00
200D:RTS
这里我们来为初学者分析一下代码运行过程:
第1次循环   (X)=00 地址[3000+00]=3000的内容送地址[3100+00]=3100
第2次循环   (X)=01 地址[3000+01]=3001的内容送地址[3100+01]=3101
第3次循环   (X)=02 地址[3000+02]=3002的内容送地址[3100+02]=3102
......
第256次循环 (X)=FF 地址[3000+FF]=30FF的内容送地址[3100+FF]=31FF

3. CPY--寄存器Y的内容和存储器比较

符号码格式   指令操作码  寻址方式
CPY #$data   C0          立即寻址
CPY $addr    C4          零页寻址
CPY $addr16  CC          绝对寻址
这些指令和CPX指令相似,不过前者是寄存器X,后者是寄存器Y.

4. BIT--位测试指令

符号码格式   指令操作码  寻址方式
BIT $addr    24          零页寻址
BIT $addr16  2C          绝对寻址
这条指令的功能和AND指令有相同之处,那就是把累加器A同存储器单元相与,但和AND指令不同的是相与的结果不送入累加器A
另外该指令对标志位的影响也和AND指令不同
若 结果=0,那么Z=1
若 结果<>0,那么Z=0
N=M的第7位
V=M的第6位
所以执行该指令后N,V两标志位的状态就是参加与操作的存储单元的最高两位状态
这些指令在通讯程序中用的相当多,大家要给予足够的重视,是很有用的指令

[移位指令]

1. 算术左移指令ASL

符号码格式      指令操作码  寻址方式
ASL             0A          累加器寻址
ASL $data       06          零页寻址
ASL $addr16     0E          绝对寻址
ASL $addr,X     16          零页X变址
ASL $addr16,X   1E          绝对X变址
ASL移位功能是将字节内各位依次向左移1位,最高位移进标志位C中,最底位补0
ASL执行结果相当于把移位前的数乘2
例如ASL的应用
2000:LDA #$20            //把立即数20送累加器A
2002:ASL                 //累加器A的内容算术左移
2003:STA $2100           //把累加器A的内容送地址2100
2006:ASL $2100           //地址2100的内容算术左移
2009:LDA $2100           //地址2100的内容送累加器A
200C:RTS                 //程序结束

2. 逻辑右移指令LSR

符号码格式     指令操作码  寻址方式
LSR            4A          累加器寻址
LSR $data      46          零页寻址
LSR $addr16    4E          绝对寻址
LSR $addr,X    56          零页X变址
LSR $addr16,X  5E          绝对X变址
该指令功能是将字节内各位依次向右移1位,最低位移进标志位C,最高位补0.
该操作对于无符号数和正数相当于乘1/2
例:拆字程序,将地址2100单元的高四位送地址2101的低四位,将地址2100单元的低四位送地址2102的底四位,并且清除地址2101和地址2102的高四位
2000:LDA $2100           //地址2100的内容送A
2003:AND #$0F            //A和0F进行逻辑与运算,屏蔽了A的高四位
2005:STA $2102          //结果送地址2102
2008:LDA $2100         
200B:LSR                //将A的高四位挪到低四位,高四位补0
200C:LSR
200D:LSR
200E:LSR
200F:STA $2101          //结果送地址2101
2012:RTS

3. 循环左移指令ROL

符号码格式     指令操作码  寻址方式
ROL            2A          累加器寻址
ROL $data      26          零页寻址
ROL $addr16    2E          绝对寻址
ROL $addr,X    36          零页X变址
ROL $addr16,X  3E          绝对X变址
ROL的移位功能是将字节内容连同进位C一起依次向左移1位

4. 循环右移指令ROR

符号码格式      指令操作码  寻址方式
ROR             6A          累加器寻址
ROR $data       66          零页寻址
ROR $addr16     6E          绝对寻址
ROR $addr,X     76          零页X变址
ROR $addr16,X   7E          绝对X变址


[堆栈操作指令]

1. 累加器进栈指令 PHA
PHA是隐含寻址方式的单字节指令,操作码是 48
功能是把累加器A的内容按堆栈指针S所指示的位置送入堆栈,然后堆栈指针减1
该指令不影响标志寄存器P的状态
2. 累加器出栈指令 PLA
PLA是隐含寻址方式的单字节指令,操作码是 68
功能是先让堆栈指针S+1,然后取加过1的S所指向的单元的内容,把它送累加器A
该指令影响标志寄存器P中的N,Z两标志位
3. 标志寄存器P进栈指令 PHP
PHP是隐含寻址方式的单字节指令,操作码是 08
功能是把标志寄存器P的内容按堆栈指针S所指示的位置送入堆栈,然后堆栈指针减1
该指令不影响标志寄存器P的状态
4. 标志寄存器P出栈指令 PLP
PLP是隐含寻址方式的单字节指令,操作码是 28
功能是先让堆栈指针S+1,然后取加过1的S所指向的单元的内容,把它送标志寄存器P
5. 堆栈用法举例
   堆栈是一个存储区域,用来存放调用子程序或响应中断时的主程序断点,以及其他寄存器或存储器的内容.
   当主程序需要调用子程序时,有一组中间结果及标志位的状态需分别保留在寄存器和标志寄存器中.但被调用的子程序执行时,也需要占用这些寄存器并影响标志寄存器,这样除了在执行调用指令时将断点(调用指令后紧接着的一条指令地址)保存在堆栈中外,还必须将原主程序中保留在寄存器中中间结果和标志位的状态保留在堆栈中,直到子程序结束,返回主程序时,再将这些中间结果及标志位状态送回寄存器和标志寄存器中.
   6502的堆栈地址是0100-01FF,但由于实际上系统也占用了堆栈,所以堆栈指针并不是指向栈底,我们可以来测试一下.
   进入 NCTOOLS下,A 2000
2000: TSX      堆栈指针低8位送寄存器X
2001: RTS
   然后 G 2000,按 R 查看寄存器状态,结果发现 (X) = B4
   这里说明堆栈指针的值是 01B4,但这是可变的
   下面我们来看看堆栈指针随进栈和出栈的变化情况:
A 2000
2000: TSX            ;堆栈初始地址低8位送寄存器 X
2001: STX $3000
2004: PHA            ;寄存器A的数据压入堆栈
2005: TSX            ;把这时堆栈地址低8位送寄存器 X
2006: STX #3001      ;结果送地址3001
2009: PHA            ;寄存器A的数据压入堆栈
200A: TSX            ;把这时堆栈地址低8位送寄存器 X
200B: STX $3002      ;结果送地址3002
200E: PLA            ;从堆栈中弹出一个数据
200F: TSX            ;把这时堆栈地址低8位送寄存器 X
2010: STX $3003      ;结果送地址3003
2013: PLA            ;从堆栈中弹出一个数据
2014: TSX            ;把这时堆栈地址低8位送寄存器 X
2015: STX $3004      ;结果送地址3004
2018: RTS
然后我们 G 2000
然后 V 3000
    看见地址3000-3004的内容分别是  B4 B3 B2 B3 B4
    说明堆栈指针地址 是 01B4 01B3 01B2 01B3 01B4
    大家可见,当把一个数据进栈后,堆栈指针地址 减 1了,最开始堆栈指针地址是 01B4,后来不就是 01B3了.但把一个数据出栈后,是把最近压入堆栈的数据先出栈,大家可以看到,当又压入一个数据进栈后堆栈指针地址为01B2,但是当我们弹出一个数据后,堆栈指针为 01B3,说明把最近的一个数据弹出了.
    所以6502堆栈是 遵循 "先进后出"的原则,就好象我们把书一本一本的层叠,但我们拿书的时候,拿的却是最上面的那本.
   我举个例子:
A 2000
2000: LDA $00        ;地址 00的内容压入堆栈
2002: PHA
2003: LDA $0A        ;地址 0A的内容压入堆栈
2005: PHA
2006: LDA $0D        ;地址 0D 的内容压入堆栈
2008: PHA
2009: PLA            ;注意:出栈时,先出的是地址0D 的内容,所以是把结果送地址0D,不是地址00
200A: STA $0D
200C: PLA
200D: STA $0A
200F: PLA
2010: STA $00
2012: RTS
   当调用子程序时,系统自动将子程序 后一指令的地址 - 1 送堆栈,我们举例说明:
子程序从地址2100开始:
A 2100
2100: LDY #$00
2102: LDA #$01         ;堆栈指针地址高 8 位 送地址46
2104: STA $46
2106: TSX             ;堆栈指针地址低 8 位 送寄存器X
2107: INX             ;寄存器X内容加1
2108: STX $45         ;送地址 45
210A: LDA ($45), Y    ;读取目标地址值送地址3000
210C: STA $3000
210F: INC $45         ;堆栈指针 低 8位加 1
2111: LDA ($45), Y    ;读取目标地址值送地址3001
2113: STA $3001     
2116: RTS
主程序从地址2000开始:
A 2000
2000: JSR $2100       ;调用子程序 $2100
2003: RTS
    这里我们 G 2000,V 3000,发现(3000) = 02,(3001) = 20,执行完JSR $2100后,应该执行 地址 2003的指令,为什么是 2002呢?因为返回后,程序计数器还会自动加 1.
    程序在调用 子程序时,会保存 下一指令地址 -1,到堆栈,保存的顺序是 先存地址 高8位,再存地址 低8位.

[转移指令]
    程序在大多数的情况下是按顺序执行的,即依靠程序计数器PC不断自动加1的操作,指示出下一条指令所在地址,这样计算机就可以按照程序中指令的排列顺序一条接一条的执行下去.
    不过在某些条件下,需要改变程序顺序执行的次序,而转入执行另一个地址中存放的指令,这就要依靠转移指令来实现.
    在6502中有无条件转移和条件转移,无条件转移是无条件的将程序转向另外一个地址,而条件转移是当满足某些条件时程序才发生转移,比如C=0,C=1,Z=1,Z=0等条件.
    无条件转移的跳转步长为整个64K内存,即可以跳转到任意地址
    条件跳转指令的跳转步长是有限制的  正跳转127个字节   负跳转128个字节

1. JMP--无条件转移指令
符号码格式 指令操作码 寻址方式
JMP  $data16 4C 绝对寻址
JMP ($data16) 5C 间接寻址
2. 条件转移指令
符号码格式    指令操作码  寻址方式   指令功能
BEQ $data16   F0          相对寻址   如果标志位Z=1则转移,否则继续
BNE $data16   D0          相对寻址   如果标志位Z=0则转移,否则继续
BCS $data16   B0          相对寻址   如果标志位C=1则转移,否则继续
BCC $data16   90          相对寻址   如果标志位C=0则转移,否则继续
BMI $data16   30          相对寻址   如果标志位N=1则转移,否则继续
BPL $data16   10          相对寻址   如果标志位N=0则转移,否则继续
BVS $data16   70          相对寻址   如果标志位V=1则转移,否则继续
BVC $data16   50          相对寻址   如果标志位V=0则转移,否则继续
这里我重点讲讲用的最多的 BEQ,BNE,BCC,BCS
①BNE 如果标志位 Z = 0则转移,否则继续
    在6502中,要判断两个数是不是相同,就可以使用该指令
例1:判断 地址3000与地址3001的内容是不是相同,若相同,则送 01 到地址3002,否则送 00
A 2000
2000:LDA $3000          ;读取地址3000的内容到寄存器A
2003:CMP $3001          ;和地址3001的内容比较,其实就是把地址3000的内容减去地址3001的内容
2006:BNE $200E          ;若Z = 0,说明结果不等于 0 ,那么说明两个地址的内容不同,程序转到地址200E
2008:LDA #$01           ;这里说明Z = 1,那么说明两个数相同
200A:STA $3002
200D:RTS
200E:LDA #$00
2020:STA $3002
2023:RTS
    从上面的程序,我们知道,要比较两个数是不是相同,需要先使用比较指令,然后通过标志寄存器的状态位来判断是不是相同
②BEQ 如果标志位 Z = 1,那么就转移,否则继续
例: 判断地址3000与地址3001的内容是不是相同,若相同,则送 01 到地址3002,否则送 00
A 2000
2000:LDA $3000
2003:CMP $3001
2006:BEQ $200E
2008:LDA #$00
200A:STA $3002
200D:RTS
200E:LDA #$01
2020:STA $3002
2023:RTS
该程序和上面的几乎是一样的,大家分析一下吧.
③BCC 如果标志为 C = 0,转移,否则继续
    该指令可以用来判断两个数谁大谁小
例:判断地址3000和地址3001的内容,谁大谁小,若地址3000的内容大,送 01 到地址3002,若地址3001的内容大,送 02到地址3002,如果都是一样大,送00到地址3002
A 2000
2000:LDA $3000              ;读取地址3000的内容到寄存器A
2003:CMP $3001              ;和地址3001的内容比较,其实就是把地址3000的内容减去地址3001的内容
2006:BEQ $2010              ;如果 Z = 1,那么说明相同,转地址2010,送 00 到地址3002
2008:BCC $2016              ;如果 C = 0,那么说明地址 3000的内容 < 地址3001的内容,转地址2016,送 02到地址3002
200A:LDA #$01                ;这里说明 C = 1,那么说明地址3000的内容 > 地址3001的内容
200C:STA $3002
200F:RTS
2010:LDA #$00
2012:STA $3002
2015:RTS
2016:LDA #$02
2018:STA $3002
201B:RTS
④BCS 若 C = 1,则转移,否则继续
和上面的用法是一样的,大家用该指令完成上面的功能吧!
3. 转移到子程序指令JSR和从主程序返回指令RTS
JSR指令仅仅是 绝对寻址,它的操作码是 20
RTS指令是     隐含寻址,它的操作码是 60     
    在程序设计中,如果程序比较大,那么一般是采取模块化设计方法,把一个大的程序分割成若干小程序,然后在主程序调用这些小程序.在6502中就是用JSR这条指令调用子程序的.
    转子指令和转移指令的区别在于转移指令控制程序转出后就不再返回了,而转子指令使程序转向子程序后,当子程序被执行完后还要返回主程序被打断处,实现这个返回是依靠在子程序末尾使用一条子程序返回指令RTS,就可以控制程序自动返回到主程序被打断处.
例如:
指令
2000:JSR 2100      //程序执行到这里时,就跳转到地址2100开始执行那里的程序
2003:....      
....
2100:....
....
XXXX:RTS          //当程序执行到这里后,会自动返回主程序被打断处,即跳转到地址2003那里继续执行


[中断指令]

在文曲星内部大量使用了这种指令,该指令占三个字节.
操作符为 INT,机器码为 00
例如 INT $8A01
     INT $C001
那么INT $8A01是执行那里的程序呢?首先8A 是页码,01是字偏移量
我们先转到8A页码,看到下面的数据:
4000-60EA 8540 5549 6F56
4008-DE56 5E63 5A68 C75B
4010-5177 5E7B 028B 688D
4018-C06D 0E6C C57C CB85
4020-DD83 74A6 4AA0 4AA0
4028-18A7 22A7 26A7 30A7
    这里我们先要切换到8A页码,然后根据01,知道执行地址是4085,因为60EA不算,从8540开始算偏移1,5549算偏移2,所以 INT $8A02就是执行地址4955,大家可以自己算.
    这里的 INT $C001就不是转到C0页码了,而是当地址(0A) = 00时,C000-DFFF那里算
C000-60EA 59C0 01C1 25C2
C008-7FC2 5FC2 52C3 E0C7
C010-62C3 53C3 BEC7 4AC4
C018-ADC8 17C4 17D0 3BD0
C020-71D0 39D1 38D2 4BD2
C028-5ED2 ABD2 F7D2 B2D3
    这里INT $C001 就是执行C059

***第三章 基本程序设计方法***
   我们介绍了 循环程序,子程序,显示程序的设计与实现.
   我们在编程中,大量使用了上面的这些程序,所以大家必须掌握它们.

[循环程序设计]

例1:找最大值  从地址2100-地址21FF中找最大值,结果送地址2200
2000:LDX #$00             //初始化计数器X的内容=00
2002:LDA #$00             //先假设最大值=00,这是最小的可能值
2004:CMP $2100,X          //下一个数比当前最大值大吗?
2007:BCS $200C            //否,维持寄存器A的值不变
2009:LDA $2100,X          //是,用该数代替最大值
200C:INX                  //计数器X的内容加1
200D:BNE $2004            //继续循环比较,直到全部比较完毕
200F:STA $2200            //结果送地址2200
2012:RTS
通过上面的程序,我们可以知道,一个循环程序一般是由4个部分组成
①初始化部分.    它建立计数器的初值和其他变量的起始值
2000:LDX #$00             //初始化计数器X的内容=00
2002:LDA #$00             //先假设最大值=00,这是最小的可能值
②处理部分.      在这部分进行实际的数据处理,这是循环程序中要求重复执行的那段代码
2004:CMP $2100,X          //下一个数比当前最大值大吗?
2007:BCS $200C            //否,维持寄存器A的值不变
2009:LDA $2100,X          //是,用该数代替最大值
③循环控制部分.  它为下一轮循环做好修正计数器的准备,并且判断是否循环结束
200C:INX                  //计数器X的内容加1
200D:BNE $2004            //继续循环比较,直到全部比较完毕
④结束部分.      它分析和存放结果
例2://数据块搬家    地址2100-21FF的内容送地址2200-22FF
2000:LDX #$00          //初始化部分
2002:LDA $2100,X       //处理部分
2005:STA $2200,X
2008:INX               //循环控制部分
2009:BNE $2002
200B:RTS               //结束部分
例3. 在文曲星的程序中,大量使用了下面的这种循环方式,请看
2000: LDX #$9C
2002: LDA $3FFF,X
2005: STA $1FFF,X
2008: DEX
2009: BNE $2002
200B: RTS
    这段程序实现的是将地址 4000-409B 的内容送地址 2000-209B,发送的字节数是 9C个
    初学者可能会说,为什么是 LDA $3FFF,X    STA $1FFF,X 呢,为什么不是 LDA $4000,X   STA $2000,X 呢?这里 3FFF + 9C = 309B,因为最后的地址是309B,如果是 LDA $4000,X,那么最后的地址岂不是 4000 + 9C = 409C吗?显然是错误的.所以地址要减 1
例4:在我们前面所讲的数据传送例子中,只能实现少量数据的传送,这里我们讲讲如何实现大量数据的传送大量数据的搬家:地址2000-2FFF的内容送地址3000-3FFF
2000:LDY #$00          
2002:LDA #$00
2004:STA $40
2006:LDA #$20
2008:STA $41
200A:LDA #$00
200C:STA $42
200E:LDA #$30
2010:STA $43
2012:LDA ($40),Y
2014:STA ($42),Y
2016:LDA $40
2018:CMP #$FF
201A:BNE $2023
201C:LDA $41
201E:CMP #$2F
2020:BNE $2023
2022:RTS
2023:INC $40
2025:BNE $2029
2027:INC $41
2029:INC $42
202B:BNE $202F
202D:INC $43
202F:JMP $2012
这个程序的代码稍微长了些,但是我们分析一下,发现并不复杂
首先要说明一下,在这个程序中我们使用了 后变址Y间址 的寻址方式
地址40 41   放源地址
地址42 43   放目标地址
开始的时候,对地址40,41,42,43进行初始化为 40:00 20 00 30,并且让寄存器Y的值=00
该段代码如下:
2000:LDY #$00
2002:LDA #$00
2004:STA $40
2006:LDA #$20
2008:STA $41
200A:LDA #$00
200C:STA $42
200E:LDA #$30
2010:STA $43

然后运用 后变址Y间址 的寻址方式,先读取地址40 41的值,把地址40的值作为源地址的低8位,地址41的值为高8位.这里一开始(40)=00,(41)=20,那么就是将地址[2000+Y]的内容送寄存器A,由于一开始就让寄存器Y的值=00.所以执行该指令的结果就是将地址2000的值送寄存器A,然后 同样用 后变址Y间址 的寻址方式 先读取地址42 43的值,把地址42的值作为目标地址的低8位,地址43的值为高8位.这里一开始(42)=00,(43)=30,那么就是将寄存器A的值送地址[3000+Y],即将A的值送地址3000
该段代码如下:
2012:LDA ($40),Y
2014:STA ($42),Y
然后就是判断地址40,41的内容是不是(40)=FF,(41)=2F,如果相等,说明数据已经传送完成.
如果不相等,那么把地址40,42的值加1,继续传送.这里有段代码可能有人不明白
2023:INC $40
2025:BNE $2029  
2027:INC $41
2029:INC $42
202B:BNE $202F
202D:INC $43
如果地址40的值加1后,地址40的值=00,那么地址41的值就应该加1,比如开始(40)=FF,(41)=20,地址40的值加1后,(40)=00,如果此时地址41的值不加1,那么(40)=00,(41)=20,那么显然是错误的  


[子程序的设计]

    在程序设计中,如果程序要解决的问题比较复杂,则程序的代码就会比较多,这时就应该采用模块化的设计方法.
    所谓模块化程序设计,就是先列举出一个大的模块中,各个个别的小模块,分别建立出各个模块的解决方案,再应用于大的模块中.在6502汇编程序设计中,只能通过设计子程序的方式建立各个小的模块,然后在主程序中调用它们.
    设计子程序的优点:
1.使程序的可读性,可维护性大大增强
2.使得程序的空间效率得到大大的提高
3.使得代码得到重复利用,程序的可扩展性强
    设计子程序的原则:
1.空间小,结构完整,可以独立被调用:子程序不能太大,否则无法灵活利用.程序的结构又必须独立完整,以便于分解组合,供二次开发程序自由调用
2.效率高,弹性大,便于其他程序调用:各种应用程序都有各自特殊的需求和条件,子程序的设计就要考虑到通用性,否则很难满足各种条件
3.功能明确,其变化以参数来实现
4.子程序执行完后,以寄存器或地址作为返回的数据
5.要保护个寄存器的内容
    由于在子程序中也要用到一些寄存器,那么这比如会影响寄存器的值,所以我们应该在子程序前把各寄存器压入堆栈,在执行子程序结束要把各寄存器从堆栈中弹出.希望大家能养成这样的习惯,因为在一些大的程序中,一些错误往往就是因为没有保护好寄存器的原因,而且这些错误又比较难发现,所以如果每个子程序都能保护好寄存器的值,那么在程序调试时,就不需要考虑是否是没有保护寄存器的原因了,就可以节约大量的程序调试时间
例: 在前面的循环程序设计教程中,我们讲解了将地址2000-2FFF的内容送地址3000-3FFF.假设在我们设计的一个程序里,我们需要实现将地址2000-2FFF的内容送3000-3FFF,那么我们是不是就将前面的程序做为子程序呢?从程序设计的角度讲,这样的子程序显然是非常失败.这样的子程序没有任何弹性,假如我又要实现将地址0000-0FFF的内容送地址3000-3FFF,是不是又重新设计呢?
    所以我们应该设计一个可以通用的子程序,可以实现任意段地址的数据之间的传送.
这里我们可以这样规定:
地址40 41:放源开始地址
地址42 43:放源结束地址
地址44 45:放目标开始地址
这样我们只要把源开始地址,源结束地址,目标开始地址 确定,放地址40 41 42 43 44 45,然后调用该子程序就可以了,程序的功能是不是得到大大的拓宽呢?还大大节约了空间.下面我们就讲解如何实现,其实和前面的实现2000-2FFF送地址3000-3FFF基本上是一样的.
2000:PHA                            //把各个寄存器压入堆栈
2001:TXA
2002:PHA
2003:TYA
2004:PHA
2005:PHP
2006:LDY #$00          
2008:LDA ($40),Y
200A:STA ($44),Y
200C:LDA $40
200E:CMP $42                   //关键的一步,把地址40 41的值和地址42 43比较,若相同,就说明完了
2010:BNE $201F
2012:LDA $41
2014:CMP $43
2016:BNE $201F
2018:PLA                          //发送完毕后,恢复各个寄存器的值
2019:TAY
201A:PLA
201B:TAX
201C:PLA
201D:PLP
201E:RTS
201F:INC $40
2021:BNE $2025
2023:INC $41
2025:INC $44
2027:BNE $202B
2029:INC $45
202B:JMP $2008
    我们可以发现,这个子程序是不是更理想呢,你想实现数据的传送,都可以用这个子程序,而且程序代码又不长.
    由于本人时间有限,所以就讲这么多,希望大家能编写出理想的子程序!


[显示程序设计]

    显示的编程步骤是:
①输入要显示的数据到地址,在NCTOOLS里,可以按 输入法 输入字符,汉字或符号,可以按 E C命令输入16进制
②调用 清屏中断 INT $8A2E
③把要显示信息的数据全部送到相应的屏幕 RAM区域
④调用屏幕刷新中断,INT $8A15.这里,若是显示小字 (0402) = 01,否则 (0402) = 00
⑤为了使显示的信息可以被看到,我们一般还调用 读键中断,INT $C008
显示小字屏幕RAM区域(一共能显示6行,每行最大显示26个字符,13个汉字)
02C0 02C1 02C2 02C3 ... 02D9       第1行
02DA 02DB 02DC 02DD ... 02F3       第2行
02F4 02F5 02F6 02F7 ... 030D       第3行
030E 030F 0310 0311 ... 0327       第4行
0328 0329 032A 032B ... 0341       第5行
0342 0343 0344 0345 ... 035B       第6行
显示大字屏幕RAM区域(一共显示5行,每行最大显示20个字符,10个汉字)
02C0 02C1 02C2 02C3 ... 02D3       第1行
02D4 02D5 02D6 02D7 ... 02E7       第2行
02E8 02E9 02EA 02EB ... 02FB       第3行
02FC 02FD 02FE 02FF ... 030F       第4行
0310 0311 0312 0313 ... 0323       第5行
现在我们实现在屏幕上显示,以小字显示
  我的每个幻想
      总在每一个秋天飞扬
  我的每个悲伤
      总在每一个夜里生长
  我的每次飞翔
      总在漫无目的路上
我们按步骤来
1.输入信息,我们从地址2000开始,按输入法,输入
需要注意的是,由于每行的信息长度不够26个字节,所以我们要在后面补 20
例如 按 输入法 输入"我的每个幻想",然后我们还要用E C 命令输入26 - 12 = 14个 20
2. 然后我们开始写程序,从地址2100开始
A 2100
2100: INT $8A2E               ;清屏
2103: LDX #$9C                ;寄存器X的内容为要发送的字节数,这里是一个屏幕 26 * 6 = 156 即$9C
2105: LDA $1FFF,X             ;读取信息数据
2108: STA $02BF,X             ;送显示RAM
210B: DEX
210C: BNE $2105
210E: LDA #$01                ;确定是显示小字
2110: STA $0402
2113: INT $8A15               ;调用显示中断
2116: INT $C008               ;调用读键中断
2119: RTS
我们 G 2100,看看吧,是不是很漂亮.


[分支程序设计]
    什么叫分支程序,其实很简单,就是利用条件或无条件转移指令改变程序的流向.
例. 编写按下面的函数式求Y值,变量X在寄存器A中,结果送寄存器Y
X + 1   ( X > 0 )
Y = 0   ( X = 0 )
1       ( X < 0 )
A 2000
2000: BMI $2009          ;若结果为负数,那么转地址2009
2002: BEQ $200C          ;若 = 0,转 地址200C
2004: CLC                ;这里说明 > 0
2005: ADC #$01
2007: TAY
2008: RTS
2009: LDY #$01
200B: RTS
200C: LDY #$00
200E: RTS
    从上面的程序我们看到,分支程序就是根据不同的条件使程序转向不同的地址去执行相当于C语言的IF 语句,请看
#include <stdio.h>
  #include <conio.h>
  void main()
  {
       int x;                         //定义变量X
       int y;                         //定义Y
       clrscr();
       printf("Please input X: ");
       scanf("%d", &x);
       if ( x == 0 )
           y = 0;
       else if ( x > 0 )
           y = ++ x;
       else
           y = 1;
       printf("y = %d",y);
       getch();
   }


[查表程序设计]
    查表程序就是把事先计算出的结果或测得的数据按一定顺序组成表格,再利用查表程序查出所出的结果.
    查表法主要是针对某些复杂的函数的计算,若每次都用计算程序,程序很长,并且很费时间,还有一些非线性参数并非一般算术运算可以解决,所以用查表能很方便的解决.
    查表法还可以处理代码转换,键值搜索等.
    查表程序结果如下:
①所需函数计算结果或测量所得数据按一定规律制成表格
②数据表一般放程序区
③用查表法实现查表
例1. 已知0-9十个16进制数,对应的ASCii码为 30-39,编程实现0-9十个数换成ASCii码
这里我们这样安排,0-9在地址2000-2009,30-39在地址2010-2019,即:
2000: 00 01 02 03 04 05 06 07 08 09
2010: 30 31 32 33 34 35 36 37 38 39
这里我们设寄存器A放16进制数,结果放寄存器Y.如果没有找到,设置标志位 C= 0,找到了设置 C = 1
A 2020
2020: LDX #$0A           ;一共10个数据
2022: CMP $1FFF,X        ;查表比较
2025: BEQ $202C          ;找到了,转202C
2027: DEX
2028: BNE #2022
202A: CLC                ;没有找到,C = 0
202B: RTS
202C: LDA $200F,X        ;把结果送Y
202F: TAY
2030: SEC                ;找到了,C = 1
2031: RTS


[小牛试刀]

用NCTOOLS修改 黄金英雄坛说
    大家一定很羡慕那些会修改游戏的朋友,其实学了汇编后,这些都是些雕虫小技,你完全可以做到,下面我们就来实现修改 黄金英雄坛
    这里我修改的目的有三个:
1. 不需要攒够经验值,就可以向 独行大侠 请教
2. 拜了一个师傅后,还可以拜另外的师傅,从而学习更多的武功
3. 搬一次石头,得10000经验值 和 10000潜能
    这些修改其实是很简单的过程,下面我们来实现它们:
1.由于当你经验不足时,独行大侠 说 "去去去,攒够经验再来吧!".所以我们根据这一线索,就可以找到相关的程序
    我们进入 NCTOOLS,用 S C 命令查找 "去去去":
S C 4000BFFF040F,按 = 号,输入 "去去去"
    在我的机子上,是在 0B 页码的地址 7BDC
    我们应该知道,要显示这一文字,是要调用子程序的,而入口参数应该就是该段文字的开始地址
    所以我们判断,应该有这样的程序,
LDA #$DC    A9 DC
STA ??      85 ??
LDA #$7B    A9 7B
STA ??      85 ??
    我们输入  S C 4000BFFF0B,输入查找内容 A9 DC 85
    结果找到了,在地址7BC9
7BC9: A9 DC 85 A6 A9 7B 85 A7
7BD1: 20 D9 62 60
    我们反汇编
7BC9: LDA #$DC
7BCB: STA $A6
7BCD: LDA #$7B
7BCF: STA $A7
7BD1: JSR $62D9
7BD4: RTS
    这里我们应该知道,前面一定有条件跳转指令,当不满足某些条件时,程序才转到地址7BC9执行.当满足条件时,独行大侠 才教你.
    我们往前面看,看到下面数据
7B99: C9 07 D0 38 A9 03 85 82
7BA1: A9 00 85 83 A9 40 85 80
7BA9: A9 0D 85 81 AD B3 2E C5
7BB1: 83 D0 13 AD B2 2E C5 82
7BB9: D0 0C AD B1 2E C5 81 D0
7BC1: 05 AD B0 2E C5 80 B0 0C
7BC9: A9 DC 85 A6 A9 7B 85 A7
7BD1: 20 D9 62 60
    我们反汇编下:
7B99: CMP #$07
7B9B: BNE $7BD5 
7B9D: LDA #$03          ;03送地址82
7B9F: STA $82
7BA1: LDA #$00          ;00送地址83
7BA3: STA $83
7BA5: LDA #$40          ;40送地址80
7BA7: STA $80
7BA9: LDA #$0D          ;0D送地址81
7BAB: STA $81
7BAD: LDA $2EB3         ;地址2EB3的内容和地址83的内容比较
7BB0: CMP $83
7BB2: BNE $7BC7         ;不同,则转 $7BC7
7BB4: LDA $2EB2         ;地址2EB2的内容和地址82的内容比较
7BB7: CMP $82    
7BB9: BNE $7BC7         ;不同,转地址 7BC7
7BBB: LDA $2EB1         ;地址2EB1的内容和地址81的内容比较
7BBE: CMP $81          
7BC0: BNE $7BC7          ;不同,转地址 7BC7
7BC2: LDA $2EB0          ;地址2EB0的内容和地址80的内容比较
7BC5: CMP $80
7BC7: BCS $7BD5          ;若地址2EB0的内容 > 地址80的内容,转地址7BD5
7BC9: LDA #$DC           ;这里就应该是显示"去去去,攒够经验再来吧!"
7BCB: STA $A6
7BCD: LDA #$7B
7BCF: STA $A7
7BD1: JSR $62D9
7BD4: RTS
    我们知道,只有经验值到了 20万,独行大侠才教你基本工夫,我们通过计算器得知,20万用16进制表示是 00 03 0D 40
    这里地址2EB0-2EB3是经验值的地址,一共4个字节,所以经验值最大是 FF FF FF FF,即10进制的4294967295,在这个程序里比较经验值是否 > 20万,如果是,程序就转地址 7BD5,我们为了达到我们的目的,只要这样做
7B9D: JMP $7BD5
    就可以,即无条件转到地址 7BD5,这样不管经验值是多少,都可以向独行大侠 请教了.


2.当你已经有师傅的时候,你再拜师傅,师傅就会说:"你已另有名师,还想来我这儿偷师学艺吗?"
    这句话就是线索,我们就可以找到相关的程序.
    我们  S C 4000BFFF040F
输入查找数据 "你已另有名师",结果我们找到了,这里我是在 0B 页码的地址 802B.根据前面的经验,我们知道有这样的指令
LDA #$2B  
STA ??
LDA #$80
STA ??
    我们找到机器码是  A9 2B 85
然后我们查找  S C 4000BFFF0B:输入查找数据 A9 2B 85,结果找到了,在0B 页码的地址 7B37
7B37: A9 2B 85 A6 A9 80 85 A7
7B3F: AD A9 2E C9 00 F0 07 CD
7B47: 91 2F F0 02 D0 28 A5 50
    这里我一眼就能看出是改那里,但大家可能不懂机器码,所以也要反汇编
    这里我建议大家还是学些机器码,要不这些字节在你眼里是数据,但在我的眼中却是程序,而且反汇编的时候,你由于不懂机器码,不知道从那里开始反汇编,不能分辨出数据与代码.这里我们反汇编
7B37: LDA #$2B
7B39: STA $A6
7B3B: LDA #$80
7B3D: STA $A7
7B3F: LDA $2EA9
7B42: CMP #$00
7B44: BEQ $7B4D
7B46: CMP $2F91
7B49: BEQ $7B4D
7B4B: BNE $7B75
7B4D: LDA $50
    这里我们看到,程序判断地址 2EA9的内容是不是0,如果是,说明你还没有师傅,那么就可以拜师.所以我们改这里为
7B3F: JMP $7B4D
机器码为:
4C 4D 7B
    就可以拜很多师傅,学很多东西了.


3. 那工地管事说"你是干什么的,别在工地乱跑"
   我们就查找,结果我在 0D 页码地址7B45找到了
7B45-你是干什么的,别在工
7B58-地乱跑  你真粗心,一
7B6B-定是半路上把石料丢媚
7B7E-湍 多谢你大老远把石
7B91-料送过来,辛苦了  你
7BA4-被奖励了: ````  点棠
(上面用"`"来表示乱码)
    当你把石头搞丢了,工地管事说"你真粗心一定是半路上把石料丢了",该文字开始地址 7B60,当你搬来了石头,工地管事说"多谢你大老远把石料送过来,辛苦了    你被奖励了...",该文字开始地址 7B81
    我们查找  S C 4000 0D ,输入查找数据 A9 45 85,找到了,在地址 7A84
7A84-A945 8590 A97B 8591
7A8C-2C4E 3530 034C 9C79
7A94-A960 8590 A97B 8591
7A9C-AD4E 3529 7F8D 4E35
7AA4-A957 0037 30B0 034C
7AAC-9C79 DE5E 2FA9 8185
由于数据较多,我就不反汇编了,我直接告诉大家吧.
    前面我们已经知道,2EB0-2EB3是经验值地址,通过看这里程序,我们又知道 2ECF-2ED0 是潜能的地址
    既然我们知道地址了,那么我们想怎么改都可以,用计算器算出 10000的十六进制是2710,那么我们只要修改地址:
7B0A: AD B0 2E 69 10 8D B0 2E AD B1 2E 69 27 8D B1 2E EA EA
7B27: AD CF 2E 69 10 8D CF 2E AD D0 2E 69 27 8D D0 2E EA EA
然后我们还需要修改信息,因为已经是10000了呀
V 7B81
按 输入法 ,输入"Thank you! 你被奖励了1万经验值和潜能",然后后面多出的字用20代替
7B81-Thank You! 你被奖励
7B94-了1万经验值和潜能.
7BA7-
7BBA-
7BCD-   .娠写|÷c. 90`*荒
7BE0-墚粟`#,`10``` 70``嘎
我们进入GMUD,看看效果
(下面的2幅图由于是GMUD游戏画面无法打出)

***第四章 探索NC1020文件系统***
    这章我们讲了NC1020文件系统,包括 目录列表,文件列表,闪存分配表等.
    我们还介绍了 在NC1020上手工做BIN文件的方法,为BIN文件减肥等.
    当然,我们也是在不断探索之中,难免有错误,希望高手指正.

 **BIN文件结构**
    NC1020的BIN文件分为 2 种,一种是文件大小 < 8K 的BIN文件,一种是 >8K 的BIN 文件
①若文件大小 < 8K,那么该BIN文件执行的时候,系统会自动把该BIN文件装入 RAM $2000开始的地址,然后在 RAM里执行,其文件格式如下:
A000-AAA5 5A00 1000 204C
A008-1020 7003 3103 FFFF
A010-AD03 0448 AD68 0448
A018-297F 8D68 04A9 018D
A020-0304 002E 8A8A 48A9
A028-00A2 099D F803 CAD0

2000~2002: AA A5 5A
2003~2005: 文件实际长度
2006: 20
2007: 4C
2008:文件入口地址低8位
2009:文件入口地址高8位
200A-200F: 70 03 31 03  FF FF  这可能是 版本号,我不管,照着抄就是     
2010开始就是程序了
②若文件大于 8K,那么该BIN文件在执行的时候,系统自动寻找一个 空闲的页码,然后把该BIN文件送地址4000 开始的闪存
    这就是为什么在第一次运行一个 BIN文件时,出现 “装入中...”的进度条,其文件格式如下:
4000-AEEE EA00 2000 204C
4008-1A63 7003 1003 FFFF
4010-6820 315B 4C0D 5AFF
4018-8019 1009 1110 0221
4020-4002 0014 2016 61FF
4028-A545 484A 4A4A 4A85

4000~4006: AE EE EA 00 20 00 20      这是固定
4007: 4C
4008:入口地址 低 8 位
4009:入口地址 高 8位
400A-400F:70 03 10 03 FF FF   这个也是固定的!
4010开始就是程序了.

 **NC1020闪存分配表**
    NC1020有512K闪存,闪存以 4K 为 1 族
    我个人认为,NC1020以 4K 为 1 族 是不科学的,因为总计才 512K 的闪存,每 族就 4K,显然闪存的利用率是很低的.我举个例子,一个 才 200 字节 的文本,下载到 NC1020上,也要占据 4K 的字节,也就是说,不管你的文件有多小,最小也要占据 4K 的闪存.
    下面我再说说 系统占据闪存的情况:
内核:
总计占据                 16+16+96+8=136K
00 页码的 8000-BFFF      16K
01-03 页码的 4000-BFFF   3*32=96K
C000-DFFF                8K
0F 页码的 8000-BFFF      16K
资料存储: 最小占据4+20+4+4+4=36K
名片:     4K 闪存                   
行程:     20K 闪存            
重要事项: 4K 闪存       
记事:     4K 闪存  
笔记便笺: 4K 闪存   
下面总计:         4*8=32K
闪存分配表:       4K
各种文件目录列表: 4K
BIN文件列表:      4K
BAS文件列表:      4K
SKI文件列表:      4K
TXT文件列表:      4K
有声读物列表:     4K
字典数据列表:     4K   
所以经过我的计算NC1020 实际可利用闪存大小  = 512 - 136 - 36 - 32/2(因为有时候用户并没有下载全部类型文件)=324K(左右)
    下面我们说说NC1020闪存分配表,这个名字是我自己取的,也不管对还是错,反正可以大概描述就可以了,闪存分配表如下所示:(注意,闪存分配表的位置不是固定的,下面只是我机子上某时刻闪存分配表的位置.)
7000-5751 5846 6C61 7368
7008-5358 AAFF 8000 FFFF
7010-FF5F FF01 FF5F FF01
7018-FF5F FF01 FF5F FF01
7020-FF5F FF01 FF5F FF01
7028-FF5F FF01 FF5F FF01

7000-WQXFlashSX`````````
(以后是乱码)
    为了方便系统管理 闪存,知道闪存的利用情况,方便系统分配闪存,NC1020的闪存里有一个表,从这个表就可以知道NC1020全部闪存的分配情况,比如 哪个页码已经被利用了,是什么文件等,下面我们就来分析一下:
    首先大家看到的是这样的数据:
7000:57 51 58 46 6C 61 73 68
7008:53 58 AA FF 80 00 FF FF
(这些数据在每个表都是一样的,可能 是 标志,大家可以不管.但是我们一般就是通过查找这16个字节来定位该表的位置,因为该表的位置是经常变的,所以我们要找到这张表目前只能通过查找的方法,但是,实际上系统不是通过查找方法找到该表的,具体什么方法,目前我也不明确.)
然后就是这样的数据:
7010: FF 5F FF 01 FF 5F FF 01
7018: FF 5F FF 01 FF 5F FF 01
...
7088:FF 5F FF 01 FF 5F FF 01
...
    这里系统是以 4K为 1族,那么在闪存分配表中,是以 4 个字节代表 1族的,即是这样的:
7010-7013: FF 5F FF 01    表示00 页码 4000-4FFF
7014-7017: FF 5F FF 01    表示00 页码 5000-5FFF
....
7090-7093: ?? ?? ?? ??   表示 04 页码 4000-4FFF
....
7208-720B: FF 5F FF 01   表示0F 页码  A000-AFFF
720C-720F: FF 5F FF 01   表示 0F 页码 B000-BFFF
    在分配表里,这四个字节的内容是有意义的,具体如下:
FF 5F FF 01: 表示该 4K 闪存位置被 内核 占用
FF 5F FF 03: 表示该 4K 闪存位置被 闪存分配表 占用
FF FF FF FF: 表示该 4K 闪存位置 没有被占用,即是空闲 的闪存
00 5E 00 06: 表示该 4K 闪存位置被 根目录表 占用
XX 5E 00 04: 表示该 4K 闪存位置被  子目录表 占用
XX 5E YY C2: 表示 该 4N K (N=1,2,3,...)闪存被一般文件占用,包括 BIN文件,BAS,SKI等
XX 5E YY 18: 表示 该 4N K (N=1,2,3,...)闪存被 重要事项 占用
XX 5E YY 0C: 表示 该 4N K (N=1,2,3,...)闪存被 记事 占用
XX 5E YY 10: 表示 该 4N K (N=1,2,3,...)闪存被 笔记便笺 占用
XX 5E YY 0A: 表示 该 4N K (N=1,2,3,...)闪存被 行程 占用
XX 5E YY 08: 表示 该 4N K (N=1,2,3,...)闪存被 名片 占用
    这里 参数 YY 表示文件的分块数,每块是4K,因为若文件为 16K,那么需要 16个字节来标志,但由于文件在 闪存中的存储不一定是线形的,所以需要 YY 来标志,YY从 00 开始,然后是 01,02,03,04等,我们看下图:
70C8-055E 000A 055E 010A
70D0-055E 020A 055E 030A
70D8-055E 040A FFFF FFFF
70E0-FFFF FFFF FFFF FFFF
70E8-FFFF FFFF FFFF FFFF
70F0-FFFF FFFF FFFF FFFF
    这里我知道 XX 5E YY 0A,表示 被 行程 占用,我们通过 资料管理 得知,行程 占 5%,所以是 5 * 4 = 20K,那么这里就需要YY了,我们看 最开始 06 5E 00 0A,代表文件最开始的 4K,然后就是 06 5E 01 0A,代表下一个 4K,依次类推.
    这里有还有个参数XX,作用我们先不说,我们还是先分析下 根目录表 的结构,如图:
5020-C0F7 B8F6 C8CB CEC4
5028-B5B5 2020 2020 2020
5030-2020 7900 00FF FFFF
5038-0010 0001 00FF FFFF
5040-C0FF D3A6 D3C3 B3CC
5048-D0F2 2020 2020 2020

5020-厉个人文档````````
5033-`````````````````拍
5046-程序```````````````
5059-```````````````````
506C-
507F-
    根目录表一般是在04 页码的 地址 5000开始,从偏移#$20开始就是正式的内容
    根目录下有子目录,例如 应用程序,个人文档等,通过根目录,我们可以找到 子目录在闪存中的位置,子目录的信息等.我们就以上图的总目录表来举例:
5020: C0 说明子目录有效, 00 说明该子目录已经无效
5021: FF 说明该子目录有效  F7 说明该子目录为系统目录,不可以删除,比如 个人文档 目录
5022-5031:  子目录名字   在上图中,子目录名 为 "个人文档",最大子目录名 为8个汉字,如果少于8个汉字,后面补 20
5032: 子目录建立的 年   79: 2002年    7A: 2003年,依次类推
5033: 子目录建立的 月   00: 1月   01: 2月 ....
5034: 子目录建立的 日   00: 1日   01: 2日 ....
5035-5037: FF FF FF   没有发现有什么作用,可能是扩展功能用的
5038-503A: 一共 3个字节, 是标志子目录的大小的,一般就是 00 10 00,如果是8K,就是 00 20 00
503B: 这里就是最重要的了,就是 上面的参数   XX.这里我重点讲下 这里的 XX,通过XX,我们就能通过 闪存分配表 找到该子目录在 闪存中的位置由于:
XX 5E 00 04: 表示该 4K 闪存位置被  子目录表 占用
在上图中,XX= 01 那么我们就只要在 闪存分配表里 找01 5E 00 04 就可以了,如下图:
7090-015E 0004 005E 0006
7098-021E 00C2 FF5F FF03
70A0-021E 00C2 021E 00C2
70A8-025E 00C2 035E 00C2
70B0-045E 00C2 045E 01C2
70B8-045E 02C2 045E 03C2
    我们在地址 7090 发现了,计算一下,就知道是在 04页码的地址 4000-4FFF
    我们再来分析下 子目录表的 结构,如下图:(这里我们以 应用程序 这个子目录来分析,通过 这个子目录,我们可以知道如下信息:)
1.BIN 文件的大小
2.文件建立 年月日
3.文件在闪存中的 位置
4.文件名字
B020-C8DF 4E43 5F54 4F4F
B028-4C53 2E62 696E 2020
B030-2020 7900 00FF FFFF
B038-0060 0004 00FF FFFF
B040-FFFF FFFF FFFF FFFF
B048-FFFF FFFF FFFF FFFF

B020-冗NC_TOOLS.bin    y
B033-      `
B046-
B059-
B06C-
B07F-

B020~B021: C8 DF 代表该BIN文件有效  08 DF 代表该BIN文件已经无效
B022-B031: BIN文件名,最大支持 6个汉字,后面一定要加.bin
B032:  BIN文件建立 年份  79: 2002, 7A: 2003, 7B: 2004.....
B033:  BIN文件建立 月    00: 1月  01: 2月......
B034:  BIN文件建立 日    00: 1日  01: 2日.......
B035-B037: FF FF FF     不知道有什么用
B038-B03A: BIN文件大小,这里注意的是 这里不是 BIN实际大小,而是 4K 的整数倍
00 10: 4K
00 20: 8K
但是如果是 01 20,系统就认为是 12K哦
B03B: XX 的值,这里是 04,那么我们只要在闪存分配表里找  04 5E 00 C2,就能找到 该 BIN文件在闪存中位置
70B0-045E 00C2 045E 01C2
70B8-045E 02C2 045E 03C2
70C0-045E 04C2 045E 05C2
70C8-055E 000A 055E 010A
70D0-055E 020A 055E 030A
70D8-055E 040A FFFF FFFF
    我们计算一下,就知道该 BIN的位置了,大家还要注意这时的 YY 的值,是不是 00 01 02 03 04 05,那么说明该 BIN 有 6* 4K= 24K

 **用NCTOOLS手工建立一个 BIN文件**
    有了前面的知识,我们现在就来在 NCTOOLS里面手工建立一个 BIN文件,以巩固我们前面的知识.
    这里由于程序比较小,所以我们决定制作 <8K 的BIN文件.
    我们应该都玩过 电子宠物猫 吧,那么我们这里就制作一个 电子宠物猫存档修改器
    由于 RESET 后,养的猫就 没有了,我们 我们推断,存档一定是存在 RAM区域,那么需要我们去查找存档的位置,方法很简单,如下:

   * 猫咪领养卡 *
猫咪姓名: 土豆
出生时间: 2002/01/01
            12:01:35
拼            [姓名]

    如上,我们先领养一只猫,名字叫做"土豆",然后马上你就看到一只活蹦乱跳的猫眯了.
    然后我们退出游戏,进入 NCTOOLS,查找 "土豆",查找范围当然是RAM区域,地址范围 0000-3FFF
输入 SC 0000 3FFF,输入 "土豆",然后我们马上就找到了,如图:
(作者忘了附图)
    哦,原来是地址06DC啊,居然名字存在这里,那附近一定就是其他参数的存贮位置啊,我们看看我们的 土豆 的参数,如图:
土豆          1   天
体重:  7
成长:  0
天使:  0
饥饱度:(此处的心形图无法打出)

土豆          1   天
金钱     $200
猫粮     *9
超级猫粮 *8
牛奶     *7

    好了,我们的 土豆 有 自身状态 和 物品状态
自身状态是  07 00 00 03 ..
物品状态是  C8 09 08 07 ..
    我们往地址06DC附近看看,是不是有这些数据,结果找到了,如图:
06AC-0000 0000 0000 0000
06B4-0000 0000 0000 0C79
06BC-0000 810F 0000 0007
06C4-0000 0302 1802 0002
06CC-0000 0000 0000 0000
06D4-0000 C809 0807 0602
    我们总结如下:
地址
06D6: 金钱
06D7: 猫粮
06D8: 超级猫粮
06D9: 牛奶
06DA: 鱼
06DB: 球

06C3: 体重
06C4: 成长
06C5: 天使
06C6: 饥饱度
06C7: 口渴度
06C8: 清洁度
06C9: 训练度
06CA: 兴奋度
    由于我们这里主要是介绍 手工建立一个 BIN,所以这里我们程序尽量简单些,这里我们只是实现把宠物猫的物品数全部调为 200,也就是 #$C8,然后 弹出一个信息框,显示"猫猫所有物品达200",然后就结束了.
    我们先实现主程序,即将物品全部改为 200个,从地址2010开始编写:
    同时 按住 Z + H 键,把地址2000-2FFF的内容全部清为 FF
A 2010
2010: LDX #$00
2012: LDA #$C8
2014: STA $06D7,X
2017: INX
2018: CPX #$06
201A: BNE $2014
201C:RTS
    这段程序就实现了改物品的个数,是不是很简单?
    然后我们实现弹出 的信息框,我 V 2020, 按 输入法,输入 " 猫猫物品达200啦",然后后面再输入 00 00 作为结束标志,如图:
2020-猫猫物品达 200啦
2033-
2046-
2059-
206C-
207F-
    然后我们做表,从地址 2038开始 输入如下数据,如图:
2038-8020 2009 1110 0241
2040-2002 FFFF FFFF FFFF
2048-A238 A020 A900 0012
2050-C300 07C0 60FF FFFF
2058-FFFF FFFF FFFF FFFF
2060-FFFF FFFF FFFF FFFF
    我们做弹出信息框:
A 2048
2048:LDX #$38
204A: LDY #$20
204C: LDA #$00
204E: INT $C312
2051: INT $C007
2054: RTS
然后我们实现主程序,从地址2058开始
A 2058
2058: JSR $2010
205B: JSR $2048
205E: RTS
我们 G 2058 试试,出现下图:
(此图无法打出)
    我们回到游戏,看看物品是不是200个,一看,果然是,如图:

土豆          1   天
金钱     $200
猫粮     *200
超级猫粮 *200
牛奶     *200

    好了,我们已经完成了这个简单的BIN程序,现在我们要做BIN文件了
1.先做文件头
E C 2000
输入 AA A5 5A
输入文件实际长度   60 00 00
输入  20
输入  4C
输入入口地址  58 20
输入  70 03 31 03  FF FF
那么就是这样的,如图:
2000-AAA5 5A60 0000 204C
2008-5820 7003 3103 FFFF
2010-A200 A9C8 9DD7 06E8
2018-E006 D0F8 60FF FFFF
2020-C3A8 C3A8 CEEF C6B7
2028-B4EF 3230 30C0 B200
2.找一块 空闲的闪存,只要有 4K 就可以了.我们在闪存分配表里找 FF FF FF FF,然后把地址2000-2FFF内容送相应的闪存地址
    这里我们找到了一块空闲的闪存,大家看到 地址70DC-70DF的内容为  FF FF FF FF,那我们就放这里吧,计算一下,发现是
06 页码的 7000-7FFF,大家应该会算吧?
70C0-045E 04C2 045E 05C2
70C8-055E 000A 055E 010A
70D0-055E 020A 055E 030A
70D8-055E 040A FFFF FFFF
70E0-FFFF FFFF FFFF FFFF
70E8-FFFF FFFF FFFF FFFF
    然后我们 输入命令  W 7000 06,就把数据写入了,如图:
7000-AAA5 5A60 0000 204C
7008-5820 7003 3103 FFFF
7010-A200 A9C8 9DD7 06E8
7018-E006 D0F8 60FF FFFF
7020-C3A8 C3A8 CEEF C6B7
7028-B4EF 3230 30C0 B200
3.然后我们修改闪存分配表,即把原来的 FF FF FF FF改为 ?? 5E 00 C2,这里??的值不能与其他重复.这里我们取 0F 5E 00 C2,这样系统就认为该块闪存已经被利用了,就不会分配数据到该块闪存了.
4.最后我们修改 BIN文件表,就是加入一个新的文件信息给系统.我们先要找到 BIN文件表,怎么找呢?在前面已经讲过,这里就不说了,你也可以在 NCTOOLS里,按帮助,按系统信息,按 文件列表  ,按 应用程序,找到了,如图
B020-C8DF 4E43 5F54 4F4F
B028-4C53 2E62 696E 2020
B030-2020 7900 00FF FFFF
B038-0060 0004 00FF FFFF
B040-FFFF FFFF FFFF FFFF
B048-FFFF FFFF FFFF FFFF
然后我们 从地址 B040开始输入  C8 DF
然后就是文件名,这里取  猫猫存档修改.bin
然后 输入文件建立日期    7A 09 04
然后输入 FF FF FF
然后输入文件大小  00 10 00
然后输入 XX 的值  0F  (因为先前是 0F 5E 00 C2)
然后输入  00
那么就可以了,按 网络,是不是看见文件了

NC_TOOS
猫猫存档修改

我们执行一下看看,如图:
(此图无法打出)

 **利用系统知识为BIN文件"减肥"**
    NC1020上的很多BIN文件都是从CC800,PC1000上移植过来的,所以还保持了32K的大小,实际上,很多文件实际是很小的,有的不到8K,就比如 有声读物,即 SPEED.BIN,我看了下,实际大小就 8K 多一点,所以我们有必要为这个 "超级大肥猪" 减减肥,让我们那本来就小的可怜的闪存空间能够多放些东西.
    下面我就为 有声读物 减肥,一方面是"减肥",另一方面也可以巩固我们先前所学的知识.
1.首先,我们下载 SPEED.BIN,运行一下,通过 资料管理 得知SPEED.BIN 大小为 32K,如图:

有声读物          9%
NC_TOOLS          6%

2.进入 NCTOOLS,找到该BIN文件所在的页码,我这里是 05页码.
    利用前面的知识,我们可以很快的找到某个BIN文件所在的页码,不过由于大于8K的文件在闪存中的存储地址是4000.所以我们也可用 V 4000 ??,这里??是页码,范围是从05-0F,若本机有多个BIN,那么你可以通过 G 4007 ??来确定该BIN文件的页码,我这里是05页码,如图:
4000-AEEE EA00 2000 204C
4008-615E 7003 1003 FFFF
4010-8A48 A900 A209 9DF8
4018-03CA D0FA ADF8 0329
4020-3F8D F803 68AA 20C4
4028-43AD 3510 C900 F003
    我们继续往下翻,我们发现 地址  6238 后面的数据全部是 FF,那么说明该文件实际长度为 8K + 569字节,由于NC1020以 4K 为 1 族,那么,该文件大小为 12K,不过还是浪费了好多空间啊,这是没有办法的事了,如图:
6230-B7CA B5C9 BEB3 FD00
6238-00FF FFFF FFFF FFFF
6240-FFFF FFFF FFFF FFFF
6248-FFFF FFFF FFFF FFFF
6250-FFFF FFFF FFFF FFFF
6258-FFFF FFFF FFFF FFFF
   下面我们就开始为该BIN文件减肥了,步骤如下:
找到 应用程序的 目录,然后找到 有声读物 在目录中的信息表,然后修改这里的大小
B020-C8DF D3D0 C9F9 B6C1
B028-CEEF 2E62 696E 2020
B030-2020 7900 00FF FFFF
B038-D07F 0004 00FF FFFF
B040-C8DF 4E43 5F54 4F4F
B048-4C53 2E62 696E 2020

B020-冗有声读物.bin    y
B033-``````````````呶C_T
B046-OOLS.bin    y     
B059-```
B06C-
B07F-

    大家看到,原来这里大小为 7FD0,我们将其改为 30 00,如图:
B028-CEEF 2E62 696E 2020
B030-2020 7900 00FF FFFF
B038-0030 0004 00FF FFFF
B040-C8DF 4E43 5F54 4F4F
B048-4C53 2E62 696E 2020
B050-2020 7900 00FF FFFF
    不过还没有完,我们还需要在 闪存分配表里 释放被解放的闪存空间,我们看到这里 XX = 04,那么我们就在 闪存分配表 里找04 5E 00 C2,就可以了,如图:
70B0-045E 00C2 045E 01C2
70B8-045E 02CE 045E 03CE
70C0-045E 04C2 045E 05C2
70C8-045E 06C2 045E 07C2
70D0-055E 00C2 055E 01C2
70D8-055E 02C2 055E 03C2
    因为我们这里的BIN文件大小为 30 00,所以我们需要将地址 70BC-70CF 的内容全部改为 FF,如图:
70B0-045E 00C2 045E 01C2
70B8-045E 02C2 FFFF FFFF
70C0-FFFF FFFF FFFF FFFF
70C8-FFFF FFFF FFFF FFFF
70D0-055E 00C2 055E 01C2
70D8-055E 02C2 055E 03C2
    现在,我们收工了,到 资料管理 看看,果然小了好多了:
有声读物          3%
NC_TOOLS          6%


***第五章 系统调用***
    我没有正式找过系统调用,这些都是平时无意发现的,所以不多.不过我认为只要掌握了一点就可以了,有很多人过度依赖系统调用,好象没有这个,程序就没有办法编写了,其实不然,我们可以自己实现.

 **基本系统调用**

1. ClearLCD
中断调用: INT $8A2E
作用:清屏的作用,使屏幕什么都不显示
2. UpdateLCD
中断调用: INT $8A15
入口参数:
    显示小字  (0402) = 01   (0403) = 00
    显示大字  (0402) = 00   (0403) = 01
    (042D) = FF
    (042E) = FF
作用:刷新屏幕,我们要显示信息到屏幕,除了把相应数据送屏幕RAM,还要接着调用这个中断

例如:在屏幕上最左上角显示 "HELLO WORLD"
先输入信息,从地址2000开始输入
V 2000
按输入法 输入"HELLO WORLD"
写显示程序,从地址2010开始
A 2010
2010: INT $8A2E
2013: LDX #$0B
2015: LDA $2000,X
2018: STA $02BF,X
201B: DEX
201C: BNE $2015
201E: LDA #$01
2020: STA $0402
2023: LDA #$00
2025: STA $0403
2028: INT  $8A15
202B: INT $C008
202E: RTS

3. ReadKey
中断调用: INT $C008 或 INT $C007
作用:读取用户按键的扫描码,送寄存器A.所以我们使用该中断可以判断用户按了什么键
我们进入NCTOOLS,按帮助,按 编程资料,按 键盘扫描,就可以获得各键的扫描码

4. ReadFlashData
调用方法:JSR $E917
功能:读取指定页码的某闪存地址内容
入口参数: 地址 05B4 放页码
          地址 C8 放目标地址低8位
          地址 C9 放目标地址高8位
          寄存器(Y) = 00
出口参数: 寄存器A 放读取到的数据
例如,读取03页码地址4000的内容
A 2000
2000: LDA #$03       ;页码送地址05B4
2002: STA $05B4
2005: LDA #$00       ;目标地址低8位送地址C8
2007: STA $C8
2009: LDA #$40       ;目标地址高8位送地址C9
200B: STA $C9
200D: LDY #$00       ;寄存器Y的内容为00
200F: JSR $E917
2012: RTS
不过这里,寄存器Y的内容不一定就是00,如果是01,上面的程序就是读地址4001的内容

5. DELAY
①JSR $E02A
作用:延时的作用
参数:寄存器X设置延时时间长短
例如:
A 2000
2000: LDX #$50
2002: JSR $E02A
2005: RTS
②JSR $E02D
和上面一样,不过是寄存器Y设置延时时间长短

6. CheckPower
电量检测
中断调用: INT $021A
出口是A的值
作用:若结果为0 代表电力足;若结果不为0 代表电力不足
例如 执行电量检测,若电力足,送01到地址3000;若电力不足,送00到地址3000
A 2000
2000: INT $021A
2003: BEQ $200B
2005: LDA #$00
2007: STA $3000
200A: RTS
200B: LDA #$01
200D: STA $3000
2010: RTS

7. 内置DEBUG
中断调用: INT $C40D


 **图形中断调用**
1.画圆
中断调用:int $c30e
入口参数:圆心: 043F,0440
          半径:0452
          0445:01 画圆 00 清圆(以下同)
例如:
A 2000
2000:LDA #$80
2002:sta $043f
2005:lda #$40
2007:sta $0440
200a:lda #$40
200c:sta $0452
200f:int $c30e
2012:int c008
2015:rts
2.画方块
中断调用:int $c30c
入口参数:X1,Y1:043F,0440
          X2,Y2:0441,0442
3.画线条
中断调用:INT $C30B
入口参数:X1,Y1:043F,0440
          X2,Y2:0441,0442
4.画椭圆
中断调用:INT $C30F
入口参数:圆心:043F,0440
          宽度:0457
          高度:0458
5:填充画方块
中断调用:INT $c30a
其他参数和画方块一样
6.填充画圆
中断调用:int $c310
其他参数和画圆一样
7.填充画椭圆
中断调用:int $c311
其他参数和画椭圆一样

 **弹出式信息框**

 实现这样的信息框:
(此图无法打出,是模拟的)
   ┌───────┐
   │ 无行程安排   │
   │按任意键新增  │
   └───────┘
中断调用: INT $C312
入口参数: 寄存器X放参数表地址低8位
          寄存器Y放参数表地址高8位
          寄存器 (A) = 00
参数表定义:
偏移 00 :80
     01:信息框在 X轴位置
     02:信息框在 Y轴位置
     03:信息所占字节数
     04:信息框的宽度   02  显示一行,04 显示两行  06 显示三行...
     05:02 所在地址低8位
     06:02 所在地址高8位
     07:02

例如我们实现在屏幕上弹出信息框,显示 "这是一个信息框"
进入 NCTOOLS,按 Z + H,
先输入 信息:(从地址2000开始)
V 2000
按输入法,输入 "这是一个信息框",后面用E C 命令紧接着输入 0000,作为信息结束标志

2000-这是一个信息框
2013-
2026-
2039-
204C-
205F-

然后做参数表.这里我们确定下:
信息所在开始地址 2000
X 轴位置   09
Y 轴位置   10
字节数     0E   (因为是7个汉字,所以是14个字节,即16进制 0E)
02 的地址  2019
我们从地址2010开始做表
E C命令 输入 80 00 20 09 10 0E 02 19 20 02
2010-8000 2009 100E 0219
2018-2002 FFFF FFFF FFFF
2020-FFFF FFFF FFFF FFFF
2028-FFFF FFFF FFFF FFFF
2030-FFFF FFFF FFFF FFFF
2038-FFFF FFFF FFFF FFFF
然后我们实现主程序,从地址2020开始输入程序
A 2020
2020: LDX #$10       ;确定表的位置
2022: LDY #$20
2024: LDA #$00      ;00 表示是弹出式信息框
2026: INT $C312     ;调用该中断,显示信息框
2029: INT $C008     ;该中断是读键中断,这样信息框能"定"住,要不信息框一闪就没有了
202C: RTS           ;结束
我们 G 2020,看看效果
(此图无法打出)
    这里,告诉大家一个方法,如果我们程序中多次用到信息框,那么是不是每一个信息框都要做表呢?
其实我们可以让 信息内容的开始地址固定为RAM中的一个地址,然后每次把信息内容发送到那里就可以.这样我们只要一个表,就可以实现多个信息框.

 **询问式信息框**
(选择式信息框)
实现下面的信息框
(此图无法打出,内容是一个信息框,上面的提示是一个问号后"创建新文件",框底部是"是"和"否"两个按钮)
中断: INT $C312
入口参数:  寄存器X 放参数表地址低8位
           寄存器Y 放参数表地址高8位
           寄存器A = 02,左边显示 问号,寄存器A=01,左边显示 感叹号
参数表定义:
偏移00: 80
    01: 信息内容开始地址低8位
    02: 信息内容开始地址高8位
    03: 信息框在 X 轴位置
    04: 信息框在 Y轴位置
    05: 信息内容的长度
    06: 信息框宽度, 02 显示1行,04 显示2行,06显示3行....
    07: 02 地址低8位
    08: 02 地址高8位
    09: 02
当调用这个中断后,会把用户按键扫描码送寄存器A,所以当用户选择 Y,寄存器A的内容为 79.通过寄存器A的内容可以判断用户 选择 Y,或是选择 N
例:显示 信息框  "你有信心吗?",当用户选择 Y,送立即数 01 到地址3000;当用户选择 N,送立即数 00 到地址3000
1.输入 "  你有信心吗?",从地址2000开始
V 2000
按 输入法 ,输入 "  你有信心吗?",后面紧接用E C命令输入 0000
注意,信息内容最好空2格,这样美观些
2000-  你有信心吗?
2013-
2026-
2039-
204C-
205F-
2.做参数表,从地址2010开始
E C 2010
输入 80 00 20 09 10 0E 02 19 20 02
3.实现主程序
A 2020
2020: LDX #$10
2022: LDY #$20
2024: LDA #$02
2026: INT $C312
2029: CMP #$79
202B: BNE $2033
202D: LDA #$01
202F: STA $3000
2032: RTS
2033: LDA #$00
2035: STA $3000
2038: RTS
然后我们 G 2020
(此图无法打出,内容是一个信息框,上面的提示是一个问号后"你有信心吗?",框底部是"是"和"否"两个按钮)


 **等级棒**
实现下面的形式
(此图无法打出,内容是与"音量设定"差不多的等级棒)
中断调用: INT $C314
入口参数: 寄存器X 放参数表地址低8位
          寄存器Y 放参数表地址高8位
参数表定义:
偏移 00: 级别个数
第一级别文字说明
第二级别文字说明
第三级别文字说明
......
第N级别文字说明
第一级别程序入口地址
第二级别程序入口地址
第三级别程序入口地址
......
第N级别程序入口地址
说明: 级别文字说明固定为20个字节,不够用20补充,后面不紧接 FF
例如,做三个等级,当选择第1等级,立即数01 送地址3000;当选择第2等级,立即数02 送地址3000;当选择第3等级,立即数03 送地址3000
我们先实现立即数送地址3000的3个程序
;立即数01送地址3000
A 2000
2000:LDA #$01
2002:STA $3000
2005:RTS
;立即数01送地址3000
A 2008
2008:LDA #$02
200A:STA $3000
200D:RTS
;立即数01送地址3000
A 2010
2010:LDA #$03
2012:STA $3000
2015:RTS
我们先做表,从地址2018开始
E C 2018,输入 03
输入法输入 "等级:一级",然后E C 命令输入 20 20 20 20 20 20 20 20 20 20 20
输入法输入 "等级:二级",然后E C 命令输入 20 20 20 20 20 20 20 20 20 20 20
输入法输入 "等级:三级",然后E C 命令输入 20 20 20 20 20 20 20 20 20 20 20
E C 命令输入
00 20 08 20 10 20
我们表已经做完了
我们实现主程序,从地址2060开始
A 2060
2060: LDA #$18
2062: LDY #$20
2064: INT $C314
2067: RTS


 **A类菜单的实现**
(系统菜单函数A)
1.菜单A
如图示的菜单:

1.汉英字典
2.英英字典
3.成语字典
4.中英会话

中断调用: INT $C205
入口参数:菜单参数表的低8位地址送寄存器X,高8位地址送寄存器Y
参数表定义:
偏移  00: 09
      01: 菜单项个数
      02:第1个菜单项对应程序入口地址低8位
      03:第1个菜单项对应程序入口地址高8位
      04:第1个菜单项对应帮助内容开始地址低8位
      05:第1个菜单项对应帮助内容开始地址高8位
      ....
      XX:    第N个菜单项对应程序入口地址低8位
      XX + 1:第N个菜单项对应程序入口地址低8位
      XX + 2:第N个菜单项对应帮助内开始地址低8位
      XX + 3:第N个菜单项对应帮助内容开始地址高8位
然后下面紧接着就是:
      第1个菜单项的文字说明,例如上面的"1.汉英字典",以 FF 结尾
      第2个菜单项的文字说明,例如上面的"2.英英字典",以 FF 结尾
      ....
      第N个菜单项的文字说明,以 FF 结尾
例: 设计一个如图所示的菜单
(作者未附图)
功能:当选中第1个菜单项,弹出信息框,显示 "选中第1菜单项";当选中第2个菜单项,弹出信息框,显示 "选中第2菜单项";当选中第3个菜单项,弹出信息框,显示 "选中第3菜单项"
设计步骤:
进入NCTOOLS,同时按住 Z,H 键,将地址2000-2FFF全部清为 FF
设计思路:
①先做弹出式信息框,这里由于弹出的信息仅相差一个字节,所以我们可以做一个子程序,寄存器A放31,32,或33,然后调用这个子程序,该子程序把寄存器A的值先送哪个地址,再中断调用
②然后做帮助,这里帮助内容简单些,是"菜单帮助内容",后面加 00 00,作为结束标志
③然后做菜单参数表
④最后实现菜单
V 2000
输入法  输入  选中第N菜单项,后面紧接着用 E C 命令输入 0000,作为结束标志
如图:

选中第n菜单项
2008-FFFF FFFF FFFF FFFF
2010-FFFF FFFF FFFF FFFF
2018-FFFF FFFF FFFF FFFF
2020-FFFF FFFF FFFF FFFF
拼                [中文]

2000-D1A1 D6D0 B5DA 6EB2
2008-CBB5 A5CF EE00 00FF
2010-FFFF FFFF FFFF FFFF
2018-FFFF FFFF FFFF FFFF
2020-FFFF FFFF FFFF FFFF
2028-FFFF FFFF FFFF FFFF

做弹出信息框参数表,从地址2010开始
E C 2010
输入 80 00 20 09 11 0D 02 19 20 02   
如图:
2010-8000 2009 110D 0219
2018-2002 FFFF FFFF FFFF
2020-FFFF FFFF FFFF FFFF
2028-FFFF FFFF FFFF FFFF
2030-FFFF FFFF FFFF FFFF
2038-FFFF FFFF FFFF FFFF
实现弹出信息框子程序,要显示的数字放寄存器A就可以,例如 (A) = 31,那么就显示 选中第1菜单项
A 2020
2020: STA $2006
2023:  LDX #$10
2025:  LDY #$20
2027:  LDA #$00
2029:  INT $C312
202C:  INT $C008
202F:  RTS

A 2030    显示信息框   选中第1菜单项
2030: LDA #$31
2032: JSR $2020
2035: RTS

A 2038    显示信息框   选中第2菜单项
2038: LDA #$32
203A: JSR $2020
203D: RTS

A 2040    显示信息框   选中第3菜单项
2040: LDA #$33
2042: JSR $2020
2045: RTS

做菜单帮助内容
从地址2048开始:
输入法  输入  "菜单帮助内容",注意后面输入结束标志0000
现在我们确定一下
第一菜单项入口地址  2030
第二菜单项入口地址  2038
第三菜单项入口地址  2040
帮助入口地址       2048
做菜单参数表
从地址 2060开始
E C 2060
输入 09 03 30 20 48 20 38 20 48 20 40 20 48 20
按 输入法 输入
"1.菜单项1"  后面紧接 FF
"2.菜单项2"  后面紧接 FF
"3.菜单项3"  后面紧接 FF
实现整个程序主程序
从地址2088开始
A 2088
2088: LDX #$60
208A: LDY #$20
208C: INT $C505
208F: RTS
G 2088 

 **B类菜单的实现**
(系统菜单函数B)
实现下面的菜单
(是"记事"菜单.图中的选项序号打不出来,故图略)
中断调用: INT $C20F
入口参数: 寄存器X 放参数表地址低8位
          寄存器Y 放参数表地址高8位
偏移00: 0C
    01: 菜单项个数
    02:第1个菜单项对应程序入口地址低8位
    03:第1个菜单项对应程序入口地址高8位
    04:第1个菜单项对应帮助内容开始地址低8位
    05:第1个菜单项对应帮助内容开始地址高8位
    ....
    XX:    第N个菜单项对应程序入口地址低8位
    XX + 1:第N个菜单项对应程序入口地址低8位
    XX + 2:第N个菜单项对应帮助内开始地址低8位
    XX + 3:第N个菜单项对应帮助内容开始地址高8位
然后下面紧接着就是:
第1个菜单项的文字说明,例如上面的"1.汉英字典",以 FF 结尾
第2个菜单项的文字说明,例如上面的"2.英英字典",以 FF 结尾
....
第N个菜单项的文字说明,以 FF 结尾
除了中断调用不同和参数表第1个字节不同外,和 INT$C205应用方法是一样
如果你不明白,请看INT $C205

 **C类菜单的实现**
(系统菜单函数C)
实现下面的菜单
(游戏"华容道"的菜单.略)
注意,实现的只是中间的那菜单
中断调用: INT $C312
入口参数: 寄存器X 放参数表地址低8位
          寄存器Y 放参数表地址高8位
          寄存器 A 的内容 = 03
参数表定义:
偏移 00: 82
     01:菜单项文字内容 开始地址 低8位
     02:菜单项文字内容 开始地址 高8位
     03:菜单在 X 轴 位置
     04:菜单在 Y 轴 位置
     05:菜单项字节数 (如果显示是4个汉字,这里就是08,每个菜单项大小必须一样)
     06:菜单宽度, 04  代表显示两个菜单项,06 代表显示 3个菜单项 ...
     07: 立即数 02 所在地址低8位
     08: 立即数 02 所在地址高8位 
     09: 执行菜单项开始地址低8位
     0A: 执行菜单项开始地址高8位
说明: 每个菜单项文字内容 后 要加 FF
立即数 02在什么地址,由你自己选择,但你最好在参数表的最后偏移 0B,然后偏移 07,08就写这个地址
执行菜单项开始地址是这样定义:AAAABBBBCCCCDDDDEEEEFFFF......(AAAA 是第一个菜单项执行地址,BBBB 是第二个菜单项执行地址,......)


 **D类菜单的实现**
(系统菜单函数D)
实现下面的菜单
(游戏"拼图游戏/记忆拼图"的菜单.略)
注意,实现的只是右边的那菜单
中断调用: INT $C312
入口参数: 寄存器X 放参数表地址低8位
          寄存器Y 放参数表地址高8位
          寄存器 A 的内容 = 03
参数表定义:
偏移 00: 80
     01:菜单项文字内容 开始地址 低8位
     02:菜单项文字内容 开始地址 高8位
     03:菜单在 X 轴 位置
     04:菜单在 Y 轴 位置
     05:菜单项字节数 (如果显示是4个汉字,这里就是08,每个菜单项大小必须一样)
     06:菜单宽度, 04  代表显示两个菜单项,06 代表显示 3个菜单项 ...
     07: 立即数 02 所在地址低8位
     08: 立即数 02 所在地址高8位 
     09: 执行菜单项开始地址低8位
     0A: 执行菜单项开始地址高8位
说明: 每个菜单项文字内容 后 要加 FF
      立即数 02在什么地址,由你自己选择,但你最好在参数表的最后偏移 0B,然后偏移 07,08就写这个地址
执行菜单项开始地址是这样定义:AAAABBBBCCCCDDDDEEEEFFFF......(AAAA 是第一个菜单项执行地址,BBBB 是第二个菜单项执行地址,...... )

 

 **输入法的实现**
(输入法)
作用:实现输入汉字,字符,符号等
中断调用:INT $C209
入口参数:
①寄存器X放参数表地址低8位
  寄存器Y放参数表地址高8位
②设置光标类型
  设置地址 $0431
  (0431) = 00   闪烁粗光标
  其他自己试试就知道了
③设置最右下角显示的两个汉字
  设置地址 $053A
  (053A) = 00  显示"姓名"
  (053A) = 01  显示"备注"
  (053A) = 02  显示"课程"
  (053A) = 03  显示"便笺"
  (053A) = 04  显示"纪念"
  (053A) = 05  显示"行程"
  (053A) = 06  显示"定时"
  (053A) = 07  显示"帐本"
  (053A) = 08  显示"储蓄"
  (053A) = 09  显示"个人"
  (053A) = 0A  显示"邮编"
  (053A) = 0B  显示"生字"
  (053A) = 0C  显示"中文"
  (053A) = 0D  显示"要事"
  (053A) = 0E  显示"笔记"
  (053A) = 0F  显示"查询"
  (053A) = 10  显示"开机"
  (053A) = 11  显示"汉英"
  .....
  其他自己试试
参数表定义:
偏移 00: 光标位置 (00-$63)
     01: 允许输入最大字节数
     02: 20
     03: 帮助内容开始地址低8位
     04: 帮助内容开始地址高8位
这里有个问题,用户输入后,如何获得用户输入的信息呢?由于输入的信息会在屏幕上显示,那么我们只要把对应屏幕RAM地址内容保存到另外一段地址就可以了.
我举个例子:光标位置 00;最大允许输入20个字符,即10个汉字;输入的信息保存在地址3000后;输入的字节数保存在地址3100:
1.我们 先做帮助,从地址2000开始
输入法输入 "输入法帮助",后面用 E C命令紧接输入0000
2.然后我们做参数表,从地址2010开始
E C 2010,输入 00 14 20 00 20
3.实现主程序,从地址2018开始
A 2018
2018: LDA #$00           ;设置光标类型为正常
201A: STA $0431
201D: LDA #$0C           ;最右下角显示"中文"
201F: STA $053A
2022: INT $8A2E          ;清屏
2025: LDX #$10           ;设置参数表地址
2027: LDY #$20
2029: INT $C209          ;调用输入法中断
202C: CMP #$1B           ;判断用户是否按 跳出 键
202E: BEQ $2041          ;如果按了,说明用户取消了输入,那么程序结束
2030: LDX #$00           ;开始读输入的数据,这里X的内容初始化为00
2032: LDA $02C0,X        ;因为光标位置是00,所以从地址02C0开始读,这是屏幕RAM首地址
2035: BEQ $203E          ;判断读取的内容是不是 00,如果是说明信息已经读完了
2037: STA $3000,X        ;读取信息送地址 3000+(X)
203A: INX                ;寄存器X内容加1,读取信息下一字节
203B: JMP $2032 
203E: STX $3100          ;用户输入的字节数送地址3100
2041: RTS


 **系统功能调用**
1.开关闹铃 INT$8D04
2.时间设定 INT$8304
3.秒表 INT$8307
4.时间格式设定 INT$8D03
5.整点铃声 INT$8D05
6.定时器 INT$8305
7.日期计算 INT$8306
8.万年历 INT$8302
9.倒数计日 INT$830E
10.屏幕亮度 INT$8D08
11.开机画面 INT$8D01
12.按键声音 INT$8D06
13.联想输入 INT$8D0F
14.模糊音输入 INT$8D0B
15.汉字反查 INT$8D0E
16.自动关机 INT$8D0A
17.空间整理 INT$9327
18.电子宠物 INT$8D11
19.方块世界 INT$8503
20.迷宫 INT$8504
21.华容道 INT$8403
22.挖地雷 INT$8409
23.拼图 INT$8601
24.搬运工 INT$8502
25.黑白棋 INT$8405
26.汉英字典 INT$9B02
27.英英字典 INT$8916
28.成语字典 INT$8E0E
29.情景对话 INT$8905
30.英汉字典 INT$9B01


***第六章 玩转NC1020闪存***
    这章中,我们介绍了NC1020闪存的修改和擦除,我考虑再三,最后还是决定介绍内核区闪存的修改和擦除,虽然这样对用户来说不再安全,但是对于需要该技术的朋友来说,掌握这技术是很必要的.
    任何人不得利用闪存修改技术,编写程序破坏文曲星的系统.本人对此不负任何责任.


 **NC1020闪存的分布与切换**
    这里我们先讨论NC1020闪存的基本情况.
1.闪存的基本参数
  闪存的类型: NOR FLASH
  闪存的大小: 512KB
  闪存块大小: 4K
2.NC1020闪存的分布
    由于6502最大寻址空间是64K,但NC1020有512K的闪存区域,那么是这样分布的呢?很显然,NC1020采用了分页码的方法,可以这样说,一个班只能容纳64个人,现在要招收512个人,显然,一个班是不够的,要有16个班,分别为00班,01 班,02 班......0F 班.
    NC1020的闪存也是这样分配的,分为 0F 个页码,每个页码32K
顺便说说NC1020存储器的分布:
①RAM的分布情况
NC1020RAM的大小:24K
地址0000-地址3FFFF:16K
00页码的4000-5FFF(6000-7FFF):8K
这里要说明的是,00页码的4000-5FFF的内容是会随着地址6000-7FFF的内容的改变而改变
例如 在NCTOOLS下  E C 6000 00, 输入30, 然后 D 4000 00,发现也变为30了,不过这在NC1020模拟器上是看不出来的.因为模拟器和实际机型不一样.
②闪存的分布
01-0F 页码的4000-BFFF,总计:32*15=480K
00 页码的8000-BFFF,总计:32/2=16K
当地址0A的内容=00时的 地址C000-DFFF,总计:16K
所以总的 闪存容量 = 480 + 16 + 16 = 512K
3.闪存的切换
和CC800,PC1000等一样,是通过地址00来切换的.
切换闪存有2种情况:
●当前代码处于 随机存储器RAM范围中
    这种情况,切换到不同的页码比较简单,请看下面的例子:
例:  当前处于RAM区域,现在要读取03页码的4000-40FF的内容送到地址3000-3FFF
分析:要读取03页码的4000-40FF的数据,我们必须要先切换页码到03,这样才能读取03页码的数据
程序如下:
说明: 在切换闪存前,保护原来的页码和几个重要的地址,还要保证地址0A的内容为负数,即地址0A的最高位为0
为什么要这样做,对于该例,完全可以不这样做,因为代码本身在RAM区域,不需要保护当前页码,但对于代码在闪存的那种情况,就一定要这样做了.因为我们在执行完该段程序返回后,要继续执行原来页码的程序,但由于我们切换到了另一个页码,如果不在程序返回前,切换入原来的页码,程序就无法执行.
    我举个形象点的例子:现在有16间房子,某人住在第3间房子,他每天的工作是打扫第3间房子的卫生,但打扫卫生的工具却放在04间房子,所以他打扫卫生的步骤是这样的:
①先进入第4间房子,拿卫生工具       ;相当于程序中的切换页码
②回到原来自己的房子              ;返回原来的页码
③继续工作                       ;继续执行原来页码的程序
    所以大家可以明白,如果某人到第4间房间拿了工具,却不返回原来的房间,后果是什么?
2000:LDA $00              ;地址00的内容送入堆栈,保护地址00的内容
2002:PHA                 
2003:LDA $0A              ;地址0A的内容送入堆栈,保护地址0A的内容
2005:PHA
2006:LDA $0D              ;地址0D的内容送入堆栈,保护地址0D的内容
2008:PHA
2009:LDA $0A              ;地址0A的内容送寄存器A
200B:AND #$7F             ;寄存器A的值和7F进行逻辑与运算,这里功能是将地址0A的最高位置0
200D:STA $0A       
200F:LDA #$03            ;将页码03送寄存器A
2011:STA $00             ;03送地址00,这里就切换到了03页码
2013:LDX #$00           ;开始数据的传输
2015:LDA $4000,X
2018:STA $3000,X
201B:INX
201C:BNE $2015
201E:PLA                 ;数据传输完毕,把最开始压入堆栈的内容弹出,这里注意弹出的顺序
201F:STA $0D
2021:PLA
2022:STA $0A
2024:PLA
2025:STA $00
2027:RTS                ;先前保护的地址内容已经完全恢复,程序结束返回
●当前代码处于 闪存区域内
    这种情况比上面的麻烦些,切换闪存的程序不可以在闪存区域执行,因为本身程序已经处于某页码的闪存区域又怎么可以切换到另外的页码呢?就好象你已经处于第3间房子,你不出去,在第3间房子里怎么可以到达第4间房子呢?大家可能注意到 "出去"这两个字,不错,我们要先出去,这里我们可以想切换闪存的程序发送到 RAM里,然后程序调用该RAM里的程序即可实现页码的切换.
例: 处于闪存区域的页码的切换,当前页码为0F页码,现在要读取03页码的4000-40FF的内容送到地址3000-3FFF,送入RAM的切换闪存的代码从地址4000开始.但实际执行切换闪存,读取数据的主程序是从地址4030开始
在写下面的代码前,要确定发送到的RAM的首地址,否则就无法写代码!!
这里我们确定将代码送地址1700开始的RAM区域
4000:LDA $00              ;地址00的内容送入堆栈,保护地址00的内容
4002:PHA                 
4003:LDA $0A              ;地址0A的内容送入堆栈,保护地址0A的内容
4005:PHA
4006:LDA $0D              ;地址0D的内容送入堆栈,保护地址0D的内容
4008:PHA
4009:LDA $0A              ;地址0A的内容送寄存器A
400B:AND #$7F             ;寄存器A的值和7F进行逻辑与运算,这里功能是将地址0A的最高位置0
400D:STA $0A       
400F:LDA #$03            ;将页码03送寄存器A
4011:STA $00             ;03送地址00,这里就切换到了03页码
4013:LDX #$00            ;开始数据的传输
4015:LDA $4000,X
4018:STA $3000,X
401B:INX
401C:BNE $1715        ;注意,这里不是BNE $4015,因为代码是从RAM地址1700开始执行的,所以是 BNE$1715
401E:PLA                 ;数据传输完毕,把最开始压入堆栈的内容弹出,这里注意弹出的顺序
401F:STA $0D
4021:PLA
4022:STA $0A
4024:PLA
4025:STA $00
4027:RTS                ;先前保护的地址内容已经完全恢复,程序结束返回
然后就是主程序,这里从地址4030开始吧
在写主程序前,要先计算出发送到RAM区域的程序代码的字节数,这里是 十六进制28个字节,即十进制的40个字节
4030:LDX #$00         ;将地址4000-4027的数据送地址1700-1727
4032:LDA $4000,X
4035:STA $1700,X
4038:INX
4039:CPX #$28
403B:BNE $4032
403D:JSR $1700    ;执行地址1700开始的程序
4040:RTS          ;结束


 **NC1020闪存的擦除**
    NC1020闪存的擦除比CC800,PC1000的更复杂些,闪存的擦除包括 内核区闪存的擦除 和 非内核区闪存的擦除.
先介绍非内核区闪存的擦除.内核区闪存的擦除将在介绍完了NC1020内核保护原理后才能讲解!
    NC1020的闪存是以 2K 为一个闪存块的,分别如下:
4000-47FF        1
4800-4FFF      2
5000-57FF      3
5800-5FFF4
...
B000-B7FF       15
B800-BFFF       16
    可见,一个页码的闪存有16块,闪存的擦除是以块为单位的,也就是说,最小可擦除的闪存大小是2K,不能想擦除哪个字节就擦除哪个字节.
我们先要会判断 哪块闪存 已经被擦除了,其实很简单,若该块地址的内容全为 FF,则说明该块闪存已经被擦除了.
    例 擦除某段页码的4000-47FF
    这里我们进入NCTOOLS,先找一个没有被系统利用的页码,方法是 D 4000 03,然后按 K 往下翻页码,当看到 地址4000后的内容全部为 FF 时,那就说明可以利用这块闪存做擦除闪存的实验了.这里我们发现 06 页码的 4000后内容全为 FF,如图:
4000-FFFF FFFF FFFF FFFF
4008-FFFF FFFF FFFF FFFF
4010-FFFF FFFF FFFF FFFF
4018-FFFF FFFF FFFF FFFF
4020-FFFF FFFF FFFF FFFF
4028-FFFF FFFF FFFF FFFF
为了证明我们成功的擦除了该块闪存,我们往里面写些数据,输入 E C 4000 06,输入 01 02 03 04 05 06 07 08,如图:
4000-0102 0304 0506 0708
4008-FFFF FFFF FFFF FFFF
4010-FFFF FFFF FFFF FFFF
4018-FFFF FFFF FFFF FFFF
4020-FFFF FFFF FFFF FFFF
4028-FFFF FFFF FFFF FFFF
    现在我们开始编写程序,需要注意的是,擦除闪存的程序必须在RAM执行,而不能在闪存里被执行
先说明擦除闪存的步骤:
1.设置中断标志  即 SEI
2.保护地址00 0A 0D的内容,地址0A的内容的最高位为0
3.把擦除的闪存的页码送地址00,以切换到擦除的闪存的页码
4.开始 擦除闪存的代码
5.把立即数 30 送擦除闪存的块头地址,例如 若是01闪存块,块头地址是 4000,若是02闪存块,块头地址4800
6.恢复地址 00 0A 0D 的内容
7.取消中断标志 即  CLI
程序如下:
2000:SEI
2001:LDA $00
2003:PHA
2004:LDA $0A
2006:PHA
2007:LDA $0D
2009:PHA
200A:LDA $0A
200C:AND #$7F
200E:STA $0A
2010:LDA #$06     //把擦除闪存块所在页码数送地址00,以切换到闪存块所在页码
2012:STA $00
2014:LDA #$F0    //下面的代码就是擦除闪存的代码,是不能变的,大家也别问为什么是这样的代码,照抄就是!!!
2016:STA $8000
2019:LDA #$AA
201B:STA $5555
201E:LDA #$55
2020:STA $AAAA
2023:LDA #$80
2025:STA $5555
2028:LDA #$AA
202A:STA $5555
202D:LDA #$55
202F:STA $AAAA
2032:LDA #$30     //这里是很关键的一步,把立即数30送要擦除的闪存块的块头地址 这里是第1块,所以是4000,第2块就是4800,类推!
2034:STA $4000
2037:LDA $8000    //这段代码是固定的,大家照抄!!
203A:AND #$88
203C:CMP #$88
203E:BNE $2037    //注意,这里是变化的,因为代码开始执行地址是不同的,不要这个也照抄,那就完了!!反正是跳转到 LDA $8000 那个地址
2040:LDA #$F0
2042:STA $8000
2045:CLI         //取消中断标志,也是必须的
2046:PLA         //下面就是恢复被保护的地址的内容了
2047:STA $0D
2049:PLA
204A:STA $0A
204C:PLA
204D:STA $00
204F:RTS
    在NCTOOLS下, G 2000, 然后 D 4000 06,激动人心的事情发生了,我们已经成功擦除了06页码的4000-47FF了,如图:
4000-FFFF FFFF FFFF FFFF
4008-FFFF FFFF FFFF FFFF
4010-FFFF FFFF FFFF FFFF
4018-FFFF FFFF FFFF FFFF
4020-FFFF FFFF FFFF FFFF
4028-FFFF FFFF FFFF FFFF
我写了个子程序,可以实现擦除任意连续的闪存块,希望能给大家点启发,入口参数是这样规定的
地址 F0 F1,开始块头地址
地址 F2 F3,结束块头块头地址
地址 F4   ,页码数
例如 擦除 06页码 的 4800-87FF
那么  开始块头地址是   4800
      结束块头地址是   8000
入口参数就是 :
  F0: 00    F1: 48  F2: 00   F3: 80  F4: 06
大家看程序,和上面的几乎是一样的,不同的地方为粗体
2000:SEI
2001:LDA $00
2003:PHA
2004:LDA $0A
2006:PHA
2007:LDA $0D
2009:PHA
200A:LDA $0A
200C:AND #$7F
200E:STA $0A
2010:LDA $F4      //这里读取地址F4的内容作为页码,在调用这个子程序前,要先把页码送地址F4
2012:STA $00
2014:LDA #$F0    //下面的代码就是擦除闪存的代码,是不能变的,大家也别问为什么是这样的代码,照抄就是!!!
2016:STA $8000
2019:LDA #$AA
201B:STA $5555
201E:LDA #$55
2020:STA $AAAA
2023:LDA #$80
2025:STA $5555
2028:LDA #$AA
202A:STA $5555
202D:LDA #$55
202F:STA $AAAA
2032:LDY #$00       //寄存器Y的内容 = 0
2034:LDA #$30       //地址 F0为块头地址低8位, F1为块头地址高8位,然后把立即数30送块头地址
2036:STA ($F0),Y
2038:LDA $8000    //这段代码是固定的,大家照抄!!
203B:AND #$88
203D:CMP #$88
203F:BNE $2038    //注意,这里是变化的,因为代码开始执行地址是不同的,不要这个也照抄,那就完了!!反正是跳转到 LDA $8000 那个地址
2041:LDA #$F0
2043:STA $8000
2046:LDA $F1     //由于是擦除连续的闪存块,所以这里要检查是不是已经全部擦除完毕,方法是把地址F1的内容和地址F3比较
2048:CMP $F3       如果相同,则说明已经擦除完,转地址2052,准备结束程序
204A:BEQ $2054
204C:CLC         //如不同, 地址 F1的内容加 8,请想想为什么是加 8
204D:ADC #$08
204F:STA $F1
2051:JMP $2014   //继续擦除下一闪存块
2054:CLI           //清除中断标志,恢复被保护的地址内容,程序结束
2055:PLA
2056:STA $0D
2058:PLA
2059:STA $0A
205B:PLA
205C:STA $00
205E:RTS
现在我们实验擦除一段连续的闪存块
进入NCTOOLS, D 4000 03,按 K 翻页码,知道发现地址 4000-6FFF全部为 FF 的页码,这里我们假设是 0D 页码
这里我们是擦除 地址 4000-6FFF
那么我们从地址3000开始:
3000:LDA #$00        //块头地址送F0 F1 F2 F3
3002:STA $F0
3004:LDA #$40
3006:STA $F1
3008:LDA #$00
300A:STA $F2
300C:LDA #$68
300E:STA $F3
3010:LDA #$0D       //页码送  F4
3012:STA $F4
3014:JSR $2000     //执行擦除闪存程序
3017:RTS
注:以上程序均在NCTOOLS里测试通过


 **修改非内核区的闪存**
    闪存的修改也包括内核区闪存的修改和非内核区闪存的修改,这里讨论内核区闪存的修改.
    要修改闪存区域的某个地址或某段地址的内容,首先必须保证该个地址或该段地址的内容为 FF,如果不是,那么必须擦除该个地址或该段地址所在的闪存块.
如图:
4000-AEEE EA00 2000 204C
4008-1A63 7003 1003 FFFF
4010-FFFF FFFF FFFF FFFF
4018-8019 1009 1110 0221
4020-4002 0014 2016 61FF
4028-A545 484A 4A4A 4A85
    这里由于地址4010-4017内容是 FF,所以这里我们就修改0E页码地址4010 -地址 4017的内容为 00 01 02 03 04 05 06 07,但是大家要注意的是,我这里的0E页码是没有被系统占用的,大家实验的时候,先找块没有被系统用的页码,然后才可以实验
程序如下:
A 2000
2000:SEI                          //设置中断标志
2001:LDA $00                      //保护地址00 0A 0D 的内容,方法是先压入堆栈
2003:PHA
2004:LDA $0A
2006:PHA
2007:LDA $0D
2009:PHA
200A:LDA $0A                     //地址0A的最高位置 0
200C:AND #$7F
200E:STA $0A
2010:LDA #$10                   //这里把目标地址的高8位送地址 41, 低8位送地址 40
2012:STA $40
2014:LDA #$40
2016:STA $41
2018:LDA #$0E                //把页码数送寄存器A
201A:STA $00                //切换到了 09 页码了
201C:LDY #$00               //X,Y寄存器初始化均为 00
201E:LDX #$00
2020:TXA                    //寄存器 X的内容送 A
2021:PHA                   //A 的内容送堆栈
2022:LDA #$AA            //这里就是 修改闪存的 代码了,大家不能改,照着抄
2024:STA $5555
2027:LDA #$55
2029:STA $AAAA
202C:LDA #$A0
202E:STA $5555
2031:PLA                //把原来压入堆栈 的  X 的内容弹出堆栈,并送寄存器A
2032:STA ($40),Y       //寄存器 A 的内容送以 地址 40的内容为低8位,41的内容为高8位的16地址,实现修改闪存
2034:LDA $8000         //这段代码是检测是否修改成功,大家不要变
2037:AND #$88
2039:CMP #$88
203B:BNE $2034       //这里是可能要变的,反正是跳转到 LDA $8000那里的地址
203D:LDA #$F0
203F:STA $8000
2042:CPX #$07         //寄存器X的内容为07 吗,如果是,那就说明全部修改完了,转结束
2044:BCS $2050
2046:INX             //没有,那么,寄存器X的内容加1
2047:INC $40        //地址40的内容加1,为写入下一个地址做准备
2049:BNE $204D
204B:INC $41
204D:JMP $2020       //继续开始修改闪存
2050:CLI             //程序结束
2051:PLA
2052:STA $0D
2054:PLA
2055:STA $0A
2057:PLA
2058:STA $00
205A:RTS
然后我们  G 2000
再 D 4000 0E,发现已经修改成功,如图2:
这里要提醒大家的是,在运行 修改闪存代码前,请将要修改的数据压入堆栈,然后运行完 修改闪存代码后,把压入堆栈的数据弹出,而且必须采用 后变址Y间接寻址方式,将数据写入 闪存地址,这里不支持 直接X变址 和 直接Y变址,但还支持 直接寻址,不过我们一般就是用 后变址Y间接寻址 方式
    前面的程序比较的简单,因为闪存地址的内容已经是 FF,所以我们不需要擦除闪存,不过下面我们要修改一段内容不为 FF 的闪存地址的内容为 00 01 02 03 04 05 06 07,那么我们应该怎么做,请看我们分析:
    假如我们要修改 图 2 的地址4018-401F 的内容为 00 01 02 03 04 05 06 07
由于地址 4018-401F 的内容不为 FF,所以我们要先擦除 该段地址所处的闪存块,很明显,处与 01 闪存块,那么我们就需要先擦除地址 4000-47FF 的内容全部为 FF,那么就出现一个问题,我们这样做,势必会破坏其他地址的内容啊?所以我们需要先保护地址4000-47FF的内容,在 NC1020中,我们可以这样做:
1.先把 地址 4000-47FF的内容 发送到 地址 3000-37FF
2.把 00 01 02 03 04 05 06 07 发送到地址  3018-301F,注意,这里是3018-301F
3.擦除 地址 4000-47FF
4.把地址 3000-37FF 的内容 送地址 4000-47FF
5.修改完毕,OK!
    由于上面有大量的数据传送程序,我们先来编写一个数据传输的子程序,可以实现任意地址数据之间的传输,这个子程序也被广泛用于我的 XASM,NCTOOLS等DEBUG工具中.
    我这里是这样规定的:
地址 40 41     源开始的地址
地址 42 43     源结束地址
地址 44 45     目标开始地址
地址 46        源页码,若是 RAM,页码为 FF
地址 47        目标页码,若是 RAM,页码为 FF
A 2000
2000:SEI               //这里设置中断标志,保护地址 0D 0A 00的内容
2001:LDA $00
2003:PHA
2004:LDA $0A
2006:PHA
2007:LDA $0D
2009:PHA
200A:LDA $0A        //保证地址OA 最高位为 0
200C:AND #$7F
200E:STA $0A
2010:LDY #$00       //寄存器 Y的内容为00,因为后面要用到的 后变址Y间接寻址 要求 (Y)=00
2012:LDA $46       //这里 切换到 源页码
2014:STA $00
2016:LDA ($40),Y     //读取源页码的相应地址数据到 寄存器 A
2018:PHA            //寄存器A的内容压入堆栈,准备写入 目标页码的闪存
2019:LDA $47          //目标页码 数是放在地址47,这里如果地址47的内容 大于  10,那说明目标地址所在区域为 RAM
201B:CMP #$10           那么就不需要运行 修改闪存 的代码
201D:BCS $2048        //若页码数 >10,那么转地址 2048,可以直接把数据写入目标地址
201F:STA $00          //那么说明目标地址处于闪存 或者 00页码
2021:CMP #$00         //如果是 00 页码,由于地址 4000-7FFF也是 RAM,所以也可以直接写入
2023:BEQ $2048    
2025:LDA #$AA        //这里就是  写闪存的 代码,不能变
2027:STA $5555
202A:LDA #$55
202C:STA $AAAA
202F:LDA #$A0
2031:STA $5555
2034:PLA             //把压入堆栈的数据出栈,写入 目标闪存
2035:STA ($44),Y
2037:LDA $8000       //检测 是否已经 修改 完毕,和前面的一样
203A:AND #$88
203C:CMP #$88
203E:BNE $2037      
2040:LDA #$F0
2042:STA $8000
2045:JMP $204B        //跳过下面的两行代码
2048:PLA               //这里当目标地址 是RAM,直接把数据写入
2049:STA ($44),Y
204B:LDA $40           //把地址40 41的内容和地址 42 43的数据比较,如果相同,说明数据全部发送完毕
204D:CMP $42
204F:BNE $2062
2051:LDA $41
2053:CMP $43
2055:BNE $2062
2057:CLI             //如果地址 40 41的内容和地址 42 43的内容相同,程序结束
2058:PLA
2059:STA $0D
205B:PLA
205C:STA $0A
205E:PLA
205F:STA $00
2061:RTS
2062:INC $40             //地址40, 44的内容加1,为读取和写入 下一字节做 准备
2064:BNE $2069
2066:INC $41
2068:INC $44
206A:BNE $206F
206C:INC $45
206E:JMP $2012          //继续转地址2012
有了前面的基础,我想上面的程序大家应该可以看懂吧!
在前面的擦除闪存 教程中,已经介绍了擦除任意连续闪存块的子程序,现在我们派上用场了,程序如下:
2100:SEI
2101:LDA $00
2103:PHA
2104:LDA $0A
2106:PHA
2107:LDA $0D
2109:PHA
210A:LDA $0A
210C:AND #$7F
210E:STA $0A
2110:LDA $F4      //这里读取地址F4的内容作为页码,在调用这个子程序前,要先把页码送地址F4
2112:STA $00
2114:LDA #$F0    //下面的代码就是擦除闪存的代码,是不能变的,大家也别问为什么是这样的代码,照抄就是!!!
2116:STA $8000
2119:LDA #$AA
211B:STA $5555
211E:LDA #$55
2120:STA $AAAA
2123:LDA #$80
2125:STA $5555
2128:LDA #$AA
212A:STA $5555
212D:LDA #$55
212F:STA $AAAA
2132:LDY #$00       //寄存器Y的内容 = 0
2134:LDA #$30       //地址 F0为块头地址低8位, F1为块头地址高8位,然后把立即数30送块头地址
2136:STA ($F0),Y
2138:LDA $8000    //这段代码是固定的,大家照抄!!
213B:AND #$88
213D:CMP #$88
213F:BNE $2138    //注意,这里是变化的,因为代码开始执行地址是不同的,不要这个也照抄,那就完了!!反正是跳转到 LDA $8000 那个地址
2141:LDA #$F0
2143:STA $8000
2146:LDA $F1     //由于是擦除连续的闪存块,所以这里要检查是不是已经全部擦除完毕,方法是把地址F1的内容和地址F3比较
2148:CMP $F3       如果相同,则说明已经擦除完,转地址2152,准备结束程序
214A:BEQ $2154
214C:CLC         //如不同, 地址 F1的内容加 8,请想想为什么是加 8
214D:ADC #$08
214F:STA $F1
2151:JMP $2114   //继续擦除下一闪存块
2154:CLI           //清除中断标志,恢复被保护的地址内容,程序结束
2155:PLA
2156:STA $0D
2158:PLA
2159:STA $0A
215B:PLA
215C:STA $00
215E:RTS
  现在我们可以很容易的实现修改闪存了,下面我们就实现 修改 0E 页码 的4018-401F 的内容为 00 01 02 03 04 05 06 07
A 220B
220B:LDA #$00
220D:STA $40
220F:LDA #$40
2211:STA $41
2213:LDA #$FF
2215:STA $42
2217:LDA #$47
2219:STA $43
221B:LDA #$00
221D:STA $44
221F:LDA #$30
2221:STA $45
2223:LDA #$0E
2225:STA $46
2227:LDA #$FF
2229:STA $47
222B:JSR $2000        //实现了将0E页码地址4000-47FF 送地址3000-37FF
222E:LDA #$00
2230:STA $F0
2232:LDA #$40
2234:STA $F1
2236:LDA #$00
2238:STA $F2
223A:LDA #$40
223C:STA $F3
223E:LDA #$0E
2240:STA $F4
2242:JSR $2100         //实现 擦除 0E 页码 4000-47FF
2245:LDX #$00         //实现将 0 1 2 3 4 5 6 7 写入地址 3018-301F
2247:TXA
2248:STA $3018,X
224B:INX
224D:CPX #$08
224F:BNE $2247
2251:LDA #$00
2252:STA $40
2254:LDA #$30
2256:STA $41
2258:LDA #$FF
225A:STA $42
225C:LDA #$37
225E:STA $43
2260:LDA #$00
2262:STA $44
2264:LDA #$40
2266:STA $45
2268:LDA #$FF
226A:STA $46
226C:LDA #$0E
226E:STA $47
2270:JSR $2000        //实现将地址 3000-37FF 送0E 页码 4000-47FF
2273:RTS              //结束

 

nc1020内核保护原理
    注意:这里需要你的汇编语言能力已经较强了,如果看不懂,说明你功底不够,先练练再来吧!
    我们有时候RESET后,系统显示 升级的选项,如图所示:


(图为升级系统程序的画面)
   *升级系统程序*
    1.经串口升级
    2.经红外升级
    一般来说,只要内核的数据被改动后,RESET后,系统就会出现这种情况.之所以我没有在前面讲解内核区闪存的修改就是因为这个原因,即使你能修改内核,但是如果不懂内核保护原理,RESET后,就出现上面的情况,所以,今天我要讲解内核保护原理.
    首先我们需要找到检验内核是否被修改的程序,其实是很简单的过程,我们就从上面显示的汉字作为突破点.
    进入 NCTOOLS,输入 S C 4000 BFFF 80 A0,然后输入要查找的汉字 "*升级系统程序*",不过没有找到,没有关系,继续 输入 S C 4000 BFFF 00 03,输入要查找的汉字"*升级系统程序*",不过很不幸,还是没有找到,这时大家别泄气,想想还有什么地方可找呢?对了当地址0A的内容为00时,C000-DFFF也是闪存啊!立刻  S C C000 DFFF 00,输入 "*升级系统程序*",找到了,在地址 CA73 开始,按 中英数 切换到汉字模式,
CA73:*升级系统程序* 1.经串口升级  2.经红外升级
现在好了,因为必然有一段程序将这些汉字显示在屏幕上,所以根据经验,应该可能存在有这样的代码
LDA $CA73,X  机器码: BD 73 CA
查找, S H C000 DFFF 00,输入 BD 73 CA,找到了,在地址CACC有这样的代码
CACC: BD 73 CA 99 D4 02 BD 81
CAD4: CA 99 E8 02 BD 8F CA 99
CADC: FC 02 88 CA 10 EA 8A 48
CAE4: A9 00 A2 09 9D F8 03 CA
CAEC: D0 FA AD F8 03 29 3F 8D
CAF4: F8 03 68 AA A9 FF 8D 2D
CAFC: 04 A9 FF 8D 2E 04 00 15
CB04: 8A A9 00 85 C7 A9 00 8D
CB0C: 6E 04 8D AE 04 A5 C7 10
CB14: F4 29 7F 85 C7 C9 6E F0
CB1C: 29 C9 62 D0 E8 20 5C CE
CB24: F0 03 4C 9D CA A9 10 8D
CB2C: 00 17 A9 11 8D 02 17 A9
CB34: F0 8D 01 17 A9 12 8D 03
CB3C: 17 A9 00 8D 04 17 00 10
CB44: 8B 60 20 5C CE F0 03 4C
CB4C: 9D CA A9 B0 8D 00 17 A9
CB54: 00 8D 03 17 A9 11 8D 02
CB5C: 17 A9 F0 8D 01 17 A9 00
CB64: 8D 04 17 00 10 8B 60 A9
现在我们将其反汇编,来让大家明白是什么意思
CACC: BD 73 CA    LDA $CA73,X      //读取*升级系统程序*中的汉字,发送到屏幕RAM
CACF: 99 D4 02    STA $02D4,Y     
CAD2: BD CA 81    LDA $CA81,X       //读取 "1.经串口升级",发送到屏幕RAM
CAD5: 99 E8 02    STA $02E8,Y
CAD8: BD 8F CA    LDA $CA8D,X       //读取 "2.经红外升级",发送到屏幕RAM
CADB: 99 FC 02    STA $02FC,Y
CADE: 88          DEY
CADF: CA          DEX
CAE0: 10 EA       BPL $CACC
CAE2: 8A          TXA
CAE3: 48          PHA
CAE4: A9 00       LDA #$00
CAE6: A2 09       LDX #$09
CAE8: 9D F8 03    STA $03F8,X
CAEB: CA          DEX
CAEC: D0 FA       BNE $CAE8
CAEE: AD F8 03    LDA $03F8
CAF1: 29 3F       AND #$3F
CAF3: 8D F8 03    STA $03F8
CAF6: 68          PLA
CAF7: AA          TAX
CAF8: A9 FF       LDA #$FF
CAFA: 8D 2D 04    STA $042D
CAFD: A9 FF       LDA #$FF
CAFF: 8D 2E 04    STA $042E
CB02: 00 15 8A    INT $8A15        //看到INT $8A15,就应该知道上面的代码就是显示汉字到屏幕上用的
CB05: A9 00       LDA #$00         //地址C7是用户按键的扫描码,这里没有调用 INT $C008,或INT $C007
CB07: 85 C7       STA $C7         //数据00送地址C7
CB09: A9 00       LDA #$00        //立即数00送地址046E,因为这里是无限循环,若不把数据00送046E,过5秒就死机
CB0B: 8D 6E 04    STA $046E         所以如果是无限循环,每次循环都应该加这代码
CB0E: 8D AE 04    STA $04AE
CB11: A5 C7       LDA $C7         //读取地址 C7的值,若用户按了键盘,就转到地址CB15
CB13: 10 F4       BPL $CB09
CB15: 29 7F       AND #$7F        //把按键的值和7F异或送回地址C7,那么得到的就是键盘扫描码
CB17: 85 17       STA $C7
CB19: C9 6E       CMP #$6E        //用户是否 按  2 键?
CB1B: F0 29       BEQ $CB46       //若是 则转到地址CB46,执行  经红外升级 代码
CB1D: C9 62       CMP #$62       //若 否,那么用户是不是按 1 键?
CB1F: D0 E8       BNE $CB09      //若不是,转地址CB09,继续查询用户的按键
CB21: 20 5C CE    JSR $CE5C       //调用子程序CE5C,是检查电量 的子程序
CB24: F0 03       BEQ $CB29       //若电量充足,则Z=1,否则Z=0,这里若电量足,程序转到地址CB29
CB26: 4C 9D CA    JMP $CA9D      //若电量不足,则程序转地址CA9D,在屏幕上显示 "电池电力已不足,不能做升级操作"
CB29: A9 10       LDA #$10        //这是 从串口升级 的程序
CB2B: 8D 00 17    STA $1700
CB2E: A9 11       LDA #$11
CB30: 8D 02 17    STA $1702
CB33: A9 F0       LDA #$F0
CB35: 8D 01 17    STA $1701
CB38: A9 12       LDA #$12
CB3A: 8D 03 17    STA $1703
CB3D: A9 00       LDA #$00
CB3F: 8D 04 17    STA $1704
CB42: 00 10 8B    INT $8B10
CB45: 60          RTS
CB46: 20 5C CE    JSR $CE5C    //这是 从红外升级 的开始地址
CB49: F0 03       BEQ $CB4E
CB4B: 4C 9D CA    JMP $CA9D
CB4E: A9 B0       LDA #$B0
CB50: 8D 00 17    STA $1700
CB53: A9 00       LDA #$00
CB55: 8D 03 17    STA $1703
CB58: A9 11       LDA #$11
CB5A: 8D 02 17    STA $1702
CB5D: A9 F0       LDA $F0
CB5F: 8D 01 17    STA $1701
CB62: A9 00       LDA #$00
CB64: 8D 04 17    STA $1704
CB67: 00 10 8B    INT $8B10
CB6A: 60          RTS
    这里经过反汇编,我们有理由相信,当检查到内核被修改时,一定会调用上面的程序.但是我们还应该找到这段程序的最开始代码,上面的代码是不完全的,这很简单,只要再往前些字节就应该是了,我们按 双下箭头 ,看到这样的代码:
CA9C: 20 00 2E 8A A2 28 BD 94
CAA4: CE 9D D3 02 CA D0 F7 A9
CAAC: FF 8D 2D 04 A9 FF 8D 2E
CAB4: 04 00 15 8A A9 00 8D 6E
CABC: 04 A5 C7 10 F7 29 7F 85
CAC4: C7 00 2E 8A A2 0D A0 10
我们可以继续反汇编,可能大家觉得非常疲劳了吧,不过没有办法啊,就是这样找的啊,可没有人告诉你哦!
我们目测,应该可以知道应该从地址CA9D开始反汇编,因为 00 2E 8A,我们是熟悉的,就是清屏的中断调用.所以我们就可以猜想,地址 CA9D就是入口地址了.还是先看看反汇编结果吧
CA9D: 00 2E 8A      INT $8A2E         //这是清屏的中断调用
CAA0: A2 28         LDX #$28
CAA2: BD 94 CE      LDA $CE94,X      //通过看地址CE94的内容,发现是"电池电力已不足     不能作升级操作"
CAA5: 9D D3 02      STA $02D3,X       //把汉字送屏幕RAM
CAA8: CA            DEX
CAA9: D0 F7         BNE $CAA2
CAAB: A9 FF         LDA #$FF
CAAD: 8D 2D 04      STA $042D
CAB0: A9 FF         LDA #$FF
CAB2: 8D 2E 04      STA $042E
CAB5: 00 15 8A      INT $8A15        //显示  "电池电力已不足     不能作升级操作"到屏幕
CAB8: A9 00         LDA #$00         //这是按键程序,若用户没有按键,则一直等待
CABA: 8D 6E 04      STA $046E
CABD: A5 C7         LDA $C7
CABF: 10 F7         BPL $CAB8       //没有按键,则转地址CAB8,继续等待
CAC1: 29 7F         AND #$7F        //按了键,继续执行下面的
CAC3: 85 C7         STA $C7        
CAC5: 00 2E 8A      INT $8A2E
CAC8: A2 0D         LDX #$0D
CACA: A0 10         LDY #$10
   通过反汇编,我们可以发现,主程序并不是从地址CA9D开始,我们 G CA9D ,发现屏幕上显示"电池电力已不足     不能作升级操作"
那说明用户按了1,2中的某个键,如果检测到电量不足,才去执行CA9D,若用户按了任意键,则执行从地址 CAC5开始的程序,所以
地址CAC5才是主程序
   好了,那么如果系统检测到内核被修改了,一定会调用地址CAC5开始的程序,我们于是可以判断存在这样的代码,那就是  JSR $CAC5 或者 JMP $CAC5,机器码 分别是 20 C5 CA,4C C5 CA.我们先查找 20 C5 CA,因为这个可能性大些,结果找到了,在地址C9CC
C9CC: 20 C5 CA 4C 94 C9 A9 FF
这里反汇编就是
C9CC: 20 C5 CA      JSR $CAC5
C9CF: 4C 94 C9      JMP $C994
我们可以想象,一定是有一段条件判断代码,当满足某个条件,程序才转到地址 CAC5,但这里我们没有发现.我们继续 按 双下箭头 往前看,看见这样的数据:
C994: A9 03 85 00 AD F8 BF C9
C99C: 7F F0 33 20 6B CB A9 00
C9A4: 8D 6E 04 8D 77 04 8D AE
C9AC: 04 A9 2D 8D 76 04 20 B1
C9B4: DD AD 62 04 10 12 AD 62
C9BC: 04 09 04 8D 62 04 AD BF
C9C4: 05 09 10 85 18 8D BF 05
C9CC: 20 C5 CA 4C 94 C9
  我们继续反汇编,得到下面代码:
C994: A9 03       LDA #$03      //03送寄存器A
C996: 85 00       STA $00       //切换到 03 页码
C998: AD F8 BF    LDA $BFF8     //读取地址BFF8的内容送寄存器A
C99B: C9 7F       CMP #$7F      //若地址BFF8的内容是不是等于 7F
C99D: F0 33       BEQ $C9D2     //等于7F ,程序转到地址C9D2
C99F: 20 6B CB    JSR $CB6B     //不等于,则执行下面的程序
C9A2: A9 00       LDA #$00
C9A4: 8D 6E 04    STA $046E
C9A7: 8D 77 04    STA $0477
C9AA: 8D AE 04    STA $04AE
C9AD: A9 2D       LDA #$2D
C9AF: 8D 76 04    STA $0476
C9B2: 20 B1 DD    JSR $DDB1
C9B5: AD 62 04    LDA $0462
C9B8: 10 12       BPL $C9CC
C9BA: AD 62 04    LDA $0462
C9BD: 09 04       ORA #$04
C9BF: 8D 62 04    STA $0462
C9C2: AD BF 05    LDA $05BF
C9C5: 09 10       ORA #$10
C9C7: 85 18       STA $18
C9C9: 8D BF 05    STA $05BF
C9CC: 20 C5 CA    JSR $CAC5
C9CF: 4C 94 C9    JMP $C994
这里我们发现,若03 页码 的地址BFF8 的内容不等于 7F,那么就会出现要求升级的画面,如果等于 7F,程序就转到地址C9D2 开始执行那里的程序.我们这里应该清楚,内核被修改了的检测程序应该就从地址C9D2开始.为什么呢?因为检测03页码地址BFF0的内容并不能检查出内核是否被修改,所以我们转到地址C9D2开始分析那里代码
C9D2: A9 FF 8D 0A 17 8D 0B 17
C9DA: A9 01 8D 04 17 AD 04 17
C9E2: 85 00 A9 00 85 D2 A9 40
C9EA: 85 D3 A9 00 85 80 A9 80
C9F2: 85 81 20 23 CC EE 04 17
C9FA: AD 04 17 C9 03 90 DE A9
CA02: 03 85 00 A9 00 85 D2 A9
CA0A: 40 85 D3 A9 F0 85 80 A9
CA12: 7F 85 18 20 23 CC A9 0F
CA1A: 85 00 A9 00 85 D2 A9 80
CA22: 85 D3 A9 00 85 80 A9 40
CA2A: 85 81 20 23 CC A9 03 85
CA32: 00 AD F0 BF 49 FF CD 0A
CA3A: 17 D0 0B AD F1 BF 49 FF
CA42: CD 0B 17 D0 01 60 AD BF
CA4A: 05 29 FB 85 18 A9 1F 85
CA52: 57 A9 00 85 58 A9 F8 85
CA5A: 59 A9 0F 85 5A A9 00 85
CA62: 54 00 0F C0 AD BF 05 09
CA6A: 04 85 18 8D BF 05 4C 94
CA72: C9 2A C9 FD BC B6 CF B5
    哎呀,我都感觉很累了,不过马上就完了,大家撑着啊,马上就完了.
C9D2: A9 FF     LDA #$FF
C9D4: 8D 0A 17  STA $170A
C9D7: 8D 0B 17  STA $170B
C9DA: A9 01     LDA #$01      //这段是检查 01-02页码的地址4000-BFFF的数据
C9DC: 8D 04 17  STA $1704
C9DF: AD 04 17  LDA $1704
C9E2: 85 00     STA $00
C9E4: A9 00     LDA #$00
C9E6: 85 D2     STA $D2
C9E8: A9 40     LDA #$40
C9EA: 85 D3     STA $D3
C9EC: A9 00     LDA #$00
C9EE: 85 80     STA $80
C9F0: A9 80     LDA #$80
C9F2: 85 81     STA $81
C9F4: 20 23 CC  JSR $CC23
C9F7: EE 04 17  INC $1704
C9FA: AD 04 17  LDA $1704
C9FD: C9 03     CMP #$03
C9FF: 90 DE     BCC $C9DF
CA01: A9 03     LDA #$03      //这段是检查 03页码的地址4000-BFEF的数据
CA03: 85 00     STA $00
CA05: A9 00     LDA #$00
CA07: 85 D2     STA $D2
CA09: A9 40     LDA #$40
CA0B: 85 D3     STA $D3
CA0D: A9 F0     LDA #$F0
CA0F: 85 80     STA $80
CA11: A9 7F     LDA #$7F
CA13: 85 81     STA $81
CA15: 20 23 CC  JSR $CC23
CA18: A9 0F     LDA #$0F      //这段是检查 0F页码的地址8000-BFEF的数据
CA1A: 85 00     STA $00
CA1C: A9 00     LDA #$00
CA1E: 85 D2     STA $D2
CA20: A9 80     LDA #$80
CA22: 85 D3     STA $D3
CA24: A9 00     LDA #$00
CA26: 85 80     STA $80
CA28: A9 40     LDA #$40
CA2A: 85 81     STA $81
CA2C: 20 23 CC  JSR $CC23
CA2F: A9 03     LDA #$03
CA31: 85 00     STA $00
CA33: AD F0 BF  LDA $BFF0
CA36: 49 FF     EOR #$FF
CA38: CD 0A 17  CMP $170A
CA3B: D0 0B     BNE $CA48
CA3D: AD F1 BF  LDA $BFF1
CA40: 49 FF     EOR #$FF
CA42: CD 0B 17  CMP $170B
CA45: D0 01     BNE $CA48
CA47: 60        RTS
CA48: ......     因为下面的代码和主题那样什么关系
在上面的程序里,频繁的调用了子程序 CC23,这就是最关键的算法
我们也将地址CC23反汇编如下:
CC23:SEI                   //设置中断标志
CC24:CLC                  //进位标志C = 0
CC25:LDA $80              //地址80的内容 与 地址 D2 的内容相加,结果放 地址 80
CC27:ADC $D2
CC29:STA $80
CC2B:LDA $81               //地址81的内容 与 地址D3的内容相加, 结果放地址 81
CC2D:ADC $D3
CC2F:STA $81
CC31:LDY #$00               //寄存器Y的内容为 0
CC33:LDA ($D2),Y           //以地址 D2的内容为 低8位,地址 D3的内容为高8位,组成一个16位地址,读取该地址内容送 A
CC35:EOR $170A             //把寄存器 A 的内容 和地址 170A 的内容 进行 逻辑异或 运算
CC38:TAX                   //把运算的结果送 寄存器X
CC39:LDA $170B             //把地址170B的内容送寄存器 A
CC3C:EOR $CD5C,X          //寄存器A的内容和 地址 [CD5C+X]的内容 进行逻辑异或 运算
CC3F:STA $170A            //然后把异或 的结果送地址 170A
CC42:LDA $CC5C,X          //读取地址 [CC5C+X]的内容 到寄存器A
CC45:STA $170B            //寄存器A的内容送地址 170B
CC48:INC $D2              //地址D2的内容加1
CC4A:BNE $CC4E           
CC4C:INC D3
CC4E:LDA $D3             //地址D3 的内容和 地址 81的内容比较,不相同就转 CC58,继续
CC50:CMP $81            
CC52:BNE $CC58
CC54:LDA $D2             //地址 D2的内容和地址80的内容比较,小于就转 CC33
CC56:CMP $80
CC58:BCC $CC33 
CC5A:CLI               //这里表示已经全部结束
CC5B:RTS
  子程序 CC23是整个校验程序的算法,我来分析一下其算法:
 1.  地址80 的内容和地址 D2的内容相加,结果送地址 80,地址81的内容和地址 D3的内容相加,结果送地址81.这样,地址 D2,D3就是要检查的地址的 开始地址,地址 80,81就是要检查的地址的 结束地址 - 1
比如 开始时 (D2) = 00, (D3) = 40, (80) = 00, (81) = 80,那么经过这样运算,结果是这样的: (D2) = 00, (D3) = 40, (80) = 00, (81) = C0,
那么可见检查的地址范围是 4000-BFFF,即检查整个闪存,见下面程序
CC25:LDA $80              //地址80的内容 与 地址 D2 的内容相加,结果放 地址 80
CC27:ADC $D2
CC29:STA $80
CC2B:LDA $81               //地址81的内容 与 地址D3的内容相加, 结果放地址 81
CC2D:ADC $D3
CC2F:STA $81
2.逐个读取 地址4000-BFFF的内容,和地址 170A的内容进行逻辑异或运算,结果 送 寄存器 X
3.把地址 170B 的内容和 地址[CD5C+X)的内容进行逻辑异或运算,结果送地址 170A
4.读取 地址[CC5C+X]的内容送地址 170B
5.一直循环这样的过程,直到检查完毕
检查数据是否被修改的算法就是这样,那么最后是如何检查的呢?请看下面的程序:
CA2F: A9 03     LDA #$03                      //03送寄存器A
CA31: 85 00     STA $00                       //切换到03页码
CA33: AD F0 BF  LDA $BFF0                    //03页码的地址 BFF0的内容和 FF 进行异或
CA36: 49 FF     EOR #$FF                
CA38: CD 0A 17  CMP $170A                     //异或的结果和地址 170A的内容比较
CA3B: D0 0B     BNE $CA48                    //不同转CA48,我们  G CA48,出现 要求你升级的画面
CA3D: AD F1 BF  LDA $BFF1                     //地址BFF1的内容和 FF 进行异或运算
CA40: 49 FF     EOR #$FF
CA42: CD 0B 17  CMP $170B                    //结果和地址  170B 比较
CA45: D0 01     BNE $CA48                     //不同转CA48
CA47: 60        RTS                           //相同就结束
CA48: ......     因为下面的代码和主题那样什么关系
  我们于是可以知道,最后面 是切换到 03 页码,读取地址BFF0的内容,和 FF 异或,然后和地址 170A比较.如果不同,说明内核被修改了,如果相同,还要读取地址 BFF1 的内容,和 FF 异或,再和地址 170B 的内容比较.如果相同,说明内核没有被修改,那么RESET 成功.
  这里要注意的是,该段检查内核被修改的地址范围如下:
01-02页码 4000-BFFF
03页码    4000-BFF0   注意这里不是BFFF
0F页码    8000-BFFF
    现在我们豁然开朗了,我们只要在修改了内核后,同样进行这样的检查运算,最后,把地址 170A的内容和 FF 进行 异或,保存在03 页码的地址 BFF0,把地址 170B 的内容和 FF 进行 异或,结果保存在03 页码的地址BFF1 就可以了.
    这段检查内核被修改的算法,叫CRC32算法,它的全称叫 "Cyclic Redundancy Check",中文名字: 循环冗余校验码.该算法主要就是保证数据的完整性,比如 现在的压缩软件,在压缩数据的时候,应该也会对 数据进行CRC校验,然后把校验值放在某个地址.当解压缩后,就再次对数据进行 CRC校验,把校验值和以前的比较,如果不同,说明数据已经被破坏.
    本节内容比较复杂, 如果大家有什么不懂的,可以 EMAIL 我, syj22@163.net

 

 **内核区闪存的擦除**

    对于想修改NC1020的朋友来说,已经掌握了内核保护原理,最想知道的事莫过于如何修改内核闪存了,不过要想修改内核的闪存,必须先知道如何擦除内核的闪存了,现在我就向大家介绍方法.
    由于前面已经讲了非内核区闪存的擦除,我这里就把不同点给大家列出,其他的完全和前面非内核区的一样.
1.在 擦除的最前 加这段代码:
LDA #$00
STA $18
最后擦除完毕,加这代码:
LDA #$1C
STA $18
2.代码要变了,请看表:
表1:
非内核区     内核区
LDA #$F0      LDA #$AA
STA $8000     STA $D555
LDA #$AA      LDA #$55
STA $5555     STA $AAAA
LDA #$55      LDA #$80
STA $AAAA     STA $D555
LDA #$80      LDA #$AA
STA $5555     STA $D555
LDA #$AA      LDA #$55
STA $5555     STA $AAAA
LDA #$55
STA $AAAA
表2:
非内核区         内核区
2038:LDA $8000    2038:LDA $C000
203B:AND #$88     203B:AND #$88
203D:CMP #$88     203D:CMP #$88
203F:BNE $2038    203F:BNE $2038
2041:LDA #$F0     2041:LDA #$F0
2043:STA $8000    2043:STA $C000
怕大家还是搞不清楚,我们还是举个例子吧
例: 擦除 03 页码的 B000-B7FF,因为这段地址全是 FF,擦除了后对系统没有影响,如果你想验证你是否真的擦除
你可以先往里面写入几个字节,然后再擦除,我们这里就不写入字节了.
A 2000
2000:SEI                    //设置中断
2001:LDA #$00              //最关键一段,一定要把 00 送地址 18
2003:STA $18
2005:LDA $00                //保护地址00 0D 0A的内容进栈
2007:PHA
2008:LDA $0A
200A:PHA
200B:LDA $0D
200D:PHA
200E:LDA $0A
2010:AND #$7F
2012:STA $0A
2014:LDA #$03                 //切换到 03 页码
2016:STA $00
2018:LDA #$AA              //擦除闪存的代码,固定不变的
201A:STA $D555
201D:LDA #$55
201F:STA $AAAA
2022:LDA #$80
2024:STA $D555
2027:LDA #$AA
2029:STA $D555
202C:LDA #$55
202E:STA $AAAA
2031:LDA #$30            //把 30 送闪存块头地址,这里是擦除B000-B7FF,所以是 B000
2033:STA $B000
2036:LDA $C000          //这里也是固定的,注意和擦除 非闪存区 的不同
2039:AND #$88
203B:CMP #$88
203D:BNE $2036
203F:LDA #$F0
2041:STA $C000
2044:LDA #$1C             //擦除完毕,把 1C 送地址 18
2046:STA $18
2048:CLI                //恢复被保护的数据
2049:PLA
204A:STA $0D
204C:PLA
204D:STA $0A
204F:PLA
2050:STA $00
2052:RTS
注:上面的程序在 NC1020的NCTOOLS中通过.


 **内核区闪存的修改**
如果你没有看 非内核区 闪存的修改,请先去看看!
内核区闪存的修改,和非内核区的不同点有3处:
1.最开始要运行这样代码:
LDA #$00
STA $18
结束时,运行这样的代码:
LDA #$1C
STA $18
2.修改内核的代码也变了,变为下面这样:
LDA #$AA
STA $D555
LDA #$55
STA $AAAA
LDA #$A0
STA $D555
3.验证程序应该是这样
AA:  LDA $C000
AND #$88
CMP #$88
BNE AA
LDA #$F0
STA $C000
其他就是一样的了.


***第七章 学习使用 6502_Macroassembler &Simulator***
对于有电脑的朋友来说,编写汇编就显得更加方便了,在PC上有很多6502的编译软件,比如6502SDK,不过本章我建议大家使用我推荐的一个编译软件 6502_Macroassembler &Simulator.我总结了一下,该软件有下列优点:
1.良好的编辑界面
对指令的不同部分分不同的颜色显示,如图:
(下图是6502 Simulator的工作界面)

DispAddr:    LDA $41
             PHA
             LDA $40
             PHA
             LDY #$00
             LDX #$FF
DispAddrL1:  INX
             CPX #$06
             BEQ DispAddrL6

2.指令的动态帮助
输入指令后,如果你选择了帮助菜单的"动态指令帮助",那么右边就显示该指令的介绍,比如寻址方式,例子等,这对于初学者是很有用的.如下图,我们输入 LDA,右边就显示了LDA指令的帮助.
(下图打不出,略)
3.良好的程序调试功能
  编译好了后,进入 调试状态,可以选择 单步进入,单步退出,下断点等功能,对调试程序很有用.
5.强大的宏功能
该软件原版是英文的,我把它汉化了,需要的朋友请到我网站下载:http://wqxmcode.8u8.com

 **理解标号的意义**
   标号代表该行指令的地址,标号的出现,极大的方便了我们编写程序,我们从此不再需要记地址了,我举一个例子来说明
A2000
2000: LDX #$00
2002: LDA #$00
2004: STA $3000,X
2007: CPX #$FF
2009: BEQ $200F
200B: INX
200C: JMP $2004
200F: RTS
    大家应该知道,我们写上面的程序是多么艰难,还要算出下一指令的地址,在使用跳转指令时就更加不方便了.
    我们来看看用 6502_Macroassembler &Simulator 编写上面的程序是多么的容易
.ORG $2000  ;定义程序开始地址为 $2000
.DB "HELLO WORLD" ;定义字符
   LDX #$00
   LDA #$00
L1:STA $3000,X ;这里L1就是标号,代表当前指令的地址
   CPX #$FF
   BEQ L2
   INX
   JMP L1
L2:RTS ;这里L2也是标号,代表当前指令的地址
    这里我们使用了两个标号,L1,L2,他们分别代表当前指令的地址,在编译的时候,编译器会算出该标号代表的地址,所以说,标号的出现,使得我们不再把大量的时间用在地址的计算上,而且在修改程序的时候也显得更加方便
    例如,我们如果要在下面的程序中,插入一个指令,那么所有的地址都要发生变化,大家请看
A2000
2000: LDX #$00
2002: LDA #$00
2004: STA $3000,X
2007: CPX #$FF
2009: BEQ $200F
200B: INX
200C: JMP $2004
200F: RTS
假如我们要在地址2002那里加一指令 LDY #$00,那么我们整个程序是不是都要变,大家请看,红的部分代表要修改的
A2000
2000: LDX #$00
2002: LDY #$00
2004: LDA #$00
2006: STA $3000,X
2009: CPX #$FF
200B: BEQ $200F
200D: INX
200E: JMP $2006
2011: RTS
    才加那么一个字节,就要修改怎么多,这是多么令人痛苦的事了,不过在电脑上就不一样了,要加指令,其他更本就不要变,大家看,红色的代表加进去的指令,其他指令没有变化
.ORG $2000  ;定义程序开始地址为 $2000
.DB "HELLO WORLD" ;定义字符
   LDX #$00
   LDA #$00
   LDY #$00
L1:STA $3000,X ;这里L1就是标号,代表当前指令的地址
   CPX #$FF
   BEQ L2
   INX
   JMP L1
L2:RTS ;这里L2也是标号,代表当前指令的地址
看到了吧,多方便啊!
注意:
①标号不能以数字开头
②标号不能为指令码,例如 LDA,LDX等,否则编译器可能误认为是指令
③标号一定要顶格写,否则要发生编译错误


 **字节定义 伪指令.DB**
该伪指令可以定义数据,比如字节,也可以定义 字符串,汉字等
1. 定义字节
在字节前加 $, 代表是十六进制
在字节前加 @, 代表是二进制
在字节前什么都不加,代表是十进制
例如
.ORG $0000
.DB $30,$31,$32
我们编译一下,选择 查看-内存窗口,发现地址0000开始的内容是 30 31 32
例如
.ORG $0000
.DB @00110000,@00110001,@00110010
我们编译一下,选择 查看-内存窗口,发现地址0000开始的内容是 30 31 32
例如 
.ORG $0000
.DB 48,49,50
我们编译一下,选择 查看-内存窗口,发现地址0000开始的内容是 30 31 32
2. 定义字符串或汉字
例如
.ORG $0000
.DB "HELLO WORLD"
.DB "你好啊"
一般我们都在.DB 前加一个标号,这样我们在程序中就可以用这些标号
例如,我们在屏幕上显示"HELLO WORLD"
.ORG $2000
LCD_BUFFER = $02C0
STR:.DB "HELLO WORLD",0
   LDX #$0B
L1:LDA STR - 1, X
   STA LCD_BUFFER - 1, X        ;发送到屏幕RAM
   DEX
   BNE L1
   LDA #$01
   STA $0402
.DB $00,$15,$8A             ;由于编译器不支持INT 指令,所以这里直接定义INT $8A15机器码
.DB $00,$08,$C0             ;由于编译器不支持INT 指令,所以这里直接定义INT $C008机器码
   RTS
这个程序中,STR就是字符串的首地址

 

 **字定义 .DW**
该指令是定义字的,一个字是两个字节,所以一次要定义两个字节
比如
.ORG $0000
.DW $1234
但是注意的是,编译后地址(0000) = 34,(0001) = 12,所以大家不要搞错了
大家不要用它来定义汉字和字符,最好用 .DB.


.STR 伪指令
该指令比较适合定义字符串或汉字
该指令会自动算出当前定义的字符串或汉字的长度,并且放在第1个字节
例如
.ORG $0000
.STR "HELLO"
我们编译一下,选择  查看-内存窗口,看到地址0000:05 48 45 4C 4C 4F
第一个字节是字符串的长度
我个人认为这很有用,比如我显示"HELLO WORLD"到屏幕上
.ORG $0000
LCD_BUFFER = $02C0
STR:.STR "HELLO WORLD"
    LDX STR
L1: LDA STR,X
    STA LCD_BUFFER - 1,X
    DEX
    BNE L1
    LDA #$01
    STA $0402
.DB $00,$15,$8A
.DB $00,$08,$C0
    RTS
以前我们还要算出字符串长度,然后送寄存器X,这里我们不需要算了,因为字符串长度是第一个字节了是不是好多了.


 **宏指令**
    令我感到极度兴奋的是 6502_Macroassembler &Simulator有强大的宏功能.这无疑使程序
的编写又简单方便许多了.
    我们先说说宏的格式
宏名:.MACRO  宏虚参数1,宏虚参数2,宏虚参数3.....
      宏体
     .ENDM
这里.MACRO和.ENDM必须成对出现
宏调用的格式:
宏名 宏实参数1,宏实参数2,宏参数3
具有宏调用的源程序被编译时,每个宏调用将被编译程序展开,宏展开实际上是宏定义时设计的宏体去代替相应的宏指令名,并且用实际参数去取代虚参数,以形成符合功能并可以被执行的实际代码
我们举例说明:
实现 中断指令 INT
大家知道,现在的6502编译器都不支持INT指令,给书写造成不便,不过现在我们可以用宏来实现
    .ORG $0000
INT:.MACRO INT_PARAM
    .DB $00
    .DW INT_PARAM
    .ENDM
    说明,这里 INT 是宏名,注意后面要接 : ,INT_PARAM 是虚参数,由于INT的机器码是 00,所以我们在宏体中 .DB $00,比如 INT $C008,机器码是 00 08 C0,所以我们 .DW INT_PARAM.这样,我们实现 INT 指令,例如我们在程序里输入 INT $C008,就和文曲星里的 INT $C008是一样的功能.
    我再举个例子,在6502中没有寄存器X直接入堆栈和弹出堆栈功能的指令,这里我们用宏实现
    .ORG $0000
PHX:.MACRO
     TXA
     PHA
    .ENDM
PLX:.MACRO
     PLA
     TAX
    .ENDM
MAIN:PHX
     ...
     PLX
     RTS
看到了吧,我们在程序中就可以用 PHX,PLX了
    虽然宏的功能比较大,但是我们不可以乱用,因为宏和子程序是不一样的,子程序由 JSR 调用,由 RTS 返回,所以汇编后子程序的机器码只占有一个程序段,不管调用多少次都是如此,比较节省内存.宏指令每调用一次都要占一个程序段,调用次数越多,占用内存就越多,所以从开销来说,子程序优于宏指令,但从程序的执行时间来分析,每调用一次子程序都要保护和恢复返回地址,要消耗一些时间宏指令调用不需要这个过程,执行时间较短,所以从执行时间来看,宏指令又优于子程序.
    所以说,当某一需要多次访问的程序段较长,访问次数又不是太多时,选用子程序比较好;当某一需要多次访问程序段较短,访问次数又很频繁时,选用宏指令比较好.

 

**建立BIN文件的过程**
1.定义程序开始地址
这里请 .ORG $3FD0
2. 包含文件头,这里我做了文件头,大家可以下载 HEAD_NC1020.65S
注意:该文件应该和你的程序文件在同一目录下
然后在程序中包含这个文件头
.ORG $3FD0
.INCLUDE "HEAD.65S"
注意:程序的入口地址的标号一定是 MAIN,一定是大写的.
3. 包含 宏文件,这里我做了,大家请下载 MACRO_NC1020.65S
注意:该文件应该和你的程序文件在同一目录下
.ORG $3FD0
.INCLUDE "HEAD.65S"
.INCLUDE "MACRO.65S"
这里你就可以在程序里使用下列
① INT
由于现在的编译器都不支持INT指令,我做了个宏,现在大家可以用INT指令了
例如 INT $8A15
INT $C008
② PUSH_XY
寄存器X,Y全部进堆栈
③ POP_XY
寄存器X,Y全部出堆栈
④ PRINT_ALL
这里是显示宏,如果你要显示满屏的字符或文字
那么,你先给出文字或字符的开始地址 PRINT_ALL_PARAM
然后你就可以在程序中用 PRINT_ALL PRINT_ALL_PARAM
比如,
.ORG $3FD0
.INCLUDE "HEAD.65S"
.INCLUDE "MACRO.65S"
TEXT:.DB "我的每个幻想              "
     .DB "  总在每一个秋天飞扬      "
     .DB "我的每个悲伤              "
     .DB "  总在每一个夜里生长      "
     .DB "我的每次飞翔              "
     .DB "  总在漫无目的路上        "
 
MAIN:PRINT_ALL TEXT
     LDA #$01
     STA $0402
     LDA #$FF
     STA $042D
     STA $042E
     INT $8A15
     INT $C008
     RTS
我们选择 仿真--编译,然后选择 文件-保存代码,保存的类型选择 65h 格式.因为该编译器生成的是INTEL 公司的 HEX文件格式,所以我做了转换程序HEXTOBIN,可以转化为BIN格式,大家到这里下载  HEXTOBIN.你先选择65H文件路径,然后选择 文曲星类型,这里我们选择 CC880,NC1020,NC2000A(C)类型,然后开始转换,但可惜的是BIN文件只有被加密才能被下载,这里网友BSXY为我们做了加密器,大家请下载加密解密器,然后大家选择 加密-*.TMP文件,输入 BIN的显示名,就可以了.大家看效果:
(以下是在文曲星上以小字体显示的文字)

我的每个幻想
  总在每一个秋天飞扬
我的每个悲伤
  总在每一个夜里生长
我的每次飞翔
  总在漫无目的的路上

(完)

 

原创粉丝点击