There are a limited number of ways for your programs to communicate with a user. You can use text, graphics, and rarely sound. With text, the OS offers us many tools and it doesn't need to be too fast, but with graphics, the OS routines often do not do the job. Here are a few routines to get the job done.
Circle
A good circle routine is always sure to impress users, especially those accustomed to BASIC. This does not use formulas of the nature you may be used to for drawing circles. As well, this routine uses some SMC, so if you are using this in an app, be aware that the subroutine PlotPixel will need to be in RAM (or at least part of it).
gBufLSB = 40h ;different for the 83. Good for 83+/84+ and SE models.
gBufMSB = 93h ;different for the 83. Good for 83+/84+ and SE models.
FastCircle:
;Inputs:
; BC = (x,y)
; H = radius
; A = method
; 0 = Pixel Off
; 1 = Pixel On
; 2 = Pixel Change
ld de,$A62F ;2F,A6 = 'cpl \ and (hl)'
dec a
jr z,$+5
ld de,$B600 ;00,B6 = 'nop \ or (hl)'
dec a
jr z,$+5
ld de,$AE00 ;00,B6 = 'nop \ xor (hl)'
ld (smc_PlotType)
ld a,h
or a
ret z
ret m
di
ld e,0
ld d,a
ld l,e
add hl,hl
jr Loop+4
Loop:
ex af,af'
call Plot4Pix
call Plot4Pix
inc d
sub l
jr nc,YIsGood
DecY:
dec e
inc l
add a,h
YIsGood:
ex af,af'
ld a,e
sub d
jp p,Loop
ret
Plot4Pix:
inc l
push af
push hl
push bc
ld a,e
ld e,d
ld d,a
push de
add a,b
cp 96
call c,Plot2Pix ;DC****
ld a,b
sub d
call p,Plot2Pix
pop de
pop bc
pop hl
pop af
ret
plot2Pix:
push bc
ld b,a
push bc
ld a,c
add a,e
ld c,a
;c+e
call p,PlotPixel
pop bc
ld a,c
sub e
ld c,a
;c-e
call p,PlotPixel
pop bc
ret
PlotPixel:
;Input:
; b is X
; c is y
;Output:
; HL points to byte
cp 64 \ ret nc
ld a,b
cp 96 \ ret nc
ld l,c
ld h,0
ld b,h
add hl,hl
add hl,bc
add hl,hl
add hl,hl
ld b,a
rrca \ rrca \ rrca
and 0Fh
add a,gBufLSB
ld c,a
ld a,b
ld b,gBufMSB
add hl,bc
and 7
ld b,a
inc b
ld a,1
rrca
djnz $-1
PixelType:
nop
or (hl)
ld (hl),a
ret
Pixel Plotting
Sometimes, we need to plot pixels. This routine returns a pointer to the byte the pixel is located on, as well as a mask for the pixel. If it is out of bounds, it returns the c flag reset, else it is set. To use this routine, you can do:
;turn the pixel ON
call GetPixelLoc
ret nc
or (hl)
ld (hl),a
ret
;turn the pixel OFF
call GetPixelLoc
ret nc
cpl ;invert the bits of A
and (hl)
ld (hl),a
ret
;invert the pixel
call GetPixelLoc
ret nc
xor (hl)
ret
;test the pixel (nz=ON, z=Off)
call GetPixelLoc
ret nc ;also happens to return nz if c is reset
and (hl)
ret
gBufLSB = 40h ;different for the 83. Good for 83+/84+ and SE models.
gBufMSB = 93h ;different for the 83. Good for 83+/84+ and SE models.
GetPixelLoc:
;Input:
; b is X
; c is y
;Output:
; HL points to byte
; A is the mask
; nc if not computed, c if computed
cp 64 \ ret nc
ld a,b
cp 96 \ ret nc
ld l,c
ld h,0
ld b,h
add hl,hl
add hl,bc
add hl,hl
add hl,hl
ld b,a
rrca \ rrca \ rrca
and 0Fh
add a,gBufLSB
ld c,a
ld a,b
ld b,gBufMSB
add hl,bc
and 7
ld b,a
ld a,1
inc b
rrca
djnz $-1
scf
ret
Rectangle
The OS routines automatically update the LCD area that it is drawn to, which can be a pain to assembly programmers. It also slows it down. Here is a quick routine, riddled with SMCL
gBufLSB = 40h ;different for the 83. Good for 83+/84+ and SE models.
gBufMSB = 93h ;different for the 83. Good for 83+/84+ and SE models.
RectData equ OP1
Rectangle:
;Inputs:
; D = x
; E = y
; B = height
; C = width
; A = Method
; 0=Erase
; 1=OR
; 2=XOR
dec a
jr nz,$+6
ld a,$B6 ;B6 = 'or (hl)'
jr RectPattern
dec a
jr nz,$+6
ld a,$AE ;AE = 'xor (hl)'
jr RectPattern
ld a,-1
ld (smc_Erase0),a
ld a,2Fh ;2F = CPL
ld (smc_Erase1),a
ld a,0Ch ;0C = INC C
ld (smc_Erase2),a
ld a,$A6 ;A6 = 'and (hl)'
RectPattern:
ld (smc_Logic),a
push bc
push de
push bc
; First I want to clear the RectData buffer
ld hl,RectData
.db 1
smc_Erase0:
.dw 0C00h ;changed to 0CFF for Erase
ld (hl),c
inc l
djnz $-2
ld l,78h
; Now I want to make the rectangle pattern.
; Since D is the x coordinate, we need to find which bit
; the edge starts on and set each bit after that as well.
ld a,d
and 7
ld b,a
ld a,80h
jr z,$+5
rrca
djnz $-1
add a,a
dec a
smc_Erase1:
nop
ld (hl),a
; Now we need to fill in the rest of the buffer
; First we need to know how many bits need to be set
ex (sp),hl
ld a,d
neg
and 7
ld b,a
ld a,l
smc_Erase2:
dec c ;changed to 'inc c' for Erase
sub b
ex (sp),hl
jr z,PatternFinished
jr c,LoadLastByte
inc l
ld (hl),c
sub 8
jr nc,$-4
LoadLastByte:
and 7
ld b,a
ld a,80h
jr z,$+5
rrca
djnz $-1
add a,a
dec a
xor (hl)
smc_Erase3:
nop
ld (hl),a
PatternFinished:
; At this point, the whole pattern is filled in. Bien.
; Now we can worry about where to start drawing the pattern.
; For this, we use DE for (x,y)
; B is 0, so I cheat.
ld a,d
ld d,b
ld h,d
ld l,e
add hl,hl
add hl,de
add hl,hl
add hl,hl
and %11111000
rrca \ rrca \ rrca
add a,gBufLSB ;plotSScreen = 9340h
ld e,a
ld d,gBufMSB
add hl,de
; Now HL points to where to draw in the graph buffer.
; All we do now is treat the pattern as a 12-byte wide sprite with
; the same bytes repeated over and over.
pop bc ;b is the height.
ld d,84h ;RectData = 8478h
DrawLoopStart:
ld e,78h
ld c,12
DrawLoop:
ld a,(de)
smc_Logic:
or (hl)
ld (hl),a
inc hl
inc e
dec c
jr nz,DrawLoop
djnz DrawLoopStart
pop de
pop bc
ret
Grayscale LCD Update
This code uses SMC, so it should be used in RAM. However, the SMC used can easily be removed for use in an app. This requires the use of a minor buffer and major buffer and it can use different levels of saturation from each buffer by changing GrayMask. This is designed to handle 2,3, or 4 levels of grayscale and each of the grayscale bit masks is meant to display each pixel for a certain percentage of the frames displayed. This routine must be continuously called to update the LCD in order for it to appear gray.
;===============================================================
BufferToLCD:
;===============================================================
;Inputs:
; MajBuf points to the main buffer to use. Probably plotSScreen.
; MinBuf points to the backgfor round buffer to use. Probably saveSScreen.
; GrayMask holds the gray mask. Zero takes from the major,
; buffer, one takes from the minor buffer.
;Outputs:
;
;Defines:
GrayMask50 equ %1010101010101010 ;1 1/2 1/2 0
GrayMask67 equ %1001001001001001 ;1 2/3 1/3 0
GrayMask75 equ %1000100010001000 ;1 3/4 1/4 0
GrayMask83 equ %1000001000001000 ;1 5/6 1/6 0
GrayMask92 equ %1000000000001000 ;1 11/12 1/12 0
Graymask100 equ -1
GrayMask0 equ 0
GrayMask8 equ 1-GrayMaks92
GrayMask17 equ 1-GrayMask83
GrayMask25 equ 1-GrayMask75
GrayMask33 equ 1-GrayMask67
#define LCDDelay() in a,(16) \ rlca \ jr c,$-3
;===============================================================
.db 21h ;ld hl,**
GrayMask:
.dw GrayMask50 ;replace with whatever you like
add hl,hl
jr nc,$+4
set 4,l
ld (GrayMask),hl
ld hl,MajBuf
ld ix,MinBuf
setrow:
LCDDelay()
ld a,$80
out (16),a
ld de,12
ld a,$20
col:
ld (smc_Var2),a
LCDDelay()
ld a,(smc_Var2)
out (10h),a
ld b,64
row:
ld (smc_Var1),hl ;16
.db 21h ;10 ld hl,**
GrayMask: ;
.dw GrayMask50 ;--
add hl,hl ;11
jr nc,$+4 ;12|15
set 4,l ;--
ld (GrayMask),hl ;16
ld a,(ix) ;19
.db 21h ;10 ld hl,**
smc_Var1: ;
.dw 0 ;--
xor (hl) ;7
and c ;4
xor (hl) ;7
add hl,de ;11
add ix,de ;15
;========================================================
;Not needed because the stuff before takes >130 cycles
; push af
; LCDDelay()
; pop af
;========================================================
out ($11),a
djnz row
.db 3Eh ;ld a,*
smc_Var2:
.db 20h
inc a
dec h
dec h
dec h
inc hl
dec ixh
dec ixh
dec ixh
inc ix
cp $2c
jp nz,col
ret