summaryrefslogtreecommitdiff
path: root/3_sprites/main.c
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2023-05-07 15:33:25 +0300
committerOxore <oxore@protonmail.com>2023-05-07 15:33:25 +0300
commit3029ace7b09eda3e0c3586742ee80c54e4dddc55 (patch)
treea50910600cf67e9eafed2f98ebce6bb0ec65d3ae /3_sprites/main.c
parentd39090b6a94b7c1700d190a1bb63e85ff1c1f4ad (diff)
Add 3_sprites demo
Diffstat (limited to '3_sprites/main.c')
-rw-r--r--3_sprites/main.c606
1 files changed, 606 insertions, 0 deletions
diff --git a/3_sprites/main.c b/3_sprites/main.c
new file mode 100644
index 0000000..106a8a4
--- /dev/null
+++ b/3_sprites/main.c
@@ -0,0 +1,606 @@
+// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/313e16db9c8cdd0bcd0c98223b3d4245f921b31d/3_sprites/sprites.asm
+
+//==============================================================
+// SEGA MEGA DRIVE/GENESIS - DEMO 3 - SPRITES SAMPLE
+//==============================================================
+// by Big Evil Corporation
+//==============================================================
+
+// A small, discreet, and complete sprites sample, with a healthy
+// dose of comments and explanations for beginners.
+// Runs on genuine hardware, and (hopefully) all emulators.
+//
+// I recommend reading and understanding the Scroll Planes
+// sample first.
+//
+// To assemble this program with ASM68K.EXE:
+// ASM68K.EXE /p sprites.asm,sprites.bin,sprites.map,sprites.lst
+//
+// To assemble this program with SNASM68K.EXE:
+// SNASM68K.EXE /p sprites.asm,sprites.map,sprites.lst,sprites.bin
+//
+// sprites.asm = this source file
+// sprites.bin = the binary file, fire this up in your emulator!
+// sprites.lst = listing file, shows assembled addresses alongside
+// your source code, open in a text editor
+// sprites.map = symbol map file for linking (unused)
+
+//==============================================================
+
+#include <stdint.h>
+#include <stddef.h>
+
+//==============================================================
+// CONSTANTS
+//==============================================================
+
+// 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)
+
+// 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_SPRITE_TABLE (0xF000) // NEW in this demo - the Sprite Attribute Table (SAT)
+#define VRAM_ADDR_HSCROLL (0xFC00)
+
+// 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)
+
+// The size of the sprite plane (512x512 pixels)
+//
+// With only a 320x240 display size, a lot of this
+// is off screen, which is useful for hiding sprites
+// when not needed (saves needing to adjust the linked
+// list in the attribute table).
+#define VDP_SPRITE_PLANE_WIDTH (0x0200)
+#define VDP_SPRITE_PLANE_HEIGHT (0x0200)
+
+// The sprite border (invisible area left + top) size
+//
+// The sprite plane is 512x512 pixels, but is offset by
+// -128 pixels in both X and Y directions. To see a sprite
+// on screen at 0,0 we need to offset its position by
+// this border.
+#define VDP_SPRITE_BORDER_X (0x80)
+#define VDP_SPRITE_BORDER_Y (0x80)
+
+// 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)
+
+// Sprite initial draw positions (in pixels)
+#define SPRITE_1_START_POS_X (VDP_SPRITE_BORDER_X)
+#define SPRITE_1_START_POS_Y (VDP_SPRITE_BORDER_Y)
+#define SPRITE_2_START_POS_X (VDP_SPRITE_BORDER_X + 0x0040)
+#define SPRITE_2_START_POS_Y (VDP_SPRITE_BORDER_Y + 0x0020)
+
+// Speed (in pixels per frame) to move our sprites
+#define SPRITE_1_MOVE_SPEED_X (0x1)
+#define SPRITE_1_MOVE_SPEED_Y (0x1)
+#define SPRITE_2_MOVE_SPEED_X (0x2)
+#define SPRITE_2_MOVE_SPEED_Y (0x0)
+
+//==============================================================
+// MEMORY MAP
+//==============================================================
+// We need to store the current sprite positions 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_SPRITE_1_POS_X ((uint16_t*)0x00FF0000) // 1 table entry of word size for sprite 1's X pos
+#define RAM_SPRITE_1_POS_Y ((uint16_t*)0x00FF0002) // 1 table entry of word size for sprite 1's Y pos
+#define RAM_SPRITE_2_POS_X ((uint16_t*)0x00FF0004) // 1 table entry of word size for sprite 2's X pos
+#define RAM_SPRITE_2_POS_Y ((uint16_t*)0x00FF0006) // 1 table entry of word size for sprite 2's Y pos
+
+// !! 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.
+
+// Number of palettes to write to CRAM
+#define PALETTE_COUNT (0x2)
+
+//==============================================================
+// TILE IDs
+//==============================================================
+// The indices of the first tile in each sprite. We only need
+// to tell the sprite table where to find the starting tile of
+// each sprite, so we don't bother keeping track of every tile
+// index.
+//
+// Note we still leave the first tile blank (planes A and B are
+// filled with tile 0) so we'll be uploading our sprite tiles
+// from index 1.
+//
+// See bottom of the file for the sprite tiles themselves.
+//==============================================================
+#define TILE_ID_BLANK (0x00) // The blank tile at index 0
+#define TILE_ID_SPRITE_1 (0x01) // Sprite 1 index (4 tiles)
+#define TILE_ID_SPRITE_2 (0x05) // Sprite 2 index (12 tiles)
+
+// Total number of tiles in the sprites to upload to VRAM
+#define TILE_COUNT (0x11) // Total tiles = 16
+
+//==============================================================
+// INITIAL VDP REGISTER VALUES
+//==============================================================
+// In this demo, we're particularly interested in register 0x5,
+// which specifies the address of the Sprite Attribute Table
+// (SAT) within VRAM. Here it's set to 0xF000.
+//==============================================================
+static const 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 Attribute 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 per-page, H scroll per-page
+ 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
+//==============================================================
+// In this demo we'll be using one palette per sprite,
+// so we've added a palette count to upload the correct number
+// of entries.
+//==============================================================
+
+static const uint16_t palette[SIZE_PALETTE_W * PALETTE_COUNT] = {
+ // Palette for sprite 1
+ 0x0000,
+ 0x0020,
+ 0x0EEE,
+ 0x00AC,
+ 0x02EA,
+ 0x00EE,
+ 0x0008,
+ 0x000C,
+ 0x000A,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ 0x0000,
+ // Palette for sprite 2
+ 0x0000,
+ 0x0004,
+ 0x0226,
+ 0x0040,
+ 0x0446,
+ 0x0262,
+ 0x0662,
+ 0x004A,
+ 0x0468,
+ 0x0882,
+ 0x006C,
+ 0x0202,
+ 0x04A0,
+ 0x0AC2,
+ 0x06AE,
+ 0x02EC,
+};
+
+//==============================================================
+// SPRITE TILES
+//==============================================================
+// The sprite graphics tiles. Too big to paste in here, so we'll
+// include them from external files at the bottom of the ROM.
+//
+// If your tile data is in binary format rather than text, use
+// INCBIN instead of INCLUDE.
+//==============================================================
+
+extern const uint32_t sprite_tiles[TILE_COUNT * SIZE_TILE_L];
+
+//==============================================================
+// 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);
+}
+
+//==============================================================
+// SPRITE ATTRIBUTE MACRO
+//==============================================================
+// A macro to help build an entry in the Sprite Attribute
+// Table, since manipulating structures and bit twiddling isn't
+// the focus of this demo, and would make the code harder to
+// read.
+//==============================================================
+// Proper game implementations would make use of a local SAT
+// table in RAM and use DMA to transfer the table to VRAM each
+// frame (which also allows us to use RAM like a "stream" to write
+// this data more efficiently) but this is the best method for
+// teaching the basics first.
+//==============================================================
+// Each sprite attribute entry is in the following format:
+//
+// Y coordinate 1 word - the Y coordinate on the sprite plane
+// Dimensions bits 1 byte - bits describing the layout (1x1 tiles up to 4x4 tiles)
+// Linked list next 1 byte - the index of the next sprite to draw, or 0 if end of list
+// Prio/palette/flip 1 byte - the priority (bit 15), palette (bits 14-13),
+// v/h flip (bits 12 and 11), and top 3 bits of the tile ID
+// Tile ID bottom 1 byte - the bottom 8 bits of the tile ID
+// X coordinate 1 word - the X coordinate on the sprite plane
+//==============================================================
+
+// Writes a sprite attribute structure to 4 registers, ready to write to VRAM
+static struct SpriteStructure { uint16_t reg1, reg2, reg3, reg4; } BuildSpriteStructure(
+ uint16_t x_pos, // X pos on sprite plane
+ uint16_t y_pos, // Y pos on sprite plane
+ uint16_t dimension_bits, // Sprite tile dimensions (4 bits)
+ uint16_t next_id, // Next sprite index in linked list
+ uint16_t priority_bit, // Draw priority
+ uint16_t palette_id, // Palette index
+ uint16_t flip_x, // Flip horizontally
+ uint16_t flip_y, // Flip vertically
+ uint16_t tile_id) // First tile index
+{
+ return (struct SpriteStructure){
+ .reg1 = y_pos,
+ .reg2 = (dimension_bits << 8) | next_id,
+ .reg3 = (priority_bit << 14) | (palette_id << 13) | (flip_x << 11) | (flip_y << 10) |
+ tile_id,
+ .reg4 = x_pos,
+ };
+}
+
+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 palettes to CRAM
+ //
+ // This time we're writing multiple palettes, so multiply the word count
+ // by the palette count (and don't forget the -1 for the loop counter).
+ for (size_t i = 0; i < PALETTE_COUNT * SIZE_PALETTE_W; i++) {
+ *VDP_DATA = palette[i]; // Write palette entry
+ }
+
+ //==============================================================
+ // Write the sprite tiles to VRAM
+ //==============================================================
+
+ // Setup the VDP to write to VRAM address 0x0020 (the address of the first sprite tile, index 1)
+ //
+ // We need to leave the first tile blank (we cleared VRAM, so it should be all 0's) for
+ // planes A and B to display, so skip the first tile (offset address by size_tile_b).
+ SetVRAMWrite(VRAM_ADDR_TILES + SIZE_TILE_B);
+
+ // 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 = sprite_tiles[i];
+ }
+
+ //==============================================================
+ // Set up the Sprite Attribute Table (SAT)
+ //==============================================================
+
+ // The Sprite Attribute Table is a table of sprites to draw.
+ // Each entry in the table describes the first tile ID, the number
+ // of tiles to draw (and their layout), the X and Y position
+ // (on the 512x512 sprite plane), the palette to draw with, a
+ // priority flag, and X/Y flipping flags.
+ //
+ // Sprites can be layed out in these tile dimensions:
+ //
+ // 1x1 (1 tile) - 0000
+ // 1x2 (2 tiles) - 0001
+ // 1x3 (3 tiles) - 0010
+ // 1x4 (4 tiles) - 0011
+ // 2x1 (2 tiles) - 0100
+ // 2x2 (4 tiles) - 0101
+ // 2x3 (6 tiles) - 0110
+ // 2x4 (8 tiles) - 0111
+ // 3x1 (3 tiles) - 1000
+ // 3x2 (6 tiles) - 1001
+ // 3x3 (9 tiles) - 1010
+ // 3x4 (12 tiles)- 1011
+ // 4x1 (4 tiles) - 1100
+ // 4x2 (8 tiles) - 1101
+ // 4x3 (12 tiles)- 1110
+ // 4x4 (16 tiles)- 1111
+ //
+ // The tiles are layed out in COLUMN MAJOR, rather than planes A and B
+ // which are row major. Tiles within a sprite cannot be reused (since it
+ // only accepts a starting tile and a count/layout) so the whole sprite
+ // needs uploading to VRAM in one consecutive chunk, even if some tiles
+ // are duplicates.
+ //
+ // The X/Y flipping flags take the layout into account, you don't need
+ // to re-adjust the layout, position, or tile IDs to flip the entire
+ // sprite as a whole.
+ //
+ // There are 64 entries in the table, but the number of them drawn,
+ // and the order in which they're processed, is determined by a linked
+ // list. Each sprite entry has an index to the NEXT sprite to be drawn.
+ // If this index is 0, the list ends, and the VDP won't draw any more
+ // sprites this frame.
+
+ // Start writing to the sprite attribute table in VRAM
+ SetVRAMWrite(VRAM_ADDR_SPRITE_TABLE);
+
+ //==============================================================
+ // Set up sprite 1
+
+ // Write all values into registers first to make it easier. We
+ // write to VRAM one word at a time (auto-increment is set to 2
+ // in VDP register 0xF), so we'll assign each word to a register.
+ //
+ // Since bit twiddling and manipulating structures isn't the focus of
+ // this sample, we have a macro to simplify this part.
+
+ // Position: sprite_1_start_pos_x,sprite_1_start_pos_y
+ // Dimensions: 2x2 tiles (8 tiles total) = 0101 in binary (see table above)
+ // Next link: sprite index 1 is next to be processed
+ // Priority: 0
+ // Palette id: 0
+ // Flip X: 0
+ // Flip Y: 0
+ // Tile id: tile_id_sprite_1
+ const struct SpriteStructure sprite1 = BuildSpriteStructure(
+ SPRITE_1_START_POS_X, SPRITE_1_START_POS_Y, 5, 1, 0, 0, 0, 0, TILE_ID_SPRITE_1);
+
+ // Write the entire sprite attribute structure to the sprite table
+ *VDP_DATA = sprite1.reg1;
+ *VDP_DATA = sprite1.reg2;
+ *VDP_DATA = sprite1.reg3;
+ *VDP_DATA = sprite1.reg4;
+
+ //==============================================================
+ // Set up sprite 2
+
+ // Position: sprite_2_start_pos_x,sprite_2_start_pos_y
+ // Dimensions: 4x3 tiles (16 tiles total) = 1110 in binary (see table above)
+ // Next link: sprite index 0 (end of linked list)
+ // Priority: 0
+ // Palette id: 1
+ // Flip X: 0
+ // Flip Y: 0
+ // Tile id: tile_id_sprite_2
+ const struct SpriteStructure sprite2 = BuildSpriteStructure(
+ SPRITE_2_START_POS_X, SPRITE_2_START_POS_Y, 0xe, 0, 0, 1, 0, 0, TILE_ID_SPRITE_2);
+
+ // Write the entire sprite attribute structure to the sprite table
+ *VDP_DATA = sprite2.reg1;
+ *VDP_DATA = sprite2.reg2;
+ *VDP_DATA = sprite2.reg3;
+ *VDP_DATA = sprite2.reg4;
+
+ //==============================================================
+ // Intitialise variables in RAM
+ //==============================================================
+ *RAM_SPRITE_1_POS_X = SPRITE_1_START_POS_X;
+ *RAM_SPRITE_1_POS_Y = SPRITE_1_START_POS_Y;
+ *RAM_SPRITE_2_POS_X = SPRITE_2_START_POS_X;
+ *RAM_SPRITE_2_POS_Y = SPRITE_2_START_POS_Y;
+
+ //==============================================================
+ // 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();
+}
+
+// Vertical interrupt - run once per frame (50hz in PAL, 60hz in NTSC)
+__attribute__((interrupt)) void INT_VInterrupt(void)
+{
+ // Fetch current sprite coordinates from RAM
+ //
+ // Animate them (x/y coords are 9 bits, so this
+ // wraps around the whole 512x512 sprite plane)
+ //
+ // Store updated values back in RAM for next frame
+ *RAM_SPRITE_1_POS_X += SPRITE_1_MOVE_SPEED_X;
+ *RAM_SPRITE_1_POS_Y += SPRITE_1_MOVE_SPEED_Y;
+ *RAM_SPRITE_2_POS_X += SPRITE_2_MOVE_SPEED_X;
+ *RAM_SPRITE_2_POS_Y += SPRITE_2_MOVE_SPEED_Y;
+
+ // Write updated coordinates to the Sprite Attribute Table in VRAM.
+ // Each entry is 8 bytes in size, so sprite 1 is at table+0x0000,
+ // and sprite 2 is at table+0x0008.
+ //
+ // The Y coord is the 1st word in the structure, and the X coord is
+ // the 4th. As already noted, there are cleaner ways to do this,
+ // like storing the tables in RAM and copying them via DMA every
+ // frame, but that's beyond the focus of this sample.
+
+ // Sprite 1's Y coordinate is at table+0x0000
+ SetVRAMWrite(VRAM_ADDR_SPRITE_TABLE + 0x0000);
+ *VDP_DATA = *RAM_SPRITE_1_POS_Y;
+
+ // Sprite 1's X coordinate is at table+0x0006
+ SetVRAMWrite(VRAM_ADDR_SPRITE_TABLE + 0x0006);
+ *VDP_DATA = *RAM_SPRITE_1_POS_X;
+
+ // Sprite 2's Y coordinate is at table+0x0008
+ SetVRAMWrite(VRAM_ADDR_SPRITE_TABLE + 0x0008);
+ *VDP_DATA = *RAM_SPRITE_2_POS_Y;
+
+ // Sprite 2's X coordinate is at table+0x000E
+ SetVRAMWrite(VRAM_ADDR_SPRITE_TABLE + 0x000e);
+ *VDP_DATA = *RAM_SPRITE_2_POS_X;
+}