Compiling for a bootloader in MPLAB
Compiling a program that will be loaded using a bootloader requires a lot of extra code in MPLAB. Basically this code is doing two things:
- Vector remapping: Resets, high-priority interrupts, and low-priority interrupts usually make the PIC start executing code at addresses 0x00, 0x08, and 0x18 respectively. However, this is the domain of the bootloader, which is sitting in this first region of memory. So some "remapping" goes on, as explained in the comments to the first chunk of code below.
- Linker script: When a hex file is compiled from a source, it contains information about where in program memory it should be written. By default it starts at address 0x00 and continues as far as it needs to go, but this would overwrite the bootloader. So you need something to tell MPLAB where in memory it can put code; this is done by a linker script. There's an example of one below.
All this code assumes you are using the HID bootloader found here. That bootloader takes up memory up to address 0x10FF (hexadecimal). User code starts at 0x1100. If you are using a bootloader that takes up a different amount of space, such as the CDC bootloader (which only takes up to 0x7FF), you will need to change some numbers. All mentions of numbers like 0x10FF, 0x1100, 0x1108, or 0x1118 will need to be replaced by numbers like 0x7FF, 0x800, 0x808, and 0x818 respectively.
Vector Remapping
This code goes at the top of your code, or in an include file, and accomplishes the first bullet point above. Note the functions where you should place your interrupt-handling code, if you have any!
//The following lengthy and involved incantation sets things up so that resets and interrupt //handling operate as they should, given the presence of the bootloader. //Omitting this incantation will make the code stop working. //If you are generating absolutely no interrupts, you can probably leave out just the stuff //referring to interrupts. //Comments are by Microchip. //On PIC18 devices, addresses 0x00, 0x08, and 0x18 are used for //the reset, high priority interrupt, and low priority interrupt //vectors. However, the current Microchip USB bootloader //examples are intended to occupy addresses 0x00-0x7FF or //0x00-0xFFF depending on which bootloader is used. Therefore, //the bootloader code remaps these vectors to new locations //as indicated below. This remapping is only necessary if you //wish to program the hex file generated from this project with //the USB bootloader. #define REMAPPED_RESET_VECTOR_ADDRESS 0x1100 #define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS 0x1108 #define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS 0x1118 void YourHighPriorityISRCode(); void YourLowPriorityISRCode(); extern void _startup (void); // See c018i.c in your C18 compiler dir #pragma code REMAPPED_RESET_VECTOR = REMAPPED_RESET_VECTOR_ADDRESS void _reset (void) { _asm goto _startup _endasm } #pragma code REMAPPED_HIGH_INTERRUPT_VECTOR = REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS void Remapped_High_ISR (void) { _asm goto YourHighPriorityISRCode _endasm } #pragma code REMAPPED_LOW_INTERRUPT_VECTOR = REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS void Remapped_Low_ISR (void) { _asm goto YourLowPriorityISRCode _endasm } //If the output hex file is not programmed with //the bootloader, addresses 0x08 and 0x18 would end up programmed with 0xFFFF. //As a result, if an actual interrupt was enabled and occured, the PC would jump //to 0x08 (or 0x18) and would begin executing "0xFFFF" (unprogrammed space). This //executes as nop instructions, but the PC would eventually reach the REMAPPED_RESET_VECTOR_ADDRESS //(0x1000 or 0x800, depending upon bootloader), and would execute the "goto _startup". This //would effective reset the application. //To fix this situation, we should always deliberately place a //"goto REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS" at address 0x08, and a //"goto REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS" at address 0x18. When the output //hex file of this project is programmed with the bootloader, these sections do not //get bootloaded (as they overlap the bootloader space). If the output hex file is not //programmed using the bootloader, then the below goto instructions do get programmed, //and the hex file still works like normal. The below section is only required to fix this //scenario. #pragma code HIGH_INTERRUPT_VECTOR = 0x08 void High_ISR (void) { _asm goto REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS _endasm } #pragma code LOW_INTERRUPT_VECTOR = 0x18 void Low_ISR (void) { _asm goto REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS _endasm } #pragma code #pragma interrupt YourHighPriorityISRCode void YourHighPriorityISRCode() { //Check which interrupt flag caused the interrupt. //Service the interrupt //Clear the interrupt flag //Etc. } //This return will be a "retfie fast", since this is in a #pragma interrupt section #pragma interruptlow YourLowPriorityISRCode void YourLowPriorityISRCode() { //Check which interrupt flag caused the interrupt. //Service the interrupt //Clear the interrupt flag //Etc. } //This return will be a "retfie", since this is in a #pragma interruptlow section //End incantation
Here's a shorter version of the same thing, stripped of most explanatory comments:
#define REMAPPED_RESET_VECTOR_ADDRESS 0x1100 #define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS 0x1108 #define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS 0x1118 void YourHighPriorityISRCode(); void YourLowPriorityISRCode(); extern void _startup (void); // See c018i.c in your C18 compiler dir #pragma code REMAPPED_RESET_VECTOR = REMAPPED_RESET_VECTOR_ADDRESS void _reset (void) { _asm goto _startup _endasm } #pragma code REMAPPED_HIGH_INTERRUPT_VECTOR = REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS void Remapped_High_ISR (void) { _asm goto YourHighPriorityISRCode _endasm } #pragma code REMAPPED_LOW_INTERRUPT_VECTOR = REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS void Remapped_Low_ISR (void) { _asm goto YourLowPriorityISRCode _endasm } #pragma code HIGH_INTERRUPT_VECTOR = 0x08 void High_ISR (void) { _asm goto REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS _endasm } #pragma code LOW_INTERRUPT_VECTOR = 0x18 void Low_ISR (void) { _asm goto REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS _endasm } #pragma code #pragma interrupt YourHighPriorityISRCode void YourHighPriorityISRCode() { //high priority interrupt code } #pragma interruptlow YourLowPriorityISRCode void YourLowPriorityISRCode() { //low priority interrupt code } If you wish to use the CDC bootloader with MPLAB source code, you only need to change three lines of the above, where the REMAPPED_VECTOR_ADDRESS constants are defined: #define REMAPPED_RESET_VECTOR_ADDRESS 0x800 #define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS 0x808 #define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS 0x818
Linker Script
Here is a sample linker script. Save it in a file with a ".lkr" extension and include it in your MPLAB project. All MPLAB projects have a linker script. Often it's just the default one provided by the C18 compiler, which this one replaces.
// Sample linker script for the PIC18F4550 processor LIBPATH . FILES c018i.o FILES clib.lib FILES p18f4550.lib //change if you're not using the 4550 //non-bootloader lines: CODEPAGE NAME=boot START=0x0 END=0x10FF PROTECTED //don't overwrite the bootloader CODEPAGE NAME=vectors START=0x1100 END=0x1129 PROTECTED //put reset/interrupt vectors here CODEPAGE NAME=page START=0x112A END=0x7FFF //code goes after bootloader //the reset of these lines do something unrelated to the bootloader: CODEPAGE NAME=idlocs START=0x200000 END=0x200007 PROTECTED CODEPAGE NAME=config START=0x300000 END=0x30000D PROTECTED CODEPAGE NAME=devid START=0x3FFFFE END=0x3FFFFF PROTECTED CODEPAGE NAME=eedata START=0xF00000 END=0xF000FF PROTECTED ACCESSBANK NAME=accessram START=0x0 END=0x5F DATABANK NAME=gpr0 START=0x60 END=0xFF DATABANK NAME=gpr1 START=0x100 END=0x1FF DATABANK NAME=gpr2 START=0x200 END=0x2FF DATABANK NAME=gpr3 START=0x300 END=0x3FF DATABANK NAME=usb4 START=0x400 END=0x4FF PROTECTED DATABANK NAME=usb5 START=0x500 END=0x5FF PROTECTED DATABANK NAME=usb6 START=0x600 END=0x6FF PROTECTED DATABANK NAME=usb7 START=0x700 END=0x7FF PROTECTED ACCESSBANK NAME=accesssfr START=0xF60 END=0xFFF PROTECTED SECTION NAME=CONFIG ROM=config STACK SIZE=0x100 RAM=gpr3