// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/313e16db9c8cdd0bcd0c98223b3d4245f921b31d/2_scroll_planes/scroll.asm #include #include //============================================================== // 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++) { // Write tile line (4 bytes per line), and post-increment address *(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; }