MSX Assembly Page

Load graphic data into the VRAM

Here is an couple of routine which can load a screen from disk into the VRAM. The routines are oriented towards MSX-DOS 2, if you want it to use DOS 1 you will have to change the disk load routines, aswell as the error abort routine. Also, they are programmed in Compass, so it they probably won’t directly compile in another assembler because of certain differences like hex notation and label length and such, but it should be easy to adapt.

First, I’ll give an example of how the LoadScreen routine should be called:

;
; Load screen FILENAME at #18000 (page 3 in screen 5)
;
Start:        ld      de,FILENAME
              ld      a,1
              ld      hl,#8000
              call    LoadScreen
              ret

FILENAME:     DB      "IMAGE.SC5",0

Load a file into the VRAM

Following are the actual routines. I’ll explain a bit inbetween, but if you want to use them in your own routines, just copy and paste a little, and either delete or comment the inbetween explanation parts.

First of all, some definitions are made. I go by the book, and the book is called MSX-DOS 2 in this case. These are the official naming conventions, so ah, why not just use them. Also, the temporary area is defined here. You can place it anywhere you want, and change the size to whatever value you want (clearly, larger values give better results) by modifying the given constants.

;
;=================================================================
;= Graphic loaders ===============================================
;=================================================================
;
;Definitions
;
Bdos:         equ     5
_TERM0:       equ     #00           ;. program terminate
_STROUT:      equ     #09           ;. string output
_OPEN:        equ     #43           ;. open file handle
_CLOSE:       equ     #45           ;. close file handle
_READ:        equ     #48           ;. read from file handle
_SEEK:        equ     #4A           ;. move file handle pointer
_TERM:        equ     #62           ;. terminate with error code
.EOF:         equ     #C7           ;. EOF error
              
TEMP1:        equ     #4000         ;start address temporary space
TEMP1_SIZE:   equ     #4000         ;size of temporary space

Following is the routine that loads the entire file into the VRAM. This means it keeps on reading bytes and sending it to the VRAM until DOS returns a .EOF ‘End Of File’ error. The first part is the initializer, which a. sets the VDP’s VRAM Write start address by calling the SetVDP_Write routine (for details about that, see below), b. opens the file, and c. (optionally) skips the first 7 bytes of the file. Here I say optionally, because I actually left that part commented out, since I myself don’t use files in the BSAVE format (I convert all files to the pure VRAM contents) (so I’ve also got a separate palette).

;
;Load the entire file into the VRAM
;
;DE  = filenamenaam
;AHL = VRAM start address
;
Load_Screen:  call    SetVdp_Write
              ld      c,_OPEN
              xor     a              ;no flags
              call    Bdos
              jp      nz,Error
              ld      a,b            ;Put the file handle
              ld      (LS_FHANDLE),a ; into the ld b,0 instruction

;             ld      de,0           ;Move file handle pointer to 7th byte
;             ld      hl,7
;             xor     a
;             ld      c,_SEEK
;             call    Bdos
;             jp      nz,Error

This next part actually loads the file from disk. The file handle was put directly inside the LD B,n instruction register by the previous piece of code, which is a little faster. Note that in this case, when loading files from a physical medium, a few extra T-states don’t *really* matter, and it’s actually better to use a more ‘tidy’ solution. But, I like self-modifying code, so alas, I couldn’t resist.

LoadScr_Loop: ld      hl,TEMP1_SIZE
              ld      de,TEMP1
              ld      b,0           ;self-modifying
LS_FHANDLE:   equ     $-1
              ld      c,_READ
              call    Bdos          ;Read temp1_size bytes
              cp      .EOF          ; check if end of file,
              jp      z,LoadScr_End ; if so, done!
              and     a
              jp      nz,Error

Now comes the (somewhat) neat part. This is the part that actually sends the data from the buffer to the VRAM. Now, in this case even single T-States *DO* matter, because this concerns a loop which is called numerous times. So, this part is highly optimized using techniques from my ‘Fast Loops’ article.

              dec     hl            ;Use "Mystery Fast Loop Calculus"
              ld      b,l           ;(result: loop HL (=nr of bytes read) times)
              inc     b
              ld      a,h
              inc     a
              ld      c,#98
              ld      hl,TEMP1
LoadScr_LdLp: otir
              dec     a
              jp      nz,LoadScr_LdLp
              jp      LoadScr_Loop

And finally, the end of the routine. This part is jumped to when an .EOF is returned by the disk read routines, meaning the end of the file has been reached. Commented out is a part which also sets the stored palette (for BSAVE format files, see the separate palette file loader for more info on that), and after that the file is closed.

LoadScr_End:
;             ld      de,0          ;Move file handle pointer
;             ld      hl,#7680 + 7  ; to palette start address
;             ld      a,(LS_FHANDLE)
;             ld      b,a
;             xor     a
;             ld      c,_SEEK
;             call    Bdos
;             jp      nz,Error
;             ld      hl,32
;             ld      de,TEMP1
;             ld      a,(LS_FHANDLE)
;             ld      b,a
;             ld      c,_READ       ;Read From File Handle
;             call    Bdos
;             jp      nz,Error
;             ld      hl,TEMP1
;             call    SetPalet

              ld      a,(LS_FHANDLE)
              ld      b,a
              ld      c,_CLOSE      ;Close File Handle
              call    Bdos
              jp      nz,Error
              ret

Last, there’s also the error handling routine, which exits to dos with the appropriate error message.

;
;Handle an error...
;Returns to DOS 2 which will show the error.
;
Error:
    ld b,a
    ld c,_TERM
    jp Bdos

Load and set a palette

This routine loads a file from disk, which should be 32 bytes in size, and contains the palette, stored in the same format as the VDP uses (also the same format Basic stores at address #7680). Basically it doesn’t do much interesting - just loads the data into the temporary buffer, then calls a function which sets that palette.

;
;Load palette file
;
;DE = filename
;
Load_Palet:   ld      c,_OPEN   ;Open File Handle (no flags)
              xor     a
              call    Bdos
              jp      nz,Error
              push    bc
              ld      de,TEMP1
              ld      hl,32
              ld      c,_READ   ;Read From File Handle
              call    Bdos
              jp      nz,Error
              pop     bc
              ld      c,_CLOSE  ;Close File Handle
              call    Bdos
              jp      nz,Error
              ld      hl,TEMP1  ;Set palette
              jp      SetPalet

The VDP routines

The following routines are the VDP routines which are called from the Load functions. The first sets the start address of the VDP VRAM I/O port (port #98) to the specified value in registers A:HL (where bit 0 of register A contains bit 17 of the address).

;
;
;=================================================================
;= VDP-Routines ==================================================
;=================================================================
;
;
;Set VDP port #98 to start writing from address AHL (17-bit)
;
SetVdp_Write: rlc     h
              rla
              rlc     h
              rla
              srl     h
              srl     h
              di
              out     (#99),a       ;set bits 15-17
              ld      a,14+128
              out     (#99),a
              ld      a,l           ;set bits 0-7
              nop
              out     (#99),a
              ld      a,h           ;set bits 8-14
              or      64            ; + write access
              ei
              out     (#99),a       
              ret

And second, there’s the routine which sends a new palette to the VDP, which is read from address HL. You might notice the huge lot of DW #A3EDs... Those are actually 32 OUTI instructions. I use this notition because it is much more compact than writing 32 lines with OUTIs. Ofcouse you could also use an OTIR instruction, however OUTI is about 25% faster than OTIR.

;
;Set the VDP's palette to the palette HL points to
;Changes: AF, BC, HL (=updated)
;
SetPalet:     xor     a             ;Set p#pointer to zero.
              di
              out     (#99),a
              ld      a,16+128
              ei
              out     (#99),a
              ld      c,#9A
              DW      #A3ED,#A3ED,#A3ED,#A3ED   ;32x OUTI instruction
              DW      #A3ED,#A3ED,#A3ED,#A3ED   ; (faster than OTIR)
              DW      #A3ED,#A3ED,#A3ED,#A3ED
              DW      #A3ED,#A3ED,#A3ED,#A3ED
              DW      #A3ED,#A3ED,#A3ED,#A3ED
              DW      #A3ED,#A3ED,#A3ED,#A3ED
              DW      #A3ED,#A3ED,#A3ED,#A3ED
              DW      #A3ED,#A3ED,#A3ED,#A3ED
              ret

Well, we’re done. I hope it’s been of any use to you! Greetings, earthling!

Grauw