' GCBOOT v8 11/13/10 - a PIC18F bootloader written in Great Cow Basic ' Code is for gcbasic (0.9 Sept 10 2010) and gpasm from gptools 0.13.7 ' Compile command: gcbasic -a:"gpasm -i gcboot.asm" gcboot.gcb ' Pickit2 burn command: pk2cmd -P -T -R -M -F gcboot.hex ' ' This is a bootloader for the PIC18F2525 microcontroller, once loaded ' application code can be loaded via a serial terminal rather than having ' to use a conventional programmer. Unlike just about every other PIC18F ' bootloader, this one accepts standard microchip hex without requiring ' a specific PC app, can send code using any terminal emulator that can ' provide a short delay after each line. The present implementation does ' not support eeprom, anything in the hex file that isn't program code is ' ignored. Does not erase code that's already present, generally this ' doesn't matter and permits loading overlays or multiple apps. ' ' Configured for a 9600 baud serial port connected to the TX and RX pins ' (RC6 and RC7) using series resistors, about 1K in series with TX and ' about 22K in series with RX. The RX line should be pulled high using ' a 100K resistor to run the app when the serial port is not connected. ' Configured to flash an LED connected to RB0, if using as-is make sure ' this pin isn't grounded or connected to another device output. Runs at ' 32mhz using the internal oscillator. Bootloader starts at 0xB000. ' Edit below to configure differently. Work in progress... ' ' v0 10/24/10 - 1st working version. ' v1 10/25/10 - better error-checking, verifies jump at loc 0 ' on startup (writes only if not present), ' hex entry no longer case-sensitive, ' cleaned up the code a bit. ' v2 10/26/10 made compatible w/ gpasm from gputils 0.13.7, ' added extra logic for locating past 0x8000. ' v3 10/26/10 changed flash buffer to 0x100, added wipe chip feature. ' v4 10/27/10 enabled MCLR reset pin, fixed invalid address bug. ' v5 10/29/10 modified to use 8mhz or 32mhz clock, prints config. ' 10/31/10 tweaked license info and comments, no code changes. ' v6 11/1/10 added a goto 2 after words saved from 1st 2 words. ' v7 11/10/10 added Set PLLEN off before running app. ' v8 11/13/10 added wait to avoid serial garbage w/ v7 when running app, ' shortened messages to reduce memory usage a bit. ' ' The source code for the program is written by Terry Newton, it is provided ' as-is and without warranty of any kind. Under no circumstances shall the ' author be held liable for damages resulting from the use of the software. ' The source code itself is public domain and may be used for any purpose. ' The hex file that results from compiling this program includes libraries ' from the Great Cow Basic compiler that are Copyright Hugh Considine ' under the terms of the Lesser GNU Public License version 2.1 or later. ' GC Basic and source code is available from: http://gcbasic.sourceforge.net/ ' #chip 18F2525, 8 #config OSC = INT, BODEN = OFF, WDT = OFF, MCLRE = ON, LVP = OFF 'the following string is printed on startup to document configuration #define CONFIGSTRING "Configuration = 32mhz MCLR LED=portb.0" #define QUADCLOCK 1 '0 = 8mhz clock, 1 = 32mhz clock 'note.. use gpasm to locate past 0x7000 (unless using patched GCB) #option bootloader 0xB000 'location of bootloader #define BOOTLOADERHIGH 0xB0 'must be on 256-byte boundary #define APPGOTO 0xBFF0 'location of first relocated goto word #define APPGOTO2 0xBFF2 'location of second relocated goto word #define APPGOTO3 0xBFF4 'location of 1st GOTO 2 instruction #define APPGOTO4 0xBFF6 'location of 2nd GOTO 2 instruction #define BUFFERHIGH 1 'location of flash buffer high/low #define BUFFERLOW 0 'make sure it doesn't conflict w/ vars #define USELED 1 '1 to use LED pin #define LEDPIN portb.0 'pin for LED #define USEXONOFF 1 '1 to (attempt to) use xon/xoff flow #define SERPOL 1 '1 for direct connect, 0 for MAX232 etc #define APPRUNPIN portc.7 'pin to indicate to run app (def. RX) #define APPRUNPOL 1 'pin state to run app #define USART_BLOCKING true #script if QUADCLOCK = 1 then 'if PLL enabled USART_BAUD_RATE = 2400 ' use 2400 to mean 9600 baud ONESEC = 4 ' wait 4 s = 1 second end if if QUADCLOCK = 0 then 'if PLL not enabled USART_BAUD_RATE = 9600 'for normal 8mhz clock ONESEC = 1 end if #endscript Dim address As Word Dim address2 As Word Dim char As Byte Dim temp As Byte Dim temp1 As Byte Dim temp2 As Byte Dim subtemp As Byte Dim subword As Word Dim subword2 As Word Dim tempword1 As Word Dim tempword2 As Word Dim tempword3 As Word Dim tempword4 As Word Dim invalid As Byte Dim hexdigit As Byte Dim hexbyte As Byte Dim count As Byte Dim byteloop As Byte Dim checksum As Byte Dim extadr As Byte Dim currpage As Word Dim newpage As Word Dim buffcnt As Byte Dim tblptrhsave As Byte Dim tblptrlsave As Byte Dim pgmword As Word Dim pgmaddress As Word Dim pgmaddresssave As Word Dim bytebuffer(33) 'buffer for incoming bytes bootstart: IntOff 'no interrupts Set IRCF2 on Set IRCF1 on Set IRCF0 on 'max frequency #ifdef QUADCLOCK 1 Set PLLEN on 'enable PLL for 32mhz #endif #ifdef QUADCLOCK 0 Set PLLEN off 'in case not enabled and app enables then runs bootloader #endif Dir PORTA in 'all pins input Dir PORTB in Dir PORTC in Dir PORTC.6 out 'except serial output Set RXDTP SERPOL 'set polarity of serial port Set TXCKP SERPOL If APPRUNPIN = APPRUNPOL Then Goto run 'if serial not connected then run app #ifdef USELED 1 Dir LEDPIN out Set LEDPIN on 'flash LED to show boot mode wait ONESEC s 'and verify timing Set LEDPIN off wait ONESEC s #endif #ifndef USELED 1 wait ONESEC s #endif Gosub xon: Gosub crlf HSerPrint "=== Terry's Bootloader === v8 11/13/10" Gosub crlf HSerPrint CONFIGSTRING menu: #ifdef USELED 1 Set LEDPIN on #endif Gosub initboot 'make sure 0-3 = jump, set extadr = 0, currpage = 0xFFFF Gosub xon: Gosub crlf HSerPrint "Select...":Gosub crlf HSerPrint "P: Program (or just send)":Gosub crlf HSerPrint "M: Examine ram":Gosub crlf HSerPrint "V: Examine code":Gosub crlf HSerPrint "W: Wipe chip":Gosub crlf HSerPrint "X: Execute app":Gosub crlf HSerPrint "> " getcommand: HSerReceive char If char = 58 Then Goto directhex If char = 80 Or char = 112 Then Gosub showentry: Goto loadhex If char = 77 Or char = 109 Then Gosub showentry: Goto dump If char = 86 Or char = 118 Then Gosub showentry: Goto dumpcode If char = 87 Or char = 119 Then Gosub showentry: Goto wipechip If char = 88 Or char = 120 Then Gosub showentry: Goto run Goto getcommand crlf: HSerSend 13:HSerSend 10 Return showentry: HSerSend char:Gosub crlf Return gethexdigit: invalid = 0 hexdigit = 0 HSerReceive char If char > 96 And char < 103 Then char = char - 32 If char < 48 Then invalid = 1: Return If char > 70 Then invalid = 1: Return If char > 57 And char < 65 Then invalid = 1: Return hexdigit = char - 48 If char >= 65 Then hexdigit = char - 55 Return gethexbyte: 'does not get 2nd char if 1st invalid 'adds byte to checksum hexbyte = 0 Gosub gethexdigit subtemp = hexdigit * 16 If invalid = 0 Then Gosub gethexdigit hexbyte = subtemp + hexdigit End If subtemp = checksum checksum = subtemp + hexbyte Return printhexdigit: char = hexdigit + 48 If hexdigit > 9 Then char = hexdigit + 55 HSerSend char Return printaddress: hexdigit = address / 4096 Gosub printhexdigit subword = address - (hexdigit * 4096) hexdigit = subword / 256 Gosub printhexdigit subword2 = subword - (hexdigit * 256) hexdigit = subword2 / 16 Gosub printhexdigit hexdigit = subword2 - (hexdigit * 16) Gosub printhexdigit Return printhexbyte: hexdigit = hexbyte / 16 Gosub printhexdigit subtemp = hexdigit hexdigit = hexbyte - (subtemp * 16) Gosub printhexdigit Return loadhex: HSerReceive char if char = 27 Then Goto menu 'abort if char <> 58 Then Goto loadhex 'wait for ":" directhex: #ifdef USELED 1 Set LEDPIN off #endif checksum = 0 'get count Gosub gethexbyte If invalid then Goto hexerror count = hexbyte AND 127 If count > 32 Then Goto counterror If count = 0 Then Goto terminateload 'get address Gosub gethexbyte If invalid then Goto hexerror pgmaddress_h = hexbyte Gosub gethexbyte If invalid then Goto hexerror [byte]pgmaddress = hexbyte 'get record type Gosub gethexbyte If invalid then Goto hexerror If hexbyte = 4 Then 'handle extended address sets If count <> 2 Then Goto counterror Gosub gethexbyte If invalid then Goto hexerror Gosub gethexbyte If invalid then Goto hexerror extadr = hexbyte 'get checksum but don't modify temp = checksum Gosub gethexbyte checksum = (NOT temp) + 1 if checksum <> hexbyte Then Goto checksumerror Goto loadhex End If If hexbyte <> 0 Then Goto recordtypeerror 'if processing configs ee etc then more code is needed 'but this code only processes normal program code If extadr <> 0 Then Goto loadhex 'ignore anything but 0 'get bytes If (count AND 1) = 1 Then Goto counterror 'must be even For byteloop = 1 To count Gosub gethexbyte bytebuffer(byteloop) = hexbyte Next byteloop 'get checksum but don't modify temp = checksum Gosub gethexbyte checksum = (NOT temp) + 1 if checksum <> hexbyte Then Goto checksumerror 'now program the bytes Gosub xoff #ifdef USELED 1 Set LEDPIN on #endif byteloop = 1 pgmbyteloop: [byte]pgmword = bytebuffer(byteloop) pgmword_h = bytebuffer(byteloop+1) Gosub programword pgmaddress = pgmaddress + [word]2 byteloop = byteloop + 2 If byteloop < count Then Goto pgmbyteloop Gosub xon Goto loadhex terminateload: Gosub xoff If currpage <> 0xFFFF Then Gosub writebuffer 'if anything loaded Gosub xon finishlineread: HSerReceive char If char = 13 Then Goto menu If char = 10 Then Goto menu If char = 27 Then Goto menu Goto finishlineread 'flow control... generally xon/xoff doesn't work with 'buffered serial cards but might help. If needed these 'subroutines can be used to add hardware flow control xoff: #ifdef USEXONOFF 1 HSerSend 19 'tell sender to stop sending #endif Return xon: #ifdef USEXONOFF 1 HSerSend 17 'tell sender to resume sending #endif Return hexerror: Gosub crlf HSerPrint "Invalid hex" Goto waitforesc counterror: Gosub crlf HSerPrint "Invalid count" Goto waitforesc checksumerror: Gosub crlf HSerPrint "Checksum error" Goto waitforesc recordtypeerror: Gosub crlf HSerPrint "Invalid record" Goto waitforesc dump: HSerPrint "HEX digit of block to dump: " promptforblock: Gosub gethexdigit If char = 27 Then Goto menu If invalid Then Goto promptforblock Gosub printhexdigit: Gosub crlf address = hexdigit * 256 for temp1 = 0 to 15 Gosub printaddress HSerPrint ": " address2 = address for temp2 = 0 to 15 fsr0h = address2_h fsr0l = address2 hexbyte = indf0 Gosub printhexbyte: HSerPrint " " address2 = address2 + 1 next temp2 Gosub crlf address = address + 16 next temp1 Goto menu dumpcode: HSerPrint "HEX Address: " gethd1: Gosub gethexdigit If char = 27 Then Goto menu If invalid Then Goto gethd1 Gosub printhexdigit pgmaddress = hexdigit * [word]4096 gethd2: Gosub gethexdigit If char = 27 Then Goto menu If invalid Then Goto gethd2 Gosub printhexdigit pgmaddress = [word]pgmaddress + ([byte]hexdigit * [word]256) gethd3: Gosub gethexdigit If char = 27 Then Goto menu If invalid Then Goto gethd3 Gosub printhexdigit pgmaddress = [word]pgmaddress + ([byte]hexdigit * [word]16) gethd4: Gosub gethexdigit If char = 27 Then Goto menu If invalid Then Goto gethd4 Gosub printhexdigit pgmaddress = [word]pgmaddress + [byte]hexdigit Gosub crlf currpage = 0xFFFF For temp1 = 1 To 16 address = pgmaddress Gosub printaddress HSerPrint ": " For temp2 = 1 To 8 newpage = pgmaddress / [word]64 If newpage <> currpage Then currpage = newpage Gosub readbuffer End If fsr0h = BUFFERHIGH fsr0l = BUFFERLOW fsr0l = fsr0l + ([byte]pgmaddress AND 63) hexbyte = postinc0 [byte]address = hexbyte hexbyte = postinc0 address_h = hexbyte Gosub printaddress HSerSend 32 pgmaddress = pgmaddress + [word]2 Next temp2 Gosub crlf Next temp1 Goto menu run: 'avoid corruption of "x" when running app #ifdef QUADCLOCK 1 Wait 200 ms 'wait for last char+crlf to be sent Set PLLEN off 'downshift to 8mhz default clock #endif Goto APPGOTO 'original 2 words stored there waitforesc: Gosub xon Gosub crlf HSerPrint "Send ESC for menu " wfeloop: HSerReceive char If char <> 27 Then Goto wfeloop Gosub crlf Goto menu enablewrite: asm bcf intcon, gie asm movlw 0x55 asm movwf eecon2 asm movlw 0xAA asm movwf eecon2 asm bsf eecon1, wr Return readbuffer: 'reads program code into ram buffer 'currpage = current 64 byte block (word) 'tblptrhsave and tblptrlsave left pointing to currpage fsr0h = BUFFERHIGH fsr0l = BUFFERLOW tblptru = 0 tblptrhsave = ([word]currpage / [word]4) AND 255 tblptrlsave = (currpage AND 3) * 64 tblptrh = tblptrhsave tblptrl = tblptrlsave For buffcnt = 1 To 64 asm tblrd*+ postinc0 = tablat Next buffcnt Return writebuffer: 'writes ram buffer to program space 'tblptrhsave tblptrlsave = 64 byte block to program tblptru = 0 tblptrh = tblptrhsave tblptrl = tblptrlsave asm bsf eecon1,eepgd asm bcf eecon1,cfgs asm bsf eecon1,wren asm bsf eecon1,free gosub enablewrite asm tblrd*- fsr0h = BUFFERHIGH fsr0l = BUFFERLOW For buffcnt = 1 To 64 tablat = postinc0 asm tblwt+* Next buffcnt asm bsf eecon1,eepgd asm bcf eecon1,cfgs asm bsf eecon1,wren gosub enablewrite asm bcf eecon1,wren Return initboot: 'initialize... make sure 1st 2 words contain a jump to the bootloader so a 'partial load won't crash on reset (only write jump if it doesn't exist) 'on exit set extadr to 0, set currpage to 0xFFFF to init loading [byte]pgmaddress = 0 [byte]pgmaddress_h = BOOTLOADERHIGH currpage = pgmaddress / [word]64 Gosub readbuffer fsr0h = BUFFERHIGH fsr0l = BUFFERLOW [byte]tempword1 = postinc0 tempword1_h = postinc0 [byte]tempword2 = postinc0 tempword2_h = postinc0 'tempword1 and tempword2 contain jump words currpage = 0 Gosub readbuffer fsr0h = BUFFERHIGH fsr0l = BUFFERLOW [byte]tempword3 = postinc0 tempword3_h = postinc0 [byte]tempword4 = postinc0 tempword4_h = postinc0 'tempword3 and tempword4 contain locs 0-3 If tempword1 <> tempword3 Or tempword2 <> tempword4 Then 'jump to bootloader not present, put it there pgmaddress = 0 pgmword = tempword1 Gosub programrawword 'no relocation pgmaddress = 2 pgmword = tempword2 Gosub programrawword Gosub writebuffer currpage = APPGOTO3 / [word]64 Gosub readbuffer pgmaddress = APPGOTO3 pgmword = 0xEF01 Gosub programrawword pgmaddress = APPGOTO4 pgmword = 0xF000 Gosub programrawword Gosub writebuffer End If extadr = 0 currpage = 0xFFFF Return programword: 'buffers/writes words to program memory 'pgmaddress = address pgmword = word to write 'determines page, if different then writes previous page and reloads buffer 'if previous page = 0xFFFF then don't write (initial state) 'error if pgmaddress is past start of bootloader 'relocate 1st 4 bytes to APPGOTO pgmaddresssave = pgmaddress_h pgmaddresssave_h = 0 If pgmaddresssave >= [word]BOOTLOADERHIGH Then Goto pageerror pgmaddresssave = pgmaddress 'save in case relocated If pgmaddress = [word]0 Then pgmaddress = APPGOTO If pgmaddress = [word]2 Then pgmaddress = APPGOTO2 Gosub programrawword pgmaddress = pgmaddresssave Return programrawword: 'this doesn't relocate, no checking newpage = pgmaddress / [word]64 If newpage <> currpage Then If currpage <> 0xFFFF Then Gosub writebuffer currpage = newpage Gosub readbuffer HSerSend "." End If fsr0h = BUFFERHIGH fsr0l = BUFFERLOW + ([byte]pgmaddress AND 63) asm movff pgmword, postinc0 asm movff pgmword_h, postinc0 Return pageerror: 'this error occurs from within a subroutine so 'should be handled differently #ifdef USELED 1 Set LEDPIN off #endif Gosub crlf HSerPrint "Invalid address " address = pgmaddress : Gosub printaddress HSerPrint " ESC to reset" Gosub crlf : Gosub xon waittoreset: HSerReceive char If char <> 27 Then Goto waittoreset Reset wipechip: HSerPrint "Zero ram? ":Gosub ucyconfirm If char <> 89 Then Goto confirmerase Gosub showentry HSerPrint "Zeroing ram from 140 to EFF" For address = 0x0140 to 0x0EFF fsr0h = address_h fsr0l = [byte]address indf0 = 0 Next address confirmerase: Gosub crlf HSerPrint "Erase code? ":Gosub ucyconfirm If char <> 89 Then Goto menu Gosub showentry pgmaddress = 0x0004 'don't wipe initial goto pgmword = 0xFFFF wipeloop: Gosub programword pgmaddress = pgmaddress + 2 if pgmaddress_h <> BOOTLOADERHIGH Then Goto wipeloop Goto menu ucyconfirm: HSerPrint "(uppercase Y to confirm) " HSerReceive char Return 'end