summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2023-05-06 23:23:59 +0300
committerOxore <oxore@protonmail.com>2023-05-06 23:23:59 +0300
commitd39090b6a94b7c1700d190a1bb63e85ff1c1f4ad (patch)
tree3c1702dd04aad5ba79b243422c75a21a26a4bc16
parentebcd974b8e18a2033c94cb0050dab73964d6c19b (diff)
Add 2_scroll_planes demo
-rw-r--r--1_hello_world/Makefile6
-rw-r--r--1_hello_world/main.c2
-rw-r--r--2_scroll_planes/Makefile25
-rw-r--r--2_scroll_planes/_rom.ld17
-rw-r--r--2_scroll_planes/_sram.ld26
-rw-r--r--2_scroll_planes/m68k.ld4
-rw-r--r--2_scroll_planes/main.c463
-rw-r--r--2_scroll_planes/startup.c130
-rw-r--r--Readme.md11
9 files changed, 679 insertions, 5 deletions
diff --git a/1_hello_world/Makefile b/1_hello_world/Makefile
index 0a4b3a2..da61f6a 100644
--- a/1_hello_world/Makefile
+++ b/1_hello_world/Makefile
@@ -8,12 +8,12 @@ _LDFLAGS = $(LDFLAGS) $(ARCHFLAGS) $(OPTFLAGS) $(addprefix -T,$(LDSCRIPTS)) -Wl,
OBJECTS=startup.o main.o
.PHONY: all
-all: hellorom.bin
+all: hello.bin
%.bin: %.elf Makefile
m68k-none-elf-objcopy -O binary $< $@
-hellorom.elf: $(OBJECTS) $(LDSCRIPTS) Makefile
+hello.elf: $(OBJECTS) $(LDSCRIPTS) Makefile
m68k-none-elf-gcc -o $@ $(OBJECTS) $(_LDFLAGS)
OBJECTS: Makefile
@@ -22,4 +22,4 @@ OBJECTS: Makefile
m68k-none-elf-gcc -c $< -o $@ $(_CFLAGS)
clean:
- rm -rfv hellorom.elf hellorom.bin $(OBJECTS)
+ rm -rfv hello.elf hello.bin $(OBJECTS)
diff --git a/1_hello_world/main.c b/1_hello_world/main.c
index 1a30562..811035b 100644
--- a/1_hello_world/main.c
+++ b/1_hello_world/main.c
@@ -1,4 +1,4 @@
-// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/master/1_hello_world/hello.asm
+// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/313e16db9c8cdd0bcd0c98223b3d4245f921b31d/1_hello_world/hello.asm
#include <stdint.h>
#include <stddef.h>
diff --git a/2_scroll_planes/Makefile b/2_scroll_planes/Makefile
new file mode 100644
index 0000000..75c1747
--- /dev/null
+++ b/2_scroll_planes/Makefile
@@ -0,0 +1,25 @@
+WARNFLAGS = -Wall -Wextra -pedantic -Wshadow -Wlogical-op
+OPTFLAGS = -Os
+ARCHFLAGS = -m68000
+_CFLAGS = $(CFLAGS) $(WARNFLAGS) $(ARCHFLAGS) $(OPTFLAGS) -g3 -ffunction-sections -fdata-sections
+LDSCRIPTS = m68k.ld _sram.ld _rom.ld
+_LDFLAGS = $(LDFLAGS) $(ARCHFLAGS) $(OPTFLAGS) $(addprefix -T,$(LDSCRIPTS)) -Wl,--gc-sections -Wl,--build-id=none -nostartfiles
+
+OBJECTS=startup.o main.o
+
+.PHONY: all
+all: scroll.bin
+
+%.bin: %.elf Makefile
+ m68k-none-elf-objcopy -O binary $< $@
+
+scroll.elf: $(OBJECTS) $(LDSCRIPTS) Makefile
+ m68k-none-elf-gcc -o $@ $(OBJECTS) $(_LDFLAGS)
+
+OBJECTS: Makefile
+
+%.o: %.c Makefile
+ m68k-none-elf-gcc -c $< -o $@ $(_CFLAGS)
+
+clean:
+ rm -rfv scroll.elf scroll.bin $(OBJECTS)
diff --git a/2_scroll_planes/_rom.ld b/2_scroll_planes/_rom.ld
new file mode 100644
index 0000000..916d9d9
--- /dev/null
+++ b/2_scroll_planes/_rom.ld
@@ -0,0 +1,17 @@
+SECTIONS {
+ __rom_start = ORIGIN(ROM);
+ . = ORIGIN(ROM);
+ .text : {
+ KEEP(*(.stack))
+ KEEP(*(.vectors))
+ KEEP(*(.smd_header))
+ KEEP(*(.text))
+ . = ALIGN(4);
+ *(.text*)
+ . = ALIGN(4);
+ KEEP(*(.rodata))
+ *(.rodata*)
+ . = ALIGN(4);
+ } >ROM
+ __rom_end = .;
+}
diff --git a/2_scroll_planes/_sram.ld b/2_scroll_planes/_sram.ld
new file mode 100644
index 0000000..e9fcc24
--- /dev/null
+++ b/2_scroll_planes/_sram.ld
@@ -0,0 +1,26 @@
+SECTIONS {
+ __stacktop = ORIGIN(SRAM) + 0xe000;
+ __data_load = LOADADDR(.data);
+ . = ORIGIN(SRAM);
+
+ .data ALIGN(4) : {
+ __data_start = .;
+ *(.data)
+ *(.data*)
+ . = ALIGN(4);
+ __data_end = .;
+ } >SRAM AT >ROM
+
+ .bss ALIGN(4) (NOLOAD) : {
+ __bss_start = .;
+ *(.bss)
+ *(.bss*)
+ . = ALIGN(4);
+ __bss_end = .;
+ *(.noinit)
+ *(.noinit*)
+ } >SRAM
+
+ . = ALIGN(4);
+ __heap_start = .;
+}
diff --git a/2_scroll_planes/m68k.ld b/2_scroll_planes/m68k.ld
new file mode 100644
index 0000000..dcc5257
--- /dev/null
+++ b/2_scroll_planes/m68k.ld
@@ -0,0 +1,4 @@
+MEMORY {
+ ROM(rx) : ORIGIN = 0x00000000, LENGTH = 4M
+ SRAM(rwx) : ORIGIN = 0x00ff0000, LENGTH = 64K
+}
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;
+}
diff --git a/2_scroll_planes/startup.c b/2_scroll_planes/startup.c
new file mode 100644
index 0000000..f2574cd
--- /dev/null
+++ b/2_scroll_planes/startup.c
@@ -0,0 +1,130 @@
+typedef void (*ptr_func_t)();
+
+struct smd_header {
+ char console_name[16];
+ char copyright[16];
+ char domestic_name[48];
+ char international_name[48];
+ char version[14];
+ unsigned short checksum;
+ char io_support[16];
+ unsigned long rom_start;
+ unsigned long rom_end;
+ unsigned long ram_start;
+ unsigned long ram_end;
+ unsigned long sram_enabled;
+ unsigned long unused1;
+ unsigned long sram_start;
+ unsigned long sram_end;
+ unsigned long unused2;
+ unsigned long unused3;
+ char notes[40];
+ char country_codes[16];
+};
+
+void INT_Null(void) __attribute__((weak, interrupt));
+void CPU_EntryPoint(void) __attribute__((weak, alias ("INT_Null")));
+void INT_HInterrupt(void) __attribute__((weak, alias ("INT_Null")));
+void INT_VInterrupt(void) __attribute__((weak, alias ("INT_Null")));
+void CPU_Exception(void) __attribute__((weak, interrupt));
+
+extern unsigned __stacktop;
+extern unsigned __rom_start;
+extern unsigned __rom_end;
+
+__attribute__((section(".stack"), used)) unsigned *__stack_init = &__stacktop;
+
+__attribute__((section(".vectors"), used)) ptr_func_t __isr_vectors[] = {
+ CPU_EntryPoint,
+ CPU_Exception,
+ CPU_Exception,
+ CPU_Exception,
+ CPU_Exception,
+ CPU_Exception,
+ CPU_Exception,
+ CPU_Exception,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_HInterrupt,
+ INT_Null,
+ INT_VInterrupt,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+ INT_Null,
+};
+
+__attribute__((section(".smd_header"), used)) struct smd_header __smd_header = {
+ .console_name = "SEGA MEGA DRIVE ",
+ .copyright = "BIGEVILCORP. ",
+ .domestic_name = "HELLO WORLD ",
+ .international_name = "HELLO WORLD ",
+ .version = "GM XXXXXXXX-XX",
+ .checksum = 0x0000,
+ .io_support = "J ",
+ .rom_start = (unsigned long)&__rom_start,
+ .rom_end = (unsigned long)&__rom_end-1,
+ .ram_start = 0xff0000,
+ .ram_end = 0xffffff,
+ .sram_enabled = 0x00000000,
+ .unused1 = 0x00000000,
+ .sram_start = 0x00000000,
+ .sram_end = 0x00000000,
+ .unused2 = 0x00000000,
+ .unused3 = 0x00000000,
+ .notes = " ",
+ .country_codes = " E ",
+};
+
+void INT_Null(void) {}
+
+void CPU_Exception(void)
+{
+ asm inline volatile (" stop #2700");
+}
diff --git a/Readme.md b/Readme.md
index a547825..ee69d0d 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,5 +1,14 @@
# SEGA Mega Drive/Genesis Samples in C
-An attempt to rewrite [gh:BigEvilCorporation/megadrive_samples](https://github.com/BigEvilCorporation/megadrive_samples) in C.
+An attempt to rewrite
+[gh:BigEvilCorporation/megadrive_samples](https://github.com/BigEvilCorporation/megadrive_samples)
+in C. There is a trade-off of showing some C's best practices of bare metal
+projects and keeping the code as close as possible to original assembly code, at
+least by structure and sequence.
Inspired by [gh:cortexm/baremetal](https://github.com/cortexm/baremetal).
+
+Comments are copy-pasted in most cases. Sometimes they tell about assembler
+language specific things, but there is no assembly code at all - only C and ld
+scripts. Maybe at some point in the future I will rewrite them according to C
+code, but IMHO some of them are so much redundant and should be removed.