Làm game Snes nào!

Cửa hàng game Nintendo nShop

1658m6As

Nấm bình thường
Đọc tại nguồn:

http://yugisokubodai.blogspot.com/2017/09/asm1.html


(Tham khảo video trên và xem phần hướng dẫn bên dưới)

Hướng dẫn lập trình cho máy Snes bằng ngôn ngữ Assembly 65816


Bài 1: chuẩn bị môi trường + định dạng


1.1. Mở đầu

Trong các ngôn ngữ bậc cao như C, C++ hay đối với các loại CPU cao cấp như x86, bài vỡ lòng luôn là viết dòng chữ "Hello World" ra màn hình.Việc in dòng chữ này ra màn hình rất đơn giản, chỉ gồm 1 đến vài dòng code là có thể thực hiện được.

Thực ra thì quá trình in dòng chữ "Hello World" ra ngoài màn hình không hề đơn giản như nhiều người lầm tưởng.

Đây là ưu điểm và cũng chính là nhược điểm của ngôn ngữ bậc cao. Nó giúp đơn giản hóa mọi việc phức tạp, nhưng cũng chính vì thế mà người học ở giai đoạn đầu không thể hiểu sâu vào bản chất của vấn đề. Để in được dòng chữ "Hello World" thì máy tính phải trải qua nhiều công đoạn khác nhau, nhưng phần lớn các công đoạn đều do máy tự thực hiện nhờ bộ thư viện có sẵn, nên người học tưởng chừng như nó chỉ thực hiện mỗi một vài lệnh do họ viết ra. Chính vì vậy người học gần như không thể hiểu được những gì mình đang làm trong giai đoạn đầu.

Ngược lại, các loại CPU đời cũ như dòng 65816 dùng cho máy Snes của hãng Western Design Center (WDC) sản xuất vào thập niên 1980 không có những tính năng "tự động" như x86 sau này. Người dùng phải tự làm hết mọi công việc và máy (CPU) chỉ làm nhiệm vụ đơn giản là làm theo những chỉ thị (dòng code) của người dùng. Để đạt hiệu quả cao đối với CPU hiệu năng thấp, người ta buộc phải dùng đến ngôn ngữ Assembly, thứ ngôn ngữ gần với ngôn ngữ nhị phân nhất, thứ duy nhất CPU có thể "hiểu" được. Vì vậy mà việc lập trình cho máy Snes, cũng như các hệ máy console đời cũ khác luôn được cho là cực nhọc và khó khăn hơn khi lập trình bằng ngôn ngữ bậc cao như C hay VB cho CPU x86. Nhưng cũng chính vì phải làm mọi việc nên người học dễ dàng hình dung được sâu sắc hơn về cấu trúc, kiến trúc cũng như cách làm việc của CPU đối tượng.

Loạt bài này hướng dẫn các kỹ thuật lập trình cho máy SNES (hay còn gọi là SFC ở Nhật) từ những kiến thức căn bản đến nâng cao.
Vì những lý do nêu trên, nên bài vỡ lòng không phải là bài viết dòng chữ "Hello World" ra màn hình. Để làm được việc này thì trước đó cần định dạng để máy Snes có thể hoạt động đúng chức năng.


1.2. Những thứ cần chuẩn bị:

- Phần mềm soạn thảo văn bản bất kỳ. Có thể dùng Notepad có sẵn trong Windows. Khuyên dùng EmEditor hoặc Notepad +. Mọi nội dung code sẽ được gõ vào đây.
- Trình biên dịch opcode 65816 thành mã nhị phân để CPU 65816 hiểu được. Có nhiều loại, khuyên dùng xkas.
- Phần mềm Hex editor bất kỳ.
- Phần mềm Lunar Address để chuyển đổi địa chỉ Snes sang địa chỉ PC. Nếu thông thạo cách chuyển đổi thì có thể bỏ qua phần mềm này.
- Phần mềm Tile Editor bất kỳ. Chủ yếu để tạo dữ liệu đồ họa.
- Kiến thức căn bản về ngôn ngữ 65816. Người viết tự đọc sách mà có. Tài liệu ngày nay không hề thiếu.
- Kiến thức căn bản về phần cứng của máy Snes, chủ yếu là hiểu về các Register, Vram, SPC,.... Tài liệu không hề thiếu.
- Phần mềm Debugger bất kỳ. Khuyên dùng Geiger's Snes9x vì tiện lợi. Chủ yếu để thực thi và kiểm tra code, phát hiện chỗ sai.

Tất cả những thứ trên đều có thể tìm dễ dàng qua trang world wide web http://wwww.GOOGLE.com

1.3. Chuẩn bị môi trường:

- Tạo một Folder với tên bất kỳ
- Đặt xkas vào Folder này
- Tạo file bất kỳ, có thể đổi thành định dạng máy Snes (giả lập/Debugger) đọc được là .smc hoặc .sfc. Có thể để bất cứ định dạng gì.
- Tham khảo cách dùng xkas trong file readme. (tạo file .bat có nội dung "xkas.exe -o sample.smc code.asm", khi thực thi thì xkas sẽ biến mọi dòng code trong code.asm thành mã nhị phân vào trong sample.smc)
- Tùy vào phiên bản xkas mà cách khai báo trong code.asm có thể khác nhau. Luôn phải khai báo Rom đối tượng là LoRom hay HiRom. Ở đây ví dụ với LoRom.

1.4. Header:

Nhiều người quen thuộc với Rom Snes cho rằng header là $200 (512) byte đầu tiên của Rom. Thực ra không phải. Đó chỉ là phần header vô thưởng vô phạt mà các công cụ dump (trích xuất) từ Catridge thành file để chạy trên giả lập. Có thể dùng Hex editor xóa đi $200 (512) byte này.

Header thực sự của Rom Snes luôn nằm ở một vị trí cố định trong Rom do hãng Nintendō quy định. Khi máy Snes nhận Rom, đầu tiên nó sẽ tìm đến địa chỉ này và đọc thông tin tại đây. Đó là lý do vì sao máy Snes (hay giả lập Snes) cho biết chính xác tên, loại Rom, checksum của từng Rom khác nhau.

Header thật sự của Rom nằm ở địa chỉ $FFB0 (tương đương với $7FB0 địa chỉ PC đối với LoRom và $FFB0 địa chỉ PC đối với HiRom). Địa chỉ PC là địa chỉ ta nhận thấy được khi mở Rom bằng Hex Editor trên PC.

Thành phần của Header theo thứ tự lần lượt như sau, bắt đầu từ $FFB0:

- 2 byte mã sản xuất do Nintendō quy định. Trường hợp là Rom tự viết thì có thể điền 2 số thập lục bất kỳ.
- 4 byte mã game. Có thể điền 4 chữ cái bất kỳ.
- 7 byte cố định $00.
- 1 byte mô tả kích thước Ram mở rộng. Điền $00 nếu Rom chỉ dùng phần Ram sẵn có của máy Snes như phần lớn trường hợp.
- 1 byte mô tả phiên bản đặc biệt. Phần này dành cho các mục đích đặc biệt như bảo vệ bản quyền. Thường để $00. Các số $01, $03, $05, $06, $07 tương đương với kích thước Ram mở rộng 16Kbit, 64Kbit, 256Kbit, 512Kbit, 1Mbit.
- 1 bye mô tả loại Catridge (Rom) để phân biệt Catridge này với Catridge khác cùng loại. Thường để $00.
- 21 byte mô tả tên Rom (game) chẳng hạn Final_Fantasy_VIII_FIN hay FIRE_EMBLEM_FINAL_VER.
- 1 byte mô tả Map mode, chính là tốc độ của CPU. $20 cho mode 20, 2.68 MHZ (tốc đồ thường, còn gọi là Slow Rom), $35 cho mode 25 (3.58 MHZ hay còn gọi là HiRom),...
- 1 byte mô tả kích thước Rom. $09 cho Rom có kích thước 3 ~4 M Bit, $0A cho kích thước 5~8 M Bit, $0B cho kích thước 9~16 M Bit, $0C cho kích thước 17~32 M Bit và $0D cho kích thước 33~64 M Bit.
- 1 byte cho kích thước Sram. $00 không dùng Sram, $01 cho 16K Bit, $03 cho 64K Bit, $05 cho 256K Bit, $06 cho 512K Bit, $07 cho 1M Bit.
- 1 byte mô tả thị trường bán của Rom. $00 là Nhật, $01 là Bắc Mỹ, $02 là EU,....
- 1 byte cố định là $33.
- 1 byte miêu tả số phiên bản của Rom.
- 2 byte kiểm tra bù: chính là phép toán NOT của checksum.
- 2 byte checksum. Tổng số byte trong Rom và AND với FFFF. Luôn có 2 byte kiểm tra bù + 2 byte checksum = FFFF.


1.5. Vector:

Vector là các Pointer chỉ đến các bộ ngắt (Interrupt). Chẳng hạn Vector Reset luôn nhảy đến địa chỉ bắt đầy định dạng Snes khi ta bật nguồn, load Rom hay sau khi nhấn nút Reset. Còn Vector NMI nhảy đến các địa chỉ thực hiện chức năng do người dùng định nghĩa mỗi khi tia laser của màn hình quét từ trên xuống dưới, từ trái qua phải hết 224 dòng (kích thước màn hình Snes chuẩn: 256x224).

Mọi vector phải nằm trong địa chỉ $00:FFE0-$00:FFFF (tiếp nối ngay sau phần Header).

Mọi vector phải chỉ đến vùng dữ liệu trong bank $00. Máy Snes có 2 loại Vector: Native vector và Emulation vector. Mỗi loại gồm lần lượt:


- 4 byte Vector chưa dùng
- 2 byte Vector Coprocessor empowerment: dùng với các loại CPU phụ đi kèm.
- 2 byte Vector Program break: vector này nhảy tới địa chỉ xử lý do người dùng định nghĩa khi code BRK được thực thi. Vector này dành cho chức năng Debug16 và không mấy khi dùng đến trong thực tế.
- 2 byte Vector Abort: vector này hủy bỏ lệnh thực thi, bảo toàn trạng thái như trước khi thực thi.
- 2 byte Vector NMI: vector này nhảy đến địa chỉ thực thi chức năng riêng biệt do người dùng định nghĩa vào mỗi kỳ V-blank. Chẳng hạn tại mỗi kỳ V-blank (kết thúc chu kỳ quét của tia laser đi hết chiều dọc màn hình) sẽ kiểm tra nút Pause có nhấn hay không, nếu có thì cho dừng mọi hình ảnh trên màn hình.
- 2 byte Vector Reset: vector này nhảy đến địa chỉ thực thi code đầu tiên của Snes khi khởi động hoặc sau khi nhấn nút Reset. Chú ý là Cpu sẽ nhảy đến địa chỉ do Vector Reset ở chế độ Emulation chỉ định.
- 2 byte Vector IRQ: vector này nhảy đến địa chỉ thực hiện chức năng do người dùng chỉ định vào mỗi chu kỳ nhất định. Chu kỳ này do người dùng định nghĩa qua các Register từ $4208 ~ $420A. Chu kỳ có thể bắt đầu vào đầu, giữa hay cuối mỗi dòng quét của tia laser trên màn hình, tùy vào giá trị của các Register này.

1.6. Format:

Các Register của Snes có đặc điểm là có thể ở trạng thái không ổn định (mang giá trị ngẫu nhiên) vào lúc đầu khi bật nguồn định. Điều này có thể dẫn tới nhiều hiện tượng không mong muốn như nhiễu hình, vỡ tiếng. Vì vậy cần phải định dạng các Vector này để tránh tình trạng trên. Quy trình gồm:

- Tắt màn hình (ghi giá trị 80/8F vào Register $2100)
- Cho các Register về giá trị $00.
- Format càng nhiều Register càng tốt, nhưng ít nhất phải bảo đảm từ $2101~$2133 và từ $4200~$420D.

Dưới đây là đoạn code đơn giản để format các Register tối thiểu này.

Mã:
format_routine:
    SEP #$20
    REP #$10
    LDA #$80
    STA $2100
    LDX #$0000
    LDA #$00
repeat_format:
    STA $2101,x
    INX
    CPX #$0033
    BNE repeat_format
    LDX #$0000
repeat_format2:
    STA $4200,x
    INX
    CPX #$000D
    BNE repeat_format2
    RTL

1.7. Lặp bất tận:

Đối với các ngôn ngữ bậc cao, luôn có ký hiệu kết thúc chương trình để máy dừng thực thi mệnh lệnh, trở về trạng thái "thả lỏng". Về thực chất, trạng thái "thả lỏng" này chính là vòng lặp bất tận bằng cách nhảy đến chính nó.
Tuy nhiên máy Snes không thể tự nhảy về trạng thái "thả lỏng" nếu không có sự can thiệp của người viết, dẫn đến crash chương trình sau khi thực hiện xong các code bên trên. Vì nó không hiểu cần phải làm gì tiếp theo nên ta cần phải hướng dẫn nó.


abc:
JMP abc

hoặc

def:
BRA def

đều cho kết quả nhảy đến chính nó, khiến máy luôn ở trạng thái hoạt động. Và đây cũng là trạng thái đúng khi kết thúc một chức năng, một chương trình.


1.8. Đổi màu màn hình:

Đây mới chính là "Hello World" của lập trình Snes. Việc đổi màu được thực hiện qua 2 Register:

- $2121: ghi giá trị vào đây một lần. Register này chỉ thứ tự của slot màu của layer Background.
- $2122: ghi giá trị vào đây hai lần. Tùy vào giá trị được ghi vào Register này mà màn hình sẽ có màu khác nhau. Ghi $00 hai lần vào $2122 sẽ cho màn hình màu đen, ghi $FF hai lần vào $2122 sẽ cho màn hình màu trắng.

Tuy nhiên trước khi có thể thấy màu sắc màn hình thay đổi thì cần phải "bật" nó lên vì màn hình đã bị "tắt" trong quá trình Format. Register quản lý bật tắt màn hình là:

- $2100. Các giá trị từ $00 đến $0F sẽ bật màn hình với 15 cấp độ sáng từ tối nhất ($00) đến sáng nhất ($0F).

Bài sau sẽ nói kỹ hơn về màu sắc và layer Background.
 

1658m6As

Nấm bình thường
Đọc tại nguồn:

http://yugisokubodai.blogspot.com/2017/10/asm2.html


(Tham khảo video trên và xem phần hướng dẫn bên dưới)

Hướng dẫn lập trình cho máy Snes bằng ngôn ngữ Assembly 65816


Bài 2: màu sắc, bối cảnh

2.1. Màu sắc

Máy Snes có khả năng hiển thị cao nhất là 256 màu, mỗi màu là 15 bit (2 byte). Do vậy, bảng palette màu của Snes chiếm 512 byte cho 256 slot (ngăn, ô chứa) chứa màu.
Định dạng màu của Snes là:
0BBBBBGG GGGRRRRR

(B: Blue, màu lam; G: Green, màu lục; R:Red, màu đỏ)
Bit thứ 15 luôn là 0. Như vậy có thể thấy mỗi màu cơ bản (lam, lục, đỏ) chỉ gồm 5 bit, có giá trị dao động từ thấp nhất (0 hay $00) là 0 đến cao nhất ( 11111 hay $1F) là 31. Màu RGB 24 bit (mỗi màu cơ bản chiếm 8 bit, hay 1 byte) thông thường có giá trị dao động từ 0 (0 hay $00) đến 255 (11111111 hay $FF).

Như vậy, để chuyển đổi từ màu RGB 24 bit sang định dạng màu BGR 15 bit của Snes thì áp dụng công thức:

R = R / 8 (vd: 17 / 8 = 2)
G = G / 8 (vd: 16 / 8 = 2)
B = B / 8 (vd: 14 / 8 = 1)
=========
Màu Snes = B x 1024 + G x 32 + R

Chẳng hạn, đổi màu trắng (255, 255, 255) 24 bit sang định dạng của Snes:

R = 255 / 8 = 31
G = 255 / 8 = 31
B = 255 / 8 = 31
================
Màu Snes = 31 x 1024 + 31 x 32 + 31 = 32767

Tương đương với giá trị $07FF.
Đoạn code sau đổi màu nền thành màu trắng.

STZ $2121 //slot màu 0, tức màu nền
LDA #$FF
STA $2122
LDA #$07
STA $2122


2.2. Bối cảnh (Background - BG):

Một khung hình Snes hiển thị gồm tối đa 4 lớp (Layer), mỗi lớp thể hiện một bối cảnh (Background) và 1 lớp thể hiện sprite. Chẳng hạn như hình ảnh trong game Mario dưới đây gồm: 3 lớp Background + 1 lớp sprite.


Gồm

Background 1

và Background 2

và Background 3

và cuối cùng là lớp sprite



Snes có 7 cách thể hiện các lớp bối cảnh này, hay còn gọi là 7 mode thể hiện. Số lượng lớp Background, số lượng màu cho từng lớp khác nhau tùy thuộc vào mode hiển thị. Cụ thể:

Mode # Màu BG
1 2 3 4
======---=---=---=---=
0 4 4 4 4
1 16 16 4 -
2 16 16 - -
3 256 16 - -
4 256 4 - -
5 16 4 - -
6 16 - - -
7 256 - - -

(Mode 0 có thể hiển thị cùng lúc 4 BG + 1 lớp sprite, mỗi BG thể hiện được 4 màu, Mode 1 thể hiện được cùng lúc 3 BG + 1 lớp sprite, trong đó 2 BG hiển thị 16 màu và 1 BG hiển thị 4 màu,...)
Mode 7 là mode hiển thị "nổi tiếng" nhất của Snes, với khả năng cho hình ảnh 3D, xoay bối cảnh, phóng to, thu nhỏ...


Mode hiển thị được đăng ký qua Register $2105.
Đây là Register 8 bit với nội dung như sau:

Bit 7 Kích thước Tile BG4 (0=8x8, 1=16x16)
Bit 6 Kích thước Tile BG3 (0=8x8, 1=16x16) ; (BgMode5: 8x8 thực chất là 16x8)
Bit 5 Kích thước Tile BG2 (0=8x8, 1=16x16) ; (BgMode6: cố định 16x8)
Bit 4 Kích thước Tile BG1 (0=8x8, 1=16x16) ; (BgMode7: cố định 8x8)
Bit 3 Mức độ ưu tiên của BG3 ở Mode 1 (0=thường, 1=ưu tiên)
Bit 2-0 Chế độ BG, cụ thể như bên dưới

Mode BG1 BG2 BG3 BG4
0 4 màu 4 màu 4 màu 4 màu
1 16 màu 16 màu 4 màu
2 16 màu 16 màu
3 256 màu 16 màu
4 256 màu 4 màu
5 16 màu 4 màu
6 16 màu
7 256 màu


Chẳng hạn, muốn thể hiện màn hình ở Mode 1 với kích thước BG1 là 16x16 pixel cho mỗi Tile thì cần ghi giá trị 00010001 vào $2105

LDA #$11
STA $2105

2.3. Video Ram:

Video Ram (gọi tắt là Vram) là vùng Ram chứa mọi dữ liệu để hiển thị lên màn hình. Địa chỉ Vram thuộc kiểu Word, tức tính bằng 2 byte một.
Vram được truy cập thông qua 2 Register:

- $2116, $2117: Register 16 bit này giữ địa chỉ của dữ liệu trong Vram.
- $2118, $2119: Register 16 bit này cho phép ghi dữ liệu vào Vram.

Chẳng hạn:

REP #$30 // đổi sang A, X, Y 16 bit
LDX #$0200
STX $2116
LDA #$DEAD
STX $2118

Sẽ ghi giá trị $ADDE vào địa chỉ Vram (200 x2) $400.

Ngoài ra, cách ghi dữ liệu vào Vram còn được định nghĩa qua giá trị của bit 7, bit 3~bit 0 ở Register $2115. (Bit 6~ bit 4 không sử dụng)

Bit 7:
- 0: địa chỉ Vram sẽ gia tăng sau khi dữ liệu được ghi vào $2118.
- 1: địa chỉ Vram sẽ gia tăng sau khi dữ liệu được ghi vào $2119.
Bit 3~bit 0:
0 1 0 0 | gia tăng 8 trong 32 lần
1 0 0 0 | gia tăng 8 trong 64 lần
1 1 0 0 | gia tăng 8 trong 128 lần
0 0 0 0 | gia tăng 1
0 0 0 1 | gia tăng 32
0 0 1 0 | gia tăng 64
0 0 1 1 | gia tăng 128

Chẳng hạn, ta có chuỗi FF-03-2E-1A ghi vào địa chỉ $00 trong Vram:

- Nếu bit 7=1, bit 3~bit 0 đều là 0 thì FF và 03 lần lượt được ghi vào $00 và $01, 2E và 1A lần lượt được ghi vào $02 và $03
- Nếu bit 7=1, bit 3~ bit 1 là 0001 thì FF và 03 được ghi vào $00 và $01, đến lượt 2E và 1A được ghi vào địa chỉ cách đó 32 byte. 32 tức $20, nhưng vì địa chỉ Vram là kiểu Word nên phải nhân đôi, tức 2 byte tiếp theo trong chuỗi là 2E và 1A sẽ được ghi vào địa chỉ $40.
- Nết bit 7=0, bit 3~1 là 0000 thì đầu tiên, FF được ghi qua $2118 vào $00, sau đó địa chỉ Vram được tăng 1 ($01 x 2=$02) nên 2E, 03 được lần lượt được ghi vào $02, $03.


Register $2118 chỉ có thể được truy cập (ghi) trong 2 khoảng thời gian duy nhất: Vblank và Forced blank. Khái niệm này sẽ được giải thích trong phần sau.

2.4. Tileset và Tilemap:

Có thể hình dung mọi thành phần đồ họa trên màn hình như các mảnh của trò ghép hình.

Bản thân "dữ liệu" các mảnh ghép được gọi là Tileset (hay còn gọi là Character data), còn "bản đồ" bố trí vị trí các mảnh này để tạo nên hình thù mong muốn được gọi là Tilemap.

Địa chỉ của Tileset của các BG được xác định qua Register $210B và $210C.
- Register $210B: bit 7~4: địa chỉ Vram của Tileset của BG2, bit 3~0: địa chỉ Vram của Tileset của BG1
- Register $210C:
bit 7~4: địa chỉ Vram của Tileset của BG4, bit 3~0: địa chỉ Vram của Tileset của BG3

Địa chỉ Vram của Tileset được tính bằng cách Left-shift (dời trái) 13 lần.
Chẳng hạn:

LDA #$03
STA $210B

Sẽ cho địa chỉ Tileset của BG1 tại $6000 trong Vram. (03 <<13 = 24756, hay $6000). Địa chỉ byte của nó là $3000.

Địa chỉ Tilemap của các BG trong Vram được xác định qua 4 Register từ $2107 ~ $210A (lần lượt từ BG1~BG5) theo định dạng:

aaaaaass

Trong đó 6 bit aaaaaa xác định địa chỉ, 2 bit ss chỉ kích thước Tilemap (số lượng Tile trong Tilemap). Cụ thể:
00=32x32 01=64x32
10=32x64 11=64x64

Địa chỉ Vram qua 6 bit được tính bằng cách left-shift (dời trái) 11 lần.
Chẳng hạn:

LDA #$20
STA $2107

Xác định Tilemap của BG1 có các tính chất sau:

- $20 = 00100000, có 001000 = 08 << 11 = 16384 = $4000. Tức địa chỉ Tilemap của BG1 này bắt đầu tại $4000 trong Vram, hay $2000 tính theo địa chỉ byte.
- $20 = 00100000, có 00 cho kích thước Tilemap là 32x32 Tile. Kích thước của mỗi Tile là bao nhiêu pixel phụ thuộc vào giá trị từ bit 7 ~ bit 4 của Register $2105 như đã nói ở phần trước.


2.5. Cụ thể về Tilemap:

Như đã đề cập, Tilemap chính là sơ đồ bố trí các mảng (mảnh) đồ họa để tạo nên hình ảnh trên màn hình. Mỗi mảng gồm 16 bit (2 byte) nên vì thế địa chỉ Vram là kiểu Word (2 byte). Nội dung các bit này là

vhopppcc cccccccc

Trong đó:
- Bit v cho phép xoay mảng (mảnh) theo chiều đứng
- Bit h cho phép xoay mảng (mảnh) theo chiều ngang
- Bit o: mức độ ưu tiên hiển thị của mảng
- 3 bit ppp: số thứ tự của slot (ngăn, ô chứa) màu. Tùy thuộc vào BG và Mode hiển thị, như đã đề cập ở phần trên.
- 10 bit cc cccccccc còn lại: số thứ tự của mảng


Chẳng hạn, nếu mảng có giá trị C002 (1100000000000010) trong Vram (do Snes dùng kiểu Little-endian nên hiển thị là 02C0) thì có các đặc tính:

- Xoay ngược chiều ngang và dọc
- Không được hiển thị ưu tiên (có thể bị Tile khác đè lên)
- Có palette màu như cách tính slot bên dưới
- Là Tile số 02 (0000000010)

Số thứ tự của palette của Tile thay đổi tùy thuộc vào chế độ hiển thị của BG. Cụ thể:

- Mode 0: vị trí của palette được tính bằng công thức ppp*4 + (BG#-1)*32
- Mode 1: vị trí của palette được tính: ppp*n màu
- Mode 2: vị trí của palette được tính: ppp*16
- Mode 3: BG1 cố định 256 màu, BG 2 có vị trí của palette được tính: ppp*16
- Mode 4: BG1 cố định 256 màu, BG 2 có vị trí của palette được tính: ppp*4
- Mode 5: vị trí của palette được tính: ppp*n màu
- Mode 6: duy nhất BG1 16 màu và vị trí của palette được tính: ppp*n màu
- Mode 7: 256 màu.



Như vậy, Tile có giá trị C402 (1100010000000010) đối với Mode 1, BG1 sẽ có palette màu bắt đầu từ slot số: 0001 = 1 * 16 = $10. Palette của mảng (Tile) mang giá trị C402 sẽ có 16 màu bắt đầu từ slot $10 đến slot $20. Trong mảng (Tile) đó, pixel nào có màu số mấy phụ thuộc vào giá trị dữ liệu trong Tileset, do người dùng tự quy định.
 

1658m6As

Nấm bình thường
Đọc tại nguồn:

https://yugisokubodai.blogspot.com/2017/11/asmlap-trinh-snes-bai-3.html


(Tham khảo video trên và xem phần hướng dẫn bên dưới)

Hướng dẫn lập trình cho máy Snes bằng ngôn ngữ Assembly 65816


Bài 3: Truyền dữ liệu đồ họa bằng DMA

3.1. Khái quát về DMA

Trong bài trước đã đề cập đến Register cho phép viết dữ liệu vào Vram là $2118. Tuy nhiên cách chuyển dữ liệu đồ họa vào Vram thông qua cổng $2118 có khuyết điểm:

- Mỗi lần chỉ ghi được 02 byte (16 bit)
- Chỉ có thể ghi trong khi tắt màn hình (Forced blank) hoặc trong thời gian V(ertical) blank

Do vậy, đối với những khối đồ họa có dung lượng lớn thì cần nhiều thời gian để cập nhật trong màn hình vì phải viết rất nhiều lần, ảnh hưởng tới hiệu suất của game. Để chuyển khối dữ liệu $400 (1024) byte vào Vram thì phải ghi $200 (512) lần, nếu mỗi lần ghi 02 byte.

Để khắc phục nhược điểm này, người ta dùng đến cơ chế DMA để chuyển dữ liệu. DMA là viết tắt chữ "Direct Memory Access" (truy cập bộ nhớ trực tiếp). Đây là chức năng mà rất nhiều loại máy tính có, chứ không riêng gì máy Super Famicom. Chức năng này cho phép đọc dữ liệu trực tiếp từ bộ nhớ (Rom, Ram) thay vì cần CPU đọc, ghi nhiều lần.
DMA là một chức năng quan trọng trong lập trình máy SFC (Snes). Khối dữ liệu $400 byte được đề cập bên trên được ghi vào Vram thông qua chức năng này chắc chỉ chiếm 1% của cái tích tắc.


3.2. Vblank:

Ở trên có đề cập đến Vblank. Vậy đó là gì?

Giống như việc ghi qua cổng $2118, chức năng DMA chỉ hoạt động trong khi màn hình được tắt hẳn (Force blank) hoặc trong thời gian Vblank.


Hình ảnh ta thấy trên màn hình Tivi thực chất là không liên tục, mà rời rạc. Sở dĩ mắt người thấy hình liền khối, liền mạch là do tính chất lưu ảnh của mắt. Hình ảnh được tạo ra bằng cách quét tia laser lên màn hình, từ trái sang phải, từ trên xuống dưới.

Hình ảnh mà ta thấy là được "vẽ" trong thời gian tia laser đang quét.

Khi tia laser được quét hết một đường ngang, màn hình sẽ tắt và tia laser chuyển xuống hàng dưới, bắt đầu quét từ trái sang phải và màn hình sáng. Quãng thời gian tắt màn hình này được gọi là Horizontal blank hay Hblank.

Quá trình trên lặp lại cho đến khi tia laser quét hết hàng cuối cùng của màn hình. Lúc này màn hình sẽ tắt, tia laser lại nhảy trở về vị trí xuất phát đầu tiên ở góc trên, bên trái. Quãng thời gian tắt màn hình này gọi là Vertical blank hay Vblank.


Màn hình Snes có độ phân giải tiêu chuẩn là 255x224 điểm ảnh. Có nghĩa là khi quét hết 255 điểm ảnh theo đường ngang thì sẽ phát sinh 1 Hblank, và khi quét hết 224 đường như trên sẽ phát sinh 1 Vblank. Tức cứ 255 Hblank sẽ có 1 Vblank.

Vblank là quãng thời gian an toàn để cập nhật hình ảnh lên màn hình, vì lúc này màn hình được tắt nên không có hình ảnh nào hiển thị. Cứ 1 giây có 60 lần Vblank, tần suất quá nhanh nên mắt người không thể nhìn thấy màn hình tắt, mà thấy hình ảnh liên tục.

Nếu mỗi 1 Vblank, ta cập nhật 1 hình ảnh thì sẽ có được hình 60 khung hình trong 1 giây. Nói cách khác là về lý thuyết, máy SFC có thể dựng được hình ảnh 60 fps.

3.3. Các Register cho DMA:

Trước khi có thể sử dụng chức năng DMA, ta phải bật cho phép NMI hoạt động. DMA là bộ ngắt NMI duy nhất của máy SFC. Việc bật/tắt NMI được quản lý ở Register $4200, bit 7. Nếu bit 7 của Register này = 1 thì NMI được cho phép, nếu bit 7 = 0 sẽ vô hiệu hóa NMI.

SEP #$20
LDA #$80
STA $4200

Khi 1 được ghi vào bit 7 của $4200 thì bit 7 của Register $4210 sẽ cho giá trị 1 nếu đang trong quãng thời gian Vblank, ngoài ra sẽ cho giá trị 0.
Máy SFC có tất cả 8 kênh chuyển DMA, được kích hoạt qua 8 bit của Register $420B. Nếu giá trị 01 được ghi vào $420B sẽ kích hoạt kênh số 0, nếu giá trị 02 được ghi vào đây sẽ kích hoạt kênh số 1,....

Ngoài ra còn có những Register sau xác định điều kiện của DMA:

- $43x0: bit 7 thiết lập chế độ đọc từ hay ghi vào Vram (0=ghi, 1=đọc); bit 6 chỉ dùng cho chức năng HDMA, sẽ đề cập trong bài sau; bit 5 không được dùng đến; bit 4~bit 3: tăng dần hoặc giảm dần địa chỉ dữ liệu khi đọc/ghi; bit 2~bit 0: chế độ chuyển. Đối với Vram thì dùng chế độ 01.
- $43x1: cổng giao tiếp. Ghi vào Vram qua cổng $2118 nên trong trường hợp này phải ghi giá trị $18 vào $43x1. Đối với trường hợp ghi palette màu (cổng $2122) thì ghi giá trị $22.
- $43x2, $43x3: địa chỉ dữ liệu nguồn (đọc từ Rom/Ram)
- $43x4: bank của dữ liệu nguồn.
- $43x5: kích thước khối dữ liệu.


Lưu ý: x chỉ kênh số tương ứng với giá trị được ghi ở $420B. Ngoài ra cũng cần xác định vị trí cần ghi ở Vram thông qua $2116 như đã đề cập trong bài trước. Khi giá trị được ghi vào $420B thì DMA sẽ bắt đầu, nếu như đang ở trong quãng thời gian Vblank. Nếu không đang ở trong Vblank thì cần phải đợi.

Ví dụ: cần chuyển khối dữ liệu có kích thước $800 byte ở địa chỉ $41:00FF trong Rom vào vị trí $6000 trong Vram.


SEP #$20 //Set Register A = 8 bit
REP #$10 //Reset Register X,Y= 16 bit
LDA #$01
STA $4310 //chế độ ghi vào Vram qua kênh Dma 1
LDA #$18
STA $4311 //ghi cổng $2118 vào kênh Dma 1
LDX #$00FF
STX $4312 //ghi địa chỉ dữ liệu vào kênh 1
LDA #$41
STA $4314 //ghi bank dữ liệu vào kênh 1
LDX #$0800
STX $4315 //ghi kích thước dữ liệu vào kênh 1
LDX #$3000
STX $2116
JSR đợi_vblank
LDA #$02
STA $420B //chuyển DMA bằng kênh 1

đợi_vblank:
LDA $4210
BIT #$80 //kiểm tra bit 7 của $4210
BEQ đợi_vblank //nếu bit 7 =0 (không đang trong vblank) thì lặp lại quá trình trên
RTS
 

Bình luận bằng Facebook

Cộng đồng Facebook của NintendoVN

Pokémon Center

Pokémon Center Việt Nam

Top