;******************************************************************************
;   Bootloader for Microchip PIC16F870/871/873/874/876/877                    *
;                     (c) 2000-2002, Petr Kolomaznik                          *
;                          Freely distributable                               *
;******************************************************************************
;                                                                             *
; Name of file:      bootldr.asm                                              *
; Date:              29th Dec 2002                                            *
; Version:           2.6                                                      *
;                                                                             *
; Author:            Petr Kolomaznik                                          *
; Company:           EHL elektronika, Czech Republic                          *
; Email:             kolomaznik@ehl.cz                                        *
; Url:               www.ehl.cz/pic                                           *
; Modified by:       Shane Tolmie (v2.30)                                     *
; Company:           DesignREM                                                *
; Email:             shane@microchipc.com                                     *
; Url:               www.microchipc.com                                       *
;                                                                             *
;******************************************************************************
; How to use this bootloader:                                                 *
; - open project bootldr.pjt in MPLAB                                         *
; - check and modify of parameters in the user setting section with <<< mark  *
; - make project with MPLAB and MPASM                                         *
; - use any programmer for programming of bootldr.hex to microcontroller      *
; - set configuration bits                                                    *
; - use PIC downloader program for user program download                      *
; - check if user program doesn't use the top 214 program memory locations    *
;                                                                             *
; Notes:                                                                      *
; - tab size for editor is 2                                                  *
; - bootloader is compatible with HI-TECH's and Shane Tolmie's bootl./downl.  *
;******************************************************************************
; 2.60 - 29.12.2002 (Shane Tolmie [shane@microchipc.com])                     *
;  - made it so that frequencies <=4Mhz have XT, and freq >4Mhz have HS       *
;    configuration in the fuse bits, to enable it to work at all freq with    *
;    resonators.                                                              *
; 2.50 - 28.8.2002 (Shane Tolmie [shane@microchipc.com])                      *
;  - switched off BODEN which resets part if low voltage power supply used    *
;    (many grateful thanks to Richard (joshanks@earthlink.net)                *
; 2.50 - 28.8.2002 (Shane Tolmie [shane@microchipc.com])                      *
;  - switched off BODEN which resets part if low voltage power supply used    *
;    (many grateful thanks to Richard (joshanks@earthlink.net)                *
; 2.20 - 8.8.2001 (Shane Tolmie [shane@microchipc.com])                       *
;  - added watchdog user change, defaults to off (leaving it on can create    *
;    difficult to track down problems)                                        *
; 2.10 - 3.8.2001 (jvo@vinylium.ch)                                           *
;  - substantially reduced size of program                                    *
;  - restores USART to reset condition before starting user program           *
;  - made user program start variable                                         *
;  - added trap to avoid unintended running into bootloader                   *
;  - watchdog timeout is directly handled over to user program                *
; 2.00 - 30.03.2001 (Shane Tolmie [shane@microchipc.com])                     *
;  - for the 16F876, the 4 instructions in the original .hex file at 0x0000   *
;    to 0x0003 are copied to 0x1F00 to 0x1F03, and executed once the          *
;    bootloader is finished, to jump back to user code. The reset vector in   *
;    the chip at 0x0000 to 0x0003 points to the bootloader.  Inserted a       *
;    pagesel 0x0000 (2 instructions) at 0x1F00 to make a short jump into a    *
;    long jump so it would work with .hex files that didnt have a long jump   *
;    as the first 4 instructions in the .hex file.                            *
;    This addition dramatically increases compatibility with .hex files.      *
; 1.06 - 30.03.2001 (Shane Tolmie)                                            *
;  - added config bits                                                        *
;  - program now jumps immediately to user program after download             *
; 1.02 - 15.11.2000                                                           *
;  - added check of user constants                                            *
;  - added support for the new 16F870/1/2                                     *
;  - added errorlevel directive for message 302 and 306                       *
;******************************************************************************

	errorlevel -302, -306			; no message 302 and 306
	list       b=2						; tabulator size = 2

;================== User setting section ======================================

	;list p=16f871							; <<< set type of microcontroller
													  ;     set same microcontroller in the project
	#define ICD_DEBUG 0				; <<< if using MPLAB ICD Debugger, moves bootloader down 256 bytes to make room for it [0|1]													  
	#define FOSC D'18432000' 	; <<< set quartz frequence [Hz], max. 20 MHz
	#define BAUD D'19200'			; <<< set baud rate [bit/sec]
	#define	BAUD_ERROR	D'0'	;	<<< set baud rate error [%]
	#define TIME							; <<< set method of bootloader start PIN/TIME
														;     PIN	: start on low level of trigger pin
                            ;     TIME: start on receive IDENT byte in TIMEOUT
  #define	TRIGGER		PORTB,7 ; <<< only for PIN - set PORT_X,PIN_NR
	#define	TIMEOUT		D'2'		; <<< only for TIME - set time [0.1s], max. 25 sec
	#define WATCHDOGTIMER 0		; <<< Watchdog timer default OFF/ON [0|1]

;=================== Configuration ============================================

	__IDLOCS H'2100'					; version ID of bootloader

  IF WATCHDOGTIMER == 0
    #define MY_WDT _WDT_OFF
  ELSE
    #define MY_WDT _WDT_ON
  ENDIF

  ;note: for high voltage parts, you can set BODEN_ON, but for low voltage parts it resets the circuit continuously!
  IF FOSC<=D'4000000'
    #define _MYCRYSTAL _XT_OSC ;see datasheet
  ELSE
    #define _MYCRYSTAL _HS_OSC  
  ENDIF
  __CONFIG _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _MYCRYSTAL & _WRT_ENABLE_ON & _LVP_ON & _DEBUG_OFF & _CPD_OFF 

;=============== End of user setting section ==================================


;================== Check User Constants ======================================

  IFNDEF FOSC
    ERROR "FOSC must be defined"
  ENDIF

  IFNDEF BAUD
    ERROR "BAUD must be defined"
  ENDIF

	IF FOSC > D'20000000'
		ERROR "max. quartz frequency = 20 MHz"
	ENDIF

	IFNDEF PIN
		IFNDEF TIME
			ERROR "wrong start method of bootloader, must be PIN or TIME"
		ENDIF
	ENDIF

	IF TIMEOUT > D'254'
		ERROR "max. timeout = 25.4 sec"
	ENDIF

	IF ICD_DEBUG != 0
		IF ICD_DEBUG != 1
			ERROR "ICD debug must be 1 (enabled) or 0 (not used)"
		ENDIF
	ENDIF

;========================== Constants =========================================

	IF ((FOSC/(D'16' * BAUD))-1) < D'256'
		#define DIVIDER (FOSC/(D'16' * BAUD))-1
		#define HIGH_SPEED 1
	ELSE
		#define DIVIDER (FOSC/(D'64' * BAUD))-1
		#define HIGH_SPEED 0
	ENDIF

BAUD_REAL	EQU	FOSC/((D'64'-(HIGH_SPEED*D'48'))*(DIVIDER+1))

	IF BAUD_REAL > BAUD
		IF (((BAUD_REAL - BAUD)*D'100')/BAUD) > BAUD_ERROR
			ERROR	"wrong baud rate"
		ENDIF
	ELSE
		IF (((BAUD - BAUD_REAL)*D'100')/BAUD) > BAUD_ERROR
			ERROR	"wrong baud rate"
		ENDIF
	ENDIF

	IF FOSC > D'10240000'
		#define	T1PS 8
		#define	T1SU 0x31
	ELSE
		IF FOSC > D'5120000'
			#define T1PS 4
			#define T1SU 0x21
		ELSE
			IF FOSC > D'2560000'
				#define T1PS 2
				#define T1SU 0x11
			ELSE
				#define T1PS 1
				#define T1SU 0x01
			ENDIF
		ENDIF
	ENDIF

TIMER	EQU	(D'65538'-(FOSC/(D'10'*4*T1PS))); reload value for TIMER1 (0.1s int)

  IFDEF __16F877
	  #include <p16f877.inc>
	  #define ProgHI 0x1FFF
  ELSE
    IFDEF __16F876
	    #include <p16f876.inc>
      #define ProgHI 0x1FFF
    ELSE
      IFDEF __16F874
	      #include <p16f874.inc>
        #define ProgHI 0x0FFF
      ELSE
        IFDEF __16F873
	        #include <p16f873.inc>
          #define ProgHI 0x0FFF
        ELSE
          IFDEF __16F872
	          #include <p16f872.inc>
            #define ProgHI 0x07FF
          ELSE
            IFDEF __16F871
	            #include <p16f871.inc>
              #define ProgHI 0x07FF
            ELSE
              IFDEF __16F870
	              #include <p16f870.inc>
                #define ProgHI 0x07FF
              ELSE
                ERROR   "incorrect type of microcontroller"
              ENDIF
            ENDIF
          ENDIF
        ENDIF
      ENDIF
    ENDIF
  ENDIF

#define LoaderSize	0xD6			  						; size of bootloader
#define LoaderMain	UserStart+5							; main address of bootloader
#define LoaderTop 	ProgHI-(ICD_DEBUG*0x100); top address of bootloader
#define LoaderStart	(LoaderTop)-LoaderSize+1; start address of bootloader

#define	NumRetries	1												; number of writing retries


#define WRITE       0xE3										; communication protocol
#define WR_OK       0xE4
#define WR_BAD      0xE5

#define DATA_OK     0xE7
#define DATA_BAD    0xE8

#define IDENT       0xEA
#define IDACK       0xEB

#define DONE        0xED

;=============== Variables ====================================================

buff			EQU	0x20
; RAM address 0x70 reserved for MPLAB-ICD
amount		EQU	0x71
chk1			EQU	0x72
chk2			EQU	0x73
retry			EQU	0x74
address		EQU 0x75
tmpaddr		EQU 0x77
temp			EQU	0x79
time			EQU	0x7A
count			EQU 0x7B

;------------------------------------------------------------------------------
		ORG     0x0000                ; reset vector of microcontroller
	nop															; for compatibility with ICD
	pagesel Main
  goto    Main

;------------------------------------------------------------------------------
		ORG			LoaderStart
TrapError
	pagesel TrapError
	goto		TrapError								; trap for unintended running into Bootloader

UserStart
	; this instruction never gets overwritten
	clrf	PCLATH										; set PCLATH to program page 0

	; the following 4 instructions get overwritten by user program
	pagesel UserStart								; set PCLATH to program page of UserStart
	goto		UserStart								; loop for first start without a user program

;------------------------------------------------------------------------------
		ORG     LoaderMain
Main
	btfss STATUS,NOT_TO							; run user program after WatchDog-TimeOut
		goto UserStart
start
	movlw	0x90											; SPEN = 1, CREN = 1
	movwf	RCSTA
	bsf	STATUS,RP0									; bank1
	IF HIGH_SPEED == 1							; USART SYNC=0; SPEN=1; CREN=1; SREN=0;
		bsf	TXSTA,BRGH                ;       TX9=0; RX9=0; TXEN=1;
	ELSE
		bcf TXSTA,BRGH
	ENDIF
	bsf		TXSTA,TXEN
	movlw	DIVIDER										; baud rate generator
	movwf	SPBRG
	IFDEF PIN
		bsf	TRIGGER										; for PIN:  setting of TRIS for selected pin
		bcf	STATUS,RP0
		btfss	TRIGGER
		 goto receive									; go to bootloader
		goto	user_restore						; run user program
	ELSE
		bcf	STATUS,RP0
		movlw	TIMEOUT+1								; for TIME: set timeout
		movwf	time
		movlw	T1SU
		movwf	T1CON										; TIMER1 on, internal clock, prescale T1PS
		bsf   PIR1,TMR1IF
		call	getbyte									; wait for IDENT
		xorlw	IDENT
		btfss	STATUS,Z
		 goto	user_restore
		clrf	time										; no more wait for IDENT
		goto	inst_ident							; bootloader identified, send of IDACK
	ENDIF

;------------------------------------------------------------------------------
receive														; programming
	call	getbyte										; get byte from USART
	movwf	temp
	xorlw	WRITE
	btfsc	STATUS,Z
	 goto	inst_write								; write instruction
	movf	temp,w
	xorlw	IDENT
	btfsc	STATUS,Z
	 goto	inst_ident								; identification instruction
	movf	temp,w
	xorlw	DONE
	btfss	STATUS,Z									; done instruction ?
	 goto	receive

;------------------------------------------------------------------------------
inst_done													; very end of programming
;------------------------------------------------------------------------------
	movlw	WR_OK
  call	putbyte										; send of byte
	movlw	TIMEOUT+1
	movwf	time
  call	getbyte                   ; has built in timeout - waits until done
;------------------------------------------------------------------------------
user_restore
	clrf  T1CON											; shuts off TIMER1
	clrf  RCSTA
	bsf   STATUS,RP0
	clrf  TXSTA											; restores USART to reset condition
	bcf   STATUS,RP0
	clrf  PIR1
	goto	UserStart								  ; run user program

;------------------------------------------------------------------------------
inst_ident
	movlw	IDACK											; send IDACK
	goto	send_byte
;------------------------------------------------------------------------------
inst_write
	call	getbyte
	movwf	address+1									; high byte of address
	call	getbyte
	movwf	address										; low byte of address
	call	getbyte
	movwf	amount										; number of bytes -> amount -> count
	movwf count
	call	getbyte										; checksum -> chk2
	movwf	chk2
	clrf	chk1											; chk1 = 0
	movlw buff
	movwf FSR												; FSR pointer = buff
receive_data
	call	getbyte										; receive next byte -> buff[FSR]
	movwf	INDF
	addwf	chk1,f										; chk1 := chk1 + buff[FSR]
	incf  FSR,f											; FSR++
	decfsz count,f
		goto receive_data							; repeat until (--count==0)
checksum
	movf	chk1,w
	xorwf	chk2,w										; if (chk1 != chk2)
	movlw DATA_BAD
	btfss	STATUS,Z
	 goto	send_byte									; checksum WRONG
checksum_ok
	movlw	DATA_OK										; checksum OK
	call	putbyte
write_byte
	call	write_eeprom							; write to eeprom
	iorlw	0
	movlw WR_OK											; writing OK
	btfsc	STATUS,Z
	movlw	WR_BAD										; writing WRONG

;------------------------------------------------------------------------------
send_byte
	call	putbyte										; send of byte
	goto	receive										; go to receive from UART
;------------------------------------------------------------------------------

;************************* putbyte subroutine *********************************
putbyte
	clrwdt
	btfss	PIR1,TXIF									; while(!TXIF)
	 goto	putbyte
	movwf	TXREG											; TXREG = byte
	return

;************************* getbyte subroutine *********************************
getbyte
	clrwdt
	IFNDEF PIN											; for TIME
		movf	time,w
		btfsc	STATUS,Z								; check for time==0
		 goto	getbyte3
		btfss	PIR1,TMR1IF							; check for TIMER1 overflow
		 goto	getbyte3								; no overflow
		bcf		T1CON,TMR1ON						; timeout 0.1 sec
		decfsz	time,f								; time--
			goto	getbyte2
		retlw 0												; if time==0 then return
getbyte2
		bcf		PIR1,TMR1IF
		movlw	high TIMER
		movwf	TMR1H										; preset TIMER1 for 0.1s timeout
		bsf		T1CON,TMR1ON
	ENDIF
getbyte3
	btfss	PIR1,RCIF									; while(!RCIF)
	 goto	getbyte
	movf	RCREG,w										; RCREG
	return

;******************** write eeprom subroutine *********************************
write_eeprom
	movf	address,w
	movwf	tmpaddr										; tmpaddr = address
	movf	address+1,w
	movwf	tmpaddr+1
	clrf	count											; count=0
write_loop
	movlw	NumRetries+1							; retry = NumRetries+1
	movwf	retry
w_e_l_1
	movf	amount,w
	subwf	count,w										; while (count<amount)
	btfsc	STATUS,C
	 retlw 1												; otherwise return 1 (OK)
	movf	count,w
	addlw	buff											; set buffer pointer
	movwf	FSR
w_e_l_2
	movlw	0x21											; if (0x2100 <= tmpaddr <= 0x21FF) ..........
	subwf	tmpaddr+1,w
	bsf	STATUS,RP1
	bsf	STATUS,RP0									; (bank3)
	btfsc	STATUS,Z
	 goto	data_eeprom								; goto data_eeprom
program_eeprom
	bsf		EECON1,EEPGD							; EEPGD = 1 -> program memory
	clrf	STATUS
	movlw	high (LoaderStart)				; if (tmpaddr >= LoaderStart) ...............
	subwf	tmpaddr+1,w
	movlw	low (LoaderStart)					; mask Booloader, [ICD-Debugger],
	btfsc	STATUS,Z									;      __IDLOCS & __CONFIG
	subwf	tmpaddr,w
	btfsc	STATUS,C
	 goto next_adr									; next address
	goto  w_e_l_3
data_eeprom
	bcf		EECON1,EEPGD							; EEPGD = 0 -> data memory
	clrf	STATUS
w_e_l_3
	movf	tmpaddr,w
	bsf		STATUS,RP1
	movwf	EEADR											; EEADR = low tmpaddr
	bcf		STATUS,RP1
	movf	tmpaddr+1,w								;	if (tmpaddr < 0x0004) .....................
	btfss STATUS,Z
	 goto w_e_l_4
	movlw	4
	subwf	tmpaddr,w
	btfsc	STATUS,C
	 goto	w_e_l_4
	bsf STATUS,RP1                  ; (bank3)
	bsf STATUS,RP0
	btfss EECON1,EEPGD              ; skip if (EEPGD)
	 goto	w_e_l_31
	bcf	STATUS,RP0                  ; (bank2)
	movlw low UserStart+1						; EEADRL + low UserStart+1
	addwf EEADR,f                   ; (relocated first 4 user instructions)
w_e_l_31
	clrf	STATUS                    ; (bank0)
	movlw	high UserStart						; EEADRH = high UserStart
	goto	w_e_l_5
w_e_l_4
	movf	tmpaddr+1,w								; EEADRH = high tmpaddr
w_e_l_5
	bsf	STATUS,RP1
	movwf	EEADRH										; set EEADRH
	movf	INDF,w
	movwf	EEDATH										; EEDATH = buff[count]
	incf  FSR,f
	movf  INDF,w
	movwf	EEDATA										; EEDATA = buff[count+1]
	bsf	STATUS,RP0
	bsf	EECON1,WREN									; WREN=1
	movlw	0x55											; EECON2 = 0x55
	movwf	EECON2
	movlw	0xAA											; EECON2 = 0xAA
	movwf	EECON2
	bsf	EECON1,WR										;	WR=1
	nop															; instructions are ignored
	nop															; microcontroller waits for a complete write
	clrf	STATUS
wait_write
		clrwdt
		btfss	PIR2,EEIF								; necessary for a write to data eeprom
		 goto	wait_write
	bcf	PIR2,EEIF
	bsf STATUS,RP0									; (bank3)
	bsf STATUS,RP1
	bcf	EECON1,WREN									; WREN=0
	bsf	EECON1,RD										; RD=1
	nop
	nop
	bcf STATUS,RP0									; (bank2)
	decf  FSR,f
	movf  INDF,w										; if ((EEDATH != buff[count]) || (EEDATA != buff[count+1]))
	xorwf EEDATH,w
	btfss STATUS,Z
	 goto	w_e_l_6										; repeat write
	incf  FSR,f
	movf  INDF,w
	xorwf EEDATA,w
	btfsc	STATUS,Z
	 goto	next_adr									; verification OK, next address
w_e_l_6
	clrf	STATUS										; (bank0)
	decfsz	retry,f
		goto	w_e_l_1									; if (--retry != 0) repeat write
	retlw	0													; else return 0 (BAD)
next_adr
	bcf STATUS,RP1
	movlw	2													;	count := count + 2
	addwf	count,f
	incf	tmpaddr,f									; tmpaddr := tmpaddr + 1
	btfsc	STATUS,Z
	 incf	tmpaddr+1,f
	goto	write_loop

;------------------------------------------------------------------------------
	END

