【原创】NES第十波:解说一个NES音乐贺卡的源代码
我将自己写的一编音乐贺卡源代码拿来做解说。完整的工程和源代码见最后面的下载链接。
之前的解说都是保姆级的。从这一章开始,就变得简洁了。
一、定义内存变量(汇编都是用全局静态变量的)
COUNTER = $00 MUSIC_OFFSET1 = $01 MUSIC_OFFSET2 = $02 vNamL = $03 vNamH = $04
我只用了5个字节,好省。
二、初始化
;.start reset .org $C000 reset: sei ; 禁用中断 cld ldx #$ff ; 初始化栈顶指针到$FF txs inx ; x=0了 ;stx COUNTER ;stx MUSIC_OFFSET1 ;stx MUSIC_OFFSET2 _loop_1: ; 清理内存 STA $00,x STA $0100,x STA $0200,x STA $0300,x STA $0400,x STA $0500,x STA $0600,x STA $0700,x INX BNE _loop_1
sei 是禁止IRQ中断,本代码也用不着,而且初始化时,能禁则禁,后面使用时再解禁。
cld 是关闭十进制运算。正统的6502有十进制运算功能,但NES(或FC)的cpu不兼容十进制运算功能,或者说没有这项功能,所以必须关闭,防止cpu出错。
接下来初始化栈道和内存。
三、ppu热机
; 原写法(热机)有点区别 _vb1: BIT $2002 BPL _vb1 _vb2: BIT $2002 BPL _vb2
ppu上电比cpu慢,就是说通电了,cpu能快速启动工作,但ppu还没有达到正常工作的状态,要等一等,大约等ppu发来两次帧信号就可以了。
四、ppu操作之关屏和配色
lda #$00 ; 关屏 sta $2001 LDA #$3F ; 写入配色盘 STA $2006 LDA #$00 STA $2006 LDX #$00 _loop_pal: LDA bg_pal,x STA $2007 INX CPX #$10 BNE _loop_pal
现在才可以对ppu操作,初上电时ppu的内存是随机的,那显示出的画面也是乱的,所以先关屏,不让玩家看见。
下一步是写上配色,就是写入调色板。调色板数据,已放在bg_pal这个地址,在代码的后面。这里先向端口$2006写入$3F00,再用循环连续写入$10个数据。
五、ppu操作之清除背景
LDA #$20 ; 清除背景 STA $2006 LDA #$00 STA $2006 LDY #$04 _loop_ppu_1: LDX #$00 LDA #$00 ; 0号Title 是空白的 _loop_ppu_2: STA $2007 DEX BNE _loop_ppu_2 DEY BNE _loop_ppu_1
我先择使用第00页背景,地址是$2000,包括它的命名表和属性表。向先向端口$2006写入$2000。
当下是将命名表和属性表都清零。我这里使用两重循环,共$0400次。那就是从$2000到$23FF。
我设计CHR时,将0号Title设计成空白。那就是说清零后,命名表用0号Title填满,属性表用0号调色板填满。
六、ppu操作之绘画背景
LDA #<bg_nam STA vNamL LDA #>bg_nam STA vNamH LDA #$20 STA $2006 LDA #$00 STA $2006 LDY #$00 LDX #$04 _loop_ppu_4: LDA (vNamL),y STA $2007 INY BNE _loop_ppu_4 INC vNamH DEX BNE _loop_ppu_4
背景数据,我放在bg_nam这个地址。我将它的低位和高位赋给vNamL和vNamH。
再用两重循环,这次是Y递增的,那就是从$2000+$00到$2000+$FF,重复4次。即从$2000到$23FF。
将从bg_nam开始的数据,连续写入ppu。因为我一直都是在黑屏下操用的。这是可以的。静态画面可以这样做。
七、ppu操作之对齐画面
LDX #$00 STX $2005 STX $2005
前几章,应该说过,向PPU的背景页写入数据,画面就会移位。这个是代码是纠正移位的。(滚屏也用到它,但本例没有滚屏,所以不多提。)
八、设置apu(音乐)参数
LDA #$0f ; 声音切换:0000 1111,使能:方波#1#2,三角波,噪声 STA $4015 LDA #$8f STA $4000 ; APU方波#1控制端口 LDA #$00 STA $4001 ; APU方波#1控制端口
开启音乐功能,4个声道我都打开了,但本例只使用一个声道 方波1#
九、启动NMI中断,并设置图库
LDA #$88 ; 开nmi中断,(加入画面)图库:精灵用$1000,背景用$0000 STA $2000
本例只用背景的CHR图库。没设定这个值之前,不能开屏,因为没设定的话也是随机的。
十、ppu操作之开屏
_vb3: BIT $2002 BPL _vb3 LDA #$08 STA $2001
开屏之前,要等一个帧信号,在屏幕扫描开始时开屏。
十一、主程序
main: jmp main
这是一个静态贺卡,没有什么手柄操作和动态画面,所以主程序什么也没有。
十二、NMI中断和音乐播放
nmi: inc COUNTER ; 计数器增加 ldx MUSIC_OFFSET1 lda music1,x ; 取得音长 cmp COUNTER ; 判断是否该播放 beq next2 ; 播放 cmp #$ff beq next1 ; 是否结束 jmp over ; 退出 next1: ldx #$00 ; 回到开始 stx MUSIC_OFFSET1 jmp over next2: lda #$00 sta COUNTER lda MUSIC_OFFSET2 tay lda music2,y ; 波长低八位 cmp #$ff ; 结束了吗? beq next3 sta $4002 iny lda music2,y ; 波长高三位+音长计数器 cmp #$ff ; 结束了吗? beq next3 sta $4003 iny ; 下一个音符 sty MUSIC_OFFSET2 inx stx MUSIC_OFFSET1 jmp over next3: ldy #$00 ; 回到开始 sty MUSIC_OFFSET2 over: rti music1: ; 音长 8*4+1=33 .db $10,$10,$20,$20,$20,$20,$20,$10 .db $10,$20,$20,$20,$20,$20,$20,$10 .db $10,$20,$20,$20,$20,$20,$20,$20 .db $20,$10,$10,$20,$20,$20,$20,$20 .db $20,$FF music2: ; 两个一组,$FF为结束 8*8+2=66 .db $1C,$09,$1C,$09,$FD,$08,$1C,$09 .db $D4,$08,$E1,$08,$00,$00,$1C,$09 .db $1C,$D9,$FD,$08,$1C,$09,$BD,$08 .db $D4,$08,$D4,$08,$00,$00,$1C,$09 .db $1C,$09,$8D,$08,$A8,$08,$D4,$08 .db $E1,$08,$FD,$08,$FD,$08,$FD,$08 .db $00,$00,$9F,$08,$9F,$08,$A8,$08 .db $D4,$08,$BD,$08,$D4,$08,$D4,$D4 .db $08,$00,$FF,$FF
因为主程序什么也没有,所以中断的数据现场保护(进出栈)就全都省了。
这个音乐播放也是很简单,取一个音长,再取两个频率值的高低位,写入端口,累计音长值(就是帧数),音长结束则更新下一组数据。如此循环。
十三、背景画面数据
bg_pal: .include "bg_pal.inc" bg_nam: .include "bg_nam.inc" .db $00,$00,$00,$00
调色板 :bg_pal
背景的命名表和属性表:bg_nam
这两个都是通过我的背景编辑工具,编制出来的。见前面章节的讲解。
十四、IRQ
irq: rti
本例不使用。
十五、启动的指针
.org $fffa .dw nmi,reset,irq
这三个地址很重要,也很简单。前面章节说过了。
源代码和工程下载:地址 http://fogota.ysepan.com/
找到 NES Tool Kit文件夹,各章节的相关下载都是在一起。
编译和组成NES的办法,前面章节的讲的。相关工具,在第一章和第六章就已有下载。