PIC USB bootloader

  PIC

I’ve got a couple of projects on the go that use the PIC18F2550 (or PIC18F2553) microcontrollers.

Normally I program PICs in-circuit with a PICkit 3 programmer while debugging my code. While this works great, I also need a way for people to easily field-upgrade my PIC firmware, without the need for a PIC programmer.

So I decided to modify my projects to be compatible with an existing USB bootloader.

The bootloader will allow new firmware to be downloaded over the PIC’s in-built USB interface. This requires two special pieces of software: a boot loader application (running on a PC) that takes a compiled PIC user program (in “.hex” format) produced by the C18 compiler, and bootloader firmware (running on the PIC) that accepts commands and data from the bootloader application, and stores the downloaded user program in the PIC’s program memory.

The main downsides of using a boot loader are:

  1. the bootloader firmware permanently occupies space in the PIC’s program memory. So it reduces the maximum potential length of your user program.
  2. because the bootloader firmware sits in low memory, the reset and interrupt vectors need to be remapped to appropriate locations in the user program, above the bootloader firmware.
  3. the configuration bits will need to be set in the PIC to suit the bootloader application. Your user code will need to use these configuration bits, as there cannot be one set of configuration bits for the bootloader and a different set for the user code.

Choosing a bootloader

It seems there are two good USB bootloader options available from Microchip.  The first is known as a “CDC bootloader”. This has the advantage of the bootloader firmware only occupying 0x0800 bytes of the PIC’s program memory, but the downside is that (I think) special USB drivers need to be loaded on the PC. The second option is a “HID bootloader”. This doesn’t need any special USB drivers loaded on the PC, but the bootloader firmware occupies the lower 0x1000 bytes of the PIC’s memory.

I’ve decided to go with the HID bootloader method, to avoid the need for users to install special USB drivers on their PC.

Obtaining the bootloader software

Unfortunately Microchip don’t make the required files available as a simple stand-alone download.  Instead you have to download and install the whole “Microchip Libraries for Applications” (MLA) 278MB catastrophe.

Downloading

You can download various versions of MLA from here on Microchip’s website. You will find Windows, Linux and Mac OS X versions of MLA available for both the current IDE environment (MPLAB X), or the one I am using (MPLAB 8).

Because I’m using MPLAB IDE v8.92 (I’ve not converted my user programs to work with MPLAB X), I had to go to the “Legacy” tab of the MLA website to get the right version of MLA. I chose the latest available release (v2013-06-15) and downloaded the Windows version of it.

Installation

Installation of MLA is fairly straight forward. I accepted all the defaults during setup (including the proposed destination folder of C:microchip_solutions_v2013-06-15).  On the “Select Components” screen, I accepted the pre-selected defaults, which probably installs a bunch of stuff that we don’t really need.  I got an error message towards the end of the somewhat-lengthy installation process that said “Jave Run Time Environment Version is invalid or not found. Download JRE?”.  I answered NO, as I know that my MPLAB 8 IDE environment is functioning just fine.  The install process seemed to complete OK.

The bootloader application

Once MLA has been installed, navigate to “C:microchip_solutions_v2013-06-15USBDevice – BootloadersHIDSoftware Windows Executable”. In this directory you will find the file “HIDBootloader.exe”.  This is the bootloader application that you will run on the PC.  When run it reports its name as being “USB Bootloader v2.9j”. You can’t just move it elsewhere in your file system, as it seems to rely on one or more DLLs in the MLA directory structure. So instead, create a link to it in the directory you like to work in (such as the directory containing the project files for your PIC project files).

The bootloader firmware

Navigate to “C:microchip_solutions_v2013-06-15USBDevice – BootloadersHID”. Here you will find a bunch of “.hex” files. The file “USB Device – HID – HID Bootloader – C18 – PIC18F4550.hex” is the bootloader firmware that you need to download to your PIC18F2550. Yes – this file works with both a PIC18F4550 and a PIC18F2550. You will need to use the PICkit programmer to get this firmware into the PIC chip.

The firmware requires that your board uses a 20MHz crystal, and the bootloader switch is connected to pin RB4.

To program the bootloader firmware into the PIC, do the following:

  1. Start the MPLAB IDE
  2. Use Configure -> Select Device, to select PIC18F2550 as the target device
  3. Use Programmer -> Select Programmer, to select the PICkit 3 (or other programmer that you might be using)
  4. Use File -> Import, to read the bootload firmware “.hex” file into MPLAB
  5. Use Programmer -> Program, to program and verify the PIC

Testing the bootloader functionality

With the bootloader firmware loaded in the PIC, and the PIC’s pin RB4 held low, reset the PIC. This will run the bootloader code inside the PIC.

Now start the bootloader application on your PC.  The screen should appear as follows:

hid-bootloader-application-screen-shotThis shows us that both pieces of bootloader software (the application on the PC, and the firmware on the PIC) are talking to each other.

You’re now *almost* ready to download user programs (in “.hex” format) to the PIC via the bootloader. The missing step is that you need to modify your PIC program’s source code, and compile it using a modified linker script, so it is compatible with a PIC chip that has the bootloader resident in lower memory. These steps are explained below.

Under the Program -> Settings there are 3 checkboxes that you can set or clear. One of these says “EEPROM”. If this setting is ticked, EEPROM values that are set in your user code can be downloaded (via the bootloader) to the PIC. This is a nice touch. However, the trap here is that even if this box is not ticked, the bootloader firmware will still erase all EEPROM memory locations. There isn’t any option in the bootloader application that allows you to retain the existing EEPROM contents when downloading your user code to the PIC’s program memory. Just be aware of this, if your user code starts misbehaving because it expected the EEPROM contents to be preserved.

Linker script

You will need to add a linker script to your project in MPLAB. A bunch of sample linker scripts are provided by MLA in the directory “C:microchip_solutions_v2013-06-15USBDevice – BootloadersHIDFirmware – PIC18 Non-JLinker files for applications”.

I don’t recall all the steps I took in experimenting with linker scripts, but here’s the one I’ve been using successfully recently with the PIC18F2550:

LIBPATH .

FILES c018i.o
FILES clib.lib
FILES p18F2550.lib

CODEPAGE   NAME=bootloader START=0x0                 END=0xFFF          PROTECTED
CODEPAGE   NAME=vectors    START=0x1000              END=0x1029         PROTECTED
CODEPAGE   NAME=page       START=0x102A            END=0x7FFF
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

SECTION     NAME=USB_VARS   RAM=usb4

Remapping reset and interrupt vectors

Each time the PIC is reset, the bootloader code looks at input pin RB4.

If the pin in high, the bootloader code doesn’t execute, and instead the bootloader jumps to the start address of your user program at 0x1000. When high and low priority interrupts occur, the bootloader codes re-directs them to your user code at addresses 0x1008 and 0x1018 respectively.

So our user code needs to be written to take the above considerations into account.  The “main.c” files in various projects in MLA will give you guidance as to how to do this.

But the basic principles are as follows:

  1. your code should have jump vectors at 0x0000 (to 0x1000), 0x0008 (to 0x1008) and 0x0018 (to 0x1018) so that if programmed without the bootloader, the code will still operate normally. If the PIC is programmed via the bootloader, the bootloader will simply discard the attempts to write to addresses with the address space occupied by the bootloader
  2. your code’s startup address should be 0x1000
  3. your code should assume high priority interrupts will arrive at 0x1008 and low priority interrupts will arrive at 0x1018

Here’s an outline of the relevant parts of my main.c file that addresses the above structural points:

#define REMAPPED_RESET_VECTOR_ADDRESS             0x1000
#define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS    0x1008
#define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS     0x1018
#define APP_VERSION_ADDRESS                       0x1016 //Fixed location, so the App FW image version can be read by the bootloader.
#define APP_SIGNATURE_ADDRESS                     0x1006 //Signature location that must be kept at blaknk value (0xFFFF) in this project (has special purpose for bootloader).

//--------------------------------------------------------------------------
//Application firmware image version values, as reported to the bootloader
//firmware.  These are useful so the bootloader can potentially know if the
//user is trying to program an older firmware image onto a device that
//has already been programmed with a with a newer firmware image.
//Format is APP_FIRMWARE_VERSION_MAJOR.APP_FIRMWARE_VERSION_MINOR.
//The valid minor version is from 00 to 99.  Example:
//if APP_FIRMWARE_VERSION_MAJOR == 1, APP_FIRMWARE_VERSION_MINOR == 1,
//then the version is "1.01"
#define APP_FIRMWARE_VERSION_MAJOR  1   //valid values 0-255
#define APP_FIRMWARE_VERSION_MINOR  0   //valid values 0-99
//--------------------------------------------------------------------------

#pragma romdata AppVersionAndSignatureSection = APP_VERSION_ADDRESS
ROM unsigned char AppVersion[2] = {APP_FIRMWARE_VERSION_MINOR, APP_FIRMWARE_VERSION_MAJOR};

#pragma romdata AppSignatureSection = APP_SIGNATURE_ADDRESS
ROM unsigned short int SignaturePlaceholder = 0xFFFF;

#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
}

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
//These are your actual interrupt handling routines.

#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

Other things worth knowing

At startup, the bootloader firmware looks at address 0x1006 (the “APP_SIGNATURE_ADDRESS”) and expects to find the value 0x600D (the “APP_SIGNATURE_VALUE”) here. If a different value is found, the firmware assumes there is no valid application loaded in the PIC (apart from the bootloader itself), and will therefore enter the bootloader even if the input switch was not pressed. For this reason, it is essential that your application loads address 0x1006 with the value 0x600D. Unfortunately some of Microchip’s own sample code does not do this (see the code immediately above!) and is therefore not HID bootloader compatible.

The bootloader application gives you an option to download PIC configuration settings via the bootloader. This is a very dangerous practice, and generally you shouldn’t select this option unless you really know what you are doing. One of the limitations of using a bootloader is that your user code should use the same configuration settings as the bootloader. If you do choose to download new configuration setting via the bootloader, those new settings will also be used by the bootloader istelf (when it is next restarted). If they are incompatible with the settings needed for the bootloader to function properly, you will have “bricked” your PIC and will need to re-program the bootloader into it using a PICkit programmer.

The source code for the bootloader firmware is available. You can modify it if needed (for example, to use an input other than RB4 as the input switch). However, if you compile it with the regular (free) C18 compiler, it won’t fit in the required 0x1000 bytes. So you’ll need to allocate extra space (perhaps a total of 0x1100 bytes) and make all necessary adjustments to the linker script and user source code files to accommodate the changed address boundaries.

Some people prefer to use v2.6 of the bootloader application. There are obvious user-interface differences between v2.6 and v2.9j, but I don’t have a strong preference for one or the other. Consider trying them both out. One really nice thing about v2.6 is that you can download and run this as a stand-alone program (ie without downloading and installing the whole “Microchip Libraries for Applications” environment). I found a copy on RADO’s webpage (Look for the link to “USB HID bootloader and application template package.zip”).

There are many other helpful articles out there in relation to USB bootloaders for the PIC18F4550. Some links are provided below.

Customising the HID bootloader firmware

The HID bootloader firmware can be customised if necessary, to suit the hardware design that you are using your PIC in.

For example, the pre-compiled “.hex” firmware file makes the following (non-changeable) assumptions your hardware, if you are using a PIC18F2550 or PIC18F4550:

  1. The bootloader switch is connected to pin RB4
  2. The status LED is connected to pin D0 (which doesn’t exist on a PIC18F2550)
  3. The PIC has a 20 MHz crystal connected to it

If your hardware can’t support the 3 criteria above, you will need to modify the HID bootloader project files, and re-compile the project.

Obtaining the HID bootloader firmware project files

Microchip have made the HID bootloader firmware source code available as part of the MLA distribution in the directory “C:\microchip_solutions_v2013-06-15\USB\Device – Bootloaders\HID”.

For the PIC18F2550 and PIC18F4550, the relevant MPLAB 8 Project File is “HID Bootloader PIC18F4550 Family.mcp”, which you can find in the subdirectory “Firmware – PIC18 Non-J”.

Compiling the HID bootloader firmware

Once you’ve opened the Project, you need to make sure you have the correct PIC selected in MPLAB. So go to “Configure -> Select Device” and make sure you have the PIC18F4550 selected in the “Device” drop-down box. Use this setting even if you are compiling for the PIC18F2550, as the project will generate errors during the build phase if you select PIC18F2550 here. The bootloader (even when configured for a PIC18F4550) will compile and work just fine in the PIC18F2550. You will get an error message that says “Target Device ID (00001240) does not match expected Device ID (00001200)”, but you can proceed to program the PIC18F2550 and it will work just fine.

MPLAB 8 with C18 can re-compile the HID bootloader firmware. Unfortunately the “standard” edition of the compiler is crippled (optimisations are disabled) and the resulting complied code won’t fit in the first 0x1000 bytes of the PIC’s program memory. You could fix this by changing various vectors etc in the firmware project (so the compiled firmware occupies the first 0x1100 bytes of program memory). However, if you are operating the C18 compiler in the time-limited “evaluation mode” (also known as “extended mode”) that automatically applies when you first install the compiler, the compiled code will fit in the first 0x1000 bytes of program memory.

It is easy to verify whether the compiler is in “standard” or “evaluation” mode. If it is in evaluation mode, you will get the following messages in the MPLAB “output” window during the build phase:

MPLAB C18 3.47 (evaluation)
Copyright 2000-2011 Microchip Technology Inc.
Days remaining until evaluation becomes feature limited:  2

It is also easy to verify whether your compiled code crosses the 0x1000 boundary. To check this, in MPLAB go to “View -> Program Memory” and scroll down until you can see the code at 0x1000. With the un-modified HID bootloader firmware, after compilation you should see the final byte of the bootloader code at 0x0FB0. There will then be some NOPs up to 0x0FFF, and then a “GOTO 0x1C” instruction at 0x1000 (this GOTO instruction will ultimately be over-written by the user code that you download to the PIC using the bootloader firmware).

Changing the I/O pin assignments

The following notes should be helpful, if you want to customise the HID bootloader firmware, to make changes to suit the I/O assignment that your project uses.

  1. The Project selects the I/O pins to use for the bootloader switch and status LED based on the “hardware platform” that you are using. It determines the hardware platform based on the device type you have selected in MPLAB.  The code for determining the hardware platform is at lines 71-80 of the file “usbcfg.h. Assuming you have selected a PIC18F2550 or PIC18F4550 in MPLAB, the Project defines “PIC18F4550_PICDEM_FS_USB” (see lines 72 and 73) and this is used elsewhere in the Project to select the I/O pins to be used.
  2. The Project will require Pins RA1 and RA2 for USB-related functionality, if the symbols “USE_SELF_POWER_SENSE_IO” and/or “USE_USB_BUS_SENSE_IO” are defined. Fortunately these are commented out by default (see lines 52 and 53 of usbcfg.h), so these pins are not normally needed by the bootloader firmware.
  3. To change the I/O pins used for the bootloader switch and/or the status LED, edit lines 68 to 80 of “io_cfg.h”.
  4. If you want to disable the output LED, you can do this by commenting out “#define ENABLE_USB_LED_BLINK_STATUS” at line 48 of “usbcfg.h”. This define statement is enabled by default. If you disable it, the size of the compiled firmware will be reduced a little, as the LED output code is not compiled into the firmware.

The original version of lines 68 to 80 of “io_cfg.h” are shown below:

    /** L E D ***********************************************************/
    #define mInitAllLEDs()      LATD &= 0xFE; TRISD &= 0xFE;
    #define mLED_1              LATDbits.LATD0
    #define mLED_1_On()         mLED_1 = 1;
    #define mLED_1_Off()        mLED_1 = 0;
    #define mLED_1_Toggle()     mLED_1 = !mLED_1;
    
    /** S W I T C H *****************************************************/
    #define mInitAllSwitches()  {mInitSwitch2();}
    #define mInitSwitch2()      {ADCON1 = 0x0F;}
    #define sw2                 PORTBbits.RB4
    #define mDeInitSwitch2()    {ADCON1 = 0x07;}
    /********************************************************************/

Changing the configuration bits

If you want to change the configuration bits that are used by the bootloader firmware, you will need to edit the block of code at lines 168 to 219 of “main.c”.

The original version of lines 168 to 219 of “main.c” are shown below:

/** C O N F I G U R A T I O N ************************************************/
// Note: For a complete list of the available config pragmas and their values, 
// see the compiler documentation, and/or click "Help --> Topics..." and then 
// select "PIC18 Config Settings" in the Language Tools section.

#if defined(PIC18F4550_PICDEM_FS_USB)   // Configuration bits for PICDEM FS USB Demo Board
 	#if defined(__18F4550) || defined(__18F4553)
        #pragma config PLLDIV   = 5     // (20 MHz crystal on PICDEM FS USB board)
        #pragma config CPUDIV   = OSC1_PLL2	
        #pragma config USBDIV   = 2     // Clock source from 96MHz PLL/2
        #pragma config FOSC     = HSPLL_HS
        #pragma config FCMEN    = OFF
        #pragma config IESO     = OFF
        #pragma config PWRT     = OFF
        #pragma config BOR      = ON
        #pragma config BORV     = 3
        #pragma config VREGEN   = ON    //USB Voltage Regulator
        #pragma config WDT      = OFF
        #pragma config WDTPS    = 32768
        #pragma config MCLRE    = ON
        #pragma config LPT1OSC  = OFF
        #pragma config PBADEN   = OFF   //NOTE: modifying this value here won't have an effect
                                        //on the application.  See the top of the main() function.
                                        //By default the RB4 I/O pin is used to detect if the
                                        //firmware should enter the bootloader or the main application
                                        //firmware after a reset.  In order to do this, it needs to
                                        //configure RB4 as a digital input, thereby changing it from
                                        //the reset value according to this configuration bit.
//      #pragma config CCP2MX   = ON
        #pragma config STVREN   = ON
        #pragma config LVP      = OFF
//      #pragma config ICPRT    = OFF   // Dedicated In-Circuit Debug/Programming
        #pragma config XINST    = OFF   // Extended Instruction Set
        #pragma config CP0      = OFF
        #pragma config CP1      = OFF
//      #pragma config CP2      = OFF
//      #pragma config CP3      = OFF
        #pragma config CPB      = OFF
//      #pragma config CPD      = OFF
        #pragma config WRT0     = OFF
        #pragma config WRT1     = OFF
//      #pragma config WRT2     = OFF
//      #pragma config WRT3     = OFF
        #pragma config WRTB     = OFF   // Boot Block Write Protection
        #pragma config WRTC     = OFF
//      #pragma config WRTD     = OFF
        #pragma config EBTR0    = OFF
        #pragma config EBTR1    = OFF
//      #pragma config EBTR2    = OFF
//      #pragma config EBTR3    = OFF
        #pragma config EBTRB    = OFF
	#endif	//18F4550 and 18F4553

Care should be taken in making any changes to these configuration bits, as you will need to consider the impact of the changes on both the bootloader firmware itself and the user code that will be downloaded and executed later. But if (for example) you need to make changes to the PLL settings to accommodate a crystal other than 20 MHz, this is the place to do it.

Downloads

Below are 4 versions of the HID bootloader firmware, to suit a PIC18F2550 or PIC18F4550 with a 20 MHz crystal.

The first file was compiled using Microchip’s standard configuration. The other 3 versions were compiled using modified PIC source code and the process described above.

File Description
hid-bootloader-pic18f4550-family-sw-rb4-led-rd0.hex Switch=RB4, LED=RD0
hid-bootloader-pic18f4550-family-sw-rb4-led-disabled.hex Switch=RB4, LED=Disabled
hid-bootloader-pic18f4550-family-sw-rc2-led-rc0.hex Switch=RC2, LED=RC0
hid-bootloader-pic18f4550-family-sw-rc2-led-disabled.hex Switch=RC2, LED=Disabled

Useful links

Microchip’s Microchip Libraries for Applications

RADO’s webpage regarding Microchip’s USB HID bootloader

Digital DIY’s webpage on PIC18 USB Bootloaders

Hade’s webpage on USB bootloading (covers both CDC and HID bootloaders)

Brian Schmalz’s UBW32 webpage (which includes bootloader downloads)

 

 

 

LEAVE A COMMENT