Bài 4: PPU


Famicom thể hiện hình ảnh bằng PPU (Picture Processing Unit). Đây là vùng không gian riêng biệt với CPU 6502 và được gọi là VRAM (video Ram), có nhiệm vụ vẽ ảnh nền (BG) và ảnh sprite. PPU vẽ không đồng bộ, độc lập với 6502. Dù 6502 có ghi vào Register vẽ sprite thì không phải sprite sẽ được hiển thị ra màn hình lập tức. Để thao tác PPU từ 6502 thì cần phải thiết lập giá trị Register điều khiển PPU.


Bit flag
Vì 6502 là CPU 8 bit nên nó có thể xử lý cùng lúc 8 hàng giá trị ở hệ nhị phân. Trong các chương trình thông thường thì có một biến số gọi là flag để giữ trạng thái ON hay OFF của cái gì đó. Khi giữ 1 bye ở dạng nhị phân thì có thể sử dụng đến 8 bit từ 0 đến 7 như dưới đây. Đối với người biết ngôn ngữ C thì dù không biết Assembly cũng thấy quen thuộc. Chẳng hạn như trường hợp dưới đây, có thể nói 0 và 5 đang ON.

Giá trị Bit flag 0 0 1 0 0 0 0 1
Hàng số 7 6 5 4 3 2 1 0


Thiết lập PPU
Để sử dụng PPU thì đầu tiên cần định dạng Register điều khiển PPU bằng Bit flag này. Register điều khiển PPU chia làm 2 địa chỉ lần lượt là $2000 và $2001, có nội dung như dưới đây.

$2000: Register điều khiển PPU 1, trong đó
bit 7: thi hành NMI khi VBlank (0: không thi hành, 1: thi hành)
bit 6: chọn PPU Master/Slave (0: chọn Master mode)
bit 5: kích thước sprite (0: 8x8, 1:8x16)
bit 4: địa chỉ Pattern table cho BG (0: $0000, 1: $1000)
bit 3: địa chỉ Pattern table cho sprite (0: $0000, 1: $1000)
bit 2: cộng vào địa chỉ PPU (0: +1, 1:+32)
bit 1-0: số hiệu địa chỉ Name table hiển thị

00=$2000 (VRAM)
01=$2400 (VRAM)
10=$2800 (VRAM)
11=$2C00 (VRAM)

$2001: Register điều khiển PPU 2
bit 7-5: khi bit 0=1 thì chỉnh cường độ màu của BG
000: không
001: lục
010: lam
100: đỏ
(không chấp nhận số khác ngoài 3 số này)

bit 4: hiển thị sprite (0: không hiển thị, 1: hiển thị)
bit 3: hiển thị BG (0: không hiển thị, 1: hiển thị)
bit 2: xén sprite (0: không hiển thị 8 điểm bên trái màn hình, 1: không cắt xén)
bit 1: xén BG (0: không hiển thị 8 điểm bên trái màn hình, 1: không cắt xén)
bit 0: kiểu hiển thị (0: màu, 1: trắng đen)

Ta thử định dạng Register điều khiển PPU về mặc định bằng đoạn code dưới đây

LDA #001000
STA $2000
LDA #011110
STA $2001

Tức là:

1. Tải giá trị nhị phân 001000 vào Accumulator. Giá trị này tương đương #$08
2. Chứa giá trị của Accumulator (001000) vào địa chỉ $2000
3. $2000 là địa chỉ của Register điều khiểnPPU thứ nhất, có các trạng thái sau:
+ Không thực hiện ngắt NMI khi VBlank (bit 7 =0)
+ Sprit là 8x8 điểm ảnh (bit 5 = 0)
+ Pattern table cho BG bắt đầu từ $0000 (bit 4 =0)
+ Địa chỉ PPU cộng thêm từng 1 đơn vị (bit 2= 0)
+ Số hiệu địa chỉ Name table là $2000 (bit 1,0=00)

4. Tải giá trị nhị phân 011110 vào Accumulator. Giá trị này tương đương #$1E
5. Chứa giá trị của Accumulator (011110) vào địa chỉ $2001
6. $2001 là địa chỉ của Register điều khiểnPPU thứ hai, có các trạng thái sau:
+ Hiển thị ảnh sprite (bit 4=1)
+ Hiển thị ảnh nền (bit 3=1)
+ Không cắt xén sprite (bit 2=1)
+ Không cắt xén BG (bit 1=1)
+ Chế độ hiển thị màu (bit 0=0)

Vậy là ta đã định dạng về mặc định xong 2 Register điều khiển PPU ở $2000 và $2001.
Điểm cần chú ý là khi định dạng VRAM là cho hiển thị sprite và BG bằng giá trị 0, sau khi định dạng thì trả về 1, nếu không thì có nguy cơ VRAM không được định dạng đúng.



Chương trình đơn giản

Dưới đây là một chương trình đơn giản, chỉ hiển thị một màn hình màu.
Thử thay đổi giá trị các bit từ 7 đến 5 của Register $2001 và quan sát sự thay đổi màu sắc trên màn hình.

.inesprg 1 ; 1x 16KB PRG code
.ineschr 1 ; 1x 8KB dữ liệu CHR
.inesmap 0 ; mapper 0 = NROM, không đổi bank
.inesmir 1 ; đối xứng gương ảnh nền



.bank 0
.org $C000
RESET:
SEI ; vô hiệu hóa IRQ
CLD ; vô hiệu hóa chế độ thập phân
LDX #$40
STX $4017 ; vô hiệu hóa APU frame
LDX #$FF
TXS ; Set up stack
INX ; X = 0
STX $2000
STX $2001
STX $4010

vblankwait1: ; đợi VBlank để bảo đảm PPU đã sẵn sàng
BIT $2002
BPL vblankwait1

clrmem:
LDA #$00
STA $0000, x
STA $0100, x
STA $0200, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
LDA #$FE
STA $0300, x
INX
BNE clrmem

vblankwait2: ; đợi VBlank, PPU đã hoàn tất
BIT $2002
BPL vblankwait2


LDA #%10100001 ;định dạng Register điều khiển PPU2
STA $2001

Forever:
JMP Forever ;nhảy về Forever, lặp vĩnh viễn


NMI:
RTI

 

 .bank 1
.org $FFFA ;vector đầu tiên trong số 3 vector bắt đầu tại đây
.dw NMI ; khi NMI xảy ra (1 lần mỗi frame) thì bộ xử lý nhảy tới label NMI
.dw RESET ;khi bộ xử lý được bật hay reset, nó nhảy tới label RESET:
.dw 0 ;ngắt IRQ không được dùng ở đây
.bank 2
.org $0000
.incbin "kage.bkg" ; kèm file ảnh nền kage.bkg

Tải file này (https://www.dropbox.com/s/1eob4pjbjrxc2wa/background.zip?dl=0) về, giải nén được background.asm và kage.bkg.
background.asm là file chương trình ở trên, còn kage.bkg là file ảnh nền, tuy chưa cần đến trong chương trình này nhưng ta cũng đưa vào.
Đặt 2 file này chung thư mục với NESASM, rồi lập file .bat với nội dung như sau

NESASM background.asm
pause

Click đôi vào file .bat vừa tạo để NESASM thi hành các lệnh trong background.asm. Cũng có thể thi hành từ cửa sổ CMD, gõ đường dẫn tới thư mục chứa NESASM. Khi chạy NESASM, ta thấy cửa sổ như sau


[​IMG]

Như vậy, NESASM sẽ tạo ra file có tên background.nes. Có thể dùng các loại giả lập NES để mở file này, quan sát kết quả.

1 bình luận :

 
Top