diff --git a/parts/core.asm b/parts/core.asm new file mode 100644 index 0000000..e6ad6cc --- /dev/null +++ b/parts/core.asm @@ -0,0 +1,193 @@ +; core +; +; Routines used by pretty much all parts. You will want to include it first +; in your glue file. + +; *** CONSTS *** +ASCII_CR .equ 0x0d +ASCII_LF .equ 0x0a + +; *** CODE *** + +; add the value of A into DE +addDE: + add a, e + jr nc, .end ; no carry? skip inc + inc d +.end: + ld e, a + ret + +; Format the lower nibble of A into a hex char and stores the result in A. +fmtHex: + and a, 0xf + cp 10 + jr nc, .alpha ; if >= 10, we have alpha + add a, '0' + ret +.alpha: + add a, 'A'-10 + ret + +; Formats value in A into a string hex pair. Stores it in the memory location +; that HL points to. Does *not* add a null char at the end. +fmtHexPair: + push af + + ; let's start with the rightmost char + inc hl + call fmtHex + ld (hl), a + + ; and now with the leftmost + dec hl + pop af + push af + and a, 0xf0 + rra \ rra \ rra \ rra + call fmtHex + ld (hl), a + + pop af + ret + +; jump to the location pointed to by HL. This allows us to call HL instead of +; just jumping it. +jumpHL: + jp hl + ret + +; Parse the hex char at A and extract it's 0-15 numerical value. Put the result +; in A. +; +; On success, the carry flag is reset. On error, it is set. +parseHex: + ; First, let's see if we have an easy 0-9 case + cp '0' + jr c, .error ; if < '0', we have a problem + cp '9'+1 + jr nc, .alpha ; if >= '9'+1, we might have alpha + ; We are in the 0-9 range + sub a, '0' ; C is clear + ret + +.alpha: + call upcase + cp 'A' + jr c, .error ; if < 'A', we have a problem + cp 'F'+1 + jr nc, .error ; if >= 'F', we have a problem + ; We have alpha. + sub a, 'A'-10 ; C is clear + ret + +.error: + scf + ret + +; Parses 2 characters of the string pointed to by HL and returns the numerical +; value in A. If the second character is a "special" character (<0x21) we don't +; error out: the result will be the one from the first char only. +; +; On success, the carry flag is reset. On error, it is set. +parseHexPair: + push bc + push hl + + ld a, (hl) + call parseHex + jr c, .end ; error? goto end, keeping the C flag on + rla \ rla \ rla \ rla ; let's push this in MSB + ld b, a + inc hl + ld a, (hl) + ;cp 0x21 + ;jr c, .single ; special char? single digit + call parseHex + jr c, .end ; error? + or b ; join left-shifted + new. we're done! + ; C flag was set on parseHex and is necessarily clear at this point + jr .end + +.single: + ; If we have a single digit, our result is already stored in B, but + ; we have to right-shift it back. + ld a, b + and a, 0xf0 + rra \ rra \ rra \ rra + +.end: + pop hl + pop bc + ret + +; print null-terminated string pointed to by HL +printstr: + push af + push hl + +.loop: + ld a, (hl) ; load character to send + or a ; is it zero? + jr z, .end ; if yes, we're finished + call aciaPutC + inc hl + jr .loop + +.end: + pop hl + pop af + ret + +; print A characters from string that HL points to +printnstr: + push bc + push hl + + ld b, a +.loop: + ld a, (hl) ; load character to send + call aciaPutC + inc hl + djnz .loop + +.end: + pop hl + pop bc + ret + +; Compares strings pointed to by HL and DE up to A count of characters. If +; equal, Z is set. If not equal, Z is reset. +strncmp: + push bc + push hl + push de + + ld b, a +.loop: + ld a, (de) + cp (hl) + jr nz, .end ; not equal? break early + inc hl + inc de + djnz .loop + +.end: + pop de + pop hl + pop bc + ; Because we don't call anything else than CP that modify the Z flag, + ; our Z value will be that of the last cp (reset if we broke the loop + ; early, set otherwise) + ret + +; Transforms the character in A, if it's in the a-z range, into its upcase +; version. +upcase: + cp 'a' + ret c ; A < 'a'. nothing to do + cp 'z'+1 + ret nc ; A >= 'z'+1. nothing to do + ; 'a' - 'A' == 0x20 + sub 0x20 + ret diff --git a/parts/shell.asm b/parts/shell.asm index 56d2b10..9d4008d 100644 --- a/parts/shell.asm +++ b/parts/shell.asm @@ -13,8 +13,6 @@ ; See constants below for error codes. ; *** CONSTS *** -CR .equ 0x0d -LF .equ 0x0a ; number of entries in shellCmdTbl SHELL_CMD_COUNT .equ 2 @@ -25,8 +23,19 @@ SHELL_ERR_UNKNOWN_CMD .equ 0x01 ; Arguments for the command weren't properly formatted SHELL_ERR_BAD_ARGS .equ 0x02 +; *** VARIABLES *** +; Memory address that the shell is currently "pointing at" for peek and deek +; operations. Set with seek. +SHELL_MEM_PTR .equ SHELL_RAMSTART +; Used to store formatted hex values just before printing it. +SHELL_HEX_FMT .equ SHELL_MEM_PTR+2 +SHELL_RAMEND .equ SHELL_HEX_FMT+2 + ; *** CODE *** shellInit: + xor a + ld (SHELL_MEM_PTR), a + ; print prompt ld hl, .prompt call printstr @@ -40,32 +49,14 @@ shellLoop: call chkbuf jr shellLoop -; print null-terminated string pointed to by HL -printstr: - push af - push hl - -.loop: - ld a, (hl) ; load character to send - or a ; is it zero? - jr z, .end ; if yes, we're finished - call aciaPutC - inc hl - jr .loop - -.end: - pop hl - pop af - ret printcrlf: - ld a, CR + ld a, ASCII_CR call aciaPutC - ld a, LF + ld a, ASCII_LF call aciaPutC ret - ; check if the input buffer is full or ends in CR or LF. If it does, prints it ; back and empty it. chkbuf: @@ -81,9 +72,9 @@ chkbuf: ld a, (hl) ; now, that's our char we have in A inc hl ; put HL back where it was - cp CR + cp ASCII_CR jr z, .do ; char is CR? do! - cp LF + cp ASCII_LF jr z, .do ; char is LF? do! ; nothing matched? don't do anything @@ -101,51 +92,12 @@ chkbuf: call shellParse ret -; Compares strings pointed to by HL and DE up to A count of characters. If -; equal, Z is set. If not equal, Z is reset. -strncmp: - push bc - push hl - push de - - ld b, a -.loop: - ld a, (de) - cp (hl) - jr nz, .end ; not equal? break early - inc hl - inc de - djnz .loop - -.end: - pop de - pop hl - pop bc - ; Because we don't call anything else than CP that modify the Z flag, - ; our Z value will be that of the last cp (reset if we broke the loop - ; early, set otherwise) - ret - -; add the value of A into DE -addDE: - add a, e - jr nc, .end ; no carry? skip inc - inc d -.end: - ld e, a - ret - -; jump to the location pointed to by HL. This allows us to call HL instead of -; just jumping it. -jumpHL: - jp hl - ret - ; Parse command (null terminated) at HL and calls it shellParse: push af push bc push de + push hl ld de, shellCmdTbl ld a, SHELL_CMD_COUNT @@ -165,19 +117,34 @@ shellParse: jr .end .found: + ; all right, we're almost ready to call the cmd. Let's just have DE + ; point to the cmd jump line. ld a, 4 call addDE - ex hl, de + ; Now, let's swap HL and DE because, welll because that's how we're set. + ex hl, de ; HL = jump line, DE = cmd str pointer + + ; Before we call our command, we want to set up the pointer to the arg + ; list. Normally, it's DE+5 (DE+4 is the space) unless DE+4 is null, + ; which means no arg. + ld a, 4 + call addDE + ld a, (DE) + cp 0 + jr z, .noarg ; char is null? we have no arg + inc de +.noarg: + ; DE points to args, HL points to jump line. Ready to roll! call jumpHL - ex hl, de .end: + pop hl pop de pop bc pop af ret -; Print the error code set in A (doesn't work for codes > 9 yet...) +; Print the error code set in A (in hex) shellPrintErr: push af push hl @@ -185,9 +152,10 @@ shellPrintErr: ld hl, .str call printstr - ; ascii for '0' is 0x30 - add a, 0x30 - call aciaPutC + ld hl, SHELL_HEX_FMT + call fmtHexPair + ld a, 2 + call printnstr call printcrlf pop hl @@ -198,19 +166,66 @@ shellPrintErr: .db "ERR ", 0 ; *** COMMANDS *** +; When these commands are called, DE points to the first character of the +; command args. + +; Set memory pointer to the specified address. +; Example: seek 01fe + shellSeek: - ld hl, .str - call printstr + push de + push hl + + ex de, hl + call parseHexPair + jr c, .error + ld (SHELL_MEM_PTR), a + inc hl + inc hl + call parseHexPair + jr c, .error + ld (SHELL_MEM_PTR+1), a + jr .success + +.error: + ld a, SHELL_ERR_BAD_ARGS + call shellPrintErr + jr .end + +.success: + ld a, (SHELL_MEM_PTR) + ld hl, SHELL_HEX_FMT + call fmtHexPair + ld a, 2 + call printnstr + ld a, (SHELL_MEM_PTR+1) + call fmtHexPair + ld a, 2 + call printnstr + call printcrlf + +.end: + pop hl + pop de ret -.str: - .db "seek called", CR, LF, 0 + +; peek byte where memory pointer points to aby display its value shellPeek: - ld hl, .str - call printstr + push af + push hl + + ld hl, (SHELL_MEM_PTR) + ld a, (hl) + ld hl, SHELL_HEX_FMT + call fmtHexPair + ld a, 2 + call printnstr + call printcrlf + + pop hl + pop af ret -.str: - .db "peek called", CR, LF, 0 ; Format: 4 bytes name followed by 2 bytes jump. fill names with zeroes shellCmdTbl: diff --git a/recipes/rc2014.md b/recipes/rc2014.md index 73486b0..7dcc4e0 100644 --- a/recipes/rc2014.md +++ b/recipes/rc2014.md @@ -47,7 +47,7 @@ This is what your glue code would look like: jr init .fill 0x38-$ - jr aciaInt + jp aciaInt init: di @@ -63,8 +63,10 @@ init: ei call shellLoop +#include "core.asm" ACIA_RAMSTART .equ RAMSTART #include "acia.asm" +SHELL_RAMSTART .equ ACIA_RAMEND #include "shell.asm" ``` @@ -83,11 +85,9 @@ We set interrupt mode to 1 because that's what `acia.asm` is written around. Then, we init ACIA, shell, enable interrupt and give control of the main loop to `shell.asm`. -What comes below is actual code include from the acia and shell modules. As you -can see, we need to tell each module where to put their variables. `shell.asm` -doesn't have variables, but if it did, we would have a `SHELL_RAMSTART .equ -ACIA_RAMEND` just below the `acia.asm` include. `ACIA_RAMEND` is defined in -`acia.asm`. +What comes below is actual code include from parts we want to include in our +OS. As you can see, we need to tell each module where to put their variables. +See `parts/README.md` for details. ### Build the image