collapseos/kernel/sms/vdp.asm

299 lines
6.1 KiB
NASM

; vdp - console on SMS' VDP
;
; Implement PutC on the console. Characters start at the top left. Every PutC
; call converts the ASCII char received to its internal font, then put that
; char on screen, advancing the cursor by one. When reaching the end of the
; line (33rd char), wrap to the next.
;
; In the future, there's going to be a scrolling mechanism when we reach the
; bottom of the screen, but for now, when the end of the screen is reached, we
; wrap up to the top.
;
; When reaching a new line, we clear that line and the next to help readability.
;
; *** Defines ***
; FNT_DATA: Pointer to 7x7 font data.
; *** Consts ***
;
.equ VDP_CTLPORT 0xbf
.equ VDP_DATAPORT 0xbe
; *** Variables ***
;
; Row of cursor
.equ VDP_ROW VDP_RAMSTART
; Line of cursor
.equ VDP_LINE @+1
.equ VDP_RAMEND @+1
; *** Code ***
vdpInit:
xor a
ld (VDP_ROW), a
ld (VDP_LINE), a
ld hl, vdpInitData
ld b, vdpInitDataEnd-vdpInitData
ld c, VDP_CTLPORT
otir
; Blank VRAM
xor a
out (VDP_CTLPORT), a
ld a, 0x40
out (VDP_CTLPORT), a
ld bc, 0x4000
.loop1:
xor a
out (VDP_DATAPORT), a
dec bc
ld a, b
or c
jr nz, .loop1
; Set palettes
xor a
out (VDP_CTLPORT), a
ld a, 0xc0
out (VDP_CTLPORT), a
xor a ; palette 0: black
out (VDP_DATAPORT), a
ld a, 0x3f ; palette 1: white
out (VDP_DATAPORT), a
; Define tiles
xor a
out (VDP_CTLPORT), a
ld a, 0x40
out (VDP_CTLPORT), a
ld hl, FNT_DATA
ld c, 0x7e-0x20 ; range of displayable chars in font.
; Each row in FNT_DATA is a row of the glyph and there is 7 of them.
; We insert a blank one at the end of those 7. For each row we set, we
; need to send 3 zero-bytes because each pixel in the tile is actually
; 4 bits because it can select among 16 palettes. We use only 2 of them,
; which is why those bytes always stay zero.
.loop2:
ld b, 7
.loop3:
ld a, (hl)
out (VDP_DATAPORT), a
; send 3 blanks
xor a
out (VDP_DATAPORT), a
nop ; the VDP needs 16 T-states to breathe
out (VDP_DATAPORT), a
nop
out (VDP_DATAPORT), a
inc hl
djnz .loop3
; Send a blank row after the 7th row
xor a
out (VDP_DATAPORT), a
nop
out (VDP_DATAPORT), a
nop
out (VDP_DATAPORT), a
nop
out (VDP_DATAPORT), a
dec c
jr nz, .loop2
; Bit 7 = ?, Bit 6 = display enabled
ld a, 0b11000000
out (VDP_CTLPORT), a
ld a, 0x81
out (VDP_CTLPORT), a
ret
; Spits char set in A at current cursor position. Doesn't move the cursor.
; A is a "sega" char
vdpSpitC:
; store A away
ex af, af'
push bc
ld b, 0 ; we push rotated bits from VDP_LINE into B so
; that we'll already have our low bits from the
; second byte we'll send right after.
; Here, we're fitting a 5-bit line, and a 5-bit column on 16-bit, right
; aligned. On top of that, our righmost bit is taken because our target
; cell is 2-bytes wide and our final number is a VRAM address.
ld a, (VDP_LINE)
sla a ; should always push 0, so no pushing in B
sla a ; same
sla a ; same
sla a \ rl b
sla a \ rl b
sla a \ rl b
ld c, a
ld a, (VDP_ROW)
sla a ; A * 2
or c ; bring in two low bits from VDP_LINE into high
; two bits
out (VDP_CTLPORT), a
ld a, b ; 3 low bits set
or 0x78 ; 01 header + 0x3800
out (VDP_CTLPORT), a
pop bc
; We're ready to send our data now. Let's go
ex af, af'
out (VDP_DATAPORT), a
ret
vdpPutC:
; Then, let's place our cursor. We need to first send our LSB, whose
; 6 low bits contain our row*2 (each tile is 2 bytes wide) and high
; 2 bits are the two low bits of our line
; special case: line feed, carriage return, back space
cp LF
jr z, vdpLF
cp CR
jr z, vdpCR
cp BS
jr z, vdpBS
push af
; ... but first, let's convert it.
call vdpConv
; and spit it on screen
call vdpSpitC
; Move cursor. The screen is 32x24
ld a, (VDP_ROW)
cp 31
jr z, .incline
; We just need to increase row
inc a
ld (VDP_ROW), a
pop af
ret
.incline:
; increase line and start anew
call vdpCR
call vdpLF
pop af
ret
vdpCR:
call vdpClrPos
push af
xor a
ld (VDP_ROW), a
pop af
ret
vdpLF:
; we don't call vdpClrPos on LF because we expect it to be preceded by
; a CR, which already cleared the pos. If we cleared it now, we would
; clear the first char of the line.
push af
ld a, (VDP_LINE)
call .incA
call vdpClrLine
; Also clear the line after this one
push af ; --> lvl 1
call .incA
call vdpClrLine
pop af ; <-- lvl 1
ld (VDP_LINE), a
pop af
ret
.incA:
inc a
cp 24
ret nz ; no rollover
; bottom reached, roll over to top of screen
xor a
ret
vdpBS:
call vdpClrPos
push af
ld a, (VDP_ROW)
or a
jr z, .lineup
dec a
ld (VDP_ROW), a
pop af
ret
.lineup:
; end of line
ld a, 31
ld (VDP_ROW), a
; we have to go one line up
ld a, (VDP_LINE)
or a
jr z, .nowrap
; We have to wrap to the bottom of the screen
ld a, 24
.nowrap:
dec a
ld (VDP_LINE), a
pop af
ret
; Clear tile under cursor
vdpClrPos:
push af
xor a ; space
call vdpSpitC
pop af
ret
; Clear line number A
vdpClrLine:
; see comments in vdpSpitC for VRAM details.
push af
; first, get the two LSB at MSB pos.
rrca \ rrca
push af ; --> lvl 1
and 0b11000000
; That's our first address byte
out (VDP_CTLPORT), a
pop af ; <-- lvl 1
; Then, get those 3 other bits at LSB pos. Our popped A has already
; done 2 RRCA, which means that everything is in place.
and 0b00000111
or 0x78
out (VDP_CTLPORT), a
; We're at the right place. Let's just spit 32*2 null bytes
xor a
push bc ; --> lvl 1
ld b, 64
.loop:
out (VDP_DATAPORT), a
djnz .loop
pop bc ; <-- lvl 1
pop af
ret
; Convert ASCII char in A into a tile index corresponding to that character.
; When a character is unknown, returns 0x5e (a '~' char).
vdpConv:
; The font is organized to closely match ASCII, so this is rather easy.
; We simply subtract 0x20 from incoming A
sub 0x20
cp 0x5f
ret c ; A < 0x5f, good
ld a, 0x5e
ret
; VDP initialisation data
vdpInitData:
; 0x8x == set register X
.db 0b00000100, 0x80 ; Bit 2: Select mode 4
.db 0b00000000, 0x81
.db 0b11111111, 0x82 ; Name table: 0x3800
.db 0b11111111, 0x85 ; Sprite table: 0x3f00
.db 0b11111111, 0x86 ; sprite use tiles from 0x2000
.db 0b11111111, 0x87 ; Border uses palette 0xf
.db 0b00000000, 0x88 ; BG X scroll
.db 0b00000000, 0x89 ; BG Y scroll
.db 0b11111111, 0x8a ; Line counter (why have this?)
vdpInitDataEnd: