Hello Sega Master System
Long overdue, it time for another Hello World hack, and this time it’s for the 8bit console Sega Master System (SMS). Based on the Z80 it will be the first system in this series that is not based on the Motorola M68000 CPU. I learned to code the SMS in 2005, and have release two tiny demo hacks under the alias blind io (you can find them here if really want to see them).
The major difference between the SMS and the Atari ST and Amiga is how the graphics hardware works. Most 8- and 16bit consoles, are based on tiled graphics and sprites. What this meas is that the screen is split into blocks of 8 by 8 pixels, and what tile is displayed one of those blocks is read from a tile map. Sprites are also blocks of pixels, but can be moved freely around the screen. The number of sprites are limited and only a few of them can be displayed on the same scanline due to restrictions in the hardware.
The tool used to assemble the code to a ROM image is called WLA DX. It is a great tool and can assemble code and produce ROM images for several different platforms. In this post, I’m going leave out most of the directives that tell the assembler about the output format and such, and focus on the Z80 code. For the full source, you can go the project on bitbucket.
The code
To start off, we tell the assembler to place the following code at address 0. This is where the Master System fetches starts to execute once the logo has been displayed.
.orga $0
di ; Disable Interrupts
im 1 ; Interrupt mode 1
jp start ; Jump to start label
In interrupt mode 1, the cpu jumps to address $38 when an interrupt occurs. Only the VDP can trigger normal interrupts on the SMS, either every VBL and/or every scanline. Address $66 is where the CPU jumps when a non-maskable interrupt occurs. This happens to be connected to the Pause button on the Master System. For us, lets just ignore the interrupts and return immediately.
.orga $38
reti ; RETurn from Interrupt
.orga $66
retn ; RETurn from Non-maskable interrupt.
Now we can do the thing we came here to do; show some graphics. First we must setup the VDP, which is the video controller. A bit further down in the listing, there is a section of register values for the video chip, and here is the code that writes these values to the data port connected to the VDP.
start:
; Upload the graphics mode to VDP
ld c,$bf ; Out port for VDP control
ld b,vdp_data_end-vdp_setup ; calculate length of vdp data and store in b.
ld hl,vdp_setup ; load address of vdp_setup data on hl register pair
otir ; OuT, Increase, Repeat
otir is an interesting command. It actually does several things. First off, it takes the byte pointed to by hl and output it to the port contained in register c and increases hl by one. Then it decreases the value in register b, and if the result is not 0, the program counter is changed to run the same command again.
Once the VDP configuration is done, we upload the graphics data tiles to the tile RAM inside the VDP. The address in the VDP to where we want to place the tiles is $4020 which we output to VDP control port ($bf). The highest two bits are in reality not part of the address, but tells the VDP that we want to write to the video ram (VRAM). By using offset $20 in VRAM, we leave the first tile empty so all unused blocks on the screen are left black. Note: Running this on real hardware will probably leave some junk on screen since the content of the VRAM might not be initialized to 0.
Then we setup the registers for another otir instruction. The port to write data to the VDP is $be, and it’s quicker to decrease $bf by one than to write the new value to the register.
ld hl,$4020
out (c),l
out (c),h
ld hl,gfx
ld b,32*6
dec c
otir
Now we have uploaded the tile data, but we must still set the colour palette. Same procedure as when we uploaded graphics data, by the colour palette address is $c000 (actually the two highest bytes indicate that we should write to colour memory (CRAM), and the following zeros indicate offset in CRAM), and the number of bytes in the palette is 16.
ld hl,color_palette
inc c ; increase c again, now it's $bf again
xor a
out (c),a ; write low byte of destination address
ld a,$c0 ; load high byte of VPD palette address
out (c),a ; output high byte of address.
dec c ; Decrease register c, it now contains $be
ld b,16
otir
One thing remains, we must tell the VPD what tiles to display at what position. Since we uploaded the tile data to tile number 1 to 6, we should write values 1 to six in the first 6 entries in the tile map. Tile map entries are 16 bit, so we need to write a 0 every second byte. In the code below, we write 0 to register e and writes that after we have written the value in register a, which we use to count from 1 to 6. The last thing we to is just loop forever.
inc c ; c = $bf
ld de,$7800
out (c),e
out (c),d
dec c
xor a ; xor with self is faster than ld a,0
ld e,a
ld b,6
-:
inc a
out (c),a
out (c),e
djnz -
halt:
jp halt
Here comes the VDP setup data. If you want to know what all these bits and values mean, I suggest you go to the development section of the SMS Power homepage in the link at the bottom of this post.
vdp_setup:
.db $04,$80 ; Mode control reg. 1
.db $40,$81 ; Mode control reg. 2 ( bit 6 enables display)
.db $ff,$82 ; Name table base
.db $ff,$83 ; Color table base
.db $ff,$84 ; Pattern gen. table base
.db $ff,$85 ; Sprite Attr. table base
.db $ff,$86 ; Sprite Pattern get. table base
.db $ff,$87 ; Overscan/backdrop color
.db $00,$88 ; background scroll x
.db $00,$89 ; background scroll y
.db $ff,$8a ; Interrupt line counter.
vdp_data_end:
The palette is quite straight forward. RGB data with 2 bits each. This give us a palette of 64 possible colours. 16 entries. There are actually two different palettes you can use on the Master System, one for background tiles and on for the sprites. Since no sprites are used in this short sample code, we only set the tile palette.
color_palette:
.db $00,$3f,$00,$00,$00,$00,$00,$00
.db $00,$00,$00,$00,$00,$00,$00,$00
Here comes the tile data. It’s the same graphics as with the Atari and Amiga version, but it has been converted to tiles. The tiles are 8*8 pixels with four bitplanes where the first four bytes of data are the four bitplanes for the first row etc. This means that every tile is 32 bytes of data.
gfx:
; 8*8 pixels, 4 bit plane
.db %00000000,%00000000,%00000000,%00000000
.db %01000100,%00000000,%00000000,%00000000
.db %01000100,%00000000,%00000000,%00000000
.db %01000101,%00000000,%00000000,%00000000
.db %01111101,%00000000,%00000000,%00000000
.db %01000101,%00000000,%00000000,%00000000
.db %01000100,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000101,%00000000,%00000000,%00000000
.db %11100101,%00000000,%00000000,%00000000
.db %00010101,%00000000,%00000000,%00000000
.db %11110101,%00000000,%00000000,%00000000
.db %00000101,%00000000,%00000000,%00000000
.db %11110101,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000001,%00000000,%00000000,%00000000
.db %00110001,%00000000,%00000000,%00000000
.db %01001001,%00000000,%00000000,%00000000
.db %01001001,%00000000,%00000000,%00000000
.db %01001001,%00000000,%00000000,%00000000
.db %00110000,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00010000,%00000000,%00000000,%00000000
.db %00010011,%00000000,%00000000,%00000000
.db %00010100,%00000000,%00000000,%00000000
.db %01010100,%00000000,%00000000,%00000000
.db %01010100,%00000000,%00000000,%00000000
.db %10100011,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000001,%00000000,%00000000,%00000000
.db %00011001,%00000000,%00000000,%00000000
.db %10100101,%00000000,%00000000,%00000000
.db %10100001,%00000000,%00000000,%00000000
.db %10100001,%00000000,%00000000,%00000000
.db %00100001,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
.db %00001010,%00000000,%00000000,%00000000
.db %00111010,%00000000,%00000000,%00000000
.db %01001010,%00000000,%00000000,%00000000
.db %01001010,%00000000,%00000000,%00000000
.db %01001000,%00000000,%00000000,%00000000
.db %00111010,%00000000,%00000000,%00000000
.db %00000000,%00000000,%00000000,%00000000
That is it really. For more info see the links below. I apologize for any errors and weird stuff in this blog post, it’s also 5 in the morning and I have written most of this post during the night.
The next installment in this blog series might come sooner than you think. :)
Links
- Z80 CPU User Manual - All you need to know about the Z80
- SMS POWER! - A goldmine for Sega Master System lovers.
- MEKA - Emulator with debugging possibilities.
- WLA DX - Multi platform cross assembler