Adding BLE OAD to an Existing Project

This section will detail how to add BLE OAD to an existing project. The intention is to start with an unmodified sample app from the SDK that does not currently use OAD, and add OAD to it.

Customers can use this section to add OAD to their existing projects. The example project used to demonstrate these steps will be multi_role. The steps are presented in a generic way such that they should apply to all toolchains. If there is a toolchain specific step it will be called out.

Off-Chip OAD

This section will detail how to add off-chip OAD to an existing project.

Project Changes

In order to add OAD, certain files and include paths should be added to the project, these are detailed in the list below.

  1. Replace the default linker file with the following:

    • cc26x2_app_oad_agama.cmd : For CCS
    • cc26xx_app_and_stack_agama_oad.icf : For IAR

Note

In CCS this means editing the .projectspec file for the application and re-importing the project.

Open the projectspec file and replace references to cc13x2_cc26x2_app.cmd and replace them with cc26x2_app_oad_agama.cmd. Then re-import the project

In IAR, this can be done through the project -> options -> linker options menu.

  1. Add the following to the linker defines:

    • OAD_IMG_E=1
  2. Add the following define to the .opt files:

    • -DMAX_PDU_SIZE=251

    Note

    This define will control the negotiated block size. Block Size is derived from MTU size, which is derived by the minimum of the local supported PDU size and peer’s supported MTU. See OAD Block Size Rules.

  3. Relocate the TI-RTOS reset vectors:

    • Set m3Hwi.resetVectorAddress  = 0x50; in the application’s .cfg file. Note: Each application has two of these (debug, release), apply change for both. Replace any previous values in the file.
  4. Enable memory deletes. Add the following option to the .cfg file:

    /* Allow dynamic creation and deletion */
    Defaults.common$.memoryPolicy = Types.DELETE_POLICY;
    
  5. The following files should be removed from the project:

    • ccfg_app_ble.c : The BIM links the CCFG in an OAD system.
  6. Add the following files to the project

    • oad.c : OAD profile implementation
    • oad.h : Public OAD profile API definition
    • crc32.c : CRC32 algorithm and helper functions
    • crc32.h : CRC32 public API definition
    • flash_interface.h : A flash abstraction layer that abstracts flash operations for on and off-chip OAD.
    • oad_iamge_header.h : Structure definitions for the OAD image header
    • oad_image_header_app.c: Application definition of image header.
    • oad_defines.h: Common header containing OAD definitions
    • bim_util.h : Public API definition of utility functions shared between the BIM and application.
    • bim_util.c : Implementation of BIM utility functions
    • flash_interface_ext_rtos_NVS.c: TI-RTOS NVS driver implementation of flash interface for off-chip OAD.

    The following files are only required if the optional revert to factory image feature is required:

    • mark_switch_factory_img.h : Public API definition for factory image switch.
    • mark_switch_factory_img.c : Implementation of factory image switching mechanism.
  7. The following pre-processor include paths should be added to the project.

    • source/ti
    • source/ti/ble5stack
  8. Add the OAD image tool post build step to the project.

    • See the sample applications within the SDK with OAD enabled (project_zero, simple_peripheral_oad_offchip) and copy the post build step from there.
    • Note that the TOOLS_OAD_DIR environment variable may not exist for all projects. It can either be added or substituted with a path to the OAD executable.
    • Also note that some projects do not append their configuration name to their output hex file. If the image tool reports that it cannot find the input file, check the naming.

Code Changes

The following changes need to be performed in the source code of the application

  1. Performing the following changes in the high level application task file.

    • Add the required include directives:
    #include <profiles/oad/cc26xx/oad.h>
    #include <ti/common/cc26xx/oad/oad_image_header.h>
    
    #include <ti/devices/DeviceFamily.h>
    #include DeviceFamily_constructPath(driverlib/sys_ctrl.h)
    
    • Add the OAD events to the app’s *_ALL_EVENTS macro (be sure to add line breaks as necessary):
    OAD_QUEUE_EVT | \
    OAD_DL_COMPLETE_EVT)
    
    • Define the connection event end event if it does not exist:
    #define OAD_CONN_EVT_END_EVT CONN_EVENT_TO_INDEX(MAX_NUM_BLE_CONNS)
    
    • Add the following static variable declarations:
    static uint8_t numPendingMsgs = 0;
    
    static oadTargetCBs_t MultiRole_oadCBs =
    {
        .pfnOadWrite = MultiRole_processOadWriteCB // Write Callback.
    };
    
    • Inside the application’s *_init function, add the following:
    // Open the OAD module and add the OAD service to the application
    if(OAD_SUCCESS != OAD_open(OAD_DEFAULT_INACTIVITY_TIME))
    {
        // Report Error
    }
    else
    {
        // Register the OAD callback with the application
        OAD_register(&MultiRole_oadCBs);
    }
    
    • Add the OAD event processing in the application’s task function:
    // OAD events
    if(events & OAD_QUEUE_EVT)
    {
        // Process the OAD Message Queue
        uint8_t status = OAD_processQueue();
    
        // If the OAD state machine encountered an error, print it
        // Return codes can be found in oad_constants.h
        if(status == OAD_DL_COMPLETE)
        {
            // Report status
        }
        else if(status == OAD_IMG_ID_TIMEOUT)
        {
            // This may be an attack, terminate the link,
            // Note HCI_DISCONNECT_REMOTE_USER_TERM seems to most closet reason for
            // termination at this state
            MAP_GAP_TerminateLinkReq(
                OAD_getactiveCxnHandle(),
                HCI_DISCONNECT_REMOTE_USER_TERM);
        }
        else if(status != OAD_SUCCESS)
        {
            // Report Error
        }
    }
    
    if(events & OAD_DL_COMPLETE_EVT)
    {
        // Register for L2CAP Flow Control Events
        L2CAP_RegisterFlowCtrlTask(selfEntity);
    }
    
    • Process L2CAP messages from the stack. This should be added to the application stack message parser. Most sample applications call this function *_processStackMsg.
    case L2CAP_SIGNAL_EVENT:
    {
        // Process L2CAP free buffer notification
        MultiRole_processL2CAPMsg(
            (l2capSignalEvent_t *)pMsg);
        break;
    }
    
    • Make an application level L2CAP handler function as below. Add this function to the foward declarations at the top of the file.
    static void MultiRole_processL2CAPMsg(l2capSignalEvent_t *pMsg)
    {
        static bool firstRun = TRUE;
    
        switch(pMsg->opcode)
        {
        case L2CAP_NUM_CTRL_DATA_PKT_EVT:
        {
            if(firstRun)
            {
                firstRun = false;
    
                // We only want to set the numPendingMsgs once
                numPendingMsgs = MAX_NUM_PDU -
                                 pMsg->cmd.numCtrlDataPktEvt.numDataPkt;
    
                // Wait the number of connection events
                HCI_EXT_ConnEventNoticeCmd(OAD_getactiveCxnHandle(),
                                            selfEntity,
                                            OAD_CONN_EVT_END_EVT);
            }
    
            break;
        }
        default:
            break;
        }
    }
    
    • In the application’s ICall message handle add a function to process connection event callbacks:
    // Check for BLE stack events first
    if(pEvt->signature == 0xffff)
    {
        // ..
    
        // Handle an Incoming OAD reboot
        MultiRole_processOADReboot(pEvt->event_flag);
    }
    
    • Define the reboot function as below, be sure to add it to the list of forward declarations:
    static void MultiRole_processOADReboot(uint32_t stack_event)
    {
        if(stack_event & OAD_CONN_EVT_END_EVT)
        {
            // Wait until all pending messages are sent
            if(numPendingMsgs == 0)
            {
                // Reset the system
                SysCtrlSystemReset();
            }
            numPendingMsgs--;
        }
    }
    
    • Add the following code to the ATT_MTU_UPDATED_EVENT handler. This should be processed by the application when it receives a messages of gattMsgEvent_t from the stack.
    OAD_setBlockSize(pMsg->msg.mtuEvt.MTU);
    
  2. Reset OAD if connection drops

    • Add the following code to the GAP_LINK_TERMINATED_EVENT handler.
    // Cancel the OAD if one is going on
    // A disconnect forces the peer to re-identify
    OAD_cancel();
    
  3. (Optional) Add revert to factory image feature to application:

    • Add the following code to main.c
    if(!PIN_getInputValue(Board_BUTTON0))
    {
        markSwitchFactoryImg();
    }
    
  4. (Optional) Changing external flash pins:

    • The examples will work on the CC26x2 LaunchPad out of the box, but for custom hardware the following is recommended.
    • Change the pins for the BIM. See bsp.h, change the defines below.
    // Board external flash defines
    #define BSP_IOID_FLASH_CS       IOID_20
    #define BSP_SPI_MOSI            IOID_9
    #define BSP_SPI_MISO            IOID_8
    #define BSP_SPI_CLK_FLASH       IOID_10
    
    • Change the following defines to match in the application’s board file.
    /* SPI */
    #define CC26X2R1_LAUNCHXL_SPI_FLASH_CS          IOID_20
    #define CC26X2R1_LAUNCHXL_FLASH_CS_ON           0
    #define CC26X2R1_LAUNCHXL_FLASH_CS_OFF          1
    
    /* SPI Board */
    #define CC26X2R1_LAUNCHXL_SPI0_MISO             IOID_8
    #define CC26X2R1_LAUNCHXL_SPI0_MOSI             IOID_9
    #define CC26X2R1_LAUNCHXL_SPI0_CLK              IOID_10