Source Code - By Popular Request

Vegipete's no-host serial bootloader for PIC18 devices.

Source Code - By Popular Request

Postby vegipete » Mon Nov 18, 2013 11:09 pm

A bunch of people have requested the source code so it is time to share.

Two versions of my bootloader follow.

The first, version 0.52, uses hardware handshaking and is targeted for a PIC18F14K22. It should be easy enough to modify for other PIC18's.

The second, version 0.63, uses software flow control. It also is targeted for the PIC18F14K22 but has been expanded for a few other chips using compile time #defines. A big improvement is support for more than 256 bytes of EEPROM. XON/XOFF flow control turned out to be an interesting challenge, especially since interrupts are not available for the bootloader given my design choices. (Something in the serial communications is possibly still not perfect so if you experience flow control problems, increase the number of stop bits to 2.)

The comments for both versions are hopefully good enough that moving the code to other PIC18's shouldn't be too difficult - mainly just altering the processor CONFIGs and the #define characteristics near the start of the two source files.

The bootloader works well for me, but I make no guarantees... etc.etc.etc.
/vp
vegipete
 
Posts: 37
Joined: Sat Jul 09, 2011 7:38 pm

Source Code - By Popular Request

Sponsor

Sponsor
 

Re: Source Code - By Popular Request

Postby vegipete » Mon Nov 18, 2013 11:15 pm

Here is version 0.52: (using hardware handshaking)

Code: Select all
;******************************************************************************
; PIC18 BootLoader
; ©2013 by vegipete@strongedge.net
;
;   A bootloader to allow loading firmware via serial port without
;   the need for dedicated host software.
;   Written for a PIC18F14K22 chip
;      Flash Erase Block Size = 64
;      Flash Write Block Size = 16
;   On RESET, the loader checks if !CTS is shorted to TX with a resistor.
;   (470 Ohms is a suitable resistor value.)
;   If so, the BootLoader runs. Otherwise, the code performs
;   a GOTO 4 which must be a vector to the start of the user
;   program.
;   If the user code space is blank, the BootLoader takes
;   control.
;   Configured for 38400,8N1
;   Uses Hardware flow control
;   Written by VegiPete   picprog.strongedge.net
;
;   Version Control:
;   0.1   The beginning
;   0.2
;   0.3   Working
;   0.4   Change to actual bootloader
;   0.5   Refinement, increased robustness
;   0.51
;   0.52   Auto-programs "GOTO BOOT_LOADER_ADDR" at address 0
;
#define   version   "0.52"
;
;   Hardware:
;   Internal RC OSC with 4x PLL enabled
;
;   RB4 - !CTS flow control output - drive low to receive
;   RB5 - RX - Serial in
;   RB6 -
;   RB7 - TX - Serial out
;
;   RC5 - LED
;   
;******************************************************************************
;Resources Used:
;   Serial Port   
;******************************************************************************

   LIST   P=18F14K22      ;directive to define processor
   #include <P18F14K22.INC>   ;processor specific variable definitions
   radix   dec
   errorlevel -311                 ;don't warn on HIGH() operator values >16-bits

;******************************************************************************
;
; CONFIGURATION WORD SETUP
;
; The 'CONFIG' directive is used to embed the configuration word within the
; .asm file. The lables following the directive are located in the respective
; .inc file.  See the data sheet for additional information on configuration
; word settings.
;
;******************************************************************************

     ;Setup CONFIG11H
     CONFIG FOSC = IRC, PLLEN = ON, PCLKEN = OFF, FCMEN = OFF, IESO = OFF
     ;Setup CONFIG2L
     CONFIG PWRTEN = ON, BOREN = OFF, BORV = 30
     ;Setup CONFIG2H
     CONFIG WDTEN = OFF, WDTPS = 1
     ;Setup CONFIG3H
     CONFIG MCLRE = ON, HFOFST = OFF
     ;Setup CONFIG4L
     CONFIG STVREN = OFF, LVP = OFF, BBSIZ = OFF, XINST = OFF
     ;Setup CONFIG5L
     CONFIG CP0 = OFF, CP1 = OFF
     ;Setup CONFIG5H
     CONFIG CPB = OFF, CPD = OFF
     ;Setup CONFIG6L
     CONFIG WRT0 = OFF, WRT1 = OFF
     ;Setup CONFIG6H
     CONFIG WRTB = OFF, WRTC = OFF, WRTD = OFF
     ;Setup CONFIG7L
     CONFIG EBTR0 = OFF, EBTR1 = OFF
     ;Setup CONFIG7H
     CONFIG EBTRB = OFF

;******************************************************************************
; PIC18F14K22 Characteristics
#define DEVICEID                .633
#define WRITE_FLASH_BLOCKSIZE   .16
#define ERASE_FLASH_BLOCKSIZE   .64
#define END_FLASH               0x4000
#define END_GPR                 0x200
#define   FLASH_LOADER_ADDR   0x3B00
#define   FLASH_LOADER_STR   "0x3B00" ;same thing in string form
#define   IDADDRESS      0x200000 ;location of ID in memory map
#define   CONFIGADDRESS      0x300000 ;location of CONFIG in memory map
#define   EEADDRESS      0xF00000 ;location of EE in memory map
#define   EE_LENGTH      0x100    ;number of bytes of EEPROM

;serial port flow control - CTS low to permit incoming data
#define   CTS_PIN      4
#define   CTS_STOP   bsf   LATB,4
#define   CTS_GO      bcf   LATB,4
#define   TX_PIN      7

#define   LED_PIN      5

   CBLOCK   0x000
   OSCCON_save   :0   ; preserve OSCCON during startup tests
   hex32_length   :1   ; number of data bytes in record
   hex32_type   :1   ; type of record
   hex32_chksum   :1   ; running checksum calculation
   hex32_count   :1   ; loop counter
   hex32_addr   :4   ; address long word
   start_count   :1   ; counter to track first 4 bytes of flash
   user_cmd   :1   ; character typed by user
   temp_var   :1   ; general counter
   flags      :1   ; misc useful flags
   ENDC

WRITEDATA   EQU   0   ; set when data waiting for write to flash
RANGERR      EQU   1   ; set if write addr too high
WRITEEEPROM   EQU   2   ; set when EEPROM data found

   CBLOCK
   save_TBLPTR:3
   stim_TBLPTR:3
   ENDC

   CBLOCK   0x100
   rx_buffer
   ENDC

;******************************************************************************
; Note: each line of DE data must have an even number of bytes to avoid 0 padding.
;EEPROM data
   ORG   0xF00000
   DE   "The first line of code starting at the reset vector,"
   DE   " location 0, must be \"GOTO 0x0004\". Put "
   DE   "\"GOTO USER_START\" at location 4. Serial port settings:"
   DE   " 38400 Baud, 8N1, hardware flow control. RB4 outputs"
   DE   " low when the bootloader is ready to receive characters."

;******************************************************************************
;Reset vector
; This code will start executing when a reset occurs.
   ORG   0x0000     ; start address 0
   goto   0x0004      ; place holder
   goto   DummyUserProg   ; no user program yet

;This code acts as a preliminary user program.
;It displays an introduction message and then
;periodically calls the bootloader.
DummyUserProg:
   movlw   0x55
   xorwf   hex32_count,w   ; test if we've just booted
   bnz   FirstBoot

AlreadyBooted
   clrf   temp_var
   bsf   temp_var,7
;delay a while
NoUserLoop:   
   movlw   0
NoUserLoopInner:
   decfsz   WREG,f
   bra   NoUserLoopInner
   decfsz   temp_var,f
   bra   NoUserLoop
   
; visit the bootloader to try find user input requesting bootloader access.
   goto   FlashLoaderBoot   

FirstBoot
;set up oscillator
   movlw   b'01111100'   ;sleep, 16MHz, R, R, Primary Clock
   movwf   OSCCON      ;set oscillator

;set up Timer 0
   movlw   b'10010110'   ; On, 16 bit, Internal, 1:128 prescale
   movwf   T0CON

;set up serial port (USART)
   movlw   b'11101111'   ; Set most of RB to input so UART can control it
   movwf   TRISB      ; RB4 as output for !CTS

   bcf   ANSELH,2   ; make !CTS pin digital
   bcf   ANSELH,ANS11   ; make RX pin digital

   clrf   LATB

   clrf   SPBRGH       ;EUSART Baud Rate Generator Register High Byte
;   movlw   51      ;19k2 @ 16MHz (16MHz x4 PLL / 4 clocks/instruction)
   movlw   25      ;38k4 @ 16MHz (16MHz x4 PLL / 4 clocks/instruction)
   movwf   SPBRG      ;EUSART Baud Rate Generator Register Low Byte

   bsf   TXSTA,TXEN   ; TXEN = 1  Transmit enabled
   bsf   RCSTA,CREN   ; CREN = 1  Enables receiver

   CTS_STOP      ; no serial characters thanks
   bsf   RCSTA,SPEN   ; enable serial port
   call   pause100ms   ; wait for serial port to settle. (Why?)
   
   movlw   0x55
   movwf   hex32_count   ; set variable so we know PIC has aleady started
   
   call   UARTPutString   ;
   db   "\r\n\r\nPIC18 BootLoader v" version
   db   " installed at " FLASH_LOADER_STR
   db   ".\r\n©2013 by vegipete@strongedge.net\r\n\n"
   db   "No user program yet.\r\n"
   db   "The start of the user program must be:\r\n"
   db   " org  0\r\n goto 4\r\n goto START_USER_PROG \r\n"
   db   ";(The bootloader will automatically change the 'goto 4' statement.) \r\n"
   db   "\r\nShort CTS to TX to access bootloader.\r\n\r\n\r\n",0
   
   reset

;******************************************************************************
;******************************************************************************
;*
;*   Flash Loader
;*
;******************************************************************************
;******************************************************************************
;Flash loading program  goes into top of memory

   ORG   FLASH_LOADER_ADDR
;Initialize hardware
;Perform a minimal hardware init then test whether we should run user code
FlashLoaderBoot:
;   clrf   INTCON      ;make sure interupts are off
   clrf   BSR      ;make sure bank 0 is selected

   movff   OSCCON,OSCCON_save
   movlw   b'01111100'   ;sleep, 16MHz, R, R, Primary Clock
   movwf   OSCCON      ;set oscillator

   bcf   ANSELH,2   ; make !CTS pin digital

   clrf   LATB
;   movlw   b'00110000'   ; Set RX & !CTS pins as inputs, TX remains output for now
;   movwf   TRISB      ; Set PORTB direction bits
   bcf   TRISB,TX_PIN   ; Set TX pin direction to output
;   clrf   WPUB      ; Turn off PORTB weak pullups
;   bsf   WPUB,CTS_PIN   ; Enable weak pullup on CTS pin
   bcf   INTCON2,RABPU   ; Global enable pullups

;Test if the CTS pin follows the TX pin, indicating a short (using ~470 Ohm resistor)
;If CTS does not follow TX, goto 4 to execute user code
;If user code space is blank (contains 0xFF), the bootloader will keep rerunning.
;This will give the appearance of a hung processor, until CTS is shorted to TX
   movlw   4
   movwf   temp_var
CTSTestShort:
   dcfsnz   temp_var,f
   bra   FlashLoaderStart
   bsf   LATB,TX_PIN   ; Drive pin high
   rcall   CTSTestDelay
   btfss   PORTB,CTS_PIN
   bra   RunUserCode
   bcf   LATB,TX_PIN   ; Drive pin low
   rcall   CTSTestDelay
   btfss   PORTB,CTS_PIN
   bra   CTSTestShort
RunUserCode:
   setf   WPUB      ; Restore special function
   setf   INTCON2      ;   registers as close
   setf   ANSELH      ;   to reset condition
   setf   TRISB      ;   as reasonably
   bsf   INTCON2,RABPU   ;   possible
   movff   OSCCON_save,OSCCON
;   reset
   goto   4      ; Run user code

CTSTestDelay:
   movlw   0
CTSTestDelayLoop:
   decfsz   WREG,f
   bra   CTSTestDelayLoop
   return

FlashLoaderStart:
   bcf   TRISC,LED_PIN   ; LED pin as output
   bsf   LATC,LED_PIN   ; LED on to indicate bootloader has started
WaitButtonRelease:
   btfss   PORTB,CTS_PIN   ; wait for pin to go high - button released (weak pull-up still active)
   bra   WaitButtonRelease
;set up Timer 0
   movlw   b'10010110'   ; On, 16 bit, Internal, 1:128 prescale
   movwf   T0CON

;set up serial port (USART)
   movlw   b'11101111'   ; Set TX pin back to input so UART can control it
   movwf   TRISB      ; RB4 as output for !CTS
; BAUDCON reset value is fine
;   movlw   b'00000000'
      ; 0.......    Auto-Baud Acquisition Rollover Status bit
      ; .0......    (read only) 1 = Receive operation is Idle
      ; ..0..... RXDTP 0 = Receive data (RX) is not inverted (active-high)
      ; ...0.... TXCKP 0 = Idle state for transmit (TX) is a high level
      ; ....0... BRG16 1 = 16-bit Baud Rate Generator – SPBRGH and SPBRG
      ; .....0..    Unimplemented: Read as ‘0’
      ; ......0.    Wake-up Enable bit
      ; .......0    0 = Baud rate measurement disabled or completed
;   movwf   BAUDCON

   clrf   SPBRGH       ;EUSART Baud Rate Generator Register High Byte
;   movlw   51      ;19k2 @ 16MHz (16MHz x4 PLL / 4 clocks/instruction)
   movlw   25      ;38k4 @ 16MHz (16MHz x4 PLL / 4 clocks/instruction)
   movwf   SPBRG      ;EUSART Baud Rate Generator Register Low Byte

;   movlw   b'00100000'
      ; 0....... CSRC  don't care
      ; .0...... TX9   0 = Selects 8-bit transmission
      ; ..1..... TXEN  1 = Transmit enabled
      ; ...0.... SYNC  0 = Asynchronous mode
      ; ....0... SENDB Send Break Character bit
      ; .....0.. BRGH  1 = High speed BRGH: High Baud Rate Select bit
      ; ......0. TRMT  TRMT: Transmit Shift Register Status bit (1=TSR empty)
      ; .......0 TX9D  9th Bit of Transmit Data
;   movwf   TXSTA
   bsf   TXSTA,TXEN

;   movlw   b'00010000'
      ; 0....... SPEN  0 = Serial port NOT YET enabled
      ; .0...... RX9   0 = Selects 8-bit transmission
      ; ..0..... SREN  don't care
      ; ...1.... CREN  1 = Enables receiver
      ; ....0... ADDEN don't care
      ; .....0.. FERR  1 = Framing error
      ; ......0. OERR  1 = Overrun error
      ; .......0 RX9   read only, don't care
;   movwf   RCSTA
   bsf   RCSTA,CREN

   CTS_STOP      ; no serial characters thanks
   bcf   ANSELH,ANS11   ; make RX pin digital
   bsf   RCSTA,SPEN   ; enable serial port
   rcall   pause100ms   ; wait for serial port to settle. (Why?)

   rcall   UARTPutString   ;
   db   "\r\n\n\nPIC18 BootLoader v" version
   db   "\r\n©2012 by vegipete@strongedge.net\r\n",0

HelpRequested:
   rcall   ShowHelp
NewPrompt:
   rcall   ShowPrompt
   rcall   pause100ms
   clrf   user_cmd
InputLoop:
   rcall   UARTReadChar   ; Wait for user to type something
   xorlw   '\r'      ; Enter Key?
   bz   EvalUserCmd   ; yes so evaluate what, if anything, was received
   xorlw   '\r'      ; recover character
   movwf   user_cmd   ; save it
   rcall   UARTprint   ; echo it
   bra   InputLoop

EvalUserCmd:
   movf   user_cmd,w
   bz   NewPrompt   ; nothing received, go back and wait
   xorlw   '?'
   bz   HelpRequested
   iorlw   b'00100000'   ; convert possible lowercase to upper
   xorlw   'P'^'?'
   bz   ProgramHEX
;   xorlw   'V'^'P'
;   bz   VerifyHEX
   xorlw   'W'^'P'
   bz   WriteEEPROM
   xorlw   'E'^'W'
   bz   EraseEEPROM
   xorlw   'R'^'E'
   bz   ReadEEPROM

   xorlw   'X'^'R'
;   bz   RunUserCode
   btfsc   STATUS,Z
   reset
ReportError:
   rcall   UARTPutString   ;
   db   "\r\n\aSyntax Error",0   ;'\a' is bell character
   bra   NewPrompt   ; Unrecognized command so keep looking


EraseEEPROM:
   rcall   UARTPutString   ;
   db   "\r\nErase EEPROM - ",0
   rcall   ConfirmPrompt
   bnz   CancelPrompt   ; aborted so return to prompt
   rcall   UARTPutString   ;
   db   "\r\nErasing EEPROM... ",0

   setf   EEDATA      ; 0xFF is erased value
   clrf   EEADR      ; start of EEPROM
EraseEEloop:
   movlw   b'00000100'     ; Setup for EEPROM data writes
   movwf   EECON1
   rcall   StartWrite
   btfsc   EECON1, WR      ; wait for write to complete
   bra   $-2      ;    \  before moving to next address
   incfsz   EEADR,f      ; finished all 256 bytes yet?
   bra   EraseEEloop   ; keep looping

   rcall   ShowDone
   bra   NewPrompt

WriteEEPROM:
   bsf   flags,WRITEEEPROM
    bra   StartEEwrite

VerifyHEX:
CancelPrompt:
   rcall   UARTPutString   ;
   db   "\r\nCanceled",0   ;
   bra   NewPrompt   ; canceled command so keep looking

; read all of EEPROM and dump it out the serial port
ReadEEPROM:
   rcall   UARTPutString   ;
   db   "\r\nEEPROM:",0   ;
   clrf   EEADR      ; start of EEPROM
EE_Readloop:
   movlw   0x0F
   andwf   EEADR,w      ; if low nibble is zero, start a new line
   bnz   EE_ReadNext
   movlw   '\n'
   rcall   UARTprint
   movlw   '\r'
   rcall   UARTprint
   movf   EEADR,w
   rcall   UART_send_wreg
   movlw   ':'
   rcall   UARTprint
EE_ReadNext:
   bcf   EECON1, EEPGD   ; Point to DATA memory
   bcf   EECON1, CFGS   ; Access EEPROM
   bsf   EECON1, RD   ; EEPROM read
   movf   EEDATA,w   ; grab value
   rcall   UART_send_wreg
   movlw   ' '
   rcall   UARTprint
   incfsz   EEADR,f      ; keep going until EEADR rolls back to zero
   bra   EE_Readloop   ; loop back for more
   bra   NewPrompt

ProgramHEX:
   rcall   UARTPutString   ;
   db   "\r\nReprogram Flash - ",0
   rcall   ConfirmPrompt
   bnz   CancelPrompt   ; aborted so return to prompt
   rcall   UARTPutString   ;
   db   "\r\nErasing Flash...",0

;Erase flash starting from high end working down to 0
;set TBLPTR to start of last block _before_ FlashLoader
   movlw   low (FLASH_LOADER_ADDR-ERASE_FLASH_BLOCKSIZE)
   movwf   TBLPTRL
   movlw   high (FLASH_LOADER_ADDR-ERASE_FLASH_BLOCKSIZE)
   movwf   TBLPTRH

   clrf   TBLPTRU      ; device has less than 64K flash
EraseFlashLoop:
   movlw   b'10010100'   ; setup FLASH erase
   movwf   EECON1
   rcall   StartWrite   ; erase the block
;adjust TBLPTR to next lower block of flash
   movlw   ERASE_FLASH_BLOCKSIZE
   subwf   TBLPTRL, F
   clrf   WREG
   subwfb   TBLPTRH, F
   subwfb   TBLPTRU, F
   bnn   EraseFlashLoop   ; any more blocks?

   rcall   ShowDone

   clrf   flags      ; all clear to start writing flash

StartEEwrite:
   rcall   UARTPutString   ;
   db   "\r\nWaiting for PIC18F14K22 Hex32 file...\r",0

;Prepare some pointers for receiving a HEX file and writing it to flash/eeprom
   movlw   4      ; first 4 bytes must be
   movwf   start_count   ;   changed to "GOTO FLASH_LOADER_ADDR"
   clrf   hex32_addr+0
   clrf   hex32_addr+1
   clrf   hex32_addr+2
   clrf   hex32_addr+3
   clrf   TBLPTRL
   clrf   TBLPTRH
   clrf   TBLPTRU      ; point to flash start to begin

FlashRx:
   movlw   '\n'
   rcall   UARTprint
   lfsr   2,rx_buffer   ; serial test receive buffer
;read characters until the buffer fills, EOL is found or CTRL-C is detected
FlashRxLoop:
   rcall   UARTReadChar

   xorlw   3      ; is it CTRL-C?
   bz   CancelPrompt

   xorlw   3^'\n'      ; is it New Line?
   bz   FlashRxLoop   ; ignore New Line character
   xorlw   '\n'      ; recover character
   movwf   POSTINC2   ; put it in buffer
; The following lines have some tricky subtleties to test for buffer overflow.
; If FSR2L = 0, the character just written is the last one to fit in the buffer
; and FSR2 has rolled from 0x01FF to 0x0200. If the character just added to the
; buffer is not \r then the current line is not complete and therefore must
; overflow because at least one more character is required.
   xorlw   '\r'      ; EOL? - set flags
   tstfsz   FSR2L      ; is receive buffer full?
   bnz   FlashRxLoop   ; no EOL, buffer not full so keep looking
   bnz   FlashEchoNG   ; no EOL, buffer full so error on line

;got a line of hex file in the buffer
;step 1 - confirm line starts with ":"
   lfsr   2,rx_buffer   ; point to start of buffer
   movf   POSTINC2,w   ; grab first character
   xorlw   ':'      ; must be a colon
   bnz   FlashEchoNG   ; error on line

;step 2 - test if checksum is correct
   rcall   ReadByte   ; read number of data bytes
   movwf   hex32_chksum   ; start the checksum
   movwf   hex32_count   ; initial loop counter
   incf   hex32_count,f   ; adjust loop counter for end condition
   rcall   ReadByte   ; read addr high
   addwf   hex32_chksum,f   ; add it to checksum
   rcall   ReadByte   ; read addr low
   addwf   hex32_chksum,f   ; add it to checksum
   rcall   ReadByte   ; read record type
   addwf   hex32_chksum,f   ; add it to checksum
F_Calc_Chksum:
   dcfsnz   hex32_count,f   ; test for more bytes to read
   bra   F_Read_Chksum
   rcall   ReadByte   ; read (another) data bye
   addwf   hex32_chksum,f   ; add it to checksum
   bra   F_Calc_Chksum   ; keep looping for more

F_Read_Chksum:
   rcall   ReadByte   ; read the checksum
   addwf   hex32_chksum,f   ; add to calculated checksum - result must be zero
   bnz   FlashEchoNG   ; brach if checksum doesn't match

;step 3 - evaluate line
   lfsr   2,rx_buffer+1   ; point to byte count
   rcall   ReadByte   ; read number of data bytes
   movwf   hex32_count

   rcall   ReadByte   ; read address high byte
   movwf   hex32_addr+1
   rcall   ReadByte   ; read address low byte
   movwf   hex32_addr+0

   rcall   ReadByte   ; read block type 00 - normal, 01 - end, 04 - high word
   movwf   hex32_type
   bz   FlashLoadData   ; 00 - found a line of regular data bytes
   decf   WREG,w
   bz   FlashEndFile   ; 01 - found end of file
   decf   WREG,w
   bz   FlashEchoNG   ; 02 - unrecognized
   decf   WREG,w
   bz   FlashEchoNG   ; 03 - unrecognized
   decf   WREG,w
   bz   FlashSetHigh   ; 04 - set high address
   bra   FlashEchoNG

;Echo the line back out the serial port with a result code in front
;"OK"   - accepted line and written to flash
;"RE"   - Range Error - tried to write into flashloader or higher
;"SQ"   - SeQuence error - hex32 records are not in ascending order
;"NG"   - No Good - hex32 record fails checksum or has other flaws
FlashEchoOK:
   movlw   'O'
   rcall   UARTprint
   movlw   'K'
   bra   FlashEcho
FlashEchoNG:
   movlw   'N'
   rcall   UARTprint
   movlw   'G'
   bra   FlashEcho
FlashEchoSQ:
   movlw   'S'
   rcall   UARTprint
   movlw   'Q'
   bra   FlashEcho
FlashEchoRE:
   bsf   flags,RANGERR   ; block type 4 range error
   movlw   'R'
   rcall   UARTprint
   movlw   'E'
FlashEcho:
   rcall   UARTprint
   lfsr   2,rx_buffer   ; point to start of buffer
FlashEchoLoop:
   movf   POSTINC2,w   ; grab next character in buffer
   rcall   UARTprint   ; echo it
   xorlw   '\r'      ; EOL?
   tstfsz   FSR2L      ; test for buffer overflow, flags unaffected
   bnz   FlashEchoLoop   ; no, more to do
   bz   FlashEchoDone   
   movlw   '\r'
   rcall   UARTprint
FlashEchoDone:

   bra   FlashRx

;Block type 04 - set high address
;If the high byte is not zero, an impossible location has been requested - Range Error
;If the low byte = 0x20, an ID location has been requested - not supported: Range Error
;If the low byte = 0x30, a CONFIG location has been requested - not supported: Range Error
;If the low byte = 0xF0, an EEPROM location has been requested
;For a device with <= 64K of flash,
;           any high address other than 0x00F0 (or 0x0000) is a Range Error
FlashSetHigh:
   decf   hex32_count,w
   decfsz   WREG,w      ; make sure block length is 2
   bra   FlashEchoNG   ; length not 2 so it's an error
   rcall   ReadByte   ; read upper address high byte
   movwf   hex32_addr+3
   bnz   FlashEchoRE   ; indicate range error
   rcall   ReadByte   ; read upper address low byte
   movwf   hex32_addr+2
   bz   FlashEchoOK   ; done with this block
   sublw   upper EEADDRESS
   bnz   FlashEchoRE   ; indicate range error
   bsf   flags,WRITEEEPROM
   bra   FlashEchoOK   ; done with this block

;block type 01 - end of file
;Write any leftover bytes to flash.
FlashEndFile:
   btfss   flags,WRITEDATA   ; is there anything left in the holding registers?
   bra   DataAllWritten   ; done if not
FlushToFlash:
   setf   TABLAT      ; pad holding registers with 0xFF
   tblwt   *+      ; post increment
   movf   TBLPTRL, w   ; have we crossed into the next write block?
   andlw   (WRITE_FLASH_BLOCKSIZE-1)
   bnz   FlushToFlash   ; loop for more
   rcall   WriteThisBlock   ; initiate a page write

DataAllWritten:
   rcall   UARTPutString   ;
   db   "Finished!\r\n",0
   btfsc   EECON1, WR      ; wait for any previous write to complete
   bra   $-2      ;    \  before moving to next address
   clrf   EECON1
   bra   NewPrompt

;Block type 00 - normal line of data
;If the running data address matches the TBLPTR, add the next byte to the write buffer.
; Otherwise, add 0xFF to the write buffer until the TBLPTR catches up.
; THIS REQUIRES THAT THE HEX32 FILE DATA BE IN ASCENDING ORDER. Writes
; scattered all over the Hex32 file won't work correctly.
; MPLAB seems to generate Hex32 files that meet this requirement.
;Fill write buffer with WRITE_FLASH_BLOCKSIZE bytes of data.
;When the write buffer is full, write the bytes to flash.
;Current block may have exactly enough bytes, or too many or too few.
;If RANGERR is set, current block can't be written, unless...
;If WRITEDATA is also set, the holding registers must be filled and written to flash
FlashLoadData:
   btfsc   flags,WRITEEEPROM
   bra   EEPROMWrite   ; branch if into EEPROM space
   btfss   flags,RANGERR   ; has a range error been found?
   bra   FlashLoadLoop   ; no so proceed
   btfss   flags,WRITEDATA   ; is there data in the holding registers?
   bra   FlashEchoRE   ; no so indicate range error
   bra   FlashAddrNoMatch

FlashLoadLoop:
   ;if TBLPTR != hex32_addr then
   ;   TABLAT = 0xFF
   ;else
   ;   TABLAT = next byte from input text
   ;   set flag indicating real data
   ;   hex32_addr++
   ;
   ;tblwt *+ (add another byte to holding registers
   ;
   ;if TABLPTR into next write block
   ;   write this block
   ;   clear real data flag

   movf   hex32_addr+0,w
   subwf   TBLPTRL,w   ; WREG = TBLPTRL - hex32_addr+0
   movwf   PRODL      ; store each comparison value
   movf   hex32_addr+1,w
   subwfb   TBLPTRH,w
   iorwf   PRODL,f
   movf   hex32_addr+2,w
   subwfb   TBLPTRU,w
   bnc   FlashAddrNoMatch ;Branch if TBLPTR < FLASH_LOADER_ADDR - fill with 0xFF
   iorwf   PRODL,f
   bnz   FlashEchoSQ   ; Branch if TBLPTR > FLASH_LOADER_ADDR - sequence error

   rcall   ReadByte   ; read next data byte

;The first 4 bytes must be "GOTO FLASH_LOADER_ADDR" or else
;bootloader access will be lost.
   tstfsz   start_count   ; Are we still in the first 4 bytes of FLASH?
   rcall   ChangeByte   ; yes so change byte

UseThisByte:
   decf   hex32_count,f   ;
   movwf   TABLAT      ; load the holding register
   bsf   flags,WRITEDATA   ; got something to write to flash

   rcall   IncHex24
   bra   FillHolding

;return bytes that represent "goto FLASH_LOADER_ADDR"
ChangeByte:
   decf   start_count,f   ; decrement counter
   movf   PCL,w      ; load PCLATH and PCLATU
   rlncf   start_count,w   ; double the count for computed goto
   addwf   PCL,f      ; confirm no page boundary cross
;   retlw   0      ; return NOP instead
   retlw   (upper (FLASH_LOADER_ADDR >> 1)) | 0xF0
   retlw   high (FLASH_LOADER_ADDR >> 1)
   retlw   0xEF      ; OP code for 'GOTO'
   retlw   low (FLASH_LOADER_ADDR >> 1)

FlashAddrNoMatch:
   setf   TABLAT      ; pad holding registers with 0xFF
FillHolding:
   tblwt   *+      ; post increment
   movf   TBLPTRL, w   ; have we crossed into the next write block?
   andlw   (WRITE_FLASH_BLOCKSIZE-1)
   btfsc   STATUS,Z   ; Z set if buffer is full
   rcall   WriteThisBlock   ; holding registers are full so write them

   tstfsz   hex32_count   ; reached end of this line of text?
   bra   FlashLoadData   ;(FlashLoadLoop?)   ; no so loop to do more
   btfss   flags,RANGERR
   bra   FlashEchoOK   ; done with this block
   bra   FlashEchoRE   ; indicate range error

;The holding registers are full so write them to flash
;The registers could also be completely empty, so write nothing
WriteThisBlock:
   btfss   flags,WRITEDATA   ; is there actually data to write?
   bra   WriteNoData   ; done if not

   bcf   flags,WRITEDATA
   tblrd   *-      ; Point back into the block to write data
   ;test if TBLPTR is too high - protect flash loader code
   movlw   low FLASH_LOADER_ADDR
   subwf   TBLPTRL,w
   movlw   high FLASH_LOADER_ADDR
   subwfb   TBLPTRH,w
   movlw   upper FLASH_LOADER_ADDR
   subwfb   TBLPTRU,w
   bn   WriteAddrOkay   ; Branch if TBLPTR < FLASH_LOADER_ADDR
   bsf   flags,RANGERR   ; set RANGE ERROR, stays set forever
   bra   WriteAddrBad   ; no write

WriteAddrOkay:
   movlw   b'10000100'   ; Setup FLASH writes
   movwf   EECON1

   rcall   StartWrite   ; initiate a page write
WriteAddrBad:
   tblrd   *+      ; Restore pointer for loading holding registers with next block
WriteNoData:
   clrf   EECON1      ; inhibit writes
   return

;Write data to EEPROM
;EEPROM is accessible one byte at a time so no worrying about holding registers etc
;Assumes max 256 bytes of EEPROM
;Range Error if the address isn't in EEPROM space
EEPROMWrite:
   btfsc   EECON1, WR      ; wait for any previous write to complete
   bra   $-2      ;    \  before moving to next address
   movf   hex32_addr+2,w
   sublw   0xF0      ; EE is at 0x00F00000 in (HEX) file memory map
   iorwf   hex32_addr+3,w
   iorwf   hex32_addr+1,w   ; assumes only one page of EEPROM (256 bytes max)
   bnz   FlashEchoRE   ; invalid address so report range error
   
   movf   hex32_addr+0,w   ; location to write
   movwf   EEADR      ; set it
   rcall   ReadByte   ; read next data byte
   decf   hex32_count,f   ;
   movwf   EEDATA      ; load the EE data latch
   movlw   b'00000100'     ; Setup for EEPROM data writes
   movwf   EECON1
   rcall   StartWrite     
   rcall   IncHex24   ; bump the address (all 24 bits)
   tstfsz   hex32_count   ; reached end of this line of text?
   bra   EEPROMWrite   ; no so loop to do more
   bra   FlashEchoOK   ; done with this block

;increment working write address
IncHex24:
   clrf   WREG      ; increment the write address
   incf   hex32_addr+0,f   ; this sets CARRY on everflow
   addwfc   hex32_addr+1,f
   addwfc   hex32_addr+2,f
   return

;******************************************************************************
ShowHelp:
   rcall   UARTPutString   ;
   ;warning - check this string in memory to ensure there are no extra 0x00 bytes
;   db   "\r\nCommands:\r\n[P] Program FLASH and EEPROM\r\n[V] Verify memory\r\n"
;   db   "[W] Write EEPROM\r\n[E] Erase EEPROM\r\n[R] Read EEPROM\r\n["
;   db   "?] Help\r\n[X] Reset MCU\r\n",0
   db   "\r\nCommands:\r\n[P] Program FLASH and EEPROM \r\n"
   db   "[W] Write EEPROM\r\n[E] Erase EEPROM\r\n[R] Read EEPROM\r\n["
   db   "?] Help\r\n[X] Reset MCU\r\n",0
   return

ShowPrompt:
   rcall   UARTPutString
   db   "\r\nBL>",0
   return

ShowDone:
   rcall   UARTPutString
   db   "Done!",0
   return

;Ask for confirmation
;Only capital Y is yes
ConfirmPrompt:
   rcall   UARTPutString   ;
   db   "Are you sure? ",0
   rcall   UARTReadChar   ; Wait for user to type something
   rcall   UARTprint   ; echo user's response
   xorlw   'Y'      ; set flags
   return

;******************************************************************************
; Pause for 100 millisecs
; Timer0 will overflow in the desired time, setting the interrupt flag, if
; the correct value is preloaded into the Timer.
; This routine will run a bit long because of call-return and setup overhead.
pause100ms:
   movlw   high (-1600000/128)   ;remember, prescaler was set to 1:128
   movwf   TMR0H
   movlw   low (-1600000/128)
   movwf   TMR0L
   bcf   INTCON,TMR0IF
pausewait:
   btfss   INTCON,TMR0IF   ;wait for overflow
   bra   pausewait
;   bcf   INTCON,TMR0IF
   return

;******************************************************************************
;take 2 ascii chars pointed to by FSR2 and convert to 1 byte
;high nibble stored first
;value returned in WREG
;Z should be set if ascii byte is zero
ReadByte:
   swapf   INDF2,w      ;swap hi nibble into result
   btfsc   POSTINC2,6   ;check if in range 'A'-'F'
   addlw   0x8F      ;add correction swapf('1'-'A'+1)
   addwf   INDF2,w      ;add lo nibble
   btfsc   POSTINC2,6   ;check if in range 'A'-'F'
   addlw   0xF9      ;add correction '9'-'A'+1
   addlw   0xCD      ;adjust final result -0x33
   return

;******************************************************************************
;Wait until a character is received, return it in w-reg
;In DEBUG_MODE, characters are read from sample data stored in flash
UARTReadChar:
   CTS_GO         ; request serial characters

   bsf   LATC,LED_PIN

   btfss   PIR1,RCIF   ; wait for character
   bra   UARTReadChar   ; keep looping until character appears
   CTS_STOP      ; no more chacters please!

   bcf   LATC,LED_PIN

   btfss   RCSTA,OERR   ; test for overrun error
   bra   read_framing   ; jump if no overrun
   bcf   RCSTA,CREN   ; clear continous receive bit
   bsf   RCSTA,CREN   ; and set it again
read_framing:
   btfss   RCSTA,FERR   ; check from framing errors
   bra   GotChar      ; jump if no framing error
   movf   RCREG,w      ; read byte and discard
   bra   UARTReadChar   ; keep waiting

;Ignore '\n', otherwise return new character in w-reg
GotChar:
   movf   RCREG,w      ; grab the new character
   xorlw   '\n'      ; is it '\n'?
   bz   UARTReadChar   ; yes so ignore it
   xorlw   '\n'      ; recover character
   return

;******************************************************************************
;  UARTPutString - print w-reg as two ascii characters
UART_send_wreg:
   movwf   PRODL      ;use as temp storage
   swapf   PRODL,w
   rcall   UART_send_nyb
SER_send_half:
   movf   PRODL,w
;   rcall   send_nyb
;******************************************************************************
;convert lo nibble to an ascii character
; $0 - $f to '0' - '9','A' - 'F'  (note '9' + 8 = 'A')
UART_send_nyb:
   andlw   b'00001111'   ;strip hi bits
   addlw   -.10      ; Test to see if W < 10
   btfsc   STATUS,C   ; If yes, skip ahead
   addlw   'A'-'0'-.10   ; Add ASCII 'A', and negate next line
   addlw   '0'+.10      ; Add ASCII '0' and original 10
;   bra   UARTprint

;******************************************************************************
;wait until the serial transmit reg is empty, then send w-reg
UARTprint:
   btfss   PIR1,TXIF   ;test if TXREG is empty
   bra   UARTprint   ;loop until serial port ready
   nop
   movwf   TXREG
   return

;******************************************************************************
;  UARTPutString - send an in-line string out the serial port via Stack and TBLPTR
;
;  string must be terminated with a 00 byte and does not need
;  to be word aligned
;
;Usage:
;   rcall   UARTPutString   ;
;   db   str,0      ; put string into flash, null terminated
;
UARTPutString:
   movff   TOSH,TBLPTRH   ; copy return address to TBLPTR
   movff   TOSL,TBLPTRL   ;
   clrf    TBLPTRU      ; assume PIC with < 64KB FLASH
UARTPutNext:
   tblrd   *+      ; get in-line string character
   movf    TABLAT,W   ; last character (00)?
   bz      UARTPutExit   ; yes, exit, else
   rcall   UARTprint   ; print character
   bra     UARTPutNext   ; and do another
UARTPutExit:
   btfsc   TBLPTRL,0   ; odd address?
   tblrd   *+      ; yes, make it even (fix PC)
   movf    TBLPTRH,W   ; setup new return address
   movwf   TOSH      ;
   movf    TBLPTRL,W   ;
   movwf   TOSL      ;
   return         ;

;******************************************************************************
; Unlock and start the write or erase sequence.
   clrf   EECON1      ; try prevent accidental erase/write operations.
TableWriteWREG:
   movwf   TABLAT
   tblwt   *

StartWrite:
   movlw   0x55      ; Special
   movwf   EECON2      ; Unlock
   movlw   0xAA      ; Sequence
   movwf   EECON2
   bsf   EECON1, WR   ; Start the write
   nop         ; MCU stalls until erase/write is done
   clrf   EECON1      ; try prevent accidental erase/write operations.

   return

;******************************************************************************
; Test if all of user flash memory is blank (0xFF)
; Returns with Z set if all of user flash memory = 0xFF
; Returns with Z clear if a single bit is clear
;TestBlankFlash
;   movlw   low (FLASH_LOADER_ADDR-1)
;   movwf   TBLPTRL   ; Point TBLPTR at last byte of user flash
;   movlw   high (FLASH_LOADER_ADDR-1)
;   movwf   TBLPTRH
;
;   clrf   TBLPTRU      ; device has less than 64K flash
;TestFlashLoop
;   tblrd   *-
;   incf   TABLAT,w   ; w=0 if flash location=0xFF
;   bnz   FlashNotBlank   ; branch if Z clear
;   tstfsz   TBLPTRL      ; test if TBLPTR = 0,
;   bra   TestFlashLoop   ;   keep looping if not
;   tstfsz   TBLPTRH
;   bra   TestFlashLoop
;   tstfsz   TBLPTRU      ; don't really need this as device has <64K flash
;   bra   TestFlashLoop
;   tblrd   *      ; test location 0 also
;   incf   TABLAT,w   ; Z set if last location is blank
;FlashNotBlank
;   return

;******************************************************************************
;End of program
   END


Oh drat, the formatting looks a bit munched. Sorry. :-(

Edit: improved comments slightly describing how !CTS is 'shorted' to TX using a 470 Ohm resistor.
Thank you R. Meador for spotting a diagram error and terminology inconsistencies.
/vp
vegipete
 
Posts: 37
Joined: Sat Jul 09, 2011 7:38 pm

Re: Source Code - By Popular Request

Postby vegipete » Mon Nov 18, 2013 11:23 pm

Here is version 0.63 with software flow control:

Code: Select all
;******************************************************************************
; PIC18 BootLoader
; ©2013 by vegipete@strongedge.net
;
;   A bootloader to allow loading firmware via serial port without
;   the need for dedicated host software.
;
;   On RESET, the loader waits 10 seconds for the serial port
;   command 'Boot' to enter the bootloader. <ESC> exits quickly.
;   If so, the Flashloader runs. Otherwise, the code performs
;   a GOTO 4 which must be a vector to the start of the user
;   program.
;   If the user code space is blank, the Flashloader takes
;   control.
;   Configured for 38400,8N1
;   Uses Software flow control
;   Written by VegiPete   picprog.strongedge.net
;
;   Version Control:
;   0.1   The beginning
;   0.2
;   0.3   Working
;   0.4   Change to actual bootloader
;   0.5   Refinement, increased robustness
;   0.51
;   0.52   Forces "GOTO FLASH_LOADER_ADDR" at address 0
;   0.52-LEDC4   Uses LATC4 for indicator LED
;
;   0.6   Software Flow Control - Xon/Xoff (only needs TX & RX)
;   0.61   Make it better (ISR not possible - duh)
;   0.62   Conditional assembly for multiple processors, EEPROM > 256 bytes
;   0.63   better Range Error reporting, flow control error during EE writes
;
#define   version   "0.63"
;
;   Hardware:
;   Internal RC OSC with 4x PLL enabled
;
;   RC2 - LED - debug use only
;   
;   RC6 - TX - Serial out
;   RC7 - RX - Serial in
;
;******************************************************************************
;Resources Used:
;   Serial Port   
;******************************************************************************
   radix   dec
   errorlevel -311                 ;don't warn on HIGH() operator values >16-bits

;#define USE_DEBUG_LED
#define __18F14K22
;#define __18F26K22

; PIC18F14K22 Characteristics
#ifdef   __18F14K22
 #include   P18F14K22.inc
 #define DEVICEID      .259
 #define DEVICENAME      "PIC18F14K22 "   ;even number of characters
 #define WRITE_FLASH_BLOCKSIZE   .16
 #define ERASE_FLASH_BLOCKSIZE   .64
 #define END_FLASH      0x4000
 #define END_GPR      0x200
 #define FLASH_LOADER_ADDR   0x3A40   ;must be page erase boundary
 #define FLASH_LOADER_STR   "0x3A40" ;same thing in string form
 #define IDADDRESS      0x200000 ;location of ID in memory map
 #define CONFIGADDRESS      0x300000 ;location of CONFIG in memory map
 #define EEADDRESS      0xF00000 ;location of EE in memory map
 #define EE_LENGTH      0x100    ;number of bytes of EEPROM
#endif

#ifdef   __18F26K20
 #include   P18F26K20.inc
 #define DEVICEID      .257
 #define DEVICENAME      "PIC18F26K20 "   ;even number of characters
 #define WRITE_FLASH_BLOCKSIZE   .64
 #define ERASE_FLASH_BLOCKSIZE   .64
 #define END_FLASH      0x10000
 #define END_GPR      0xF60
 #define FLASH_LOADER_ADDR   0xFA40   ;must be page erase boundary
 #define FLASH_LOADER_STR   "0xFA40" ;same thing in string form
 #define IDADDRESS      0x200000 ;location of ID in memory map
 #define CONFIGADDRESS      0x300000 ;location of CONFIG in memory map
 #define EEADDRESS      0xF00000 ;location of EE in memory map
 #define EE_LENGTH      0x400    ;number of bytes of EEPROM
#endif

#ifdef   __18F46K20
 #include   P18F46K20.inc
 #define DEVICEID      .257   ;WRONG value, but not important
 #define DEVICENAME      "PIC18F46K20 "   ;even number of characters
 #define WRITE_FLASH_BLOCKSIZE   .64
 #define ERASE_FLASH_BLOCKSIZE   .64
 #define END_FLASH      0x10000
 #define END_GPR      0xF60
 #define FLASH_LOADER_ADDR   0xFA40   ;must be page erase boundary
 #define FLASH_LOADER_STR   "0xFA40" ;same thing in string form
 #define IDADDRESS      0x200000 ;location of ID in memory map
 #define CONFIGADDRESS      0x300000 ;location of CONFIG in memory map
 #define EEADDRESS      0xF00000 ;location of EE in memory map
 #define EE_LENGTH      0x400    ;number of bytes of EEPROM
#endif

#ifdef   __18F26K22
 #include   P18F26K22.inc
 #define DEVICEID      .674
 #define DEVICENAME      "PIC18F26K22 "   ;even number of characters
 #define WRITE_FLASH_BLOCKSIZE   .64
 #define ERASE_FLASH_BLOCKSIZE   .64
 #define END_FLASH      0x10000
 #define END_GPR      0xF38
 #define FLASH_LOADER_ADDR   0xFA40   ;must be page erase boundary
 #define FLASH_LOADER_STR   "0xFA40" ;same thing in string form
 #define IDADDRESS      0x200000 ;location of ID in memory map
 #define CONFIGADDRESS      0x300000 ;location of CONFIG in memory map
 #define EEADDRESS      0xF00000 ;location of EE in memory map
 #define EE_LENGTH      0x400    ;number of bytes of EEPROM
#endif

;******************************************************************************
;
; CONFIGURATION WORD SETUP
;
; The 'CONFIG' directive is used to embed the configuration word within the
; .asm file. The lables following the directive are located in the respective
; .inc file.  See the data sheet for additional information on configuration
; word settings.
;
;******************************************************************************

     CONFIG FOSC = IRC, PLLEN = ON ;14K22
     ;CONFIG FOSC = INTIO67, PLLCFG = ON ;26K22
     CONFIG PCLKEN = ON, FCMEN = OFF, IESO = OFF ;14K22
     ;CONFIG PRICLKEN = ON, FCMEN = OFF, IESO = OFF ;26K22
     CONFIG PWRTEN = ON, BOREN = OFF, BORV = 30 ;14K22
     ;CONFIG PWRTEN = ON, BOREN = OFF, BORV = 285 ;26K22
     CONFIG WDTEN = OFF, WDTPS = 1
     CONFIG MCLRE = ON, HFOFST = OFF ;14K22
     ;CONFIG MCLRE = EXTMCLR, HFOFST = OFF ;26K22
     CONFIG STVREN = OFF, LVP = OFF, XINST = OFF
     CONFIG CP0 = OFF, CP1 = OFF
     CONFIG CPB = OFF, CPD = OFF
     CONFIG WRT0 = OFF, WRT1 = OFF
     CONFIG WRTB = OFF, WRTC = OFF, WRTD = OFF
     CONFIG EBTR0 = OFF, EBTR1 = OFF
     CONFIG EBTRB = OFF

;******************************************************************************
#define   XON      17
#define   XOFF      19

#define LED_TRIS   TRISC   ; LED direction register
#define LED_LAT      LATC   ; LED latch
#define   LED_PIN      2   ; LED pin number

   CBLOCK   0x000
   OSCCON_save   :1   ; preserve OSCCON during startup tests
   hex32_length   :1   ; number of data bytes in record
   hex32_type   :1   ; type of record
   hex32_chksum   :1   ; running checksum calculation
   hex32_count   :1   ; loop counter
   hex32_addr   :4   ; address long word
   start_count   :1   ; counter to track first 4 bytes of flash
   first_start   :1   ; track 1st boot for one time message
   flags      :1   ; misc useful flags
   user_cmd   :1   ; character typed by user
   temp_var   :4   ; general counter
   ENDC

WRITEDATA   EQU   0   ; set when data waiting for write to flash
RANGERR      EQU   1   ; set if write addr too high
WRITEEEPROM   EQU   2   ; set when EEPROM data found

   CBLOCK   0x100
   rx_buffer
   ENDC

;******************************************************************************
; Note: each line of DE data must have an even number of bytes to avoid 0 padding.
;EEPROM data
   ORG   0xF00000
   DE   "The first line of code starting at the reset vector,"
   DE   " location 0, must be \"GOTO 0x0004\". Put "
   DE   "\"GOTO USER_START\" at location 4. Serial port settings:"
   DE   " 38400 Baud, 8N1, software (XON/XOFF) flow control. "

#ifdef   EEADRH
   ORG   0xF00100
   DE   "Something for Bank 1"
   ORG   0xF00200
   DE   "More stuff to throw into the second bank."
   ORG   0xF00300
   DE   "Some other stuff that will be fitted into the last and final bank."
#endif

;******************************************************************************
;Reset vector
; This code will start executing when a reset occurs.
   ORG   0x0000     ; start address 0
   goto   FlashLoaderBoot   ;
   goto   DummyUserProg   ; no user program yet

;This code acts as a preliminary user program.
;It displays an introduction message and then
;periodically calls the bootloader.
DummyUserProg:
   clrf   INTCON      ; make sure interupts are off
   movlb   0x0F      ; set BSR for banked SFRs
   rcall   FindRunInit

;set up Timer 0
   movlw   b'10010110'   ; On, 16 bit, Internal, 1:128 prescale
   movwf   T0CON

   call   pause100ms   ; wait for serial port to settle. (Why?)
   
   movlw   0x55
   movwf   first_start   ; set variable so we know PIC has aleady started
   
   call   UARTPutString   ;
   db   "\r\n\nNo user program yet.\r\n\n"
   db   "PIC18 BootLoader v" version
   db   " installed at " FLASH_LOADER_STR
   db   ".\r\n©2013 by vegipete@strongedge.net\r\n\n"
   db   "The start of the user program must be:\r\n"
   db   " org  0\r\n goto 4    ;Bootloader will"
   db   " automatically change the target address"
   db   "\r\n goto START_USER_PROG \r\n\n",0

   call   pause100ms   ; waste some time
   call   pause100ms   ; waste some time
   call   pause100ms   ; waste some time
   call   pause100ms   ; waste some time
   call   pause100ms   ; waste some time
   call   pause100ms   ; waste some time

   reset

;
;******************************************************************************
;This routine will find and call the processor init routine used by
;the bootloader. The bootloader will have written its address as a
;'GOTO' instruction at location 0x0000. The init routine starts 2 bytes
;after this address and ends with a 'RETURN'
;########  WARNING: no interrupts allowed during this code!
;The bootloader init code sets the oscillator, sets EUSART for 38400 baud,
;configures the serial port pins and enables Timer 0 with max prescale.
FindRunInit:
   push         ; make space on stack for init routine address
   clrf   TBLPTRL   ;######## WARNING: no interrupts allowed during this code!
   clrf   TBLPTRH
   clrf   TBLPTRU      ; point to start of flash
   tblrd   *+      ; get target address word low
   movf   TABLAT,w
   movwf   TOSL      ; save it
   tblrd   *+      ; op code for 'GOTO' - ignore
   tblrd   *+      ; get target address word high
   movf   TABLAT,w
   movwf   TOSH
;   tblrd   *+      ; assume <64k
   infsnz   TOSL,f      ; want next word for init routine start
   incf   TOSH,f      ; handle any carry
   bcf   STATUS,C
   rlcf   TOSL,f      ; double word value to get byte value
   rlcf   TOSH,f      ; high byte also
   clrf   TOSU      ; must be 0 for <64k flash
   return         ; simulated 'GOTO'

;******************************************************************************
;******************************************************************************
;*
;*   Flash Loader
;*
;******************************************************************************
;******************************************************************************
;Flash loading program  goes into top of memory

   ORG   FLASH_LOADER_ADDR
;Perform a minimal hardware init then test whether we should run user code
FlashLoaderBoot:
   bra   FlashLoaderStart
;******************************************************************************
;Initialize hardware
;This routine can be called by the user application if desired.
;The following sub-routine will calculate the init routine location and call it.
;findinit:
;   push         ; make space on stack for init routine address
;   clrf   TBLPTRL      ;WARNING: no interrupts allowed during this code!
;   clrf   TBLPTRH
;   clrf   TBLPTRU      ; point to start of flash
;   tblrd   *+      ; get target address word low
;   movf   TABLAT,w
;   movwf   TOSL      ; save it
;   tblrd   *+      ; op code for 'GOTO' - ignore
;   tblrd   *+      ; get target address word high
;   movf   TABLAT,w
;   movwf   TOSH
;;   tblrd   *+      ; assume <64k - ignore
;   infsnz   TOSL,f      ; want next word for init routine start
;   incf   TOSH,f      ; handle any carry
;   bcf   STATUS,C
;   rlcf   TOSL,f      ; double word value to get byte value
;   rlcf   TOSH,f      ; high byte also
;   clrf   TOSU      ; must be 0 for <64k flash
;   return         ; simulated 'GOTO'

FlashLoaderInit:
   clrf   INTCON      ; make sure interupts are off
;   clrf   BSR      ; make sure bank 0 is selected
   movlb   0x0F      ; set BSR for banked SFRs

   movff   OSCCON,OSCCON_save
   movlw   b'01111000'   ; sleep, 16MHz, R, R, Primary Clock
   movwf   OSCCON      ; set oscillator
;   bsf   OSCTUNE,PLLEN   ; 26K22 kick into overdrive

;set up Timer 0
;   movlw   b'10010110'   ; On, 16 bit, Internal, 1:128 prescale
   movlw   b'10010111'   ; On, 16 bit, Internal, 1:256 prescale
   movwf   T0CON

;set up PortC,2 for LED output
#ifdef   USE_DEBUG_LED
   bcf   ANSELC,ANSC2   ; 26K22 make RC2 pin digital (in banked memory)
   bsf   LATC,2      ; LED on
   bcf   TRISC,2      ; make it output
#endif

;set up serial port (USART)
;   setf   TRISB      ; All bits input so (E)USART can control 'em
;   bcf   ANSELC,ANSC6   ; 26K22 make TX pin digital (in banked memory)
;   bcf   ANSELC,ANSC7   ; 26K22 make RX pin digital
   bcf   ANSELH,ANS11   ; 14K22 make RX pin digital
; BAUDCON reset value is fine
;   movlw   b'00000000'
      ; 0.......    Auto-Baud Acquisition Rollover Status bit
      ; .0......    (read only) 1 = Receive operation is Idle
      ; ..0..... RXDTP 0 = Receive data (RX) is not inverted (active-high)
      ; ...0.... TXCKP 0 = Idle state for transmit (TX) is a high level
      ; ....0... BRG16 1 = 16-bit Baud Rate Generator – SPBRGH and SPBRG
      ; .....0..    Unimplemented: Read as ‘0’
      ; ......0.    Wake-up Enable bit
      ; .......0    0 = Baud rate measurement disabled or completed
   clrf   BAUDCON

   clrf   SPBRGH       ;EUSART Baud Rate Generator Register High Byte
;   movlw   51      ;19k2 @ 16MHz (16MHz x4 PLL / 4 clocks/instruction)
   movlw   25      ;38k4 @ 16MHz (16MHz x4 PLL / 4 clocks/instruction)
   movwf   SPBRG      ;EUSART Baud Rate Generator Register Low Byte

   movlw   b'00100000'
      ; 0....... CSRC  don't care
      ; .0...... TX9   0 = Selects 8-bit transmission
      ; ..1..... TXEN  1 = Transmit enabled
      ; ...0.... SYNC  0 = Asynchronous mode
      ; ....0... SENDB Send Break Character bit
      ; .....0.. BRGH  1 = High speed BRGH: High Baud Rate Select bit
      ; ......0. TRMT  TRMT: Transmit Shift Register Status bit (1=TSR empty)
      ; .......0 TX9D  9th Bit of Transmit Data
   movwf   TXSTA
;   bsf   TXSTA,TXEN

   movlw   b'00010000'
      ; 0....... SPEN  0 = Serial port NOT YET enabled
      ; .0...... RX9   0 = Selects 8-bit transmission
      ; ..0..... SREN  don't care
      ; ...1.... CREN  1 = Enables receiver
      ; ....0... ADDEN don't care
      ; .....0.. FERR  1 = Framing error
      ; ......0. OERR  1 = Overrun error
      ; .......0 RX9   read only, don't care
   movwf   RCSTA

   bsf   RCSTA,SPEN   ; enable serial port
   return

;******************************************************************************
FlashLoaderStart:
   rcall   FlashLoaderInit

#ifdef   USE_DEBUG_LED
   bcf   TRISC,LED_PIN   ; LED pin as output
   bsf   LATC,LED_PIN   ; LED on - no error yet
#endif

   rcall   pause100ms   ; wait for serial port to settle. (Why?)

;Pause at startup to give user some time to type "Boot" to access bootloader
   rcall   UARTPutString   ;
   db   "\r\n\n\nType \"Boot\" to start bootloader.", XON, 0

   movlw   11      ; startup countdown
   movwf   temp_var
RestartString:
   lfsr   1,rx_buffer-1
NewStartChar:
   movf   PREINC1,f   ; legal character already in buffer so advance pointer
BootLoop:
   btfss   INTCON,TMR0IF   ; T0 time out yet?
   bra   CheckChar   ; no so check for serial char receive
   movlw   high (-16000000/256)   ; remember, prescaler was set to 1:256
   movwf   TMR0H
   movlw   low (-16000000/256)   ; 1 second timeout
   movwf   TMR0L
   bcf   INTCON,TMR0IF
   dcfsnz   temp_var,f
   bra   RunUserCode
   decf   temp_var,w
   rcall   UART_send_nyb   ; send count down character
CheckChar:
   btfss   PIR1,RCIF   ; bit SET when character is available
   bra   BootLoop
   rcall   UARTReadChar   ; fetch character - returns 0 if framing error
   movwf   INDF1      ; put new character into buffer
   bz   BootLoop   ; no valid character so keep looping
   xorlw   27      ; <ESC> key - run user code NOW
   bz   RunUserCode
   xorlw   27^'B'
   bz   NewStartChar   ; add 'B'
   xorlw   'B'^'o'
   bz   NewStartChar   ; or 'o' to string collected so far
   xorlw   'o'^'t'
   bnz   RestartString   ; invalid character so restart access string
   movf   POSTDEC1,w   ; received a 't' so look for already received 'Boo'
   movf   POSTDEC1,w   ; grab 1st 'o'
   addwf   POSTDEC1,w   ; add second 'o'
   addlw   -('o'+'o')   ; subtract special literal, expect zero
   bnz   RestartString   ; not "oo" so restart access string
   movf   INDF1,w
   xorlw   'B'
   bnz   RestartString
   bra   FlashLoaderActive

RunUserCode:
;   rcall   UARTPutString   ;
;   db   XOFF,"\r\n\nRunning user code... ",0
   setf   WPUB      ; Restore special function registers as close
   setf   INTCON2      ;   to reset condition as reasonably possible
   setf   ANSELH      ; 14K22
;   setf   ANSELC      ; 26K22
   setf   TRISB      ; 
   setf   TRISC      ; 
   clrf   TXSTA
   clrf   RCSTA
   bcf   OSCTUNE,PLLEN   ;26K22 PLL off - default at reset
   movff   OSCCON_save,OSCCON
;   reset
   goto   4      ; Run user code

FlashLoaderActive:
;set up Timer 0
   movlw   b'10010110'   ; On, 16 bit, Internal, 1:128 prescale
   movwf   T0CON

HelpRequested:
   rcall   ShowHelp
NewPrompt:
   rcall   ShowPrompt
   clrf   user_cmd
InputLoop:
   rcall   UARTReadChar   ; Wait for user to type something
   bz   InputLoop
   xorlw   '\r'      ; Enter Key?
   bz   EvalUserCmd   ; yes so evaluate what, if anything, was received
   xorlw   '\r'      ; recover character
   movwf   user_cmd   ; save it
   rcall   UARTprint   ; echo it
   bra   InputLoop

EvalUserCmd:
   movf   user_cmd,w
   bz   NewPrompt   ; nothing received, go back and wait
   xorlw   '?'
   bz   HelpRequested
   iorlw   b'00100000'   ; convert possible lowercase to upper
   xorlw   'P'^'?'
   bz   ProgramHEX
;   xorlw   'V'^'P'
;   bz   VerifyHEX
   xorlw   'W'^'P'
   bz   WriteEEPROM
   xorlw   'E'^'W'
   bz   EraseEEPROM
   xorlw   'R'^'E'
   bz   ReadEEPROM

   xorlw   'X'^'R'
;   bz   RunUserCode
   btfsc   STATUS,Z
   reset
ReportError:
   rcall   UARTPutString   ;
   db   XOFF,"\r\n\aSyntax Error",0   ;'\a' is bell character
   bra   NewPrompt   ; Unrecognized command so keep looking

EraseEEPROM:
   rcall   UARTPutString   ;
   db   XOFF,"\r\nErase EEPROM",0
   rcall   ConfirmPrompt
   bnz   CancelPrompt   ; aborted so return to prompt
   rcall   UARTPutString   ;
   db   XOFF,"\r\nErasing EEPROM... ",0

;some conditional assemble to handle devices with >256 bytes EEPROM
#ifdef   EEADRH
   clrf   EEADRH
#endif
   setf   EEDATA      ; 0xFF is erased value
   clrf   EEADR      ; start of EEPROM
EraseEEloop:
   movlw   b'00000100'     ; Setup for EEPROM data writes
   movwf   EECON1
   rcall   StartWrite
EraseEEwait:
   btfsc   EECON1, WR      ; wait for write to complete
   bra   EraseEEwait   ;    \  before moving to next address
   incfsz   EEADR,f      ; finished all 256 bytes yet?
   bra   EraseEEloop   ; keep looping
#ifdef   EEADRH
   incf   EEADRH,f   ; doesn't set flags as expected
   tstfsz   EEADRH      ; Has EEADRH rolled back to 0x00?
   bra   EraseEEloop   ; no so keep looping
#endif
   rcall   ShowDone
   bra   NewPrompt

WriteEEPROM:
   bsf   flags,WRITEEEPROM
    bra   StartEEwrite

VerifyHEX:
CancelPrompt:
   rcall   UARTPutString   ;
   db   XOFF,"\r\nCanceled",0   ;
   bra   NewPrompt   ; canceled command so keep looking

; read all of EEPROM and dump it out the serial port
ReadEEPROM:
   rcall   UARTPutString   ;
   db   XOFF,"\r\nEEPROM:",0   ;
#ifdef   EEADRH
   clrf   EEADRH
#endif
   clrf   EEADR      ; start of EEPROM
EE_Readloop:
   movlw   0x0F
   andwf   EEADR,w      ; if low nibble is zero, start a new line
   bnz   EE_ReadNext
   movlw   '\n'
   rcall   UARTprint
   movlw   '\r'
   rcall   UARTprint
#ifdef   EEADRH
   movf   EEADRH,w   ; grab EE page number
   rcall   UART_send_wreg
#endif
   movf   EEADR,w
   rcall   UART_send_wreg
   movlw   ':'
   rcall   UARTprint
EE_ReadNext:
   bcf   EECON1, EEPGD   ; Point to DATA memory
   bcf   EECON1, CFGS   ; Access EEPROM
   bsf   EECON1, RD   ; EEPROM read
   movf   EEDATA,w   ; grab value
   rcall   UART_send_wreg
   movlw   ' '
   rcall   UARTprint
   incfsz   EEADR,f      ; keep going until EEADR rolls back to zero
   bra   EE_Readloop   ; loop back for more
#ifdef   EEADRH
   incf   EEADRH,f   ; next page - EEADRH rolls from 0x03 to 0x00
   tstfsz   EEADRH      ; need this because INCF doesn't set flags
   bra   EE_Readloop   ; loop back for more
#endif
   bra   NewPrompt

ProgramHEX:
   rcall   UARTPutString   ;
   db   XOFF,"\r\nReprogram Flash",0
   rcall   ConfirmPrompt
   bnz   CancelPrompt   ; aborted so return to prompt
   rcall   UARTPutString   ;
   db   XOFF,"\r\nErasing Flash...",0

;Erase flash starting from high end working down to 0
;set TBLPTR to start of last block _before_ FlashLoader
   movlw   low (FLASH_LOADER_ADDR-ERASE_FLASH_BLOCKSIZE)
   movwf   TBLPTRL

   movlw   high (FLASH_LOADER_ADDR-ERASE_FLASH_BLOCKSIZE)
   movwf   TBLPTRH

   clrf   TBLPTRU      ; device has less than 64K flash
EraseFlashLoop:
   movlw   b'10010100'   ; setup FLASH erase
   movwf   EECON1
   rcall   StartWrite   ; erase the block
;adjust TBLPTR to next lower block of flash
   movlw   ERASE_FLASH_BLOCKSIZE
   subwf   TBLPTRL, F
   clrf   WREG
   subwfb   TBLPTRH, F
   subwfb   TBLPTRU, F
   bnn   EraseFlashLoop   ; any more blocks?

   rcall   ShowDone

   clrf   flags      ; all clear to start writing flash

StartEEwrite:
   rcall   UARTPutString   ;
   db   "\r\nWaiting for " DEVICENAME
   db   "Hex32 file...\r",0

;Prepare some pointers for receiving a HEX file and writing it to flash/eeprom
   movlw   4      ; first 4 bytes must be
   movwf   start_count   ;   changed to "GOTO FLASH_LOADER_ADDR"
   clrf   hex32_addr+0
   clrf   hex32_addr+1
   clrf   hex32_addr+2
   clrf   hex32_addr+3
   clrf   TBLPTRL
   clrf   TBLPTRH
   clrf   TBLPTRU      ; point to flash start to begin

FlashRx:
   movlw   '\n'
   rcall   UARTprint
   movlw   XON      ; ready to start receiving lines of HEX file
   rcall   UARTprint
   lfsr   2,rx_buffer   ; point to serial receive buffer
;read characters until the buffer fills, EOL is found or CTRL-C is detected
FlashRxLoop:
   rcall   UARTReadChar
   bz   FlashRxLoop

   xorlw   3      ; is it CTRL-C?
   bz   CancelPrompt

   xorlw   3      ; recover character
   movwf   POSTINC2   ; put it in buffer
; The following lines have some tricky subtleties to test for buffer overflow.
; If FSR2L = 0, the character just written is the last one to fit in the buffer
; and FSR2 has rolled from 0x01FF to 0x0200. If the character just added to the
; buffer is not \r then the current line is not complete and therefore must
; overflow because at least one more character is required.
   xorlw   '\r'      ; EOL? - set flags
   tstfsz   FSR2L      ; is receive buffer full?
   bnz   FlashRxLoop   ; no EOL, buffer not full so keep looking
   bnz   FlashEchoNG   ; no EOL, buffer full so error on line

;got a line of hex file in the buffer
;could involve a memory write which stalls the MPU so we must ensure the
;XOFF character has actually been completely transmitted
   movlw   XOFF      ; stop serial characters for a while
   rcall   UARTprint
SWhandshake:
   btfss   TXSTA,TRMT   ; bit goes HI when tx finished
   bra   SWhandshake   ; loop until XOFF has gone

;step 1 - confirm line starts with ":"
   lfsr   2,rx_buffer   ; point to start of buffer
   movf   POSTINC2,w   ; grab first character
   xorlw   ':'      ; must be a colon
   bnz   FlashEchoNG   ; error on line

;step 2 - test if checksum is correct
   rcall   ReadByte   ; read number of data bytes
   movwf   hex32_chksum   ; start the checksum
   movwf   hex32_count   ; initial loop counter
   incf   hex32_count,f   ; adjust loop counter for end condition
   rcall   ReadByte   ; read addr high
   addwf   hex32_chksum,f   ; add it to checksum
   rcall   ReadByte   ; read addr low
   addwf   hex32_chksum,f   ; add it to checksum
   rcall   ReadByte   ; read record type
   addwf   hex32_chksum,f   ; add it to checksum
F_Calc_Chksum:
   dcfsnz   hex32_count,f   ; test for more bytes to read
   bra   F_Read_Chksum
   rcall   ReadByte   ; read (another) data bye
   addwf   hex32_chksum,f   ; add it to checksum
   bra   F_Calc_Chksum   ; keep looping for more

F_Read_Chksum:
   rcall   ReadByte   ; read the checksum
   addwf   hex32_chksum,f   ; add to calculated checksum - result must be zero
   bnz   FlashEchoNG   ; branch if checksum doesn't match

;step 3 - evaluate line
   lfsr   2,rx_buffer+1   ; point to byte count
   rcall   ReadByte   ; read number of data bytes
   movwf   hex32_count

   rcall   ReadByte   ; read address high byte
   movwf   hex32_addr+1
   rcall   ReadByte   ; read address low byte
   movwf   hex32_addr+0

   rcall   ReadByte   ; read block type 00 - normal, 01 - end, 04 - high word
   movwf   hex32_type
   bz   FlashLoadData   ; 00 - found a line of regular data bytes
   decf   WREG,w
   bz   FlashEndFile   ; 01 - found end of file
   decf   WREG,w
   bz   FlashEchoNG   ; 02 - unrecognized
   decf   WREG,w
   bz   FlashEchoNG   ; 03 - unrecognized
   decf   WREG,w
   bz   FlashSetHigh   ; 04 - set high address
   bra   FlashEchoNG

;Echo the line back out the serial port with a result code in front
;"OK"   - accepted line and written to flash
;"RE"   - Range Error - tried to write into flashloader or higher
;"SQ"   - SeQuence error - hex32 records are not in ascending order
;"NG"   - No Good - hex32 record fails checksum or has other flaws
FlashEchoOK:
   movlw   'O'
   rcall   UARTprint
   movlw   'K'
   bra   FlashEcho
FlashEchoNG:
   movlw   XOFF      ; need this one to handle buffer overflow
   rcall   UARTprint
;   rcall   UARTprint
   movlw   'N'
   rcall   UARTprint
   movlw   'G'
   bra   FlashEcho
FlashEchoSQ:
   movlw   'S'
   rcall   UARTprint
   movlw   'Q'
   bra   FlashEcho
FlashEchoRE:
   bsf   flags,RANGERR   ; block type 4 range error
   movlw   'R'
   rcall   UARTprint
   movlw   'E'
FlashEcho:
   rcall   UARTprint
   lfsr   2,rx_buffer   ; point to start of buffer
FlashEchoLoop:
   movf   POSTINC2,w   ; grab next character in buffer
   xorlw   XON      ; ignore XON character
   bz   FlashEchoLoop
   xorlw   XON
   rcall   UARTprint   ; echo it
   xorlw   '\r'      ; EOL?
   tstfsz   FSR2L      ; test for buffer overflow, flags unaffected
   bnz   FlashEchoLoop   ; no, more to do
   bz   FlashEchoDone   
   movlw   '\r'
   rcall   UARTprint
FlashEchoDone:
   bra   FlashRx

;Block type 04 - set high address
;If the high byte is not zero, an impossible location has been requested - Range Error
;If the low byte = 0x20, an ID location has been requested - not supported: Range Error
;If the low byte = 0x30, a CONFIG location has been requested - not supported: Range Error
;If the low byte = 0xF0, an EEPROM location has been requested
;For a device with <= 64K of flash,
;           any high address other than 0x00F0 (or 0x0000) is a Range Error
FlashSetHigh:
   decf   hex32_count,w
   decfsz   WREG,w      ; make sure block length is 2
   bra   FlashEchoNG   ; length not 2 so it's an error
   rcall   ReadByte   ; read upper address high byte - must be zero
   movwf   hex32_addr+3
   bnz   FlashEchoRE   ; indicate range error
   rcall   ReadByte   ; read upper address low byte: 0x00=FLASH, 0xF0=EE
   movwf   hex32_addr+2
   bz   FlashEchoOK   ; done with this block - writing FLASH
;   sublw   upper EEADDRESS
   xorlw   upper EEADDRESS
   bnz   FlashEchoRE   ; indicate range error
   bsf   flags,WRITEEEPROM
   bra   FlashEchoOK   ; done with this block - writing EEPROM

;block type 01 - end of file
;Write any leftover bytes to flash.
FlashEndFile:
   btfss   flags,WRITEDATA   ; is there anything left in the holding registers?
   bra   DataAllWritten   ; done if not
FlushToFlash:
   setf   TABLAT      ; pad holding registers with 0xFF
   tblwt   *+      ; post increment
   movf   TBLPTRL, w   ; have we crossed into the next write block?
   andlw   (WRITE_FLASH_BLOCKSIZE-1)
   bnz   FlushToFlash   ; loop for more
   rcall   WriteThisBlock   ; initiate a page write

DataAllWritten:
   btfsc   EECON1, WR      ; wait for any previous write to complete
   bra   $-2      ;    \  before moving to next address
   clrf   EECON1
   rcall   UARTPutString   ;
   db   "Finished!\r\n",0
   bra   NewPrompt

;Block type 00 - normal line of data
;If the running data address matches the TBLPTR, add the next byte to the write buffer.
; Otherwise, add 0xFF to the write buffer until the TBLPTR catches up.
; THIS REQUIRES THAT THE HEX32 FILE DATA BE IN ASCENDING ORDER. Writes
; scattered all over the Hex32 file won't work correctly.
; MPLAB seems to generate Hex32 files that meet this requirement.
;Fill write buffer with WRITE_FLASH_BLOCKSIZE bytes of data.
;When the write buffer is full, write the bytes to flash.
;Current block may have exactly enough bytes, or too many or too few.
;If RANGERR is set, current block can't be written, unless...
;If WRITEDATA is also set, the holding registers must be filled and written to flash
FlashLoadData:
   btfsc   flags,WRITEEEPROM
   bra   EEPROMWrite   ; branch if into EEPROM space
   btfss   flags,RANGERR   ; has a range error been found?
   bra   FlashLoadLoop   ; no so proceed
   btfss   flags,WRITEDATA   ; is there data in the holding registers?
   bra   FlashEchoRE   ; no so indicate range error
   bra   FlashAddrNoMatch

FlashLoadLoop:
   ;if TBLPTR != hex32_addr then
   ;   TABLAT = 0xFF
   ;else
   ;   TABLAT = next byte from input text
   ;   set flag indicating real data
   ;   hex32_addr++
   ;
   ;tblwt *+ (add another byte to holding registers
   ;
   ;if TABLPTR into next write block
   ;   write this block
   ;   clear real data flag

   movf   hex32_addr+0,w
   subwf   TBLPTRL,w   ; WREG = TBLPTRL - hex32_addr+0
   movwf   PRODL      ; store each comparison value
   movf   hex32_addr+1,w
   subwfb   TBLPTRH,w
   iorwf   PRODL,f
   movf   hex32_addr+2,w
   subwfb   TBLPTRU,w
   bnc   FlashAddrNoMatch ;Branch if TBLPTR < FLASH_LOADER_ADDR - fill with 0xFF
   iorwf   PRODL,f
   bnz   FlashEchoSQ   ; Branch if TBLPTR > FLASH_LOADER_ADDR - sequence error

   rcall   ReadByte   ; read next data byte

;The first 4 bytes must be "GOTO FLASH_LOADER_ADDR" or else
;bootloader access will be lost.
   tstfsz   start_count   ; Are we still in the first 4 bytes of FLASH?
   rcall   ChangeByte   ; yes so change byte
   movwf   TABLAT      ; load the holding register
   decf   hex32_count,f   ;
   bsf   flags,WRITEDATA   ; got something to write to flash
   rcall   IncHex24
   bra   FillHolding

;return bytes that represent "goto FLASH_LOADER_ADDR"
ChangeByte:
   decf   start_count,f   ; decrement counter
   movf   PCL,w      ; load PCLATH and PCLATU
   rlncf   start_count,w   ; double the count for computed goto
   addwf   PCL,f      ; MANUALLY confirm no page boundary cross
   retlw   (upper (FLASH_LOADER_ADDR >> 1)) | 0xF0
   retlw   high (FLASH_LOADER_ADDR >> 1)
   retlw   0xEF      ; OP code for 'GOTO'
   retlw   low (FLASH_LOADER_ADDR >> 1)

FlashAddrNoMatch:
   setf   TABLAT      ; pad holding registers with 0xFF
FillHolding:
   ;test if TBLPTR is too high - protect flash loader code
   movlw   low FLASH_LOADER_ADDR
   subwf   TBLPTRL,w
   movlw   high FLASH_LOADER_ADDR
   subwfb   TBLPTRH,w
   movlw   upper FLASH_LOADER_ADDR
   subwfb   TBLPTRU,w
   btfss   STATUS,N   ; N set if TBLPTR < FLASH_LOADER_ADDR
   bsf   flags,RANGERR   ; set RANGE ERROR, stays set forever

   tblwt   *+      ; post increment
   movf   TBLPTRL, w   ; have we crossed into the next write block?
   andlw   (WRITE_FLASH_BLOCKSIZE-1)
   btfsc   STATUS,Z   ; Z set if buffer is full
   rcall   WriteThisBlock   ; holding registers are full so write them

   tstfsz   hex32_count   ; reached end of this line of text?
   bra   FlashLoadData   ;(FlashLoadLoop?)   ; no so loop to do more
   btfss   flags,RANGERR
   bra   FlashEchoOK   ; done with this block
   bra   FlashEchoRE   ; indicate range error

;The holding registers are full so write them to flash
;The registers could also be completely empty, so write nothing
WriteThisBlock:
   btfsc   flags,RANGERR   ; is (possible) data in range?
   bra   WriteNoData   ; done if not
   btfss   flags,WRITEDATA   ; is there actually data to write?
   bra   WriteNoData   ; done if not

   tblrd   *-      ; Point back into the block to write data
   movlw   b'10000100'   ; Setup FLASH writes
   movwf   EECON1
   rcall   StartWrite   ; initiate a page write
   tblrd   *+      ; Restore pointer for loading holding registers with next block
WriteNoData:
   bcf   flags,WRITEDATA   ;
   clrf   EECON1      ; inhibit writes
   return

;Write data to EEPROM
;EEPROM is accessible one byte at a time so no worrying about holding registers etc
;EE_LENGTH specifies amount of EEPROM
;Range Error if the address isn't in EEPROM space
EEPROMWrite:
   btfsc   EECON1, WR      ; wait for any previous write to complete
   bra   $-2      ;    \  before moving to next address
   movf   hex32_addr+2,w
   sublw   0xF0      ; EE is at 0x00F00000 in (HEX) file memory map
   iorwf   hex32_addr+3,w
   bnz   FlashEchoRE   ; invalid address so report range error
   movf   hex32_addr+1,w   ; check possible high pages
#ifdef   EEADRH
   movwf   EEADRH      ; set location to write - high byte
#endif
   andlw   ~(high(EE_LENGTH-1))
   bnz   FlashEchoRE   ; invalid address so report range error

   movf   hex32_addr+0,w   ; location to write
   movwf   EEADR      ; set it
   rcall   ReadByte   ; read next data byte
   movwf   EEDATA      ; load the EE data latch
   movlw   b'00000100'     ; Setup for EEPROM data writes
   movwf   EECON1
   rcall   StartWrite     
   decf   hex32_count,f   ;
   rcall   IncHex24   ; bump the address (all 24 bits)
   tstfsz   hex32_count   ; reached end of this line of text?
   bra   EEPROMWrite   ; no so loop to do more
   bra   FlashEchoOK   ; done with this block

;increment working write address
IncHex24:
   clrf   WREG      ; increment the write address
   incf   hex32_addr+0,f   ; this sets CARRY on everflow
   addwfc   hex32_addr+1,f
   addwfc   hex32_addr+2,f
   return

;******************************************************************************
ShowHelp:
   rcall   UARTPutString   ;
   db   XOFF,"\r\n\n" DEVICENAME "BootLoader v" version
   db   " installed at " FLASH_LOADER_STR
   db   ".\r\n©2013 by vegipete@strongedge.net\r\n "
   db   "Commands:\r\n[P] Program FLASH and EEPROM \r\n"
   db   "[W] Write EEPROM\r\n[E] Erase EEPROM\r\n[R] Read EEPROM\r\n["
   db   "?] Help\r\n[X] Reset MCU\r\n",0
   return

ShowPrompt:
   rcall   UARTPutString
   db   XOFF,"\r\nBL>",XON,0
   return

ShowDone:
   rcall   UARTPutString
   db   XOFF,"Done!",0
   return

;Ask for confirmation
;Only capital Y is yes
ConfirmPrompt:
   rcall   UARTPutString   ;
   db   " - Are you sure?",XON,0
ConfPWait:
   rcall   UARTReadChar   ; Wait for user to type something
   bz   ConfPWait
   rcall   UARTprint   ; echo user's response
   xorlw   'Y'      ; set flags
   return

;******************************************************************************
; Pause for 100 millisecs
; Timer0 will overflow in the desired time, setting the interrupt flag, if
; the correct value is preloaded into the Timer.
; This routine will run a bit long because of call-return and setup overhead.
pause100ms:
   movlw   high (-1600000/256)   ;remember, prescaler was set to 1:256
   movwf   TMR0H
   movlw   low (-1600000/256)
   movwf   TMR0L
   bcf   INTCON,TMR0IF
pausewait:
   btfss   INTCON,TMR0IF   ; wait for overflow
   bra   pausewait
;   bcf   INTCON,TMR0IF
   return
   
;******************************************************************************
;take 2 ascii chars pointed to by FSR2 and convert to 1 byte
;high nibble stored first
;value returned in WREG
;Z should be set if ascii byte is zero
ReadByte:
   swapf   INDF2,w      ;swap hi nibble into result
   btfsc   POSTINC2,6   ;check if in range 'A'-'F'
   addlw   0x8F      ;add correction swapf('1'-'A'+1)
   addwf   INDF2,w      ;add lo nibble
   btfsc   POSTINC2,6   ;check if in range 'A'-'F'
   addlw   0xF9      ;add correction '9'-'A'+1
   addlw   0xCD      ;adjust final result -0x33
   return

;******************************************************************************
;Wait until a character is received, return it in w-reg
;If there is a framing error, return 0x00
UARTReadChar:
   btfss   PIR1,RCIF   ; wait for character
   bra   UARTReadChar   ; keep looping until character appears

   btfss   RCSTA,OERR   ; test for overrun error
   bra   read_framing   ; jump if no overrun
   bcf   RCSTA,CREN   ; clear continous receive bit
   bsf   RCSTA,CREN   ; and set it again

#ifdef   USE_DEBUG_LED
   bcf   LATC,2   ; @@@@@@@@@@@@@ debug - LED off - overrun error
#endif

read_framing:
   btfss   RCSTA,FERR   ; check from framing errors
   bra   GotChar      ; jump if no framing error
   movf   RCREG,w      ; read byte and discard
   retlw   0      ; framing error so return 0

;Ignore '\n', otherwise return new character in w-reg
GotChar:
   movf   RCREG,w      ; grab the new character
   xorlw   '\n'      ; is it '\n'?
   btfss   STATUS,Z   ; if yes, return 0
   xorlw   '\n'      ; recover character
   return

;******************************************************************************
;  UARTPutString - print w-reg as two ascii characters
UART_send_wreg:
   movwf   PRODL      ;use as temp storage
   swapf   PRODL,w
   rcall   UART_send_nyb
SER_send_half:
   movf   PRODL,w
;   rcall   send_nyb
;******************************************************************************
;convert lo nibble to an ascii character
; $0 - $f to '0' - '9','A' - 'F'  (note '9' + 8 = 'A')
UART_send_nyb:
   andlw   b'00001111'   ;strip hi bits
   addlw   -.10      ; Test to see if W < 10
   btfsc   STATUS,C   ; If yes, skip ahead
   addlw   'A'-'0'-.10   ; Add ASCII 'A', and negate next line
   addlw   '0'+.10      ; Add ASCII '0' and original 10
;   bra   UARTprint

;******************************************************************************
;wait until the serial transmit reg is empty, then send w-reg
UARTprint:
   btfss   PIR1,TXIF   ;test if TXREG is empty
   bra   UARTprint   ;loop until serial port ready
   nop
   movwf   TXREG
   return

;******************************************************************************
;  UARTPutString - send an in-line string out the serial port via Stack and TBLPTR
;
;  string must be terminated with a 00 byte and does not need
;  to be word aligned
;
;Usage:
;   rcall   UARTPutString   ;
;   db   str,0      ; put string into flash, null terminated
;
UARTPutString:
   movff   TOSH,TBLPTRH   ; copy return address to TBLPTR
   movff   TOSL,TBLPTRL   ;
   clrf    TBLPTRU      ; assume PIC with < 64KB FLASH
UARTPutNext:
   tblrd   *+      ; get in-line string character
   movf    TABLAT,W   ; last character (00)?
   bz      UARTPutExit   ; yes, exit, else
   rcall   UARTprint   ; print character
   bra     UARTPutNext   ; and do another
UARTPutExit:
   btfsc   TBLPTRL,0   ; odd address?
   tblrd   *+      ; yes, make it even (fix PC)
   movf    TBLPTRH,W   ; setup new return address
   movwf   TOSH      ;
   movf    TBLPTRL,W   ;
   movwf   TOSL      ;
   return         ;

;******************************************************************************
; Unlock and start the write or erase sequence.
   clrf   EECON1      ; try prevent accidental erase/write operations.
StartWrite:
;   bcf   INTCON,GIE   ; disable interrupts
   movlw   0x55      ; Special
   movwf   EECON2      ; Unlock
   movlw   0xAA      ; Sequence
   movwf   EECON2
   bsf   EECON1, WR   ; Start the write
;   bsf   INTCON,GIE   ; re-enable interrupts
   nop         ; MCU stalls until erase/write is done
            ; about 6 ms per flash write block
   clrf   EECON1      ; try prevent accidental erase/write operations.

   return

;******************************************************************************
; Test if all of user flash memory is blank (0xFF)
; Returns with Z set if all of user flash memory = 0xFF
; Returns with Z clear if a single bit is clear
;TestBlankFlash
;   movlw   low (FLASH_LOADER_ADDR-1)
;   movwf   TBLPTRL   ; Point TBLPTR at last byte of user flash
;
;   movlw   high (FLASH_LOADER_ADDR-1)
;   movwf   TBLPTRH
;
;   clrf   TBLPTRU      ; device has less than 64K flash
;TestFlashLoop
;   tblrd   *-
;   incf   TABLAT,w   ; w=0 if flash location=0xFF
;   bnz   FlashNotBlank   ; branch if Z clear
;   tstfsz   TBLPTRL      ; test if TBLPTR = 0,
;   bra   TestFlashLoop   ;   keep looping if not
;   tstfsz   TBLPTRH
;   bra   TestFlashLoop
;   tstfsz   TBLPTRU      ; don't really need this as device has <64K flash
;   bra   TestFlashLoop
;   tblrd   *      ; test location 0 also
;   incf   TABLAT,w   ; Z set if last location is blank
;FlashNotBlank
;   return

;******************************************************************************
;End of program
   END
/vp
vegipete
 
Posts: 37
Joined: Sat Jul 09, 2011 7:38 pm


Return to PIC18 BootLoader

Who is online

Users browsing this forum: No registered users and 1 guest