Starting Msx Assembly 4

October 22, 2020 - by Paul Bosselaar

So lets start where we ended in part 3. We had a piece of code which printed a letter, one by one, to the screen to finally print “Hello world”.

Execute:
        LD HL, HELLO

LOOP:   LD A, (HL)
        CP 0
        RET Z
        CALL CHPUT
        INC HL
        JP LOOP	
HELLO:  db "Hello world",0

First we point register pair HL to the label HELLO. On that label we define a set of bytes (db) in memory, “Hello world”. Effectively, HL now contains the memory adress of the capital H. INC HL will increase HL so it will point to the next byte in memory, containing the letter e. Now what will happen if we use INC (HL) instead?

At the beginning of this piece of code we load the contents of the memory where HL points to into A. This is done by LD A, (HL) at line 2. So instead of loading HL itself, which contains simply an adress, we load where HL points to. This is called an indirect access. Now, you cannot actually load HL into A since HL contains a 16 bit value while A can only hold 8 bits. We can load other 8 bit registers into A with a direct access, e.g. LD A,B or even LD A, L. This indirect access can also be used together with INC. So instead of incrementing HL itself, it will increment the value where HL points to. So if we used INC (HL) instead of pointing to the next byte in memory, the Z80 will increase the value from ‘H’ to ‘I’, since I is the next letter in the alfabet (and in the ASCII table ;) )

So with indirect access we can load from and save to memory and even change values in memory with just one instruction.

VDP

So, lets face it. Whoever is reading this, is probable interest in creating games or demo’s on the MSX. That is, IF someone is reading this at all ;). Anyway, to do that we need to know a bit more about the MSX Video Display Processor (VDP). So let’s do something with the VDP.

Video RAM

The VDP has its own video memory, which cannot be accessed like normal memory. Also, the VDP has so called registers which determine what the VDP will do or what screen mode the VDP will use. To write to video memory, we’ll have to use the registers to let the VDP know we want to write to a certain location in memory and then send it a numnber of bytes. The VDP can be accessed direcly with so called Input/Output (I/O) ports. But luckily the MSX bios had functions to deal with the VDP as well. So we are going to use that to make our life a lot easier ;)

CHMOD:  EQU     $5F

        LD A,2
        CALL CHMOD

So, what will this do? We’re loading 2 into A and then we’re calling the CHMOD BIOS function located at $5F. CHMOD is short for Change Mode and, indeed, will change the screen mode of the MSX. In this case it will switch to screen 2. Screen 2 is a graphics mode. Unlike screen 0 and 1 which are text modes, there are no BIOS calls to put a character on the screen so making a “Hello world” will be a bit different in this case.

Screen 0 to 2 are only using 16 KiloBytes of Video RAM, so some shortcuts had to be made to fit the screen 2 resolution (256x192) into memory. Yes, screen 2 is a graphics mode but it is partially handled like a charachter display. In video RAM we are going to define 8x8 characters to display on the screen. Then in another part of the video RAM we will define which characters are displayed on the screen.

Since in screens 0 to 2 only 16KiloBytes of RAM are used, some tricks had to be made to make sure the full resolution can be used. This means we cannot freely choose a color of each pixel. A character in screen 2 is defined by two different parts. First, we need the character pattern and a color patters. For every line in the 8x8 character we need to define a background and a foreground color. Then, for very pixel on that line, we need to define if we want to show the foreground or background color. That looks something like this:


CHAR1:
        db %01111110
        db %10000001
        db %10000001
        db %10000001
        db %10000001
        db %10000001
        db %10000001
        db %01111110

COLOR1:
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1        

So, the thing about different number system is that you can make parts of your code or data more readable. CHAR1 defines the character itself. Per pixel in the character we define a 0 for showing the background color, and a 1 for showing the foreground color. You should be able to see/read the character we just defined.

The colors are defined by hexadecimal numbers 0 - F (in which A = 10, B=11… F=15). So we’re defining the colors for every line to have color F (=15, white) as foreground color and to have color 1 (black) for the background color. We could have uses decimal numbers as well, but when I see 241 (241 decimal = F1 hexadecimal) I cannot immediately see what the colors will be. In binary that is a bit more easy though: 11110001. This makes clear that the first 4 bits are the foreground color, and the last 4 bits are the background color.

Ok, now we have a character defined in memory, but how are we going to display this?

Video RAM parts

In the video RAM, we have the Character Table defining the characters, we have the Color Table defining the colors for the characters, and finally, the Name Table which determined which characters are displayed on the screen. Screen 2 has a resolution of 256x192, which devided into 8x8 chararacters means the display is 32 characters wide and 24 characters long. That means 32 x 24 = 768 different characters can be defined. We can actually define 786 different characters and color combinations. However, we cannot freely use all 786 on all parts of the screen. Why is that? The part in video RAM which defines which character needs to be displayed contains 768 bytes. However, one byte can only have a value from 0 to 255. So 256 different characters. So to be able to use 768 different characters, the video RAM is divided into three parts. Three parts of 256 characters with their colors and 256 positions on the screen. This also means if you want to use a specific character everywhere on the screen, we need to define it in memory 3 times.

So we now know a bit about how to create characters and a bit about the video RAM (VRAM) layout.

When using the BIOS to switch to screen 2, the VRAM is used as follows:

$0000 - $17FF: Character Pattern Table. 3 times 256 characters
$1800 - $1AFF: Name Table. 768 positions for characters on the screen
$2000 - $37FF: Color Table. 3 times 256 definitions of 8 colors per character line

So what needs to be done is to switch to screen 2, upload the pattetn to the Character Pattern Table, upload the colors to the Color Table and finally define which characters we want to display in the name table.

BIOS calls

So to move things from memory to VRAM we can use the BIOS call LDIRVM. The name means something like LoaD, Increment, Repeat, to Vram from Memory and is a combination of the LDIR Z80 instruction which is used to move blocks of memory around and “to VRAM from Memory” to indicate we move from memory to VRAM. There is of course also a LDIRMV call which moves data from VRAM to normal memory.

For now, just remember that LDIRVM is used to move bytes from memory to the VRAM. To tell LDIRVM what it needs to do we need to fill 3 register pairs:

BC: How many bytes should be moved DE: From which part in memory HL: To which part in VRAM

So if the label CHAR1 contains our character, it looks something like this:


        LD BC, 8        ; Move 8 bytes/lines to VRAM
        LD DE, 0        ; Point DE to the start of the Character pattern table
        LD HL, CHAR1    ; Point HL to our character     
        CALL LDIRVM

Now we also need the fill the color table:


        LD BC, 8        ; Move 8 bytes/lines to VRAM
        LD DE, COLOR1   ; Point DE to the start of the Color Table
        LD HL, $2000    ; Point HL to the color definition
        CALL LDIRVM

Now after we’ve done this we should see the the top third of the screen filled with our defined character. When the BIOS is used to switch to screen 2, the Name table is filled with zero’s, e.g. the first character defined. So no need to update the Name Table to see something on the screen.

So let’s create the complete program:

        DB $FE      ; magic number
        DW Begin    ; begin address of the program
        DW End - 1  ; end address of the program
        DW Execute  ; program execution address (for ,R option)

        ORG $C000   ; The program start at C000h in the MSX's RAM

Begin:
Execute:
        LD BC, 8        ; Move 8 bytes/lines to VRAM
        LD DE, CHAR1    ; Point DE to our character
        LD HL, 0        ; Point HL to the start of the Character pattern table
        CALL LDIRMV

        LD BC, 8        ; Move 8 bytes/lines to VRAM
        LD DE, COLOR1   ; Point DE to the color definition
        LD HL, $2000    ; Point HL to the start of the Color Table
        CALL LDIRMV
LOOP:   JP LOOP

CHAR1:
        db %01111110
        db %10000001
        db %10000001
        db %10000001
        db %10000001
        db %10000001
        db %10000001
        db %01111110

COLOR1:
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1
        db $F1        
End:

When assembling and running this program, the MSX will hang because of the loop we created. The MSX should have switched over to screen 2 and should be showing one character.

And there we go. Now where to take it from here? If we can load one character, we can easily load multiple. Defining multiple characters by hand is nu fun, but luckilyu there is software for drawing them which can then export to assembly code for easy including in your own program. Let ’s do that next post and add some sprites into the mix as well!