Skip to content
sverx edited this page Dec 8, 2023 · 19 revisions

The devkitSMS wiki! How devkitSMS/SMSlib works

(a small journey into functions and functionalities)

(short) Introduction

The SEGA Master System and its portable brother, the SEGA Game Gear, are 8-bit machines powered by two hearts. One heart is the Zilog Z80, an 8-bit processor clocked at 3.58 MHz, while the other is a custom graphic chip (derived from the Texas Instruments TMS9918) that handles everything that's on-screen: it's called the 'Video Display Processor', or VDP for short. This chip processes the data stored into the Video RAM (VRAM for short) and creates the image that is sent to the TV screen, according to the video modes and settings stored into its internal programmable registers and Color RAM internal registers (CRAM, also known as 'the palettes'). Different Master System models can have different VDP revisions with different features, and the Game Gear one also has a few differences, but as long as we're concerned we can consider them all equal.

The VDP can display a few different video modes. Modes 0 to 3 are the 'legacy' video modes, which ensure retro-compatibility with the SEGA SG-1000 and are the same video modes you'll also find on MSX computers and some other home computers of that era. We're not much interested in those video modes, if we want to program a Master System (or Game Gear) game. The Master System's VDP has an unique additional video mode, the so called 'Mode 4'.

In Mode 4, the VDP works by redrawing the screen left to right, top to bottom, 50 or 60 times a second, according to television standards. Each line (a 'scanline') will be 256 pixel wide, and 192 lines of pixels will be sent to the screen generating an image of 256×192 pixel. The output consist of a wrapping background made of a grid of 8×8 characters (tiles) and up to 64 sprites that can be freely placed at any location. Any pixel on screen will use a color from one of the two 16-color palettes stored in CRAM. The first palette is reserved for background tiles while the second is used for the sprites, but background tiles can use that one too. All the sprites will share the same 16-color palette.

VRAM is 16 KB and it contains the data for the 8×8 pixel tiles used by the background and the sprites, the background map itself (the Pattern Names Table, or 'PNT' for short) and it also contains the Sprite Attribute Table (SAT) which is where the information about the sprites are stored.

The background is made up of 16-color 8×8 pixel tiles. The tilemap (PNT) is a single 32×28 table which describes how tiles should be placed on-screen. Each entry contains information for each location about which tile should be used, if it should be vertically and/or horizontally mirrored, which palette should it use, and if sprites are supposed to appear behind that tile or in front of it.

Sprites are made of 16-color 8×8 pixel tiles too, but they can be placed anywhere on screen. Even if the VDP supports up to 64 of them at the same time, it isn't able to draw more than 8 of them on any given scanline. Their positions and images (the tiles used) are stored into the SAT. Though VRAM is big enough to theoretically contain up to 512 tiles, only tiles defined into either the 1st or the 2nd half of VRAM can be used for sprites at a given time.

More in-depth details on the VDP, along with very complete docs, can be found on this page at SMSPower! website.

devkitSMS/SMSlib

SMSlib is substantially a wrapper that will hide most of the hardware inner workings, making it easier for anyone to write programs on the Master System platform. It's meant to be used with devkitSMS, and simply using devkitSMS and including SMSlib.h in your code you'll automatically initialize the library. This means the whole hardware will be ready straight from the beginning. The ROM mappers prepared, the VDP internal registers initialized to common values, the vBlank and pause handlers both ready.

In detail, the VDP will be initialized to 'Mode 4', which is Master System own mode, with the features described in the Introduction. Background scrolling will be reset to 0 both X & Y direction, the SAT (256 bytes) allocated at end of VRAM, the tilemap (1.75 KB, also called 'Pattern Names Table', PNT) just before the SAT, so to have a contiguous 14 KB free space in VRAM, enough for 448 tiles, from the VRAM beginning on. The display will be off, since the VRAM needs first to be loaded with some content before displaying anything meaningful.

Let's start!

To load tiles into VRAM, you can use

SMS_loadTiles (void *src, unsigned int Tilefrom, unsigned int len)

and to load a tilemap you can use

SMS_loadTileMap (unsigned char x, unsigned char y, void *src, unsigned int len)

or you can use this to load a rectangular area that fills a part of the screen only

SMS_loadTileMapArea (unsigned char x, unsigned char y, void *src, unsigned char width, unsigned char height)

also, you can manipulate the map directly using the following

SMS_setNextTileatXY (unsigned char x, unsigned char y)
SMS_setTile (unsigned int tile)

(setting an entry on the tilemap will automatically address the next entry unless another VDP operation is performed)

(if you've got an STM compressed tilemap, generated by Maxim's BMP2Tile, you can use)

SMS_loadSTMcompressedTileMap (unsigned char x, unsigned char y, unsigned char *src)

(also, if you've got PSGaiden compressed tiles you can load them to VRAM using)

SMS_loadPSGaidencompressedTiles (void *src, unsigned int tilefrom)

Then of course you need to load the palettes for your background and for your sprites eventually

SMS_loadBGPalette (void *palette)
SMS_loadSpritePalette (void *palette)

done that, you can finally turn on the screen, using the macro

SMS_displayOn()

of course you might want to (re)define a single color. You could then use

SMS_setBGPaletteColor (unsigned char entry, unsigned char color)
SMS_setSpritePaletteColor (unsigned char entry, unsigned char color)

and the following functions will scroll the background horizontally and/or vertically (yes, the VDP can do that!).

SMS_setBGScrollX (unsigned char scrollX)
SMS_setBGScrollY (unsigned char scrollY)

(since the tilemap is 256x224, vertical scrolling wraps at 224th pixel)

How to sync with screen / a.k.a. "Your endless 'while' loop"

In your code you'll want to sync what's going on in the game with the screen refresh so that you actually make changes happen between one frame and the next one. This is generally achieved by a while loop that lasts forever and a function that waits for the screen draw phase end. So your program's main() will probably look like this:

while (true) {
  // do your game logic here...
  SMS_waitForVBlank();
  // ... then change the contents of the screen here
}

Using sprites

SMSlib will handle sprite using a small temporary area so that it will be possible for you to define how sprites will appear in the next frame without changing the SAT contents (and thus affecting the sprite on screen in the current frame). So, in each frame, you have to declare you're going to list all the sprites one after another, front to back, with the function

SMS_initSprites (void)

each sprite can be added both using

SMS_addSprite (unsigned char x, unsigned char y, unsigned char tile)

which doesn't perform any clipping, or

SMS_addSpriteClipping (int x, int y, unsigned char tile)

which performs clipping (no sprite falling completely outside the viewport will be placed on screen) according to the viewport definition you can issue (and re-issue whenever you want) using the function

SMS_setClippingWindow (unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1)

note that both functions to add a sprite return a signed char: this is the sprite 'handle', or if it's negative the sprite hasn't been added (because it has been clipped, or because there are no more sprites available)

Also, if you need to place two sprites side-by-side, there's the fast and handy

SMS_addTwoAdjoiningSprites (unsigned char x, unsigned char y, unsigned char tile)

Done with sprites, we'll have a temporary copy of the SAT that will be needed the next frame. So you'll have to call the

SMS_copySpritestoSAT (void)

function in your while loop, likely right after the vBlank starts.

Note: sprites can only use the tiles defined in either half of the VRAM. You can choose if you're going to use tiles 0-255 or tiles 256-511 (or, better, tiles from 256 to 447 since PNT and SAT are over tiles 448-511) using the function

SMS_useFirstHalfTilesforSprites (bool usefirsthalf)

that you would probably call just once in the beginning of your program. If you never call that function, sprites will be referring to tiles in the second part of VRAM, as this is the library default.

VRAM

Reading input

Attached to your Master System you've got one (or possibly two) controllers. The library will poll the state of the keys once per frame even when the screen isn't on, and you can easily check the keys by using the four functions

SMS_getKeysStatus (void)
SMS_getKeysPressed (void)
SMS_getKeysHeld (void)
SMS_getKeysReleased (void)

in detail: the first returns the current state of the keys on both controllers, the second returns which keys were pressed since last check, the third which keys are held pressed and the last one which keys that were previously pressed are now released. The returned value is a mask which you have to bit-compare against the defined constants that follow:

#define PORT_A_KEY_UP           0x0001
#define PORT_A_KEY_DOWN         0x0002
#define PORT_A_KEY_LEFT         0x0004
#define PORT_A_KEY_RIGHT        0x0008
#define PORT_A_KEY_1            0x0010
#define PORT_A_KEY_2            0x0020
#define PORT_A_KEY_START        PORT_A_KEY_1    /* handy alias */
#define PORT_B_KEY_UP           0x0040
#define PORT_B_KEY_DOWN         0x0080
#define PORT_B_KEY_LEFT         0x0100
#define PORT_B_KEY_RIGHT        0x0200
#define PORT_B_KEY_1            0x0400
#define PORT_B_KEY_2            0x0800
#define PORT_B_KEY_START        PORT_B_KEY_1    /* handy alias */

If you plan on reading a Genesis/MegaDrive pad connected to the first controller port (port A) you can also call the following functions to read the state of the additional keys, the keys which are present on a Genesis/MegaDrive pad that aren't present on the Master System pad (thus the 'A' key, the 'Start' key and the 'X','Y','Z' and 'Mode' key if you're using a so called "6-buttons" pad)

SMS_getMDKeysStatus (void)
SMS_getMDKeysPressed (void)
SMS_getMDKeysHeld (void)
SMS_getMDKeysReleased (void)

again, you have to bit-compare the returned values with the handy defines

#define PORT_A_MD_KEY_Z         0x0001
#define PORT_A_MD_KEY_Y         0x0002
#define PORT_A_MD_KEY_X         0x0004
#define PORT_A_MD_KEY_MODE      0x0008
#define PORT_A_MD_KEY_A         0x0010
#define PORT_A_MD_KEY_START     0x0020

(Genesis/MegaDrive pad is no longer enabled by default, and support for a second Genesis/MegaDrive pad in port B is still missing)