News:

Let's find out together what makes a PIC Tick!

Main Menu

Creating a bootloader

Started by kcsl, Jun 29, 2025, 09:53 AM

Previous topic - Next topic

kcsl

I need to create a bootloader, and realised I don't know as much about this topic as I thought I did.

It seems that the PIC 18F27Q83 always boots/resets to address $0, so that's where the bootloader must reside.
So, the first chunk of program memory would contain the boot loader, then the application program would reside after that.
As an example, the application will have "DECLARE Compiler_Start_Address = 0x1000".

In the bootloader, I can configure the system clock, hardware I/O pins, timer module etc.
The bootloader then decides there's nothing to do, so starts execution of the main application from address $1000

I assume I do something like this when the Bootloader is ready to start the main application:

' Start executing the main application
ASM
   GOTO $1000
ENDASM

Now the main application is running, I just need to re-setup the interrupts for the timers, modules etc as their addresses they jump to will have all changed.
I don't need to re-configure the clock or any of the hardware as it's already been done by the bootloader and since there was no RESET, that should all still be valid.

I've probably over simplified but is this basically it, because it feels there's a giant trap I'm about to fall into?


Regards,
Joe
There's no room for optimism in software or hardware engineering.

trastikata

Here's how I typically design USB HID bootloaders for my devices:

1. Re-map Interrupt Vectors
Ensure the high and low priority interrupt vectors are redirected:

On_Interrupt      GoTo HIGH_INTERRUPT_VECTOR 
On_Low_Interrupt  GoTo LOW_INTERRUPT_VECTOR

2. Configure Fuse Settings
Fuse settings should be locked in such a way that the bootloader always runs—regardless of what is flashed afterward. Protect the bootloader's flash area when possible.

Example – Using traditional protection bits:
Config_Start
    ...
    CP0   = OFF  ; Code protection disabled for Block 0 
    CP1   = OFF  ; Code protection disabled for Block 1 
    CPB   = OFF  ; Code protection disabled for Boot block 
    CPD   = OFF  ; Code protection disabled for Data EEPROM 
    WRT0  = OFF  ; Write protection disabled for Block 0 
    WRT1  = OFF  ; Write protection disabled for Block 1 
    WRTC  = ON   ; Configuration registers write-protected 
    WRTB  = ON   ; Boot block write-protected 
    WRTD  = OFF  ; Write protection disabled for Data EEPROM 
    EBTR0 = OFF  ; Block 0 not protected from external table reads 
    EBTR1 = OFF  ; Block 1 not protected from external table reads 
    EBTRB = OFF  ; Boot block not protected from external table reads 
Config_End

Example – Using flash write-protect range:
Config_Start
    ...
    WPFP   = PAGE_3   ; Write-protect up to Page 63 
    WPEND  = PAGE_0   ; Protection applies from Page 0 to WPFP 
    WPCFG  = ON       ; Configuration Words page write/erase-protected 
    WPDIS  = ON       ; Enables write protection as defined above 
Config_End

3. Flash Status Check at Boot
Upon boot, verify whether the last firmware update completed successfully by checking a stored marker:

BootOk = ERead FlashClearedStatus  ; From EEPROM 
; or 
BootOk = CRead FlashClearedStatus  ; From FLASH

4. Decide Next Action
Based on the BootOk flag:
 - Wait a few seconds and receive a FLASH request or check an input pin to trigger flash mode
 - Immediately enter flash mode
 - Proceed to the main application

5. Firmware Flashing Procedure
If flashing a new program:
 - Mark BootOk as "Not OK" (or equivalent)
 - Set up a reliable communication and addressing mechanism
 - Handle erasure and writing, accounting for block and page sizes
 - Implement safeguards to avoid overwriting the bootloader if not protected by fuses for example if flash address is below certain number, don't erase/flash
 - Flash the new firmware
 - Set BootOk to "OK" (or equivalent)

I recommend storing the BootOk flag in a FLASH data block located within the bootloader section (rather than EEPROM) for better integrity.

6. Start the Main Application
Jump to the main application code. The start address should align with the flash erase block size:

@ Call MainCodeStart

7. Set User Interrupt Vectors
Make sure the interrupt vectors in your application code are correctly aligned:

Org MainCodeStart + $8 
HIGH_INTERRUPT_VECTOR: 

Org MainCodeStart + $18 
LOW_INTERRUPT_VECTOR:

kcsl

trastikata, that's great information, thanks.
I need to have a proper read and a think now.

Regards,
Joe
There's no room for optimism in software or hardware engineering.

kcsl

I'm pulling out what little hair I have left with this.
Is there something special you have to do when writing the code for the programs that the bootloader will download and store in PIC flash memory?

My CAN bus bootloader will communicate with the PC, download the .HEX file and write it to the PIC's flash memory, it just won't execute.

The bootloader sits at the start of memory $0000.
The downloaded code starts at $4000.

I've a routine in the bootloader that reads a block of flash memory and dumps it to the serial port. I can read back from $0000 and this matches exactly the contents of the bootloader.hex file so proves my read code is working. I then read back the code from location $4000 and this matches the code in the downloaded .HEX file, so proves my code for erasing flash, and then downloading and writing the code to flash memory is working.

The source for the downloaded file has this:
Declare Compiler_Start_Address = $4000 
and looking at the contents of the test HEX file seems to confirm it is correct.

:020000040000FA
:104000001AEF20F0036A026EFF0E0226032200D090
:10401000D8A01200030E016EE60E02D8F5D7016A91
:10402000FE0F006EE86A015AD8A0120000000006D8
:1040300000D0F8D7F86A05010001416B0401006B5C
:10404000086B106B036B0B6B136B046B0C6B146BBB
:10405000246B0001706B746BC8980501C078C89818
:08406000960ED0DFFAD7FFD75E
:020000040030CA
:0A000000FACD27C682C0FEFFFFFF05
:00000001FF
(the last 3 lines are ignored by the bootloader)

To keep things simple the test program just flashes an LED.

At this point, I've not done anything with reset or interrupt vectors, but I'm not using those in the test program yet.

This is the code in the bootloader that should start the test program:

    C1INTUbits_RXIE = 0            ' Disable receive object interrupt on CAN RX   
    INTCON0bits_GIE = 0            ' Global interrupt off
    PIE0bits_CANIE = 0              ' CAN main interrupt disabled
    PIE4bits_CANRXIE = 0            ' CAN Receive interrupt disabled
    C1FIFOCON1Lbits_TFNRFNIE = 0    ' Disable "Not Empty" interrupt for FIFO 1       
    GOTO (16384)

The watchdog timer is disabled in the Bootloader config settings.

I put a debug message just before the GOTO (16384) and that's being reached, so there's no obvious reason why the GOTO isn't working.
I looked at the output assembler for the bootloader and I can clearly see the "goto 16384"

ram_bank = 5
F1_000510 equ $ ; in [CANBOOTLOADER V0.2.BAS] goto (16384)
    goto 16384
StartBootLoader
F1_000515 equ $ ; in

It's just a GOTO and you can jump to anywhere in the program storage area, so I have to conclude there's a problem with the program I'm downloading.


This is the test program I'm using:
    Declare Compiler_Start_Address = $4000 


    Declare Optimiser_Level = 0
    Device 18F27Q83         
   
    Declare Xtal 40       

    Declare Auto_Heap_Arrays = On
    Declare Auto_Heap_Strings = On
    Declare Auto_Variable_Bank_Cross = On   
   
    ' Port C is configured by the bootloader

    Output PORTC.4   

FlashLoop:               
    Toggle PORTC.4
    DelayMS 150
    GoTo FlashLoop

Anybody any ideas why this won't execute or what I can do to try and narrow it down?

Regards,
Joe



There's no room for optimism in software or hardware engineering.

trastikata

Hello Joe,

Portc.4 by default is analog pin I think. Also toggle the LATx register just to be on the safe side:

    ANSELC.4 = 0 : Output PORTC.4   
FlashLoop:               
    Toggle LATC.4
    DelayMS 150
    GoTo FlashLoop

If this is not the main issue, let me know so I can look further.

kcsl

Thanks for that, you pointed me in the right direction.

When I wrote my test code and tested it standalone, it worked.
The problem was that the bootloader code sets PORTC.4, 5 & 6 as PWM outputs for an RGB LED, so as soon as my test program was being executed by the bootloader, those pins were no longer I/O pins so my test program wouldn't work.

So, I just need to fix the interrupt handling and that should be it, hopefully.

Thanks for your help.

Joe
There's no room for optimism in software or hardware engineering.