; kMouse.asm ; (c) 2017 David Hunter ; Implements a variation of Mouse for the Hackaday 1kB challenge 2017 ; The commands are based on the Mouse programming language first developed ; by Peter Grogono ~ 1975. I first read about it from Byte Magazine July 1979. ; The major difference is the use of hexadecimal for all values rather than ; decimal. The command set has been reduced down to something similar to ; Tiny BASIC also from ~ 1975. Some changes to the original command set were ; made to better accomodate the small space and PIC18. ; The one item to note is that access to the registers of the PIC18 is ; possible using byte read (peek) and write (poke) commands. ; ; Tested on a PIC18LF2620. The internal oscillator is used and the PLL ; enabled, so the internal clock is at 32MHz. ; ; User I/O is via a serial port: 57600,8N1 ; (an FTDI cable was used for development) ; ; ; All 16 bit variables use a "Big Endian" format ; addr = high byte, addr+1 = low byte ; ; The stacks are conventional and go from high to low memory ; the stack pointer points to the next unused value on the stack ; (calstk) calculation stack is a 16 bit stack ; (envstk) environment stack is a 16 bit stack holding the character position ; for a macro call ; ; The DEBUG definition is to enable debugging tools used in developing and ; testing kMouse. They are not used in normal operation. #include "p18F2620.inc" ;*************** ; CONSTANTS ; CR equ '\r' LF equ '\n' BS equ 0x08 ; backspace CAN equ 0x03 ; SPC equ ' ' PROMPT equ '.' ; use a period as a prompt EOF equ 0xFF ; end of file character STKSIZ equ .32 ; calculation stack size (16 words) MAXLVL equ 8 ; maximum number of nested macros ENVSIZ equ MAXLVL ; environment stack size MACSIZ equ .26*2 ; macro definitions size VARSIZ equ .26*2 ; variable data space ;*************** ; Macros and defines #define GETNEXT movff POSTINC0,ch ; get next program character #define UNGET movf POSTDEC0,W ; back up program counter ;#define DEBUG ; for debugging of the interpreter ; PIC18F2620 Configuration Bit Settings ; CONFIG1H CONFIG OSC = INTIO67 ; Oscillator Selection bits (Internal oscillator block, port function on RA6 and RA7) CONFIG FCMEN = OFF ; Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled) CONFIG IESO = OFF ; Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled) ; CONFIG2L CONFIG PWRT = OFF ; Power-up Timer Enable bit (PWRT disabled) CONFIG BOREN = SBORDIS ; Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled)) CONFIG BORV = 3 ; Brown Out Reset Voltage bits (Minimum setting) ; CONFIG2H CONFIG WDT = OFF ; Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit)) CONFIG WDTPS = 32768 ; Watchdog Timer Postscale Select bits (1:32768) ; CONFIG3H CONFIG CCP2MX = PORTC ; CCP2 MUX bit (CCP2 input/output is multiplexed with RC1) CONFIG PBADEN = OFF ; PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset) CONFIG LPT1OSC = OFF ; Low-Power Timer1 Oscillator Enable bit (Timer1 configured for higher power operation) CONFIG MCLRE = ON ; MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled) ; CONFIG4L CONFIG STVREN = ON ; Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset) CONFIG LVP = OFF ; Single-Supply ICSP Enable bit (Single-Supply ICSP disabled) CONFIG XINST = OFF ; Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode)) ; CONFIG5L CONFIG CP0 = OFF ; Code Protection bit (Block 0 (000800-003FFFh) not code-protected) CONFIG CP1 = OFF ; Code Protection bit (Block 1 (004000-007FFFh) not code-protected) CONFIG CP2 = OFF ; Code Protection bit (Block 2 (008000-00BFFFh) not code-protected) CONFIG CP3 = OFF ; Code Protection bit (Block 3 (00C000-00FFFFh) not code-protected) ; CONFIG5H CONFIG CPB = OFF ; Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected) CONFIG CPD = OFF ; Data EEPROM Code Protection bit (Data EEPROM not code-protected) ; CONFIG6L CONFIG WRT0 = OFF ; Write Protection bit (Block 0 (000800-003FFFh) not write-protected) CONFIG WRT1 = OFF ; Write Protection bit (Block 1 (004000-007FFFh) not write-protected) CONFIG WRT2 = OFF ; Write Protection bit (Block 2 (008000-00BFFFh) not write-protected) CONFIG WRT3 = OFF ; Write Protection bit (Block 3 (00C000-00FFFFh) not write-protected) ; CONFIG6H CONFIG WRTC = OFF ; Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected) CONFIG WRTB = OFF ; Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected) CONFIG WRTD = OFF ; Data EEPROM Write Protection bit (Data EEPROM not write-protected) ; CONFIG7L CONFIG EBTR0 = OFF ; Table Read Protection bit (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks) CONFIG EBTR1 = OFF ; Table Read Protection bit (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks) CONFIG EBTR2 = OFF ; Table Read Protection bit (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks) CONFIG EBTR3 = OFF ; Table Read Protection bit (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks) ; CONFIG7H CONFIG EBTRB = OFF ; Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks) ; variables cblock 0 temp:2 ; temporary 16 bit variable x:2 ; temporary 16 bit number (word) y:2 ; temporary 16 bit number (word) ch ; character i ; temporary counters j skipcnt ; character skip count ; variables for range check lolim ; low limit hilim ; high limit ; constants stored in registers reg_spc ; ASCII space reg_cr ; ASCII '\r' endc ; stacks (in FSR memory space) cblock 0x80 globvar:VARSIZ ; global variable table (a-z) macdef:MACSIZ ; macro / label definition table (A-Z) calstp:STKSIZ-1 calstk ; calculation stack envstp:ENVSIZ-1 envstk ; environment stack pgmstart ; start of program space endc ; Reset vector org 0 bra start ; High priority interrupts org 0x0008 ; Low priority interrupts org 0x0018 ; SETUP org 0x0020 start: movlw 0x70 movwf OSCCON ; set internal oscillator to 8MHz bsf OSCTUNE,6 ; set PLL on movlw 0x24 movwf TXSTA ; set asynchronous, BRGH, enable TX movlw 0x90 movwf RCSTA ; set asynchronous, enable RX bsf BAUDCON,3 ; set 16-bit BRG movlw .138 movwf SPBRG clrf SPBRGH ; set baud rate counter for ~57600 ; set ASCII registers movlw SPC movwf reg_spc movlw CR movwf reg_cr clrf pgmstart ; clear first byte ;************************************************* ; MAIN LOOP main: ; send user prompt rcall crlf movlw PROMPT rcall putc rcall getce ; get command and echo movlw 0x5F ; convert to upper case andwf ch,f ; parse command movlw 'L' ; load? xorwf ch,W bz loader movlw 'G' ; go (run)? xorwf ch,W btfsc STATUS,Z bra interp ; too far for BZ instruction ifdef DEBUG movlw 'D' ; dump program (debug) xorwf ch,W btfsc STATUS,Z bra dumpp movlw 'T' ; dump macro table (debug) xorwf ch,W btfsc STATUS,Z bra dumpt endif invCmd: rcall perror ; otherwise an error bra main ;************************************************* ; load a program from the serial port into memory ; eliminating whitespace loader: lfsr 1,macdef + MACSIZ - 1 ; FSR1 -> macro definitions table end movlw MACSIZ movwf i clrmac: ; clear macro definitions clrf POSTDEC1 decfsz i,f bra clrmac movf POSTINC1,W ; undo the last decrement ; at this point, FSR1 -> macro definitions table start movlw ':' ; indicate ready to receive program file rcall putc lfsr 0,pgmstart ; FSR0 -> program space ; read program loop ldloop: rcall getc ; get a character movlw '*' rcall putc ; show progress subwf reg_spc,W ; W = - ch bnz tstctl ; not blank, try next test movff ch,POSTINC0 ; yes, save first blank skipbl: rcall getc subwf reg_spc,W ; another blank? bz skipbl ; yes, skip remaining blanks tstctl: ; flag set by subtraction with SPC ; control char? (SPC - ch > 0) bn testch movff reg_spc,POSTINC0 ; yes, save as blank bra ldloop ; and get the next character testch: movlw '~' ; comment? xorwf ch,W bnz testch1 ; no, try next test skipeol: ; yes, skip until end of line call getc cpfseq reg_cr ; CR? bra skipeol ; no, keep going bra ldloop ; yes, end of comment testch1: movlw '"' ; string? xorwf ch,W bnz testch2 ; no, try next test movff ch,temp ; save quote for comparison movff ch,POSTINC0 ; save quote savestr: rcall getc movwf POSTINC0 ; save character cpfseq temp ; end quote? bra savestr ; no, keep going bra ldloop ; yes, end of string testch2: movlw '$' ; macro def / label or end? xorwf ch,W bnz savech ; no, just save it rcall getc ; yes, get next character and echo movlw 'A' movwf lolim movlw 'Z' movwf hilim rcall range ; upper case letter? bc endload ; no, end of file rcall calcOffset ; get table offset movff FSR0H,PLUSW1 ; save upper byte of program counter addlw 1 ; point to next byte in def table movff FSR0L,PLUSW1 ; save lower byte of program counter bra ldloop savech: movff ch,POSTINC0 ; save any other character movlw 0x0F cpfseq FSR0H ; check if at end of memory bra ldloop ; no, keep going rcall perror ; yes, show end of file error endload: movlw EOF movwf POSTINC0 ; save end of file done: bra main ; and exit ;************************************************* ; interpret the program in memory interp: ; initialize lfsr 0,pgmstart ; FSR0 = program counter lfsr 1,calstk ; FSR1 = calculation stack lfsr 2,envstk ; FSR2 = environment stack movf INDF0,W ; check first program byte btfsc STATUS,Z bra invCmd ; no program loaded, exit with error rcall crlf ; start a new line after command ;*** interpreter loop iloop: btfss PIR1,5 ; check for ^C to stop bra iloop0 ; no character, continue movf RCREG,W ; get character xorlw CAN bz done ; exit to command line if ^C ; or ignore iloop0: ; trace the execution for debugging interpreter ifdef DEBUG rcall trace endif GETNEXT ; get character in ch ; end of file? tstend: movlw '%' ; exit program? cpfseq ch bra tstLower bra main ; lower case letter? ; ch = lower case letter (global Variable) tstLower: rcall isLower bc tstOp ; no, try next test movlw 'a' subwf ch,W ; calculate table offset bcf STATUS,C rlcf WREG,W movwf temp + 1 clrf temp movlw LOW globvar addwf temp + 1,f movlw HIGH globvar addwfc temp,f ; temp = global variable + offset bra pushCal ; and push address on stack ; no, it must be an operator ; ch = operator tstOp: movlw SPC ; op = -> no action xorwf ch,W bz iloop ; ignore blanks op0: movlw '&' cpfseq ch bra op1 ; op = & put hex number on stack rcall clrtemp op0a: GETNEXT ; get digit rcall isHex ; convert to upper case, check if hex digit bc pushnum ; no, save the number rcall mult16 ; make room for new digit rcall ASCIItoBIN ; yes, convert to binary, add to temp bra op0a pushnum: UNGET ; back up pointer because last char was not a digit bra pushCal ; save number on stack op1: movlw '!' cpfseq ch bra op2 ; op = ! -> print top of stack ; op = !' -> print ASCII value from top of stack rcall popCal ; get value into temp GETNEXT ; get next character movlw '\'' ; display ASCII character instead? xorwf ch,W bnz op1a movf temp + 1,W rcall putc bra iloop ; done op1a: UNGET ; back up ; print hexadecimal number putHex: movlw 4 movwf i ; 4 digits in temp putHex0: swapf temp,W ; get upper nibble andlw 0x0F ; Hex Nibble to ASCII (Myke Predko) addlw 0x36 ; add '0' + 6 to value btfsc STATUS,DC ; if digit carry set, then 'A' - 'F' addlw 7 ; add difference between '9' and 'A' addlw 0-6 rcall putc rcall mult16 ; shift over 4 bits decfsz i,f bra putHex0 bra iloop ; done op2: movlw '?' cpfseq ch bra op3 ; op = ? -> get number and put on stack ; op = ?' -> get ASCII character and put value on stack rcall clrtemp GETNEXT ; get next character movlw '\'' ; looking for ASCII character? xorwf ch,W bnz op2a rcall getce ; get char and echo movwf temp + 1 ; and save bra pushCal op2a: UNGET ; back up op2b: rcall getce ; get char and echo rcall isHex ; convert to upper case, check if hex digit bc op2c ; not valid digit, exit loop rcall mult16 ; make room for new digit rcall ASCIItoBIN ; yes, convert to binary, add to temp bra op2b op2c: bra pushCal op3: movlw '\'' cpfseq ch bra op4 ; op = ' -> put ASCII value of char on top of stack GETNEXT movff ch,temp + 1 clrf temp bra pushCal op4: movlw ':' cpfseq ch bra op5 ; op = : -> store data in a variable rcall popCal2 rcall saveFSR2 ; set x -> FSR2 movff temp,POSTINC2 movff temp + 1,INDF2 rcall restoreFSR2 bra iloop op5: movlw '|' cpfseq ch bra op6 ; op = | -> store data in a register rcall popCal2 rcall saveFSR2 ; set x movff temp + 1,INDF2 ; write byte rcall restoreFSR2 bra iloop op6: movlw '+' cpfseq ch bra op7 ; op = + (n1 n2 -- sum) (sum = n1 + n2) rcall popCal2 movf x + 1,W addwf temp + 1,f movf x,W addwfc temp,f bra pushCal op7: movlw '-' cpfseq ch bra op8 ; op = - (n1 n2 -- diff) (diff = n1 - n2) rcall sub16 ; pop 2 values and subtract bra pushCal op8: movlw '<' cpfseq ch bra op9 ; op = < (n1 n2 -- f) (f = TRUE if n1 < n2) rcall sub16 ; pop 2 values and subtract clrf WREG ; false flag btfsc temp,7 ; skip if >= 0 addlw 1 ; true flag bra pushFlag op9: movlw '=' cpfseq ch bra op10 ; op = = (n1 n2 -- f) (f = TRUE if n1 = n2) rcall sub16 ; pop 2 values and subtract movf temp,W iorwf temp + 1,W btfss STATUS,Z ; subtract = 0? setf WREG ; no, clear flag (inverted on next instruction) comf WREG ; yes, set flag bra pushFlag op10: movlw '>' cpfseq ch bra op11 ; op = > (n1 n2 -- f) (f = TRUE if n1 > n2) rcall sub16 ; pop 2 values and subtract movf temp,W iorwf temp + 1,W ; subtract = 0? bz op10a ; yes, push false btfsc temp,7 ; temp positive? (W != 0) clrf WREG ; no, push false op10a: bra pushFlag op11: movlw '.' cpfseq ch bra op12 ; op = . -> get data from address on stack and push rcall popAddr rcall saveFSR2 ; set x -> FSR2 movff POSTINC2,temp ; read word movff INDF2,temp + 1 rcall restoreFSR2 bra pushCal op12: movlw ',' cpfseq ch bra op13 ; op = , -> get byte from address on stack and push rcall popAddr rcall saveFSR2 ; set x -> FSR2 movff INDF2,temp + 1 ; read byte clrf temp ; clear upper byte rcall restoreFSR2 bra pushCal op13: movlw '"' cpfseq ch bra op14 ; op = " -> print string op13a: GETNEXT movlw '"' xorwf ch,W ; end of string bz op14a ; yes, exit movlw '!' xorwf ch,W ; new line? bnz op13b ; no, just send the character rcall crlf ; yes, send newline bra op13a op13b: movf ch,W rcall putc bra op13a op14: movlw '[' cpfseq ch bra op15 ; op = [ conditional if flag <= 0, skip until ']' rcall popCal ; get flag btfsc temp,7 ; negative? bra skipCond ; yes, skip conditional movf temp,W xorwf temp + 1,W ; zero? bnz op14a ; no, continue skipCond: ; yes, skip conditional ; skip a character from '[' to ']' ; handle nesting by using skipcnt movlw 1 movwf skipcnt skiploop: GETNEXT ; do{ movlw '"' ; quote? xorwf ch,W bnz skip2 ; no, try another test skipstr: ; yes, skip until end quote found, ch = '"' GETNEXT movlw '"' ; quote? xorwf ch,W bnz skipstr skip2: movlw '[' ; test if [ xorwf ch,W btfsc STATUS,Z incf skipcnt,f ; if matches left character, inc count movlw ']' ; test if ] xorwf ch,W btfsc STATUS,Z decf skipcnt,f ; if matches right character, dec count movlw 1 cpfslt skipcnt ; }while (skipcnt > 0) bra skiploop op14a: bra iloop op15: movlw '@' cpfseq ch bra op16 ; op = @ return from macro movff PREINC1,FSR0H ; pop the previous character position movff PREINC1,FSR0L bra iloop op16: movlw '#' cpfseq ch bra op17 ; op = # call macro rcall getMacro ; new position in temp bz undefined ; undefined, error movff FSR0L,POSTDEC1 movff FSR0H,POSTDEC1 ; save current character position on stack newpos: movff temp,FSR0H ; go to new character position movff temp + 1,FSR0L bra iloop op17: movlw '}' cpfseq ch bra op18 ; op = } go to label rcall getMacro ; new position in temp bnz newpos ; go to new position ; bra undefined ; undefined, error ; fall into undefined error undefined: rcall perror op18: bra iloop ; ignore any other character ;** branch locations for common operations ; push a flag on stack (0 or 1) if W is 0 or not 0 pushFlag: btfss STATUS,Z ; if W = 0, save movlw 1 ; else, save "TRUE" movwf temp + 1 clrf temp ; drop into pushCal ; push data from temp on to the calculation stack ; FSR = 1 pushCal: movff temp + 1,POSTDEC1 movff temp,POSTDEC1 bra iloop ; calculate offset into macro table from ch calcOffset: movlw 'A' subwf ch,W ; get macro table offset bcf STATUS,C ; clear carry rlcf WREG,W ; multiply by 2 for index, leave in w return ; calculate a macro/label address and place in temp ; set zero flag if address is undefined (= 0000) getMacro: movlw HIGH macdef movwf x movlw LOW macdef movwf x + 1 ; x = table start rcall saveFSR2 ; save FSR2, x->FSR2 GETNEXT ; get macro name rcall calcOffset ; determine the offset into the table addwf FSR2L,f ; FSR2 = macdef + offset clrf WREG addwfc FSR2H,f movff POSTINC2,temp ; read address from table into temp movff INDF2,temp + 1 rcall restoreFSR2 movf temp,W iorwf temp + 1,W ; defined? (macro position != 0) return ;**** end of interpreter loop ; signal an error condition perror: movlw '!' bra putc ;-- utility subroutines ; save FSR2 in y, set FSR2 to x saveFSR2: movff FSR2H,y movff FSR2L,y + 1 ; save FSR2 movff x,FSR2H movff x + 1,FSR2L ; point to address return ; restore FSR2 from y restoreFSR2: movff y,FSR2H movff y + 1,FSR2L ; restore FSR2 from y return ; pop an address value from the stack into x ; FSR = 1 popAddr: movff PREINC1,x movff PREINC1,x + 1 return ; FSR = 1 ; get two values from the stack into x and temp popCal2: rcall popAddr ; drop into popCal ; pop data from the calculation stack into temp ; FSR = 1 popCal: movff PREINC1,temp movff PREINC1,temp + 1 return ; get a character and echo getce: rcall getc bra putc ; wait for a character from the serial port, return in W and store in 'ch' getc: btfss PIR1,5 ; check if character received bra getc movf RCREG,W ; get character movwf ch ; save return ; send a CR-LF pair to the serial port crlf: movlw CR rcall putc movlw LF ; drop into putc ; send a character in W to the serial port putc: btfss PIR1,4 ; check if transmit buffer empty bra putc movwf TXREG ; send character return ; ASCII char to Hex Nibble (Myke Predko) ; and store in temp + 1 ASCIItoBIN: movf ch,W addlw 0xC0 ; if "A" to "F", set the carry flag btfss STATUS,C ; if carry set, then 'A' - 'F' addlw 7 ; add difference between '9' and 'A' addlw 9 iorwf temp + 1,f ; add digit to temp return ; subtract temp = temp - x sub16: rcall popCal2 movf x + 1,W subwf temp + 1,f movf x,W subwfb temp,f return ; multiply temp by 16 mult16: movlw 4 ; multiply temp by 16 movwf j ; 4 digits in temp shift4: bcf STATUS,C ; clear carry to shift in 0 rlcf temp + 1,f rlcf temp,f ; temp << 1 decfsz j,f bra shift4 return ; range checking functions ; return carry clear = valid letter, carry set = invalid letter ; check if valid hex digit isHex: movlw '0' ; check if ch is a digit '0' - '9' movwf lolim movlw '9' movwf hilim rcall range btfss STATUS,C ; 0-9? return ; yes, return movlw 0x5F andwf ch,f ; no, convert to upper case movlw 'A' movwf lolim movlw 'F' movwf hilim bra range ; A-F? ; check if ch is a lower case letter 'a' - 'z' isLower: movlw 'a' movwf lolim movlw 'z' movwf hilim ; drop into range ; check if ch is within a range defined by lolim and hilim range: bsf STATUS,C ; indicate invalid movf lolim,W cpfslt ch ; skip if ch < low limit bra rangeHi ; valid, check high limit bra invRange ; invalid rangeHi: movf hilim,W cpfsgt ch ; skip if ch > high limit bcf STATUS,C ; ch <= high limit invRange: return ; clear 16 bit temp register clrtemp: clrf temp clrf temp + 1 return ;-- debugging routines ;-- only enabled for interpreter debugging ifdef DEBUG ; display the current character address and command ; format: aaaa:c~ trace: rcall crlf movff FSR0H,temp ; get upper byte movff FSR0L,temp + 1 ; get lower byte rcall outTemp movlw ':' rcall putc movlw SPC cpfslt INDF0 ; ctrl char? bra notCtl movlw '^' ; yes, display control character as ^ rcall putc movf INDF0,W addlw 0x40 bra continue notCtl: movf INDF0,W ; no, display as standard character continue: rcall putc movlw '~' rcall putc return ; dump program memory (after loader) dumpp: rcall crlf lfsr 0,pgmstart movlw EOF movwf temp dump0: movf POSTINC0,W ; get character call putc cpfseq temp ; go until end of file bra dump0 movlw '\\' rcall putc ; indicate end of file reached bra main ; done ; dump macro table (after loader) dumpt: lfsr 0,macdef movlw MACSIZ/2 movwf i movlw 'A' movwf ch ; ch = variable name dumpt0: ; print : hhhh\n rcall crlf movf ch,W rcall putc ; print name movlw ':' rcall putc ; print ':' movff POSTINC0,temp ; get upper byte movff POSTINC0,temp + 1 ; get lower byte rcall outTemp incf ch,f decfsz i,f bra dumpt0 bra main outTemp: swapf temp,W andlw 0x0F rcall outHex movf temp,W andlw 0x0F rcall outHex swapf temp + 1,W andlw 0x0F rcall outHex movf temp + 1,W andlw 0x0F rcall outHex movlw ' ' rcall putc return ; Hex Nibble to ASCII (Myke Predko) outHex: addlw 0x36 ; add '0' + 6 to value btfsc STATUS,DC ; if digit carry set, then 'A' - 'F' addlw 7 ; add difference between '9' and 'A' addlw 0-6 bra putc endif END