Bài 11: ngắt VBlank
Khi viết chương trình dài, có những lúc ta cần dùng lại cùng chức năng đã sử dụng trước đó nên sẽ nảy sinh nhu cần cần phải tập hợp lại những chức năng xử lý giống nhau về cùng một chỗ để có thể gọi từ một nơi khác. Trong ngôn ngữ C thì đó là quan số, trong VB là Function.
Còn trong Assembly là JSR (Jump To Subroutine) và RTS (Return from Subroutine).
Trong đoạn mã ở bài trước, từ hàng 62 trở đi có mục "định dạng tọa độ sprite 2" và từ hàng 125 có "cập nhật tọa độ sprite 2" tương đương như nhau. Nếu gom về một mối thì sẽ như thế này.
setSprite2:
; subroutine cập nhật tọa độ sprite #2
LDA Sprite1_X
ADC #8 ; lệch 8 dot sang phải
STA Sprite2_X
LDA Sprite1_Y
STA Sprite2_Y
RTS
Ta gọi khối mã xử lý chung như thế này là Subroutine.
Trước đây ta tiến hành cập nhật sprite 2 ở 2 chỗ thì giờ đổi thành
; gọi Subroutine cập nhật tọa độ sprite 2
JSR setSprite 2
Viết lại đoạn chương trình ở bài trước, sử dụng Subroutine.
Mã:
.inesprg 1
.ineschr 1
.inesmir 0
.inesmap 0
.bank 1
.org $FFFA
.dw 0
.dw Start
.dw 0
.bank 0
.org $0300
Sprite1_Y: .db 0
Sprite1_T: .db 0
Sprite1_S: .db 0
Sprite1_X: .db 0
Sprite2_Y: .db 0
Sprite2_T: .db 0
Sprite2_S: .db 0
Sprite2_X: .db 0
.org $8000
Start:
lda $2002
bpl Start
lda #001000
sta $2000
lda #000110
sta $2001
ldx #$00
lda #$3F
sta $2006
lda #$00
sta $2006
loadPal:
lda tilepal, x
sta $2007
inx
cpx #32
bne loadPal
lda X_Pos_Init
sta Sprite1_X
lda Y_Pos_Init
sta Sprite1_Y
jsr setSprite2
lda #%01000000
sta Sprite2_S
lda #011110
sta $2001
mainLoop:
lda $2002
bpl mainLoop
lda #$3
sta $4014
lda #$01
sta $4016
lda #$00
sta $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
and #1
bne UPKEYdown
lda $4016
and #1
bne DOWNKEYdown
lda $4016
and #1
bne LEFTKEYdown
lda $4016
and #1
bne RIGHTKEYdown
jmp NOTHINGdown
UPKEYdown:
dec Sprite1_Y
jmp NOTHINGdown
DOWNKEYdown:
inc Sprite1_Y
jmp NOTHINGdown
LEFTKEYdown:
dec Sprite1_X
jmp NOTHINGdown
RIGHTKEYdown:
inc Sprite1_X
NOTHINGdown:
jsr setSprite2
jmp mainLoop
setSprite2:
lda Sprite1_X
adc #8
sta Sprite2_X
lda Sprite1_Y
sta Sprite2_Y
rts
X_Pos_Init .db 20
Y_Pos_Init .db 40
tilepal: .incbin "giko2.pal"
.bank 2
.org $0000
.incbin "giko.bkg"
.incbin "giko2.spr"
Ngắt VBlank
Trong những bài trước đã giải thích về VBlank. Thực tế có rất nhiều game Famicom sử dụng ngắt VBlank, đợi đồng bộ mỗi 1/60 giây rồi mới xử lý.
Ta có thể làm phát sinh ngắt NMI qua thời điểm VBlank của Famicom. Chỉ cần đăng ký địa chỉ vòng lặp chính vào bộ ngắt NMI thì có thể xử lý mỗi 1/60 giây.
Ta có thể sửa đổi lại đoạn code bên trên như thế này.
.bank 1 ; đổi sang bank 1
.org $FFFA ; bắt đầu từ $FFFA
.dw mainLoop ; bộ ngắt VBlank (cứ mỗi 1/60 giây thì mainLoop được gọi ra)
.dw Start ; ngắt Reset. Khi khởi động và khi reset thì nhảy tới Start
.dw 0 ; phát sinh do ngắt phần cứng và ngắt phần mềm
Đầu tiên, cấm ngắt NMI ở Start bởi vì muốn tránh không cho mainLoop được thực hiện khi đang định dạng ban đầu. Việc đợi VBlank cũng như trước giờ, và sau khi định dạng xong thì cho phép ngắt NMI, bit 7 của $2000 thành 1.
; set Flag cho phép ngắt Register điều khiển PPU 1
LDA #%11001000
STA $2000
Lưu ý là $2000 là Register chuyên dùng cho việc ghi.
Sau đó là đợi vòng lặp vô hạn. Cứ mỗi 1/60 giây thì phát sinh ngắt và vòng lặp chính được gọi ra.
infinityLoop: ; vòng lặp vô hạn chỉ để đợi phát sinh ngắt VBlank
JMP infinityLoop
Cuối cùng là ghi lệnh phục hồi sau ngắt vào đoạn cuối của mainLoop. Khi xử lý xong mainLoop thì sẽ trở lại infinityLoop bên trên.
NOTHINGdown:
; gọi Subroutine cập nhật tọa độ sprite 2
JSR setSprite2
RTI ; trở về từ sau khi ngắt
Lưu đồ
Dưới đây là lưu đồ giải thích rõ hơn về trình tự xử lý
0 bình luận :
Post a Comment