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"  
JSR là nhảy đến Subroutine, và trở về bằng chức năng RTS, tiếp tục thực thi hàng code tiếp theo sau JSR.


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ý

[​IMG]

0 bình luận :

Post a Comment

 
Top