Child pages
  • Implementing BLE OTA Firmware Updates on PSoC 6 with ModusToolbox

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Introduction

Just like any type of software, embedded firmware is never perfect on its initial release. Issues like memory leaks, deadlocking, and security flaws are frequently discovered and improvements in efficiency, performance, and feature-set are implemented in subsequent versions of the application. Unfortunately, once assembled and deployed in the field, the programming interfaces on embedded devices (eg. JTAG or SWD interfaces) are typically no longer accessible. It is therefore common practice to install a bootloader on the device alongside the application firmware which allows firmware updates to be performed over a generic communication interface. While it's possible to utilize physical ports like USB, UART, or I2C for this purpose, remote devices often utilize a wireless channel such as BLE. Such updates are normally referred to as Over the Air (OTA) updates. 

Cypress's PSoC 6 line of microcontrollers (MCUs) were designed specifically with the Internet of Things (IoT) in mind. With an offering that includes on-chip BLE, dual CPUs, low-power operation, and many security features; these devices will be a perfect fit for many remote, embedded applications. To enhance these systems with OTA updating capability, Cypress provides their DFU (Device Firmware Update) library with the ModusToolbox development tools. This tutorial demonstrates how to use this library and the BLE stack to create an embedded application on a PSoC 6 device capable of receiving remote firmware updates. 

Requirements

Hardware

Info

Kit revision *G was used to create and test the procedures in this tutorial, but any revision greater than *C should work. 

Software

  • ModusToolbox 2.0
  • CySmart 1.3

Overview

This tutorial is organized into 4 sections. The first section demonstrates how to create a basic bootloader from an empty project. This basic bootloader's minimal design is meant to highlight the core elements required to implement a DFU operation. In the second section, various features intended to improve performance and usability are added to the basic bootloader, creating a more advanced version. The third section turns to the other half of OTA updates: the loadable application. In the same spirit as the basic bootloader, this section demonstrates the minimum steps required to turn a standard ModusToolbox project into a basic loadable application that can be transferred from the host to the target device. The fourth section discusses the addition of useful features to the basic loadable application and demonstrates how to implement the most complicated of them, i.e. the Immediate Alert Service (IAS). The result is an embedded device capable of remote deployment and the ability to receive firmware updates over a BLE connection. 

Terminology

Throughout this tutorial, the following terms are used to refer to the various DFU components. The first application installed on the PSoC 6 flash memory, labeled App0, is referred to as either the bootloader or the DFU application. The second application, labeled App1, is referred to as either the main application or the loadable application. The act of loading a new main application on the PSoC 6 device via an OTA update is referred to as a DFU operation. The host is the system that provides the firmware image and the target is the device receiving it. 

Anchor
basic_bootloader
basic_bootloader
Creating a Basic BLE Bootloader from an Empty Project

This section addresses the core elements required to implement an OTA firmware update via BLE. It demonstrates how to create a simple bootloader which executes after the device is reset (including power-on reset, software reset, watchdog reset, etc.) and continues executing indefinitely until the user either uses the DFU service to load a new application into flash memory or manually signals that the bootloader should transfer control to the application previously loaded to memory (by pressing SW2 on the CY8CKIT-062-BLE in this example). The flowchart representing this functionality is shown in Figure 1. Note that this project is based on the Quick Start Guide section of Cypress's DFU Middleware Library documentation, which describes how to create an I2C bootloader.


Figure 1: Flowchart of basic bootloader application

It is not required that this bootloader be created by starting with an empty project. For the sake of completeness, however, the simple BLE bootloader in this tutorial is created from scratch to demonstrate the essential configuration steps required to implement a functional bootloader. These steps include adding and setting up the DFU and BLE Middleware libraries, configuring the pushbutton pin as input, and editing both the linker script and makefile to successfully build the project.

Step 1: Create an Empty Project in ModusToolbox IDE

a) In ModusToolbox IDE, choose File > New > ModusToolbox IDE Application.

b) Choose your target board for the application (this tutorial uses the "CY8CKIT-062-BLE" board) and click Next.

c) Select "Empty PSoC6 App" as the starter application and change its name to something more descriptive (e.g. "bootloader_basic"). Click Next.

d) Verify the project information is correct and click Finish. If you are following along, it should match the project summary shown in Figure 2.


Figure 2: New project summary for simple bootloader

Step 2: Add the Required Libraries

Cypress provides the Device Firmware Update (DFU) library which allows the user to load firmware images via BLE, I2C, SPI, UART, or USB. Because we are implementing a BLE bootloader, we will need to include this library in our project along with the Bluetooth Low Energy Middleware library (BLESS, which I believe stands for BLE Subsystem).

a) In the Project Explorer, right click on the project name and choose ModusToolbox > Library Manager.

b) Under the Libraries tab, check the boxes next to the "bless" and "dfu" PSoC 6 Middleware libraries. Click Apply. You should be presented with a summary of changes matching Figure 3.


Figure 3: Summary of libraries to be added to the simple bootloader project

c) Click OK and wait for the changes to successfully be applied. Once complete, close the Library Manager.

Step 3: Copy DFU Library Files

To include the DFU library in the project's build and specify the type of application being created, some files need to be manually copied info the directory containing the main.c file (in this case, the project's root directory). Because we are creating a bootloader and not the main application, we need to copy the "app0" linker script rather than the "app1" linker script. Because the firmware will be transferred via BLE, we need to copy the transport_ble.* files and ensure that none of the other transport_* files are copied as well. The explicit instructions are as follows.

a) Copy the files dfu_user.h and dfu_user.c from libs/dfu/config/ and paste them in the directory which contains main.c.

b) Copy the files transport_ble.h and transport_ble.c from libs/dfu/config and paste them in the directory which contains main.c

c) Copy the file dfu_cm4_app0.ld from libs/dfu/linker_scripts/TOOLCHAIN_GCC_ARM and paste it in the same folder as main.c

These steps are summarized in Figure 4. Note the contents of the project root directory (which contains main.c) before and after the files are copied.


Figure 4: Copying files from DFU library directory to the project's root directory.

Step 4: Add DFU Capability to main.c

In ModusToolbox IDE, open main.c and add the following code snippets. Altogether, they will initialize and execute the DFU service, which continuously processes bootloader commands and switches to the main application once a new firmware image is successfully loaded.

a) Add #include "transport_ble.h" to the beginning of the file along with the other include directives.

b) Add the DFU reset handler to be used when switching to the main application.

Code Block
languagecpp
/*******************************************************************************
* Function:     Cy_OnResetUser
* Author:       Cypress Semiconductor
* Description:	  This function is called at the start of Reset_Handler(). DFU 
*               requires it to call Cy_DFU_OnResetApp0() in app#0.
* Date:         05-20-19
*******************************************************************************/
void Cy_OnResetUser(void)
{
    Cy_DFU_OnResetApp0();
}

c) Declare/initialize the DFU variables, call Cy_DFU_Init(), and call Cy_DFU_TransportStart() before entering the main loop. 

Code Block
languagecpp
cy_rslt_t           result;
uint32_t            count;               // Used to count seconds
cy_en_dfu_status_t  status;              // Status codes for DFU API
uint32_t            state;               // DFU state
const uint32_t      paramsTimeout = 20u; // Cy_DFU_Continue() timeout (ms)

/* Buffer to store DFU commands */
CY_ALIGN(4) static uint8_t buffer[CY_DFU_SIZEOF_DATA_BUFFER];

/* Buffer for DFU data packets for transport API */
CY_ALIGN(4) static uint8_t packet[CY_DFU_SIZEOF_CMD_BUFFER ];

/* DFU params, used to configure DFU */
cy_stc_dfu_params_t dfuParams;

/* Initialize dfuParams structure */
dfuParams.timeout          = paramsTimeout;
dfuParams.dataBuffer       = &buffer[0];
dfuParams.packetBuffer     = &packet[0];
status = Cy_DFU_Init(&state, &dfuParams);

/* Initialize the device and board peripherals */
result = cybsp_init() ;
if (result != CY_RSLT_SUCCESS)
{
    CY_ASSERT(0);
}

__enable_irq();

/* Initialize DFU communication */
Cy_DFU_TransportStart();

count = 0;
for(;;)
{
}

d) Add the host command/response protocol processing to the main loop.

Code Block
languagecpp
status = Cy_DFU_Continue(&state, &dfuParams);
++count;
if (state == CY_DFU_STATE_FINISHED)
{
   /* Finished loading the application image */
   /* Validate DFU application, if it is valid then switch to it */
   status = Cy_DFU_ValidateApp(1u, &dfuParams);
   if (status == CY_DFU_SUCCESS)
   {
       Cy_DFU_TransportStop();
       Cy_DFU_ExecuteApp(1u);
   }
   else if (status == CY_DFU_ERROR_VERIFY)
   {
       /*
       * Restarts loading, an alternatives are to Halt MCU here
       * or switch to the other app if it is valid.
       * Error code may be handled here, i.e. print to debug UART.
       */
       status = Cy_DFU_Init(&state, &dfuParams);
       Cy_DFU_TransportReset();
   }
}
else if (state == CY_DFU_STATE_FAILED)
{
   /* An error has happened during the loading process */
   /* Handle it here */
   /* In this Code Example just restart loading process */
   status = Cy_DFU_Init(&state, &dfuParams);
   Cy_DFU_TransportReset();
}
else if (state == CY_DFU_STATE_UPDATING)
{
   uint32_t passed5seconds = (count >= (5000ul/paramsTimeout)) ? 1 : 0;
   /*
   * if no command has been received during 5 seconds when the loading
   * has started then restart loading.
   */
   if (status == CY_DFU_SUCCESS)
   {
       count = 0u;
   }
   else if (status == CY_DFU_ERROR_TIMEOUT)
   {
       if (passed5seconds != 0u)
       {
           count = 0u;
           Cy_DFU_Init(&state, &dfuParams);
           Cy_DFU_TransportReset();
       }
   }
   else
   {
       count = 0u;
       /* Delay because Transport still may be sending error response to 
        * a host 
        */
       Cy_SysLib_Delay(paramsTimeout);
       Cy_DFU_Init(&state, &dfuParams);
       Cy_DFU_TransportReset();
   }
}

Anchor
basic_5
basic_5
Step 5: SW2 Configuration and Polling

At this point, the only way to exit the bootloader and switch to the main application is by loading a new firmware image. The user should be allowed to simply switch to the previously loaded application without having to perform a complete DFU operation. The simplest way to do this is by configuring a GPIO pin as an input pin (in this case, the pin connected to the user button) which the user can use to signal an application switch. Note that on the CY8CKIT-062-BLE, the user button (SW2) is connected to pin P0.4. Other methods of switching to the main application, including a timeout and a BLE IAS signal, are covered in the Creating an Advanced Bootloader section.

a) In the Project Explorer, right click on the project name and choose ModusToolbox > Device Configurator.

b) Under the Pins tab, check the box next to "P0[4]" and give the pin a name. To use the code snippet in substep e without modification, use the name PIN_SW2 as shown in Figure 5.

c) On the right-hand side of the configurator, change the "Drive Mode" parameter to "Resistive Pull-Up, Input buffer on".


Figure 5: Using the Device Configurator to configure P0.4 as an input pin.

d) Choose File > Save and close the Device Configurator.

Anchor
basic_5_e
basic_5_e
e) Add the following code to the main loop along with the DFU protocol processing operation. Once the button is pressed and the application stored in memory is validated, a reset will occur and the main application will begin executing.  

Code Block
languagecpp
/* If SW2 pressed, switch to App1 if it is valid */
if (Cy_GPIO_Read(PIN_SW2_PORT, PIN_SW2_PIN) == 0u)
{
   /* 50 ms delay for button debounce on button press */
   Cy_SysLib_Delay(50u);
   if (Cy_GPIO_Read(PIN_SW2_PORT, PIN_SW2_PIN) == 0u)
   {
       while (Cy_GPIO_Read(PIN_SW2_PORT, PIN_SW2_PIN) == 0u)
       {   /* 50 ms delay for button debounce on button release */
           Cy_SysLib_Delay(50u);
       }
       /* Validate and switch to App1 */
       status = Cy_DFU_ValidateApp(1u, &dfuParams);
       if (status == CY_DFU_SUCCESS)
       {
           Cy_DFU_TransportStop();
           Cy_DFU_ExecuteApp(1u);
       }
   }
}

Anchor
basic_6
basic_6
Step 6: BLE Configuration

To use BLE as the DFU communication interface, the Bootloader service must be added to the GATT server profile (substeps a-b). The rest of the settings in this step are optional but recommended. 

a) In the Project Explorer, right-click on the project name and choose ModusToolbox > Bluetooth Configurator (new configuration).

b) Under the GATT Settings tab, right-click on the "Server" profile and choose Add Service > Bootloader.

c) Increase the "Attribute MTU size" to 512 bytes. Figure 6 shows the GATT Settings after all changes are made.


Figure 6: Adding the Bootloader service using the Bluetooth Configurator

Anchor
basic_6d
basic_6d
d) Switch to the GAP Settings tab In the General settings provide a value for "Device name". I went with "BLE DFU Device", as shown in Figure 7.


Figure 7: Providing a name for the bootloader device

e) In the Advertisement Settings change "Discovery mode" to "General" to disable the advertising timeout. This will allow the device to remain discoverable indefinitely and will simplify our code somewhat. Uncheck the "Enable Fast advertising timeout" and "Enable slow advertising interval" boxes to remove the resulting configuration errors (Figure 8).  


Figure 8: Configure the bootloader device to be continuously discoverable by choosing general discovery mode

f) In the Advertisement Packet options, check the box next to "Enable Local name" and make sure the "Local name type" is set to "Complete" (Figure 9).


Figure 9: Include the bootloader device name in the advertisement packet

g) In the Security Configuration 0 options; change "Security level" to "Unauthenticated pairing with encryption", "IO Capability" to "No Input No Output", and "Bonding" to "No Bond". These changes are shown in Figure 10.


Figure 10: Modified bootloader security configuration

h) Under the Link Layer Settings tab, change both the "Link layer max TX payload size" and "Link layer max RX payload size" to 251 bytes (Figure 11).


Figure 11: Link Layer Settings

i) Save the settings and close the Bluetooth Configurator to generate the configuration files.

j) Add the BLE stack event handler. This function based on the BLE_Find_Me example application and BLE_Battery_Level_FreeRTOS example application. Only the essential events are handled in this example. For more events and debugging information, see the Creating an Advanced Bootloader section.

Code Block
languagecpp
/*******************************************************************************
* Function:     AppCallBack
* Author:       Cypress Semiconductor (modified by Matt Mielke)
* Description:    This is an event callback function to receive events from the
*               BLE Component. Used in Cy_DFU_TransportStart()
* Date:         03-23-20
*******************************************************************************/
void AppCallBack(uint32_t event, void* eventParam)
{
    static cy_stc_ble_gap_sec_key_info_t keyInfo =
    {
        .localKeysFlag    = CY_BLE_GAP_SMP_INIT_ENC_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_IRK_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_CSRK_KEY_DIST,
        .exchangeKeysFlag = CY_BLE_GAP_SMP_INIT_ENC_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_IRK_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_CSRK_KEY_DIST |
                            CY_BLE_GAP_SMP_RESP_ENC_KEY_DIST |
                            CY_BLE_GAP_SMP_RESP_IRK_KEY_DIST |
                            CY_BLE_GAP_SMP_RESP_CSRK_KEY_DIST,
    };

    switch(event)
    {
        /**********************************************************************
         * General events
         *********************************************************************/

        /* This event is received when the BLE stack is started */
        case CY_BLE_EVT_STACK_ON:
        {
            /* Enter into discoverable mode so that remote can search it. */
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, 0u);

            Cy_BLE_GAP_GenerateKeys(&keyInfo);
            break;
        }

        /**********************************************************************
         * GAP events
         *********************************************************************/

        case CY_BLE_EVT_GAP_AUTH_REQ:
        {
            if (cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX].security
                == (CY_BLE_GAP_SEC_MODE_1 | CY_BLE_GAP_SEC_LEVEL_1))
            {
               cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX].authErr =
                   CY_BLE_GAP_AUTH_ERROR_PAIRING_NOT_SUPPORTED;
            }

            cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX].bdHandle =
               ((cy_stc_ble_gap_auth_info_t *)eventParam)->bdHandle;

            Cy_BLE_GAPP_AuthReqReply(&cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX]);
            break;
        }

        /* This event is triggered instead of 'CY_BLE_EVT_GAP_DEVICE_CONNECTED',
        * if Link Layer Privacy is enabled in component customizer
        */
        case CY_BLE_EVT_GAP_ENHANCE_CONN_COMPLETE:
        {
            /* sets the security keys that are to be exchanged with a peer
             * device during key exchange stage of the authentication procedure
             */
            keyInfo.SecKeyParam.bdHandle =
                (*(cy_stc_ble_gap_enhance_conn_complete_param_t *)eventParam).bdHandle;

            Cy_BLE_GAP_SetSecurityKeys(&keyInfo);
            break;
        }

        /* This event indicates security key generation complete */
        case CY_BLE_EVT_GAP_KEYS_GEN_COMPLETE:
        {
            keyInfo.SecKeyParam = (*(cy_stc_ble_gap_sec_key_param_t *)eventParam);
            Cy_BLE_GAP_SetIdAddress(&cy_ble_deviceAddress);
            break;
        }

        /* This event is generated when disconnected from remote device or
         * failed to establish connection
         */
        case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
        {
            /* Enter into discoverable mode so that remote can search it. */
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, 0u);

            break;
        }

        /**********************************************************************
         * GATT events
         *********************************************************************/

        /* This event is generated at the GAP Peripheral end after connection
         * is completed with peer Central device
         */
        case CY_BLE_EVT_GATT_CONNECT_IND:
        {
            appConnHandle = *(cy_stc_ble_conn_handle_t *)eventParam;
            break;
        }

        default:
        {
            break;
        }
    }
}

k) Add the function prototype for AppCallBack() to the top of the file.

Anchor
basic_7
basic_7
Step 7: Modify the Linker Script

This basic bootloader will require about 180 KiB of flash memory. However, as shown in Figure 12a, the unmodified linker script only allocates 64 KiB for the App0 region and leaves 248 KiB of memory unallocated. To keep things simple, we will expand the App0 region to include all this unused space. The resulting memory region (312 KiB) will be much larger than required, but that's not an issue because the bootloader will be flashed with the on-board KitProg3 programmer/debugger. The entire App1 section is what will be loaded during the BLE DFU operation, regardless of how small the application is. So, to keep the OTA update as short and power efficient as possible, the App1 region should only be slightly larger than the size of the main application. Because the basic loadable application will only be about 42 KiB, we'll leave the 64 KiB App1 region unchanged.

For some reason, the original linker script leaves an unallocated row at the top of the flash memory as shown in Figure 12a. In addition to expanding the App0 region, the following steps will offset the Metadata rows to utilize this otherwise wasted space. The resulting memory map is shown in Figure 12b.

Section


Column
width50%



Column
width325px

a) Original memory map


Column
width325px

b) Memory map after modifying linker script


Column
width50%



Figure 12: Flash memory regions (not to scale)

a) Open the linker script (dfu_cm4_app0.ld) and, in the memory section, change the length of the "flash_app0" region from 0x10000 to 0x4E000 to use up the empty space.

b) To move the metadata and the metadata copy sections to the last row in flash, change the "flash_boot_meta" origin from 0x100FFA00 to 0x100FFB00.

These changes are shown in the highlighted portions of Figure 13.


Figure 13: Modified memory section of dfu_cm4_app0.ld

Step 8: Modify the Makefile

Open the Makefile and navigate to the Advanced Configuration section.

a) Enable the "BLESS_HOST" and "BLESS_CONTROLLER" BLE stack components by appending them to the "COMPONENTS=" option in the Makefile.

Code Block
languagetext
COMPONENTS=BLESS_HOST BLESS_CONTROLLER

b) Elect to use the DFU linker script rather than the default linker script.

Code Block
languagetext
LINKER_SCRIPT=dfu_cm4_app0.ld

c) Add a postbuild command to sign the .elf file after the build. For Linux/MacOS, use the following command.

Code Block
languagetext
POSTBUILD=$(CY_MCUELFTOOL_DIR)/bin/cymcuelftool --sign $(CY_CONFIG_DIR)/$(APPNAME).elf --hex $(CY_CONFIG_DIR)/$(APPNAME).hex

For Windows, use the following command.

Code Block
languagetext
POSTBUILD="$(CY_MCUELFTOOL_DIR)/bin/cymcuelftool.exe" --sign $(CY_CONFIG_DIR)/$(APPNAME).elf --hex $(CY_CONFIG_DIR)/$(APPNAME).hex

For this application, I only modified the main.c, the linker script, and the Makefile. They can be found on this project's GitHub repository.

You should now be able to compile the application and flash the device. To verify that the bootloader functions properly, it will have to be tested with a loadable application. Skip to the Creating a Basic Loadable Application section which demonstrates how to create a basic loadable application and use it to perform an OTA BLE update.

Anchor
adv_bootloader
adv_bootloader
Adding Features to Create an Advanced BLE Bootloader

As simple as it is, the basic BLE bootloader works well for many applications. That being said, there are several things about it that could be improved. For instance; it provides no status information to the user regarding its current state, it provides no means of remotely switching to the main application without completing an entire DFU operation, and it makes no effort to conserve energy and prolong the battery life of the device. There are umpteen feasible solutions to address these issues, and this section offers the following relatively simple ones:

  • Debugging printf() statements
  • Flash an LED to indicate whether the bootloader application is executing
  • Boot into the main application by default when the device is reset (the green elements in Figure 14)
  • Hibernate to conserve energy after a period of inactivity (the blue elements in Figure 14)
  • Add the Immediate Alert Service as an option to switch from the bootloader to the main application (the red element in Figure 14)

The DFU application flowchart presented previously in Figure 1 has been expanded upon to reflect these changes and the resulting flowchart is shown in Figure 14. Because none of these features depend on the others, you as the designer are free to choose which ones to incorporate into your application.


Figure 14: Flowchart of advanced bootloader application

Before performing the procedures outlined below, I made a copy of the bootloader_basic project and named it bootloader_advanced. See Copying a Project in ModusToolbox 2.0 on Digi-Key's TechForum for the details on how to do this in ModusToolbox.

Anchor
adv_printf
adv_printf
Add printf() Statements for Debugging/Status Updates

This feature is particularly useful in this application for monitoring the BLE connection status. It can also be used for any number of debugging purposes.

a) In the Project Explorer, right-click on the project name and choose ModusToolbox > Library Manager.

b) Under the Libraries tab, check the box next to the "retarget-io" library under the "Board Utils" category. Click Apply. You should be presented with a summary of changes that matches Figure 15. Click OK and close the Library Manager once the changes are successfully applied.


Figure 15: Adding the retarget-io library to the application

c) Add the following lines to the includes section at the top of main.c.

Code Block
languagecpp
#include "cy_retarget_io.h"
#include <stdio.h>

d) Add the following initialization code to the main() function after initializing the device and board peripherals (before the infinite loop).

Code Block
languagecpp
/* Initialize retarget-io to use the debug UART port */
result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
if (result != CY_RSLT_SUCCESS)
{
    CY_ASSERT(0);
}

e) And just like that, you can start using printf() statements in the application! To demonstrate, I've added several events to the AppCallBack() function and one or more printf() statements for each occurrence. The function became rather long, so see the GitHub repository for the complete main.c file. Figure 16 provides an example screenshot of the output.


Figure 16: Result of adding printf() statements to AppCallBack()

Anchor
adv_led
adv_led
LED Bootloader Indicator

An LED might be the simplest way to indicate the current state of a procedure or an application. In this case, one of the PSoC 6 GPIOs is configured to flash an LED on the development board if the DFU application (App0) is executing. This is just one example and the code can easily be modified to turn on, blink, or ramp an LED to indicate different states such as BLE connections, firmware transfers, or errors.

a) Simply copy this function into the main.c file. Note that this function is derived from code provided in Cypress's TCPWM_Square_Wave example project.

Code Block
languagecpp
/*******************************************************************************
* Function:     flashLED
* Author:       Cypress Semiconductor (modified by Matt Mielke)
* Description:    This function initializes a timer/counter for PWM operation
*               sets a low output frequency and short duty cycle, causing the
*               LED to flash every second.
* Date:         03-23-20
*******************************************************************************/
void flashLED(cyhal_gpio_t led)
{
    cyhal_pwm_t pwm_led_control; // PWM object
    cy_rslt_t   result;          // API return code

    /* Initialize the TCPWM resource for PWM operation */
    result = cyhal_pwm_init(&pwm_led_control, led, NULL);
    if(result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }

    /* Set the PWM output frequency and duty cycle */
    result = cyhal_pwm_set_duty_cycle(&pwm_led_control, 95, 1);
    if(result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }

    /* Start the PWM */
    result = cyhal_pwm_start(&pwm_led_control);
    if(result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }
}

b) Add the function prototype to the beginning of main.c.

c) Call the function immediately before the call to Cy_DFU_TransportStart().

Code Block
languagecpp
flashLED(CYBSP_LED8);

See the GitHub repository for the complete main.c file that includes this feature.

Boot into Main Application by Default

In everyday usage, it will be desirable for the main application (App1) to be executed after the device resets, not the DFU application (App0). However, the bootloader is always executed first after any form of device reset. Therefore, code must be added to the beginning of the DFU application to trigger the switch to the main application (if the application is valid, of course).

This code relies on the configuration of PIN_SW2 from the Creating a Basic Bootloader section (Step 5) to give the user the option of overriding the default behavior. By holding down SW2 for 0.5 seconds immediately after the device is reset, the DFU application will continue to execute rather than switching to the main application. Simply copy and paste the following code block into the main() function after the device and board peripherals are initialized.

Code Block
languagecpp
/* In the case of non-software reset and user does not want to stay in App0,
 * check if there is a valid app image. If there is, switch to it.
 */
if (Cy_SysLib_GetResetReason() != CY_SYSLIB_RESET_SOFT)
{
    /* If Button held for 0.5 second - Switch to App1 if it is valid */
    count = 10; // 50ms * 10 = 500ms
    while ((Cy_GPIO_Read(PIN_SW2_PORT, PIN_SW2_PIN) == 0) && (count > 0))
    {
        count--;
        Cy_SysLib_Delay(50);
    }

    /* Wait for user to release button and debounce*/
    while (Cy_GPIO_Read(PIN_SW2_PORT, PIN_SW2_PIN) == 0);
    Cy_SysLib_Delay(50);

    if (count > 0)
    {
        status = Cy_DFU_ValidateApp(1u, &dfuParams);
        if (status == CY_DFU_SUCCESS)
        {
            /* Clear reset reason */
            do
            {
                Cy_SysLib_ClearResetReason();
            } while(Cy_SysLib_GetResetReason() != 0);

            Cy_DFU_ExecuteApp(1u); // Never returns
        }
    }
}

See the GitHub repository for the complete main.c file that includes this feature. 

Hibernate after Timeout

PSoC 6 devices are designed for IoT applications. Because many IoT devices operate in remote locations powered by batteries and/or energy harvesting techniques, it is important that they consume as little energy as possible. Once a device is designed, built, and implemented in the field; firmware updates probably won't be required very frequently. It therefore doesn't make sense to design a DFU application that sends BLE advertisements indefinitely.

This issue can be avoided by enabling the fast and slow advertising interval timeouts with the Bluetooth Configurator. We take this a step further by placing the device in hibernate mode after the advertisements are stopped if there is not a valid application in the App1 memory space to switch to. SW2 is designated as the wake-up source.  Note that this procedure modifies the configuration done in the previous section, essentially undoing the changes made to the advertisement settings.

a) In the Project Explorer, right-click on the project name and choose ModusToolbox > Bluetooth Configurator.

b) Under the GAP Settings tab, select the Advertisement Settings option and change "Discovery Mode" to "Limited".

c) Check the box next to "Enable Fast advertising timeout".

d) Optionally, check the box next to "Enable slow advertising interval". At this point, you can adjust the advertising intervals and timeouts to your liking. I left them at the default, resulting in the configuration shown in Figure 17


Figure 17: Configure advertisement settings to timeout in limited mode

e) Save and close to re-generate the configuration files.

f) Add the following code to AppCallBack() under the GAP events section. You can uncomment the printf() statements if you have added that functionality as shown in the Add printf() Statements section.

Code Block
languagecpp
/* This event indicates peripheral device has started/stopped
 *  advertising
 */
case CY_BLE_EVT_GAPP_ADVERTISEMENT_START_STOP:
{
    if(CY_BLE_ADV_STATE_ADVERTISING == Cy_BLE_GetAdvertisementState())
    {
        //printf("[INFO] : BLE advertisement started\r\n");
    }
    else if (CY_BLE_ADV_STATE_STOPPED == Cy_BLE_GetAdvertisementState())
    {
        //printf("[INFO] : BLE advertisement stopped\r\n");

        /* Fast and slow advertising period complete, go to low power
         * mode (Hibernate mode) and wait for an external user event to
         * wake up the device again
         * */
        Cy_DFU_TransportStop(); // Stop DFU communication

        /* Check if app is valid, if it is then switch to it */
        uint32_t status = Cy_DFU_ValidateApp(1u, NULL);
        if (status == CY_DFU_SUCCESS)
        {
            /* Clear reset reason because Cy_DFU_ExecuteApp() performs
             * a software reset.
             * Without clearing two reset reasons would be present.
             */
            do
            {
                Cy_SysLib_ClearResetReason();
            } while(Cy_SysLib_GetResetReason() != 0);

            Cy_DFU_ExecuteApp(1u); // Never returns
        }

        /* 300 seconds has passed and app is invalid. Hibernate */
        Cy_SysPm_Hibernate();
    }
    break;
}

g) Set SW2 as the wake-up source. Place this code in main() after the variables are declared.

Code Block
languagecpp
/* Unfreeze IO after Hibernate */
if(Cy_SysPm_GetIoFreezeStatus())
{
    Cy_SysPm_IoUnfreeze();
}
/* Set SW2 as hibernate wakeup pin */
Cy_SysPm_SetHibWakeupSource(CY_SYSPM_HIBPIN1_LOW);

To indicate that the device is in hibernate mode, you can implement the flashing LED as described in the LED Bootloader Indicator section. When the LED stops flashing, you know that the device is hibernating. The main.c file that includes only this hibernating feature can be found in the GitHub repository.

Immediate Alert Service

The immediate alert service is pretty much exactly what it sounds like. After a client connects to the BLE device, it can write to the Alert Level Characteristic to cause the device to immediately alert (i.e. trigger an interrupt within the application). We can use this alert to signal the DFU application to transfer control to the main application. This will allow us to remotely switch to the main application (assuming it is valid) without having to perform a firmware upgrade. The following procedure demonstrates how to add this feature to the bootloader application and how to use it to switch to App1. Note that we are modifying the existing Bluetooth configuration created in the Creating a Basic Bootloader section (Step 6).

a) In the Project Explorer, right-click on the project name and choose ModusToolbox > Bluetooth Configurator.

b) Under the GATT Settings tab, right-click on the "Server" profile and choose Add Service > Immediate Alert. This should result in the configuration shown in Figure 18.


Figure 18: Add the Immediate Alert Service to the Server profile

c) Save and close to update the BLE configuration files.

d) Add the following function to the main.c file.

Code Block
languagecpp
/*******************************************************************************
* Function:     IasEventHandler
* Author:       Cypress Semiconductor
* Description:    This is an event callback function to receive events from the
*               BLE Component, which are specific to Immediate Alert Service.
* Date:         03-23-20
* Parameters:
*   event:       Write Command event from the BLE component.
*   eventParams: A structure instance of CY_BLE_GATT_HANDLE_VALUE_PAIR_T type
*******************************************************************************/
void IasEventHandler(uint32 event, void *eventParam)
{
    (void) eventParam;
    uint8_t alert;

    /* Alert Level Characteristic write event */
    if(event == CY_BLE_EVT_IASS_WRITE_CHAR_CMD)
    {
        /* Read the updated Alert Level value from the GATT database */
        Cy_BLE_IASS_GetCharacteristicValue(CY_BLE_IAS_ALERT_LEVEL, sizeof(alert), &alert);
        alertLevel = alert;
    }
}

e) Add function prototype to the function prototype section of main.c.

f) Add the following global variable to the global variables section of main.c.

Code Block
languagecpp
volatile uint8_t alertLevel;

g) Initialize the Alert Level and register the ISR in main() right before the infinite loop.

Code Block
languagecpp
/* Registers the IAS CallBack. */
alertLevel = 0;
Cy_BLE_IAS_RegisterAttrCallback(IasEventHandler);

h) In the main loop, check the state of Alert Level and switch to the main application if it was raised.

Code Block
languagecpp
/* If alert level raised */
if ( alertLevel != 0 )
{
    /* Validate and switch to App1 */
    status = Cy_DFU_ValidateApp(1u, &dfuParams);
    if (status == CY_DFU_SUCCESS)
    {
       Cy_DFU_TransportStop();
       Cy_DFU_ExecuteApp(1u);
    }
}

The main.c file that includes the above IAS code can be found in the GitHub repository.

After programming the device with this updated bootloader, it should appear in the scanned devices list exactly as before. This time, rather than clicking Upgrade Firmware, click Connect and you should see a screen like Figure 19. Now just follow the step as indicated in Figure 19. (1) Click Discover All Attributes and scroll down to the "Alert Level" characteristic and (2) select it. On the right-hand side (3) enter a value in the text box and (4) click Write Value Without Response. The device should immediately begin executing the main application if it is valid. Note that only values of '1' or '2' will trigger the ISR and cause an application switch.


Figure 19: Raise Alert Level using CySmart 1.3

Anchor
loadable_basic
loadable_basic
Creating a Basic Loadable Application from an Existing Project

Any standalone application can be turned into a bootloadable application with just a few simple changes to the project. These include allocating memory for the application signature, editing the linker script to define the memory section for App1, and editing the Makefile to specify a postbuild command which will generate the loadable file.

Step 1: Add a Project to the Workspace

This can either be a new project or an existing project. For this tutorial, we will be using a demo application featuring the capsense library.

a) In ModusToolbox IDE, choose File > New > ModusToolbox IDE Application.

b) Choose your target board for the application (this tutorial uses the "CY8CKIT-062-BLE" board) and click Next.

c) Select "CapSense_Buttons_and_Slider" as the starter application. You can change the name of the application if you'd like. I changed the name to "capsense_loadable_basic" for clarity. Click Next.

d) Verify the project information is correct and click Finish. If you are following along, it should match the project summary shown in Figure 20.


Figure 20: Creating a new demo application in the workspace to be used as the loadable application

Step 2: Copy the Linker Script

For this basic loadable application, you don't have to include the entire dfu library. Just copy dfu_cm4_app1.ld from the dfu library files in the bootloader project.

a) Copy dfu_cm4_app1.ld from the bootloader project and paste it into the directory containing the loadable project's main.c file. This is shown explicitly in Figure 21.


Figure 21: Copy linker script to project root

Step 3: Edit main.c

For this simple example of a loadable application, we only need to define the signature section in memory.

a) Add the following block of code to the beginning of the main() function.

Code Block
languagecpp
/* This section holds signature data for application verification */
CY_SECTION(".cy_app_signature") __USED static const uint32_t cy_dfu_appSignature[1];

Step 4: Modify the Linker Script

The memory sections of dfu_cm4_app0.ld (in the bootloader project) and dfu_cm4_app1.ld must match. If you find when building this application that you need to increase the memory region of App1 (Figure 12), then increase the length of this region in both linker files. This will mean having to re-flash the bootloader.

a)       Edit the memory section of dfu_cm4_app1.ld to match the memory section of the bootloader's linker script (dfu_cm4_app0.ld). If you followed the steps in the Creating a Basic Bootloader section (Step 7), this means changing the length of the "flash_app0" region to 0x4E000 and the origin of "flash_boot_meta" to 0x100FFB00 (Figure 13).

Step 5: Modify the Makefile

Open the Makefile and scroll down to the Advanced Configuration section.

a) Elect to use the DFU linker script rather than the default linker script.

Code Block
languagetext
LINKER_SCRIPT=dfu_cm4_app1.ld

b) Add a postbuild command to add a CRC checksum to the .elf file and use this signed file to create a firmware image file. This firmware image file will have the extension ".cyacd2" and it is what will be sent to the bootloader. For Linux/MacOS, use the following command.

Code Block
languagetext
POSTBUILD=$(CY_MCUELFTOOL_DIR)/bin/cymcuelftool --sign $(CY_CONFIG_DIR)/$(APPNAME).elf CRC --output $(APPNAME)_crc.elf && \
       $(CY_MCUELFTOOL_DIR)/bin/cymcuelftool -P $(APPNAME)_crc.elf --output $(APPNAME)_crc.cyacd2

For Windows, use the following command.

Code Block
languagetext
POSTBUILD="$(CY_MCUELFTOOL_DIR)/bin/cymcuelftool.exe" --sign $(CY_CONFIG_DIR)/$(APPNAME).elf CRC --output $(APPNAME)_crc.elf && \
       "$(CY_MCUELFTOOL_DIR)/bin/cymcuelftool.exe" -P $(APPNAME)_crc.elf --output $(APPNAME)_crc.cyacd2

This project's GitHub repository contains the complete main.c file, linker script, and Makefile. You can now build the application to generate the firmware image (the ".cyacd2" file) which will be stored in the project's root directory. If the application is larger than the allocated memory space for App1, you will receive build errors and be required to make changes to the linker scripts for both App0 and App1. See the Modify the Linker Script (enlarge App1 region) section for more details.

Anchor
loadable_install
loadable_install
Load the Application on the Target Device

At this point, a bootloader has been programmed on the PSoC 6 and the application firmware image has been generated. To perform the DFU operation, which will transfer the main application firmware to the App1 memory space (Figure 12), we will utilize the CySmart BLE host emulation tool and the BLE USB dongle that comes with the CY8CKIT-062-BLE kit. These steps assume that the basic bootloader is running on the device, but any functional and correctly configured bootloader should work.

a) Start by plugging in the BLE USB dongle and installing any required drivers. Launch CySmart 1.3 and select the dongle in the dialog box as shown in Figure 22. Click Connect.


Figure 22: Connect to the CySmart BLE USB dongle

b) Once connected to the dongle, the UI should appear like that shown in Figure 23a. Click Start Scan as indicated by the red circle. BLE devices advertising in the proximity of the dongle will begin showing up in the Discovered Devices list as shown in Figure 23b. Once you see the DFU device in the discovered devices list (the name should match the name provided in this step), click Stop Scan.

Section


Column
width50%



Column
width725px


a)
Starting the scan


Column
width725px


b)
DFU device found


Column
width50%



Figure 23: Scanning for nearby BLE devices

c) Once scanning has been stopped, select the DFU device in the device list and click Update Firmware, as shown in Figure 24.


Figure 24: Select DFU device and update firmware

d) In the dialog box that appears, ensure "Application only update" is selected and click Next. Click the button with ellipsis next to the text box and navigate to the project directory of the loadable application. Choose the *.cyacd2 file and click Open. The file path should appear in the text box as shown in Figure 25.


Figure 25: Select the firmware image

e) Click Update. The dialog box will indicate the status of the application update as the firmware is transferred to the target device (Figure 26a). If the update is successful, the dialog box in Figure 26b will be shown. Click Close.

Section


Column
width50%



Column
width400px


a)
The firmware being transferred to the target


Column
width400px


b)
The firmware was successfully updated


Column
width50%



Figure 26: Updating the firmware

The updated application should begin executing on the device as soon as the update is complete (because we designed the bootloader that way). It took a little over 10 seconds to load this basic application. Note that, as mentioned previously, this has nothing to do with the size of the application, but rather the size of the App1 memory space.

Adding Features to Create an Advanced Loadable Application

Many of the features used to create an advanced BLE bootloader can be similarly utilized in App1 to create an advanced loadable application. Perhaps the most notable is the addition of IAS capability, which allows the user to remotely switch from the main application to the bootloader. This, combined with the IAS capability in the advanced bootloader, would allow the device to be deployed in an entirely remote location and still receive OTA firmware updates. Because the basic loadable application did not include the BLE Middleware library, adding the IAS feature is a bit more of an involved process and will be explicitly outlined in this section. The other features; including the use of printf() statements, LED status indicators, and low-power modes; can be added by following the steps already provided in the Advanced Bootloader section.

Before completing the steps below, I made a copy of the capsense_loadable_basic project (from the Creating a Basic Loadable Application section) and named it capsense_loadable_advanced. See Copying a Project in ModusToolbox 2.0 on Digi-Key's TechForum for the details on how to do this in ModusToolbox.

Step 1: Add the Required Libraries

a) In the Project Explorer, right-click on the project name and choose ModusToolbox > Library Manager.

b) Under the Libraries tab, check the boxes next the "dfu" and "bless" Middleware Libraries. Click Apply

c) You should be presented with the dialog box shown in Figure 27. Click OK and wait for the changes to successfully be applied. Close the Library Manager.


Figure 27: Summary of libraries to be added to the advanced loadable project

Step 2: Copy dfu_user.h

Whereas the bootloader required both dfu_user.h and dfu_user.c, this time do not add dfu_user.c. It will overwrite the metadata row (Figure 12). 

a)Copy the file dfu_user.h from libs/dfu/config/ and paste it in the directory which contains main.c.


Figure 28: Copying dfu_user.h from DFU library directory to the project's root directory.

Step 3: BLE Configuration

a)In the Project Explorer, right-click on the project name and choose ModusToolbox > Bluetooth Configurator (new configuration)

b)Under the GATT Settings tab, right-click on the Server profile and choose Add Service > Immediate Alert (Figure 29). Ensure that "No encryption required" is chosen under the characteristic permissions. 


Figure 29: Adding the Immediate Alert Service using the Bluetooth Configurator

c)Under the GAP Settings tab, select the General options and provide a string for "Device Name". I choose "CapSense App", as shown in Figure 30.


Figure 30: Providing a name for the device when the main application is running

d)Select the Advertisement settings options and use the drop-down menu to change "Discovery mode" to "General". Disable the "Enable Fast advertising timeout" and "Enable slow advertising interval" by unchecking the boxes (Figure 31).


Figure 31: Configure the main application to be continuously discoverable by choosing general discovery mode

e)Select the Advertisement packet options and check the box for "Enable Local name". "Complete" should be the default option for "Local name type" (Figure 32).


Figure 32: Include the main application device name in the advertisement packet

f)Select the Security configuration 0 options and change "Security level" to "Unauthenticated pairing with encryption", "IO Capability" to "No Input No Output", and "Bonding" to "No Bond"; as summarized in Figure 33.


Figure 33: Modified main application security configuration

Step 4: Edit main.c

a)In the section containing the include directives, include the files cycfg_ble.h and cy_dfu.h

Code Block
languagecpp
#include "cycfg_ble.h"
#include "cy_dfu.h"

b)Define the BLESS ISR (Interrupt Service Routine) above the main() function. 

Code Block
languagecpp
/*******************************************************************************
* Function:     BlessInterrupt
* Author:       Cypress Semiconductor
* Description:    It is used used when BLE middleware operates in BLE single CM4
* Date:         
*******************************************************************************/
void BlessInterrupt(void)
{
    /* Call interrupt processing */
    Cy_BLE_BlessIsrHandler();
}

c)Add the function IASEventHandler() to the end of the file. Don't forget to add the function prototype to the function prototypes section of the file. 

Code Block
languagecpp
/*******************************************************************************
* Function:     IasEventHandler
* Author:       Cypress Semiconductor
* Description:    This is an event callback function to receive events from the
*               BLE Component, which are specific to Immediate Alert Service.
* Date:         03-23-20
* Parameters:
*   event:       Write Command event from the BLE component.
*   eventParams: A structure instance of CY_BLE_GATT_HANDLE_VALUE_PAIR_T type
*******************************************************************************/
void IasEventHandler(uint32 event, void *eventParam)
{
    (void) eventParam;
    uint8_t alert;

    /* Alert Level Characteristic write event */
    if(event == CY_BLE_EVT_IASS_WRITE_CHAR_CMD)
    {
        /* Read the updated Alert Level value from the GATT database */
        Cy_BLE_IASS_GetCharacteristicValue(CY_BLE_IAS_ALERT_LEVEL, sizeof(alert), &alert);
        alertLevel = alert;
    }
}

 d)Add the following global variables to the global variables section of the file.  

Code Block
languagecpp
volatile uint8_t alertLevel;
cy_stc_ble_conn_handle_t appConnHandle;

/* BLESS interrupt configure structure (for single CPU mode) */
static cy_stc_sysint_t blessIsrCfg =
{
	/* The BLESS interrupt */
	.intrSrc = (IRQn_Type)bless_interrupt_IRQn,

	/* The interrupt priority number */
	.intrPriority = 1u
};

e)Add the AppCallBack() function to end of file and add the function prototype to the function prototypes section of the file. 

Code Block
languagecpp
/*******************************************************************************
* Function:     AppCallBack
* Author:       Cypress Semiconductor (modified by Matt Mielke)
* Description:    This is an event callback function to receive events from the
*               BLE Component. Used in Cy_DFU_TransportStart()
* Date:         03-23-20
*******************************************************************************/
void AppCallBack(uint32_t event, void* eventParam)
{
    static cy_stc_ble_gap_sec_key_info_t keyInfo =
    {
        .localKeysFlag    = CY_BLE_GAP_SMP_INIT_ENC_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_IRK_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_CSRK_KEY_DIST,
        .exchangeKeysFlag = CY_BLE_GAP_SMP_INIT_ENC_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_IRK_KEY_DIST |
                            CY_BLE_GAP_SMP_INIT_CSRK_KEY_DIST |
                            CY_BLE_GAP_SMP_RESP_ENC_KEY_DIST |
                            CY_BLE_GAP_SMP_RESP_IRK_KEY_DIST |
                            CY_BLE_GAP_SMP_RESP_CSRK_KEY_DIST,
    };

    switch(event)
    {
        /**********************************************************************
         * General events
         *********************************************************************/

        /* This event is received when the BLE stack is started */
        case CY_BLE_EVT_STACK_ON:
        {
            /* Enter into discoverable mode so that remote can search it. */
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, 0u);

            Cy_BLE_GAP_GenerateKeys(&keyInfo);
            break;
        }

        /**********************************************************************
         * GAP events
         *********************************************************************/

        case CY_BLE_EVT_GAP_AUTH_REQ:
        {
            if (cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX].security
                == (CY_BLE_GAP_SEC_MODE_1 | CY_BLE_GAP_SEC_LEVEL_1))
            {
               cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX].authErr =
                   CY_BLE_GAP_AUTH_ERROR_PAIRING_NOT_SUPPORTED;
            }

            cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX].bdHandle =
               ((cy_stc_ble_gap_auth_info_t *)eventParam)->bdHandle;

            Cy_BLE_GAPP_AuthReqReply(&cy_ble_configPtr->authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX]);
            break;
        }

        /* This event is triggered instead of 'CY_BLE_EVT_GAP_DEVICE_CONNECTED',
        * if Link Layer Privacy is enabled in component customizer
        */
        case CY_BLE_EVT_GAP_ENHANCE_CONN_COMPLETE:
        {
            /* sets the security keys that are to be exchanged with a peer
             * device during key exchange stage of the authentication procedure
             */
            keyInfo.SecKeyParam.bdHandle =
                (*(cy_stc_ble_gap_enhance_conn_complete_param_t *)eventParam).bdHandle;

            Cy_BLE_GAP_SetSecurityKeys(&keyInfo);
            break;
        }

        /* This event indicates security key generation complete */
        case CY_BLE_EVT_GAP_KEYS_GEN_COMPLETE:
        {
            keyInfo.SecKeyParam = (*(cy_stc_ble_gap_sec_key_param_t *)eventParam);
            Cy_BLE_GAP_SetIdAddress(&cy_ble_deviceAddress);
            break;
        }

        /* This event is generated when disconnected from remote device or
         * failed to establish connection
         */
        case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
        {
            /* Enter into discoverable mode so that remote can search it. */
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, 0u);

            break;
        }

        /**********************************************************************
         * GATT events
         *********************************************************************/

        /* This event is generated at the GAP Peripheral end after connection
         * is completed with peer Central device
         */
        case CY_BLE_EVT_GATT_CONNECT_IND:
        {
            appConnHandle = *(cy_stc_ble_conn_handle_t *)eventParam;
            break;
        }

        default:
        {
            break;
        }
    }
}

f)Add the BLE initialization code to the main() function before the infinite loop. 

Code Block
languagecpp
/* Hook interrupt service routines for BLESS */
(void) Cy_SysInt_Init(&blessIsrCfg, &BlessInterrupt);

/* Store pointer to blessIsrCfg in BLE configuration structure */
cy_ble_config.hw->blessIsrConfig = &blessIsrCfg;

/* Start BLE component and register generic event handler */
/* Register the generic event handler */
Cy_BLE_RegisterEventCallback(&AppCallBack);

/* Initialize the BLE host */
(void)Cy_BLE_Init(&cy_ble_config);

/* Enable BLE Low Power Mode (LPM)*/
Cy_BLE_EnableLowPowerMode();

/* Enable BLE */
(void)Cy_BLE_Enable();

/* Register the IAS CallBack */
Cy_BLE_IAS_RegisterAttrCallback(IasEventHandler);

g)Add the following code to the main loop. 

Code Block
languagecpp
Cy_BLE_ProcessEvents();

/* If alert level written */
if (alertLevel != 0)
{
    /* Switch to bootloader (app0) */
    Cy_DFU_ExecuteApp(0u);
}

The GitHub repository for this project includes the complete main.c file after these changes have been made. 

Step 5: Modify the Makefile

a)Enable the "BLESS_HOST" and "BLESS_CONTROLLER" BLE stack components by appending them to the "COMPONENTS=" option in the Makefile.

Code Block
languagetext
COMPONENTS=BLESS_HOST BLESS_CONTROLLER

Anchor
loadable_adv_6
loadable_adv_6
Step 6: Modify the Linker Script (enlarge App1 region)

Recall from the Creating a Basic Bootloader section (Step 7) that we want to keep the App1 region as small as possible to reduce updating time (and conserve power). If you have not allocated enough memory for this region, you will get errors similar to those shown below in Listing 1. 

Code Block
languagetext
titleListing 1: Build errors resulting from insufficient memory allocated to App1
c:/users/matt/modustoolbox/tools_2.0/gcc-7.2.1/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/Matt/Cypress/workspace/capsense_loadable_advanced/build/CY8CKIT-062-BLE/Debug/mtb-example-psoc6-capsense-buttons-slider.elf section `i.CyBleControllerIP_TuneAdvEventInterPktSlots' will not fit in region `flash_app1'
c:/users/matt/modustoolbox/tools_2.0/gcc-7.2.1/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: section .cy_app_signature LMA [1005fffc,1005ffff] overlaps section i.CyBleControllerIP_TuneAdvEventInterPktSlots LMA [1005ffe4,10060023]
c:/users/matt/modustoolbox/tools_2.0/gcc-7.2.1/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: region `flash_app1' overflowed by 0 bytes

If you encounter these errors, open the linker script (dfu_cm4_app1.ld) and increase the "flash_app1" length to accommodate the application size. Figure 34 shows this length being increased to 0x35000 (212 KiB). Once enough memory is allocated, the project should compile without errors. Don’t forget to mirror these changes to the dfu_cm4_app0.ld linker script in the bootloader project and re-flash to bootloader to memory. If you forget, the OTA firmware update operation will fail.  


Figure 34: Modified memory section of dfu_cm4_app1.ld

Once the project is built, you can follow the steps in the Load the Application on the Target Device section to install the application. Just be sure to select the firmware image for the advanced loadable application rather than the basic loadable application. This time, it took about 37 seconds to update the firmware due to the larger App1 memory region. 

Conclusion

Thanks to ModusToolbox, it is a straightforward process to add the DFU library to PSoC 6 projects and create both a basic bootloader and a basic loadable application. The real design work comes in when deciding what additional features are required for usability, efficiency, etc. Hopefully, this tutorial acts as a good starting point when attempting to use the PSoC 6 to develop a remote device capable of receiving OTA updates. After becoming familiar with these concepts, you may wish to explore Cypress's documentation describing other bootloading schemes as well. These include bootloaders designed for external memory, upgradable stack, multiple loadable applications, and secure boot. 

Contact the Author

If you have any other questions/feedback about this tutorial, feel free to send an email to eewiki@digikey.com or visit Digi-Key's TechForum.