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 Bluetooth® LE (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 Bluetooth LE 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
Other SimpleLink Academy Labs
- Completion of DMM Fundamentals
- Completion of BLE Fundamentals
- Completion of BLE Connections
Hardware
For the DMM device, 1x LaunchPad of either:
- SimpleLink CC1352R1 LaunchPad,
- SimpleLink CC1352P LaunchPad,
- SimpleLink CC1352P7 LaunchPad,
- SimpleLink CC1352R LaunchPad SensorTag Kit, or
- Any other Sub-1 GHz & BLE DMM-capable LaunchPad variant
For the Proprietary "echo" device, 1x Sub-1 GHz LaunchPad
A smart phone with a Bluetooth LE explorer app
Software
- Code Composer Studio (latest version) with support for CC13xx/CC26xx devices
- SimpleLink CC13xx / CC26xx SDK (latest version)
- If the Proprietary echo device is a CC1310 or CC1350, then
- SimpleLink CC13x0 SDK (latest version)
Recommended reading
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>/prop_rf/
rfEchoTx
, found under<SDK>/examples/rtos/<BOARD>/prop_rf/
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 Bluetooth LE 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 itble/
. We will put all BLE specific application files in this folder, which means move all existing files in theApplication/
folder intoble/
.Add a new folder inside the
Application/
folder and name itprop/
. We will put allrfEchoRx
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:
Navigate to
Project Properties
→Build
→ARM Compiler
→Include Options
.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/dmm
We need to add the USE_DMM
define in order to enable DMM for TI source code:
Navigate to
Project Properties
→Build
→ARM Compiler
→Predefined Symbols
.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 instance is the BLE + 15.4-Stack combination, which we can re-use for our purpose by replacing 15.4 with Proprietary stack.
Copy (do not link) over the following files from <SDK>/source/ti/dmm/
to
dmm/
:
dmm_priority_ble_154sensor.c
dmm_priority_ble_154sensor.h
Rename both files to dmm_priority_ble_prop
, and rename all instances of
154
to PROP
and 154Sensor
to prop
in the source code.
In addition, we will replace the existing RF activities for 15.4 with Prop.
Which activities that should replace the 15.4 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 15.4 (named
activityPROP_bleLpropH
after the 154 to PROP and 154Sensor to prop replacements)
but we need need to adjust the activities to match the new activities defined
above. We also need to change the DMMPolicy_StackRole_prop
(previously
DMMPolicy_StackRole_154Sensor
before the 154Sensor
to prop
replacement)
enum to DMMPolicy_StackRole_custom1
. 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 Bluetooth LE connections. For now we will leave this as is and revisit it again later in this lab.
To further improve the final result, the Bluetooth LE 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);
BLE_LOG_INT_INT(0, BLE_LOG_MODULE_APP, "APP : ---- call GapAdv_create set=%d,%d\n", 1, 0);
/* 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);
BLE_LOG_INT_TIME(0, BLE_LOG_MODULE_APP, "APP : ---- GapAdv_enable", 0);
// 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)
{
#if 0
GapAdv_disable(advHandleLongRange);
#endif
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);
#if 0
GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
#endif
autoConnect = AUTOCONNECT_GROUP_A;
}
Display_printf(dispHandle, SP_ROW_AC, 0, "AutoConnect enabled: Group A");
}
else if (index == 2)
{
if (autoConnect != AUTOCONNECT_GROUP_B)
{
#if 0
GapAdv_disable(advHandleLongRange);
#endif
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);
#if 0
GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
#endif
autoConnect = AUTOCONNECT_GROUP_B;
}
Display_printf(dispHandle, SP_ROW_AC, 0, "AutoConnect enabled: Group B");
}
else
{
if (autoConnect)
{
#if 0
GapAdv_disable(advHandleLongRange);
GapAdv_disable(advHandleLegacy);
#endif
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);
#if 0
GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
#endif
autoConnect = AUTOCONNECT_DISABLE;
}
Display_printf(dispHandle, SP_ROW_AC, 0, "AutoConnect disabled");
}
tbm_goTo(&spMenuMain);
return (true);
}
simple_peripheral.c – SimplePeripheral_doAutoConnect()
Go through simple_peripheral.c
and comment out all references to advHandleLongRange
.
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 Bluetooth LE. 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 Bluetooth LE 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 insidedmm_priority_ble_prop.h
- Initialize and open up the DMM Scheduler
- Extract the Bluetooth LE 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 Bluetooth LE application to update the application state when appropriate. The Bluetooth LE 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
Bluetooth LE 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
folder and replace it with a copied version from
<SDK>/source/ti/ble5stack/icall/app
. This to avoid interfering with
other Bluetooth LE 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\,
.advReportIncChannel = ADV_RPT_INC_CHANNEL
};
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.h – SimplePeripheral_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:
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 therfEchoRx
main file). The function should be implemented belowmainThread
:#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()
Modify the
main()
function to call the newly "create task" function. Use the returned Task handle pointer fromrfEchoRx_createRadioTask()
to register the Proprietary example as a DMM client, and set the initial DMM application state for the Proprietary Collector toDMMPOLICY_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.c –
main()
modificationsPOSIX 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.
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
underProprietary (779 - 930 MHz)
. Then expand through the settings group.If you get the following error, navigate to RF Design and enable the relevant frequency band.
Navigate back to the Custom RF Stack. In the settings group, navigate to
PHY Properties
→TX Power (dBm)
and select some lower TX power.In the settings group, navigate to
Code Export Configuration
→Symbol Name Generation Method
and selectCustom
.The default frequency used can vary between devices. Double-check that you are using the same frequency in your rfPacketTx example and your DMM enabled example!
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
andCONFIG_GPIO_GLED
. Then connect each new GPIO to the corresponding hardware,LaunchPad LED Red
orLaunchPad LED Green
.
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:
scheduleParams.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 Bluetooth LE 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 Bluetooth LE to
push through in critical situations (for example, "about to lose the connection").
Lower priority Bluetooth LE connection
Even if the Bluetooth LE 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 Bluetooth LE 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 activity).
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, 220),
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, 230),
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, 230),
};
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, 230),
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, 230),
};
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 Bluetooth LE. 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 Bluetooth LE advertisement (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 also 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 Bluetooth LE
application or the proprietary 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 Bluetooth LE 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 suitable 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.
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.