
;=============================================================================

;                   Bootstrap Loader for Hexadecimal Files
;                    written by G. Goodhue, Signetics Co.

; This program downloading a hexadecimal program file over an asynchronous 
; serial link to a code RAM in an 80C51 system. The downloaded code may then 
; be executed as the main program for the system. This technique may be used 
; in a system that normally connects to a host PC so that the code may come 
; from a disk and thus be easily updated. The system RAM must be wired to the 
; 80C51 system so that it appears as both data and program memory (wire the 
; RAM normally, but use the logical AND of RD and PSEN for the output enable.)

; To use the bootstrap program, an Intel Hex file is sent through the serial 
; port in 8-N-1 format at 9600 baud. The baud rate and format may be altered 
; by making small changes in the serial port setup routine (SerStart). 

; Note that there is no hardware handshaking (e.g. RTS/CTS or XON/XOFF) 
; implemented between the host and the bootstrap system. This was done to keep 
; the protocol between the two systems as simple as possible.

; Since the bootstrap program does not echo the data file, there is no chance 
; of an overrun unless the 80C51 is running very slowly and/or the 
; communication is very fast. An 80C51 running at 11.0592 MHz (the most 
; commonly used frequency in systems with serial communication) will be able 
; to easily keep up with 38.4K baud communication without handshaking.

;=============================================================================

; The download protocol for this program is as follows:

;  - When the bootstrap program starts up, it sends a prompt character ("=") 
;    up the serial link to the host.

;  - The host may then send the hexadecimal program file down the serial link.
;    At any time, the host may send an escape character (1B hex) to abort and 
;    restart the download process from scratch, beginning from the "=" prompt. 
;    This procedure may be used to restart if a download error occurs.

;  - At the end of a hex file download, a colon (":") prompt is returned. If 
;    an error or other suspicious circumstance occurred, a flag value will 
;    also be returned as shown below. The flag is a bit map of possible 
;    conditions and so may represent more than one problem. If an error 
;    occurs, the bootstrap program will refuse to execute the downloaded 
;    program.

;  Exception codes:
;    01 - non-hexadecimal characters found embedded in a data line.
;    02 - bad record type found.
;    04 - incorrect line checksum found.
;    08 - no data found.
;    10 - incremented address overflowed back to zero.
;    20 - RAM data write did not verify correctly.

;  - If a download error occurs, the download may be retried by first sending 
;    an escape character. Until the escape is received, the bootstrap program 
;    will refuse to accept any data and will echo a question mark ("?") for 
;    any character sent.

;  - After a valid file download, the bootstrap program will send a message 
;    containing the file checksum. This is the arithmetic sum of all of the 
;    DATA bytes (not addresses, record types, etc.) in the file, truncated to 
;    16 bits. This checksum appears in parentheses: "(abcd)". Program 
;    execution may then be started by telling the bootstrap program the 
;    correct starting address. The format for this is to send a slash ("/") 
;    followed by the address in ASCII hexadecimal, followed by a carriage 
;    return. Example: "/8A31<CR>"

;  - If the address is accepted, an at sign ("@") is returned before executing 
;    the jump to the downloaded file.

; The bootstrap loader can be configured to re-map interrupt vectors to the 
; downloaded program if jumps to the correct addresses are set up. For
; instance, if the program RAM in the system where this program is to be used 
; starts at 8000 hexadecimal, the re-mapped interrupts may begin at 8003 for 
; external interrupt 0, etc.


;=============================================================================


$Title(Bootstrap Loader for Hexadecimal Files)
$Date(04-13-92)
$MOD51


;=============================================================================
;                                  Definitions
;=============================================================================


LF          EQU     0Ah                ; Line Feed character.
CR          EQU     0Dh                ; Carriage Return character.
ESC         EQU     1Bh                ; Escape character.
StartChar   EQU     ':'                ; Line start character for hex file.
Slash       EQU     '/'                ; Go command character.
Skip        EQU     13                 ; Value for "Skip" state.

Ch          DATA     0Fh               ; Last character received.
State       DATA     10h               ; Identifies the state in process.
DataByte    DATA     11h               ; Last data byte received.
ByteCount   DATA     12h               ; Data byte count from current line.
HighAddr    DATA     13h               ; High and low address bytes from the 
LowAddr     DATA     14h               ;   current data line.
RecType     DATA     15h               ; Line record type for this line.
ChkSum      DATA     16h               ; Calculated checksum received.
HASave      DATA     17h               ; Saves the high and low address bytes 
LASave      DATA     18h               ;   from the last data line.
FilChkHi    DATA     19h               ; File checksum high byte.
FilChkLo    DATA     1Ah               ; File checksum low byte.

Flags       DATA     20h               ; State condition flags.
HexFlag     BIT      Flags.0           ; Hex character found.
EndFlag     BIT      Flags.1           ; End record found.
DoneFlag    BIT      Flags.2           ; Processing done (end record or some
                                       ;   kind of error.

EFlags      DATA     21h               ; Exception flags.
ErrFlag1    BIT      EFlags.0          ;   Non-hex character embedded in data.
ErrFlag2    BIT      EFlags.1          ;   Bad record type.
ErrFlag3    BIT      EFlags.2          ;   Bad line checksum.
ErrFlag4    BIT      EFlags.3          ;   No data found.
ErrFlag5    BIT      EFlags.4          ;   Incremented address overflow.
ErrFlag6    BIT      EFlags.5          ;   Data storage verify error.

DatSkipFlag BIT      Flags.3           ; Any data found should be ignored.


;=============================================================================
;                          Reset and Interrupt Vectors
;=============================================================================


; The following are dummy labels for re-mapped interrupt vectors. The 
; addresses should be changed to match the memory map of the target system.

ExInt0      EQU     8003h              ; Remap address for ext interrupt 0.
T0Int       EQU     800Bh              ; Timer 0 interrupt.
ExInt1      EQU     8013h              ; External interrupt 1.
T1Int       EQU     801Bh              ; Timer 1 interrupt.
SerInt      EQU     8023h              ; Serial port interrupt.


            ORG      0000h
            LJMP     Start             ; Go to the downloader program.


; The following are intended to allow re-mapping the interrupt vectors to the 
; users downloaded program. The jump addresses should be adjusted to reflect 
; the memory mapping used in the actual application.

; Other (or different) interrupt vectors may need to be added if the target 
; processor is not an 80C51.

            ORG      0003h
;            LJMP     ExInt0            ; External interrupt 0.
            RETI

            ORG      000Bh
;            LJMP     T0Int             ; Timer 0 interrupt.
            RETI

            ORG      0013h
;            LJMP     ExInt1            ; External interrupt 1.
            RETI

            ORG      001Bh
;            LJMP     T1Int             ; Timer 1 interrupt.
            RETI

            ORG      0023h
;            LJMP     SerInt            ; Serial port interrupt.
            RETI


;=============================================================================
;                          Reset and Interrupt Vectors
;=============================================================================


Start:      MOV      IE,#0             ; Turn off all interrupts.
            MOV      SP,#5Fh           ; Start stack near top of '51 RAM.
            ACALL    SerStart          ; Setup and start serial port.
            ACALL    CRLF              ; Send a prompt that we are here.
            MOV      A,#'='            ;   "<CRLF> ="
            ACALL    PutChar
            ACALL    HexIn             ; Try to read hex file from serial port.
            ACALL    ErrPrt            ; Send a message for any errors or 
                                       ;   warnings that were noted.
            MOV      A,EFlags          ; We want to get stuck if a fatal 
            JZ       HexOK             ;   error occurred.

ErrLoop:    MOV      A,#'?'            ; Send a prompt to confirm that we 
            ACALL    PutChar           ;   are 'stuck'.  " ? "
            ACALL    GetChar           ; Wait for escape char to flag reload.
            SJMP     ErrLoop

HexOK:      MOV      EFlags,#0         ; Clear errors flag in case we re-try.
            ACALL    GetChar           ; Look for GO command.
            CJNE     A,#Slash,HexOK    ; Ignore other characters received.

            ACALL    GetByte           ; Get the GO high address byte.
            JB       ErrFlag1,HexOK    ; If non-hex char found, try again.
            MOV      HighAddr,DataByte ; Save upper GO address byte.

            ACALL    GetByte           ; Get the GO low address byte.
            JB       ErrFlag1,HexOK    ; If non-hex char found, try again.
            MOV      LowAddr,DataByte  ; Save the lower GO address byte.

            ACALL    GetChar           ; Look for CR.
            CJNE     A,#CR,HexOK       ; Re-try if CR not there.

; All conditions are met, so hope the data file and the GO address are all 
;   correct, because now we're committed.

            MOV      A,#'@'            ; Send confirmation to GO. " @ "
            ACALL    PutChar
            JNB      TI,$              ; Wait for completion before GOing.

            PUSH     LowAddr           ; Put the GO address on the stack, 
            PUSH     HighAddr          ;   so we can Return to it.
            RET                        ; Finally, go execute the user program!


;=============================================================================
;                         Hexadecimal File Input Routine
;=============================================================================

HexIn:      CLR      A                 ; Clear out some variables.
            MOV      State,A
            MOV      Flags,A
            MOV      HighAddr,A
            MOV      LowAddr,A
            MOV      HASave,A
            MOV      LASave,A
            MOV      ChkSum,A
            MOV      FilChkHi,A
            MOV      FilChkLo,A
            MOV      EFlags,A
            SETB     ErrFlag4          ; Start with a 'no data' condition.

StateLoop:  ACALL    GetChar           ; Get a character for processing.
            ACALL    AscHex            ; Convert ASCII-hex character to hex.
            MOV      Ch,A              ; Save result for later.
            ACALL    GoState           ; Go find the next state based on 
                                       ;   this char.
            JNB      DoneFlag,StateLoop ; Repeat until done or terminated.

            ACALL    PutChar           ; Send the file checksum back as 
            MOV      A,#'('            ;   confirmation. " (abcd) "
            ACALL    PutChar
            MOV      A,FilChkHi
            ACALL    PrByte
            MOV      A,FilChkLo
            ACALL    PrByte
            MOV      A,#')'
            ACALL    PutChar
            ACALL    CRLF
            RET                        ; Exit to main program.


; Find and execute the state routine pointed to by "State".

GoState:    MOV      A,State           ; Get current state.
            ANL      A,#0Fh            ; Insure branch is within table range.
            RL       A                 ; Adjust offset for 2 byte insts.
            MOV      DPTR,#StateTable
            JMP      @A+DPTR           ; Go to appropriate state.

StateTable: AJMP     StWait            ;  0 - Wait for start.
            AJMP     StLeft            ;  1 - First nibble of count.
            AJMP     StGetCnt          ;  2 - Get count.
            AJMP     StLeft            ;  3 - First nibble of address byte 1.
            AJMP     StGetAd1          ;  4 - Get address byte 1.
            AJMP     StLeft            ;  5 - First nibble of address byte 2.
            AJMP     StGetAd2          ;  6 - Get address byte 2.
            AJMP     StLeft            ;  7 - First nibble of record type.
            AJMP     StGetRec          ;  8 - Get record type.
            AJMP     StLeft            ;  9 - First nibble of data byte.
            AJMP     StGetDat          ; 10 - Get data byte.
            AJMP     StLeft            ; 11 - First nibble of checksum.
            AJMP     StGetChk          ; 12 - Get checksum.
            AJMP     StSkip            ; 13 - Skip data after error condition.
            AJMP     BadState          ; 14 - Should never get here.
            AJMP     BadState          ; 15 -   "      "    "    "


; This state is used to wait for a line start character. Any other characters 
;   received prior to the line start are simply ignored.

StWait:     MOV      A,Ch              ; Retrieve input character.
            CJNE     A,#StartChar,SWEX ; Check for line start.
            INC      State             ; Received line start.
SWEX:       RET


; Process the first nibble of any hex byte.

StLeft:     MOV      A,Ch              ; Retrieve input character.
            JNB      HexFlag,SLERR     ; Check for hex character.
            ANL      A,#0Fh            ; Isolate one nibble.
            SWAP     A                 ; Move nibble too upper location.
            MOV      DataByte,A        ; Save left/upper nibble.
            INC      State             ; Go to next state.
            RET                        ; Return to state loop.

SLERR:      SETB     ErrFlag1          ; Error - non-hex character found.
            SETB     DoneFlag          ; File considered corrupt. Tell main.
            RET


; Process the second nibble of any hex byte.

StRight:    MOV      A,Ch              ; Retrieve input character.
            JNB      HexFlag,SRERR     ; Check for hex character.
            ANL      A,#0Fh            ; Isolate one nibble.
            ORL      A,DataByte        ; Complete one byte.
            MOV      DataByte,A        ; Save data byte.
            ADD      A,ChkSum          ; Update line checksum,
            MOV      ChkSum,A          ;   and save.
            RET                        ; Return to state loop.

SRERR:      SETB     ErrFlag1          ; Error - non-hex character found.
            SETB     DoneFlag          ; File considered corrupt. Tell main.
            RET


; Get data byte count for line.

StGetCnt:   ACALL    StRight           ; Complete the data count byte.
            MOV      A,DataByte
            MOV      ByteCount,A
            INC      State             ; Go to next state.
            RET                        ; Return to state loop.


; Get upper address byte for line.

StGetAd1:   ACALL    StRight           ; Complete the upper address byte.
            MOV      A,DataByte
            MOV      HighAddr,A        ; Save new high address.
            INC      State             ; Go to next state.
            RET                        ; Return to state loop.


; Get lower address byte for line.

StGetAd2:   ACALL    StRight           ; Complete the lower address byte.
            MOV      A,DataByte
            MOV      LowAddr,A         ; Save new low address.
            INC      State             ; Go to next state.
            RET                        ; Return to state loop.


; Get record type for line.

StGetRec:   ACALL    StRight           ; Complete the record type byte.
            MOV      A,DataByte
            MOV      RecType,A         ; Get record type.
            JZ       SGRDat            ; This is a data record.
            CJNE     A,#1,SGRErr       ; Check for end record.
            SETB     EndFlag           ; This is an end record.
            SETB     DatSkipFlag       ; Ignore data embedded in end record.
            MOV      State,#11         ; Go to checksum for end record.
            SJMP     SGREX

SGRDat:     INC      State             ; Go to next state.
SGREX:      RET                        ; Return to state loop.

SGRErr:     SETB     ErrFlag2          ; Error, bad record type.
            SETB     DoneFlag          ; File considered corrupt. Tell main.
            RET


; Get a data byte.

StGetDat:   ACALL    StRight           ; Complete the data byte.
            JB       DatSkipFlag,SGD1  ; Don't process the data if the skip 
                                       ;   flag is on.
            ACALL    Store             ; Store data byte in memory.

            MOV      A,DataByte        ; Update the file checksum,
            ADD      A,FilChkLo        ;   which is a two-byte summation of 
            MOV      FilChkLo,A        ;   all data bytes.
            CLR      A
            ADDC     A,FilChkHi
            MOV      FilChkHi,A
            MOV      A,DataByte
SGD1:       DJNZ     ByteCount,SGDEX   ; Last data byte?
            INC      State             ; Done with data, go to next state.
            SJMP     SGDEX2

SGDEX:      DEC      State             ; Set up state for next data byte.
SGDEX2:     RET                        ; Return to state loop.


; Get checksum.

StGetChk:   ACALL    StRight           ; Complete the checksum byte.
            JNB      EndFlag,SGC1      ; Check for an end record.
            SETB     DoneFlag          ; If this was an end record, 
            SJMP     SGCEX             ;  we are done.

SGC1:       MOV      A,ChkSum          ; Get calculated checksum.
            JNZ      SGCErr            ; Result should be zero.
            MOV      ChkSum,#0         ; Preset checksum for next line.
            MOV      State,#0          ; Line done, go back to wait state.
            MOV      LASave,LowAddr    ; Save address byte from this line for 
            MOV      HASave,HighAddr   ;   later check.
SGCEX:      RET                        ; Return to state loop.

SGCErr:     SETB     ErrFlag3          ; Line checksum error.
            SETB     DoneFlag          ; File considered corrupt. Tell main.
            RET


; This state used to skip through any additional data sent, ignoring it.

StSkip:     RET                        ; Return to state loop.


; A place to go if an illegal state comes up somehow.

BadState:   MOV      State,#Skip       ; If we get here, something very bad 
            RET                        ;   happened, so return to state loop.


; Store - Save data byte in external RAM at specified address.

Store:      MOV      DPH,HighAddr      ; Set up external RAM address in DPTR.
            MOV      DPL,LowAddr
            MOV      A,DataByte
            MOVX     @DPTR,A           ; Store the data.

            MOVX     A,@DPTR           ; Read back data for integrity check.
            CJNE     A,DataByte,StoreErr ; Is read back OK?

            CLR      ErrFlag4          ; Show that we've found some data.
            INC      DPTR              ; Advance to the next addr in sequence.
            MOV      HighAddr,DPH      ; Save the new address
            MOV      LowAddr,DPL
            CLR      A
            CJNE     A,HighAddr,StoreEx ; Check for address overflow
            CJNE     A,LowAddr,StoreEx ;   (both bytes are 0).
            SETB     ErrFlag5          ; Set warning for address overflow.
StoreEx:    RET

StoreErr:   SETB     ErrFlag6          ; Data storage verify error.
            SETB     DoneFlag          ; File considered corrupt. Tell main.
            RET


;=============================================================================
;                                  Subroutines
;=============================================================================


; Subroutine summary:

; SerStart - Serial port setup and start.
; GetChar  - Get a character from the serial port for processing.
; GetByte  - Get a hex byte from the serial port for processing.
; PutChar  - Output a character to the serial port.
; AscHex   - See if char in ACC is ASCII-hex and if so convert to hex nibble.
; HexAsc   - Convert a hexadecimal nibble to its ASCII character equivalent.
; ErrPrt   - Return any error codes to our host.
; CRLF     - output a carriage return / line feed pair to the serial port.
; PrByte   - Send a byte out the serial port in ASCII hexadecimal format.


; SerStart - Serial port setup and start.

SerStart:   MOV      A,PCON            ; Make sure SMOD is off.
            CLR      ACC.7
            MOV      PCON,A
            MOV      TH1,#0FDh         ; Set up timer 1.
            MOV      TL0,#0FDh
            MOV      TMOD,#20h
            MOV      TCON,#40h
            MOV      SCON,#52h         ; Set up serial port.
            RET


; GetByte - Get a hex byte from the serial port for processing.

GetByte:    ACALL    GetChar           ; Get first character of byte.
            ACALL    AscHex            ; Convert to hex.
            MOV      Ch,A              ; Save result for later.
            ACALL    StLeft            ; Process as top nibble of a hex byte.
            ACALL    GetChar           ; Get second character of byte.
            ACALL    AscHex            ; Convert to hex.
            MOV      Ch,A              ; Save result for later.
            ACALL    StRight           ; Process as bottom nibble of hex byte.
            RET


; GetChar - Get a character from the serial port for processing.

GetChar:    JNB      RI,$              ; Wait for receiver flag.
            CLR      RI                ; Clear receiver flag.
            MOV      A,SBUF            ; Read character.
            CJNE     A,#ESC,GCEX       ; Re-start immediately if Escape char.
            LJMP     Start
GCEX:       RET


; PutChar - Output a character to the serial port.

PutChar:    JNB      TI,$              ; Wait for transmitter flag.
            CLR      TI                ; Clear transmitter flag.
            MOV      SBUF,A            ; Send character.
            RET


; AscHex - See if char in ACC is ASCII-hex and if so convert to a hex nibble.
;   Returns nibble in A, HexFlag tells if char was really hex. The ACC is not 
;   altered if the character is not ASCII hex. Upper and lower case letters
;   are recognized.

AscHex:     CJNE     A,#'0',AH1        ; Test for ASCII numbers.
AH1:        JC       AHBad             ; Is character is less than a '0'?
            CJNE     A,#'9'+1,AH2      ; Test value range.
AH2:        JC       AHVal09           ; Is character is between '0' and '9'?

            CJNE     A,#'A',AH3        ; Test for upper case hex letters.
AH3:        JC       AHBad             ; Is character is less than an 'A'?
            CJNE     A,#'F'+1,AH4      ; Test value range.
AH4:        JC       AHValAF           ; Is character is between 'A' and 'F'?

            CJNE     A,#'a',AH5        ; Test for lower case hex letters.
AH5:        JC       AHBad             ; Is character is less than an 'a'?
            CJNE     A,#'f'+1,AH6      ; Test value range.
AH6:        JNC      AHBad             ; Is character is between 'a' and 'f'?
            CLR      C
            SUBB     A,#27h            ; Pre-adjust character to get a value.
            SJMP     AHVal09           ; Now treat as a number.

AHBad:      CLR      HexFlag           ; Flag char as non-hex, don't alter.
            SJMP     AHEX              ; Exit
AHValAF:    CLR      C
            SUBB     A,#7              ; Pre-adjust character to get a value.
AHVal09:    CLR      C
            SUBB     A,#'0'            ; Adjust character to get a value.
            SETB     HexFlag           ; Flag character as 'good' hex.
AHEX:       RET

                      
; HexAsc - Convert a hexadecimal nibble to its ASCII character equivalent.

HexAsc:     ANL      A,#0Fh            ; Make sure we're working with only 
                                       ;   one nibble.
            CJNE      A,#0Ah,HA1       ; Test value range.
HA1:        JC        HAVal09          ; Value is 0 to 9.
            ADD       A,#7             ; Value is A to F, extra adjustment.
HAVal09:    ADD       A,#'0'           ; Adjust value to ASCII hex.
            RET


; ErrPrt - Return an error code to our host.

ErrPrt:     MOV       A,#':'           ; First, send a prompt that we are
            CALL      PutChar          ;   still here.
            MOV       A,EFlags         ; Next, print the error flag value if 
            JZ        ErrPrtEx         ;   it is not 0.
            CALL      PrByte
ErrPrtEx:   RET


; CRLF - output a carriage return / line feed pair to the serial port.

CRLF:       MOV       A,#CR
            CALL      PutChar
            MOV       A,#LF
            CALL      PutChar
            RET


; PrByte - Send a byte out the serial port in ASCII hexadecimal format.

PrByte:     PUSH      ACC              ; Print ACC contents as ASCII hex.
            SWAP      A
            CALL      HexAsc           ; Print upper nibble.
            CALL      PutChar
            POP       ACC
            CALL      HexAsc           ; Print lower nibble.
            CALL      PutChar
            RET


;=============================================================================

            END

