                TITLE   "QwikLoader - PIC16F87x Bootloader 1.1"
                LIST    P=16F877
                INCLUDE "P16F877.INC"
;*************************************************************************
; $Id: qwikloader.asm,v 1.8 2008-10-21 19:17:35 jerry Exp $
; $Source: /home/cvsroot/qwikloader/qwikloader.asm,v $
;
;    QwikLoader - bootloader for PIC16F87x processors
;    Copyright (C) 2003  Jerry E. Dunmire
; 
;    This program is free software; you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation; either version 2 of the License, or
;    (at your option) any later version.
;
;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.
;
;    You should have received a copy of the GNU General Public License
;    along with this program; if not, write to the Free Software
;    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
;
;    Author: Jerry Dunmire
;            jerry@dunmire.org
;            3720 Benton St.
;            Santa Clara, CA 95051
;
;    Based on picloader v1.5 by Rick Farmer, rfarmer@skyweb.net
;    http://www.dontronics.com/rfarmer.html
;
;*************************************************************************
;               Revision History at EOF
;
;*************************************************************************
;               Program Description
;
;  This program is a resident serial bootloader for the Microchip PIC16F87x
; series of microcontrollers. It resides in the last 2K of code space and
; uses the first 3 memory locations on the first page to hook the reset
; vector. The program does
; not use any interrupts minimizing the impact on user code. It will
; service the watchdog timer, which is enabled below.
;
;  If you make any changes or additions, please send me a copy so that
; I can merge them back into this distribution.
;
;  This code is released under the GPL.
;
;*************************************************************************
;               Program Usage
;
;  The first time a chip powers up with the bootloader, or any time that
; it powers up without a user image it will automatically activate. Once
; a user image has been loaded, it requires that CTS_D pin be pulled low AND
; that a capital 'Z' arrive within 5 seconds of boot up or it will jump to
; the user image. If CTS_D is high the UART will not even be checked for input.
; If CTS_D is low and anything other than a 'Z' is received, it will jump to
; the user image immediately. If you want to modify this behavior change
; the routine BOOTIMR. The pin can be moved by changing the CTSPORT and
; CTS_D definitions in the configuration area below.
;
;  This code sets up the serial port pins and the UART to 19200 baud assuming
; a 20.00MHz clock. The serial port is left active when control is given to
; the user code. Set the terminal to ANSI mode 19200-8-N-1, no flow control,
; local echo off, 40ms line delay.
;
;  The bootloader reserves the entire last page of code space and the first
; three memory locations. The bootloader will not allow itself to be
; overwritten. Attempting to do so will result in an error message, and the
; upload will be aborted. User code at 0000h-0003h (the reset vector
; area) will be relocated. You do NOT have to relocate your code. The
; only limitiation is that the first instruction of your code can not be
; 'addlw 0ffh' with the low bit of the high byte set.
; (The all ones opcode- which is used as an indication that memory has
; not been programmed.) This should not be a serious limitation because
; Microchip recommends that the low bit of the high byte of 'andlw 0ffh'
; be set to zero. (It is a don't care bit in the opcode table.)
;
;  There are compile options for chip memory size and password protection.
; The password feature locks the user image with a 8 character password
; that is hardcoded into the last 8 memory locations of the bootloader.
;
;  The watchdog timer is enabled by the default configuration setting
; below. Your code should service the watchdog timer, or you will need
; to modify the configuration setting in this file and rebuild the
; bootloader.
;
; Configuration settings in your HEX file will be ignored. The extended
; address record, part of the IHHX32 file, is also ignored. A warning
; message will be printed when a line is ignored.
;
;*************************************************************************
;               Suppress warning errors from MPASM

                errorlevel -306
                errorlevel -302

;*************************************************************************
;               Configuration settings

ON              EQU     1       ;TRUE
OFF             EQU     0       ;FALSE

FULLMEM         SET     ON      ;full, 8K parts, 16F877/16F876
;FULLMEM         SET     OFF     ;half, 4K parts, 16F874/16F873

;PASSPROT        SET     ON      ;password protection is on
PASSPROT        SET     OFF     ;password protection is off

;Oscillator frequency, typically 3.6864, 4, 10, 16, or 20MHz
FOSC    equ     D'20000000'

;Communications speed - note that not all baud rates are possible
;                       Do not change this value without reviewing
;                       section 10.1 of the PIC16F87x data sheet.
BAUD    equ     D'19200'

;CTS bit selection
; E2 for MEEPS
; B6 for generic picloader replacement
CTSPORT equ     PORTE
CTS_D     equ     2h              ;CTS input from host
                                  ;(The _D designation signifies that
                                  ;this is a data signal rather than
                                  ;the RS-232 signal.)

;*************************************************************************
;               CPU Configuration Fuses
;
; Note that the watchdog timer is enabled and will be serviced while
; QwikLoader (this program) is running. The user program must service
; the watchdog to avoid the watchdog reset.

 __CONFIG  _BODEN_ON&_CP_OFF&_WRT_ENABLE_ON&_PWRTE_ON&_WDT_ON&_HS_OSC&_DEBUG_OFF&_CPD_OFF&_LVP_OFF

;*************************************************************************
;		Variable Declarations

FLAGS   equ     20h             ;various binary status flags reg

DR      equ     00h             ;1 = data from UART was returned in W
VALID   equ     01h             ;1 = there is valid user code present
LOAD    equ     02h             ;1 = user wants to upload code
NOK     equ     03h             ;1 = upload not succesful
ECHO    equ     04h             ;1 = no echo in GETLINE
DIG     equ     05h             ;1 = number has a leading digit
PRGFL   equ     06h             ;1 = programming an image
STAR    equ     07h             ;1 = echo * in GETLINE

QTEMP   equ     21h             ;temp var for queueing routines
INDEX   equ     22h             ;counter for indexing into strings
FDATAH  equ     23h             ;flash data high byte
FDATAL  equ     24h             ;flash data low byte
FADDRH  equ     25h             ;flash address high byte
FADDRL  equ     26h             ;flash address low byte
EDATA   equ     27h             ;EE data byte
EADDR   equ     28h             ;EE addess byte
CMD     equ     29h             ;user command byte
TEMP    equ     2Ah             ;temp var
JUNK    equ     2Bh             ;2nd temp var
BYTECTR equ     2Ch             ;line data counter
CHKSUM  equ     2Dh             ;hex file line check sum
LINCTRO equ     2Eh             ;hex file line counter 1's
LINCTRT equ     2Fh             ;hex file line counter 10's
LINCTRH equ     30h             ;hex file line counter 100's
EECTR   equ     31h             ;count of EE bytes sent
LINTYPE equ     32h             ;hex file line type
WRTCTR  equ     33h             ;2nd copy of byte counter
HOLD    equ     34h             ;temp var
SAVAH   equ     35h             ;address save location
SAVAL   equ     36h             ;address save location
BTCNT   equ     37h             ;count TMR0 roll overs for GETC time limit
DOTCNT  equ     38h             ;dot counter for load progress indicator
EEPTR   equ     39h             ;pointer to EE address in hex file
DATAFSR equ     3Ah             ;start of data on a hex file line
RSTTMRL equ     3Bh             ;bootloader timer
RSTTMRH equ     3Ch             ;bootloader timer
UDATAH  equ     3Dh             ;user reset opcode flash data high byte
UDATAL  equ     3Eh             ;user reset opcode flash data low byte

LBUFRH  equ     50h             ;line buffer head
LBUFRT  equ     7Fh             ;line buffer tail (48 byte line buffer)

;*************************************************************************
;               Pin Declarations
;
;PORTC
TX      equ     06h             ;TX pin
RX      equ     07h             ;RX pin

;*************************************************************************
;		Constant Declarations

BSPACE  equ     08h             ;ASCII control H
CRETURN equ     0Dh             ;ASCII carriage return
LFEED   equ     0Ah             ;ASCII line feed
ESCAPE  equ     1Bh             ;ASCII escape character

;*************************************************************************
;               Constant Calculations
;
;    WARNING: These calculations rely on the assembler performing 32-bit math.
;    The values calculated will be understated due to truncations
;    inherent in the integer math.
;
;  Calculate the baud setting using the formula from Table 10-1 in the
; PIC16F87x data sheet. BRGH will be set to 1 (high speed).
BAUDSET equ     ((FOSC / BAUD) / D'16') - 1

;  Calculate delay counts for use with Timer 0 roll-over delays
; such as those used in BOOTIMR and GETC. The constant names
; indicated the approximate delay in milliseconds.
T0RATE  equ     (FOSC / D'4') / D'256'
DLY250  equ     (((D'250' * T0RATE) / D'256') / D'1000')
DLY3000 equ     (((D'3000' * T0RATE) / D'256') / D'1000')

;*************************************************************************
;               Reset Vector

        org     0000h           ;hardware reset vector

        movlw   (high BOOTLDR) | 07h ;bank 1 for jumps, calls, and goto's
        movwf   PCLATH
        goto    BOOTLDR

;*************************************************************************
;BOOTLDR        This is the Bootloader reset vector.

  if     FULLMEM == ON          ;set origin to last page of memory
        org     1800h           ;bootloader code origin
  else
        org     0800h           ;bootloader code origin
  endif


BOOTLDR bcf     STATUS,RP1      ;page 0 or 1 (org x800h)
        bsf     STATUS,RP0      ;switch to memory page 1

        ;configure the UART
        movlw   BAUDSET
        movwf   SPBRG           ;load baud rate reg

        movlw   B'00100110'     ;8bit, TX on, asynch, BRGH high
        movwf   TXSTA           ;load TX control reg

        ;configure I/O pins
        bsf     PORTC,TX        ;(TRISC,TX) TX can now be used for UART
        bsf     PORTC,RX        ;(TRISC,RX) RX can now be used for UART
        bsf     CTSPORT,CTS_D   ;(TRISx,CTS_D) ensure CTS_D pin is an input
        movlw   B'00000110'     ;No analog inputs (port E is analog in
        movwf   ADCON1          ;  after a reset)

        ;configure Timer0
        movlw   B'01010111'     ;TMR0 clk = OSC/256, port-B pull-ups ON
        movwf   OPTION_REG      ;load reg (TMR0 rolls 79.24 times/sec @ 20MHz)

        bcf     STATUS,RP0      ;switch back to page 0

        clrf    INTCON          ;ensure interrupts are turned off

        movlw   B'10010000'     ;enable receiver in 8 bit asynch mode
        movwf   RCSTA           ;load RX control reg

        clrf    FLAGS           ;init the flags reg

        call    USRSCAN         ;scan for bootable user image
        btfss   FLAGS,VALID     ;is there a bootable user image?
         goto   PAS_TOP         ;no, skip start timer

        call    BOOTIMR         ;yes, do countdown to user code boot
        btfss   FLAGS,LOAD      ;do we load new code?
         goto   BLEXIT          ;no, boot user image quietly

PAS_TOP call    SPLASH          ;send the startup message
        call    PRT_REV         ;send the current code revision

PAS_LOP bsf     FLAGS,STAR      ;yes, set the echo a star flag
        call    PASSWD          ;check password before we allow access
        bcf     FLAGS,STAR      ;clear the echo a star flag
        btfsc   FLAGS,NOK       ;was password correct?
         goto   BLPAS           ;yes

        btfsc   FLAGS,VALID     ;no, is there a valid user image?
         goto   BLDONE          ;yes, boot user image
        goto    PAS_LOP         ;no, loop until a valid password is entered

BLPAS   call    HELPIST         ;send the help prompt message

                                ;end of initialization

BLMAIN  call    PROMPT          ;send a command prompt to the screen
        call    GETLINE         ;wait for a line of data
        movf    INDF,W          ;get the first char of line
        call    UPCASE          ;force the command to upper case
        movwf   CMD             ;save the first keystroke as a command
        btfsc   STATUS,Z        ;was null string returned?
         goto   BLMAIN          ;yes, eat it


NOT_JNK movlw   'W'             ;no, load write EE command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "W/w"?
         goto   NOT_WR          ;no

        call    EEWRITE         ;yes, write EE byte
        goto    BLMAIN          ;get next command


NOT_WR  movlw   'R'             ;load read EE command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "R/r"?
         goto   NOT_EE          ;no

        call    EEREAD          ;yes, read EE byte
        goto    BLMAIN          ;get next command


NOT_EE  incf    FSR,F           ;inc pointer to null char that should end line.
        movf    INDF,W          ;test for zero
        btfss   STATUS,Z        ;null?
         goto   NOT_CMD         ;no, eat line because it had garbage on it

        movlw   '?'             ;yes, load help request command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "?"?
         goto   NOT_HLP         ;no

        call    CMDLIST         ;yes, display the list of valid commands
        goto    BLMAIN          ;get next command


NOT_HLP movlw   'U'             ;load upload command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "U/u"?
         goto   NOT_COL         ;no

        call    UPLOAD          ;yes, begin hex file upload
        goto    BLMAIN          ;get next command


NOT_COL movlw   'D'             ;load download command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "D/d"?
         goto   NOT_DL          ;no

        call    DNLOAD          ;yes, dump current user code image
        goto    BLMAIN          ;get next command


NOT_DL  movlw   'Q'             ;load quit command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "Q/q"?
         goto   NOT_Q           ;no

        btfsc   FLAGS,VALID     ;yes, is there an image to jump to?
         goto   BLDONE          ;yes, we're done

        call    PRT_REV         ;no, send the current code revision (blank)
        goto    BLMAIN          ;ignore command & get another one


NOT_Q   movlw   'V'             ;load display code rev command
        subwf   CMD,W           ;compare
        btfss   STATUS,Z        ;did user press "V/v"?
         goto   NOT_CMD         ;no

        call    PRT_REV         ;yes, send the current code revision
        goto    BLMAIN          ;get next command


NOT_CMD call    CMDERR          ;send invalid command error
        goto    BLMAIN          ;get next command


BLDONE  btfss   FLAGS,VALID     ;is there an image to jump to?
         goto   BLMAIN          ;no, get next command

        call    TMRMSG          ;yes, display jumping to user code message
BLEXIT  goto    GOTOUSR

;*************************************************************************
;PASSWD         This routine password protects user code from overwrite.

PASSWD  bcf     FLAGS,NOK       ;clear the password valid bit

 if  PASSPROT == OFF            ;fake return of a valid password

        goto    GOOD_PS         ;password checking is off

 endif

        movlw   'P'             ;"Passsword>"
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'w'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '>'             ;load ASCII char
        call    TXBYTE          ;send character

        call    GETLINE         ;get a line from the user

PSWDLOP movf    INDEX,W         ;get index into password table
        call    PASTBL          ;get password char
        subwf   INDF,W          ;compare to char that was entered
        btfss   STATUS,Z        ;same?
         goto   BAD_PS          ;no

        movf    INDF,W          ;yes, reload entered data
        btfsc   STATUS,Z        ;was the char the null string terminator?
         goto   GOOD_PS         ;yes, password is valid

        btfsc   INDEX,3         ;no, is index = 8 (max password length)?
         goto   BAD_PS          ;yes, password is invalid

        incf    INDEX,F         ;no, inc index into table
        incf    FSR,F           ;inc index into entered data
        goto    PSWDLOP         ;go check the next char

GOOD_PS bsf     FLAGS,NOK       ;set the password valid bit
BAD_PS  return                  ;done

;*************************************************************************
;EEREAD         This routine reads an EE data byte.

EEREAD  incf    FSR,F           ;inc pointer to space char
        movlw   ' '             ;load a space
        subwf   INDF,W          ;compare
        btfss   STATUS,Z        ;same?
         goto   EER_ER          ;no, bail

        incf    FSR,F           ;yes, inc pointer to first hex char
        bcf     FLAGS,NOK       ;clear the error flag
        call    HEXQ            ;call routine to hexify 2 chars from queue
        movwf   EADDR           ;load address to read
        btfsc   FLAGS,NOK       ;was there an error?
         goto   EER_ER          ;yes, bail

        movf    INDF,W          ;no, test for zero
        btfss   STATUS,Z        ;null?
         goto   EER_ER          ;no, bail

  if     FULLMEM == OFF

        btfss   EADDR,7         ;is EADDR > 80h?
         goto   EERNG2          ;no

        clrf    FADDRH          ;yes, clear high byte address
        movf    EADDR,W         ;get EE address
        movwf   FADDRL          ;load for error message
        call    ADRERR          ;send error message
        return                  ;done

  endif

EERNG2  call    EREAD           ;read the byte
        call    EEDISP          ;display EE data location
        return                  ;done

EER_ER  call    CMDERR          ;send invalid command error
        return                  ;done

;*************************************************************************
;EEWRITE        This routine writes an EE data byte.

EEWRITE incf    FSR,F           ;inc pointer to space char
        movlw   ' '             ;load a space
        subwf   INDF,W          ;compare
        btfss   STATUS,Z        ;same?
         goto   EEW_ER          ;no, bail

        incf    FSR,F           ;yes, inc pointer to first hex char
        bcf     FLAGS,NOK       ;clear the error flag
        call    HEXQ            ;call routine to hexify 2 chars from queue
        btfsc   FLAGS,NOK       ;was there an error?
         goto   EEW_ER          ;yes, bail

        movwf   EDATA           ;no, load data to write

        movlw   ' '             ;load a space
        subwf   INDF,W          ;compare
        btfss   STATUS,Z        ;same?
         goto   EEW_ER          ;no, bail

        incf    FSR,F           ;yes, inc pointer to "@" char
        movlw   '@'             ;load a "@"
        subwf   INDF,W          ;compare
        btfss   STATUS,Z        ;same?
         goto   EEW_ER          ;no, bail

        incf    FSR,F           ;yes, inc pointer to space char
        movlw   ' '             ;load a space
        subwf   INDF,W          ;compare
        btfss   STATUS,Z        ;same?
         goto   EEW_ER          ;no, bail

        incf    FSR,F           ;yes, inc pointer to second hex char
        call    HEXQ            ;call routine to hexify 2 chars from queue
        movwf   EADDR           ;load address to write data

  if     FULLMEM == OFF

        btfss   EADDR,7         ;is EADDR > 80h?
         goto   EERNGOK         ;no

        clrf    FADDRH          ;yes, clear high byte address
        movf    EADDR,W         ;get EE address
        movwf   FADDRL          ;load for error message
        call    ADRERR          ;send error message
        return                  ;done

  endif


EERNGOK btfsc   FLAGS,NOK       ;was there an error?
         goto   EEW_ER          ;yes, bail

        movf    INDF,W          ;no, test for zero
        btfss   STATUS,Z        ;null?
         goto   EEW_ER          ;no, bail

        call    EWRITE          ;yes, write the data
        call    EREAD           ;read the byte we just wrote
        call    EEDISP          ;display EE data location
        return                  ;done

EEW_ER  call    CMDERR          ;send invalid command error
        return                  ;done

;*************************************************************************
;GET_REV        This routine gets and stores the user code rev string.

GET_REV movlw   high REVSTR1    ;high byte of user revision string
                                ;storage area
        movwf   FADDRH          ;store it
        movlw   low REVSTR1     ;low byte of user revision string
                                ;storage area
        movwf   FADDRL          ;store it
        movlw   34h             ;load "retlw" instruction code
        movwf   FDATAH          ;store it
        call    REV_MSG         ;prompt for rev string

        call    GETLINE         ;get a line of data
        incf    INDEX,F         ;pre-inc the counter

REV_LOP movf    INDF,W          ;get queued data
        movwf   FDATAL          ;load data reg
        call    FWRITE          ;program it

        movf    INDF,W          ;get queued data
        btfsc   STATUS,Z        ;was that the null terminator?
         return                 ;yes, done

        incf    FADDRL,F        ;no, inc the programming address
        incf    FSR,F           ;inc the read pointer
        incf    INDEX,F         ;inc the counter
        btfsc   INDEX,5         ;has index hit 32?  JED -- Warning magic #
         clrf   INDF            ;yes, force last to null termiantor
        goto    REV_LOP         ;do it again

;*************************************************************************
;RXBYTE         This routine gets a byte from the UART.
;               If data is present it is returned in "W" and the DR flag is set.

RXBYTE  clrwdt                  ;kick the dog
        bcf     FLAGS,DR        ;clear the data ready flag

        btfsc   RCSTA,FERR      ;framing error?
         goto   NOISE           ;yes, eat the byte and return

        btfss   RCSTA,OERR      ;no, has buffer overrun?
         goto   NO_OVRR         ;no

        bcf     RCSTA,CREN      ;yes, reset the UART
        bsf     RCSTA,CREN      ;restart the UART

NOISE   movf    RCREG,W         ;read garbage/corrupted byte
        return                  ;done

NO_OVRR btfss   PIR1,RCIF       ;is there data present?
         return                 ;no, done

        bsf     FLAGS,DR        ;yes, set data ready flag
        movf    RCREG,W         ;dequeue byte
        movwf   QTEMP           ;save it

        sublw   LFEED           ;check for line feed char
        btfsc   STATUS,Z        ;was LF received?
         bcf    FLAGS,DR        ;yes, clear byte ready flag

        movf    QTEMP,W         ;get RX'ed byte into W
        return                  ;done

;*************************************************************************
;GETBYTE        This routine will wait about 10 minutes for a byte to queue.

GETBYTE clrf    TMR0            ;clear timer 0
        bcf     INTCON,T0IF     ;clear the roll over flag
        clrf    RSTTMRH         ;zero activity reset timer
        clrf    RSTTMRL         ;zero activity reset timer

RX_LOP  call    RXBYTE          ;look for a byte
        btfss   FLAGS,DR        ;was a char ready?
         goto   NO_RX           ;no

        return                  ;yes, done

NO_RX   btfss   INTCON,T0IF     ;has timer 0 rolled?
         goto   RX_LOP          ;no

        incf    RSTTMRL,F       ;yes, inc reset timer
        btfsc   STATUS,Z        ;carry out?
         incf   RSTTMRH,F       ;yes, inc reset timer

        btfss   RSTTMRH,5       ;has about 10 minutes gone by idle?
         return                 ;no, done

        btfsc   FLAGS,VALID     ;yes, is there an image to jump to?
         goto   BLDONE          ;yes, exit bootloader
        return                  ;no, stay in bootloader

;*************************************************************************
;GETC   Get a character from the UART within a time limit
;
;       Requires:
;         BTCNT - set for the number of Timer 0 roll overs to allow
;       
;       Returns:
;         If FLAGS,DR is set, then the received byte is in W
;         If FLAGS,DR is not set, then a timeout occurred.
;
GETC    clrwdt                  ;kick the dog
        call    RXBYTE          ;check for a character
        btfsc   FLAGS,DR
         goto   GETC_EX         ;yes, return

        btfss   INTCON,T0IF     ;no, has timer zero rolled?
         goto   GETC            ;no, keep checking

        bcf     INTCON,T0IF     ;yes, clear timer roll over flag
        decfsz  BTCNT,F         ;end of count?
         goto   GETC            ;no, keep checking

GETC_EX return

;*************************************************************************
;EATBUF         This routine eats all incoming data until the UART is quiet.
;               The delay time must allow for the end of line delay
;               inserted by the upload program when a HEX file is
;               transmitted. Typically that time will be 100ms or less.

EATBUF  movlw   DLY250          ;set counter for 250ms
        movwf   BTCNT

        call    GETC            ;get a character, or timeout trying
        btfsc   FLAGS,DR        ;character received?
         goto   EATBUF          ;yes, get another character

        return                  ;no, the UART is quiet, so return


;*************************************************************************
;BOOTIMR        This routine waits a limited time for a capital 'Z' (0x5Ah).

BOOTIMR bcf     FLAGS,LOAD      ;clear the load code flag
        btfsc   CTSPORT,CTS_D   ;are we connected to a terminal (is CTS_D low)?
         goto   BTMR_EX         ;no, bail

        movlw   DLY3000         ;yes, set Delay for 3 seconds
        movwf   BTCNT           ;load counter

BOOTLOP call    GETC            ;get a character or time out
        btfss   FLAGS,DR        ;did the user hit a key?
         goto   BTMR_EX         ;no, bail

        sublw   'Z'             ;yes, compare to a big 'Z'
        btfsc   STATUS,Z        ;same?
         bsf    FLAGS,LOAD      ;yes, set flag for forground to load code

BTMR_EX return                  ;done

;*************************************************************************
;GETLINE        This routine reads in a line from the terminal.
;               The line is stored in the queue as a null terimated string.

GETLINE movlw   LBUFRH          ;load start of line buffer
        movwf   FSR             ;into line pointer


LINELOP call    GETBYTE         ;scan for a character
        btfss   FLAGS,DR        ;was a char ready?
         goto   LINELOP         ;no

        movlw   BSPACE          ;yes, load DEL
        subwf   QTEMP,W         ;compare
        btfss   STATUS,Z        ;was that a DEL?
         goto   LIN_NBS         ;no

        movlw   LBUFRH          ;yes, load start of line buffer
        subwf   FSR,W           ;compare to line pointer
        btfsc   STATUS,Z        ;same (at begin of line)?
         goto   LINELOP         ;yes, just eat the char

        decf    FSR,F           ;no, dec pointer
        movlw   BSPACE          ;backspace over char
        btfss   FLAGS,ECHO      ;should we echo at all?
         call   TXBYTE          ;yes, send character

        movlw   ' '             ;load over write char
        btfss   FLAGS,ECHO      ;should we echo at all?
         call   TXBYTE          ;yes, send character

        movlw   BSPACE          ;backspace agian
        btfss   FLAGS,ECHO      ;should we echo at all?
         call   TXBYTE          ;yes, send character
        goto    LINELOP         ;get next char


LIN_NBS movlw   CRETURN         ;load CR
        subwf   QTEMP,W         ;compare
        btfsc   STATUS,Z        ;was that a CR?
         goto   LIN_CHK         ;yes, go check what we've read in

        movf    QTEMP,W         ;no, echo character to screen
        movwf   INDF            ;store it first
        btfsc   FLAGS,STAR      ;should we echo a start (*) instead?
         movlw  '*'             ;yes
        btfss   FLAGS,ECHO      ;should we echo at all?
         call   TXBYTE          ;yes, send character

        movlw   LBUFRT          ;load end of line buffer
        subwf   FSR,W           ;compare to line pointer
        btfsc   STATUS,Z        ;same (at end of line)?
         goto   LIN_CHK         ;yes, bail

        incf    FSR,F           ;no, inc Q pointer
        goto    LINELOP         ;get another char

LIN_CHK btfss   FLAGS,ECHO      ;should we echo at all?
         call   CRLF            ;yes, send a CR/LF
        clrf    INDF            ;null terminate the input string

        clrf    INDEX           ;clear the index pointer
        movlw   LBUFRH          ;load start of line buffer
        movwf   FSR             ;into line pointer
        return                  ;done

;*************************************************************************
;TXBYTE         This routine sends "W" out the UART.

TXBYTE  clrf    TMR0            ;clear timer 0
        bcf     INTCON,T0IF     ;clear the roll over flag

TXLOOP  clrwdt                  ;kick the dog
        btfss   PIR1,TXIF       ;xmit buffer empty?
         goto   TXLOOP          ;no, wait

        movwf   TXREG           ;yes, send the byte
        return                  ;done

;*************************************************************************
;HEXQ           This routine gets 2 ASCII chars and converts them to binary.
;               This routine gets it's input from the queue (FSR).

HEXQ    movf    INDF,W          ;get first char from the queue
        call    UPCASE          ;force to upper case
        call    HEXIFY          ;convert the character to binary
        btfsc   FLAGS,NOK       ;was there an error?
         return                 ;yes, quit

        movwf   JUNK            ;no, store result in temp var
        swapf   JUNK,F          ;swap data to high nibble

        incf    FSR,F           ;inc queue pointer
        movf    INDF,W          ;get second char from the queue
        call    UPCASE          ;force to upper case
        call    HEXIFY          ;convert the character to binary
        iorwf   JUNK,F          ;store result in low nibble
        movf    JUNK,W          ;move result to W

        incf    FSR,F           ;post inc queue pointer
        return                  ;done

;*************************************************************************
;USRSCAN        This routine scans for a valid user image.
;               The user image is valid when the user reset vector
;               has been programmed. Check for the erased value at
;               the reset vector location (USRVEC). It the location is
;               erased then exit with the user code flag cleared. If
;               the erased value has been changed, then set the user
;               code flag to indicate that user code is present.
;               
USRSCAN bcf     FLAGS,VALID     ;clear the valid user code flag
        movlw   high USRVEC     ;load high byte of address to read
        movwf   FADDRH          ;store high byte of address to read
        movlw   low USRVEC      ;load low byte of address to read
        movwf   FADDRL          ;store low byte of address to read
        call    FREAD           ;read from USRVEC (user code reset vector)
        movlw   03Fh            ;high byte of erased value
        subwf   FDATAH,W
        btfss   STATUS,Z        ;does the high byte match the erased value?
         goto   CODEOK          ;no, user code is present set flag and exit
        movlw   0FFh            ;yes, check low byte of erased value
        subwf   FDATAL,W
        btfss   STATUS,Z        ;does the low byte match the erased value?
CODEOK    bsf    FLAGS,VALID    ;no, set valid user code flag
        return                  ;done

;*************************************************************************
;HEXIFY         This routine turns a ASCII digit in "W" into a HEX value.
;               The char must be uppercase.

HEXIFY  movwf   TEMP            ;save input value
        movlw   '0'             ;load a zero
        subwf   TEMP,F          ;subtract zero offset
        btfss   STATUS,C        ;underflow = non hex?
         goto   BADHEX          ;yes, bail

        movlw   '9'-'0'+1       ;no, load 9 + 1
        subwf   TEMP,W          ;subtract 10 offset
        btfss   STATUS,C        ;underflow = 0 to 9?
         goto   RTNHEX          ;yes, return result

        movlw   'A'-'0'         ;no, load value to make 'A' = 0
        subwf   TEMP,F          ;subtract offset
        btfss   STATUS,C        ;underflow = non hex?
         goto   BADHEX          ;yes, bail

        movlw   'F'-'A'+1       ;no, load max offset
        subwf   TEMP,W          ;subtract 10 offset
        btfsc   STATUS,C        ;underflow = A to F?
        goto    BADHEX          ;no, bail

        movlw   0Ah             ;yes, load 10
        addwf   TEMP,F          ;set value to 10-15

RTNHEX  movf    TEMP,W          ;send hex result
        return                  ;done

BADHEX  bsf     FLAGS,NOK       ;no, set the error flag
        call    HEXERR          ;send "Non-HEX char" error message
        retlw   00h             ;return a null

;*************************************************************************
;UPCASE         This routine will convert all chars to upper case.
;               The raw data is expected in W, result returned in W.

UPCASE  movwf   QTEMP           ;store char before modification
        movlw   'z'+1           ;load highest lowercase letter +1
        subwf   QTEMP,W         ;compare
        btfsc   STATUS,C        ;is data less than or equal to "z"
         goto   NOT_LCS         ;no

        movlw   'a'             ;yes, load lowest lowercase letter
        subwf   QTEMP,W         ;compare
        btfss   STATUS,C        ;is data greater than or equal to "a"?
         goto   NOT_LCS         ;no

        movlw   'a'-'A'         ;yes, load difference between cases
        subwf   QTEMP,W         ;convert to upper case
        return                  ;done

NOT_LCS movf    QTEMP,W         ;reload W original data
        return                  ;done

;*************************************************************************
;BLNKCHK        This routine checks for a blank program memory location.

BLNKCHK call    FREAD           ;get current memory contents
        movlw   3Fh             ;high byte blank value
        subwf   FDATAH,W        ;compare
        btfss   STATUS,Z        ;same?
         return                 ;no, return Z bit clear = opcode present

        movlw   0FFh            ;yes, low byte blank value
        subwf   FDATAL,W        ;compare
        return                  ;return Z bit set if blank, clear if opcode

;*************************************************************************
;DNLOAD         This routine downloads a hex file from program memory.

DNLOAD  bcf     FLAGS,NOK       ;clear download error flag.
        call    PRT_REV         ;send rev string or part is blank message
        btfss   FLAGS,VALID     ;is there a valid image in memory?
         return                 ;no, done


DN_OK   ;output a data record for the user vector first
        ;The 2nd entry point to SENDLN is used because the
        ;vector was relocated on upload and we are going to put
        ;it back (to 0000h) on download.
        ;
        ;Note that there are always 3 instructions in this first
        ;hex data record. I did this to avoid running out of code space.
        ; jed - Sept. 17, 2003
        movlw   high USRVEC     ;output the user reset vector first
        movwf   SAVAH           ;set the saved address to point to the
        movlw   low USRVEC      ; relocated location
        movwf   SAVAL
        movlw   D'3'            ;set the number of instructions relocated
        movwf   BYTECTR
        clrf    FADDRH          ;set the hex record address to 0000h
        clrf    FADDRL
        call    RELADDR         ;call the 2nd entry point of SENDLIN

        
        ;now output the rest of the user program, starting at
        ;address 0003h
        clrf    FADDRH          ;store high byte of address to read
        movlw   03h             ;load low byte of target
        movwf   FADDRL          ;store low byte of address to read

DN_LOP  clrf    BYTECTR         ;clear the byte counter
        call    BLNKCHK         ;check for a blank memory location
        btfss   STATUS,Z        ;is location blank?
         goto   NOTBLNK         ;no, go calculate how many bytes to send

        incf    FADDRL,F        ;yes, inc address low byte
        btfsc   STATUS,Z        ;did low byte carry out?
         incf   FADDRH,F        ;yes, inc address high byte

        call    RANGE           ;no, range check
        btfss   FLAGS,NOK       ;hit the end of memory?
         goto   DN_LOP          ;no
        goto    EORLIN          ;yes, exit


NOTBLNK movf    FADDRH,W        ;get high address byte
        movwf   SAVAH           ;save beginning of valid data address
        movf    FADDRL,W        ;get low address byte
        movwf   SAVAL           ;save beginning of valid data address

QLINE   incf    BYTECTR,F       ;inc number of words to put in line
        movf    FADDRL,W        ;get low address
        movwf   TEMP            ;save it for DC check later
        incf    FADDRL,F        ;inc address low byte
        btfsc   STATUS,Z        ;did low byte carry out?
         incf   FADDRH,F        ;yes, inc address high byte

        movf    TEMP,W          ;get pre-incf low address
        xorwf   FADDRL,W        ;compare bitwise
        andlw   0F8h            ;strip low 3 bits (8 word/16 byte boundary)
        btfss   STATUS,Z        ;have we crossed an 8 word boundary?
         goto   BLSTLIN         ;yes

        call    RANGE           ;no, range check
        btfsc   FLAGS,NOK       ;hit the end of memory?
         goto   BLSTLIN         ;yes

        call    BLNKCHK         ;no, check for a blank memory location
        btfss   STATUS,Z        ;is location blank?
         goto   QLINE           ;no, scan next memory location


BLSTLIN call    SENDLIN         ;yes, BYTECTR holds number of words to send
        call    RANGE           ;range check
        btfss   FLAGS,NOK       ;hit the end of memory?
         goto   DN_LOP          ;no, go scan for another line to send


EORLIN  call    SENDEE          ;yes, insert EEPROM data into hex file
        bcf     FLAGS,NOK       ;reset range error flag
        movlw   ':'             ;send colon to start BYTECTR counter
        call    TXBYTE          ;send character
        movlw   00h             ;yes, send EOF record
        call    DISPHEX         ;send as a hex character
        movlw   00h             ;yes, send EOF record
        call    DISPHEX         ;send as a hex character
        movlw   00h             ;yes, send EOF record
        call    DISPHEX         ;send as a hex character
        movlw   01h             ;send record type
        call    DISPHEX         ;send as a hex character
        movlw   0FFh            ;send checksum
        call    DISPHEX         ;send as a hex character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;SENDEE         This routine adds all of EEPROM to the hex file.

SENDEE  clrf    EADDR           ;clear address pointer
        movlw   42h             ;load high hex file address byte ('21x2')
        movwf   EEPTR           ;init pointer (x2)

SNDEEL  movlw   ':'             ;send colon to start line
        call    TXBYTE          ;send character

        movlw   10h             ;load bytes to send in line
        movwf   CHKSUM          ;init the checksum reg
        movwf   EECTR           ;init loop counter
        bcf     STATUS,C        ;clear carry in bit
        rrf     EECTR,F         ;div byte count to word count
        call    DISPHEX         ;send byte count as a hex character

        bcf     STATUS,C        ;clear carry in bit
        rlf     EADDR,W         ;load low hex file address byte
        btfsc   STATUS,C        ;will this line be @ 43xxh (EADDR >= 80h)?
         bsf    EEPTR,0         ;yes, set EEPTR to 43h

        movf    EEPTR,W         ;load high hex file address byte
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        bcf     STATUS,C        ;clear carry in bit
        rlf     EADDR,W         ;load low hex file address byte
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        clrw                    ;send record type (00h, no checksum effect)
        call    DISPHEX         ;send as a hex character

EESLOP  call    EREAD           ;get EEPROM data (low byte)
        movf    EDATA,W         ;load byte that was read out
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        clrw                    ;send high byte (00h, no checksum effect)
        call    DISPHEX         ;send as a hex character

        incf    EADDR,F         ;inc address pointer
        decfsz  EECTR,F         ;done sending line?
         goto   EESLOP          ;no

        comf    CHKSUM,F        ;yes, complement checksum
        incf    CHKSUM,W        ;add 1
        call    DISPHEX         ;send as a hex character
        call    CRLF            ;send a CR/LF

  if     FULLMEM == ON          ;big parts have 256 bytes EEPROM, small 128

        movf    EADDR,W         ;range check
        btfss   STATUS,Z        ;at end of EEPROM (256 bytes)?
         goto   SNDEEL          ;no, get next line
        return                  ;yes, done

  else

        btfss   EADDR,7         ;at end of EEPROM (128 bytes)?
         goto   SNDEEL          ;no, get next line
        return                  ;yes, done

  endif

;*************************************************************************
;SENDLIN        This routine downloads a single hex file line from memory.
;               It uses the shadow regs for source address and BYTECTR for
;               the number of bytes to send.

SENDLIN movf    SAVAH,W         ;get high address of data to send
        movwf   FADDRH          ;store it
        movf    SAVAL,W         ;get low address of data to send
        movwf   FADDRL          ;store it

RELADDR ;Entry point to write records with relocated addresses
        ;FADDRH/L, SAVAH/L and BYTECTR must be set.

        movlw   ':'             ;send colon to start BYTECTR counter
        call    TXBYTE          ;send character

        bcf     STATUS,C        ;clear carry bit for carry in
        rlf     BYTECTR,W       ;turn word count into byte count (x2)
        movwf   CHKSUM          ;init the checksum reg
        call    DISPHEX         ;send as a hex character

        rlf     FADDRL,W        ;get high bit of low byte
        rlf     FADDRH,W        ;get start address high byte times 2
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        bcf     STATUS,C        ;clear carry in bit
        rlf     FADDRL,W        ;get start address low byte times 2
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        clrw                    ;send record type (00h, no checksum effect)
        call    DISPHEX         ;send as a hex character

        ;reload the address here just in case the RELADDR entry point
        ;was used.
        movf    SAVAH,W         ;get high address of data to send
        movwf   FADDRH          ;store it
        movf    SAVAL,W         ;get low address of data to send
        movwf   FADDRL          ;store it

SENDLOP call    FREAD           ;read flash location
        incf    FADDRL,F        ;inc address low byte
        btfsc   STATUS,Z        ;did low byte carry out?
         incf   FADDRH,F        ;yes, inc address high byte

        movf    FDATAL,W        ;get low byte
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        movf    FDATAH,W        ;get high byte
        addwf   CHKSUM,F        ;keep running checksum
        call    DISPHEX         ;send as a hex character

        decfsz  BYTECTR,F       ;done sending line?
         goto   SENDLOP         ;no

        comf    CHKSUM,F        ;yes, complement checksum
        incf    CHKSUM,W        ;add 1
        call    DISPHEX         ;send as a hex character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;WIPE           This routine wipes all of user program and EEPROM memory.

WIPE    movlw   'E'             ;send "Erasing"
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character

        bcf     FLAGS,VALID     ;clear the valid image flag

        ; erase relocated user reset vector first
        ; just in case the erase operation is interrupted
        movlw   high USRVEC     ;load relocated reset vector loc high byte
        movwf   FADDRH          ;store reloc reset vector loc high byte
        movlw   low USRVEC      ;load rev string origin
        movwf   FADDRL          ;store reloc reset vector loc low byte

        movlw   03Fh            ;load all ones (for instruction word)
        movwf   FDATAH          ;store it
        movwf   UDATAH          ;also clear the user reset opcode (high byte)
        movlw   0FFh            ;load low byte data
        movwf   FDATAL          ;store it
        movwf   UDATAL          ;also clear the user reset opcode (low byte)

WIPFVEC call    FWRITE          ;nuke it
        btfsc   FLAGS,NOK       ;erase error?
        return                 ;yes, abort upload

        incf    FADDRL,F        ;no, inc address low byte
        movlw   low VECEND      ;load end of relocated vector loc
        subwf   FADDRL,W        ;compare to current flash pointer
        btfss   STATUS,Z        ;done clearing relocated vector?
         goto   WIPFVEC         ;no


        ; now erase the program area
        bcf     FLAGS,NOK       ;yes, clear error flag
        clrf    FADDRH          ;store code start point high byte (00h)
        movlw   03h             ;load a 3 (code start point low byte)
        movwf   FADDRL          ;store code start point low byte

WIPFLOP call    FWRITE          ;nuke it
        btfsc   FLAGS,NOK       ;erase error?
         return                 ;yes, abort upload

        incf    FADDRL,F        ;no, inc address low byte
        btfss   STATUS,Z        ;did low byte carry out?
         goto   WIPBOND         ;no

        incf    FADDRH,F        ;yes, inc address high byte
        movlw   '.'             ;load ASCII char
        call    TXBYTE          ;send character

WIPBOND call    RANGE           ;check for end of memory
        btfss   FLAGS,NOK       ;hit end of memory?
         goto   WIPFLOP         ;no


        ; finally, erase the rev string
        bcf     FLAGS,NOK       ;yes, clear error flag
        movlw   high REVSTR1    ;load rev string origin, high byte
        movwf   FADDRH          ;store rev string start point high byte
        movlw   low REVSTR1     ;load rev string origin, low byte
        movwf   FADDRL          ;store rev string start point low byte

        movlw   34h             ;load "retlw" instruction code
        movwf   FDATAH          ;store it

        clrf    FDATAL          ;store a null (end of string)

WIPFREV call    FWRITE          ;nuke it
        btfsc   FLAGS,NOK       ;erase error?
         return                 ;yes, abort upload

        incf    FADDRL,F        ;no, inc address low byte
        movlw   low (END_REV)   ;load end of rev string address
        subwf   FADDRL,W        ;compare to current flash pointer
        btfss   STATUS,Z        ;done clearing rev string?
         goto   WIPFREV         ;no


  if     FULLMEM == ON

        movlw   0FFh            ;load all 1's

  else

        movlw   07Fh            ;load all 1's

  endif

        movwf   EADDR           ;load end of EEPROM memory
        movlw   0FFh            ;load all 1's
        movwf   EDATA           ;load blank data value

WIPELOP call    EWRITE          ;clear byte
        btfsc   FLAGS,NOK       ;erase error?
         return                 ;yes, abort upload

        decfsz  EADDR,F         ;no, done clearing EEPROM?
         goto   WIPELOP         ;no

        call    EWRITE          ;almost
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;UPLOAD         This routine uploads a hex file to program memory.

UPLOAD  bcf     FLAGS,NOK       ;clear upload error flag.
        bcf     FLAGS,LOAD      ;clear the upload flag

        clrf    LINCTRO         ;clear line counter 1's
        clrf    LINCTRT         ;clear line counter 10's
        clrf    LINCTRH         ;clear line counter 100's

        movlw   'A'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '?'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '['             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '/'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'N'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ']'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '>'             ;load ASCII char
        call    TXBYTE          ;send character

        call    GETLINE         ;wait for a line of data
        movf    INDF,W          ;get the first char of line
        call    UPCASE          ;force to upper case
        sublw   'Y'             ;compare
        btfss   STATUS,Z        ;did user press "Y/y"?
         return                 ;no, abort upload

        incf    FSR,F           ;yes, look for null
        movf    INDF,W          ;test for zero
        btfss   STATUS,Z        ;was that the null terminator?
         return                 ;no, abort upload

        bsf     FLAGS,LOAD      ;yes, set the upload flag
        call    WIPE            ;nuke all of user memory
        btfsc   FLAGS,NOK       ;erase error?
         return                 ;yes, abort upload

        movlw   'R'             ;no, send "Ready"
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        bsf     FLAGS,PRGFL     ;set the intercept user reset opcode flag
        movlw   01h             ;load 1 into dot counter to get one off the bat
        movwf   DOTCNT           ;into dot counter


LIN_LOP bsf     FLAGS,ECHO      ;set echo = off
        call    GETLINE         ;get a line from the hex file
        bcf     FLAGS,ECHO      ;set echo = on

        incf    LINCTRO,F       ;yes, inc line counter ones byte
        movlw   D'10'           ;load decimal carry out
        subwf   LINCTRO,W       ;compare
        btfss   STATUS,C        ;did ones byte carry out?
         goto   CNT_OVR         ;no

        clrf    LINCTRO         ;yes, clear line counter 1's
        incf    LINCTRT,F       ;inc line counter tens byte
        movlw   D'10'           ;load decimal carry out
        subwf   LINCTRT,W       ;compare
        btfss   STATUS,C        ;did tens byte carry out?
         goto   CNT_OVR         ;no

        clrf    LINCTRT         ;yes, clear line counter 10's
        incf    LINCTRH,F       ;inc line counter hundreds byte


CNT_OVR movf    INDF,W          ;get the first char of line
        sublw   ':'             ;compare to a colon
        btfss   STATUS,Z        ;same?
         goto   BAD_REC         ;no

        incf    FSR,F           ;yes, point at first byte of data
        call    HEXQ            ;read byte count
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        movwf   BYTECTR         ;no, store it
        movwf   CHKSUM          ;initialize line checksum

        bcf     STATUS,C        ;clear carry in
        rrf     BYTECTR,W       ;divide byte counter by 2 (word count)
        movwf   BYTECTR         ;save it
        movwf   WRTCTR          ;save it again (copy)

        call    HEXQ            ;read high byte of 16-bit address
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        movwf   FADDRH          ;no, store it
        addwf   CHKSUM,F        ;add byte to line checksum

        call    HEXQ            ;read low byte of 16-bit address
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        movwf   FADDRL          ;no, store it
        addwf   CHKSUM,F        ;add byte to line checksum

        bcf     STATUS,C        ;clear carry-in bit for address fix-up
        rrf     FADDRH,F        ;div address by 2 byte -> word address
        rrf     FADDRL,F        ;div address by 2 byte -> word address

        call    HEXQ            ;read record type
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        movwf   LINTYPE         ;no, store it
        addwf   CHKSUM,F        ;add byte to line checksum

        movf    LINTYPE,F       ;check for data record (00h)
        btfsc   STATUS,Z        ;is this a data record?
         goto   DAT_ST          ;yes
        sublw   04h             ;check for extended address record (04h)
        btfss   STATUS,Z
         goto   EOF_REC         ;no, check for EOF record type

DAT_ST  movf    FSR,W           ;yes, get pointer to start of data
        movwf   DATAFSR         ;save it


DAT_LOP movf    BYTECTR,F       ;load byte count
        btfsc   STATUS,Z        ;have we read in the whole line?
         goto   EOL_CHK         ;yes, check checksum

        call    HEXQ            ;no, read low byte of data
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        addwf   CHKSUM,F        ;no, add to line checksum
        call    HEXQ            ;read high byte of data
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        addwf   CHKSUM,F        ;no, add to line checksum
        decf    BYTECTR,F       ;decrement bytecount (word counter really)
        goto    DAT_LOP         ;go get next word


EOL_CHK call    HEXQ            ;read in checksum
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        addwf   CHKSUM,F        ;no, add to line checksum
        btfss   STATUS,Z        ;is result 0?
         goto   CHKBAD          ;no, we're done

        ;determine if this line should be skipped
        ; Addr >= 2000h indicates configuration flags- skip them
        ; type = 04h indicates extended address- skip
        movf    FADDRH,W
        sublw   020h            ;high byte of config 'address'
        btfsc   STATUS,Z        ;is the address in the 'config' space?
         goto   WARNSKP         ;yes, skip the line
        movf    LINTYPE,W       ;check the record (line) type
        sublw   04h             ;is it an extended address (04h)?
        btfss   STATUS,Z
         goto   WRT_LN          ;no, continue with the write operation
WARNSKP call    CRLF            ;yes, print message that line is being skipped
        call    WRNSKP
        call    LINNUM          ;report the line number
        goto    NEXTLN          ;go on to the next line

        ;program data
WRT_LN  movf    DATAFSR,W       ;reload queue pointer to head
        movwf   FSR             ;reset pointer to beginning of the line


WRT_LOP call    HEXQ            ;get queued and checksummed low data byte
        movwf   FDATAL          ;load low byte of program word

        call    HEXQ            ;get queued and checksummed high data byte
        movwf   FDATAH          ;load high byte of program word
         
        call    RANGE           ;inhibit self overwrite by user code image
        btfsc   FLAGS,NOK       ;overwrite attempt?
         call   ADRERR          ;yes, send error message
        btfsc   FLAGS,NOK       ;overwrite attempt?
         goto   UPLBAD          ;yes, done

        btfss   FLAGS,DIG       ;no, is this a FLASH address?
         call   FWRITE          ;yes, write a word to program memory

        btfsc   FLAGS,DIG       ;is this an EEPROM address?
         call   EHOOK           ;yes, write a byte to EEPROM memory

        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        incf    FADDRL,F        ;no, increment low byte of address
        btfsc   STATUS,Z        ;carry out?
         incf   FADDRH,F        ;yes, inc high byte

        decfsz  WRTCTR,F        ;decrement bytecount, end of line?
         goto   WRT_LOP         ;no, go get next word

NEXTLN  decfsz  DOTCNT,F        ;yes, should we print a progress dot?
         goto   LIN_LOP         ;no, get next line

        movlw   10h             ;yes, reload 16d
        movwf   DOTCNT          ;into dot counter
        movlw   '.'             ;load ASCII char
        call    TXBYTE          ;send character
        goto    LIN_LOP         ;get next line


EOF_REC call    CRLF            ;send a CR/LF
        decf    LINTYPE,W       ;EOF record type = 01h
        btfsc   STATUS,Z        ;was it a 1?
         goto   EOF_OK          ;yes

BAD_REC bsf     FLAGS,NOK       ;no, set the error flag
        call    RECERR          ;send "Invalid record type" error message
        goto    UPLBAD          ;done


EOF_OK  call    HEXQ            ;read in checksum
        btfsc   FLAGS,NOK       ;was there an error?
         goto   UPLBAD          ;yes, done

        addwf   CHKSUM,F        ;no, add to line checksum
        btfss   STATUS,Z        ;result should be 0, is it?
         goto   CHKBAD          ;no, error

        bcf     FLAGS,PRGFL     ;yes, clear the intercept user reset opcode flag
        movf    UDATAH,W        ;get intercepted write data
        movwf   FDATAH          ;store the data
        movf    UDATAL,W        ;get intercepted write data
        movwf   FDATAL          ;store the data

        ; Store the intercepted user reset opcode in the relocated
        ; user reset vector area
        movlw   high USRVEC     ;load high byte of address to write
        movwf   FADDRH          ;store high byte of address to write
        movlw   low USRVEC      ;load low byte of target
        movwf   FADDRL          ;store low byte of address to write

        call    FWRITE          ;store the opcode at the user vector
        call    USRSCAN         ;set valid image flag if 0000h = any opcode
        btfsc   FLAGS,VALID     ;valid image?
         goto   UPLGOOD         ;yes, done

        bsf     FLAGS,NOK       ;no, set error flag
        call    NOOPCOD         ;send error to tell why
        clrf    LINCTRO         ;clear line counter 1's
        clrf    LINCTRT         ;clear line counter 10's
        clrf    LINCTRH         ;clear line counter 100's
        incf    LINCTRO,F       ;line = 1
        goto    UPLBAD          ;done


CHKBAD  bsf     FLAGS,NOK       ;no, set the error flag
        call    CHKERR          ;send "Checksum error" error message

UPLBAD  call    EATBUF          ;eat the rest of the hex file
        call    LINNUM          ;send offending line number to screen
        return                  ;done

UPLGOOD call    EXITMSG         ;yes, display upload succesful message
        call    GET_REV         ;get user code rev string
        call    PRT_REV         ;echo new code revision string
        return                  ;done

;*************************************************************************
;EHOOK          This routine redirects a FLASH write to EEPROM.

EHOOK   movf    FDATAL,W        ;get low byte to write
        movwf   EDATA           ;redirect to EEPROM

        movf    FADDRL,W        ;get address of byte to write
        movwf   EADDR           ;redirect to EEPROM

        call    EWRITE          ;store EEPROM byte
        return                  ;yes, done

;*************************************************************************
;FWRITE         This routine writes a word to program memory.
;               The data and address must be passed in with the bank 0 regs
;               FDATAH, FDATAL, FADDRH, and FADDRL. This hides all the paging.

; this delays writing the first instruction of the user reset vector
; the remaining bytes (possibly 2) are relocated to the USRVEC area.
; the first instruction will be written after an EOF record is read
;
FWRITE  bcf     FLAGS,DIG       ;clear the bad flash bit
        clrwdt                  ;kick the dog

        ;Use a temporary location (SAVAH/L) for the write address
        ;so that the write address can be relocated if it would
        ;conflict with the reset vector for QwikLoader
        movf    FADDRL,W
        movwf   SAVAL           ;save in case we relocate the write addr
        movf    FADDRH,W
        movwf   SAVAH           ;save in case we relocate the write addr
        btfss   STATUS,Z        ;is this a write to the user code origin?
         goto   WR_LOOP         ;no

        movf    SAVAL,W        ;maybe; check start point low byte
        btfss   STATUS,Z        ;is this a write to reset vector?
         goto   RELLO           ;no, go check for other relocated addresses

        btfss   FLAGS,PRGFL     ;yes, is this word from the hex file?
         goto   WR_LOOP         ;no

        movf    FDATAH,W        ;yes, intercept the write
        movwf   UDATAH          ;save the data for later
        movf    FDATAL,W        ;intercept the write
        movwf   UDATAL          ;save the data for later
        return                  ;done

        ;relocate any code below 0003h
RELLO   movlw   03h
        subwf   SAVAL,W
        btfsc   STATUS,C        ;in relocation area (less than)?
         goto   WR_LOOP         ;no, write the code as specified
        movlw   low USRVEC      ;yes, relocate code to USRVEC area
        addwf   SAVAL,F         ;adjust and save the low address byte
        movlw   high USRVEC
        movwf   SAVAH           ;save the high address byte
        btfsc   STATUS,C        ;over flow?
         incf   SAVAH,F         ;yes, increment high byte of relocation address
        
WR_LOOP movf    SAVAH,W         ;get the high byte of the address
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEADRH          ;load it into control reg
        bcf     STATUS,RP1      ;switch to memory page 0

        movf    SAVAL,W         ;get the low byte of the address
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEADR           ;load it into control reg
        bcf     STATUS,RP1      ;switch to memory page 0

        movf    FDATAH,W        ;get the high byte of the data
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEDATH          ;load it into control reg
        bcf     STATUS,RP1      ;switch to memory page 0

        movf    FDATAL,W        ;get the low byte of the data
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEDATA          ;load it into control reg

        bsf     STATUS,RP0      ;switch to memory page 3
        bsf     EECON1,EEPGD    ;set EEPGD to indicate program memory
        bsf     EECON1,WREN     ;enable writes to memory

        movlw   55h             ;write sequence
        movwf   EECON2          ;first write
        movlw   0AAh            ;second byte of sequence
        movwf   EECON2          ;second write
        bsf     EECON1,WR       ;start write cycle
        nop                     ;wait
        nop                     ;wait
        bcf     EECON1,WREN     ;disable writes
        bsf     EECON1,RD       ;start read cycle
        nop                     ;wait
        nop                     ;wait
        bcf     STATUS,RP0      ;switch to memory page 2
        movf    EEDATH,W        ;get high data byte
        bcf     STATUS,RP1      ;switch to memory page 0
        subwf   FDATAH,W        ;compare to what we wrote
        btfss   STATUS,Z        ;was write succesful?
         goto   BAD_FL          ;no

        bsf     STATUS,RP1      ;yes, switch to memory page 2
        movf    EEDATA,W        ;get low data byte
        bcf     STATUS,RP1      ;switch to memory page 0
        subwf   FDATAL,W        ;compare to what we wrote
        btfsc   STATUS,Z        ;was write succesful?
         goto   WR_EXT          ;yes, done

BAD_FL  btfsc   FLAGS,DIG       ;no, was this the first try?
         goto   DEADFL          ;no

        bsf     FLAGS,DIG       ;yes, set flag for next pass
        goto    WR_LOOP         ;try again

DEADFL  call    FLSHERR         ;send "Flash programming error @ XXXXh\n"
        bsf     FLAGS,NOK       ;set the programming error flag

WR_EXT  return                  ;done

;*************************************************************************
;FREAD          This routine reads a word from program memory.
;               The data must be passed in with the bank 0 regs FADDRH,
;               and FADDRL. Data is returned in FDATAH & FDATAL. This hides
;               all the paging needed.

FREAD   clrwdt                  ;kick the dog
        movf    FADDRH,W        ;get the high byte of the address
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEADRH          ;load it into control reg

        bcf     STATUS,RP1      ;switch to memory page 0
        movf    FADDRL,W        ;get the low byte of the address
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEADR           ;load it into control reg

        bsf     STATUS,RP0      ;switch to memory page 3
        bsf     EECON1,EEPGD    ;set EEPGD to indicate program memory
        bsf     EECON1,RD       ;set RD to start data read
        nop                     ;wait
        nop                     ;wait

        bcf     STATUS,RP0      ;switch to memory page 2
        movf    EEDATH,W        ;get high data byte
        bcf     STATUS,RP1      ;switch to memory page 0
        movwf   FDATAH          ;store it in bank 0 reg

        bsf     STATUS,RP1      ;switch to memory page 2
        movf    EEDATA,W        ;get low data byte
        bcf     STATUS,RP1      ;switch to memory page 0
        movwf   FDATAL          ;store it in bank 0 reg
        return                  ;done

;*************************************************************************
;EWRITE         This routine write a byte to data memory.
;               The data must be passed in with the bank 0 regs EADDR and EDATA.
;               This hides all the paging needed.

EWRITE  bcf     PIR2,EEIF       ;clear write complete flag
        movf    EADDR,W         ;get the address
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEADR           ;load it into control reg

        bcf     STATUS,RP1      ;switch to memory page 0
        movf    EDATA,W         ;get the data
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEDATA          ;load it into control reg

        bsf     STATUS,RP0      ;switch to memory page 3
        bcf     EECON1,EEPGD    ;set EEPGD to indicate data memory
        bsf     EECON1,WREN     ;set WREN to allow data write

        movlw   55h             ;write sequence
        movwf   EECON2          ;first write
        movlw   0AAh            ;second byte of sequence
        movwf   EECON2          ;second write
        bsf     EECON1,WR       ;start write cycle

        bcf     STATUS,RP0      ;switch to memory page 2
        bcf     STATUS,RP1      ;switch to memory page 0

EEW_LOP clrwdt                  ;kick the dog
        btfss   PIR2,EEIF       ;write completed?
         goto   EEW_LOP         ;no

        bsf     STATUS,RP1      ;yes, switch to memory page 2
        bsf     STATUS,RP0      ;switch to memory page 3
        bcf     EECON1,WREN     ;disable writes
        bsf     EECON1,RD       ;start read cycle

        bcf     STATUS,RP0      ;switch to memory page 2
        movf    EEDATA,W        ;get data byte
        bcf     STATUS,RP1      ;switch to memory page 0
        subwf   EDATA,W         ;compare to what we wrote
        btfsc   STATUS,Z        ;same?
         return                 ;yes, done

        bsf     FLAGS,NOK       ;no, set the error flag
        call    EEERR           ;display error message
        return                  ;done

;*************************************************************************
;EREAD          This routine reads a byte from data memory.
;               The data must be passed in with the bank zero reg EADDR.
;               Data is returned in EDATA. This hides all the paging needed.

EREAD   clrwdt                  ;kick the dog
        movf    EADDR,W         ;get the address
        bsf     STATUS,RP1      ;switch to memory page 2
        movwf   EEADR           ;load it into control reg

        bsf     STATUS,RP0      ;switch to memory page 3
        bcf     EECON1,EEPGD    ;set EEPGD to indicate data memory
        bsf     EECON1,RD       ;set RD to start data read

        bcf     STATUS,RP0      ;switch to memory page 2
        movf    EEDATA,W        ;get data byte
        bcf     STATUS,RP1      ;switch to memory page 0
        movwf   EDATA           ;store it in bank 0 reg
        return                  ;done

;*************************************************************************
;RANGE          This routine range checks for valid a EEPROM or FLASH address.
;               It uses the same regs as the FWRITE/FREAD routines.
;               DIG flag set = EEPROM address.

RANGE   bcf     FLAGS,DIG       ;clear DIG flag (not an EEPROM write)

  if     FULLMEM == ON          ;load our memory page to protect it

NOT_PZ  movlw   18h             ;no, load last page address

  else

NOT_PZ  movlw   08h             ;no, load last page address

  endif

        subwf   FADDRH,W        ;compare to high byte of address
        btfss   STATUS,C        ;write target in user code space (less than)?
         return                 ;yes, valid write address


        movf    FADDRH,W        ;no, load high byte of address
        sublw   21h             ;compare to EEPROM address range
        btfss   STATUS,Z        ;is target in EEPROM?
         goto   OUT_BND         ;no

  if     FULLMEM == OFF         ;check for smaller EEPROM

        btfsc   FADDRL,7        ;maybe, test for address > 80h on small part
         goto   OUT_BND         ;no

  endif

        bsf     FLAGS,DIG       ;yes, valid EEPROM address
        return                  ;done

OUT_BND bsf     FLAGS,NOK       ;no, set the error flag for out of bounds
        return                  ;done

;*************************************************************************
;PROMPT           This routine sends a command prompt to the screen.

PROMPT  movlw   'P'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'I'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'C'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '>'             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;NOOPCOD         This routine sends "Opcode missing at 0000h".

NOOPCOD 
        movlw   'O'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '0'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character
        movlw   'h'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;CRLF           This routine sends a CR-LF pair to the screen.

CRLF    movlw   LFEED           ;line feed
        call    TXBYTE          ;send character
        movlw   CRETURN         ;carriage return
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;DASH           This routine sends " - " to the screen.

DASH    movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '-'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;EEDISP         This routine displays the content of EADDR.

EEDISP  movf    EADDR,W         ;get EE address
        call    DISPHEX         ;display it as a hex char

        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '='             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character

        movf    EDATA,W         ;get EE address
        call    DISPHEX         ;display it as a hex char
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;DISPHEX        This routine displays a byte from W in ASCII.

DISPHEX movwf   HOLD            ;save W to holding reg
        andlw   0F0h            ;strip low nibble
        movwf   JUNK            ;move it into temp
        swapf   JUNK,W          ;swap nibbles
        call    ASCII           ;asciify digit
        call    TXBYTE          ;send character

        movf    HOLD,W          ;get LSN of data byte
        andlw   0Fh             ;strip high nibble
        call    ASCII           ;asciify digit
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;ADRERR        This routine sends the "Memory protection error @ XXXXh\n".

ADRERR  movlw   'M'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        call    ERRSTR          ;send " error "
        call    LPRADR          ;send the current flash address pointer
        return                  ;done

;*************************************************************************
;PRGMSTR        This routine sends the " programming" string.

PRGMSTR movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;FLSHERR        This routine displays "FLASH programming error @ XXXXh\n".

FLSHERR movlw   'F'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'L'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'A'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'S'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'H'             ;load ASCII char
        call    TXBYTE          ;send character
        call    PRGMSTR         ;send "programming"
        call    ERRSTR          ;send " error "
        call    LPRADR          ;send the current flash address pointer
        return                  ;done

;*************************************************************************
;EEERR          This routine displays "EERPOM programming error @ XX\n" message.

EEERR   movlw   'E'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'P'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'R'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'O'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'M'             ;load ASCII char
        call    TXBYTE          ;send character
        call    PRGMSTR         ;send "programming"
        call    ERRSTR          ;send " error "
        movlw   '@'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movf    EADDR,W         ;load EE ptr
        call    DISPHEX         ;send as a hex character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;CMDERR         This routine sends the "Invalid command\n" error message.

CMDERR  call    INVLDS          ;send "Invalid "
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;HEXERR          This routine sends the "Non-HEX char error\n" message.

HEXERR  movlw   'N'             ;send "Non-HEX char"
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '-'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'H'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'E'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'X'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'h'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        call    ERRSTR          ;send " error "
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;INVLDS         This routine sends "Invalid ".

INVLDS  movlw   'I'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'v'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;RECERR         This routine sends the "Invalid record type error\n" message.

RECERR  call    INVLDS          ;send "Invalid "
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        call    ERRSTR          ;send " error "
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;CHKERR         This routine sends the "Checksum error\n" error message.

CHKERR  movlw   'C'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'h'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'k'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        call    ERRSTR          ;send " error "
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;ERRSTR         This routine sends " error ".

ERRSTR  movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;LPRADR         This routine prints "@ XXXXh\n", the flash address pointer.

LPRADR  movlw   '@'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movf    FADDRH,W        ;get MSB of the address
        call    DISPHEX         ;display it as a hex
        movf    FADDRL,W        ;get LSB of the address
        call    DISPHEX         ;display it as a hex
        movlw   'h'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;LINNUM         This displays the current hex file line number.

LINNUM  movlw   'L'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'b'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character

        bcf     FLAGS,DIG       ;clear leading sig digit bit
        movf    LINCTRH,W       ;get hundreds digit
        btfsc   STATUS,Z        ;is it a zero (strip leading zero)?
         goto   LIN_TEN         ;yes, skip this digit

        bsf     FLAGS,DIG       ;no, set leading sig digit bit
        call    ASCII           ;asciify digit
        call    TXBYTE          ;send character

LIN_TEN movf    LINCTRT,W       ;get tens digit
        btfsc   FLAGS,DIG       ;is there a leading sig digit?
         goto   NS_TEN          ;yes, inhibit digit skip

        btfsc   STATUS,Z        ;no, is this digit a zero also?
         goto   LIN_ONE         ;yes, skip this digit

NS_TEN  call    ASCII           ;no, asciify digit
        call    TXBYTE          ;send character

LIN_ONE movf    LINCTRO,W       ;get ones digit
        call    ASCII           ;asciify digit
        call    TXBYTE          ;send character

        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;REV_MSG        This routine prompts for a user code rev string.

REV_MSG movlw   'E'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        call    REVPMPT         ;send "rev string"
        movlw   '>'             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;EXITMSG        This routine displays "Upload succesful\n" message.

EXITMSG movlw   'U'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'f'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;CMDLIST        This routine sends a list of valid commands to the screen.
;
; ? - Display this list
; Q - Quit and jump to user code
; U - Upload user code to program memory
; D - Download user code from program memory
; W - Write to EEPROM, "W DD @ AA"
; R - Read from EEPROM, "R AA"
; V - Display user code rev string

CMDLIST movlw   '?'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'D'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'h'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF

        movlw   'Q'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'Q'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'j'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        call    CRLF            ;send a CR/LF

        movlw   'U'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'U'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        call    PRGMEM          ;" program memory\n"

        movlw   'D'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'D'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'w'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        movlw   'f'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        call    PRGMEM          ;" program memory\n"

        movlw   'W'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'W'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'E'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'P'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'R'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'O'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'M'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ','             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '"'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'W'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'D'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '@'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'A'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   '"'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF

        movlw   'R'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'R'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'f'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'E'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   'P'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'R'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'O'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'M'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ','             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '"'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'R'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'A'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   '"'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF

        movlw   'V'             ;load ASCII char
        call    TXBYTE          ;send character
        call    DASH            ;send " - "
        movlw   'D'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        call    REVPMPT          ;send "rev string"
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;REVPMPT       This routine displays "rev string" prompt.

REVPMPT movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'v'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;PRGMEM         This routine displays " program memory\n".

PRGMEM  movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'y'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;HELPIST         This routine displays "Press ? for help\n".

HELPIST call    PRESS           ;send "Press "
        movlw   '?'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'f'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'h'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;PRESS          This routine displays "Press ".

PRESS   movlw   'P'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        call    TXBYTE          ;send character (double)
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;TMRMSG         This routine displays "Jumping to user code\n".

TMRMSG  movlw   'J'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'p'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;USERST         This routine displays " user code ".

USERST  movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   's'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'c'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;CIMAGE          This routine displays "Current image = ".

CIMAGE  movlw   'C'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'u'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character (double)
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'n'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   't'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'm'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'g'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '='             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        return                  ;done

;*************************************************************************
;SPLASH         This routine displays the startup splash screen.
;               Terminal reset code + "xK QwikLoader vX.Y \n"

SPLASH  movlw   ESCAPE          ;used to start an escape sequence
        call    TXBYTE          ;send character
        movlw   '['             ;second character of an escape sequence
        call    TXBYTE          ;send character
        movlw   '2'             ;ESC[2J = clear screen
        call    TXBYTE          ;send character
        movlw   'J'             ;ESC[2J = clear screen
        call    TXBYTE          ;send character

        call    CRLF            ;send a CR/LF

  if     FULLMEM == ON          ;display processor FLASH memory size

        movlw   '8'             ;# Kbytes of total memory

  else

        movlw   '4'             ;# Kbytes of total memory

  endif

        call    TXBYTE          ;send character
        movlw   'K'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'Q'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'w'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'i'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'k'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'L'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'r'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   ' '             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'v'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '1'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '.'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   '1'             ;load ASCII char
        call    TXBYTE          ;send character
        call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;PRT_REV        This routine displays (prints) the revision string
;               of the user code.

PRT_REV btfss   FLAGS,VALID     ;is there a user image?
         goto   NO_CODE         ;no, part is blank

        call    REVSTR1         ;yes, get first byte in code rev string
        andlw   0FFh            ;mask all bits in returned byte
        btfsc   STATUS,Z        ;was a null(0) returned?
         goto   NO_CSTR         ;yes

        call    CIMAGE          ;no, print "Current image = "
        clrf    TEMP            ;init table ptr

UCTBL   movf    TEMP,W          ;load table ptr
        call    REVSTR          ;get char from user rev string
        andlw   0FFh            ;mask all bits in returned byte
        btfsc   STATUS,Z        ;was a null(0) returned?
         goto   CSTREND         ;yes, done

        call    TXBYTE          ;no, send character
        incf    TEMP,F          ;inc table ptr
        goto    UCTBL           ;get next char


NO_CODE movlw   'N'             ;send "No user code loaded"
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        movlw   'l'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'a'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'e'             ;load ASCII char
        call    TXBYTE          ;send character
        movlw   'd'             ;load ASCII char
        call    TXBYTE          ;send character
        goto    CSTREND         ;done


NO_CSTR movlw   'N'             ;send "No user code rev string"
        call    TXBYTE          ;send character
        movlw   'o'             ;load ASCII char
        call    TXBYTE          ;send character
        call    USERST          ;send " user code "
        call    REVPMPT         ;send "rev string"

CSTREND call    CRLF            ;send a CR/LF
        return                  ;done

;*************************************************************************
;WRNSKP   This routine warns the user that a line has been skipped.
;         A line will be skipped if it is a 'CONFIG' setting or
;         an extended address (type 04h) record.

WRNSKP  movlw   'S'
        call    TXBYTE
        movlw   'k'
        call    TXBYTE
        movlw   'i'
        call    TXBYTE
        movlw   'p'
        call    TXBYTE
        call    TXBYTE
        movlw   'i'
        call    TXBYTE
        movlw   'n'
        call    TXBYTE
        movlw   'g'
        call    TXBYTE
        movlw   ' '
        call    TXBYTE
        return

;*************************************************************************
;GOTOUSR   goto to the user's reset vector
;               The reset vector (addresses 0h-2h) are relocated
;               below. Before jumping to the reset vector, the
;               PCLATH is set to bank 0 so that a one instruction
;               goto command will work as expected by the user code
;               (PCLATH is cleared by a power on reset).
;
  if     FULLMEM == ON          ;set fixed origin for user reset vector

        org     1FBEh

  else

        org     0FBEh

  endif

GOTOUSR
        movlw   07h             ;bank 0 - just like reset
        movwf   PCLATH          ;setup for jump
USRVEC
        dw      03FFFh          ;all ones = erased
        dw      03FFFh
        dw      03FFFh
VECEND  goto    0003h           ; just in case the user code did not
                                ; start with a goto command.
;*************************************************************************
;REVSTR        This table stores a revision string for the user code.
;
;               Data is indexed by "W", the string is null terminated.

  if     FULLMEM == ON          ;set fixed origin for user code rev string

        org     1FC4h           ;page 1Fh is for all table jumps

  else

        org     0FC4h           ;page 0Fh is for all table jumps

  endif

REVSTR  addwf   PCL,F           ;add to PCL to jump into the table
REVSTR1 retlw   00h             ;First character is often tested for
                                ;null value to determine if a string has
                                ;been entered.
        retlw   00h             ;null char, init string to null
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
        retlw   00h             ;null char
END_REV retlw   00h             ;null char, 32nd char

;*************************************************************************
;ASCII          This routine turns a 0-F value in "W" into an ASCII char.

ASCII   addwf   PCL,F           ;add to PCL to jump into table
        retlw   '0'             ;0 - load ASCII char
        retlw   '1'             ;1
        retlw   '2'             ;2
        retlw   '3'             ;3
        retlw   '4'             ;4
        retlw   '5'             ;5
        retlw   '6'             ;6
        retlw   '7'             ;7
        retlw   '8'             ;8
        retlw   '9'             ;9
        retlw   'A'             ;A
        retlw   'B'             ;B
        retlw   'C'             ;C
        retlw   'D'             ;D
        retlw   'E'             ;E
        retlw   'F'             ;F

;*************************************************************************
;PASTBL          This table holds the 8 character user password.
;
;                This table is at a fixed location at the end of the
;                allocated bootloader space. This fixed location assures
;                that the assembler (or linker) will complain if the
;                bootloader code gets to be too big (because it will
;                over-run this fixed location.

  if     FULLMEM == ON          ;set fixed origin for password string
        org     1FF6h
  else
        org     0FF6h
  endif

PASTBL  addwf   PCL,F           ;add to PCL to jump into table
        DT     "Hello123"       ;default user password string, max 8 chars
        retlw   00h             ;return a null terminator (org xFFFh)

;*************************************************************************

        end                     ;compiler finish

;*************************************************************************
; Revision History:
; $Log: qwikloader.asm,v $
; Revision 1.8  2008-10-21 19:17:35  jerry
;   - changed contact info to gmail
;   - line terminations changed on some files
;
; Revision 1.7  2003/09/22 20:32:12  jerry
;   - skipped message was not printed for type 04 records- fixed.
;
; Revision 1.6  2003/09/22 02:14:36  jerry
; *** empty log message ***
;
; Revision 1.5  2003/09/18 20:20:19  jerry
;   - fixed problem with 3 instruction reset vector
;
; Revision 1.4  2003/09/18 01:55:45  jerry
;   - completed fix to EATBUF routine. Now Timer0 is used for the delay which
;     is presently set for 250ms
;   - output user reset vector as the first hex record on download
;   - calculate BAUD and delays based on FOSC setting, this should make
;     it easier to configure picloader for systems with different clock rates.
;   - move pin definition for CTS nearer to where the memory size, oscillator
;     frequency, and BAUD are set.
;
; Revision 1.3  2003/09/17 15:33:36  jerry
;   - erased user vector was not properly initialized or detected: Fixed
;   - updated comments at the top to better describe the erased value and
;       the (minor) limitation on the first user opcode at 0000h
;   - work-in-progress toward fixing the EATBUF routine. It was timing out
;       too early with a 20MHz XTAL and the uploaded lines were interpreted
;       as command requests. (Of course they were not valid commands, so
;       the effect was the same as if EATBUF had properly discarded all
;       of the remaining lines. It was just that the user would see a long list
;       of syntax errors if the upload error occurred early in a file.)
;
; Revision 1.2  2003/09/17 00:04:32  jerry
;   - Modified to work better PICCLITE and the latest version of WINMPASM
;        (v03.50) from MPLAB IDE (v6.30)
;      - Relocates reset vector
;      - Ignores configuration settings (addresses 20xxh)
;      - Ignores extended address records (type 04h)
;      - First instruction (now at 0000h but relocated) does not have to be
;          a goto opcode. Any opcode except all ones (addwf 0ffh) is valid.
;
;   - Modified for use on MEEPS
;        - 20MHz instead of 4MHz
;        - 19200 baud
;        - CTS on pin E2
;
;   - Changed some labels and used labels in place of magic numbers to
;       make the code clearer and easier to modify (at least for me- jed).
;
;   - Configured for 4k memory to make it easier to test and debug with the
;      Microchip ICD2 debugger
;
;
;*************************************************************************
; Old Revision Log:
;
;               Bugs fixed / changes in rev 1.1
;       -Range check bug that clobbered code with data. 8/16/99
;
;*************************************************************************
;               Bugs fixed / changes in rev 1.2
;
;       -added "PARTY" flag for addressing in mutidrop mode
;       -set xmit pin to RC2 from RC5 for SS compatability 12/01/99
;
;*************************************************************************
;               Bugs fixed / changes in rev 1.3
;
;       -Cleaned up startup sequence. Now the bootloader starts silently
;        and needs to see a 0x5Ah (capital "Z") within 5 seconds of
;        bootup to start. Otherwise it jumps to user image. 1/15/00
;
;*************************************************************************
;               Bugs fixed / changes in rev 1.4
;
;       -Added check for RTS from host (our CTS) asserted for bootloader start.
;       -Switched to HS osc setting. 7/13/00
;
;*************************************************************************
;               Bugs fixed / changes in rev 1.5
;
;       -Removed multidrop, party line, and half duplex code.
;       -Removed alternate entry point USRLDR.
;       -Cruft removal. 8/14/02
;
;*************************************************************************
