1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
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;
}
|