summaryrefslogtreecommitdiff
path: root/2_scroll_planes/main.c
diff options
context:
space:
mode:
Diffstat (limited to '2_scroll_planes/main.c')
-rw-r--r--2_scroll_planes/main.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/2_scroll_planes/main.c b/2_scroll_planes/main.c
new file mode 100644
index 0000000..421b6c3
--- /dev/null
+++ b/2_scroll_planes/main.c
@@ -0,0 +1,463 @@
+// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/313e16db9c8cdd0bcd0c98223b3d4245f921b31d/2_scroll_planes/scroll.asm
+
+#include <stdint.h>
+#include <stddef.h>
+
+//==============================================================
+// CONSTANTS
+//==============================================================
+// Defines names for commonly used values and addresses to make
+// the code more readable.
+//==============================================================
+
+// VDP port addresses
+#define VDP_DATA ((volatile uint16_t*)0x00C00000L)
+#define VDP_CONTROL ((volatile uint16_t*)0x00C00004L)
+
+// VDP commands
+#define VDP_CMD_VRAM_WRITE ((uint32_t)0x40000000L)
+#define VDP_CMD_CRAM_WRITE ((uint32_t)0xC0000000L)
+#define VDP_CMD_VSRAM_WRITE ((uint32_t)0x40000010L) // NEW to this demo - Vertical Scroll RAM address
+
+// VDP memory addresses
+// according to VDP registers 0x2, 0x4, and 0xD (see table above)
+#define VRAM_ADDR_TILES (0x0000)
+#define VRAM_ADDR_PLANE_A (0xC000)
+#define VRAM_ADDR_PLANE_B (0xE000)
+#define VRAM_ADDR_HSCROLL (0xFC00) // NEW to this demo - Horizonal Scroll table address
+
+// Screen width and height (in pixels)
+#define VDP_SCREEN_WIDTH (0x0140)
+#define VDP_SCREEN_HEIGHT (0x00F0)
+
+// The plane width and height (in tiles)
+// according to VDP register 0x10 (see table above)
+#define VDP_PLANE_WIDTH (0x40)
+#define VDP_PLANE_HEIGHT (0x20)
+
+// Hardware version address
+#define HARDWARE_VER_ADDRESS ((uint8_t*)0x00A10001L)
+
+// TMSS
+#define TMSS_ADDRESS ((uint32_t*)0x00A14000L)
+#define TMSS_SIGNATURE (('S' << 24) | ('E' << 16) | ('G' << 8) | 'A')
+
+// The size of a word and longword
+#define SIZE_WORD (2)
+#define SIZE_LONG (4)
+
+// The size of one palette (in bytes, words, and longwords)
+#define SIZE_PALETTE_B (0x20)
+#define SIZE_PALETTE_W (SIZE_PALETTE_B/SIZE_WORD)
+#define SIZE_PALETTE_L (SIZE_PALETTE_B/SIZE_LONG)
+
+// The size of one graphics tile (in bytes, words, and longwords)
+#define SIZE_TILE_B (0x20)
+#define SIZE_TILE_W (SIZE_TILE_B/SIZE_WORD)
+#define SIZE_TILE_L (SIZE_TILE_B/SIZE_LONG)
+
+// Text draw position (in tiles)
+#define TEXT_POS_X (0x02)
+#define TEXT_POS_Y (0x04)
+
+// Speed (in pixels per frame) to move our scroll planes
+#define PLANE_A_SCROLL_SPEED_X (0x2)
+#define PLANE_B_SCROLL_SPEED_Y (0x1)
+
+//==============================================================
+// MEMORY MAP
+//==============================================================
+// We need to store the current scroll values in RAM and update
+// them each frame. There are a few ways to create a memory map,
+// but the cleanest, simplest, and easiest to maintain method
+// uses the assembler's "RS" keywords. RSSET begins a new table of
+// offsets starting from any other offset (here we're starting at
+// 0x00FF0000, the start of RAM), and allows us to add named entries
+// of any size for the "variables". We can then read/write these
+// variables using the offsets' labels (see INT_VInterrupt for use
+// cases).
+//==============================================================
+#define RAM_PLANE_A_SCROLL_X ((uint16_t*)0x00FF0000) // 1 table entry of word size for plane A's X scroll
+#define RAM_PLANE_B_SCROLL_Y ((uint16_t*)0x00FF0002) // 1 table entry of word size for plane B's Y scroll
+
+// !! Be careful when adding any table entries of BYTE size, since
+// you'll need to start worrying about alignment. More of this in a
+// future demo.
+
+//==============================================================
+// TILE IDs
+//==============================================================
+// The indices of each tile above. Once the tiles have been
+// written to VRAM, the VDP refers to each tile by its index.
+//==============================================================
+#define TILE_ID_SPACE (0x0)
+#define TILE_ID_P (0x1)
+#define TILE_ID_L (0x2)
+#define TILE_ID_A (0x3)
+#define TILE_ID_N (0x4)
+#define TILE_ID_E (0x5)
+#define TILE_ID_B (0x6)
+#define TILE_COUNT (0x7) // Last entry is just the count
+
+static uint8_t vdp_registers[] = {
+ 0x14, // 0x00: H interrupt on, palettes on
+ 0x74, // 0x01: V interrupt on, display on, DMA on, Genesis mode on
+ 0x30, // 0x02: Pattern table for Scroll Plane A at VRAM 0xC000 (bits 3-5 = bits 13-15)
+ 0x00, // 0x03: Pattern table for Window Plane at VRAM 0x0000 (disabled) (bits 1-5 = bits 11-15)
+ 0x07, // 0x04: Pattern table for Scroll Plane B at VRAM 0xE000 (bits 0-2 = bits 11-15)
+ 0x78, // 0x05: Sprite table at VRAM 0xF000 (bits 0-6 = bits 9-15)
+ 0x00, // 0x06: Unused
+ 0x00, // 0x07: Background colour: bits 0-3 = colour, bits 4-5 = palette
+ 0x00, // 0x08: Unused
+ 0x00, // 0x09: Unused
+ 0x08, // 0x0A: Frequency of Horiz. interrupt in Rasters (number of lines travelled by the beam)
+ 0x00, // 0x0B: External interrupts off, V scroll fullscreen, H scroll fullscreen
+ 0x81, // 0x0C: Shadows and highlights off, interlace off, H40 mode (320 x 224 screen res)
+ 0x3F, // 0x0D: Horiz. scroll table at VRAM 0xFC00 (bits 0-5)
+ 0x00, // 0x0E: Unused
+ 0x02, // 0x0F: Autoincrement 2 bytes
+ 0x01, // 0x10: Scroll plane size: 64x32 tiles
+ 0x00, // 0x11: Window Plane X pos 0 left (pos in bits 0-4, left/right in bit 7)
+ 0x00, // 0x12: Window Plane Y pos 0 up (pos in bits 0-4, up/down in bit 7)
+ 0xFF, // 0x13: DMA length lo byte
+ 0xFF, // 0x14: DMA length hi byte
+ 0x00, // 0x15: DMA source address lo byte
+ 0x00, // 0x16: DMA source address mid byte
+ 0x80, // 0x17: DMA source address hi byte, memory-to-VRAM mode (bits 6-7)
+};
+
+//==============================================================
+// PALETTE
+//==============================================================
+// A single colour palette (16 colours) we'll be using to draw text.
+// Colour #0 is always transparent, no matter what colour value
+// you specify.
+// We only use white (colour 2) and transparent (colour 0) in this
+// demo, the rest are just examples.
+//==============================================================
+// Each colour is in binary format 0000 BBB0 GGG0 RRR0,
+// so 0x0000 is black, 0x0EEE is white (NOT 0x0FFF, since the
+// bottom bit is discarded), 0x000E is red, 0x00E0 is green, and
+// 0x0E00 is blue.
+//==============================================================
+static uint16_t palette[SIZE_PALETTE_W] = {
+ 0x0000, // Colour 0 = Transparent
+ 0x0000, // Colour 1 = Black
+ 0x0EEE, // Colour 2 = White
+ 0x000E, // Colour 3 = Red
+ 0x00E0, // Colour 4 = Blue
+ 0x0E00, // Colour 5 = Green
+ 0x0E0E, // Colour 6 = Pink
+ 0x0000, // Leave the rest black...
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+};
+
+//==============================================================
+// GRAPHICS TILES
+//==============================================================
+// The 8x8 pixel graphics tiles that describe the font.
+// We only need to specify glyphs for "PLANE B" since the A is reusable.
+// 'SPACE' is first, which is unneccessary but it's a good teaching tool for
+// why we leave the first tile in memory blank (try changing it
+// and see what happens!).
+//==============================================================
+// 0 = transparent pixel
+// 2 = colour 'white' in our palette (see palette above)
+//==============================================================
+// Change all of the 2's to 3, 4 or 5 to draw the text in red, blue
+// or green (see the palette above).
+//==============================================================
+static uint32_t characters[TILE_COUNT * SIZE_TILE_L] = {
+ // Space
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ 0x00000000L,
+ // P
+ 0x22222200L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222200L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x00000000L,
+ // L
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22222220L,
+ 0x00000000L,
+ // A
+ 0x22222220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x00000000L,
+ // N
+ 0x22000220L,
+ 0x22000220L,
+ 0x22200220L,
+ 0x22220220L,
+ 0x22022220L,
+ 0x22002220L,
+ 0x22000220L,
+ 0x00000000L,
+ // E
+ 0x22222220L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22222220L,
+ 0x22000000L,
+ 0x22000000L,
+ 0x22222220L,
+ 0x00000000L,
+ // B
+ 0x22222200L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222200L,
+ 0x22000220L,
+ 0x22000220L,
+ 0x22222200L,
+ 0x00000000L,
+};
+
+//==============================================================
+// VRAM WRITE MACROS
+//==============================================================
+// Some utility macros to help generate addresses and commands for
+// writing data to video memory, since they're tricky (and
+// error prone) to calculate manually.
+// The resulting command and address is written to the VDP's
+// control port, ready to accept data in the data port.
+//==============================================================
+
+// Set the VRAM (video RAM) address to write to next
+static inline void SetVRAMWrite(uint16_t addr)
+{
+ *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_VRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
+}
+
+// Set the CRAM (colour RAM) address to write to next
+static inline void SetCRAMWrite(uint16_t addr)
+{
+ *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_CRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
+}
+
+// Set the VSRAM (vertical scroll RAM) address to write to next
+static inline void SetVSRAMWrite(uint16_t addr)
+{
+ *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_VSRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
+}
+
+static void VDP_WriteTMSS(void)
+{
+ // The TMSS (Trademark Security System) locks up the VDP if we don't
+ // write the string 'SEGA' to a special address. This was to discourage
+ // unlicensed developers, since doing this displays the "LICENSED BY SEGA
+ // ENTERPRISES LTD" message to screen (on Mega Drive models 1 and higher).
+ //
+ // First, we need to check if we're running on a model 1+, then write
+ // 'SEGA' to hardware address 0xA14000.
+ const uint8_t ver = (*HARDWARE_VER_ADDRESS) & 0x0f;
+ if (ver != 0) {
+ *TMSS_ADDRESS = TMSS_SIGNATURE;
+ }
+
+ // Check VDP
+ // Read VDP status register (hangs if no access)
+ *VDP_CONTROL;
+}
+
+static void VDP_LoadRegisters(void)
+{
+ // To initialise the VDP, we write all of its initial register values from
+ // the table at the top of the file, using a loop.
+ //
+ // To write a register, we write a word to the control port.
+ // The top bit must be set to 1 (so 0x8000), bits 8-12 specify the register
+ // number to write to, and the bottom byte is the value to set.
+ //
+ // In binary:
+ // 100X XXXX YYYY YYYY
+ // X = register number
+ // Y = value to write
+
+ // Set VDP registers
+ for (size_t i = 0; i < sizeof(vdp_registers) / sizeof(*vdp_registers); i++) {
+ const uint16_t cmd = 0x8000; // 'Set register 0' command
+ const uint16_t reg_num = i << 8;
+ *VDP_CONTROL = cmd | reg_num | vdp_registers[i];
+ }
+}
+
+int main(void)
+{
+ //==============================================================
+ // Initialise the Mega Drive
+ //==============================================================
+
+ // Write the TMSS signature (if a model 1+ Mega Drive)
+ VDP_WriteTMSS();
+
+ // Load the initial VDP registers
+ VDP_LoadRegisters();
+
+ //==============================================================
+ // Clear VRAM (video memory)
+ //==============================================================
+
+ // Setup the VDP to write to VRAM address 0x0000 (start of VRAM)
+ SetVRAMWrite(0x0000);
+
+ // Write 0's across all of VRAM
+ const size_t count = (0x00010000 / SIZE_WORD); // Loop counter = 64kb, in words
+ for (size_t i = 0; i < count; i++) {
+ *VDP_DATA = 0x0; // Write a 0x0000 (word size) to VRAM
+ }
+
+ //==============================================================
+ // Write the palette to CRAM (colour memory)
+ //==============================================================
+
+ // Setup the VDP to write to CRAM address 0x0000 (first palette)
+ SetCRAMWrite(0x0000);
+
+ // Write the palette to CRAM
+ for (size_t i = 0; i < SIZE_PALETTE_W; i++) {
+ *VDP_DATA = palette[i]; // Write palette entry
+ }
+
+ //==============================================================
+ // Write the font tiles to VRAM
+ //==============================================================
+
+ // Setup the VDP to write to VRAM address 0x0000 (the address of the first graphics tile, index 0)
+ SetVRAMWrite(VRAM_ADDR_TILES);
+
+ // Write the font glyph tiles to VRAM
+ for (size_t i = 0; i < TILE_COUNT * SIZE_TILE_L; i++) {
+ *(volatile uint32_t*)VDP_DATA = characters[i]; // Write palette entry
+ }
+
+ //==============================================================
+ // Write "PLANE A" and "PLANE B" text to Plane A and B
+ //==============================================================
+
+ // See the Hello World sample for what's going on here
+
+ // Write "PLANE A" font tile IDs to Plane A
+ SetVRAMWrite(VRAM_ADDR_PLANE_A + (((TEXT_POS_Y * VDP_PLANE_WIDTH) + TEXT_POS_X) * SIZE_WORD));
+ *VDP_DATA = TILE_ID_P;
+ *VDP_DATA = TILE_ID_L;
+ *VDP_DATA = TILE_ID_A;
+ *VDP_DATA = TILE_ID_N;
+ *VDP_DATA = TILE_ID_E;
+ *VDP_DATA = TILE_ID_SPACE;
+ *VDP_DATA = TILE_ID_A;
+
+ // Write "PLANE B" font tile IDs to Plane B
+ SetVRAMWrite(VRAM_ADDR_PLANE_B + (((TEXT_POS_Y * VDP_PLANE_WIDTH) + TEXT_POS_X) * SIZE_WORD));
+ *VDP_DATA = TILE_ID_P;
+ *VDP_DATA = TILE_ID_L;
+ *VDP_DATA = TILE_ID_A;
+ *VDP_DATA = TILE_ID_N;
+ *VDP_DATA = TILE_ID_E;
+ *VDP_DATA = TILE_ID_SPACE;
+ *VDP_DATA = TILE_ID_B;
+
+ //==============================================================
+ // Intitialise variables in RAM
+ //==============================================================
+ *RAM_PLANE_A_SCROLL_X = 0;
+ *RAM_PLANE_B_SCROLL_Y = 0;
+
+ //==============================================================
+ // Initialise status register and set interrupt level.
+ // This begins firing vertical and horizontal interrupts.
+ //
+ // Since the vinterrupt does something meaningful in this
+ // demo, we start this AFTER setting up the VDP to draw and
+ // intialising the variables in RAM.
+ //==============================================================
+ asm inline volatile (" move.w #0x2300, %sr");
+
+ // Finished!
+
+ //==============================================================
+ // Loop forever
+ //==============================================================
+ // This loops forever, effectively ending our main routine,
+ // but the VDP will continue to run of its own accord and
+ // will still fire vertical and horizontal interrupts (which is
+ // where our update code is), so the demo continues to run.
+ //
+ // For a game, it would be better to use this loop for processing
+ // input and game code, and wait here until next vblank before
+ // looping again. We only use vinterrupt for updates in this demo
+ // for simplicity (because we don't yet have any timing code).
+ while (1);
+}
+
+//==============================================================
+// CODE ENTRY POINT
+//==============================================================
+// The "main()" function. Your code starts here. Once the CPU
+// has finished initialising, it will jump to this entry point
+// (specified in the vector table at the top of the file).
+//==============================================================
+__attribute__((interrupt)) void CPU_EntryPoint(void)
+{
+ main();
+}
+
+__attribute__((interrupt)) void INT_VInterrupt(void)
+{
+ // Fetch the current scroll values from RAM.
+ //
+ // These labels are just named offsets from 0x00FF0000 (start of RAM)
+ // so we can read from/write to them like any other address.
+ //
+ // Animate them
+ //
+ // Store updated scroll values back into RAM
+ *RAM_PLANE_A_SCROLL_X += PLANE_A_SCROLL_SPEED_X;
+ *RAM_PLANE_B_SCROLL_Y += PLANE_B_SCROLL_SPEED_Y;
+
+ // VDP register 0xB specifies how the planes scroll. It's set to per-page mode
+ // in this demo, so we only need to write one word value to scroll an entire plane
+ // in a particular direction.
+ //
+ // There are two areas of memory used for scroll values - horizontal scroll
+ // is within VRAM (location is specified by VDP register 0xD), and
+ // vertical scroll has its own separate memory (VSRAM), and therefore has
+ // its own macro for setting the address (it has its own VDP command).
+
+ // Write Plane A's H-scroll value to horizontal scroll memory (in VRAM).
+ // Plane A's horizontal page scroll value is at VRAM 0xFC00, Plane B's is at 0xFC02.
+ SetVRAMWrite(VRAM_ADDR_HSCROLL);
+ *VDP_DATA = *RAM_PLANE_A_SCROLL_X;
+
+ // Write Plane B's V-scroll value to vertical scroll memory (VSRAM).
+ // Plane A's vertical page scroll value is at VSRAM 0x0000, Plane B's is at 0x0002.
+ SetVSRAMWrite(0x0000 + SIZE_WORD);
+ *VDP_DATA = *RAM_PLANE_B_SCROLL_Y;
+}