\chapter{Utility Functions} \label{chp:utils} This chapter describes and implements a few functions that are usable by tasks, and have some sort of utility value. %%%%%%%%%%%%%%%%%%%% \section{memset256 - set up to 256 bytes of memory to a certian byte} Here we will implement a function that sets a region of memory to a certian value. Load the value into [[a]], the base address into [[hl]], and the number of bytes into [[b]]. We might want to use this in task space, so we'll make it a utility function. <>= ;; memset256 - set up to 256 bytes of ram to a certain value ; in a value to poke ; in b number of bytes to set 0x00 for 256 ; in hl base address of the memory location ; out - ; mod hl, bc memset256: ld (hl), a ; *hl = 0 inc hl ; hl++ djnz memset256 ; decrement b, jump to memset256 if b>0 ret ; return @ %%%%%%%%%%%%%%%%%%%% \section{memsetN - set N blocks of memory to a certian byte} Here we will implement a function that sets a region of memory to a certian value. Load the value into [[a]], the base address into [[hl]], and the number of blocks of 256 bytes into [[b]]. We might want to use this in task space, so we'll make it a utility function. <>= ;; memsetN - set N blocks of ram to a certain value ; in a value to poke ; in b number of blocks to set ; in hl base address of the memory location ; out - ; mod hl, bc memsetN: push bc ; set aside bc ld b, #0x00 ; b = 256 call memset256 ; set 256 bytes pop bc ; restore the outer bc djnz memsetN ; if we're not done, set another chunk. ret ; otherwise return @ %%%%%%%%%%%%%%%%%%%% \section{cls - clear the screen} The screen ram is two chunks of ram from [[0x4000]] through [[0x43FF]] as well as [[0x4400]] through [[0x47FF]]. We will clear these to black. We'll basically nest two loops, both using the [[djnz]]. The inner loop happens in the memset function. The outer loop happens 8 times, since we need to do 256 bytes 8 times. ([[djnz]] only looks at 8 bits of register 'b'.) <>= ;; cls - clear the screen (color and video ram) ; in - ; out - ; mod - cls: push hl ; set aside some registers push af push bc ld hl, #(vidram) ; base of video ram ld a, #0x00 ; clear the screen to 0x00 ld b, #0x08 ; need to set 256 bytes 8 times. call memsetN ; do it. pop bc ; restore the registers pop af pop hl ret ; return @ %%%%%%%%%%%%%%%%%%%% \section{guicls - clear the screen to GUI background} Basically, this will just do a [[cls]], but it will draw the textured background to the screen insteas of just leaving it blank. The tiles to use for this are defined in the [[task0]] definition, in \S\ref{sec:guicursorwallpaper}. Due to the fact that we're going to be using a different value for the tile and color, we need to have distinct, seperate loops for the color ram and video ram, unfortunately. <>= ;; guicls - clear the screen to the GUI background ; in - ; out - ; mod - guicls: push hl ; set aside some registers push af push bc ; fill the screen with the background color ld hl, #(colram) ; color ram ld a, #(PwpC) ; color ld b, #0x04 ; 4 blocks call memsetN ; fill the screen with the background tile ld hl, #(vidram) ; character ram ld a, #(PwpS) ; background tile ld b, #0x04 ; 4 blocks call memsetN pop bc ; restore the registers pop af pop hl ret ; return @ %%%%%%%%%%%%%%%%%%%% \section{rand - get a random number} This function returns a pseudorandom number in register A. We need a byte for persistance, to get the previous Random number we gave out: <>= ; random assistance register (byte) randval = (ram + 23) @ The algorithm I'm doing here is just a standard mutilating calculation like so: <>= new random number = current timer + sine( last random number ) + R @ It's just something simple that we can replace with something better later. In the meantime, it should give something reasonably random, although not decently distributed throughout [[[0..256]]]. We also will include the memory refresh register, since that one is constantly changing. If our application used sound, and we're on Pac hardware, we could also add in the accumulator registers from the sound hardware as well. We can pull out the items between [[.r01]] and [[.r02]] if we've determined that the [[R]] register adds nothing useful to the randomization of the system <>= ;; rand - get a random number ; in - ; out a random number 0..256 ; mod flags rand: ; set aside registers push hl push bc ; compute a random number ld hl, (randval) ; hl = last random number push hl pop bc ; bc = hl call sine ; a = sine (c) ld c, a ; c = sine ( last value ) .r01: ld a, r ; a = R add a, c ; a += sine( last value ) ld c, a ; c = sine( last value ) + R .r02: add hl, bc ; rnd += sin ( last value ) + R ld bc, (timer) add hl, bc ; rnd += timer ld (randval), hl ; hl = computed random (rnd) ld a, (randval) ; a = rnd ; restore registers pop bc pop hl ; return ret @ %%%%%%%%%%%%%%%%%%%% \section{sine - return the sine} This function returns the modified sine of the angle passed in in register [[C]]. It returns this value in register [[A]]. To simplify this, instead of expecting rotational angle on a range of [[[0..360]]] degrees, we will instead expect the rotational angle to be on a range of 256 units per complete circle. We will also return a value from [[[-127..127]]] instead of [[[-1..1]]] since we can't work with decimal values easily. This should be good enough for most uses. <>= ;; sine - get the sine of a ; in c value to look up ; out a sine value 0..256 ; mod - sine: ; set aside registers push hl push bc ; look up the value in the sine table ld hl, #(.sinetab) ; hl = sinetable base ld b, #0x00 ; b = 0 add hl, bc ; hl += bc ld a, (hl) ; a = sine(c) ; restore registers pop bc pop hl ; return ret @ Since we're here, we might as well throw in a cosine function as well. We just add [[0x7f]] onto the angle passed in via [[C]], and look up that value in the sine table using the above method. <>= ;; cosine - get the cosine of a ; in c value to look up ; out a cosine value 0..256 ; mod f cosine: ; set aside registers push bc ; add 180 degrees, call sine ld a, #0x3f add a, c ld c, a call sine ; restore registers pop bc ; return ret @ <>= .sinetab: .byte 0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95 .byte 0x99, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae .byte 0xb1, 0xb4, 0xb6, 0xb9, 0xbc, 0xbf, 0xc2, 0xc4 .byte 0xc7, 0xc9, 0xcc, 0xcf, 0xd1, 0xd3, 0xd6, 0xd8 .byte 0xda, 0xdc, 0xdf, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8 .byte 0xea, 0xec, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf5 .byte 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd .byte 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff .byte 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfd .byte 0xfd, 0xfc, 0xfb, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7 .byte 0xf5, 0xf4, 0xf3, 0xf1, 0xf0, 0xee, 0xed, 0xeb .byte 0xe9, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, 0xdb .byte 0xd9, 0xd7, 0xd5, 0xd2, 0xd0, 0xcd, 0xcb, 0xc8 .byte 0xc6, 0xc3, 0xc0, 0xbd, 0xbb, 0xb8, 0xb5, 0xb2 .byte 0xaf, 0xac, 0xa9, 0xa6, 0xa3, 0xa0, 0x9d, 0x9a .byte 0x97, 0x94, 0x91, 0x8e, 0x8b, 0x87, 0x84, 0x81 .byte 0x7e, 0x7b, 0x78, 0x74, 0x71, 0x6e, 0x6b, 0x68 .byte 0x65, 0x62, 0x5f, 0x5c, 0x59, 0x56, 0x53, 0x50 .byte 0x4d, 0x4a, 0x47, 0x44, 0x42, 0x3f, 0x3c, 0x39 .byte 0x37, 0x34, 0x32, 0x2f, 0x2d, 0x2a, 0x28, 0x26 .byte 0x24, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x16 .byte 0x14, 0x12, 0x11, 0x0f, 0x0e, 0x0c, 0x0b, 0x0a .byte 0x08, 0x07, 0x06, 0x05, 0x04, 0x04, 0x03, 0x02 .byte 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02 .byte 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 .byte 0x0a, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x15 .byte 0x17, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x23, 0x25 .byte 0x27, 0x29, 0x2c, 0x2e, 0x30, 0x33, 0x36, 0x38 .byte 0x3b, 0x3d, 0x40, 0x43, 0x46, 0x49, 0x4b, 0x4e .byte 0x51, 0x54, 0x57, 0x5a, 0x5d, 0x60, 0x63, 0x66 .byte 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7c, 0x7f @ That table was generated with this perl snippet: <>= $across = 8; # number to print horizontally $current = $across +1; print ".sinetab:"; for ( $x=0 ; $x < 256 ; $x++ ) { $rads = ($x/255.0) * 6.283185307; #printf "%3d %f\n",$x, 128 + 128 *(sin $rads); $value = 128 + 128 *(sin $rads); if ($current >= $across) { print "\n\t.word\t"; $current = 0; } $current ++; printf "0x%02x", $value; if ( ($x < 255) && ($current < $across)) { printf ", "; } } print "\n"; @ %%%%%%%%%%%%%%%%%%%% \section{textcenter - centers text to be drawn} This function modifies the coordinates in [[BC]] based on the pascal string contained in HL. It simply replaces the value in [[B]] with a value that will result in the text being centered on the screen. <>= ;; textcenter - adjust the x ordinate ; in hl pascal string ; in b x ordinate ; in c y ordinate BC -> 0xXXYY ; out - ; mod b adjusted for center hscrwide = 14 textcenter: ; set aside registers push af ; halve the width ld b, (hl) ; b = length of text jp NC, .tcrr ; make sure carry is cleared ccf .tcrr: rr b ; b = half of text length ; add on the center position ld a, #hscrwide ; a = screenwidth/2 sub b ; a = screenwidth/2 - textlength/2 ld b, a ; b = that result ; restore registers pop af ; return ret @ %%%%%%%%%%%%%%%%%%%% \section{textright - right justifies text to drawn} This function modifies the coordinates in [[BC]] based on the pascal string contained in HL. It simply replaces the value in [[B]] with a value that will result in the text being right justified off of that location. <>= ;; textright - adjust the x ordinate ; in hl pascal string ; in b x ordinate ; in c y ordinate BC -> 0xXXYY ; out - ; mod b adjusted for right textright: ; set aside registers push af ; halve the width ld a, b ; a = start location ld b, (hl) ; b = length of text sub b ; a = start loc - length ld b, a ; b = new position ; restore registers pop af ; return ret @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Screen Region A tools} \begin{figure} \begin{center} \includegraphics[scale=0.8]{../gfx/pacscreen.pdf} \caption{Video Screen Layout} \label{fig:screen} \end{center} \end{figure} Screen region A is the topmost two rows of characters of the screen. The characters are addressed right-to-left for the top row, then right-to-left for the second row. These are shown in figure \ref{fig:screen} as the topmost two purple rows ``E'' and ``F''. We now provide routines for converting XY for this region into offsets into the color or video ram, as well as routines for drawing out text. %%%%%%%%%%%%%%%%%%%% \subsection{xy2offsAC - convert X,Y into offsets in screen region A and C} Since regions A and C are pretty muc hthe same thing, we will use the same function for both regions. We will define the bottom two rows (``A'' and ``B'' in figure \ref{fig:screen}) as rows [[2]] and [[3]], while the top two rows, ``E'' and ``F'' will be defined as rows [[0]] and [[1]]. <>= .acoffs: .word 0x03dd ; Region A row 'E' -> AC row 0 .word 0x03fd ; Region A row 'F' -> AC row 1 .word 0x001d ; Region C row 'A' -> AC row 2 .word 0x003d ; Region C row 'B' -> AC row 3 @ To make the decoding a little easier, we first will define this table of four offset addresses. To decode the offset from the XY position passed in via [[BC]], we use [[C]] as the index into this table, then we just add on [[B]] to that, and return the computed value in [[HL]]. <>= ;; xy2offAC - get the vid/color buffer offset of the X Y coordinates ; in b x ordinate ; in c y ordinate BC -> 0xXXYY ; out hl offset ; mod - xy2offsAC: ; set aside registers push bc push de push ix ; generate the X component into DE ld d, #0x00 ; d = 0 ld e, b ; e = X ; get the base offset ld ix, #(.acoffs) ; ix = offset table base ; add in the y component. (BC) ld b, #0x00 ; zero B (top of BC) rlc c ; y *= 2 add ix, bc ; offset += index ; retrieve that value into HL ld b, 1(ix) ld c, 0(ix) push bc pop hl ; hl = acroffs[x] ; subtract out the X component. sbc hl, de ; hl -= DE hl = acoffs[y]-x ; restore registers pop ix pop de pop bc ; return ret @ %%%%%%%%%%%%%%%%%%%% \subsection{putstrA - draw a string on region A of the screen} Since regions A and C are pretty much the same thing, just with different start positions, we will have hooks in here for C to jump into. <>= ;; putstrA - get the vid/color buffer offset of the X Y coordinates ; in hl pointer to the string (asciz) ; in b x position ; in c y position ; in a color ; out - ; mod - putstrA: ; set aside registers push bc .psChook: ; this is where putstrC joins in... push hl push de push ix push iy ; compute the offsets push hl ; set aside the string pointer call xy2offsAC push hl pop ix ; move the offset into ix (char ram) push hl pop iy ; move the offset into iy (color ram) ld de, #(vidram) ; base of video ram add ix, de ; set IX to appropriate location in vid ram ld de, #(colram) ; base of color ram add iy, de ; set IY to appropriate location in color ram ; prep for the loop pop hl ld b, (hl) ; b is the number of bytes (pascal string) inc hl ; HL points to the text now .pstra1: ; loop for each character ld c, (hl) ; c = character ld (ix), c ; vidram[b+offs] = character ld (iy), a ; colram[b+offs] = color ; adjust pointers inc hl ; inc string location dec ix ; dec char ram pointer dec iy ; dec color ram pointer djnz .pstra1 ; dec b, jump back if not done ; restore registers pop iy pop ix pop de pop hl pop bc ; return ret @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Screen Region C tools} Since region C is addressed similarly to region A, we will discuss that next instead of going into region B. In fact, this section leverages heavily on the previous section. Screen region C is the bottommost two rows of characters of the screen. The characters are addressed right-to-left for the second-to-bottom row, then right-to-left for the bottom row. These are shown in figure \ref{fig:screen} as the bottommost two purple rows ``A'' and ``B''. We now provide routines for drawing out text. %%%%%%%%%%%%%%%%%%%% \subsection{putstrC - draw a string on region C of the screen} Since regions A and C are pretty much the same thing, just with different start positions, we simply massage the input position data, and jump into the above [[putstrA]] function. <>= ;; putstrC - get the vid/color buffer offset of the X Y coordinates ; in hl pointer to the string (asciz) ; in b x position ; in c y position ; in a color ; out - ; mod - putstrC: ; set aside registers push bc inc c ; just change indexing 0,1 into 2,3 inc c jp .psChook ; jump back into putstrA @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Screen Region B tools} Screen Region B is the main body of the screen. It's characters are addressed from top-to-bottom for the rightmost column, then top-to-bottom for the column just to the left of that, and so on for 28 columns. These are shown in figure \ref{fig:screen} as the center blue area, starting at column ``C'', then ``D''. We now provide routines for converting XY for this region into offsets into the color or video ram, as well as routines for drawing out text. %%%%%%%%%%%%%%%%%%%% \subsection{xy2offsB - convert X,Y into offsets in screen region B} Since a lot of what we're doing involves interacting with the screen, we might as well have a method in here for converting X,Y (from the upper left) to screen offsets. The offset generated by this can be added to either the base video or color ram to determine screen locations in RAM. Basically, you load [[B]] with the X component, and [[C]] with the Y component. You then call this utility, and the correct offset gets loaded into [[HL]]. You can then add in the base for video or color ram to draw your characters to the screen, or retrieve information from the screen. It should be noted that the location [[X,Y == (0,0)]] is in the upper left of the screen, two character tiles from the top of the visible area of the screen, due to the existence of Region A. <>= ;; xy2offsB - get the vid/color buffer offset of the X Y coordinates ; in b x ordinate ; in c y ordinate BC -> 0xXXYY ; out hl offset ; mod - xy2offsB: ; set aside registers push af push bc push de push ix ; set aside Y for later in DE ld d, #0x00 ; d = 0 ld e, c ; shove Y into E ; get the base offset ld ix, #(.scroffs) ; ix = offset table base ; add in X component ;; XXXXJJJJJ This can probably be shortened if we ;; drop the range check. ld a, b ; shove X into A and a, #0x1f ; make sure X is reasonable rlc a ; x *= 2 ld c, a ; c = offset * 2 ld b, #0x00 ; b = 0 add ix, bc ; ix += bc ; retrieve that value into HL ld b, 1(ix) ld c, 0(ix) push bc pop hl ; hl = scroffs[x] ; add in Y component add hl, de ; hl += DE hl = scroffs[x]+y ; restore registers pop ix pop de pop bc pop af ; return ret @ This looks into the following table of screen offsets, which define where each column (left-to-right) starts in the color or video buffers. These just need to be added on to either of those buffer base addresses, then simply add in the y position. <>= .scroffs: .word 0x03a0, 0x0380, 0x0360, 0x0340 .word 0x0320, 0x0300, 0x02e0, 0x02c0 .word 0x02a0, 0x0280, 0x0260, 0x0240 .word 0x0220, 0x0200, 0x01e0, 0x01c0 .word 0x01a0, 0x0180, 0x0160, 0x0140 .word 0x0120, 0x0100, 0x00e0, 0x00c0 .word 0x00a0, 0x0080, 0x0060, 0x0040 @ That table was generated with this perl snippet: <>= #!/usr/bin/perl $wide = 28; $tall = 36; # screen offset = .scroffs[x] + y; $across = 4; $current = $across +1; printf ".scroffs:"; for ($x=0 ; $x<$wide ; $x++) { if( $current >= $across) { print"\n\t.byte\t"; $current = 0; } $current++; printf "0x%04x", (928 - ($tall-4) * $x); if( ($x < $wide) && ($current < $across)) { printf ", "; } } printf "\n"; @ %%%%%%%%%%%%%%%%%%%% \subsection{putstrB - draw a string on region B of the screen} This is just a simple routine to draw out a pascal string to the screen within the vertical scanning region. (ie not the top two or bottom two rows of the screen, which are addressed differently. Simply load the color into [[A]], the X,Y position into [[B]],[[C]], and the pointer to the pascal string into [[HL]]. In a single loop, it draws out the character and sets the color for the text it is drawing. It should be noted that there are no safeguards around this, so if your text is longer than 28 characters wide, it will get truncated, and might overwrite program RAM, which is a very bad thing to do. The code simply sets up the char and color pointers into [[IX]] and [[IY]], and increments them by [[-32]] for each iteration through the loop, while at the same time, it draws the correct character and color through those pointers. <>= ;; putstrB - get the vid/color buffer offset of the X Y coordinates ; in hl pointer to the string (asciz) ; in b x position ; in c y position ; in a color ; out - ; mod - offsadd = -32 putstrB: ; set aside registers push hl push bc push de push ix push iy push hl ; compute the offsets call xy2offsB ; hl = core offset push hl pop ix ; move the offset into ix (char ram) push hl pop iy ; move the offset into iy (color ram) ld de, #(vidram) ; base of video ram add ix, de ; set IX to appropriate location in vid ram ld de, #(colram) ; base of color ram add iy, de ; set IY to appropriate location in color ram ; prep for the loop pop hl ld b, (hl) ; b is the number of bytes (pascal string) inc hl ; HL points to the text now ld de, #offsadd ; set up the column offset .pstrb1: ; loop for each character ld c, (hl) ; c = character ld (ix), c ; vidram[b+offs] = character ld (iy), a ; colram[b+offs] = color ; adjust pointers inc hl ; inc string location add ix, de ; add in offset into char ram add iy, de ; add in offset into color ram djnz .pstrb1 ; dec b, jump back if not done ; restore registers pop iy pop ix pop de pop bc pop hl ; return ret @ Here's an older implementation, which did more stack pushing and popping. It is 54 bytes long, and uses two loops to draw the text. One to draw the text, and one to draw the color. The previous routine is 47 bytes long, and does it all within one loop. <>= ;; putstr - get the vid/color buffer offset of the X Y coordinates ; in iy pointer to the string (asciz) ; in b x position ; in c y position ; in d color ; out - ; mod - offsadd = -32 putstr: ; set aside registers push hl push af push bc push iy push de ; retrieve the offset call xy2offsB ; hl = core offset push hl ; store it on the stack pop hl push hl ld de, #(vidram) ; base of video ram add hl, de ; set HL to appropriate location in vid ram ; draw out the string ld de, #offsadd ; setup the column offset ld b, (iy) ; b is the number of bytes (pascal string) .pstr1: inc iy ; iy is now the string offset ld a, (iy) ; a contains a character to draw ld (hl), a ; send it to the screen add hl, de ; add in the offset to the screen djnz .pstr1 ; dec b, jump back if not done ; set the color pop hl ; restore offset value ld de, #(colram) ; base of color ram add hl, de ; set HL to appropriate location in color ram pop de ; restore the color info ld a, d ; draw up the color pop iy ; restore the string pointer (for length) ld b, (iy) ; b is the number of bytes (pascal string) ld de, #offsadd ; setup the column offset .pstr2: ld (hl), a ; fill in the color add hl, de ; add in the offset to the screen djnz .pstr2 ; dec b, jump back if not done ; restore registers pop bc pop hl pop af ; return ret @ %%%%%%%%%%%%%%%%%%%% \subsection{mult8 - 8 bit multiply} <>= HL=H*E LD L, 0 LD D, L ; L = 0 and D = 0 LD B, 8 MULT: ADD HL, HL JR NC, NOADD ADD HL, DE NOADD: DJNZ MULT @