Compiling for a bootloader in MPLAB

From Mech
Jump to navigationJump to search

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