;===================================================================================================================== ; DIGIMATIC TO RS-232C INTERFACE FIRMWARE ; ; Original PIC Assembly Code by Carl Huben ; ; c1999, Huben Consulting, Inc. ; 10039 Crosley, Redford MI 48239 ;===================================================================================================================== ; COMPILE OPTIONS ; To build for 16C558, change directive to LIST P=16C558 LIST P=16F84 ; The parser accepts both CR and CRLF delimited strings from the PC. Responses are CR-delimited by default. ; For CRLF termination, uncomment the following line: ;#DEFINE DELIM_CRLF IFDEF __16C558 INCLUDE USER_RAM_ST EQU 0x50 ELSE INCLUDE ; Assume 16F84 if not 16C558 USER_RAM_ST EQU 0x0C ENDIF ;===================================================================================================================== ; CONFIGURATION BITS __config _WDT_ON & _XT_OSC & _CP_OFF & _PWRTE_ON ;===================================================================================================================== ; MACROS #DEFINE PMASK(theBit) (0x01 << theBit) ; Positive mask from bit index. Ex: PMASK(3) = B'00001000' #DEFINE NMASK(theBit) (0xFF ^ PMASK(theBit)) ; Negative mask from bit index. Ex: NMASK(3) = B'11110111' #DEFINE PROCPTR(F) (LOW F), (HIGH F) #DEFINE STRPTR(F) (LOW F) ; Assumes all strings are located on the same page ;===================================================================================================================== ; GENERAL DEFINITIONS CLK_FREQ EQU D'4000000' ; Clock frequency CYC_FREQ EQU (CLK_FREQ >> 2) ; Cycle frequency (= CLK_FREQ / 4) ; Constant for delay of (at least) one millisecond (longer due to interrupts) cMSECDELAY EQU ((D'1000' * CYC_FREQ / D'1000000') - 6) / 5 #if (cMSECDELAY > 0xFF) ERROR "The timing loop requires cMSECDELAY <= 0xFF" #endif ;===================================================================================================================== ; SERIAL RX/TX DEFINITIONS SERBAUDRATE EQU D'1200' ; Baud Rate (8 data bits, 1 stop bit, no parity) SERDATABITS EQU 0x08 ; # Data Bits SERSTOPBITS EQU 0x01 ; # Stop Bits BPARTSLOG2 EQU 0x02 ; Number of partitions per bit, log 2 (0x02 = 4 parts) CYC_PER_BIT EQU (CYC_FREQ / SERBAUDRATE) ; Clock cycles per bit CYC_PER_PART EQU (CYC_PER_BIT >> BPARTSLOG2) ; Clock cycles per bit part ADJCYC_PER_PART EQU (CYC_PER_PART - 4) PREX EQU 0x00 ; Prescale value (0x00 = 1:2, 0x07 = 1:256) DELTA_TMR0 EQU (ADJCYC_PER_PART >> (PREX + 1)) TMR0_INIT EQU (0x0FF - DELTA_TMR0) OPTION_INIT EQU (B'11000000' | PREX) ; Pull-ups off, RB0 rising, TMR0 from CLKOUT, PSX TMR0 w/ PREX #if ((PREX & 0xF8) != 0x00) ERROR "PREX must be between 0x00 and 0x07, inclusive." #endif #if (DELTA_TMR0 >= 0xFF) ERROR "PREX is too low. Set it to the lowest value that gives DELTA_TMR0 < 0xFF" #endif TSBIT EQU BPARTSLOG2 ; Timing constant used by serial routines ;===================================================================================================================== ; PARSER DEFINITIONS cOVRFLWTOKEN EQU 0x80 ; Used by parser routines to indicate RX buffer overflow cCMDENDCHAR EQU '\r' ; Treat '\r' as the command line delimiter (parser ignores '\v' ; and so is compatible with CR and CRLF terminated strings) ;===================================================================================================================== ; PORT DEFINITIONS (I) = PIC Input (O) = PIC Output ; The Digimatic interface is on PORTB. Bring REQ low (PIC pin high) to elicit a message from the Digimatic device. ; Messages consist of a stream of 52 bits sent using a 2 wire synchronous protocol (DAT has current bit, ; sample on CLK high-to-low transition). Some Digimatic devices implement a READY line, which goes low when ; the operator presses a "DATA" button on the gage, indicating that a reading should be taken immediately. ; There is a pushbutton switch (ioSW1) in the PIC circuit that has the same function (for gages without DATA ; buttons). ioDGM_CLK EQU 0x00 ; (I) Digimatic clock is on INT pin (RB0) ioDGM_DAT EQU 0x01 ; (I) Digimatic data (sync xfer) ioDGM_REQ EQU 0x02 ; (O) Digimatic message request ioDGM_RDY EQU 0x03 ; (I) Digimatic ready line (gage DATA pushbutton) ioSW1 EQU 0x04 ; (I) Local pushbutton (same function as Digimatic ready line) ; The RS-232 interface is on PORTA. The TX line is on RA4. This pin has an open drain output that feeds into ; the TX port pin driver, a PNP transistor. The RX line is on RA3. The RX port pin connects to RA3 through ; a 22K resistor (for current limiting) and relies on the protection diodes to keep the voltage in range. ; Some PIC port lines do not have full input protection, so make sure you check before changing this pin or ; moving to a different chip. Lastly, the VDBL line is used to drive a charge pump, which generates the positive ; voltage needed by the TX port pin driver. ioVDBL EQU 0x02 ; (O) Charge pump driver ioSER_RX EQU 0x03 ; (I) Serial RX line ioSER_TX EQU 0x04 ; (O) Serial TX line ;===================================================================================================================== ; RAM DEFINITIONS ; Digimatic Interface-Related Variables ACCaLO EQU (USER_RAM_ST + 0x00) ; Scratch area used by math utilities ACCaHI EQU (USER_RAM_ST + 0x01) ACCbLO EQU (USER_RAM_ST + 0x02) ACCbHI EQU (USER_RAM_ST + 0x03) IT0 EQU (USER_RAM_ST + 0x04) ; Used by the seconds timer IT1 EQU (USER_RAM_ST + 0x05) ITSEC EQU (USER_RAM_ST + 0x06) mPORTBSW EQU (PMASK(ioDGM_RDY) | PMASK(ioSW1)) OLDPORTBSW EQU (USER_RAM_ST + 0x07) PUSHEDPORTBSW EQU (USER_RAM_ST + 0x08) ; When button is pushed, the corresponding bit is set here varDF EQU (USER_RAM_ST + 0x09) ; Current settings for format, mode, and period variables varMD EQU (USER_RAM_ST + 0x0A) varRP EQU (USER_RAM_ST + 0x0B) MICFLAGS EQU (USER_RAM_ST + 0x0C) ; Flags used by the digimatic interface routines fDOMICREAD EQU 0x00 fMSGREQ EQU 0x01 fNONZERO EQU 0x02 MICBITCNT EQU (USER_RAM_ST + 0x0D) ; Buffer and bit count for Digimatic message reception MICBUFST EQU (USER_RAM_ST + 0x0E) MICBUFLEN EQU 0x07 ; Misc Variables ITMP1 EQU 0x21 ; Temp variable for the interrupt routine TMP1 EQU 0x22 ; Temp variables for noninterrupt routines TMP2 EQU 0x23 TMP3 EQU 0x24 FSR_TMP EQU 0x25 ; Used by ISR to save and restore context STAT_TMP EQU 0x26 W_TMP EQU 0x27 ; Serial TX/RX Variables SFLAGS EQU 0x28 ; Flags used by the serial TX and RX interrupt procs fCUR_SER_TX EQU 0x00 ; Current state of TX line (port is updated from this flag) fCUR_SER_RX EQU 0x01 ; Current state of RX line (this flag is updated from the port) fACTIVE_SER_TX EQU 0x02 ; Set when TX is in progress fACTIVE_SER_RX EQU 0x03 ; Set when RX in progress fREQ_SER_TX EQU 0x04 ; Set externally to request that the contents of REQTX be sent fSER_RX_DONE EQU 0x05 ; Set by RX interrupt proc to indicate reception (data in CURRX) fPERIODUP EQU 0x06 TXTICKER EQU 0x29 ; Used by serial TX interrupt proc for timing TXBITCNT EQU 0x2A CURTX EQU 0x2B ; REQTX EQU 0x2C ; RXTICKER EQU 0x2D ; Used by serial RX interrupt proc for timing RXBITCNT EQU 0x2E CURRX EQU 0x2F ; Parser Variables RXBUFCNT EQU 0x30 RXBUFPOS EQU 0x31 LASTPOS EQU 0x32 CMDSTODO EQU 0x33 PARSEREQ EQU 0x34 IPFLAGS EQU 0x35 fREQCMDBYTE EQU 0x00 fCURDELIM EQU 0x01 fRXOVRFLW EQU 0x02 CHUNKIDX EQU 0x36 ; Index of current chunk of command line MPFLAGS EQU 0x37 ; Flags used by the external (noninterrupt) parser proc fCMDCHAR EQU 0x00 fCMDEND EQU 0x01 fPARSEERR EQU 0x02 fVALIDCMD EQU 0x03 SCBUFLEN EQU 0x38 ; Scratch buffer, used to process each chunk of command line SCBUF1_ST EQU 0x39 SCBUF1_MAX EQU 0x04 PCMDIDX EQU 0x3D CPSTAT1 EQU 0x3E CPSTAT2 EQU 0x3F RXBUF_ST EQU 0x40 ; Parser RX buffer RXBUF_LEN EQU 0x10 ;===================================================================================================================== ; COMMAND DEFINITIONS cCMDENTRY_NAME EQU 0x00 ; Command word cCMDENTRY_PROCL EQU 0x01 ; Command proc address cCMDENTRY_PROCH EQU 0x02 cCMDENTRY_CUST EQU 0x03 ; Start of custom data for command proc cMD_STANDBY EQU 0x00 cMD_PERIODIC EQU 0x01 cDF_RAWBCD EQU 0x00 cDF_FORMATTED EQU 0x01 cDAT_UNIT EQU 0x00 ; Unit fDAT_UNIT_IN EQU 0x00 ; Unit Flag (Set=Inches, Clear=Millimeters) cDAT_DECLOC EQU 0x01 ; Decimal Point Location (# digits to the right of decimal) cDAT_DIGIT6 EQU 0x02 ; Digit #6 cDAT_DIGIT5 EQU 0x03 ; Digit #5 cDAT_DIGIT4 EQU 0x04 ; Digit #4 cDAT_DIGIT3 EQU 0x05 ; Digit #3 cDAT_DIGIT2 EQU 0x06 ; Digit #2 cDAT_DIGIT1 EQU 0x07 ; Digit #1 cDAT_SIGN EQU 0x08 ; Sign fDAT_SIGN_NEG EQU 0x03 ; Sign Flag (Set=Negative, Clear=Positive/Zero) cDAT_MSGLEN EQU 0x0D ; Gage Message Length (in nibbles) PPROC_ERR EQU 0x01 PPROC_NOERR EQU 0x00 SEC_ITBASIS EQU (SERBAUDRATE << BPARTSLOG2) SEC_IT0 EQU ((LOW SEC_ITBASIS) + 0x01) ; !!! CHECK THIS SEC_IT1 EQU ((HIGH SEC_ITBASIS) + 0x01) ;===================================================================================================================== ; MAIN PROGRAM LOOP ORG 0x000 GOTO START ; Skip over interrupt vector ORG 0x004 GOTO SERVICEINTS START CALL INITPIC MAINLP CALL MAINLOOPHOOK ; Call the digimatic interface procedure MOVF CMDSTODO, W ; Has the parser's interrupt proc posted a command? BTFSC STATUS, Z GOTO MAINLP CALL PARSEPROC ; Yes, parse the command GOTO MAINLP ; and jump to the top of the main loop ;===================================================================================================================== ; MAIN PARSER PROCEDURE PARSEPROC CLRF MPFLAGS ; Clear flags used by this proc CLRF CHUNKIDX ; Reset chunk index CLRF CPSTAT1 ; Clear the command proc temp vars CLRF CPSTAT2 CALL CLEARSCRATCH ; Clear the scratch buffer PMAINLP CALL GETNEXTCMDCHAR ; Get the next character of the command line from the parser's ; interrupt procedure (sets/clears fCMDEND and fCMDCHAR flags) MOVLW STRPTR(sOVERFLOW) BTFSC PARSEREQ, 7 ; If it's an overflow token, send "RX OV" error and return GOTO TXDELIMSTR BTFSC MPFLAGS, fPARSEERR ; If error flag is set, jump to end of loop (wait for delimiter) GOTO PMSK1 BTFSS MPFLAGS, fCMDCHAR ; If it's not a valid command character, jump ahead to process the GOTO PMSK2 ; current chunk (assume we're at a chunk boundary) MOVLW SCBUF1_MAX ; We get here if it's a valid command character SUBWF SCBUFLEN, W BTFSS STATUS, Z GOTO SCHASROOM ; Add to scratch buffer if there's room BSF MPFLAGS, fPARSEERR ; If Z is set, buf len exceeded. Set error flag and continue loop GOTO PMAINLP ; (assumes delimiter is not a command character) SCHASROOM MOVF PARSEREQ, W MOVWF INDF ; Add character to buffer INCF FSR, F ; Increment buffer pointer INCF SCBUFLEN, F ; Increment length count GOTO PMAINLP ; Continue loop PMSK2 MOVF SCBUFLEN, W ; If the scratch buffer is empty, jump to end of loop BTFSC STATUS, Z GOTO PMSK1 INCF CHUNKIDX, F ; Otherwise, process the contents of the scratch buffer CALL PPROC_DISPATCH ANDLW 0xFF ; If nonzero return value, set error flag BTFSS STATUS, Z BSF MPFLAGS, fPARSEERR CALL CLEARSCRATCH ; Clear scratch and fall through to check for delimiter PMSK1 BTFSS MPFLAGS, fCMDEND GOTO PMAINLP PDONE MOVLW STRPTR(sBADCMD) ; Done parsing. If error, send error message BTFSC MPFLAGS, fPARSEERR GOTO TXDELIMSTR BTFSS MPFLAGS, fVALIDCMD ; If no valid command on command line, send error GOTO TXDELIMSTR CLRF CHUNKIDX ; Otherwise, clear CHUNKIDX and fall through to dispatch ; CMDPROCs must recognize a zero CHUNKIDX as "execute" order PPROC_DISPATCH DECFSZ CHUNKIDX, W ; If CHUNKIDX=1, that's the command word; fall through to process it GOTO PPROC_ARGS ; Otherwise, we are on a subsequent chunk (an argument) PPROC_CMD CALL CAPSCRATCH ; Convert all alpha in scratch buf to caps MOVLW (CTAB_PCMDEND - CTAB_PCMDSTART - 1) MOVWF PCMDIDX PPCLP MOVLW cCMDENTRY_NAME ; Based on current PCMDIDX, get pointer to current command name CALL GETCMDENTRYBYTE CALL CMPTABTOSCRATCH ; Compare command name to scratch buffer BTFSC STATUS, Z GOTO CMDMATCH ; Jump out of loop to CMDMATCH if it jibes DECF PCMDIDX, F ; Otherwise, continue looping through command list BTFSS PCMDIDX, 7 GOTO PPCLP CMDNOMATCH RETLW PPROC_ERR ; No such command. Return with error CMDMATCH BSF MPFLAGS, fVALIDCMD ; Valid command. Set valid command flag and return "no err" RETLW PPROC_NOERR ; Process subsequent chunks of text on the command line ; Index of current chunk in CHUNKIDX (first arg after command word is 2, second is 3, etc.) PPROC_ARGS MOVLW cCMDENTRY_PROCL ; Jump to user procedure CALL GETCMDENTRYBYTE ; Note that GETCMDENTRYBYTE destroys PCLATH and TMP1 MOVWF TMP2 MOVLW cCMDENTRY_PROCH CALL GETCMDENTRYBYTE MOVWF PCLATH MOVF TMP2, W MOVWF PCL ; Requests another character of the command line from the parser's interrupt proc, and sets/clears ; fCMDEND and fCMDCHAR flags depending on its value. GETNEXTCMDCHAR BSF IPFLAGS, fREQCMDBYTE ; Request the next command character RWLP BTFSC IPFLAGS, fREQCMDBYTE ; Wait for it GOTO RWLP MOVLW (NMASK(fCMDEND) & NMASK(fCMDCHAR)) ANDWF MPFLAGS, F BTFSC PARSEREQ, 7 ; If >= 128, return now, since it is an overflow token RETURN MOVLW cCMDENDCHAR ; If delimiter, set "fCMDEND" flag SUBWF PARSEREQ, W BTFSC STATUS, Z BSF MPFLAGS, fCMDEND MOVLW 0x21 ; If between 33 and 127, it's a valid command character SUBWF PARSEREQ, W BTFSC STATUS, C BSF MPFLAGS, fCMDCHAR RETURN ; Assumes PCMDIDX is set up with current command index ; On entry, WREG contains byte index ; Destroys TMP1 GETCMDENTRYBYTE MOVWF TMP1 MOVLW (HIGH TABSTART) ; Set up PCLATH for command table lookups MOVWF PCLATH MOVF PCMDIDX, W ADDLW (LOW CTAB_PCMDSTART) CALL LOOKUPPROC ; Get the address of the current command table entry ADDWF TMP1, W LOOKUPPROC MOVWF PCL ; On entry, WREG contains starting lookup tab address of word ; On exit, Z Clear if not equal (C clear if scratch name < table name, C set otherwise) ; Destroys TMP1, TMP2 CMPTABTOSCRATCH MOVWF TMP1 ; Save lookup tab address in TMP1 MOVF SCBUFLEN, W ; Set up position count in TMP2 MOVWF TMP2 CALL REWINDSCRATCH ; Point FSR at start of scratch buffer MOVLW (HIGH STRSTART) ; Set up PCLATH for string table lookups MOVWF PCLATH CTLP DECF TMP2, F ; Predec position counter MOVF TMP1, W ; Look up the next char in table CALL LOOKUPPROC XORLW '\r' ; Set Z flag if current table char is our string delimiter BTFSC TMP2, 7 ; And return now if we're at end of scratch buffer RETURN BTFSS STATUS, Z ; Otherwise, return "scratch < table" if delimiter GOTO CTSK SUBLW 0x01 ; (WREG has 0x00, so clear Z and C with SUBLW 0x01) RETURN CTSK XORLW '\r' ; Reconstitute the table char SUBWF INDF, W ; And check it against the scratch buffer BTFSS STATUS, Z ; If not equal, return with C clear if scratch name comes RETURN ; before table name alphabetically, C set otherwise INCF FSR, F ; If equal, move to next letter and continue loop INCF TMP1, F GOTO CTLP ; Capitalize the contents of the scratch buffer ; Kills TMP1, TMP2 CAPSCRATCH CALL REWINDSCRATCH MOVF SCBUFLEN, W MOVWF TMP2 CSLP MOVF INDF, W ; If alpha, capitalize CALL TESTALPHA BTFSC STATUS, C MOVWF INDF INCF FSR, F DECFSZ TMP2, F GOTO CSLP RETURN ; Reset the scratch buffer (clears length count and points FSR to buffer start in RAM) CLEARSCRATCH CLRF SCBUFLEN ; Clear length count and fall through to reset FSR ; Rewind FSR to the start of the scratch buffer REWINDSCRATCH MOVLW SCBUF1_ST MOVWF FSR RETURN ; Transmits a lookup table string through RS-232 interface ; On entry, WREG contains starting lookup table address of string ; - All strings assumed to be within a single memory page ; - Table strings are delimited with '\r' (0x0D) ; Destroys TMP1 TXSTRING MOVWF TMP1 MOVLW (HIGH STRSTART) ; Set PCLATH for string table lookups (assumes all strings on one page) MOVWF PCLATH TXSLP MOVF TMP1, W ; Get next character of string CALL LOOKUPPROC XORLW '\r' ; If at end of string, return after sending delimiter BTFSC STATUS, Z RETURN XORLW '\r' CALL TXCHAR ; Else, send it INCF TMP1, F ; Increment char index GOTO TXSLP ; Loop back to send next char ; Transmits a lookup table string through RS-232 interface, delimited according to compiler switch ; Destroys TMP1 TXDELIMSTR CALL TXSTRING ; Send the string and fall through to send delimiter TXDELIM IFDEF DELIM_CRLF MOVLW '\r' ; Send delimiter (either CR+LF, or just CR) CALL TXCHAR MOVLW '\v' GOTO TXCHAR ELSE MOVLW '\r' GOTO TXCHAR ENDIF ; Convert a decimal digit to ASCII and send through RS-232 interface TXNUM ADDLW '0' ; Add '0' to decimal number and fall through ; Send a character through RS-232 interface TXCHAR BTFSC SFLAGS, fREQ_SER_TX ; Wait out any pending transmissions GOTO TXCHAR MOVWF REQTX ; Place in TX register and set TX request flag BSF SFLAGS, fREQ_SER_TX RETURN ; Initialize all variables used by the parser INITPARSER CLRF SFLAGS CLRF IPFLAGS CLRF RXBUFPOS CLRF CMDSTODO CLRF RXBUFCNT CLRF LASTPOS RETURN ;===================================================================================================================== ; PARSER UTILITIES ; Alphabetical test on contents of WREG, returns uppercase letter and "C" set if valid, ; "C" clear if not an upper or lowercase letter ; Kills TMP1 TESTALPHA ANDLW B'11011111' ; Clear bit #5 to ensure uppercase MOVWF TMP1 SUBLW 'Z' ; Is it less than or equal to ASCII "Z" ? BTFSS STATUS, C RETURN ; Out of bounds, return with "C" clear MOVLW 'A' ; Is it greater than or equal to ASCII 'A' ? SUBWF TMP1, W BTFSS STATUS, C ; Out of bounds, return with "C" clear RETURN MOVF TMP1, W ; Valid, load uppercase version RETURN ; and return with "C" set ; Tests WREG for ASCII numbers, returns binary number and "C" set if valid, "C" clear if not ; If WREG contains '0', the Z flag will be set on return ; Kills TMP1 TESTNUM MOVWF TMP1 SUBLW '9' ; Is it less than or equal to ASCII "9" ? BTFSS STATUS, C RETURN ; Out of bounds, return with "C" clear MOVLW '0' ; Is it greater than or equal to ASCII '0' ? SUBWF TMP1, W RETURN ; Delay for at least WREG milliseconds (more due to interrupts) ; WREG assumed > 0 ; Destroys TMP1, TMP2 DELXMS MOVWF TMP1 MOVLW cMSECDELAY DXMSLP1 MOVWF TMP2 DXMSLP2 GOTO DXMSSK DXMSSK DECFSZ TMP2, F GOTO DXMSLP2 DECFSZ TMP1, F GOTO DXMSLP1 RETURN ;===================================================================================================================== ; PRIMARY INITIALIZATION PROCEDURE INITPIC CLRF INTCON ; Disable interrupts CLRWDT ; Clear watchdog timer BSF STATUS, RP0 ; Switch to Bank 1 MOVLW OPTION_INIT ; Init OPTION register MOVWF OPTION_REG MOVLW (NMASK(ioSER_TX) & NMASK(ioVDBL)) MOVWF TRISA ; PORTA used for RS-232C interface. TX and VDBL output, RX input MOVLW B'11111011' MOVWF TRISB ; PORTB used for Mitutoyo interface. Start out with all inputs BCF STATUS, RP0 ; Back to Bank 0 MOVLW TMR0_INIT ; Reset TMR0 MOVWF TMR0 MOVLW PMASK(ioSER_TX) MOVWF PORTA MOVLW 0x00 MOVWF PORTB CALL INITPARSER CALL INITUSERVARS MOVLW PMASK(T0IE) ; Only TMR0 interrupts (see ISR for info on why no INTE) MOVWF INTCON RETFIE ;===================================================================================================================== ; INTERRUPT SERVICE ROUTINE ; This routine handles interrupts, in this case the key interrupt is the TMR0 overflow, which is set up to ; trigger an interrupt at a frequency of 4 (or another power of 2) times the serial baud rate. ; During this periodic interrupt: ; ; - The serial port TX line is updated based on the fCUR_SER_TX flag ; - The serial port RX line is sampled, and used to set or clear the fCUR_SER_RX flag ; - The VDBL line is toggled to produce a square wave that feeds the charge pump (used by the RS-232 TX driver) ; ; - The serial transmission handler is executed (may set or clear the fCUR_SER_TX flag for next update) ; - The serial reception handler is executed (looks at fCUR_SER_RX flag to monitor the RX line) ; - The parser's interrupt routine takes care of newly received bytes, alerting its counterpart routine ; (which runs in noninterrupt time) to any new receptions and handling transfer of command line characters ; from the RX buffer to the main parser proc. ; ; - A user routine (INTHOOK) is called, and watches for button presses and manages the seconds timer used ; by the Digimatic interface routines. ; ; Also, we check for transitions on the INT (RB0) pin, since this is the sync clock of the Digimatic interface. ; Since the Digimatic interface clock is slow compared to the TMR0 periodic interrupt (2048 Hz vs 9600 Hz), ; we don't need to enable INTE interrupts. Instead, we just check the INTF bit during the periodic ; interrupt, and if set, we jump to the SERVICE_INTF routine, which handles the reception of messages sent ; using the Digimatic protocol. ; ; IMPORTANT: If you make any changes to this routine, or to the serial port settings (baud rate, data bits, etc.), ; you must make sure the interrupt service routine always finishes execution before the next TMR0 overflow, or ; it will screw everything up. SERVICEINTS MOVWF W_TMP ; Save context SWAPF STATUS, W MOVWF STAT_TMP MOVF FSR, W MOVWF FSR_TMP ; The following section must always be at the beginning of the interrupt service routine BTFSS INTCON, T0IF ; TMR0 rollover interrupt? GOTO PASTTMR0INT BCF INTCON, T0IF ; Yes. Clear interrupt flag MOVLW DELTA_TMR0 ; Reset TMR0 SUBWF TMR0, F MOVF PORTA, W ; Read PORTA ANDLW NMASK(ioSER_TX) ; Update TX line from fCUR_SER_TX BTFSS SFLAGS, fCUR_SER_TX IORLW PMASK(ioSER_TX) XORLW PMASK(ioVDBL) ; Toggle VDBL MOVWF PORTA ; Write result back to PORTA BCF SFLAGS, fCUR_SER_RX ; Update the fCUR_SER_RX flag from RX line ANDLW PMASK(ioSER_RX) BTFSS STATUS, Z BSF SFLAGS, fCUR_SER_RX CALL SERTXTICK ; Run the transmit algorithm CALL SERRXTICK ; Run the receive algorithm CALL IPPROC ; Call the parser's interrupt proc CLRWDT CALL INTHOOK PASTTMR0INT BTFSC INTCON, INTF ; RB0 pin interrupt? CALL SERVICE_INTF ; Yes, handle it INTEXIT MOVF FSR_TMP, W ; Restore context MOVWF FSR SWAPF STAT_TMP, W MOVWF STATUS SWAPF W_TMP, F SWAPF W_TMP, W RETFIE ;===================================================================================================================== ; SERIAL TRANSMISSION HANDLER ; This routine runs at 4 (or another power of 2) intervals per bit period, and transmits ; serial data to the RS-232 port (TX pin). To send a byte, wait until the fREQ_SER_TX flag ; is clear, move the data into REQTX, and set the fREQ_SER_TX flag. This routine will ; clear fREQ_SER_TX when it's ready for another transmission request. SERTXTICK BTFSS SFLAGS, fACTIVE_SER_TX ; Is there a serial TX in progress? GOTO TXNOTINPROG DECFSZ TXTICKER, F ; Decrement the TX ticker RETURN ; If not zero, we're done with TX routine BSF TXTICKER, TSBIT ; We're at a bit boundary. Reset ticker INCF TXBITCNT, F DECF TXBITCNT, W SUBLW SERDATABITS BTFSS STATUS, C ; Have we sent all the data bits? GOTO TXSTOPBIT ; Yes, skip ahead RRF CURTX, F ; No, send the next bit BCF SFLAGS, fCUR_SER_TX BTFSS STATUS, C BSF SFLAGS, fCUR_SER_TX RETURN ; And we're done here TXSTOPBIT BCF SFLAGS, fCUR_SER_TX ; Default to "stop bit" ADDLW SERSTOPBITS ; But did we already send the last stop bit? BTFSC STATUS, Z BCF SFLAGS, fACTIVE_SER_TX ; Yes, clear "active TX" flag and fall through TXNOTINPROG BTFSS SFLAGS, fREQ_SER_TX ; Has a transmission been requested? RETURN ; No, we're done here MOVF REQTX, W ; Yes, move from REQTX to CURTX MOVWF CURTX BCF SFLAGS, fREQ_SER_TX ; Clear request flag BSF SFLAGS, fACTIVE_SER_TX ; Set "TX in progress" flag CLRF TXBITCNT ; Reset TX bit count MOVLW PMASK(TSBIT) MOVWF TXTICKER BSF SFLAGS, fCUR_SER_TX ; Put start bit on TX line (high) and return RETURN ;===================================================================================================================== ; SERIAL RECEPTION HANDLER ; This routine runs at 4 (or another power of 2) intervals per bit period, and receives ; serial data from the RS-232 port (RX pin). When a valid byte is received, the fSER_RX_DONE ; flag is set and the data will be in CURRX. To ensure that reception of multiple-byte ; messages goes smoothly, the caller must check for the done flag immediately after this ; routine executes (at interrupt time, of course); if set, the data in CURRX must be used ; immediately and the fSER_RX_DONE must be cleared. SERRXTICK BTFSS SFLAGS, fACTIVE_SER_RX ; Is there a serial RX in progress? GOTO RXNOTINPROG DECFSZ RXTICKER, F ; Decrement the RX ticker RETURN ; If not zero, we're done with RX routine BSF RXTICKER, TSBIT ; We're at a sample point. Reset ticker RXSAMPLEBIT INCF RXBITCNT, F ; Sample RX line DECF RXBITCNT, W BTFSC STATUS, Z GOTO RXSTARTBIT SUBLW SERDATABITS BTFSS STATUS, C GOTO RXSTOPBIT RXDATABIT BCF STATUS, C ; Otherwise, record the next data bit BTFSS SFLAGS, fCUR_SER_RX BSF STATUS, C RRF CURRX, F RETURN RXSTARTBIT BTFSS SFLAGS, fCUR_SER_RX ; Verify that the start bit is valid (high) BCF SFLAGS, fACTIVE_SER_RX ; If invalid, reset receiver algorithm (error is not reported) RETURN ; Either way, we're done here RXSTOPBIT ADDLW SERSTOPBITS ; Are we on the last stop bit? BTFSS STATUS, Z RETURN ; No, return BCF SFLAGS, fACTIVE_SER_RX ; Yes, clear "active RX" flag BSF SFLAGS, fSER_RX_DONE ; and set "RX done" flag RETURN ; Return (If "RX done" is set, caller must process CURRX before next tick) RXNOTINPROG BTFSS SFLAGS, fCUR_SER_RX ; Do we have a start bit? (high) RETURN ; No, just return BSF SFLAGS, fACTIVE_SER_RX ; Yes, set "RX in progress" flag CLRF CURRX ; Clear CURRX (in case fewer than 8 data bits) CLRF RXBITCNT ; Reset RX bit count MOVLW PMASK(TSBIT-1) ; Prep RX ticker for first bit MOVWF RXTICKER RETURN ;===================================================================================================================== ; PARSER INTERRUPT PROCEDURE ; Adds newly received characters to a buffer, and alerts the main parser proc (which runs in noninterrupt time) ; when a delimited command has been received. It is then up to the main proc to request the command line from ; IPPROC, one character at a time. IPPROC can keep track of multiple commands, and has fairly decent buffer ; management capabilities. IPPROC BTFSS SFLAGS, fSER_RX_DONE ; Did we just receive a byte? GOTO IPEXTCOM ; No, there's time to handle requests from noninterrupt parser routines BCF SFLAGS, fSER_RX_DONE ; Yes, we must add it to the parser buffer (reset serial RX routine) BCF CURRX, 7 ; Parser ignores high bit MOVF CURRX, W ; Just ignore LF char (treat CR and CRLF delimited strings the same) SUBLW '\v' BTFSC STATUS, Z RETURN BCF IPFLAGS, fCURDELIM ; Set/Clear fCURDELIM flag depending on CURRX MOVF CURRX, W SUBLW cCMDENDCHAR BTFSC STATUS, Z BSF IPFLAGS, fCURDELIM MOVLW (RXBUF_LEN - 1) ; Do we have room in the buffer for another character? SUBWF RXBUFCNT, W BTFSC STATUS, C BSF IPFLAGS, fRXOVRFLW ; If not, set overflow flag BTFSS IPFLAGS, fRXOVRFLW ; If overflow flag is clear, skip ahead GOTO ADDCURRXTOBUF BTFSS IPFLAGS, fCURDELIM ; An overflow has occurred. Just wait for the next delimiter RETURN ; If current byte isn't a delimiter, we're done here BCF IPFLAGS, fRXOVRFLW ; Overflow, but delimiter has just arrived. Clear overflow flag MOVLW RXBUF_LEN ; Do we have room in the buffer for the overflow token? SUBWF RXBUFCNT, W BTFSC STATUS, C RETURN ; If not, just return MOVF LASTPOS, W ; Rewind to just after last intact command (adjust RXBUFPOS, RXBUFCNT) SUBWF RXBUFPOS, W ; Subtract LASTPOS from current position BTFSS STATUS, C ; If negative, add RXBUF_LEN ADDLW RXBUF_LEN SUBWF RXBUFCNT, F ; And subtract result from RXBUFCNT MOVF LASTPOS, W ; Reset RXBUFPOS to just after last intact command MOVWF RXBUFPOS MOVLW cOVRFLWTOKEN ; Add "Overflow" token to RX buffer MOVWF CURRX BSF IPFLAGS, fCURDELIM ; The token acts like a full, delimited command ; Fall through to add it ADDCURRXTOBUF INCF RXBUFCNT, F ; Increment received byte count MOVLW RXBUF_LEN ; Bounds-check buffer position index SUBWF RXBUFPOS, W BTFSC STATUS, C CLRF RXBUFPOS MOVLW RXBUF_ST ; Add the new data to the buffer in the next open slot ADDWF RXBUFPOS, W MOVWF FSR MOVF CURRX, W MOVWF INDF INCF RXBUFPOS, F ; Increment buffer position index BTFSS IPFLAGS, fCURDELIM ; If current byte isn't a delimiter, we're done here RETURN MOVF RXBUFPOS, W ; Update LASTPOS with current position MOVWF LASTPOS INCF CMDSTODO, F ; Increment "commands to do" count RETURN IPEXTCOM BTFSS IPFLAGS, fREQCMDBYTE ; Has the main parser proc requested another byte? RETURN BCF IPFLAGS, fREQCMDBYTE ; Clear flag to indicate request has been processed MOVF RXBUFCNT, W SUBWF RXBUFPOS, W BTFSS STATUS, C ADDLW RXBUF_LEN ADDLW RXBUF_ST MOVWF FSR MOVF INDF, W MOVWF PARSEREQ DECF RXBUFCNT, F ; And decrement the byte count BTFSC PARSEREQ, 7 ; Check if last char was an error or a delimiter GOTO CMDOUT SUBLW cCMDENDCHAR BTFSS STATUS, Z RETURN CMDOUT DECF CMDSTODO, F ; If so, decrement "commands pending" count RETURN ;===================================================================================================================== ; DIGIMATIC INTERFACE ROUTINES INITUSERVARS MOVLW 0x05 ; Default report period is 5 seconds MOVWF varRP MOVLW cDF_FORMATTED MOVWF varDF MOVLW cMD_STANDBY MOVWF varMD BCF MICFLAGS, fMSGREQ ; Begin with a clear message request flag CALL RESETITPROC ; Reset the seconds timer CLRF OLDPORTBSW CLRF PUSHEDPORTBSW RETURN ; This routine is called during the periodic (4X baud rate) interrupt. It watches for button presses and ; manages the seconds timer used by the Digimatic interface routines. INTHOOK MOVF PORTB, W ; Get current PORTB bits ANDLW mPORTBSW ; Mask off button bits MOVWF ITMP1 ; Save in temp reg XORWF OLDPORTBSW, W ; XOR with previous PORTB bits (changed bits will be only ones set) ANDWF OLDPORTBSW, W ; Mask off button bits (on previous copy) that changed since last time IORWF PUSHEDPORTBSW, F ; WREG has bits set where pushes (1->0) just occured. OR with MOVF ITMP1, W ; PUSHEDPORTBSW reg and move current PORTB to OLDPORTBSW MOVWF OLDPORTBSW BTFSC SFLAGS, fPERIODUP RETURN ITPROC DECFSZ IT0, F RETURN DECFSZ IT1, F RETURN DECF ITSEC, F BTFSC STATUS, Z RESETITPROC BSF SFLAGS, fPERIODUP MOVLW SEC_IT0 MOVWF IT0 MOVLW SEC_IT1 MOVWF IT1 RETURN SERVICE_INTF BCF INTCON, INTF ; Clear interrupt flag BTFSS MICFLAGS, fMSGREQ RETURN BCF PORTB, ioDGM_REQ BCF STATUS, C BTFSC PORTB, ioDGM_DAT BSF STATUS, C RRF (MICBUFST + 0x00), F RRF (MICBUFST + 0x01), F RRF (MICBUFST + 0x02), F RRF (MICBUFST + 0x03), F RRF (MICBUFST + 0x04), F RRF (MICBUFST + 0x05), F RRF (MICBUFST + 0x06), F INCF MICBITCNT, F MOVF MICBITCNT, W SUBLW (cDAT_MSGLEN << 0x02) ; Message length in bits BTFSC STATUS, Z BCF MICFLAGS, fMSGREQ RETURN MAINLOOPHOOK MOVLW mPORTBSW ; If either switch (local pb, or gage pb) was pressed, ANDWF PUSHEDPORTBSW, W ; set "DOMICREAD" flag BTFSS STATUS, Z BSF MICFLAGS, fDOMICREAD MOVF varMD, W ; If we're not in "periodic" mode, skip ahead to end of XORLW cMD_PERIODIC ; section where we will set the "period is up" flag BTFSS STATUS, Z ; before falling through to the next section (ensures GOTO MLHNPSK ; clock starts fresh when mode is changed to periodic) BTFSS SFLAGS, fPERIODUP ; If we are, check if the period is up GOTO MLHSK ; If period isn't up yet, skip to next section MOVF varRP, W ; If the period is up, reset the clock MOVWF ITSEC BCF SFLAGS, fPERIODUP BSF MICFLAGS, fDOMICREAD ; And set the "DOMICREAD" flag GOTO MLHSK ; and skip to the next section MLHNPSK BSF SFLAGS, fPERIODUP ; If "standby" mode, set "period is up" flag and fall through MLHSK BTFSS MICFLAGS, fDOMICREAD ; If "DOMICREAD" set, read the mic and transmit the data RETURN ; (can be set via periodic trigger, "R" command or button presses) BCF MICFLAGS, fDOMICREAD CLRF PUSHEDPORTBSW ; Make sure switch press flags are clear READOUTGAGE BCF INTCON, INTF ; Clear interrupt flag CLRF MICBITCNT BSF MICFLAGS, fMSGREQ ; Tell ISR to listen for message BSF PORTB, ioDGM_REQ ; Prompt the gage for a new reading MOVLW D'200' ; Wait for at least 200 ms CALL DELXMS MOVF MICBITCNT, F ; Throw an error ("NO GAGE") if we haven't gotten a response yet MOVLW STRPTR(sNOGAGE) BTFSC STATUS, Z GOTO GAGEREADERR MOVLW D'60' ; Wait for at least 60 ms, the worst case message length CALL DELXMS MOVLW STRPTR(sBADREAD) BTFSC MICFLAGS, fMSGREQ ; If we haven't gotten a valid message at this point, GOTO GAGEREADERR ; return the "BAD READ" error MOVF varDF, W ; If DF is set to "RAW DATA" jump to its print handler SUBLW cDF_RAWBCD ; Otherwise, we just assume this switch is set to BTFSC STATUS, Z ; "formatted" and fall through GOTO GPR_RAW GPR_FORMATTED BCF MICFLAGS, fNONZERO ; Format and print the current reading (DF set to 1, or "Formatted") MOVLW cDAT_SIGN ; Prefix output with a minus sign if negative CALL GETMICNIBBLE ANDLW PMASK(fDAT_SIGN_NEG) MOVLW '-' BTFSS STATUS, Z CALL TXCHAR MOVLW 0x06 ; 6 Digits MOVWF TMP3 GPRF0LP DECF TMP3, F BTFSC TMP3, 7 GOTO GPRF0END MOVF TMP3, W ; Digits run backwards in our buffer ADDLW cDAT_DIGIT6 CALL GETMICNIBBLE ; Get next digit and save in TMP2 BTFSS STATUS, Z ; Set "got a nonzero number" flag if > 0 BSF MICFLAGS, fNONZERO MOVWF TMP2 MOVLW cDAT_DECLOC ; Does the decimal point come after the current digit? CALL GETMICNIBBLE SUBWF TMP3, W BTFSS STATUS, Z GOTO GNTDC MOVF TMP2, W CALL TXNUM ; If so, print the digit and set "got a nonzero number" flag BSF MICFLAGS, fNONZERO ; so that any zero digits past the decimal will be printed MOVLW '.' ; Print the decimal point CALL TXCHAR GOTO GPRF0LP ; Go back to the top of the loop GNTDC MOVF TMP2, W ; If decimal doesn't come after current digit, print the BTFSC MICFLAGS, fNONZERO ; digit only if the "got a nonzero" number flag is set CALL TXNUM GOTO GPRF0LP ; Go back to the top of the loop GPRF0END MOVLW ' ' ; Print a space CALL TXCHAR MOVLW cDAT_UNIT ; Send unit string ("IN" or "MM") CALL GETMICNIBBLE ANDLW PMASK(fDAT_UNIT_IN) MOVLW STRPTR(sIN) BTFSC STATUS, Z MOVLW STRPTR(sMM) GOTO TXDELIMSTR ; This routine prints the current reading as 13 raw hex digits (DF set to 0, or "Raw Data") GPR_RAW MOVLW cDAT_MSGLEN MOVWF TMP2 GPRLP DECF TMP2, W CALL GETMICNIBBLE ; Get next nibble CALL HEXTOASCII ; Convert to ASCII hex digit CALL TXCHAR ; Print it DECFSZ TMP2, F GOTO GPRLP GOTO TXDELIM GAGEREADERR MOVWF TMP1 ; Save error code in TMP1 MOVLW cMD_STANDBY ; Return to standby mode MOVWF varMD BCF MICFLAGS, fMSGREQ ; Reset the gage read routine and print the error BCF PORTB, ioDGM_REQ MOVF TMP1, W GOTO TXDELIMSTR ; Nibble index in WREG, returns with nibble in low half of WREG (and Z set if zero) ; Destroys TMP1 GETMICNIBBLE MOVWF TMP1 BCF STATUS, C RRF TMP1, W ADDLW MICBUFST MOVWF FSR MOVF INDF, W BTFSS TMP1, 0 SWAPF INDF, W ANDLW 0x0F RETURN ; Rules for Command Procs ; Can use TMP1, TMP2, TMP3 as temp ; Can use CPSTAT1, CPSTAT2 as semi static (preserved across calls for a single command line, cleared in between) ; Must return using RETLW, either PPROC_ERR or PPROC_NOERR ;===================================================================================================================== ; "HELP" COMMAND PROCEDURE CMDPROC_HELP MOVF CHUNKIDX, W ; Is CHUNKIDX zero, meaning "cmd line end" ? BTFSS STATUS, Z RETLW PPROC_ERR ; If not, return error, since this cmd takes no args TP1 MOVF PCMDIDX, W ; Save current PCMDIDX MOVWF TMP2 CLRF PCMDIDX ; Run through commands, printing names and info HELPLP MOVLW cCMDENTRY_NAME ; Based on current PCMDIDX, get pointer to current command name CALL GETCMDENTRYBYTE CALL TXSTRING ; Send the command name MOVLW '\t' ; Followed by a tab CALL TXCHAR MOVLW (HIGH HTAB_START) ; Look up string pointer in help string table MOVWF PCLATH MOVLW (LOW HTAB_START) ADDWF PCMDIDX, W CALL LOOKUPPROC CALL TXDELIMSTR ; Send the help info for the current command INCF PCMDIDX, F ; Loop through command list MOVLW (CTAB_PCMDEND - CTAB_PCMDSTART) SUBWF PCMDIDX, W BTFSS STATUS, Z GOTO HELPLP MOVF TMP2, W ; Restore PCMDIDX MOVWF PCMDIDX RETURN ;===================================================================================================================== ; "R" COMMAND PROCEDURE CMDPROC_READG MOVF CHUNKIDX, W ; Is CHUNKIDX zero, meaning "cmd line end" ? BTFSS STATUS, Z RETLW PPROC_ERR ; If not, return error, since this cmd takes no args BSF MICFLAGS, fDOMICREAD ; Set "do read" flag RETURN ;===================================================================================================================== ; "VERS" COMMAND PROCEDURE CMDPROC_VERS MOVF CHUNKIDX, W ; Is CHUNKIDX zero, meaning "cmd line end" ? BTFSS STATUS, Z RETLW PPROC_ERR ; If not, return error, since this cmd takes no args MOVLW STRPTR(sVERS1) CALL TXDELIMSTR MOVLW STRPTR(sVERS2) GOTO TXDELIMSTR ;===================================================================================================================== ; "SET/GET" PROCEDURE FOR "F" "M" "P" COMMANDS CMDPROC_SETGET MOVF CHUNKIDX, W ; Is CHUNKIDX zero, meaning "cmd line end" ? BTFSC STATUS, Z GOTO SETGETFINISH ; Yes, jump ahead and execute the command SUBLW 0x03 ; If we're on the second arg, return error BTFSC STATUS, Z RETLW PPROC_ERR CLRF ACCbLO ; We must be on the first (and only) argument. CLRF ACCbHI ; This command proc takes a decimal number from -128 to +127 CALL REWINDSCRATCH ; Point FSR at beginning of scratch buffer MOVF SCBUFLEN, W ; Set up to loop through scratch buffer MOVWF TMP3 MOVF INDF, W ; Is the first character a minus sign? SUBLW '-' BTFSS STATUS, Z GOTO SETGETNOTMIN BSF CPSTAT1, 0 ; Yes, set "negative" flag and move to next char GOTO SGNEXT SETGETNOTMIN SUBLW ('-' - '+') ; OK, was it a '+' sign? BTFSC STATUS, Z GOTO SGNEXT ; Yes, just ignore it and move ahead (otherwise fall through) SGLP MOVF INDF, W ; Get the next character CALL TESTNUM ; Is it a number? BTFSS STATUS, C RETLW PPROC_ERR ; If not, return error CALL XFERBTOA ; Multiply previous value by 10 MOVLW D'9' MOVWF TMP2 MTLP CALL D_add BTFSC STATUS, C RETLW PPROC_ERR ; Leave with error if overflow DECFSZ TMP2, F GOTO MTLP CLRF ACCaHI ; Add next digit to one's place MOVLW '0' SUBWF INDF, W MOVWF ACCaLO CALL D_add BTFSC STATUS, C RETLW PPROC_ERR ; Leave with error if overflow SGNEXT INCF FSR, F DECFSZ TMP3, F GOTO SGLP CALL XFERBTOA ; Negate if negative flag was set BTFSC CPSTAT1, 0 CALL neg_A CLRF ACCbHI ; ACCa now has binary value of number MOVLW 0x80 ; Load ACCb with +128 MOVWF ACCbLO CALL D_add ; Add 128 to value MOVF ACCbHI, W ; If result is <=255, we're OK BTFSS STATUS, Z RETLW PPROC_ERR ; Else, return error MOVLW (cCMDENTRY_CUST + 0x01) ; Check against min value from table CALL GETCMDENTRYBYTE SUBWF ACCaLO, W BTFSS STATUS, C RETLW PPROC_ERR MOVLW (cCMDENTRY_CUST + 0x02) ; Check against max value from table CALL GETCMDENTRYBYTE SUBWF ACCaLO, W BTFSS STATUS, Z BTFSS STATUS, C GOTO ARGINBOUNDS RETLW PPROC_ERR ARGINBOUNDS MOVF ACCaLO, W MOVWF CPSTAT2 BSF CPSTAT1, 1 ; Set "have arg" flag RETLW PPROC_NOERR ; On finish up call, return value is not important ; And, we must send response SETGETFINISH MOVLW (cCMDENTRY_CUST + 0x00) ; Get variable address from table CALL GETCMDENTRYBYTE MOVWF FSR BTFSC CPSTAT1, 1 GOTO SETTYPE GETTYPE MOVF INDF, W ; Get current value MOVWF TMP3 BTFSC TMP3, 7 ; Put absolute value in WREG CALL NEGATEWREG CALL B8_TO_BCD ; And convert it to BCD in ACCa MOVLW '-' BTFSC TMP3, 7 ; Send '-' if it was negative CALL TXCHAR BCF TMP1, 0 ; This portion prints the number w/out leading zeros MOVF ACCaHI, W ; Clear prev nonzero flag (see GTPD routine) CALL GTPD ; Send the first digit SWAPF ACCaLO, W ; Send second digit CALL GTPD BSF TMP1, 0 MOVF ACCaLO, W ; Send third digit (even if zero) CALL GTPD GOTO TXDELIM ; Send a delimiter, and return GTPD ANDLW 0x0F ; Zero test digit (in low nibble) BTFSC STATUS, Z ; If nonzero, or if prev nonzero flag set, send BTFSC TMP1, 0 ; the current digit (and set prev nonzero flag) GOTO GTPDSK RETURN GTPDSK BSF TMP1, 0 GOTO TXNUM SETTYPE MOVF CPSTAT2, W ; Set variable to new value MOVWF INDF MOVLW (CTAB_PCMD4 - CTAB_PCMDSTART) ; If "P" command, reset the timer SUBWF PCMDIDX, W BTFSC STATUS, Z CALL RESETITPROC MOVLW STRPTR(sOK) ; And return after sending "OK" GOTO TXDELIMSTR ;===================================================================================================================== ; USER PROC UTILITIES ; Convert hex digit in low half of WREG to an ASCII hex digit, returned in WREG HEXTOASCII ANDLW 0x0F SUBLW 0x09 BTFSS STATUS, C ADDLW ('9'-'A'+1) SUBLW '9' RETURN ; Negate WREG NEGATEWREG XORLW B'11111111' ADDLW 0x01 RETURN ; Double Precision Subtraction ACCb-ACCa->ACCb and Addition ACCb+ACCa->ACCb. Lifted from Microchip's math lib D_sub CALL neg_A ; At first negate ACCa; Then add D_add MOVF ACCaLO, W ADDWF ACCbLO, F ; add lsb BTFSC STATUS, C ; add in carry INCF ACCbHI, F MOVF ACCaHI, W ADDWF ACCbHI, F ; add msb RETURN neg_A COMF ACCaLO, F ; negate ACCa ( -ACCa -> ACCa ) INCF ACCaLO, F BTFSC STATUS, Z DECF ACCaHI, F COMF ACCaHI, F RETURN ; Binary To BCD Conversion Routine. Adapted from the Microchip routine. On entry, 8 bit binary number in WREG. ; On exit, the 3 digit BCD number is returned in ACCaHI, ACCaLO with MSD in right nibble of ACCaHI. ; Destroys TMP1, TMP2, ACCbLO B8_TO_BCD MOVWF ACCbLO BCF STATUS, C ; Clear the carry bit MOVLW D'8' MOVWF TMP2 CLRF ACCaHI CLRF ACCaLO loop8 RLF ACCbLO, F RLF ACCaLO, F RLF ACCaHI, F DECFSZ TMP2, F GOTO adjDEC RETURN adjDEC MOVLW ACCaLO MOVWF FSR CALL adjBCD MOVLW ACCaHI MOVWF FSR CALL adjBCD GOTO loop8 adjBCD MOVLW 0x03 ADDWF INDF, W MOVWF TMP1 BTFSC TMP1, 3 ; Test if result > 7 MOVWF INDF MOVLW 0x30 ADDWF INDF, W MOVWF TMP1 BTFSC TMP1, 7 ; Test if result > 7 MOVWF INDF ; Save as MSD RETURN ; Transfer ACCb to ACCa XFERBTOA MOVF ACCbLO, W MOVWF ACCaLO MOVF ACCbHI, W MOVWF ACCaHI RETURN ;===================================================================================================================== ; COMMAND TABLE ; - Command names in the table must be in caps ; - All strings, including command names, must be delimited by '\r' ; Commands: ; ; HELP LIST COMMANDS ; VERS VERSION INFO ; ; M [X] OPERATING MODE 0=STANDBY 1=PERIODIC READ ; P [X] READ PERIOD [SEC] ; F [X] DATA FORMAT 0=RAW BCD 1=FORMATTED ; R READ GAGE ORG 0x300 TABSTART CTAB_PCMDSTART ; Command List CTAB_PCMD0 DT (LOW CTAB_CMD0) CTAB_PCMD1 DT (LOW CTAB_CMD1) CTAB_PCMD2 DT (LOW CTAB_CMD2) CTAB_PCMD3 DT (LOW CTAB_CMD3) CTAB_PCMD4 DT (LOW CTAB_CMD4) CTAB_PCMD5 DT (LOW CTAB_CMD5) CTAB_PCMDEND ; "F" Command CTAB_CMD0 DT STRPTR(sCMD_DF) DT PROCPTR(CMDPROC_SETGET), varDF, 0x00, 0x01 ; "HELP" Command CTAB_CMD1 DT STRPTR(sCMD_HELP) DT PROCPTR(CMDPROC_HELP) ; "M" Command CTAB_CMD2 DT STRPTR(sCMD_MODE) DT PROCPTR(CMDPROC_SETGET), varMD, 0x00, 0x01 ; "R" Command CTAB_CMD3 DT STRPTR(sCMD_READGAGE) DT PROCPTR(CMDPROC_READG) ; "P" Command CTAB_CMD4 DT STRPTR(sCMD_READPERIOD) DT PROCPTR(CMDPROC_SETGET), varRP, D'1', D'60' ; "VERS" Command CTAB_CMD5 DT STRPTR(sCMD_VERSION) DT PROCPTR(CMDPROC_VERS) STRSTART sCMD_DF DT "F\r" sCMD_HELP DT "HELP\r" sCMD_MODE DT "M\r" sCMD_READGAGE DT "R\r" sCMD_READPERIOD DT "P\r" sCMD_VERSION DT "VERS\r" HTAB_START HTAB_CMD0 DT STRPTR(sHLP_DF) HTAB_CMD1 DT STRPTR(sHLP_HELP) HTAB_CMD2 DT STRPTR(sHLP_MODE) HTAB_CMD3 DT STRPTR(sHLP_READGAGE) HTAB_CMD4 DT STRPTR(sHLP_READPERIOD) HTAB_CMD5 DT STRPTR(sHLP_VERSION) sHLP_DF DT "FORMAT 0=RAW 1=DECIMAL\r" sHLP_HELP DT "LIST COMMANDS\r" sHLP_MODE DT "MODE 0=STANDBY 1=PERIODIC\r" sHLP_READGAGE DT "READ DATA\r" sHLP_READPERIOD DT "PERIOD SEC 1-60\r" sHLP_VERSION DT "VERSION\r" sOK DT "OK\r" sOVERFLOW DT "RX OV\r" sBADCMD DT "BAD CMD\r" sNOGAGE DT "NO GAGE\r" sBADREAD DT "BAD READ\r" sIN DT "IN\r" sMM DT "MM\r" sVERS1 DT "DIGIMATIC TO RS232 INTERFACE v1.0\r" sVERS2 DT "C1999 HUBEN CONSULTING INC.\r" END