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 CC13x2 or CC26x2 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 collector example together with the BLE Simple Peripheral example. The proprietary collector example utilizes a custom synchronous time-slotted radio protocol which allows for a sleepy collector implementation. 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 recommended to have at least two LaunchPads in order to setup the proprietary sensor/collector network.

Do not get confused!

While the proprietary examples uses the notion of "Sensor" and "Collector", this is not related to the TI 15.4-Stack Sensor and Collector.

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

Prerequisites

Hardware

*A smart phone with a BLE explorer app

  • Recommendation is to use LightBlue®, which is available for both Android and Apple devices

Note

The Proprietary RF Collector and Sensor examples are currently only supported for the CC1352R.

Software

Proprietary examples on GitHub

The Proprietary RF Collector and Sensor examples used in this lab are found on the proprietary examples GitHub repository and is not part of the official SDK. These examples need to be downloaded in addition to the SimpleLink CC13x2 / CC26x2 SDK.

It is recommended to read the DMM User's Guide alongside this lab for details and further information.

Proprietary Collector and Sensor

The Proprietary Collector and Sensor examples use a very simple time-slotted protocol to realize synchronous communication between a collector and multiple sensors without the need to keep the radio on at all times. Because the protocol uses fixed time-slots, there are time-slots which are potentially unused. This allows for collision-free access to the radio at certain time-slots for other concurrently running radio protocol stacks. This makes the collector and sensor examples a good fit for showcasing the different aspects of DMM and how to integrate this with existing proprietary solutions.

The collector and sensor operate inside a fixed time window, referred to in the examples as the BEACON_INTERVAL. Once per time window the collector will broadcast its existence for new sensors in order for them to join the network. For the duration of the time window the collector will also communicate with the sensors that are already connected, exchanging data at a given point in time as illustrated in the image below.

When a new sensor wants to join the network it listens for a beacon from the collector. When a beacon is received, the sensor may send a request to join the network back to the collector that issued the beacon. When the collector and sensor has acknowledged the request, the sensor will have joined the network and been assigned a time-slot. The full association sequence following a collector beacon is illustrated in the following sequence diagram:

Please refer to the README files for each project for more information.

Running the Examples

A great way to understand how the Proprietary Collector and Sensor examples work is to run them. Start by importing and building both the collector and sensor example from the GitHub repo. When both projects have been built, program the LaunchPads with one project each and let them run.

Both the collector and sensor will output information regarding the connection status over the LaunchPad's application UART port. The output can be seen by connecting to the LaunchPad's serial port using a terminal application, such as PuTTY.

The Sensor will output its device address, data, number of missed acknowledgements, and whether it is currently connected to a Collector or regarded as an orphan. Prior to any association both the "is connected" and "is orphan" labels will display false. Similarly, the Collector will display the status of all connected nodes from its terminal. Each node is referenced by its device address.

When a Sensor has formed an association to the Collector, it will be shown in both terminals. Per default, a sensor is considered an orphan following five unsuccessful communication exchanges with the collector.

Clock drift is not handled by the simple radio protocol. This means that the collector and sensor will drift apart and over time start to miss the time slots where they are supposed to meet. However, this sorts itself out when the Sensor is regarded as an orphan and will re-associate itself to the Collector.

When you feel familiarized with the Sensor and Collector applications, you can proceed with the lab.

Before you start

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

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

  • rfCollector, found under <CLONED_REPO>/examples/rtos/<BOARD>/

  • rfSensor, found under <CLONED_REPO>/examples/rtos/<BOARD>/

where <SDK> is the install location of the SimpleLink SDK, <CLONED_REPO> is the location of the cloned proprietary_examples repo, 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". The LaunchPad used with the RF Sensor will be referred to as the "Sensor device".

After building the simple_peripheral_app 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][#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_app example will be used as the base line. The reason for integrating the rfCollector example into the simple_peripheral_app 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_app 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_app 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 Proprietary Collector specific application 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_ROOT}/Application/ble
    • ${PROJECT_ROOT}/Application/prop
    • ${PROJECT_ROOT}/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

The pre-compiled DMM library also needs to be included as part of the linker step:

  1. Navigate to Project PropertiesBuildARM LinkerFile Search Path.

  2. Add the pre-compiled DMM library at the top in the Include library file view:

    Library order matters

    It is important the DMM library is added before the RF multi-mode library.

    • If you are compiling for a CC13x2 device:

      • ${COM_TI_SIMPLELINK_CC13X2_26X2_SDK_INSTALL_DIR}/source/ti/dmm/library/tirtos/ccs/bin/dmmlib_cc13x2.a
    • If you are compiling for a CC26x2 device:

      • ${COM_TI_SIMPLELINK_CC13X2_26X2_SDK_INSTALL_DIR}/source/ti/dmm/library/tirtos/ccs/bin/dmmlib_cc26x2.a

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 copy the following files into the project dmm/ folder:

  • dmm_rfmap.h
  • dmm_policy.h
  • dmm_scheduler.h

The DMM files do not require any modifications, but we need a policy table and a global priority table. We will generate the policy table with SysConfig, so 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 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 in the source code. In addition, we will replace the existing RF activities for WSN with Prop. These RF activities have already been defined by the rfCollector example, as such:

  • RfActivity_Prop_Tx = 0x0A01
  • RfActivity_Prop_Rx = 0x0A02
  • RfActivity_Prop_Tx_Rx = 0x0A03

Note that the activity and the corresponding value is important to match. We will also use the reserved1 stack role for the Proprietary stack. You should end up with something like this:

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

extern GlobalTable globalPriorityTable[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_TX    = 0x0A01,
    DMM_PROP_RX    = 0x0A02,
    DMM_PROP_TX_RX = 0x0A03,
} DMMStackActivityPROP;

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

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

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

    DMM_GLOBAL_PRIORITY(DMM_PROP_RX, DMM_StackPNormal,  90),
    DMM_GLOBAL_PRIORITY(DMM_PROP_RX, DMM_StackPHigh,   180),
    DMM_GLOBAL_PRIORITY(DMM_PROP_RX, DMM_StackPUrgent, 240),

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


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

    {
        .globalTableArray =  activityPROP,
        .tableSize = (uint8_t)(ACTIVITY_NUM_PROP * PRIORITY_NUM),
        .stackRole = DMMPolicy_StackRole_reserved1,
    },
};

dmm_priority_ble_prop.c

This global priority table will prioritize Proprietary data over BLE connections.

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

Create the Policy Table

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 reserved1 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 made 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;
    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. Looking back at the DMM Fundamentals, one can see that for BLE, there is a "fast state update" callback. This can be leveraged for changing state when going from a non-connected to connected state and back. 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.

Remember the DMM Fundamentals!

In DMM Fundamentals, Task 4, the user is told to add a new copy of ble_user_config.c to the project to avoid interfering with other BLE projects. This is still the case.

// =========== ADD START ===========
// Include application header
#include "simple_peripheral.h"
#include "dmm/dmm_policy.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                   = 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,
  // =========== ADD START ===========
  .fastStateUpdateCb                    = SimplePeripheral_fastStateUpdateCb,
  .bleStackType                         = DMMPolicy_StackRole_BlePeripheral,
  // =========== ADD END ===========
  .extStackSettings                     = EXTENDED_STACK_SETTINGS
};

ble_user_config.c - bleStackConfig

Now SimplePeripheral_fastStateUpdateCb() needs to be implemented. This is done inside simple_peripheral.c and declared as external 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_app project has been modified to use DMM, it is time to add in the second radio protocol. Copy the contents of the application/ folder in the rfCollector project into the Application/prop/ folder in the simple_peripheral_app project.

Initial Proprietary Collector Changes

The following modifications are required to integrate the Proprietary stack to the simple_peripheral_app project:

  1. The rfCollector project has two functions, rfCollector_createRadioTask() and rfCollector_createNodeTask(), in its main.c which creates the two different tasks for the project. Copy over these functions, including the task function prototypes, defines and variables, into main.c of the simple_peripheral_app project.

     void radioThread(uintptr_t arg0, uintptr_t arg1);
     void processingThread(uintptr_t arg0, uintptr_t arg1);
    
     #define RADIO_TASK_PRI     2
     #define RADIO_TASK_SIZE    1024
     static uint8_t radioTaskStack[RADIO_TASK_SIZE];
     static Task_Struct radioTaskStruct;
     static Task_Handle radioTaskHandle;
    
     #define NODE_TASK_PRI     1
     #define NODE_TASK_SIZE    1024
     static uint8_t nodeTaskStack[NODE_TASK_SIZE];
     static Task_Struct nodeTaskStruct;
     static Task_Handle nodeTaskHandle;
    
     /*
      *  ======== rfCollector_createRadioTask ========
      */
     Task_Handle* rfCollector_createRadioTask(void)
     {
         Task_Params taskParams;
         Task_Params_init(&taskParams);
         taskParams.stack = radioTaskStack;
         taskParams.stackSize = RADIO_TASK_SIZE;
         taskParams.priority = RADIO_TASK_PRI;
    
         Task_construct(&radioTaskStruct, radioThread, &taskParams, NULL);
         radioTaskHandle = Task_handle(&radioTaskStruct);
         return &radioTaskHandle;
     }
    
     /*
      *  ======== rfCollector_createNodeTask ========
      */
     Task_Handle* rfCollector_createNodeTask(void)
     {
         Task_Params taskParams;
         Task_Params_init(&taskParams);
         taskParams.stack = nodeTaskStack;
         taskParams.stackSize = NODE_TASK_SIZE;
         taskParams.priority = NODE_TASK_PRI;
    
         Task_construct(&nodeTaskStruct, processingThread, &taskParams, NULL);
         nodeTaskHandle = Task_handle(&nodeTaskStruct);
         return &nodeTaskHandle;
     }
    

    main.c – copy over create task functions

  2. Modify the main() function to call the newly copied create task functions. Use the returned Task handle pointer from rfCollector_createRadioTask() to register the Proprietary Collector as a DMM client, and set the initial DMM application state for the Proprietary Collector to DMMPOLICY_PROP_IDLE. Lastly, also make a call to GPIO_init() after Board_initGeneral().

     #include <ti/drivers/GPIO.h>
    
     int main()
     {
         Task_Handle* pBleTaskHndl;
         // =========== ADD START ===========
         Task_Handle* pPropTaskHndl;
         // =========== ADD END ===========
         DMMPolicy_Params dmmPolicyParams;
         DMMSch_Params dmmSchParams;
    
         Board_initGeneral();
         // =========== ADD START ===========
         GPIO_init();
         // =========== ADD END ===========
    
         SimplePeripheral_createTask();
         // =========== ADD START ===========
         pPropTaskHndl = rfCollector_createRadioTask();
         rfCollector_createNodeTask();
         // =========== ADD END ===========
    
         DMMSch_registerClient(pBleTaskHndl, DMMPolicy_StackRole_BlePeripheral);
         // =========== ADD START ===========
         DMMSch_registerClient(pPropTaskHndl, DMMPolicy_StackRole_reserved1);
         // =========== ADD END ===========
    
         DMMPolicy_updateStackState(DMMPolicy_StackRole_BlePeripheral, DMMPOLICY_BLE_ADV);
         // =========== ADD START ===========
         DMMPolicy_updateStackState(DMMPolicy_StackRole_reserved1, DMMPOLICY_PROP_IDLE);
         // =========== 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. Next is to add the new DMM application states for the Proprietary Collector in SysConfig. Open SysConfig and navigate to the DMM component. Add the following new application states.

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

    • Add the Custom component under the RF Stacks section. Select 50 kbps, 2-GFSK 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.

  5. Lastly, we need to add the red LED GPIO in SysConfig. Navigate to the GPIO component and add another GPIO. Rename it to CONFIG_GPIO_RLED and connect it to the LaunchPad LED Red hardware.

DMM changes

Currently, the Collector application uses the RF driver and is not initially prepared for DMM. To add flexibility to the project, one can make the driver inclusion case dependent on which defines are in the current project. This way, the code will compile both by itself and in a DMM application. Create an #ifdef USE_DMM where the RF driver header ti/drivers/rf/RF.h is replaced with the DMM RF map dmm_rfmap.h header.

/* Drivers */
#include <ti/drivers/GPIO.h>
#include <ti/display/Display.h>
#include <ti/drivers/utils/List.h>

// =========== MODIFY START ===========
#ifdef USE_DMM
#include "dmm/dmm_rfmap.h"
#else
#include <ti/drivers/rf/RF.h>
#endif
// =========== MODIFY START ===========

rfCollector.c – replace the RF driver header file

Also include the dmm_scheduler.h and dmm_policy_blesp_rfColl.h headers files.

/* DMM */
#include "dmm/dmm_scheduler.h"
#include "ti_dmm_application_policy.h"

rfCollector.c – adding DMM header files.

Update Collector Application States

The Collector application states used above serve no purpose unless they are also utilized somewhere in the collector application. It is now time to have a look at the Collector radioThread() to identify when it is reasonable to update the DMM application state.

Would I need to look at the processing thread as well?

No! Simply put, the DMM application states should cover radio related application states. In the case of the processingThread(), it does not perform any radio related tasks and hence does not need to update application states.

One possible start could be to update the states in the beginning of each function using the radio so that the relevant state is set. In between application states, the idle state could be used to mark that the collector currently do not use the radio.

For the collector application, however, this is not enough as some of the states span over several functions. As the radio thread in the RF collector application has a linear execution, a simpler way to implement the state change is to do this inside the main application loop. The following snippet shows an example of how the state transitions could look like for the collector application.

/*
 *  ======== radioThread ========
 */
void radioThread(uintptr_t arg0, uintptr_t arg1)
{
    DMMPolicy_updateStackState(DMMPolicy_StackRole_reserved1, DMMPOLICY_PROP_IDLE);

    ...

    while (1)
    {
        /* Set beacon application state */
        DMMPolicy_updateStackState(DMMPolicy_StackRole_reserved1, DMMPOLICY_PROP_BEACON);
        /* Send a beacon to probe for new nodes */
        nodeAddress = sendBeacon(nextBeaconTime);

        /* If a node request to join, process it */
        if (nodeAddress > 0)
        {
            /* Processing a join request-state */
            DMMPolicy_updateStackState(DMMPolicy_StackRole_reserved1, DMMPOLICY_PROP_JOIN_REQ);

            BeaconStatus_t retVal = processJoinRequest(nodeAddress, nextBeaconTime);

            ...

        }

        /* If there is any joined nodes, process them */
        if (nodeEntries)
        {
            /* Process each node */
            NodeElement_t* node;
            for (node = (NodeElement_t *) List_head(&nodeList);
                 node != NULL;
                 node = (NodeElement_t *) List_next((List_Elem *) node))
            {
                if (!(node->isOrphan))
                {
                    /* Entering "listen for node"-state */
                    DMMPolicy_updateStackState(DMMPolicy_StackRole_reserved1, DMMPOLICY_PROP_LISTENFORNODE);

                    SensorStatus_t retVal = listenForNode(node);
                    ...

                }
                else
                {
                    /* Idle when not listening for a node */
                    DMMPolicy_updateStackState(DMMPolicy_StackRole_reserved1, DMMPOLICY_PROP_IDLE);
                }
            }
        }

        /* Calculate the next beacon time stamp */
        nextBeaconTime = nextBeaconTime + RF_convertMsToRatTicks(BEACON_INTERVAL);
    }
}

rfCollector.c - Add DMM state transitions

This particular use-case could be further simplified by simply setting the initial state to a high-priority one and leave it at that as all states is setup in the same way. To further highlight the DMM functionality in this lab, multiple state transitions are performed which could be redundant.

Extend the DMM Policy

Now that we have added the collector application to the project, we can now exten the DMM policy table with addition policy entries if needed.

In this lab, the desired policy should consider the collector application to always have high priority. However, when forming a BLE connection there is a lot of critical initial communication that should not be disrupted. Because of this, the BLE-stack could be given an increased priority while forming the connection, returning back to lower priority when connected.

However, the current setup with the global priority table ensures that the Proprietary Collector has higher priority than BLE in the normal use case, and BLE has higher priority when establishing a connection. Hence, we don't need to extend the DMM policy table.

Final Changes, Resolve Display Conflicts

Now the integration is almost done but before the project is ready to be tested some resource conflicts need to be addressed. As both the simple_peripheral and rfCollector example utilize the Display driver to output information over the serial port there would be a resource conflict in the current implementation where both tries to allocate the same hardware.

To simplify the lab, we will simply comment out the part of rfCollector.c which opens the handle to the Display driver. In rfCollector.c, comment out the following code:

void processingThread(uintptr_t arg0, uintptr_t arg1)
{
    Display_Handle displayHandle = NULL;

    // =========== COMMENT OUT START ===========
    /* Open the display handle */
    //displayHandle = Display_open(Display_Type_ANY, NULL);
    // =========== COMMENT OUT END ===========
}

rfCollector.c – comment out Display_open()

Build and download the project to the DMM device. If you did everything correct, you should now have a fully operational DMM device with a BLE simple peripheral and a proprietary RF Collector.

Testing the Final Project

To test this dual project and see that it is actually working we need to start the RF Sensor again. Open up the two serial terminals by opening the two LaunchPad serial ports COM*: XDS110 Class Application/User UART and COM*: XDS110 Class Auxiliary Data Port. While the DMM device is running, you should be able to verify that the sensor and collector are able to establish and maintain a connection, just like before we added DMM.

Now, 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 Collector and Sensor interact simultaneously.

The functionality can be verified by setting up a RF Sensor and simultaneously connect to the DMM device with a smartphone (or your preferred BLE central).

Task 3 – Debug DMM Performance Issues

If something is not functioning properly or if you are seeing unexpected radio performance issues there is most likely a scheduling collision between the two radio protocols. One powerful tool for debugging the radio functionality is to route the RF core PA/LNA signals to GPIO pins so that they can be observed. These signals are toggled as the radio goes in and out of transmission/receiver mode. Observing these signals together with the antenna switching signal can help debugging as it shows when the radio switching between the two protocols and when the radio is active.

Note

While being able to use the antenna switching signal to determine which radio protocol is active when combining 2.4 GHz and Sub-1 Ghz protocols, this is not possible when using the DMM to combine two protocols sharing the same RF path.

It is however still possible to observe the PA and LNA signals to trace radio activity.

The chapter "Routing doorbell signals to GPIO pins" in the Proprietary RF User's Guide goes over how to route the RF core signals to GPIO pins to makes them available for observation.

In this lab, you can do the routing in the rfCollector.c file by doing the following modifications.


/***** Includes *****/
#include <ti/drivers/pin/PINCC26XX.h>
...

/***** Variable declarations *****/
static PIN_Handle pinHandle;
static PIN_State pinState;

/* PIN configuration */
PIN_Config heartbeatLedPinTable[] = {
    CC1352R1_LAUNCHXL_PIN_RLED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,

    // =========== EDIT START ===========

    /* Add two additional  pins to the pin configuration table. This makes
       sure the pins we use for the LNA/PA signals is not used any other
       part of the application */

    CC1352R1_LAUNCHXL_DIO21 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    CC1352R1_LAUNCHXL_DIO22 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,

    // ============ EDIT END ============
 PIN_TERMINATE
};

...

/*
 *  ======== processingThread ========
 */
void *processingThread(void *arg0) {
    Display_Handle  displayHandle;
    PIN_Handle heartbeatLedPinHandle;
    PIN_State heartbeatLedPinState;

    /* Open the PIN handle */
    heartbeatLedPinHandle = PIN_open(&heartbeatLedPinState, heartbeatLedPinTable);
    if (NULL == heartbeatLedPinHandle) {
        /* Error opening the PIN driver */
        while(1) {};
    }

    // =========== EDIT START ===========
    // Map RFC_GPO0 to IO 21
    PINCC26XX_setMux(heartbeatLedPinHandle, CC1352R1_LAUNCHXL_DIO21, PINCC26XX_MUX_RFC_GPO0);
    // Map RFC_GPO0 to IO 22
    PINCC26XX_setMux(heartbeatLedPinHandle, CC1352R1_LAUNCHXL_DIO22, PINCC26XX_MUX_RFC_GPO1);
    // ============ EDIT END ============

rfCollector.c - Routing RF Core PA and LNA signals

Assuming these modifications are done, the following signals should be available for debugging:

  • Radio PA Enable (TX) on DIO22
  • Radio LNA Enable (RX) on DIO21
  • Antenna switch direction on DIO30

The antenna switch direction pin will be LOW when the 2.4 GHz path is selected and HIGH when the Sub-1 GHz path is selected.

A showcase along with a brief description of what can be seen using these from these signals can be seen below. Pay attention to the scale in each picture since it is not constant.

The first image shows the radio activity during a full beacon interval when connected to one Sensor while also sending BLE advertisements. The first thing you see in this image, marked by yellow, is a beacon follow by a join request from the sensor. Marked with green is the typical activity seen when performing BLE advertisements on channel 37-39. Since the device is advertising scannable, the device goes into RX immdediately after sending each advertisement packet in order to listen for scan requests.

A closer look around the beacon and join request, we can also find the first packet sent by the sensor following the join procedure. This is marked with a cyan color in the picture and is can be easily identified by looking at the antenna switch signal and that the collector starts with an RX rather then a TX as in the beacon case.

The following image shows the radio activity of the DMM device when a BLE central connects to the DMM device. In contrast to the first image, here there is a lot of data being both transmitted and received during a smaller interval. If this gets disrupted, there is a high chance for the BLE connection to fail.

While all the images above shows a well-functioning DMM use case, the information we could extract from the logic traces can prove helpful when this is not the case.

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.