;:ts=8 ; ; EXAMPLE PROGRAM #4 ; ; This is a collection of all the useful functions I have developed for ; the command line interpreters found in my projects. They are very easy ; to use and commented. Since seeing them used is much more instructive ; than reading the descriptions, I have tied them together in a dummy CLI ; that has a few 'do nothing' commands whose sole purpose it is to use all ; of the subroutines. ; ; Also included is the function "setbaud" which shows how to set different ; RS232 baud rates on the 8031's serial port. ; ; Internal memory allocation ; 00-07: Register bank 0 -- used for non-interrupt code ; 08-0F: Register bank 1 -- used for interrupt code ; 10-17: Register bank 2 \ ; 18-1F: Register bank 3 > misc. variables go here ; 20-2F: Bit addressable space / ; 30-7F: Generic memory -- used for stack .equ ireg0, 8 .org 0x10 .flag quietflag,0x20.0 .flag badflag,0x20.1 .flag xoffflag,0x20.2 .org 0x28 tabpos: .skip 1 bufptr: .skip 1 keycode: .skip 1 pausecount: .skip 1 ; External memory allocation .equ bufsize,127 .equ bufpage,0x27 .org 0x100*bufpage inbuf: .skip bufsize+1 .org 0x2800 ; CPU initialization ; ------------------ ; Initialize CPU registers. start: mov SP,#0x2f ; Stack at 30H mov PSW,#0 ; Registers at 0H mov TCON,#0 ; Disable timers mov IE,#0 ; Disable all interrupts mov IP,#0 mov PCON,#0 ; Initialize other registers. mov P1,#0xff mov P3,#0xff ; Serial port initialization ; -------------------------- ; Set both timers to free running, 8 bit auto reload. mov TMOD,#0x22 ; Start baud rate generator and initialize serial port. mov TH1,#0xfd setb TCON.6 mov SCON,#0x52 clr xoffflag mov tabpos,#0 mov dptr,#intromsg lcall puts ;--------------------------------------------------------------------------- ; Module: prompt ; ; Description: ; This is the top level command entry point. It prints a prompt, waits ; for an input string, decodes the command, and jumps to the appropriate ; command routine. ; ; Inputs: n/a ; Outputs: n/a prompt: mov a,#'>' lcall putchar lcall getline lcall skipspc jz prompt ; Decode the first word of the input string, and print an ; error message if it does not match any of the commands. mov dptr,#cmdtbl lcall matchtoken jnz got_cmd mov dptr,#invalidcmd lcall puts sjmp prompt ; Dispatch a command by executing the appropriate entry from ; the jump table. got_cmd: mov B,#3 mul ab mov dptr,#jmptbl-3 jmp @a+dptr ; Command jump table jmptbl: ljmp help_cmd ljmp help_cmd ljmp baud_cmd ljmp echo_cmd ljmp keytest_cmd ljmp chartest_cmd ljmp hextest_cmd ; Error message invalidcmd: .byte "Invalid command.",13,10,0 ; Command table cmdtbl: .byte "?",0,"HELP",0,"BAUD",0,"ECHO",0 .byte "KEYTEST",0,"CHARTEST",0,"HEXTEST",0,0 intromsg: .byte 13,10,"CLI DEMO",13,10,10 .byte "Type '?' or 'HELP' for commands.",13,10,10,0 ;--------------------------------------------------------------------------- ; Function: keytest_cmd ; ; Command syntax: KEYTEST ; ; Gets characters from the terminal and echos back the character codes in ; hex. Exits when ESC is received. keytest_cmd: lcall skipspc jz kt_ok mov dptr,#junkmsg lcall puts kt_ok: mov dptr,#ktmsg lcall puts kt_wait: jnb SCON.0,kt_wait mov a,SBUF clr SCON.0 push ACC lcall printhex2 mov a,#' ' lcall putchar pop ACC clr ACC.7 cjne a,#27,kt_wait lcall crlf ljmp prompt ktmsg: .byte "Type characters, press ESC to exit.",13,10,0 ;--------------------------------------------------------------------------- ; Function: chartest_cmd ; ; Command syntax: CHARTEST [ [ ...]] ; ; Echos back the ASCII characters requested. chartest_cmd: lcall skipspc jz ct_done mov r2,#' ' lcall getnum jb badflag,ct_done2 mov a,r1 lcall putchar sjmp chartest_cmd ct_done: lcall crlf ct_done2: ljmp prompt ;--------------------------------------------------------------------------- ; Function: hextest_cmd ; ; Command syntax: HEXTEST ; ; Evaluates the hexadecimal number (up to 4 digits) and prints in ; decimal. Since 16-bit decimal number routines aren't provided, prints ; the high and low byte separately. hextest_cmd: lcall skipspc jnz ht_ok ljmp inc_cmd ht_ok: mov r2,#' ' lcall gethex jb badflag,ht_ok2 mov dptr,#htstr1 lcall puts mov r2,#0 ; Field width = 0 (don't pad) mov a,r3 lcall printnum mov dptr,#htstr2 lcall puts mov r2,#0 mov a,r4 lcall printnum mov dptr,#htstr3 lcall puts lcall skipspc jz ht_ok2 mov dptr,#junkmsg lcall puts ht_ok2: ljmp prompt htstr1: .byte "The high byte is ",0 htstr2: .byte " in decimal. The low byte is ",0 htstr3: .byte " in decimal.",13,10,0 ;--------------------------------------------------------------------------- ; Function: echo_cmd ; ; Command syntax: ECHO [] ; ; Echos back if present. echo_cmd: lcall skipspc jz ec_done2 echo_loop: lcall bgetchar jz ec_done lcall putchar sjmp echo_loop ec_done: lcall crlf ec_done2: ljmp prompt ;--------------------------------------------------------------------------- ; Function: help_cmd ; ; Command syntax: HELP ; ? ; ; Inputs: None ; Outputs: None ; help_cmd: mov dptr,#helptbl lcall puts ljmp prompt ; Help for user commands. helptbl: .byte 13,10,"? or Help " .byte "Get this help" .byte 13,10,"Baud <110|300|1200|2400|4800|9600|19200> " .byte "Set baud rate" .byte 13,10,"Echo [] " .byte "Echo back if present" .byte 13,10,"Keytest " .byte "Echo key codes" .byte 13,10,"Chartest [ [ ...]] " .byte "Generate ASCII characters" .byte 13,10,"HExtest " .byte "Convert hex to decimal" .byte 13,10,10,"All string tokens can be abbreviated to " .byte "fewer characters,",13,10 .byte "e.g. 'B 1' to set 110 baud or 'B 12' to set 1200." .byte 13,10,10,0 ;--------------------------------------------------------------------------- ; Function: baud_cmd ; ; Description: ; This function sets the baud rate of the serial port. The user must ; change the baud rate of the terminal to continue using the CLI. ; ; Command syntax: BAUD ; ; Inputs: Taken from the command line ; Outputs: None ; ; Get the baud rate from the command line and exit if not ; valid. baud_cmd: lcall skipspc jnz cmd_ok ljmp inc_cmd cmd_ok: mov dptr,#baudnametbl lcall matchtoken jz badbaud lcall setbaud lcall skipspc jnz junkparms ljmp prompt ; Error exit. badbaud: mov dptr,#baudmsg lcall puts baudret: ljmp prompt ; Baud rate match table. baudnametbl: .byte "110",0,"300",0,"1200",0,"2400",0,"4800",0,"9600",0 .byte "19200",0,0 ; Error message. baudmsg: .byte "Invalid or unsupported baud rate.",13,10,0 inc_cmd: mov dptr,#icmsg lcall puts ljmp prompt icmsg: .byte "Incomplete command.",13,10,0 junkparms: mov dptr,#junkmsg lcall puts ljmp prompt junkmsg: .byte "Extra parameters ignored.",13,10,0 ;--------------------------------------------------------------------------- ; Function: setbaud ; ; Description: ; This routine sets the baud rate for the 8031's built-in serial port. ; ; Inputs: A - baud rate code (1 = 110 bps, 2 = 300 bps, ... 7 = 19200 bps). ; ; Outputs: None ; ; Wait for any last character to be transmitted. setbaud: jnb SCON.1,setbaud mov r0,a ; Stop the baud rate counter. clr TCON.6 ; Load SMOD and TH1 from the tables. mov dptr,#baudth1tbl-1 movc a,@a+dptr mov TH1,a mov dptr,#baudsmodtbl-1 mov a,r0 movc a,@a+dptr jz clearsmod orl PCON,#80h sjmp baud_done clearsmod: anl PCON,#7fh baud_done: mov SCON,#52h ; Restart the baud rate counter and exit. setb TCON.6 ret ; Tables of values for TH1 and SMOD. baudth1tbl: .byte 0,0a0h,0e8h,0f4h,0fah,0fdh,0fdh baudsmodtbl: .byte 0,0,0,0,0,0,1 ;--------------------------------------------------------------------------- ; Function: printhex2 ; ; Description: ; Prints the value in A as 2 hexadecimal digits. ; ; Inputs: A - number to print ; ; Outputs: None ; printhex2: mov R0,a swap a acall prtdig mov a,R0 prtdig: anl a,#0fh cjne a,#10,prt2 prt2: jc notalpha add a,#7 notalpha: add a,#'0' ljmp putchar ;--------------------------------------------------------------------------- ; Function: puts ; ; Description: ; Prints a zero-terminated string pointed to by DPTR. ; ; Inputs: DPTR - address of string. ; ; Outputs: None. ; puts: movx A,@DPTR jz prtdone prtwait: lcall putchar inc DPTR sjmp puts prtdone: ret ;--------------------------------------------------------------------------- ; Function: tab ; ; Description: ; Moves the cursor to the indicated column, if not already past it. ; ; Inputs: A - desired column. ; ; Outputs: None. ; tab: clr C subb A,tabpos jc tab_exit jz tab_exit mov R0,A tabloop: mov a,#' ' lcall putchar djnz R0,tabloop tab_exit: ret ;--------------------------------------------------------------------------- ; Function: crlf ; ; Description: ; Prints and ; ; Inputs: None. ; ; Outputs: None. ; crlf: mov A,#13 lcall putchar mov A,#10 ljmp putchar ;--------------------------------------------------------------------------- ; Function: printnum ; ; Description: ; This function prints the number contained in A, in decimal. It right ; justifies the result in a field of given width, using a given pad ; character. ; ; Inputs: A - the number to be printed. ; R1 - pad character. ; R2 - field width. ; ; Outputs: None ; Since the digits are generated in reverse order, store ; them on the stack for use later. This is OK, since only ; 3 digits, plus a terminating character, will ever be ; pushed. ; Push a -1 on the stack to terminate the character list. printnum: mov B,#0ffh push B ; Divide A by 10, and push the remainder on the stack. The ; result stays in A, and the operation is repeated until ; nothing is left. Subtract 1 from the field width for ; each digit pushed. printloop: mov B,#10 div AB push B dec R2 jnz printloop ; Check if the field width is still positive. mov A,R2 jz nopad jb ACC.7,nopad ; It is, so put out however many bytes of padding required. padloop: mov A,R1 lcall putchar djnz R2,padloop ; Now pop the digits off the stack and print them. The -1 ; will generate a carry when '0' is added to it, and ; terminate the loop. nopad: pop ACC add A,#'0' jc printdone lcall putchar sjmp nopad printdone: ret ;--------------------------------------------------------------------------- ; Function: skipspc ; ; Description: ; Advances the command line input buffer pointer until a non-space ; character or the terminating zero is found. Does not advance the ; pointer past the termintating zero even if called more than once. ; Returns the first nonspace character found. ; ; Inputs: None ; ; Outputs: A - first nonspace character ; ; Use P2 and R0 to access the buffer, rather than ; corrupting the DPTR. skipspc: mov P2,#bufpage mov R0,bufptr skiploop: movx A,@R0 jz skipdone cjne A,#' ',skipdone inc R0 mov bufptr,R0 sjmp skiploop skipdone: ret ;--------------------------------------------------------------------------- ; Function: bgetchar ; ; Description: ; Gets one character from the command line input buffer, and advances ; the buffer pointer. Does not advance the pointer if the terminating ; zero was found. ; ; Inputs: None ; ; Outputs: A - character from the buffer ; ; Use P2 and R0 to access the buffer, rather than ; corrupting the DPTR. bgetchar: mov P2,#bufpage mov r0,bufptr movx a,@r0 jz gotzero inc bufptr gotzero: ret ;--------------------------------------------------------------------------- ; Function: matchtoken ; ; Description: ; Given the current position in the command line buffer, which must be ; on a non-space character, matches the current word against a table of ; tokens. Abbreviations are allowed. Can use a token table where each ; entry is "padded" to the same length, to allow scanning of tables in ; which the words are entered by the user. ; ; Inputs: DPTR - pointer to token table ; ; Outputs: A - number of the token which was matched or zero if none. ; ; Initialize P2 (upper 8 bits of external memory address) ; and the token number counter. matchtoken: mov P2,#bufpage mov r1,#1 ; Outer loop: Check the first character of the token in ; the table. If zero, the end of the table has been ; reached. trynext: movx a,@dptr jz nomatch ; Point at the beginning of the word in the buffer. mov r0,bufptr ; Get a character from the buffer. If zero or space, ; we have a successful abbreviation match (it is up to ; the caller to ensure that the buffer pointer does not ; point to a space or zero to begin with; they would ; match anything. matchloop: movx a,@r0 jz gotmatch clr C subb a,#' ' jz gotmatch ; The buffer pointer is a nonblank character. Get it ; again and compare to the character from the token table. movx a,@r0 lcall toupper mov r2,a movx a,@dptr ; Loop until a mismatch occurs or the end of the word on ; the command line is reached. cjne a,2,mismatch inc dptr inc r0 sjmp matchloop ; A mismatch occurred. Skip the rest of the token in the ; token table. mismatch: inc dptr movx a,@dptr jnz mismatch ; Skip any padding (CTRL-A = 1) after the terminating zero ; of the token in the token table. skip_pad: inc dptr movx a,@dptr dec a jz skip_pad ; Increment the token counter and try to match the next token ; in the table (to which DPTR now points). inc r1 sjmp trynext ; A match was obtained. Update the buffer pointer to point ; past the matched word, and return the token number. gotmatch: mov bufptr,r0 mov a,r1 nomatch: ret ;--------------------------------------------------------------------------- ; Functions: getnum, getnumquiet ; ; Description: ; Given the current position in the command line buffer, which must be ; on a non-space character, reads a decimal number from the buffer. The ; number must be terminated by a null or the character given in R2. If ; an error is encountered, "badflag" is set. "getnum" will also print ; an error message. ; ; Inputs: R2 - character (other than 0) which may terminate the number. ; Usually set to a space. ; ; Outputs: R1 - the number which was read. ; badflag - set if an error occurred and R1 is not valid. ; getnumquiet: setb quietflag sjmp getnumcont getnum: clr quietflag ; Continue here for both functions. Initially clear the ; error flag, and set the number accumulator to zero. Then ; get the first digit. getnumcont: clr badflag mov r1,#0 lcall bgetchar ; Digit loop. Convert the current character to a binary ; value, and exit with an error if it is not valid. nextdigit: clr C subb a,#'0' jc badnum cjne a,#10,dig3 dig3: jnc badnum ; Multiply the number accumulator by 10 and add the current ; digit. Exit with an error if the result would exceed 255. xch a,r1 mov B,#10 mul AB jb OV,badnum add a,r1 jc badnum mov r1,a ; Get the next digit, and return if it is either zero or the ; value in R2. This check is done here since there must be ; at least one digit. lcall bgetchar jz numdone cjne a,2,nextdigit numdone: ret ; Error exit. badnum: setb badflag jb quietflag,numdone mov dptr,#badnumstr ljmp puts badnumstr: .byte "Invalid number entered.",13,10,0 ;--------------------------------------------------------------------------- ; Function: toupper ; ; Description: ; Converts a character to uppercase if it is a lowercase alphabetic, ; otherwise leaves it alone. ; ; Inputs: A - character. ; ; Outputs: A - character converted to uppercase. ; toupper: push ACC clr C subb a,#'a' jc notlower subb a,#26 jnc notlower pop ACC subb a,#1fh ret notlower: pop ACC ret ;--------------------------------------------------------------------------- ; Function: putchar ; ; Description: ; Prints a character to the controlling terminal. Also maintains the ; "tab position" counter required by the "tab" function. ; ; Absorbs characters coming in from the serial port (since we don't ; do typeahead). This allows XON/XOFF output control. ; ; Inputs: A - character to print. ; ; Outputs: None. ; putchar: push ACC sploop: lcall getchar jb xoffflag,sploop jnb SCON.1,sploop pop ACC mov SBUF,A clr SCON.1 cjne A,#13,notcr2 mov tabpos,#0 ret notcr2: clr C subb A,#' ' jc put_exit inc tabpos put_exit: ret ;--------------------------------------------------------------------------- ; Function: getchar ; ; Description: ; Gets a character from the serial port. Also handles XON/XOFF. ; Returns character in A, or zero if no character. getchar: clr A jnb SCON.0,notxon mov a,SBUF clr SCON.0 clr ACC.7 ; Clear parity bit cjne a,#19,notxoff setb xoffflag sjmp getchar notxoff: cjne a,#17,notxon clr xoffflag sjmp getchar notxon: ret ;--------------------------------------------------------------------------- ; Function: getline ; ; Description: ; "getline" gets a command line from the controlling terminal into the ; line input buffer. ; ; Inputs: None. ; getline: mov P2,#bufpage mov r1,#0 gloop: lcall getchar jz gloop ; Check for backspace or rubout. cjne a,#8,notbs sjmp delchar notbs: cjne a,#127,notdel ; Delete a character, but only if the buffer is not empty. delchar: mov a,r1 jz gloop lcall do1del sjmp gloop ; Check for carriage return. If it is one, zero-terminate ; the buffer, echo a CRLF, initialize the input buffer ; pointer, and return. notdel: cjne a,#13,notcr clr a movx @r1,a mov bufptr,a ljmp crlf ; Check for CTRL-X. If it is one, delete characters until ; nothing is left of the line. notcr: cjne a,#24,notctlx cloop: mov a,r1 jz gloop lcall do1del sjmp cloop ; That's it for the control characters. If the character ; isn't printable and hasn't been handled by now, get rid ; of it. notctlx: movx @r1,a anl a,#0e0h jz gloop ; Check if the input line is at maximum length already. ; If it is, don't advance the buffer pointer and don't ; echo the character. mov a,r1 clr c subb a,#bufsize jnc gloop ; Advance the buffer pointer and echo the character if echo ; is enabled. Otherwise, echo a space. movx a,@r1 inc r1 lcall putchar sjmp gloop ; Delete one character by decrementing the buffer pointer and ; printing . do1del: mov a,#8 lcall putchar mov a,#32 lcall putchar mov a,#8 lcall putchar dec r1 ret ;--------------------------------------------------------------------------- ; Function: gethex, gethexquiet ; ; Description: ; Evaluates a hexadecimal number of up to 4 digits from the command ; input buffer. If an error is encountered, "gethex" will print a message, ; whereas "gethexquiet" will not. Both will set the "badflag". ; ; Inputs: R2 - character other than zero which may terminate the number. ; ; Outputs: R3 - high 8 bits of number entered. ; R4 - low 8 bits of number entered. ; gethexquiet: setb quietflag sjmp gethexcont gethex: clr quietflag ; Continue here for both functions. Initialize the error ; flag and the number accumulator, then get the first digit. gethexcont: clr badflag mov r3,#0 mov r4,#0 lcall bgetchar ; Convert the character to a binary value, and exit with ; an error if it is not a valid hex digit. nexthexdigit: lcall toupper clr c subb a,#'0' jc badhex cjne a,#10,hexdig2 hexdig2: jc dodigit cjne a,#17,hexdig3 hexdig3: jc badhex clr c subb a,#7 cjne a,#16,hexdig4 hexdig4: jnc badhex ; A now contains the digit in binary form. Save it away and ; shift the number accumulator left by 4 bits. Exit with ; an error if that would overflow it. dodigit: mov r1,a mov a,r3 anl a,#0f0h jnz badhex mov a,r4 anl a,#0f0h orl a,r3 swap a mov r3,a mov a,r4 swap a anl a,#0f0h ; Finish up by adding in the digit saved earlier. orl a,r1 mov r4,a ; Get the next digit and continue unless it is one of the ; termination characters. lcall bgetchar jz hexdone cjne a,2,nexthexdigit hexdone: ret ; Error exit. badhex: mov dptr,#badhexstr lcall puts setb badflag ret badhexstr: .byte "Invalid hexadecimal number.",13,10,0