Introduction

This SimpleLink™ Academy lab will take you through the process of integrating an existing proprietary radio application alongside another radio protocol stack using DMM.

The Dynamic Multi-protocol Manager (DMM) allows multiple wireless stacks to coexist and operate concurrently on a single radio on a CC13xx or CC26xx device. DMM allows the user to define and control the priorities and timing constraints of each stack at run-time.

This lab will combine a proprietary radio example together with the BLE Simple Peripheral example. The user will be instructed on how to integrate DMM in regards to the policy table, global priority table, application states as well as how to transition between states in the application.

At the end of the lab, the user will have a DMM project featuring concurrent proprietary radio communication alongside an active BLE connection. While the lab can be completed using a single LaunchPad™, it is required to have at least two LaunchPads in order to setup the proprietary RX/TX echo example that is used in the lab.

This lab assumes the reader has basic knowledge about the TI BLE and DMM offering, as well as basic embedded programming skills.

Prerequisites

Hardware

Software

It is recommended to read the DMM User's Guide, BLE5-Stack User's Guide, and Proprietary RF User's Guide alongside this lab for details and further information.

The Proprietary Radio Example

The proprietary radio examples that will be used in this lab is the rfEchoRx and rfEchoTx. These examples are part of the SimpleLink CC13xx / CC26xx SDK and showcase a basic RX/TX chain to echo the received data. For the purpose of this lab, rfEchoRx will be used to create the DMM project while rfEchoTx will be used for verification of the latter.

If unfamiliar with these examples, it is recommended to start with running these before proceeding with creating the DMM project. Please refer to the README files for each example for more information.

Before you start

Before we start, create an empty CCS workspace and import the following examples projects:

  • simple_peripheral, found under <SDK>/examples/rtos/<BOARD>/ble5stack/

  • rfEchoRx, found under <SDK>/examples/rtos/<BOARD>/drivers/

  • rfEchoTx, found under <SDK>/examples/rtos/<BOARD>/drivers/

where <SDK> is the install location of the SimpleLink SDK and <BOARD> must correspond to the board being used for that example project. When all projects has been imported, build all projects and make sure that there are no build errors.

Device terminology

From this point on, we will refer to the LaunchPad used for the DMM project as the "DMM device".

After building the simple_peripheral project, download it to the DMM device and verify that it is working as expected. For more information on what output to expect, see the BLE labs referenced under Prerequisites.

When looking to integrate two existing protocols together using DMM it is recommended to start with the more complex of the projects (in terms of structure) as a base line. In this lab, the simple_peripheral example will be used as the base line. The reason for integrating the rfEchoRx example into the simple_peripheral example is because it requires less modifications to the project than the other way around.

Task 1 – Prepare Project for DMM Integration

Time to prepare simple_peripheral for DMM integration.

Restructure CCS Project

The first step is to adjust the project folder structure for the additional stack application and DMM. In the simple_peripheral project, do the following:

  • Add a new folder inside the Application/ folder and name it ble/. We will put all BLE specific application files in this folder, which means move all existing files in the Application/ folder into ble/.

  • Add a new folder inside the Application/ folder and name it prop/. We will put all rfEchoRx specific files in this folder, but we will do this later.

  • Create a new top-level folder called dmm/ . We will put all DMM specific source files in this folder.

When you are done, your project explorer should look something like this:

In order for the project to find the recently added files, add the following include search paths in the project properties:

  1. Navigate to Project PropertiesBuildARM CompilerInclude Options.

  2. Add the following search paths:

    • ${PROJECT_LOC}/Application/ble
    • ${PROJECT_LOC}/Application/prop
    • ${PROJECT_LOC}/dmm
    • ${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti
    • ${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/dmm

We need to add the USE_DMM define in order to enable DMM for TI source code:

  1. Navigate to Project PropertiesBuildARM CompilerPredefined Symbols.

  2. Add the following pre-define symbol:

    • USE_DMM

Add DMM Files

Now it is time to add the DMM files to the newly created dmm/ folder. Go to the <SDK>/source/ti/dmm/ directory and link the following files into the project dmm/ folder:

  • dmm/dmm_rfmap.h
  • dmm/dmm_policy.h
  • dmm/dmm_scheduler.h

When asked by CCS if to copy or link the files to the project, select linking them. The DMM files do not require any modifications which means we do not need to create local copies of them.

In addition to the header files above we need a policy table and a global priority table. The policy table is defined within SysConfig, so there is no need to create any source files for that. However, we will be required to create the global priority table manually.

Create the Global Priority Table

There already exists pre-made global priority tables in the SDK for DMM examples, which consists of various stack combinations. One particularly is the BLE + WSN combination, which we can re-use for our purpose by replacing WSN with Proprietary stack.

Copy (do not link) over the following files from <SDK>/source/ti/dmm/ to dmm/:

  • dmm_priority_ble_wsn.c
  • dmm_priority_ble_wsn.h

Rename both files to dmm_priority_ble_prop, and rename all instances of WSN to PROP (as well as wsn and prop) in the source code.

The replacement is case sensitive!

If using a "search and replace" feature to replace the occurrences of WSN/wsn and PROP/prop you will need to make sure it is set to be case sensitive.

In addition, we will replace the existing RF activities for WSN with Prop. Which activities that should replace the WSN ones depend on the requirements of the proprietary radio application. In this lab we only require two activities due to the low complexity of the rfEchoRx, RX/TX and FS:

  • DMM_PROP_RX_TX = 0x0A01
  • DMM_PROP_FS = 0x0A02

Note that the value used for the activity is not to important as long as each activity has its own number. What is important is that the application later refer to the correct activity number when scheduling radio commands.

We will re-use the Stack Activity struct defined for WSN (named activityPROP_bleLpropH after the wsn to prop replacement) but we need need to adjust the activities to match the new activities defined above. We also need to change the DMMPolicy_StackRole_PropNode (previously DMMPolicy_StackRole_WsnNode before the Wsn to Prop replacement) enum to DMMPolicy_StackRole_reserved1. When done, you should end up with something like this:

#include "dmm/dmm_policy.h"

//! \brief Activity numbers and priority numbers
#define ACTIVITY_NUM_BLE                        4
#define ACTIVITY_NUM_PROP                       2

extern GlobalTable globalPriorityTable_bleLpropH[DMMPOLICY_NUM_STACKS];

dmm_priority_ble_prop.h

#include <dmm/dmm_priority_ble_prop.h>
#include "dmm/dmm_policy.h"

/* BLE Activity */
typedef enum
{
    /* ... unmodified ... */
} DMMStackActivityBLE;

typedef enum
{
    DMM_PROP_RX_TX = 0x0A01,
    DMM_PROP_FS    = 0x0A02,
} DMMStackActivityPROP;

/* Global Priority Table: BLE connection lower than Prop data */

StackActivity activityBLE_bleLpropH[ACTIVITY_NUM_BLE * PRIORITY_NUM] =
{
    /* ... unmodified ... */
};

StackActivity activityPROP_bleLpropH[ACTIVITY_NUM_PROP * PRIORITY_NUM] =
{
    /* Activity order matters */
    DMM_GLOBAL_PRIORITY(DMM_PROP_RX_TX, DMM_StackPNormal, 90),
    DMM_GLOBAL_PRIORITY(DMM_PROP_RX_TX, DMM_StackPHigh, 180),
    DMM_GLOBAL_PRIORITY(DMM_PROP_RX_TX, DMM_StackPUrgent, 240),

    DMM_GLOBAL_PRIORITY(DMM_PROP_FS, DMM_StackPNormal, 90),
    DMM_GLOBAL_PRIORITY(DMM_PROP_FS, DMM_StackPHigh, 180),
    DMM_GLOBAL_PRIORITY(DMM_PROP_FS, DMM_StackPUrgent, 240),
};


/* the order of stacks in policy table and global table must be the same */
GlobalTable globalPriorityTable_bleLpropH[DMMPOLICY_NUM_STACKS] =
{
    {
        .globalTableArray =  activityBLE_bleLpropH,
        .tableSize = (uint8_t)(ACTIVITY_NUM_BLE * PRIORITY_NUM),
        .stackRole = DMMPolicy_StackRole_BlePeripheral,
    },

    {
        .globalTableArray =  activityPROP_bleLpropH,
        .tableSize = (uint8_t)(ACTIVITY_NUM_PROP * PRIORITY_NUM),
        .stackRole = DMMPolicy_StackRole_custom1,
    },
};

dmm_priority_ble_prop.c

This global priority table will prioritize Proprietary data over BLE connections. For now we will leave this as is and revisit it again later in this lab.

To further improve the final result, the BLE long-range advertisement set is disabled. This is done because the long-range communication takes up more radio time than legacy advertising. Since our project does not need to be a long-range application, the extra radio time can be freed up in favor of other commands. It is possible to use long- range, but it will impact the final performance.

To disable the second advertisement set, make the changes according to the snippets found in the following expansion:

First, disable the second advertisement set in SysConfig.

Then, comment out the configuration of said advertisement set in simple_peripheral.c.

        // Enable legacy advertising for set #1
        status = GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
        SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);

        /* Disable this chunk of code */
#if 0
        // Create Advertisement set #2 and assign handle
        status = GapAdv_create(&SimplePeripheral_advCallback, &advParams2,
                               &advHandleLongRange);
        SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);

        // Load advertising data for set #2 that is statically allocated by the app
        status = GapAdv_loadByHandle(advHandleLongRange, GAP_ADV_DATA_TYPE_ADV,
                                     sizeof(advData2), advData2);
        SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);

        // Set event mask for set #2
        status = GapAdv_setEventMask(advHandleLongRange,
                                     GAP_ADV_EVT_MASK_START_AFTER_ENABLE |
                                     GAP_ADV_EVT_MASK_END_AFTER_DISABLE |
                                     GAP_ADV_EVT_MASK_SET_TERMINATED);

        // Enable long range advertising for set #2
        status = GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
        SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
#endif

simple_peripheral.c – SimplePeripheral_processGapMessage()

Finally, remove the advertisement set from the auto connect function.

    bool SimplePeripheral_doAutoConnect(uint8_t index)
    {
        if (index == 1)
        {
          if (autoConnect != AUTOCONNECT_GROUP_A)
          {
            GapAdv_disable(advHandleLongRange);
            GapAdv_disable(advHandleLegacy);
            advData1[2] = 'G';
            advData1[3] = 'A';
#if 0
            advData2[2] = 'G';
            advData2[3] = 'A';
#endif
            GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            autoConnect = AUTOCONNECT_GROUP_A;
          }    
          Display_printf(dispHandle, SP_ROW_AC, 0, "AutoConnect enabled: Group A");
        }
        else if (index == 2)
        {
          if (autoConnect != AUTOCONNECT_GROUP_B)
          {
            GapAdv_disable(advHandleLongRange);
            GapAdv_disable(advHandleLegacy);
            advData1[2] = 'G';
            advData1[3] = 'B';
#if 0
            advData2[2] = 'G';
            advData2[3] = 'B';
#endif
            GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            autoConnect = AUTOCONNECT_GROUP_B;
          } 
          Display_printf(dispHandle, SP_ROW_AC, 0, "AutoConnect enabled: Group B");
        }
        else
        {
          if (autoConnect)
          {
            GapAdv_disable(advHandleLongRange);
            GapAdv_disable(advHandleLegacy);
            advData1[2] = 'S';
            advData1[3] = 'P';
#if 0
            advData2[2] = 'S';
            advData2[3] = 'P';
#endif
            GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            autoConnect = AUTOCONNECT_DISABLE;
          } 
          Display_printf(dispHandle, SP_ROW_AC, 0, "AutoConnect disabled");
        }
        tbm_goTo(&spMenuMain);

        return (true);
    }

simple_peripheral.c – SimplePeripheral_doAutoConnect()

Setting up DMM and Policy Table in SysConfig

The policy table is configured in SysConfig. Open SysConfig by double clicking simple_peripheral.syscfg.

Add the DMM component found under the Multi-Protocol section. Note that you will immediately get a warning, but we will fix this shortly.

First set the stack roles to be BLE Peripheral and Custom 1. The Custom 1 stack role will correspond to the custom1 stack role we used in the global priority table.

Next, we add the following application states for BLE. Note that the first application state is reserved for the Any application state, which is common for both stack roles.

Lastly, in the policy table, there is already one DMM policy. This is the default policy, which we need to set one stack role with higher priority. This is the source of the warning we initially got, because the default policy is used as a tie breaker between both stack roles.

In this case, set BLE to be higher priority in a tie breaker by giving it a higher weight. Any number suffices. When set, the warning should disappear.

Initialize and use DMM in the Project

Now that the DMM source files and tables in place, the BLE application needs to be modified to use DMM. We will start with initializing DMM and make it aware of the BLE stack. This means we need to do the following modifications to the main.c file, found in the Startup/ folder:

  • Include the following header files at the top

    • dmm/dmm_policy.h
    • dmm/dmm_scheduler.h
    • dmm/dmm_priority_ble_prop.h
    • ti_dmm_application_policy.h
  • Initialize and open up the DMM Policy Manager using the policy table defined inside ti_dmm_application_policy.h and the global priority table defined inside dmm_priority_ble_prop.h

  • Initialize and open up the DMM Scheduler
  • Extract the BLE stack handle using Icall_getRemoteTaskHandle(0) and register the client with the DMM Scheduler
  • Set the initial BLE Peripheral stack state to "Advertising"

Add the following changes inside main.c:

/*******************************************************************************
 * INCLUDES
 */

/* Include DMM module */
#include "dmm/dmm_policy.h"
#include "dmm/dmm_scheduler.h"
#include "dmm/dmm_priority_ble_prop.h"
#include "ti_dmm_application_policy.h"

int main()
{
    // =========== ADD START ===========
    Task_Handle* pBleTaskHndl;
    DMMPolicy_Params dmmPolicyParams;
    DMMSch_Params dmmSchParams;
    // =========== ADD END ===========

    ...

    /* Start tasks of external images - Priority 5 */
    ICall_createRemoteTasks();
    // =========== ADD START ===========
    pBleTaskHndl = ICall_getRemoteTaskHandle(0);
    // =========== ADD END ===========

    ...

    // =========== ADD START ===========
    /* initialize and open the DMM policy manager */
    DMMPolicy_init();
    DMMPolicy_Params_init(&dmmPolicyParams);
    dmmPolicyParams.numPolicyTableEntries = DMMPolicy_ApplicationPolicySize;
    dmmPolicyParams.policyTable = DMMPolicy_ApplicationPolicyTable;
    dmmPolicyParams.globalPriorityTable = globalPriorityTable_bleLpropH;
    DMMPolicy_open(&dmmPolicyParams);

    /* initialize and open the DMM scheduler */
    DMMSch_init();
    DMMSch_Params_init(&dmmSchParams);
    memcpy(dmmSchParams.stackRoles,
           DMMPolicy_ApplicationPolicyTable.stackRole,
           sizeof(DMMPolicy_StackRole) * DMMPOLICY_NUM_STACKS);
    dmmSchParams.indexTable = DMMPolicy_ApplicationPolicyTable.indexTable;
    DMMSch_open(&dmmSchParams);

    /* register clients with DMM scheduler */
    DMMSch_registerClient(pBleTaskHndl, DMMPolicy_StackRole_BlePeripheral);

    /*set the stacks in default states */
    DMMPolicy_updateStackState(DMMPolicy_StackRole_BlePeripheral, DMMPOLICY_BLE_ADV);
    // =========== ADD END ===========

    /* enable interrupts and start SYS/BIOS */
    BIOS_start();

    return(0);
}

main.c – Initialize and open up the DMM Scheduler and Policy module

Now we also need to add code to the BLE application to update the application state when appropriate. The BLE stack features a "fast state update" callback which can be used to react quickly to state changes within the app, such as going from a non-connected to connecting state. This means the callback can be used to handle two of the four application state transitions, the other two need to be handled in other parts of the application.

In order to use this callback, the callback function needs to be assigned inside the ble_user_config.c file as seen in the code snippet below where SimplePeripheral_fastStateUpdateCb() has been defined as the callback function. We also need to include the simple_peripheral.h header file which will contain the function declaration. We also require the dmm_policy.h header file to access the DMM stack role enum, and dmm_rfmap.h to override the BLE RF calls which would typically override the DMM.

It is HIGHLY recommended to delete the existing ble_user_config.c linked file in the iCallBLE with a copied version from <SDK>/source/ti/ble5stack/icall/app. This to avoid interfering with other BLE projects in the SDK. While performing the changes one might find that the code block is not currently in use due to a missing ICALL_JT define. This is OK as the define will later be generated by SysConfig.

// =========== ADD START ===========
// Include application header
#include "simple_peripheral.h"
#include "dmm/dmm_policy.h"
#include "dmm/dmm_rfmap.h"
// =========== ADD END ===========

// BLE Stack Configuration Structure
const stackSpecific_t bleStackConfig =
{
  .maxNumConns                          = MAX_NUM_BLE_CONNS,
  .maxNumPDUs                           = MAX_NUM_PDU,
  .maxPduSize                           = 0,
  .maxNumPSM                            = L2CAP_NUM_PSM,
  .maxNumCoChannels                     = L2CAP_NUM_CO_CHANNELS,
  .maxWhiteListElems                    = MAX_NUM_WL_ENTRIES,
  .maxResolvListElems                   = CFG_MAX_NUM_RL_ENTRIES,
  .pfnBMAlloc                           = &pfnBMAlloc,
  .pfnBMFree                            = &pfnBMFree,
  .rfDriverParams.powerUpDurationMargin = RF_POWER_UP_DURATION_MARGIN,
  .rfDriverParams.inactivityTimeout     = RF_INACTIVITY_TIMEOUT,
  .rfDriverParams.powerUpDuration       = RF_POWER_UP_DURATION,
  .rfDriverParams.pErrCb                = &(RF_ERR_CB),
  .eccParams                            = &eccParams_NISTP256,
  .fastStateUpdateCb                    = SimplePeripheral_fastStateUpdateCb,
  .bleStackType                         = DMMPolicy_StackRole_BlePeripheral,
#ifdef CC2652RB_LAUNCHXL
  .extStackSettings                     = EXTENDED_STACK_SETTINGS | CC2652RB_OVERRIDE_USED,
#else
  .extStackSettings                     = EXTENDED_STACK_SETTINGS,
#endif
  .maxNumCteBuffers                     = MAX_NUM_CTE_BUFS
};

ble_user_config.c – bleStackConfig

Now SimplePeripheral_fastStateUpdateCb() needs to be implemented. This is done inside simple_peripheral.c and declared inside simple_peripheral.h. The function prototype for the callback is found inside ble_user_config.h.

A possible implementation of the SimplePeripheral_fastStateUpdateCb() function, which covers the "Advertising" and "Connecting" (which falls under "Params Update") application states, is shown in the code snippet below. Note that the "Connected" application state will be handled by a different part of the BLE application.

/*********************************************************************
 * INCLUDES
 */
#include "dmm/dmm_policy.h"
#include "ti_dmm_application_policy.h"

...

/* Fast State Change Callback */
void SimplePeripheral_fastStateUpdateCb(uint32_t stackType, uint32_t stackState)
{
    if(stackType == DMMPolicy_StackRole_BlePeripheral)
    {
      static uint32_t prevStackState = 0;

      if( !(prevStackState & LL_TASK_ID_SLAVE) && (stackState & LL_TASK_ID_SLAVE))
      {
          //We just initiated a connection

          /* update DMM policy */
          DMMPolicy_updateStackState(DMMPolicy_StackRole_BlePeripheral, DMMPOLICY_BLE_PARAMUPDATE);

      }
      else if( (prevStackState & LL_TASK_ID_SLAVE) && !(stackState & LL_TASK_ID_SLAVE))
      {
          //We just disconnected

          /* update DMM policy */
          DMMPolicy_updateStackState(DMMPolicy_StackRole_BlePeripheral, DMMPOLICY_BLE_ADV);
      }

      prevStackState = stackState;
    }
}

simple_peripheral.c – SimplePeripheral_fastStateUpdateCb(): Update the DMM stack state when connecting to or disconnecting from a BLE host

In the simple_peripheral.h file, declare the function prototype of the SimplePeripheral_fastStateUpdateCb function used in ble_user_config.c.

/*********************************************************************
 * FUNCTIONS
 */

/* Fast State Change Callback */
void SimplePeripheral_fastStateUpdateCb(uint32_t stackType, uint32_t stackState);

simple_peripheral.hSimplePeripheral_fastStateUpdateCb() prototype

The remaining connected state should be set when the device is considered "connected". In this lab, the connected state is defined as having an active connection and that the connection parameters request has been completed. To achieve this, the state can be updated following a gap_link_param_update_event in the SimplePeripheral_processGapMessage() function.

static void SimplePeripheral_processGapMessage(gapEventHdr_t *pMsg)
{
    ...

    case GAP_LINK_PARAM_UPDATE_EVENT:
    {
        ...

        // =========== ADD START ===========
        //update the state to connected
        DMMPolicy_updateStackState(DMMPolicy_StackRole_BlePeripheral, DMMPOLICY_BLE_CONNECTED);
        // =========== ADD END ===========

        break;
    }
    ...
}

simple_peripheral.c – Set the connected state

Time to build the project and download to the device! If done correctly, you should still observe the same behavior and print output.

Marvelous!

The Simple Peripheral project is now a DMM-enabled project! However, the DMM functionality isn't used, as there currently isn't a secondary radio application to run concurrently alongside the BLE-stack.

It is time to add the Proprietary Collector!

Task 2 – Add Proprietary Stack to the Project

Now that the simple_peripheral project has been modified to use DMM, it is time to add in the second radio protocol. Copy the contents of the rfEchoRx project into the Application/prop/ folder in the simple_peripheral project:

  • rfEchoRx.c
  • RFQueue.c
  • RFQueue.h

Initial Proprietary Example Changes

The following modifications are required to integrate the Proprietary example to the simple_peripheral project:

  1. The proprietary radio task handle need to be registered with the DMM. To do this we need to implement a rfEchoRx_createRadioTask() function that sets up the task (this was previously done within the rfEchoRx main file). The function should be implemented below mainThread:

     #include <ti/sysbios/knl/Task.h>
    
     #define PROP_STACK_SIZE    2096
     #define PROP_TASK_PRIORITY 1  
    
     Task_Struct propTask;
     Task_Handle propTaskHandle;
    
     uint8_t propTaskStack[PROP_STACK_SIZE];
    
     Task_Handle* rfEchoRx_createRadioTask() {
         Task_Params taskParams;
    
         // Configure task
         Task_Params_init(&taskParams);
         taskParams.stack = propTaskStack;
         taskParams.stackSize = PROP_STACK_SIZE;
         taskParams.priority = PROP_TASK_PRIORITY;
    
         Task_construct(&propTask, (ti_sysbios_knl_Task_FuncPtr) mainThread, &taskParams, NULL);
    
         propTaskHandle = Task_handle(&propTask);
         return &propTaskHandle;
     }
    

    rfEchoRx.c – rfEchoRx_createRadioTask()

  2. Modify the main() function to call the newly "create task" function. Use the returned Task handle pointer from rfEchoRx_createRadioTask() to register the Proprietary example as a DMM client, and set the initial DMM application state for the Proprietary Collector to DMMPOLICY_ANY.

     extern Task_Handle* rfEchoRx_createRadioTask();
    
     int main()
     {
         Task_Handle* pBleTaskHndl;
         // =========== ADD START ===========
         Task_Handle* pPropTaskHndl;
         // =========== ADD END ===========
         DMMPolicy_Params dmmPolicyParams;
         DMMSch_Params dmmSchParams;
    
         Board_initGeneral();
    
         ...
    
         SimplePeripheral_createTask();
         // =========== ADD START ===========
         pPropTaskHndl = rfEchoRx_createRadioTask();
         // =========== ADD END ===========
    
         ...
    
         DMMSch_registerClient(pBleTaskHndl, DMMPolicy_StackRole_BlePeripheral);
         // =========== ADD START ===========
         DMMSch_registerClient(pPropTaskHndl, DMMPolicy_StackRole_custom1);
         // =========== ADD END ===========
    
         ...
    
         DMMPolicy_updateStackState(DMMPolicy_StackRole_BlePeripheral, DMMPOLICY_BLE_ADV);
         // =========== ADD START ===========
         DMMPolicy_updateStackState(DMMPolicy_StackRole_custom1, DMMPOLICY_ANY);
         // =========== ADD END ===========
    
         ...
     }
    

    main.cmain() modifications

    POSIX Threads with DMM

    It is currently not possible to use POSIX Threads with DMM, as DMM expects handles of type TI-RTOS Task handle pointer when registering a DMM client. It is not possible to trivially extract the Task handle pointer from POSIX Threads, and hence must be converted to use TI-RTOS Tasks instead.

  3. We need to add RF settings for the Proprietary Collector, which is done in SysConfig.

    • Under the RF Design section, set the Sub-1 GHz frequency band to 770 - 930 MHz.

    • Add the Custom component under the RF Stacks section. Select 50 kbps, 25 kHz Deviation, 2-GFSK, 100 kHz RX Bandwidth under Proprietary (779 - 930 MHz). Then expand through the settings group.

    • In the settings group, navigate to PHY PropertiesTX Power (dBm) and select some lower TX power.

    • In the settings group, navigate to Code Export ConfigurationSymbol Name Generation Method and select Custom.

  4. Lastly, we need to add the red and green LED GPIO in SysConfig. Navigate to the GPIO component and add two new GPIO and rename them to CONFIG_GPIO_RLED and CONFIG_GPIO_GLED. Then connect each new GPIO to the corresponding hardware, LaunchPad LED Red or LaunchPad LED Green.

    PIN Workaround

    The rfEchoRx example leverages the PIN driver which is currently not fully supported by SysConfig. While SysConfig will generate values that can be used together with the PIN driver, the naming scheme depend on a range of factors.

    A simple workaround to not have to consider the changing naming scheme is to define your own PIN defines to match what was used in the rfEchoRx project. For the green and red LED, the following defines should be added to the top of the rfEchoRx.c file.

         #define CONFIG_PIN_GLED PIN_ID(7)
         #define CONFIG_PIN_RLED PIN_ID(6)
    

DMM changes

The rfEchoRx application uses the RF driver and is currently not prepared for DMM. The first step in making the rfEchoRx code DMM compatible is to swap out the default RF.h header file for the DMM equivalent. To do this, replace ti/drivers/rf/RF.h with the DMM RF map dmm_rfmap.h header. Also include the dmm_scheduler.h and ti_dmm_application_policy.h header files.

#ifdef USE_DMM
#include "dmm/dmm_rfmap.h"
#include "dmm/dmm_scheduler.h"
#include "dmm/dmm_policy.h"
#include "ti_dmm_application_policy.h"
#else
#include <ti/drivers/rf/RF.h>
#endif

rfEchoRx.c – Replace the RF driver header file and add DMM header files

Adapt the use of RF driver APIs

The first adaption we need to do in the code is set the approprieate nID, which allows the stack to be properly registered when DMM_RFOpen is executed.

void *mainThread(void *arg0)
{
    RF_Params rfParams;
    RF_Params_init(&rfParams);
    rfParams.nID = RF_STACK_ID_CUSTOM;

rfEchoRx.c – Add rfParams.nID to mainThread

The next change is to replace the use of RF_postCmd() with RF_scheduleCmd() where the "schedule" version is a DMM compatible API.

There is however a caveat in our application and that is the "Set frequency" call. As CMD_FS (RF_cmdFs in the code) needs to run before any RX or TX command can run we want to block the rfEchoRx task until it is done. We can do this by instead using RF_runScheduleCmd() where "run" implies that the API call is blocking.

As we are moving to the use of RF_scheduleCmd() and RF_runScheduleCmd(), we also need to adapt to some new input parameters. While RF_postCmd() and RF_runCmd() has a RF_Priority argument, the RF_scheduleCmd() and RF_runScheduleCmd() instead have a RF_ScheduleCmdParams argument. The resulting API replacement should end up looking something like this:

/* Set the frequency */
RF_ScheduleCmdParams scheduleParams;
RF_ScheduleCmdParams_init(&scheduleParams);

RF_runScheduleCmd(rfHandle, (RF_Op*)&RF_cmdFs, &scheduleParams, NULL, 0);

rfEchoRx.c – Replace the RF_postCmd() API.

The final touch needed is to populate the newly added scheduleParams with fitting values. An example on how these could be specified in the case of the RF_cmdFs command is:

/* RF activities enum matching those defined for the GPT */
typedef enum {
    RfActivity_Rx_Tx = 0x0A01,
    RfActivity_Fs = 0x0A02,
} RfActivity_t;

...

RF_ScheduleCmdParams_init(&scheduleParams);

/* =========== Populated parameters =========== */
scheduleParams.startTime    = 0;
scheduleParams.startType    = RF_StartNotSpecified;
scheduleParams.allowDelay   = RF_AllowDelayAny;
scheduleParams.duration     = ~(0); // The CMD_FS will run until done
scheduleParams.endTime      = ~(0); // The CMD_FS will run until done
scheduleParams.endType      = RF_EndNotSpecified;
scheduleParams.activityInfo = ((RfActivity_Fs) & 0xFFFF) << 16 | ((DMM_StackPNormal) & 0xFFFF);
/* ============================================= */

RF_runScheduleCmd(rfHandle, (RF_Op*)&RF_cmdFs, &scheduleParams, NULL, 0);

rfEchoRx.c – Populated scheduling parameters.

The parameters above can be interpreted as the RF_cmdFs not having an start or end time and that it is expected to run until done. We also assign it as a RfActivity_Fs activity (matching the value defined earlier when creating the GPT) with a normal priority.

Why is the activityInfo parameter so messy?

The activityInfo parameter found in the RF_ScheduleCmdParams struct is used by the DMM and ties in to the GPT. It needs to contain both the current activity and priority level of the command. The DMM expects to find the activity in the top two bytes and the priority level in the lower two bytes.

To make user code more clean, a macro can be defined to help assign this value:

    #define GEN_ACTIVITY_TABLE(activity, priority)  (((activity) & 0xFFFF) << 16 | ((priority) & 0xFFFF))

activityInfo utility define

This means we could instead assign the activityInfo value as below, making the activityInfo easier to read:

    schParams.activityInfo = GEN_ACTIVITY_TABLE(RfActivity_Fs, DMM_StackPNormal);

Once we have sorted out the RF_cmdFs above, we need to address the RF_runCmd() used when issuing the RF_cmdPropRx radio command. We do this by replacing it with the same API as above, RF_runScheduleCmd(). We also need to adjust the scheduling parameters once again to fit our RX command:

    /* Wait for a packet
        * - When the first of the two chained commands (RX) completes, the
        * RF_EventCmdDone and RF_EventRxEntryDone events are raised on a
        * successful packet reception, and then the next command in the chain
        * (TX) is run
        * - If the RF core runs into an issue after receiving the packet
        * incorrectly onlt the RF_EventCmdDone event is raised; this is an
        * error condition
        * - If the RF core successfully echos the received packet the RF core
        * should raise the RF_EventLastCmdDone event
        */


    scheduleParams.activityInfo = GEN_ACTIVITY_TABLE(RfActivity_Rx_Tx, DMM_StackPNormal);

    RF_EventMask terminationReason =
            RF_runScheduleCmd(rfHandle, (RF_Op*)&RF_cmdPropRx, &scheduleParams,
                        echoCallback, (RF_EventRxEntryDone |
                        RF_EventLastCmdDone));

rfEchoRx.c – Replace the RF_runCmd() API.

As can be seen above, we do not need to adjust the scheduling parameters much as the RX command is not using absolute timing or timeouts in this application. In an appliation where the RX was scheduled to run at a specific time and/or to use a RX timeout was used, the rest of the parameters would have to be adopted accordingly.

The example uses radio command chaining!

The rfEchoRx example is designed to have the TX operation chained to the RX command. It is configured to perform the TX operation following a successful RX operation.

This lab will not cover this in more detail but instead treat it as a single command (just like the DMM would).

Final Changes to the Global Priority Table

Before we are ready to run the project, we will want to make a few adjustments to the GPT defined previously. As we want to allow the rfEchoRx application to stay in RX for most of the time, we want to make sure the "Normal" priority for the DMM_PROP_RX_TX is set higher than most of BLE activities of the same priority level.

We do not want to set it higher than DMM_BLE_BROADCASTING as we would like the device to be able to advertise at all time. We also do not want to set it higher than the DMM_BLE_CONNECTION and DMM_BLE_CON_EST activities of "High" or "Urgent" priority. This as we would like to allow the BLE to push through in critical situations (for example, "about to lose the connection").

Lower priority BLE connection

Even if the BLE connection would be setup to a lower priority compared to the proprietary application, it is still likely it will find a window to operate before the supervision timeout runs out.

If it would not find a free window and get closer to the critical end of the supervision timeout, the BLE stack would raise the priority level. This allows us to configure the GPT to "keep the connection alive" and reset the supervision timeout by "guaranteeing" access to the radio (assuming the GPT values for "High" or "Urgent" are set higher than any other activitiy).

We also want to set the DMM_PROP_FS activity of "Normal" priority to be higher then any other activity (BLE or Prop). This is a simple approach to making sure the command will be allowed to run. After performing the modifications in the dmm_priority_ble_prop.c file, we should have something looking like this:

    /* Global Priority Table: BLE connection lower than PROP data */
    StackActivity activityBLE_bleLpropH[ACTIVITY_NUM_BLE*PRIORITY_NUM] =
    {

        DMM_GLOBAL_PRIORITY(DMM_BLE_CONNECTION, DMM_StackPNormal, 80),
        DMM_GLOBAL_PRIORITY(DMM_BLE_CONNECTION, DMM_StackPHigh, 170),
        DMM_GLOBAL_PRIORITY(DMM_BLE_CONNECTION, DMM_StackPUrgent, 240),

        DMM_GLOBAL_PRIORITY(DMM_BLE_CON_EST, DMM_StackPNormal, 80),
        DMM_GLOBAL_PRIORITY(DMM_BLE_CON_EST, DMM_StackPHigh, 200),
        DMM_GLOBAL_PRIORITY(DMM_BLE_CON_EST, DMM_StackPUrgent, 240),

        DMM_GLOBAL_PRIORITY(DMM_BLE_BROADCASTING, DMM_StackPNormal, 100),
        DMM_GLOBAL_PRIORITY(DMM_BLE_BROADCASTING, DMM_StackPHigh, 160),
        DMM_GLOBAL_PRIORITY(DMM_BLE_BROADCASTING, DMM_StackPUrgent, 240),

        DMM_GLOBAL_PRIORITY(DMM_BLE_OBSERVING, DMM_StackPNormal, 80),
        DMM_GLOBAL_PRIORITY(DMM_BLE_OBSERVING, DMM_StackPHigh, 160),
        DMM_GLOBAL_PRIORITY(DMM_BLE_OBSERVING, DMM_StackPUrgent, 240),
    };

    StackActivity activityPROP_bleLpropH[ACTIVITY_NUM_PROP*PRIORITY_NUM] =
    {
        DMM_GLOBAL_PRIORITY(DMM_PROP_RX_TX, DMM_StackPNormal, 90),
        DMM_GLOBAL_PRIORITY(DMM_PROP_RX_TX, DMM_StackPHigh, 180),
        DMM_GLOBAL_PRIORITY(DMM_PROP_RX_TX, DMM_StackPUrgent, 240),

        DMM_GLOBAL_PRIORITY(DMM_PROP_FS, DMM_StackPNormal, 250),
        DMM_GLOBAL_PRIORITY(DMM_PROP_FS, DMM_StackPHigh, 180),
        DMM_GLOBAL_PRIORITY(DMM_PROP_FS, DMM_StackPUrgent, 240),
    };

dmm_priority_ble_prop.c – Update to GPT.

The reason for only changing the "Normal" priorities is due to the rfEchoRx application only using this priority level. In a more complex application it might be needed to move between priority levels and in that case, each combination in the GPT needs to be considered.

Time to build the project and download to the device! If it all works as expected, you should observe the green LED lighting up and the device advertising over BLE. The green LED is an indication from the rfEchoRx application that it was aborted/preempted which is expected given the RX would be preempted by the higher priority BLE advertisment (BLE activity DMM_BLE_BROADCASTING).

Testing the Final Project

To test this DMM project and see that it is actually works, we need to start up the rfEchoTx device again. While the DMM device is running, you should be able to verify that the rfEchoRx and rfEchoTx are able to communicate just like before we added DMM. You should however be able to observe the potential "echo error" in the LED blinking pattern which is something that is expected as we have configured our DMM project to dynamically prioritize either the BLE application or the propriteary example depending on the situation.

Using the BLE central of your choice, you should be able to find the device advertising what you have named it. Connect to it and confirm that you can read out information using the BLE connection while the rfEchoRx and rfEchoTx applications keep working in the "background".

Considerations

While this lab shows how to create a basic DMM project from scratch with a proprietary stack, it has not been designed for optimal performance. One considering that was not taken was that the rfEchoRx application is of the "RX always on" kind per design.

In these scenarios (collectors etc.) a more suiting approach could be to leverage the DMM "Balanced Mode" to assign priorities in a "dynamic time window" manner. For more information on how to used this feature, please see the DMM User's Guide.

Limitations

The current limitations are documented in the DMM User's Guide.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.