6502汇编语言程序设计(2版)之程序设计part1

6502汇编语言程序设计(2版)之程序设计part1

FC技术书籍 2008-11-09 22:26:17 阅读620 评论1 字号:大中小 订阅

6502汇编语言程序设计 返回

第一部分 基本概述 第二部分 指令和算法 第三部分 程序实例

___________________________________________________________________________________

(一)

一. CPU 对命令的执行

6502是普通的8位CPU.它需要外接其他器件(如:SRAM、EPROM、定时/计数器、I/O接口器件等)才能工作,它地址线为16,直接寻址能力为64K($0000--$FFFF)之间. 数据线为8,一次只能完成8位数据(0--255)的处理. CPU通过地址线联系到要操作的内存单元,然后读取该单元的操作码,通过预设好了的操作码所代表的操作,CPU对其进行相应命令执行. 例如对命令 ASL 的执行,假设程序计数(PC)=$7000 , ($7000)=$0A .

CPU 首先通过程序计数 PC 联系到单元$7000 ,并读取该单元内的操作码 0A(对应6502汇编助计符为ASL) 然后根据操作码的类型,完成相应的操作,这里0A代表将累加器A的内容左移一位.如果是一个时钟周期完成一项指令操作,那么就是第一个时钟周期CPU读取单元$7000中的0A,第二个时钟周期CPU执行命令0A(将累加器A的内容左移一位,高位被移到C进位标志中,低位被移入0)

二. 6502系统说明

1. $0000--$00FF 6502 CPU 零页公共区 (256字节)

2. $0100--$01FF 6502 CPU 公共堆栈区 (256字节)

没有数据进栈时堆栈指针 S=$FF,每当有一个数据进栈S-1→S. 每当有一个数据出时栈S+1→S .当执行JSR指令时,CPU把下一条指令地址-1(两个字节)自动入栈(也即程序的返回地址-1自动入栈), 然后转入到子程序中执行,当遇到RTS时弹出栈顶2个字节,作为程序的返回地址,并转到下一条指令中执行. 由于堆栈$100--$1FF最多可以存放127个双字节,所以JSR最大连续的转入深度为127层,如果还有其它数据入栈,子程序转入深度还要减少.在设计复杂的程序时,应该考虑堆栈是否会溢出,或作相关返回处理,以防止堆栈溢出而造成死机.

3. $0200--$0700(cheapchange注:综合上下文,应为0200--07FF) 公共内存区 (1.5K字节)

4. $0800--$1FFF $0000 -- $0700的镜像. 连做了3次镜像(6K字节)

换句话说,对它们的操作(读/写)实际就是对$0000--$07FF的操作。比如:读取 $08AB 的内容实际等于读取$00AB的内容。而向$15CC写数据实际等于向$05CC写数据. $0800--$0FFF, $1000--$17FF, $1800--$1FFF 这3块不是物理的 RAM,它们都是镜像.

5. $2000--$3FFF 6502 PPU I/O 地址区,系统使用. (8K字节)

6. $4000--$43FF CPU 内部功能部件 I/O 地址区,系统使用 (1K字)

7. $4400--$5FFF 扩充空间 (5K字节)

可用于各种扩充外设的 I/O 端口,或扩充ROM和RAM地址空间.

8. $6000--$7FFF 公

共RAM空间

公共内存区,可为系统程序及用户程序使用

9. $8000--$BFFF 分页区间 (16K字节)

按16K字节分区,页号00--FF(一个字节),最大支持4M字节容量.用于系统程序及应用程序的主要程序数据区.

10.$C000--$FF00 公共RAM空间(16K字节)

公共内存区,可为系统程序及用户程序使用. (注$C000--$FFFF 区间的RAM与分页区间的最后一页第FF页的RAM是同一段区域)

11.$FF00--$FFFF 系统区

系统中断向量及I/O地址.

三. 汇编指令格式

汇编指令包括四部分,格式如下:

[标号:] 操作码 [操作数][;注释]

[]中的内容为可选项

标号 标号标识了一条指令的位置,可以使用标号作为访问该指令的地址,标号的格式应遵循下面规则:

.必须从第一列开始或者以: 结束

. 不能用操作代码或伪指令的名字命名

. 标号长度为1~28个字符(视各汇编而定)

. 以字母或下划线开头,由字母、下划线、数字组成的字符串

操作码 指令区域,用于写指令

操作数 操作数可以是程序中的地址或数据. 当操作码为单字节时,无操作数. 当进行立即寻址时,操作数为一个字节数据,

使用一个符号来标识其所在位置. 当操作数作为程序地址时,其实就是一个标号.

注释 注释可以提高程序的可读性. 在注释语句前面应该加上一个(;)

例如: LDA #$01 ;将数据01放入累加器A

注意: 操作码和操作数之间必需要加空格.

四. 计算机中的数

计算机内存中只以01011....的形式来存放数据.每8位划分为一个存储单元,每个单元都有一个地址与这个单元对应,用来识别该单元的所在位置.单元中存放着数据,一个位有0和1两种状态,一个单元8位组合起来只能表示256中状态,可用来表示0~255中的一个数,即一个单元里可以存放0~255中的一个数据.

每个字符都有一个数值与其对应,这个数值就是该字符的ASC码值,把一个字符存入内存单元,就是把它的ASC值放入内存单元中,对于字符是以ASC码值存在于内存单元中的,例如: 将字符"A"存于单元$7000,实际上是把65存到$7000中.

对于数,如果它在0~255之间,则可以用一个单元来存储,如果在0~65535这间则要用两个单元来存放,如果更大,则要用多个单元来存放.在计算机中,一般是以数的补码形式存放在单元的,当要表示的数是一个负数时,则是把该负数的补码存于单元中,这时一个单元只能存放 -128~127之间的数,两个单元只能存放 -32768~32767之间的数.数的符号用最高位来表示,最高位为1时表示负数,最高位为0时表示正数.

一个正数的补码是它本身.

负数的补码的求法是将它的原码取反再加一得到.例-7的求补,它的原码10000111,保留符号位,将其它位取反得 11111000,最后加一得补码为11111001 ,如果-7的原码在寄存器A中,对它求补的6502指令如下:

eor #$7f

clc

adc #$01

也可由它的相反数全部取反,再加一得到,若寄存器A中是-7的相反数7,则可由如下指令得到-7的补码

eor #$ff

clc

adc #$01

如果在内存单元中进行的话可以这样实现,设$A0单元存放是-7的相反数7,则可以直接用0-($A0)得到

lda #$00

sec

sbc $A0

 

五. 简单的程序设计示例

例1.

;把16进制字符成换机内整数(0~FFFF) 6502汇编程序

;这里用了对应变址取对应值方法,避免了求字符ASC的值,目的是简化程序复杂性.

;转换的算法:从高位算起,以次累加到结果单元,每累加一次后把结果单元*16,

;直到末位 (运算前先把16进制标志符$,H或h 处理掉)

HEXCHR:

db "0123456789ABCDEFabcdef"

NUMCODE:

db 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,10,11,12,13,14,15

HEXASC: ;待转换16进制字符串 ,高位在前.(高位为0时也必需写上)

db "0a7F" ;例数

HEXASCBIN: ;对应的数值.

db 0,0,0,0

CODEBIN: ;结果单元.低位在前,高位在后

db 0,0

HEXASC_BIN:

ldy #$00

sty CODEBIN

sty CODEBIN+1 ;清理结果单元

HEXASC_BIN02:

ldx #$00

HEXASC_BIN04:

lda HEXASC,y

cmp HEXCHR,x

beq HEXASC_BIN06

inx

bne HEXASC_BIN04

HEXASC_BIN06:

lda NUMCODE,x

sta HEXASCBIN,y ;存放对字符的对应数

iny

cpy #$04

bne HEXASC_BIN02 ;查找字符对应的数值

ldy #$00

clc

HEXASC_BIN08:

lda HEXASCBIN,y

adc CODEBIN

sta CODEBIN

lda #$00

adc CODEBIN+1

sta CODEBIN+1

iny

cpy #$04 ;检查是否累加结束

beq HEXASC_BIN12

asl CODEBIN

rol CODEBIN+1

rol CODEBIN

rol CODEBIN+1

rol CODEBIN

rol CODEBIN+1

rol CODEBIN

rol CODEBIN+1 ;将(CODEBIN+1|CODEBIN)*16 再累加次位数据

jmp HEXASC_BIN08

HEXASC_BIN12:

rts

例2.

;无符号字节型数据乘法

;($A1)*($A2)=(A|$A2) 其中在寄存器A中是积高位,低位在原来乘数单元中

;算法: 逐次判断$A2的低位,若是0则直接,将A右位移,若是1则累加后再移,由于每次可以得到一位不参加运算的低位,则可将它从左到右地移到乘数单元中,最后原乘数单元的数位全部被移出,取而代之的是积所产生的低位.

MULCH:

lda #$00

ldy #$08

clc

MULCH02:

ror $A2

dey

bmi MULCH08

bcc MULCH04

clc


adc $A1

MULCH04:

ror

jmp MULCH02

MULCH08:

rts

 

例3.

;无符号整型数除法

;($A2|$A1)/($A4|$A3)=$A2|$A1).........($A6|$A5)

;结果 , 商:($A2|$A1) 余数:($A6|$A5)

;说明: C=0有效, C=1 无效(除数为0)

DIV:

lda $A4

bne DIV02

lda $A3

bne DIV02

sec

rts

DIV02:

ldy #17

lda #0

sta $A5

sta $A6

clc

DIV04:

rol $A1

rol $A2

dey

beq DIV08

rol $A5

rol $A6

lda $A5

sec

sbc $A3

tax

lda $A6

sbc $A4

bcc DIV04

sta $A6

stx $A5

bcs DIV04

DIV08:

rts

例4.

; 中缀表达试到后缀表达式的转换,子程序设计

;程序:

top_lev: equ $cf ;栈顶算符优先级单元

ch_reg: equ $d0 ;字符暂存单元

p_inf: equ $d1 ;中缀表达式指针

p_suf: equ $d3 ;后缀表达式指针

p_stack: equ $d5 ;算符栈指针

stackstr: equ $0200 ;算符栈

sufstr: equ $0400 ;后缀表达式生成区( 逆波兰式)

numdef: ;数字定义

db "0123456789ABCDEFabcdef"

db "Hh$",0

operatdef: ;算符定义

db "+-*/%",0

levdef: ;优先级定义

db "+-",0 ;优先级1

db "*/%",0 ;优先级2

infstr: ;调试串

db "585+415*(582%(256-36548)-95)",0

;初始化各指针

initial_p:

ldx #>infstr ;(X)=高位

ldy #
stx p_inf+1

sty p_inf

ldx #>sufstr

ldy #
stx p_suf+1

sty p_suf

ldx #>stackstr-1

ldy #
stx p_stack+1

sty p_stack

rts

isdigit: ;判断是否为数字

ldx #$ff

isdigit02:

inx

lda numdef,x

beq isdigit06

cmp ch_reg

bne isdigit02

isdigit04:

lda ch_reg

ldx #$00 ;yes num N=0

rts

isdigit06:

lda ch_reg

ldx #$ff ;no num N=1

rts

isoperat: ;判断ch是否为算符

ldx #$ff

isoperat02:

inx

lda operatdef,x

beq isoperat06

cmp ch_reg

bne isoperat02

isoperat04:

lda ch_reg

ldx #$00 ;N=0 yes

rts

isoperat06:

lda ch_reg

ldx #$ff ;N=1 no

rts

getch_inf: ;取出一个字符到(A)

ldy #$0

lda (p_inf),y

inc p_inf

bne getch_inf02

inc p_inf+1

getch_inf02:

rts

putch_suf: ;输出一个字符(A)

ldy #$00

sta (p_suf),y

inc p_suf

bne putch_suf02

inc p_suf+1

putch_suf02:

rts

ch_join: ;算符(A)进栈

inc p_stack

bne ch_join02

inc p_stack+1

ch_join02:

ldy #$00

sta (p_stack),y

rts

get_stack: ;算符出栈到(A)

ldy #$00

lda (p_stack),y

ldy p_stack

dey

sty p_stack


cpy #$ff

bne get_stack02

dec p_stack+1

get_stack02:

rts

stack_suf: ;把算符退栈到指定地,直遇到“(“为止

jsr get_stack

cmp #40; "("

beq stack_suf02

jsr putch_suf

jmp stack_suf

stack_suf02:

rts

stack_lev: ;得出栈顶算符优先级别, 在(top_lev)中

ldy #$00

sty top_lev

lda (p_stack),y

cmp #40;"("

beq stack_lev06 ;把栈中的“(”设为0级

cmp #$00

beq stack_lev06

ldx #$ff

stack_lev02:

inc top_lev

stack_lev04:

inx

lda levdef,x

beq stack_lev02

cmp (p_stack),y

bne stack_lev04

stack_lev06:

rts



regA_lev: ;得出(A)中算符优先级,在(X)中

ldx #$00

ldy #$ff

regA_lev02:

inx

regA_lev04:

iny

lda levdef,y

beq regA_lev02

cmp ch_reg

bne regA_lev04

rts

lev_cmp: ;比较当前ch与栈顶算符优先级,并处理

sta ch_reg

lev_cmp02:

jsr stack_lev

jsr regA_lev

cpx top_lev

bcc lev_cmp04 ;若小于或等时把栈中算符输出到

beq lev_cmp04 ;指定位置

lda ch_reg

jsr ch_join

rts

lev_cmp04:

jsr get_stack

jsr putch_suf

jmp lev_cmp02 ;直到ch可以入栈为止

;*****把中缀表达式转换成后缀表达式子程序*****

inftosuf:

jsr initial_p

lda #$00

jsr ch_join ;设置栈初为0

inftosuf02:; main

jsr getch_inf

sta ch_reg

jsr isdigit

bne inftosuf08 ;no num

inftosuf06:

jsr putch_suf

jsr getch_inf

sta ch_reg

jsr isdigit ;

beq inftosuf06 ;num

lda #$20 ; 每个数字段输一空格

jsr putch_suf

lda ch_reg

inftosuf08:

cmp #40; "("

bne inftosuf10

jsr ch_join ;遇“(”时直接入栈

jmp inftosuf02

inftosuf10:

jsr isoperat

bne inftosuf12

jsr lev_cmp

jmp inftosuf02

inftosuf12:

cmp #41 ;")"

bne inftosuf14

jsr stack_suf ;遇“)”退出栈中算符直到遇“(”

jmp inftosuf02

inftosuf14:

cmp #$00

bne inftosuf18

inftosuf16: ;(A)=0时,输出栈中所有算符,到0为止

jsr get_stack

jsr putch_suf

cmp #$00

bne inftosuf16

inftosuf18:

rts

 

---------------------------------------------------------------------------------------------------------

附:

为了后面更好说明清楚,在这里附带介绍几条步步学生电脑系统的几个调用.其它系统请自行设置所用功能.步步学生电脑系统分为BIOS调用 , DOS调用功能, 文件调用 ,外设调用.

1. BIOS 调用说明

★ BIOS调用功能号放在寄存器A中,BIOS调用功能入口地址为$5800. 即:

入口

: (A)=功能号 入口地址=$5800

其它参数视各功能号而定.

出口: 状态寄存器(C、V、I)内容不变

其它参数视各功能号而定

★ 使用BIOS调用的功能号及调用入口都要用符号来表示,不要直接使用数值.

ROMBiosEntry equ $5800

BiosSetPage equ $01 ;设置当前使用的页,

BiosGetPage equ $02 ;取当前使用的页号

BIOS页功能子程序:

; ** getPage() A page **

getpage:

lda #BiosGetPage

jsr ROMBiosEntry

rts

; ** setPage() X page **

setpage:

pha

lda #BiosSetPage

jsr ROMBiosEntry

pla

rts

★ BIOS调用中出现的X|Y,表示由8位寄存器X和Y合成的一个16位字,其中X为字的高8位,Y为低8位.

★ BIOS调用中,用到的缓冲区位置通常由页号连同指针共同给出,其中仅当这个缓冲区指针处于分页区($8000--$BFFF)时才使用

页号,其它地址时页号不被采用.

★ 除了特别说明外,调用后所有寄存器和输入变量都可能被破坏,所有未用的内存单元的内容都不被破坏.

 

2. DOS调用功能号说明

DOS调用功能号放在寄存器A中,DOS调用功能入口地址为$580C. 即:

入口: (A)=功能号 入口地址=$580C

其它参数视各功能号而定.

出口: Cy=0 操作正确.

Cy=1 操作错误. (A)=出错返回码.

其它参数视各功能号而定

(1). DosProgramReturn equ 0

程序结束返回.(等效于 MS-DOS AH=0)

入口: (X)=返回码

(Y) 位0=1 :要求初始化显示屏幕(执行DosInitialScreen)

位1=1 :要求初始化系统资源(执行BiosRunInitial)

(2). DosReadKeyEcho equ 1

读键盘屏幕回显.(等效于MS-DOS AH=1)

出口: (A)=字符码

当字符码等于0为功能键输入,下次调用取出功能键码.

(有关字符码和功能键码与MS-DOS完全相同)

(3). DosDisplayChar equ 2

显示字符. (等效于MS-DOS AH=2)

入口: (X)=字符码

当启动中文系统后,处理汉字国标码(两个国标码拼成一个汉字)

(4). DosGetKBSting equ 6

输入字符串并回显.(等效于MS-DOS AH=0AH)

入口: X|Y=字符串缓存指针(当前页)

缓存字节0: 可输入的最大字符个数(包括回车字符).

出口: 缓存内容 缓存字节0: 不改变

缓存字节1: 输入字符串的字符个数(不包括回车字符)

缓存字节2开始: 输入的字符串(最后一个为回车字符).

注: 字符串不接收功能键,字符串以回车[Enter]结束.DosGetKBString支持命令行编辑功能键(F1-F4)

 

输入及显示字符DOS功能子程序:

; A: char *

putc: ;在屏幕显示一个字符

tax

lda #DosDisplayChar

jmp DosIOEntry

; A : exit mode . (0 or

ff) *

exit: ; 退出程序

tay

lda #DosProgramReturn

jmp DosIOEntry

;printf() A:X|Y the string, end by 0 *

printf: ;在屏幕上显示一串字符,字符串以0为结束标志

sta printf02+1

stx printf04+2

sty printf04+1

jsr getpage

sta printfE+1

printf02:

ldx #$00

jsr setpage

printf04:

lda $7000

beq printfE

jsr putc

printf08:

inc printf04+1

bne printf04

inc printf04+2

lda printf04+2

cmp #$c0

bcc printf04

asl printf04+2

inc print02+1

bne printf02

printfE:

ldx #$00

jsr setpage

rts

相关文档
最新文档