diff --git a/emul/hw/sms/README.md b/emul/hw/sms/README.md index 606b354..f5d1e0c 100644 --- a/emul/hw/sms/README.md +++ b/emul/hw/sms/README.md @@ -16,10 +16,13 @@ Launch the emulator with `./sms /path/to/rom` (you can use the binary from the This will show a window with the screen's content on it. The mappings to the pad are: -* Arrows -* Z --> A -* X --> B -* C --> C -* S --> Start +* W --> Up +* A --> Left +* S --> Down +* D --> Right +* H --> A +* J --> B +* K --> C +* L --> Start Press ESC to quit. diff --git a/emul/hw/sms/sms.c b/emul/hw/sms/sms.c index ff9e9d7..516042a 100644 --- a/emul/hw/sms/sms.c +++ b/emul/hw/sms/sms.c @@ -180,29 +180,29 @@ void event_loop() bool ispressed = e->response_type == XCB_KEY_PRESS; switch (ev->detail) { case 0x09: return; // ESC - case 0x27: // S - pad_setbtn(&pad, PAD_BTN_START, ispressed); - break; - case 0x34: // Z - pad_setbtn(&pad, PAD_BTN_A, ispressed); - break; - case 0x35: // X - pad_setbtn(&pad, PAD_BTN_B, ispressed); - break; - case 0x36: // C - pad_setbtn(&pad, PAD_BTN_C, ispressed); - break; - case 0x62: + case 0x19: // W pad_setbtn(&pad, PAD_BTN_UP, ispressed); break; - case 0x64: + case 0x26: // A pad_setbtn(&pad, PAD_BTN_LEFT, ispressed); break; - case 0x66: + case 0x27: // S + pad_setbtn(&pad, PAD_BTN_DOWN, ispressed); + break; + case 0x28: // D pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed); break; - case 0x68: - pad_setbtn(&pad, PAD_BTN_DOWN, ispressed); + case 0x2b: // H + pad_setbtn(&pad, PAD_BTN_A, ispressed); + break; + case 0x2c: // J + pad_setbtn(&pad, PAD_BTN_B, ispressed); + break; + case 0x2d: // K + pad_setbtn(&pad, PAD_BTN_C, ispressed); + break; + case 0x2e: // L + pad_setbtn(&pad, PAD_BTN_START, ispressed); break; } break; diff --git a/kernel/grid.asm b/kernel/grid.asm new file mode 100644 index 0000000..16dd922 --- /dev/null +++ b/kernel/grid.asm @@ -0,0 +1,228 @@ +; grid - abstraction for grid-like video output +; +; Collapse OS doesn't support curses-like interfaces: too complicated. However, +; in cases where output don't have to go through a serial interface before +; being displayed, we have usually have access to a grid-like interface. +; +; Direct access to this kind of interface allow us to build an abstraction layer +; that is very much alike curses but is much simpler underneath. This unit is +; this abstraction. +; +; The principle is simple: we have a cell grid of X columns by Y rows and we +; can access those cells by their (X, Y) address. In addition to this, we have +; the concept of an active cursor, which will be indicated visually if possible. +; +; Additionally, this module provides a PutC routine, suitable for plugging into +; stdio. +; +; *** Defines *** +; +; GRID_COLS: Number of columns in the grid +; GRID_ROWS: Number of rows in the grid +; GRID_SETCELL: Pointer to routine that sets cell at row D and column E with +; character in A. If C is nonzero, this cell must be displayed, +; if possible, as the cursor. +; +; *** Consts *** +.equ GRID_SIZE GRID_COLS*GRID_ROWS + +; *** Variables *** +; Cursor's column +.equ GRID_CURX GRID_RAMSTART +; Cursor's row +.equ GRID_CURY @+1 +; Grid's in-memory buffer of the contents on screen. Because we always push to +; display right after a change, this is almost always going to be a correct +; representation of on-screen display. +; The buffer is organized as a rows of columns. The cell at row Y and column X +; is at GRID_BUF+(Y*GRID_COLS)+X. +.equ GRID_BUF @+1 +.equ GRID_RAMEND @+GRID_SIZE + +; *** Code *** + +gridInit: + xor a + ld b, GRID_RAMEND-GRID_RAMEND + ld hl, GRID_RAMSTART + jp fill + +; Place HL at row D and column E in the buffer +; Destroys A +_gridPlaceCell: + ld hl, GRID_BUF + ld a, d + or a + ret z + push de ; --> lvl 1 + ld de, GRID_COLS +.loop: + add hl, de + dec a + jr nz, .loop + pop de ; <-- lvl 1 + ; We're at the proper row, now let's advance to cell + ld a, e + jp addHL + +; Push row D in the buffer onto the screen. +gridPushRow: + push af + push bc + push de + push hl + ; Cursor off + ld c, 0 + ld e, c + call _gridPlaceCell + ld b, GRID_COLS +.loop: + ld a, (hl) + ; A, C, D and E have proper values + call GRID_SETCELL + inc hl + inc e + djnz .loop + + pop hl + pop de + pop bc + pop af + ret + +; Clear row D and push contents to screen +gridClrRow: + push af + push bc + push de + push hl + ld e, 0 + call _gridPlaceCell + xor a + ld b, GRID_COLS + call fill + call gridPushRow + + pop hl + pop de + pop bc + pop af + ret + +gridPushScr: + push de + ld d, GRID_ROWS-1 +.loop: + call gridPushRow + dec d + jp p, .loop + pop de + ret + +; Set character under cursor to A +gridSetCur: + push de + push hl + push af ; --> lvl 1 + ld a, (GRID_CURY) + ld d, a + ld a, (GRID_CURX) + ld e, a + call _gridPlaceCell + pop af \ push af ; <--> lvl 1 + ld (hl), a + call GRID_SETCELL + pop af ; <-- lvl 1 + pop hl + pop de + ret + +; Clear character under cursor +gridClrCur: + push af + ld a, ' ' + call gridSetCur + pop af + ret + +gridLF: + call gridClrCur + push de + push af + ld a, (GRID_CURY) + call .incA + ld d, a + call gridClrRow + ld (GRID_CURY), a + xor a + ld (GRID_CURX), a + pop af + pop de + ret +.incA: + inc a + cp GRID_ROWS + ret nz ; no rollover + ; bottom reached, stay on last line and scroll screen + push hl + push de + push bc + ld de, GRID_BUF + ld hl, GRID_BUF+GRID_COLS + ld bc, GRID_SIZE-GRID_COLS + ldir + pop bc + pop de + pop hl + call gridPushScr + dec a + ret + +gridBS: + call gridClrCur + push af + ld a, (GRID_CURX) + or a + jr z, .lineup + dec a + ld (GRID_CURX), a + pop af + ret +.lineup: + ; end of line, we need to go up one line. But before we do, are we + ; already at the top? + ld a, (GRID_CURY) + or a + jr z, .end + dec a + ld (GRID_CURY), a + ld a, GRID_COLS-1 + ld (GRID_CURX), a +.end: + pop af + ret + +gridPutC: + cp LF + jr z, gridLF + cp BS + jr z, gridBS + cp ' ' + ret c ; ignore unhandled control characters + + call gridSetCur + push af ; --> lvl 1 + ; Move cursor + ld a, (GRID_CURX) + cp GRID_COLS-1 + jr z, .incline + ; We just need to increase X + inc a + ld (GRID_CURX), a + pop af ; <-- lvl 1 + ret +.incline: + ; increase line and start anew + call gridLF + pop af ; <-- lvl 1 + ret diff --git a/kernel/sms/pad.asm b/kernel/sms/pad.asm index 308e0b9..c64e1d7 100644 --- a/kernel/sms/pad.asm +++ b/kernel/sms/pad.asm @@ -181,8 +181,7 @@ padGetC: ; no action button pressed, but because our pad status changed, update ; VDP before looping. ld a, (PAD_SELCHR) - call vdpConv - call vdpSpitC + call gridSetCur jp padGetC .return: ld a, LF diff --git a/kernel/sms/vdp.asm b/kernel/sms/vdp.asm index 903a00d..f130609 100644 --- a/kernel/sms/vdp.asm +++ b/kernel/sms/vdp.asm @@ -17,22 +17,12 @@ ; .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 +.equ VDP_COLS 32 +.equ VDP_ROWS 24 ; *** Code *** vdpInit: - xor a - ld (VDP_ROW), a - ld (VDP_LINE), a - ld hl, vdpInitData ld b, vdpInitDataEnd-vdpInitData ld c, VDP_CTLPORT @@ -107,19 +97,30 @@ vdpInit: 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: +; 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 + +; grid routine. Sets cell at row D and column E to character A +vdpSetCell: + call vdpConv ; store A away ex af, af' push bc - ld b, 0 ; we push rotated bits from VDP_LINE into B so + ld b, 0 ; we push rotated bits from D 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) + ld a, d sla a ; should always push 0, so no pushing in B sla a ; same sla a ; same @@ -127,9 +128,9 @@ vdpSpitC: sla a \ rl b sla a \ rl b ld c, a - ld a, (VDP_ROW) + ld a, e sla a ; A * 2 - or c ; bring in two low bits from VDP_LINE into high + or c ; bring in two low bits from D into high ; two bits out (VDP_CTLPORT), a ld a, b ; 3 low bits set @@ -142,147 +143,6 @@ vdpSpitC: 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 diff --git a/recipes/sms/glue.asm b/recipes/sms/glue.asm index f1ef91e..9836ed8 100644 --- a/recipes/sms/glue.asm +++ b/recipes/sms/glue.asm @@ -16,12 +16,16 @@ .equ PAD_RAMSTART RAMSTART .inc "sms/pad.asm" -.equ VDP_RAMSTART PAD_RAMEND .inc "sms/vdp.asm" +.equ GRID_RAMSTART PAD_RAMEND +.equ GRID_COLS VDP_COLS +.equ GRID_ROWS VDP_ROWS +.equ GRID_SETCELL vdpSetCell +.inc "grid.asm" -.equ STDIO_RAMSTART VDP_RAMEND +.equ STDIO_RAMSTART GRID_RAMEND .equ STDIO_GETC padGetC -.equ STDIO_PUTC vdpPutC +.equ STDIO_PUTC gridPutC .inc "stdio.asm" ; *** BASIC *** @@ -51,6 +55,7 @@ init: ld sp, RAMEND + call gridInit call padInit call vdpInit call basInit diff --git a/recipes/sms/kbd/glue.asm b/recipes/sms/kbd/glue.asm index 75a3fbc..7f376d1 100644 --- a/recipes/sms/kbd/glue.asm +++ b/recipes/sms/kbd/glue.asm @@ -18,12 +18,16 @@ .equ KBD_FETCHKC smskbdFetchKCB .inc "kbd.asm" -.equ VDP_RAMSTART KBD_RAMEND .inc "sms/vdp.asm" +.equ GRID_RAMSTART KBD_RAMEND +.equ GRID_COLS VDP_COLS +.equ GRID_ROWS VDP_ROWS +.equ GRID_SETCELL vdpSetCell +.inc "grid.asm" -.equ STDIO_RAMSTART VDP_RAMEND +.equ STDIO_RAMSTART GRID_RAMEND .equ STDIO_GETC kbdGetC -.equ STDIO_PUTC vdpPutC +.equ STDIO_PUTC gridPutC .inc "stdio.asm" ; *** BASIC *** @@ -64,6 +68,7 @@ init: out (0x3f), a call kbdInit + call gridInit call vdpInit call basInit jp basStart diff --git a/recipes/sms/romasm/glue.asm b/recipes/sms/romasm/glue.asm index d94a82a..2524c55 100644 --- a/recipes/sms/romasm/glue.asm +++ b/recipes/sms/romasm/glue.asm @@ -47,12 +47,16 @@ .equ KBD_FETCHKC smskbdFetchKCB .inc "kbd.asm" -.equ VDP_RAMSTART KBD_RAMEND .inc "sms/vdp.asm" +.equ GRID_RAMSTART KBD_RAMEND +.equ GRID_COLS VDP_COLS +.equ GRID_ROWS VDP_ROWS +.equ GRID_SETCELL vdpSetCell +.inc "grid.asm" -.equ STDIO_RAMSTART VDP_RAMEND +.equ STDIO_RAMSTART GRID_RAMEND .equ STDIO_GETC kbdGetC -.equ STDIO_PUTC vdpPutC +.equ STDIO_PUTC gridPutC .inc "stdio.asm" .equ MMAP_START 0xd700 @@ -124,6 +128,7 @@ init: call fsOn call kbdInit + call gridInit call vdpInit call basInit @@ -165,7 +170,7 @@ f1PutB: ld ix, FS_HANDLES+FS_HANDLE_SIZE jp fsPutB -; last time I checked, PC at this point was 0x1e92. Let's give us a nice margin +; last time I checked, PC at this point was 0x128f. Let's give us a nice margin ; for the start of ed. .fill 0x1f00-$ .bin "ed.bin"