;
; bootldr.asm   Flash-resident bootloader for the PIC 16f876/7
;
; Derived from the original Microchip program, boot877.asm
;

;
; This bootloader uses the state of PB1 to determine action. If PB1 is low on reset, the bootloader prepares 
; to download a user file into memory. If PB1 is high on reset, the bootloader tests the value in
; a reserved flash cell, CodeStatus, to see if memory contains a valid code image. If CodeStatus is zero, the 
; code image is assumed valid and control jumps to StartUserCode to run the downloaded image.
;

	list p=16f876, st=OFF, x=OFF, n=0
	errorlevel -302
	#include <p16f876.inc>

	#include "bank.mac"

	__CONFIG _BODEN_OFF & _CP_OFF & _PWRTE_ON & _WDT_OFF & 					_WRT_ENABLE_ON & _XT_OSC & _DEBUG_OFF & _CPD_OFF & 				_LVP_OFF

;-----------------------------------------------------------------------
;Constants
;

;
; I changed the download selector input to PB1 from the original of PB0. I didn't want to tie up the valuable 
; INT* pin for this selection.
;

TEST_INPUT	EQU	1	;Port B Pin 1 input indicates download

;
; The following baud rate constants assume a 4.0 MHz crystal.
;

BAUD_9600	equ	0x19
BAUD_19200	equ	0x0c

BAUD_DEFAULT	equ	BAUD_9600

;-----------------------------------------------------------------------
;Variables in bank0

		CBLOCK	0x20
		AddressH:	1	;flash program memory address high byte
		AddressL:	1	;flash program memory address low byte
		NumWords:	1	;number of words in line of hex file
		Checksum:	1	;byte to hold checksum of incoming data
		Counter:	1	;to count words being saved or programmed
		TestByte:	1	;byte to show reset vector code received
		HexByte:	1	;byte from 2 incoming ascii characters
		DataPointer:1	;pointer to data in buffer
		DataArray:	0x40	;buffer for storing incoming data
		ENDC

;=======================================================================
;Reset vector code

		ORG	0x0000 

ResetVector:
		movlw	high Main
		movwf	PCLATH		;set page bits for page3
 		goto  Main			;go to boot loader
;=======================================================================
;Start of boot code in upper memory traps accidental entry into boot ;code area

;		ORG	0x1e00		;Use last part of page3 for '876/7
;		ORG	0x0e00		;Use last part of page1 for '873/4
;		ORG	0x0600		;Use last part of page0 for '870/1

	ORG	0x1e00			;leave lots of room for loader

StartOfBoot:
	movlw	high TrapError		;trap if user code hits boot code
	movwf	PCLATH			;set correct page
TrapError:
	goto	TrapError			;trap error and wait for reset

;-----------------------------------------------------------------------
;Relocated user reset code to jump to start of user code
;Must be in bank0 before jumping to this routine

StartUserCode:
		clrf	PCLATH		;set correct page for reset 
		nop				;relocated user code replaces this 
		nop				;relocated user code replaces this 
		nop				;relocated user code replaces this 
		nop				;relocated user code replaces this 
		movlw	high TrapError1	;trap if no goto in user reset code
		movwf	PCLATH		;set correct page
TrapError1:
		goto	TrapError1		;trap error and wait for reset

;-----------------------------------------------------------------------
;Program memory location to show whether valid code has been programmed

CodeStatus:
		DA	0x3fff		;0 for valid code, 0x3fff if no code

;-----------------------------------------------------------------------
;Main boot code routine
;Tests to see if a load should occur and if valid user code exists

Main:
		Bank0				;change to bank0 in case of soft 
		btfss	PORTB,TEST_INPUT 	;check pin for boot load		
		goto	Loader		;if low then do bootload
		call	LoadStatusAddr	;load address of CodeStatus word
		call	FlashRead		;read data at CodeStatus location
		Bank2				;change from bank3 to bank2
		movf	EEDATA,F		;set Z flag if data is zero
		Bank0				;change from bank2 to bank0
		btfss	STATUS,Z		;test Z flag
TrapError2:
		goto	TrapError2		;if not zero then is no valid code
		goto	StartUserCode	;if zero then run user code

;-----------------------------------------------------------------------
;Start of routine to load and program new code

Loader:
	Bank0					; entry from vector at end of ROM
	clrf	TestByte			; indicate no reset vector code yet

	call	LoadStatusAddr		; load address of CodeStatus word
	movlw	0x3f				; load data to indicate no program
	movwf	EEDATH
	movlw	0xff				; load data to indicate no program
	movwf	EEDATA
	call	FlashWrite			; write new CodeStatus word

	call	SerialSetup			; set up serial port
	goto	GetCmd			; skip initial prompt

;-----------------------------------------------------------------------
;Get new line of hex file starting with ':'
;Get first 8 bytes after ':' and extract address and number of bytes

GetNewLine:
	call	SendCRLF
	movlw	'>'				; issue prompt
	call	SerialTransmit
GetCmd:
	call	SerialReceive		; get new byte from serial port
	xorlw	0x0a				; linefeed?
	btfsc	STATUS,Z
	goto	GetCmd			; yes, just skip silently
	xorlw	0x0a				; restore char
	call	SerialTransmit		; echo char
	xorlw	':'				; check if ':' received
	btfsc	STATUS,Z
	goto	GetRec			; got :, start processing record
	xorlw	':'				; restore the char
	xorlw	0dh				; not :, is it CR?
	btfsc	STATUS,Z
	goto	GetNewLine			; got CR, just start over
	xorlw	0dh				; restore the char
	xorlw	'G'				; was it GO command?
	btfsc	STATUS,Z
	goto	DoGoCmd			; do the GO

;
; Control reaches this point if the user sent an unknown command.
; Receive and echo all characters until CR.
;

EatLine:
	call	SerialReceive		; get char from serial port
	xorlw	0ah				; line-feed?
	btfsc	STATUS,Z
	goto	EatLine			; yes, ignore it
	xorlw	0ah				; recover char
	xorlw	0dh				; CR?
	btfsc	STATUS,Z
	goto	GetNewLine			; yes, done with this line
	xorlw	0dh				; recover char
	call	SerialTransmit		; echo the char
	goto	EatLine			; loop until hit CR

;
; Control reaches this point after user enters a semicolon, marking the start of a HEX record. Note that 
; the leading semi has already been echoed.
;

GetRec:
	clrf	Checksum		; start with checksum zero
	call	GetHexByte		; get number of program data bytes in line
	andlw	0x1F			; limit number in case of error in file
	movwf	NumWords
	bcf	STATUS,C
	rrf	NumWords,F		; divide by 2 to get number of words
	call	GetHexByte		; get upper half of program start address
	movwf	AddressH

	call	GetHexByte		; get lower half of program start address
	movwf	AddressL

	bcf	STATUS,C
	rrf	AddressH,F		;divide address by 2 to get word address
	rrf	AddressL,F

	call	GetHexByte		;get record type
	xorlw	0x01
	btfsc	STATUS,Z		;check if end of file record (0x01)
	goto	FileDone		;if end of file then all done

	movf	HexByte,W
	xorlw	0x00
	btfss	STATUS,Z		;check if regular line record (0x00)
	goto	LineDone		;if not then ignore line and send '.'

	movlw	0xe0
	addwf	AddressH,W	;check if address >= 0x2000 (was <)
	btfsc	STATUS,C		;which is ID locations and config bits
	goto	LineDone		;if so then ignore line and send '.'

;-----------------------------------------------------------------------
;Get data bytes and checksum from line of hex file

	movlw	DataArray
	movwf	FSR			;set pointer to start of array
	movf	NumWords,W
	movwf	Counter		;set counter to number of words

GetData:
	call	GetHexByte		;get low data byte
	movwf	INDF			;save in array
	incf	FSR,F			;point to high byte

	call	GetHexByte		;get high data byte
	movwf	INDF			;save in array
	incf	FSR,F			;point to next low byte

	decfsz	Counter,F
	goto	GetData

	call	GetHexByte		;get checksum
	movf	Checksum,W		;check if checksum correct
	btfss	STATUS,Z
	goto	ErrorMessage

;
; Get saved data one word at a time to program into flash 
;
	movlw	DataArray
	movwf	FSR			;point to start of array
	movf	NumWords,W
	movwf	Counter		;set counter to half number of bytes
;
; Check if address is in reset code area
;

CheckAddress:
	movf	AddressH,W			;checking for boot location code
	btfss	STATUS,Z			;test if AddressH is zero 
	goto	CheckAddress1		;if not go check if reset code received

	movlw	0xfc	
	addwf	AddressL,W			;add 0xfc (-4) to address
	btfsc	STATUS,C			;no carry means address < 4
	goto	CheckAddress1		;if not go check if reset code received

	bsf	TestByte,0			;show that reset vector code received
	movf	AddressL,W			;relocate addresses 0-3 to new location
	addlw	low (StartUserCode + 1) ;add low address to new location
	Bank2					;change from bank0 to bank2
	movwf	EEADR				;load new low address
	movlw	high (StartUserCode + 1) ;get new location high address
	movwf	EEADRH			;load high address
	goto	LoadData			;go get data byte and program into flash

;-----------------------------------------------------------------------
;Check if reset code has been received
;Check if address is too high and conflicts with boot loader

CheckAddress1:
	btfss	TestByte,0			;check if reset vector code received first
	goto	ErrorMessage		;if not then error

	movlw	high StartOfBoot	 	;get high byte of address
	subwf	AddressH,W
	btfss	STATUS,C			;test if less than boot code address 
	goto	LoadAddress			;yes so continue with write
	btfss	STATUS,Z			;test if equal to boot code address 
	goto	ErrorMessage		;no so error in high byte of address

	movlw	low StartOfBoot		;get low byte of address
	subwf	AddressL,W
	btfsc	STATUS,C			;test if less than boot code address 
	goto	ErrorMessage		;no so error in address

;-----------------------------------------------------------------------
;Load address and data and write data into flash

LoadAddress:
	movf	AddressH,W		;get high address
	Bank2				;change from bank0 to bank2
	movwf	EEADRH		;load high address
	Bank0				;change from bank2 to bank0
	movf	AddressL,W		;get low address
	Bank2				;change from bank0 to bank2
	movwf	EEADR			;load low address

LoadData:
	movf	INDF,W		;get low byte from array
	movwf	EEDATA		;load low byte
	incf	FSR,F			;point to high data byte
	movf	INDF,W		;get high byte from array
	movwf	EEDATH		;load high byte
	incf	FSR,F			;point to next low data byte

	call	FlashWrite		;write data to program memory

	Bank0				;change from bank3 to bank0
	incfsz	AddressL,F	;increment low address byte
	goto	CheckLineDone	;check for rollover
	incf	AddressH,F		;if so then increment high address byte

CheckLineDone:
	decfsz	Counter,F		;check if all words have been programmed
	goto	CheckAddress		;if not then go program next word

;-----------------------------------------------------------------------
;Done programming line of file

LineDone:
	movlw	'.'				; line has been programmed so
	call	SerialTransmit		; transmit progress indicator back
	call	SerialReceive		; eat the trailing CR (hopefully!)
	goto	GetNewLine			; go get next line hex file

;
; Control reaches this point after loading a file. Change the status indicator to show that a legal file has 
; been loaded, then issue the success indicator and return to the top for the next command.
;

FileDone:
File1:
	call	SerialReceive		; need to eat checksum for file record
	xorlw	0x0d				; hit the CR yet?
	btfsc	STATUS,Z
	goto	File2				; got the CR, finish the report
	xorlw	0x0d				; need to echo, restore the char
	call	SerialTransmit		; do the echo
	goto	File1				; loop until CR

File2:
	call	LoadStatusAddr	;load address of CodeStatus word
	clrf	EEDATH		;load data to indicate program exists
	clrf	EEDATA		;load data to indicate program exists
	call	FlashWrite

	call	SendCRLF
	movlw	'O'			; show success
	call	SerialTransmit
	movlw	'K'
	call 	SerialTransmit

	goto	GetNewLine

;
; An error occurred in the file download. Send an error indicator, then return to the top for another command.
;

ErrorMessage:
	call	SendCRLF
	movlw	'!'			; show failure
	call	SerialTransmit
	call 	SerialTransmit
	goto	GetNewLine

;
; Control reaches this point to process the GO command. Check for a valid file (CodeStatus). If OK, jump 
; to user program, otherwise, send an error code. Note that this code is copied from the code at Main that 
; runs on power-up in user mode.
;
; There are two forms of this command:
;
; G <CR>	code status must be good before jump to user code.
; G! <CR>	jumps to user code WITHOUT checking code status 
;
; Note that the G! form forces the CodeStatus word to 0 before jumping to the user code. This means
;  subsequent resets with the bootloader select line high will also jump to user code. There had better be
; something there!
;

DoGoCmd:
	call	SerialReceive		; get next char
	xorlw	0x0d				; check it
	btfss	STATUS,Z
	goto	GoCmd1			; no CR, might be !
	call	SendCRLF			; echo CR and LF
	call	LoadStatusAddr		; load address of CodeStatus word
	call	FlashRead			; read data at CodeStatus location
	Bank2					; change from bank3 to bank2
	movf	EEDATA,F			; set Z flag if data is zero
	Bank0					; change from bank2 to bank0
	btfsc	STATUS,Z			; skip if code status is bad
	goto	StartUserCode		; run the user program

	call	SendCRLF			; make it pretty
	movlw	'N'				; get the failure marker
	call	SerialTransmit		; send it
	movlw	'O'				; get rest of marker
	call	SerialTransmit		; send it
	goto	GetNewLine			; back to the top

GoCmd1:
	xorlw	0x0d				; restore char
	xorlw	'!'				; force char?
	btfss	STATUS,Z
	goto	EatLine			; not !, must be trash...
	movlw	'!'				; need to echo
	call	SerialTransmit		;
	call	SerialReceive		; get next char
	xorlw	0x0d				; better be CR
	btfss	STATUS,Z
	goto	EatLine			; no CR, ignore it
	call	SendCRLF			; echo CR and LF
	call	LoadStatusAddr		;load address of CodeStatus word
	clrf	EEDATH			;load data to indicate program exists
	clrf	EEDATA			;load data to indicate program exists
	call	FlashWrite
	goto	StartUserCode		; run the user program (hopefully)

	

;
; SendCRLF   send a CR/LF sequence
;

SendCRLF:
	movlw	0x0d			; CR
	call	SerialTransmit
	movlw	0x0a			; LF
	call	SerialTransmit
	return


;
; LoadStatusAddr   load address of CodeStatus word to flash addr 
;
; This routine returns in bank2.
;

LoadStatusAddr:
	Bank2				;change from bank0 to bank2
	movlw	high CodeStatus	;load high addr of CodeStatus location
	movwf	EEADRH
	movlw	low CodeStatus	;load low addr of CodeStatus location
	movwf	EEADR
	return

;
; GetHexByte   get two ASCII digits, convert to a byte
;
; This routine reads and echoes two characters from the USART and converts them to a single byte, 
; returned in W. No check is made for legality. The byte is added to the checksum.
; This routine returns in bank0.

GetHexByte:
	call	SerialReceive	;get new byte from serial port
	call	SerialTransmit	;echo the character
	addlw	0xbf			;add -'A' to Ascii high byte
	btfss	STATUS,C		;check if positive
	addlw	0x07			;if not, add 17 ('0' to '9')
	addlw	0x0a			;else add 10 ('A' to 'F') 
	movwf	HexByte		;save nibble
	swapf	HexByte,F		;move nibble to high position

	call	SerialReceive	;get new byte from serial port
	call	SerialTransmit	;echo the character
	addlw	0xbf			;add -'A' to Ascii low byte
	btfss	STATUS,C		;check if positive
	addlw	0x07			;if not, add 17 ('0' to '9')
	addlw	0x0a			;else add 10 ('A' to 'F') 
	iorwf	HexByte,F		;add low nibble to high nibble
	movf	HexByte,W		;put result in W reg
	addwf	Checksum,F		;add to cumulative checksum
	return

;
; SerialSetup   initialize the USART
;
; Configure the USART for transmit and receive at the default baud rate.
;
; Note that this routine returns with the banking registers set for bank0.
;

SerialSetup:
	Bank1
	movlw	BAUD_DEFAULT	;set baud rate 
	movwf	SPBRG
	bsf	TXSTA,BRGH		;baud rate high speed option
	bsf	TXSTA,TXEN		;enable transmission
	Bank0				;change from bank1 to bank0
	bsf	RCSTA,CREN		;enable reception
	bsf	RCSTA,SPEN		;enable serial port
	return

;
; SerialReceive   read a byte from USART, return it in W
;
; Note that this routine returns with the banking registers set for bank0.
;

SerialReceive:
	Bank0				;change from unknown bank to bank0
	btfss	PIR1,RCIF		;check if data received
	goto	$-1			;wait until new data
	movf	RCREG,W		;get received data into W
	return

;
; SerialTransmit   transmit byte in W register from USART
;
; Note that this routine returns with the banking registers set for bank0.
;

SerialTransmit:
	Bank0				;change from unknown bank to bank0
	btfss	PIR1,TXIF		;check that buffer is empty
	goto	$-1
	movwf	TXREG			;transmit byte
	return


;
; FlashWrite   write to a location in the flash program memory.
;
; The calling routine should write the address of interest in EEADRH
; and EEADR, and the data in EEDATH and EEDATA.
;
; Note that this routine returns with the banking registers set for bank3.
;

FlashWrite:
		Bank3			;change from bank2 to bank3
		movlw	0x84		;enable writes to program flash
		movwf	EECON1

		movlw	0x55		;do timed access writes
		movwf	EECON2
		movlw	0xaa
		movwf	EECON2
		bsf	EECON1,WR	;begin writing to flash

		nop			;processor halts here while writing
		nop
		return

;-----------------------------------------------------------------------
;
; FlashRead   read from a location in the flash program memory
;
; Address is in EEADRH and EEADR, data returned in EEDATH and EEDATA
; This routine returns in bank3.
;

FlashRead:
		Bank2			; get it right
		movlw	0x1f		; keep address within range
		andwf	EEADRH,F

		Bank3			; change from bank2 to bank3
		movlw	0x80		; enable reads from program flash
		movwf	EECON1
		bsf	EECON1,RD	; read from flash
		nop			; processor waits while reading
		nop
		return

;
; The following instructions act as vectors to permit other programs access to routines within the monitor.
; Programs should execute a CALL to one of these addresses, rather than a CALL directly to
; the target routine, as the target routine might move in future versions of this program!
;
; Pay close attention to the banking when these routines exit, as they often change the current register bank.
;
; Note that entry to the Loader at $xxff should be via a GOTO, not a CALL.
;

	org	StartOfBoot+0x1f8

	return				; $1ff8
	return				; $1ff9
	return				; $1ffa
	return				; $1ffb
	goto	SerialSetup			; $1ffc
	goto	FlashRead			; $1ffd
	goto	FlashWrite			; $1ffe
	goto	Loader			; $1fff
	
;-----------------------------------------------------------------------

		END
