Coding
Assembly Tutorial #3 – Key Input by Lee Stewart
In this program we are introducing a fair amount of new information. We will be writing small subroutines that we will branch to with a return address provided by the BL (Branch and Link) instruction. We already have learned a bit about the BLWP (Branch and Load Workspace Pointer) instruction, which we have used so far to execute TI-provided subroutines for reading and writing VRAM (VDP RAM) among other things. We will be using it again to process keyboard input in this program via a console subroutine called KSCAN, which, among other things, scans the keyboard for a keystroke and stores information in scratchpad RAM.
The last branch instruction we have yet to learn is B (Branch), which has no provision for returning to the caller as with BLWP and BL. We will use it four times in this program—once, explicitly and three times implicitly through the RT directive. The RT directive is a proxy for B *R11, which introduces a new kind of addressing, viz., workspace register indirect addressing. B *R11 means to branch to the address contained in register 11. Register 11 is special to the BL instruction. When the TMS9900 executes BL, it will save the address following the BL instruction in register 11 so we can return to it with RT (B *R11).
The unfortunate fact about the BL instruction is that it is limited to one level of branching unless we take pains to save the contents of Rll before branching to a second level. We actually do this in one of the subroutines (BSPACE) because we need to Branch and Link to a second subroutine (CURBL) from within BSPACE.
Before going any further, let's list the subroutines in the program with very brief functional descriptions:
- CLRSCR—Clears the screen by filling the SIT (Screen Image Table) with blanks.
- DSPMSG—Displays a message at the designated screen location. This subroutine expects the zero-based screen row in R0, the zero-based column in R1 and the address of the length word (2 bytes) that precedes the message.
- CURDSP—Displays an underscore at the current cursor position, which is kept in R7 throughout the program.
- CURBL—Blanks the current cursor position.
- BSPACE—Reacts to entry of backspace (ASCII 8) by blanking the cursor, backing up one space (unless at the first position) displaying the cursor in the new position and awaiting a new keystroke.
The PROMPT and GREET strings are stored in the same manner as MSG in the last tutorial, i.e., a length word followed by the message. Space for the user-entered string is provided by the BSS (Block Starting with Symbol) directive, BSS 20, which reserves 20 bytes for FNAME, the label preceding BSS.
This program also is the first in which we provide a program exit. When the break key (FCTN+4) is pressed during key entry or in the infinite loop at the end of the program, the program will exit to the TI-99/4A color-bar screen.
There are a couple of things we could do with this program that would improve it. One would be including a string-length check on the input. As it stands, you can enter a string as long as you like. This will cause a problem if it is long enough to overwrite the GREET string (39 characters). Overwriting the PROMPT string doesn't matter because we've already used it.
Another improvement would be a blinking cursor during keyboard input. We will add this embellishment in the next program dealing with keyboard input.
*=== KEYINPUT =================================================
* This program does the following:
* 1. Changes screen to TEXT mode
* 2. Clears screen
* 3. Changes text and screen colors to white on dark blue
* 4. Asks for keyboard input with "Your First Name?"
* 5. Echoes what is typed, allowing
* a. Correction with backspace
* b. Reset to TI title screen with (FCTN+4)
* 6. Displays the typed input as a greeting
* 7. Greeting remains on screen until detected
*
* The following registers are used to track the indicated values throughout
* the program. We could have used addresses, but this is not a very in-
* volved program and registers are faster, though speed is not really
* an issue here:
*
* R6 = first character position on the screen of keyboard input.
* R7 = cursor position on screen.
*================================================
REF VWTR,VSBW,VMBW,KSCAN reference E/A utilities
DEF START declare program's entry point for E/A loader
SCSTRT EQU 0 start of SIT (Screen Image Table)
SCWID EQU 40 screen width
SCEND EQU 960 screen size and address just past SIT
BREAK DATA >0200 ASCII code for (FCTN+4)
* Program entry point
START LI R0,>01B0 load R0 with TEXT mode and screen blank settings
* for VR01 (VDP register #1) while we change stuff
BLWP @VWTR Write to VR01
LI R0,>07F4 load R0 with screen and text color settings for VR07
BLWP @VWTR Write to VR07
*: BL = Branch and Link. We use it here to branch to the CLRSCR subroutine
*: because it will store the address of the instruction following the BL
*: instruction, allowing our program to continue.
BL @CLRSCR clear screen
* Turn display back on. We are using the console's KSCAN routine, which writes
* the contents of >83D4 to VR01--so, first, we need to copy to >83D4 what
* we will then put in VR01
LI R0,>01F0
*: SWPB = SWaP Bytes. It is used to swap the bytes of the contents of an
*: address or register.
SWPB R0 get >F0 to high byte
MOVB R0,@>83D4 copy byte (>F0) to >83D4 for KSCAN's use
SWPB R0 restore integrity of R0
BLWP @VWTR write VR01
* Display prompt. To do this we need to pass 3 values to DSPMSG. There is
* more than one way to do this. Here, we'll do it through registers.
* We have set up DSPMSG to expect these values in R0-R2.
LI R0,11 load screen row
*: CLR = CLeaR contents of address or register, i.e., replace with 16 zeros.
CLR R1 load screen column
LI R2,PROMPT RAM location of prompt text length word
BL @DSPMSG display prompt and update cursor position
* Get keyboard input
LI R5,FNAME+2 load FNAME buffer address
MOV R5,R6 use R6 to track start of input
GETNAM BL @CURDSP call cursor display routine
*: We will use the console's KSCAN subroutine to get keyboard input. When
*: KSCAN returns, we need to check bit >20 of the GPL status byte at
*: >837C to see whether a key was pressed. We can then get the ASCII
*: value of the key from >8375. If no key was pressed, this value will
*: be >FF.
BLWP @KSCAN get keyboard input
MOVB @>837C,R0 get status byte
*: ANDI = AND Immediate. ANDI performs a bitwise AND of the register contents
*: and the immediate value, storing the result in the register.
ANDI R0,>2000 check status byte for key press
*: JEQ = Jump if EQual. It performs the jump if the ST (Status Register)
*: equal bit is set.
JEQ GETNAM check key input again if not
CLR R1 clear R1 to allow easier byte comparisons
MOVB @>8375,R1 get character typed
*: CI = Compare Immediate. It compares the register contents with the imme-
*: iate value and sets the ST equal bit to 1 if they are the same or
*: resets it to 0 (clears), otherwise.
CI R1,>FF00 really a character?
JEQ GETNAM check key input again if not
CI R1,>0200 ?
JEQ EXIT if so, exit program
CI R1,>0800 ?
JNE NOTBSP jump if not
BL @BSPACE erase previous input
JMP GETNAM check key input again
NOTBSP CI R1,>0D00 ?
JEQ GETNMX if so, we're outta here!
MOV R7,R0 cursor position to R0
*: INC = INCrement by one the contents of the operand.
INC R7 increment cursor position
BLWP @VSBW echo character to display, replacing cursor
*: The addressing mode, represented by *R5+ below, is new to us. The '*'
*: makes the addressing indirect and the '+' auto-increments the contents
*: of the register, i.e., the referenced, indirect address, by 1 or 2,
*: depending on the nature of the instruction. A byte instruction, such
*: as MOVB, will increment by 1. A word instruction, such as MOV, will
*: increment by 2.
MOVB R1,*R5+ char to FNAME and inc cursor pos & FNAME pointer
JMP GETNAM check key input again
GETNMX BL @CURBL blank cursor
LI R1,FNAME+2 load start address of FNAME entry
*: S = Subtract contents of source (first) operand from contents of des-
*: tination (second) operand and store in destination operand.
S R1,R5 calculate name length
MOV R5,@FNAME store character count
BL @CLRSCR clear screen
LI R0,5 load screen row
LI R1,10 load screen column
LI R2,GREET RAM location of greeting text length word
BL @DSPMSG display greeting and update cursor position
* Display First Name stored in FNAME
MOV R7,R0 get cursor position to R0 for VRAM destination
LI R1,FNAME load address of length word
MOV *R1,R2 get length
*: A = Add contents of source operand to contents of destination operand
*: and store in destination operand.
A R2,R7 update cursor position
*: INCT = INCrement by Two the contents of the operand.
INCT R1 correct RAM source address of message
BLWP @VMBW write message to screen display
* display '!' after First Name
MOV R7,R0 get cursor position to R0 for VRAM destination
LI R1,>2100 load '!'
BLWP @VSBW write '!' to screen display
SPIN BLWP @KSCAN check keyboard
*: CB = Compare Bytes of the contents of the source operand with those of
*: the destination operand.
CB @>8375,@BREAK ?
JEQ EXIT exit program if so
JMP SPIN loop forever
* Exit program
EXIT CLR @>83C4 first, zero ISR (Interrupt Service Routine) hook--
* probably not necessary here, but it's a good habit
BLWP @>0000 exit to console power-up routine
*== Clear Screen Routine ====================================================
* Clear the screen by writing spaces to SIT
*
CLRSCR LI R0,SCEND-1 we're gonna start at the end of the SIT and work down
LI R1,>2000 writing blank (ASCII code=[>20] must be in MSB for VSBW)
* Loop writing 1 blank at a time to the SIT
BLANKS BLWP @VSBW write one blank to next lower SIT location
DEC R0 decrement by 1 the value in R0
JNE BLANKS if R0<>0, write another blank
*: RT = ReTurn to caller. Actually, this is a Pseudo-instruction that trans-
*: lates to B *R11, which uses register indirect addressing to branch to
*: the address contained in R11 (the return address).
RT
*== Display Message Routine ==================================================
* This routine expects the following information in R0-R2:
* R0 = zero-based screen row
* R1 = zero-based screen column
* R2 = address of length word preceding message
*
DSPMSG LI R3,SCWID load screen width
*: MPY = MultiPlY the contents of the destination register by the contents
*: of the source operand (register or address) and place the 32-bit,
*: right-justified product in two consecutive registers starting with
*: the destination register.
MPY R0,R3 cursor position of start of row
*: The 16-bit product we expect from the above multiplication is in R4.
A R1,R4 add column to cursor position
MOV R4,R7 we'll use R7 for the cursor position
MOV R4,R0 copy for display via VMBW
MOV R2,R1 get address of message text length word
INCT R1 correct to address of message text
MOV *R2,R2 message text length to R2
A R2,R7 adjust cursor position to end of message
BLWP @VMBW display message
RT return to caller
*== Cursor Display Routine ===================================================
* Display '_' (ASCII 95) at cursor position
*
CURDSP MOV R7,R0 cursor position to R0
LI R1,>5F00 load '_' to R1
BLWP @VSBW display cursor
RT return to caller
*== Cursor Blanking Routine ==================================================
* Write blank (ASCII 32) at cursor position
*
CURBL MOV R7,R0 cursor position to R0
LI R1,>2000 load ASCII blank to R1
BLWP @VSBW blank cursor
RT return to caller
*== Backspace Routine ========================================================
* Erase cursor. Adjust cursor position if not at start of input
*
*: C = Compare 16-bit contents of source and destination operands, setting
*: or resetting the ST equal bit.
BSPACE C R5,R6 first character?
JEQ BSPXIT return to caller if so
*: At this point, we need to save R11 because we're about to call another sub-
*: routine with BL, which will destroy our return to the main program. We'd
*: be stuck in this subroutine forever!
MOV R11,R4 save return
BL @CURBL call cursor blanking routine
LI R0,>2000 load blank
MOVB R0,*R5 blank last-written FNAME buffer position
DEC R5 decrement FNAME buffer pointer
DEC R7 decrement cursor pointer
*: B = Branch to address represented by the operand. In this case, *R4 means
*: the address contained in R4.
BSPXIT B *R4 return to caller (to saved address)
*== Various strings ==========================================================
*: BSS = Block Starting with Symbol and will reserve a buffer with the number
*: of bytes that follow BSS.
FNAME BSS 20 First Name storage--first word will be char count
PROMPT DATA 17 length of prompt text
TEXT 'Your First Name? '
EVEN
GREET DATA 11 length of greeting text
TEXT 'GREETINGS, '
EVEN
END START