Feb 04

Microchip Harmony and Bootloaders

I’ve been messing around with a Digilent Max32, and have been using Microchip’s MPLAB Harmony framework to program the device.  The framework is rather nice, but it can be a little confusing at times.  It took me a while to figure out how to work the bootloader on it, so this post will go into how to setup your projects.

Prerequisites:

  1. PIC32 based device.  In our case, this will be the Max32, but any PIC based device should work fine.
  2. MPLAB X, Microchip’s dev platform based on Netbeans.
  3. Microchip XC32 compiler.  This should be a part of your MPLAB X install, but if not you can download it from Microchip’s website.  I am using version 1.42.
  4. PIC Programmer.  I have an ICD3, but it should be possible to use a PICKit programmer as well.

Part I: Install Harmony

Before we can mess around with Harmony, we need to install the latest version.  In our case, we will be using the current beta version of 2.0.

  1. Download the new Harmony Configurator from Microchip’s website.
  2. Go to the ‘Downloads’ tab, and download the proper version for your system

    Step 1 and 2 – Download Harmony and Configurator

  3. Run the installer for Harmony.  Make sure that you remember where you installed it, you will need this information later.
  4. In MPLAB X, go to Tools->Plugins.

    Plugins location

  5. Go to the ‘Downloaded’ tab
  6. Click ‘Add Plugins’
  7. Browse to where you downloaded the Harmony plug-in to, and select it.

    How to install the downloaded plugin

  8. The plugin will now show up in your Plugins window.  Click the ‘Install’ button.
  9. The plugin will install.  Once it does, you will have to restart MPLAB X.

Part II: Create Bootloader project

Note that before you come here, you must have MPLAB X Harmony and the proper Harmony plugin installed.

  1. Create a new project by going to File->New Project.
  2. Select ’32-bit MPLAB Harmony Project’ and click ‘Next’

    Basic project configuration

  3. Fill in all of the information on this screen.  Don’t forget to set the PIC device that you are using.
  4. If the Harmony configurator does not open up for you automatically, go to Tools -> Embedded -> MPLAB Harmony Configurator

    Open up the Harmony Configurator if it closes

  5. Under the ‘Application Configuration’ section, we want to create an application(we will call it max32_bl) and click ‘Generate application code for selected Harmony components’.  We want to generate code for the bootloader in this area.

    Bootloader configuration #1

  6. Under the ‘Harmony Framework Configuration’ section, select ‘Bootloader Library’.  We will be using the USART bootloader.  Under the ‘Bootloader or Applicaton?’ section, select ‘Bootloader’.  Also select ‘Ports’ under ‘System Services’

    Bootloader configuration #2

  7. Click the ‘Generate Code’ button on the Configurator screen.  We will have a number of new C/H files for us to edit.  Also, there is now a custom linker script for us under the ‘Linker Files’ folder.
  8. Now that code has been generated, there are a few things that we want to do to make it clear what is happening.  First, we don’t need the legacy bootloader options.  So under ‘Header Files’, go to app/system_config/default/system_config.h, and comment out the preprocessor macros BOOTLOADER_LEGACY and BTL_SWITCH

    Delete/comment out these macros

  9. The first thing for us to edit in the code is to open up our max32_bl.c file.  This file contains generated code for us to modify.  The first thing is that we need to move the line that says
    BOOTLOADER_ForceBootloadRegister(MAX32_BL_Bootloader_ForceEvent);

    to be in the

    MAX32_BL_Initialize

    function.  Otherwise, the bootloader code will not call our ForceEvent function.

  10. Now, let’s add a splash screen.  For now, we can simply add a simple function to print out to the UART, and then call that function once in the MAX32_BL_Tasks function, in the MAX32_BL_STATE_INIT case statement of our switch.  Here’s the simple UART printing function:
    static void writeStringToUART( const char* string ){
     int pos = 0;
     
     while( string[ pos ] != 0 ){
     while( DRV_USART0_TransmitBufferIsFull() ){}
     DRV_USART0_WriteByte( string[ pos++ ] );
     }
    }
  11. You should now be able to program your device with the bootloader.  Open up a terminal with a terminal emulator such as Putty, and if you have done everything correctly you should see a splash screen whenever you reset the processor.  On the MAX32, this is accomplished by pressing the reset switch.
  12. Let’s also make sure that we can get to the bootloader manually.  To do this, we will have to configure one pin on the chip as a GPIO input, with a pull-up resistor.  Go back to the Harmony Configurator screen, and go to the ‘Pin Settings’ tab.

    Pin settings

  13. Choose a pin to use as an input.  In my case, I chose RG6.  This corresponds to pin 52 on the Max32 board.  I will also enable the pull up resistor on this pin, so that when the pin is shorted to ground the bootloader will start.
  14. Go back to our max32_bl file, and modify MAX32_BL_Bootloader_ForceEvent to look like the following(change your pin if you used a different one).
    int MAX32_BL_Bootloader_ForceEvent(void)
    {
     int rg6 = PLIB_PORTS_PinGet (PORTS_ID_0, PORT_CHANNEL_G, PORTS_BIT_POS_6);
     
     if( !rg6 ){
     writeStringToUART( "Button held, forcing bootloader\n" );
     return 1;
     }
     
     /* Check the trigger memory location and return true/false. */
     if (*(uint32_t *)MAX32_BL_BOOTLOADER_TRIGGER_MEMORY_ADDRESS == 0xFFFFFFFF)
     return (1);
     else
     return (0);
    }
  15. Build and re-flash your device.  When you short pin RG6 to GND, the string “Button held, forcing bootloader” should now print out on your console when the bootloader starts.

Here’s the entire file for reference:

/*******************************************************************************
  MPLAB Harmony Application Source File
  
  Company:
    Microchip Technology Inc.
  
  File Name:
    max32_bl.c

  Summary:
    This file contains the source code for the MPLAB Harmony application.

  Description:
    This file contains the source code for the MPLAB Harmony application.  It 
    implements the logic of the application's state machine and it may call 
    API routines of other MPLAB Harmony modules in the system, such as drivers,
    system services, and middleware.  However, it does not call any of the
    system interfaces (such as the "Initialize" and "Tasks" functions) of any of
    the modules in the system or make any assumptions about when those functions
    are called.  That is the responsibility of the configuration-specific system
    files.
 *******************************************************************************/

// DOM-IGNORE-BEGIN
/*******************************************************************************
Copyright (c) 2013-2014 released Microchip Technology Inc.  All rights reserved.

Microchip licenses to you the right to use, modify, copy and distribute
Software only when embedded on a Microchip microcontroller or digital signal
controller that is integrated into your product or third party product
(pursuant to the sublicense terms in the accompanying license agreement).

You should refer to the license agreement accompanying this Software for
additional information regarding your rights and obligations.

SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF
MERCHANTABILITY, TITLE, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE.
IN NO EVENT SHALL MICROCHIP OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER
CONTRACT, NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR
OTHER LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE OR
CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT OF
SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
(INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.
 *******************************************************************************/
// DOM-IGNORE-END


// *****************************************************************************
// *****************************************************************************
// Section: Included Files 
// *****************************************************************************
// *****************************************************************************

#include "max32_bl.h"

#include <stdio.h>

// *****************************************************************************
// *****************************************************************************
// Section: Global Data Definitions
// *****************************************************************************
// *****************************************************************************

// *****************************************************************************
/* Application Data

  Summary:
    Holds application data

  Description:
    This structure holds the application's data.

  Remarks:
    This structure should be initialized by the APP_Initialize function.
    
    Application strings and buffers are be defined outside this structure.
*/

MAX32_BL_DATA max32_blData;
#define MAX32_BL_BOOTLOADER_TRIGGER_MEMORY_ADDRESS					0x9D000000		

static void writeStringToUART( const char* string ){
    int pos = 0;
    
    while( string[ pos ] != 0 ){
        while( DRV_USART0_TransmitBufferIsFull() ){}
        DRV_USART0_WriteByte( string[ pos++ ] );
    }
}

static void writeIntAsCharToUART( int i ){
    while( DRV_USART0_TransmitBufferIsFull() ){}
    DRV_USART0_WriteByte( i + 48 );
}

static void waitForUARTFlush(){
    while( !PLIB_USART_TransmitterIsEmpty( USART_ID_1 ) ){}
}

// *****************************************************************************
// *****************************************************************************
// Section: Application Callback Functions
// *****************************************************************************
// *****************************************************************************
/******************************************************************************
  Function:
    static void MAX32_BL_Bootloader_ForceEvent (void)
    
   Remarks:
    Sets a trigger to be passed to force bootloader callback.
	Run bootloader if memory location == '0xFFFFFFFF' otherwise jump to user 
	application.
*/ 
int MAX32_BL_Bootloader_ForceEvent(void)
{
    int rg6 = PLIB_PORTS_PinGet (PORTS_ID_0, PORT_CHANNEL_G, PORTS_BIT_POS_6);
    
    if( !rg6 ){
        writeStringToUART( "Button held, forcing bootloader\n" );
        return 1;
    }
    
    /* Check the trigger memory location and return true/false. */
    if (*(uint32_t *)MAX32_BL_BOOTLOADER_TRIGGER_MEMORY_ADDRESS == 0xFFFFFFFF)
        return (1);
    else
		return (0);
}

/* TODO:  Add any necessary callback functions.
*/

// *****************************************************************************
// *****************************************************************************
// Section: Application Local Functions
// *****************************************************************************
// *****************************************************************************


/* TODO:  Add any necessary local functions.
*/


// *****************************************************************************
// *****************************************************************************
// Section: Application Initialization and State Machine Functions
// *****************************************************************************
// *****************************************************************************

/*******************************************************************************
  Function:
    void MAX32_BL_Initialize ( void )

  Remarks:
    See prototype in max32_bl.h.
 */

void MAX32_BL_Initialize ( void )
{
    /* Place the App state machine in its initial state. */
    max32_blData.state = MAX32_BL_STATE_INIT;

    
    /* TODO: Initialize your application's state machine and other
     * parameters.
     */
    BOOTLOADER_ForceBootloadRegister(MAX32_BL_Bootloader_ForceEvent);
}

static void printSplash(){    
    writeStringToUART( "MAX32 Bootloader\n" );
    writeStringToUART( "Version " );
    writeIntAsCharToUART( MAJOR_VERSION );
    writeStringToUART( "." );
    writeIntAsCharToUART( MINOR_VERSION );
    writeStringToUART( "\n" );
    writeStringToUART( __DATE__ );
    writeStringToUART( " " );
    writeStringToUART( __TIME__ );
    writeStringToUART( "\n" );
    
    waitForUARTFlush();
}

/******************************************************************************
  Function:
    void MAX32_BL_Tasks ( void )

  Remarks:
    See prototype in max32_bl.h.
 */

void MAX32_BL_Tasks ( void )
{

    /* Check the application's current state. */
    switch ( max32_blData.state )
    {
        /* Application's initial state. */
        case MAX32_BL_STATE_INIT:
        {
            bool appInitialized = true;
       
        
            if (appInitialized)
            {
            
                max32_blData.state = MAX32_BL_STATE_SERVICE_TASKS;
                
                //print splash screen.  this will happen only once
                printSplash();
            }
            break;
        }

        case MAX32_BL_STATE_SERVICE_TASKS:
        {
        
            break;
        }

        /* TODO: implement your application state machine.*/
        

        /* The default state should never be executed. */
        default:
        {
            /* TODO: Handle error in application's state machine. */
            break;
        }
    }
}

 

/*******************************************************************************
 End of File
 */

Part III: Create our main program

Now that we have our bootloader setup, we need to create the main program that will get loaded by the bootloader.

  1. Create a new project by going to File->New Project.
  2. Select ’32-bit MPLAB Harmony Project’ and click ‘Next’
  3. Fill in all of the information on this screen.  Don’t forget to set the PIC device that you are using.
  4. If the Harmony configurator does not open up for you automatically, go to Tools -> Embedded -> MPLAB Harmony Configurator
  5. Under the ‘Application Configuration’ section, we want to create an application(we will call it max32_program) and click ‘Generate application code for selected Harmony components’.  We want code for the Timer and USART to give us some code to work with.

    Main program configuration #1

  6. Under the ‘Harmony Framework Configuration’ section, select ‘Bootloader Library’.  We will be using the USART bootloader.  Under the ‘Bootloader or Applicaton?’ section, select ‘Application’

    Main program configuration #2

  7. Under the ‘Harmony Framework Configuration’ section, also make sure that you have drivers for the timer and USART 0 selected.  The USART should be running at the same baud rate as the bootloader.
  8. Go to the ‘Pin Settings’ page as we did before.  For the MAX32, pin RC1 is an output that drives an LED.  We will use RG6 as an input as we did before on the botloader.  Setup those settings.

    Pin diagram for the main program

  9. Click ‘Generate code’
  10. Your project is now created with some basic information.  Since we set up our timer to use interrupts, we now have a TimerCallback function in mainapp.c.  Let’s modify it to toggle the RC1 LED every time the timer is called.  The code will now look like this:
    static void TimerCallback ( uintptr_t context, uint32_t alarmCount )
    {
     static int state = 0;
     PLIB_PORTS_PinWrite (PORTS_ID_0, PORT_CHANNEL_C, PORTS_BIT_POS_1, state);
     state = !state;
    }
  11. Build your project.  The hex file that is generated is what you want to send to the bootloader.  Note that this hex file can’t be sent as-is, you must convert it to binary and use Microchip’s protocol.  See the next section for some discussion.

Part IV: Bootloading

Now that we have our bootloader and a program, we should try to actually load information onto the device using the bootloader.  To do this, we will need a program to write the information.  You can use either Microchip’s version in AN1388, or I wrote a version that is cross-platform compatible using my CSerial library.

Whichever method you use, once you have built the actual program you need to send the hex file across to get the bootloader to write the data.  If everything has gone correctly, you will then be able to re-set the board and have your application running.

One more tip: If you can program the entire chip at once(e.g. you have an ICD3), if you set the bootloader project as a loadable of your main project you can flash both projects to the chip at the same time.  Since they get flashed in different locations, there’s no conflict.  Note that this won’t work with the lower-end programmers.

 

This tutorial probably skims over a few steps, but it should at least be enough to get people started and working with Harmony and its bootloader.  Questions? Add a comment and I will see about expanding upon either this post or a later one.