;******************************************************************************
;   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=16f876               ; <<< 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'6000000'     ; <<< set quartz frequence [Hz], max. 20 MHz
    #define BAUD D'19200'       ; <<< set baud rate [bit/sec]
    #define BAUD_ERROR  D'4'    ;   <<< 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_OFF & _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 LoaderSize  0xD8                    ; 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
;    bcf     TRISB,7 ; //jc - PIN_B7 as output
    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              ; bank0
;    bsf     PORTB,7 ; //jc - set PIN_B7
      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

